-
Notifications
You must be signed in to change notification settings - Fork 1
/
CuZenWindows.ahk
838 lines (772 loc) · 43.1 KB
/
CuZenWindows.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
/*
d8888 888 888 88888888888 .d88888b. 8888888888 Y88b d88P 8888888888 .d8888b.
d88888 888 888 888 d88P" "Y88b 888 Y88b d88P 888 d88P Y88b
d88P888 888 888 888 888 888 888 Y88o88P 888 888 888
d88P 888 888 888 888 888 888 8888888 Y888P 8888888 888
d88P 888 888 888 888 888 888 888 d888b 888 888
d88P 888 888 888 888 888 888 888 d88888b 888 888 888
d8888888888 Y88b. .d88P 888 Y88b. .d88P 888 d88P Y88b 888 Y88b d88P
d88P 888 "Y88888P" 888 "Y88888P" 8888888888 d88P Y88b 8888888888 "Y8888P"
*/
{
/*
GitHub: https://github.com/cy-gh/AHK_ZenWindows/
License: GNU General Public License v3.0
https://www.gnu.org/licenses/gpl-3.0.en.html
do not be surprised by the gigantic ASCII figlets, they're just for VSCode minimap
generated by https://textart.io/figlet
*/
#SingleInstance, Force ; Off, Force
#KeyHistory, 0
#MaxThreadsPerHotkey, 1 ; no re-entrant hotkey handling
#Warn All ; like perl's -w -t or php's error_reporting(E_ALL);
#NoEnv ; do not check for environment variables, recommended for performance and compatibility with future AutoHotkey releases
SendMode Input ; forces Send and SendRaw to use SendInput buffering for speed
SetWorkingDir %A_ScriptDir% ; ensures a consistent starting directory
SetBatchLines, -1
ListLines, Off
DetectHiddenWindows On
SetTitleMatchMode 2 ; partial match, 1: ? - 2: partial - 3: exact
SplitPath, A_ScriptName, , , , _tmp_this_script_name ; required
#WinActivateForce
; only necessary for $log/$dbg/$verbose messages
#Include <cCuLogger>
#Include <Functions>
#Include %A_ScriptDir%\Neutron.ahk
global oLogger := new cCuLogger({ level: cCuLogger.LOG_LEVEL_NORMAL, out2sys: true, autoflush: true, minmem: true, timestamp: false, quoteleft: "{", quoteright: "}", cleardebugview: true })
global APP_NAME := _tmp_this_script_name ; used for .INI file name, temp DLL file name, etc.
global APP_TITLE:= "Zen Windows" ; window title
; this DLL must be installed before instantiating the cZenWindows class
if (!FileExist(A_ScriptDir "\" APP_NAME ".dll") && !FileExist(A_Temp "\" APP_NAME)) {
FileCreateDir, % A_Temp "\" APP_NAME
FileInstall, .\CuZenWindows.dll, % A_Temp "\" APP_NAME "\" APP_NAME ".dll", 1 ; the first parameter in this line may NOT contain a variable - change it manually if needed
}
global oZW := new cZenWindows()
; alternative instantiation
; global oZW := new cZenWindows({ UpdateFreq: 5000, Transparency: 150 }) ; parameters here always override the INI values
return
}
/*
888 888 .d88888b. 88888888888 888 d8P 8888888888 Y88b d88P .d8888b.
888 888 d88P" "Y88b 888 888 d8P 888 Y88b d88P d88P Y88b
888 888 888 888 888 888 d8P 888 Y88o88P Y88b.
8888888888 888 888 888 888d88K 8888888 Y888P "Y888b.
888 888 888 888 888 8888888b 888 888 "Y88b.
888 888 888 888 888 888 Y88b 888 888 "888
888 888 Y88b. .d88P 888 888 Y88b 888 888 Y88b d88P
888 888 "Y88888P" 888 888 Y88b 8888888888 888 "Y8888P"
*/
{
; can't load a DLL dynamically via this method as suggested by Neutron.ahk, we need to install a DLL the old-school way
; on a side note, I could extend Neutron to load the DLLs dynamically as well
; but I don't want the basic functionality - which requires the DLL - to be dependent on the GUI/Neutron parts
; so that I can use cZenWindows in my other already existing scripts which don't use Neutron
;FileInstall, CuZenWindows.dll, CuZenWindows.dll
FileInstall, CuZenWindows.ico, CuZenWindows.ico
FileInstall, CuZenWindows.png, CuZenWindows.png
FileInstall, CuZenWindowsSettings.html, CuZenWindowsSettings.html
FileInstall, materialize.min.css, materialize.min.css
FileInstall, materialize.min.js, materialize.min.js
; one can also instantiate the class and bind the hotkey like this
; but then since the instance is not a global variable the "ZenWindowsClose" cannot be called anymore
; #z::new cZenWindows({ UpdateFreq: 5000, Transparency: 150 }).Toggle()
; #z::oZW.Toggle()
; ^#z::oZW.SettingsHandler()
#^q::Reload ; TODO - DELETE
#SC029::oZW.GetPropertiesOfAllWindows(false,true,true,true,true) ; TODO - DELETE
; #Tab::oZW.ShowInfoforWindowUnderMouse() ; TODO - DELETE
}
/*
8888888888P 8888888888 888b 888 888 888 8888888 888b 888 8888888b. .d88888b. 888 888 .d8888b.
d88P 888 8888b 888 888 o 888 888 8888b 888 888 "Y88b d88P" "Y88b 888 o 888 d88P Y88b
d88P 888 88888b 888 888 d8b 888 888 88888b 888 888 888 888 888 888 d8b 888 Y88b.
d88P 8888888 888Y88b 888 888 d888b 888 888 888Y88b 888 888 888 888 888 888 d888b 888 "Y888b.
d88P 888 888 Y88b888 888d88888b888 888 888 Y88b888 888 888 888 888 888d88888b888 "Y88b.
d88P 888 888 Y88888 88888P Y88888 888 888 Y88888 888 888 888 888 88888P Y88888 "888
d88P 888 888 Y8888 8888P Y8888 888 888 Y8888 888 .d88P Y88b. .d88P 8888P Y8888 Y88b d88P
d8888888888 8888888888 888 Y888 888P Y888 8888888 888 Y888 8888888P" "Y88888P" 888P Y888 "Y8888P"
*/
; see the this.neutron.Gui("+LabelZenWindows") in cZenWindowsGUI.__New()
; AHK calls this label automatically
ZenWindowsClose() {
oZW.CloseSettingsGUI()
return
}
class cZenWindows {
static instance
__New(pOptions := false) {
$dbg(A_ThisFunc, "-STARTED")
if (cZenWindows.instance) {
return cZenWindows.instance
}
this.opt := {}
this.fnShortcutToggle := ObjBindMethod(this, "Toggle")
this.fnShortcutSettings := ObjBindMethod(this, "SettingsHandler")
this.fnChangeTransparencyInc := ObjBindMethod(this, "ChangeTransparency", +255/5)
this.fnChangeTransparencyDec := ObjBindMethod(this, "ChangeTransparency", -255/5)
this.fnTimer := ObjBindMethod(this, "Tick")
this.timer_active := false
this.my_handle := A_ScriptHwnd + (0x1000 << 32) ; get this script's handle for registering desktop switch events
this.sIniFileGlobal := A_ScriptDir "\" APP_NAME ".ini"
; this is needed for the VDesk interface, it's a AHK-DLL wrapper for the Windows COM interface
;this.hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", A_ScriptDir . "\CuWinMan.dll", "Ptr")
if (FileExist(A_ScriptDir "\" APP_NAME ".dll")) {
this.hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", A_ScriptDir "\" APP_NAME ".dll", "Ptr")
} else if (FileExist(A_Temp "\" APP_NAME "\" APP_NAME ".dll")) {
this.hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", A_Temp "\" APP_NAME "\" APP_NAME ".dll", "Ptr")
} else {
MsgBox,, Severe Error, Virtual Desktop Helper DLL cannot be loaded
return
}
this.GetDesktopCountProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "GetDesktopCount", "Ptr")
this.GetCurrentDesktopNumberProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "GetCurrentDesktopNumber", "Ptr")
this.IsWindowOnDesktopNumberProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "IsWindowOnDesktopNumber", "Ptr")
this.IsPinnedWindowProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "IsPinnedWindow", "Ptr")
this.IsPinnedAppProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "IsPinnedApp", "Ptr")
this.RegisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "RegisterPostMessageHook", "Ptr")
this.UnregisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", this.hVirtualDesktopAccessor, "AStr", "UnregisterPostMessageHook", "Ptr")
this.current_vdesk := this.GetCurrentDesktopNumber() ; this line must be after the DLL calls above
this.current_vdesk := this.current_vdesk ? this.current_vdesk : 0
this.prev_vdesk := this.current_vdesk
this.SetupTrayMenu()
this.ReadSettings(pOptions)
this.RegisterAllHotkeys()
if (this.opt.RunAsAdmin && (A_Args.Length() = 0 || StringUpper(A_Args[1]) != "admin")) {
;this.cUtil.ShowError("Restarting as admin")
this.RestartAsAdminHandler()
}
cZenWindows.instance := this
this.cUtil.ShowToast("See tray menu icon for options")
if (!FileExist(this.sIniFileGlobal)) {
this.SettingsHandler()
}
$dbg(A_ThisFunc, "-FINISHED")
}
GetInstance() {
return new cZenWindows()
}
SetupTrayMenu() {
_fnSettingsHandler:= ObjBindMethod(this, "SettingsHandler")
_fnReload := ObjBindMethod(this, "ReloadHandler")
_fnExit := ObjBindMethod(this, "ExitHandler")
Menu, Tray, NoStandard
Menu, Tray, Add, &Settings, % _fnSettingsHandler
Menu, Tray, Add, &Reload, % _fnReload
Menu, Tray, Add, E&xit, % _fnExit
Menu, Tray, Default, &Settings
}
IniRead(Key := "", Default := "") {
IniRead, _val, % this.sIniFileGlobal, % APP_NAME, % Key, % Default
Return _val
}
IniWrite(Key, Value) {
IniWrite, % Value, % this.sIniFileGlobal, % APP_NAME, % Key
}
ReadSettings(pOptions) {
$dbg($dump(pOptions, A_ThisFunc, false))
; programatically given parameters always override ini values
this.opt := {}
this.opt.ShortcutToggle := pOptions.HasKey("ShortcutToggle") ? pOptions.ShortcutToggle : this.IniRead("ShortcutToggle", "Win-Z")
this.opt.ShortcutSettings := pOptions.HasKey("ShortcutSettings") ? pOptions.ShortcutSettings : this.IniRead("ShortcutSettings", "Ctrl-Win-Z")
this.opt.KeepFocusedOpaque := pOptions.HasKey("KeepFocusedOpaque") ? pOptions.KeepFocusedOpaque : this.IniRead("KeepFocusedOpaque", true)
this.opt.MoveZenFocusToActivatedWindow := pOptions.HasKey("MoveZenFocusToActivatedWindow") ? pOptions.MoveZenFocusToActivatedWindow : this.IniRead("MoveZenFocusToActivatedWindow", true)
this.opt.AutoActivateWindowAfterToggle := pOptions.HasKey("AutoActivateWindowAfterToggle") ? pOptions.AutoActivateWindowAfterToggle : this.IniRead("AutoActivateWindowAfterToggle", false)
this.opt.RunAsAdmin := pOptions.HasKey("RunAsAdmin") ? pOptions.RunAsAdmin : this.IniRead("RunAsAdmin", false)
this.opt.UseMouseScrollForTransparency := pOptions.HasKey("UseMouseScrollForTransparency") ? pOptions.UseMouseScrollForTransparency : this.IniRead("UseMouseScrollForTransparency", true)
this.opt.MouseScrollModifier := pOptions.HasKey("MouseScrollModifier") ? pOptions.MouseScrollModifier : this.IniRead("MouseScrollModifier", "Win-Ctrl")
this.opt.UpdateFreq := pOptions.HasKey("UpdateFreq") ? pOptions.UpdateFreq : this.IniRead("UpdateFreq", 100)
this.opt.Transparency := pOptions.HasKey("Transparency") ? pOptions.Transparency : this.IniRead("Transparency", 15)
$dbg($dump(this.opt, A_ThisFunc, false))
}
SaveSettings() {
$log(A_ThisFunc)
this.IniWrite("ShortcutToggle", this.cUtil.TranslateHotkey(this.opt.ShortcutToggle))
this.IniWrite("ShortcutSettings", this.cUtil.TranslateHotkey(this.opt.ShortcutSettings))
this.IniWrite("KeepFocusedOpaque", this.opt.KeepFocusedOpaque)
this.IniWrite("MoveZenFocusToActivatedWindow", this.opt.MoveZenFocusToActivatedWindow)
this.IniWrite("AutoActivateWindowAfterToggle", this.opt.AutoActivateWindowAfterToggle)
this.IniWrite("RunAsAdmin", this.opt.RunAsAdmin)
this.IniWrite("UseMouseScrollForTransparency", this.opt.UseMouseScrollForTransparency)
this.IniWrite("MouseScrollModifier", this.opt.MouseScrollModifier)
this.IniWrite("UpdateFreq", this.opt.UpdateFreq)
this.IniWrite("Transparency", this.opt.Transparency)
}
GetCurrentSettings() {
return this.opt
}
SetCurrentSettings(pOptions) {
_prev_asadmin := this.opt.RunAsAdmin
for _my_key, _my_val in this.opt {
_user_key := "opt." _my_key
_user_val := pOptions[_user_key]
if (RegExMatch(_user_key, "(KeepFocusedOpaque|MoveZenFocusToActivatedWindow|UseMouseScrollForTransparency|AutoActivateWindowAfterToggle|RunAsAdmin)")) {
this.opt[_my_key] := _user_val = "on" ? 1 : 0
} else if (RegExMatch(_user_key, "(ShortcutToggle|ShortcutSettings|MouseScrollModifier)")) {
; this.opt[_my_key] := this.cUtil.TranslateToAHKHotkey(this.opt["ShortcutToggle"]) ; nah, keeping the originals is better
this.opt[_my_key] := _user_val
} else {
this.opt[_my_key] := _user_val
}
}
$dbg($dump(this.opt, A_ThisFunc, false))
; register/check shortcuts
try {
this.RegisterAllHotkeys()
this.SaveSettings()
} catch e {
this.cUtil.ShowError(e.what, e.title)
}
if (this.timer_active) {
this.Stop()
Sleep(this.frequency + 1)
this.Start()
}
if (_prev_asadmin != this.opt.RunAsAdmin) {
this.RestartAsAdminHandler()
}
;try {
; this.RegisterHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["ShortcutToggle"]), this.fnShortcutToggle)
; this.RegisterHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["ShortcutSettings"]), this.fnShortcutSettings)
; this.RegisterHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["MouseScrollModifier"] "-WheelUp"), this.fnChangeTransparencyInc)
; this.RegisterHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["MouseScrollModifier"] "-WheelDown"), this.fnChangeTransparencyDec)
; this.SaveSettings()
;} catch e {
; this.cUtil.ShowError(e.what, e.title)
;}
}
RegisterAllHotkeys() {
; register/check shortcuts - bubble up any exception to the parent
this.RegisterSingleHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["ShortcutToggle"]), this.fnShortcutToggle)
this.RegisterSingleHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["ShortcutSettings"]), this.fnShortcutSettings)
this.RegisterSingleHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["MouseScrollModifier"] "-WheelUp"), this.fnChangeTransparencyInc, this.opt.UseMouseScrollForTransparency ? "On" : "Off")
this.RegisterSingleHotkey(this.cUtil.TranslateToAHKHotkey(this.opt["MouseScrollModifier"] "-WheelDown"), this.fnChangeTransparencyDec, this.opt.UseMouseScrollForTransparency ? "On" : "Off")
}
RegisterSingleHotkey(pHotkey, pBoundMethod, pState := "On") {
; register a single hotkey - might throw an exception
$dbg(A_ThisFunc, pHotkey, pBoundMethod, IsObject(pBoundMethod))
Hotkey(pHotkey, pBoundMethod, "UseErrorLevel " pState)
if (ErrorLevel) {
_title := "Error: " ErrorLevel " - HotKey register error"
_message := "Hotkey: " pHotkey "`nError: " ErrorLevel "`n"
if (ErrorLevel = 1) {
_message .= "Nonexistent label"
} else if (ErrorLevel = 2) {
_message .= "Unsupported/unrecognized keys"
} else if (ErrorLevel = 3) {
_message .= "Unsupported prefix key"
} else if (ErrorLevel = 4) {
_message .= "Keyname not suitable for AltTab/ShiftAltTab"
} else if (ErrorLevel = 5) {
_message .= "Attempted to modify nonexistent hotkey"
} else if (ErrorLevel = 6) {
_message .= "Attempted to modify nonexistent variant of existing hotkey"
} else if (ErrorLevel = 98) {
_message .= "Would exceed limit of hotkeys per script"
} else if (ErrorLevel = 99) {
_message .= "Out of memory"
}
throw { what: _message, title: _title }
}
}
GUI[] {
get {
return this.wSettingsGUI
}
}
CloseSettingsGUI() {
this.wSettingsGUI.Close()
this.wSettingsGUI := false
dllcall("psapi.dll\EmptyWorkingSet", "UInt", -1)
}
Toggle() {
$dbg(A_ThisFunc, "Timer active: " this.timer_active)
if (!this.timer_active) {
this.Start()
} else {
this.Stop()
}
}
Start() {
$dbg(A_ThisFunc, "Current values: " Round(100 * this.opt.Transparency / 255) "% every " this.opt.UpdateFreq "ms")
this.RegisterVDeskEvents()
this.timer_active := true
this.original_focused_handle := WinExist("A")
SetTimer(this.fnTimer, - this.opt.UpdateFreq)
}
Stop() {
$dbg(A_ThisFunc)
this.timer_active := false
SetTimer(this.fnTimer, "Off")
this.UnregisterVDeskEvents()
; activate the window under the mouse - avoids redraw problems
if (this.opt.AutoActivateWindowAfterToggle) {
$verbose($dump(this.opt, A_ThisFunc, false))
MouseGetPos, , , _handle_under ; get window handle under the mouse
WinActivate, % "ahk_id " _handle_under
}
; make all windows across all VDesks opaque again
_list_windows := this.GetPropertiesOfAllWindows(false)
for _handle, _obj in _list_windows {
this.SetTransparency(_handle, 255)
}
this.LastX := this.LastY := this.LastActive := this.LastUnder := false
}
Tick() {
$verbose(A_ThisFunc "--STARTED")
if (!this.timer_active) {
return
}
CoordMode, Mouse, Screen
MouseGetPos, _mx, _my, _handle_under ; get window handle under the mouse
;PixelGetColor, _transRGB, % _mx , % _my, RGB
;_handle_active := WinExist("A")
if (this.opt.MoveZenFocusToActivatedWindow) {
_handle_active := WinExist("A")
} else {
_handle_active := this.original_focused_handle
}
$dbg("KeepFocused: " this.opt.KeepFocusedOpaque, "MoveFocus: " this.opt.MoveZenFocusToActivatedWindow, "Mouse X.Y: " _mx "." _my, "Active: " _handle_active, "ActTitle: " WinGetTitle("ahk_id " _handle_active), "Under: " _handle_under, "UndTitle: " WinGetTitle("ahk_id " _handle_under), "UndClass: " WinGetClass("ahk_id " _handle_under) )
/*
case 0: Alt-Tab window is active, do not change any transparency
if mouse did NOT move:
- the active window is the same & the window under is the same --> 1. nothing to do, quit early
- the active window is the same & the window under is NOT the same --> 2. full recalculate (even though it seems unlikely it can happen via dialog windows, VDesk change, resizing of obscured windows etc)
- the active window is NOT the same & the window under is the same --> 3. no need to repaint the window under, check only KeepFocusedOpaque
- the active window is NOT the same & the window under is NOT the same --> 4. repaint the windows under (the old one and new one) & check KeepFocusedOpaque
if mouse did move:
- the active window is the same & the window under is the same --> 5. nothing to do, quit early
- the active window is the same & the window under is NOT the same --> 6. full recalculate
- the active window is NOT the same & the window under is the same --> 7. no need to repaint the window under, check only KeepFocusedOpaque
- the active window is NOT the same & the window under is NOT the same --> 8. full recalculate
*/
_handle_alttab := DllCall("FindWindow", "Str", "MultitaskingViewFrame", "Ptr", 0, "Ptr")
if (_handle_active = _handle_alttab) {
; do not meddle with transparency if Alt-Tab window is active
; nothing to do
} else if (_mx = this.LastX && _my = this.LastY && _handle_active = this.LastActive && _handle_under = this.LastUnder) {
} else if (_handle_active = this.LastActive && _handle_under = this.LastUnder && this.current_vdesk = this.prev_vdesk) {
; case 1 & 5 - saves a lot of CPU cycles
; $log("Case 1 & 5")
;} else if (_handle_active != this.LastActive && _handle_under = this.LastUnder) {
; ; case 3 & 7
; $log("Case 3 & 7")
; if (this.opt.KeepFocusedOpaque) {
; this.SetTransparency(_handle_active, 255)
; }
; this.SetTransparency(this.LastActive, this.opt.Transparency)
;} else if (_mx = this.LastX && _my = this.LastY && _handle_active != this.LastActive && _handle_under != this.LastUnder) {
; ; case 4 - two overlapping windows under the mouse and the user changed the active window via Alt-Tab
; $log("Case 4")
; if (this.opt.KeepFocusedOpaque) {
; this.SetTransparency(_handle_active, 255)
; }
; this.SetTransparency(this.LastActive, this.opt.Transparency)
} else {
$dbg("Everything else")
_visible_windows := this.GetPropertiesOfAllWindows(true)
for _whnd, _obj in _visible_windows {
; the "this.timer_active ? this.opt.Transparency : 255" check below helps against race-conditions!
; if Tick has just started but the script gets a Stop() request, the Stop() will be immediately executed first before Tick has the chance to stop
; and when that happens, Stop() makes all windows opaque, but then this Tick() method resets the transparency back again
; _transparency := _handle_under = _whnd ? 255 : (this.timer_active ? this.opt.Transparency : 255)
if (_whnd = _handle_under || (_whnd = _handle_active && this.opt.KeepFocusedOpaque) ) {
_transparency := 255
} else if (this.timer_active) {
_transparency := this.opt.Transparency
} else {
_transparency := 255
}
$dbg(A_ThisFunc, StringLower(WinGetProcessName("ahk_id " _whnd)), WinGetClass("ahk_id " _whnd), _transparency, _wx, _wy, _ww, _wh, _transRGB)
this.SetTransparency(_whnd, _transparency)
}
}
this.LastX := _mx
, this.LastY := _my
, this.LastActive := _handle_active
, this.LastUnder := _handle_under
if (this.timer_active) {
$verbose(A_ThisFunc, "Timer still active")
SetTimer(this.fnTimer, - this.opt.UpdateFreq)
}
$verbose(A_ThisFunc "--FINISHED")
}
SetTransparency(pHandle, pTransLevel) {
WinSetTransparent(pTransLevel, "ahk_id " pHandle)
if (pTransLevel = 255) {
WinSetTransparent("Off", "ahk_id " pHandle)
}
}
; copied from another script of mine - style might not match :)
ChangeTransparency(pStep) {
$dbg(A_ThisFunc, pStep)
MIN_TRANSPARENCY_ALLOWED := Round(255/5)
MouseGetPos, _mx, _my, _handle
PixelGetColor, _transRGB, % _mx , % _my, RGB
$dbg(A_ThisFunc " - handle: " _handle ", pStep: " pStep ", _transRGB: " _transRGB)
WinGetClass, _win_class, % "ahk_id " _handle
if(_win_class = "Progman") {
$dbg(A_ThisFunc " - skipping desktop")
return
}
WinGet, _old_level, Transparent, % "ahk_id " _handle
WinGet, _trans_color, TransColor, % "ahk_id " _handle
if(!_old_level || _old_level = Off) {
$dbg(A_ThisFunc " - Old value (" _old_level ") not found or invalid, setting to 255")
_old_level := 255
}
if(!_trans_color) {
$dbg(A_ThisFunc " - Old transparency color(" _trans_color ") invalid, using " _transRGB)
_trans_color := _transRGB
}
_new_level := _old_level + pStep
_new_level := Round(_new_level)
$dbg(A_ThisFunc " - New level: " _new_level)
if(_new_level > 255) {
$dbg(A_ThisFunc " - New transparency(" _new_level ") too big, setting to 255")
_new_level := "255"
} else if(_new_level <= MIN_TRANSPARENCY_ALLOWED) {
$dbg("New transparency(" _new_level ") too small, setting to " MIN_TRANSPARENCY_ALLOWED)
_new_level := MIN_TRANSPARENCY_ALLOWED
}
$dbg(A_ThisFunc " - before change - color: " _trans_color ", pStep: " pStep ", old: " _old_level ", new: " _new_level)
ToolTip, % "Transparency: " Round(_new_level * 100 / 255) "%"
_removefn := ObjBindMethod(this, "RemoveToolTip")
SetTimer, % _removefn, -1000
WinSet, TransColor, % _transRGB, "ahk_id " _handle
if(_new_level = 255) {
WinSet, Transparent, Off, % "ahk_id " _handle
} else {
WinSet, Transparent, % _new_level, % "ahk_id " _handle
}
$dbg(A_ThisFunc " - after change - color: " _trans_color ", pStep: " pStep ", old: " _old_level ", new: " _new_level)
}
RemoveToolTip() {
ToolTip
}
RegisterVDeskEvents() {
DllCall(this.UnregisterPostMessageHookProc, "Int", this.my_handle, "Int", 0x1400 + 30)
DllCall(this.RegisterPostMessageHookProc, "Int", this.my_handle, "Int", 0x1400 + 30)
OnMessage(0x1400 + 30, ObjBindMethod(this, "VWMess"))
}
UnregisterVDeskEvents() {
DllCall(this.UnregisterPostMessageHookProc, "Int", this.my_handle, "Int", 0x1400 + 30)
OnMessage(0x1400 + 30, ObjBindMethod(this, "VWMess"))
}
GetPropertiesOfAllWindows(pSkipNotOnCurrentVDesk := true, pSkipGhosts := true, pSkipSystemWindows := true, pSkipCloaked := false) {
/*
pSkipNotOnCurrentVDesk : Windows that are not on the current Virtual Desk
pSkipGhosts : Windows without a VDesk number
pSkipSystemWindows : Desktop, Taskbar, Tray Menu, Start Menu, etc.
pSkipCloaked : Invisible windows, e.g. invisible Windows 10 apps like System Settings, Calc app, Weather app, etc.
To get all windows across all VDesks use: pSkipNotOnCurrentVDesk := false, pSkipGhosts := true, pSkipSystemWindows := true, pSkipCloaked := false
To get windows only on current VDesk use: pSkipNotOnCurrentVDesk := true, pSkipGhosts := true, pSkipSystemWindows := true, pSkipCloaked := false
Alt-Tab activates:
AHK: {WinEventHook} {pEvent: 0x0003 / 3} {pHandle: 65836} {Exe: Explorer.EXE} {Class: ForegroundStaging} {Title: } {pObjectID: 0} {pChildID: 0} {pEventThread: 7760}
AHK: {WinEventHook} {pEvent: 0x0003 / 3} {pHandle: 7734228} {Exe: Explorer.EXE} {Class: MultitaskingViewFrame} {Title: Task Switching} {pObjectID: 0} {pChildID: 0} {pEventThread: 3464}
AHK: {WinEventHook} {pEvent: 0x0003 / 3} {pHandle: 65890} {Exe: Explorer.EXE} {Class: ForegroundStaging} {Title: } {pObjectID: 0} {pChildID: 0} {pEventThread: 3464}
Win-Tab activates:
AHK: {WinEventHook} {pEvent: 0x0003 / 3} {pHandle: 65836} {Exe: Explorer.EXE} {Class: ForegroundStaging} {Title: } {pObjectID: 0} {pChildID: 0} {pEventThread: 7760}
AHK: {WinEventHook} {pEvent: 0x0003 / 3} {pHandle: 131114} {Exe: Explorer.EXE} {Class: Windows.UI.Core.CoreWindow} {Title: Task View} {pObjectID: 0} {pChildID: 0} {pEventThread: 12100}
direct method to get the handle of Alt-Tab window
https://www.autohotkey.com/boards/viewtopic.php?t=11849
hwnd := DllCall("FindWindow", "str", "MultitaskingViewFrame", "ptr", 0, "ptr")
*/
_dhwprev := DetectHiddenWindows(false) ; no hidden windows
, _total_vdesk_num := this.GetNumberOfDesktops()
, _current_vdesk_num := _total_vdesk_num = 1 ? 1 : this.GetCurrentDesktopNumber()
, _windows := {}
;$log(A_ThisFunc, _total_vdesk_num, _current_vdesk_num)
WinGet, _arr, List
DetectHiddenWindows(_dhw_prev)
loop, % _arr {
_handle := _arr%A_Index%
, _windows[_handle] := {}
, _windows[_handle].exe := StringLower(WinGetProcessName("ahk_id " _handle))
, _windows[_handle].class := WinGetClass("ahk_id " _handle)
, _windows[_handle].title := WinGetTitle("ahk_id " _handle)
, _windows[_handle].is_cloaked := this.IsWindowCloaked(_handle)
, _windows[_handle].is_pinned := DllCall(this.IsPinnedWindowProc, "UInt", _handle) = 1 || DllCall(this.IsPinnedAppProc, "UInt", _handle) = 1
if (_windows[_handle].is_pinned) {
_windows[_handle].vdesk := _current_vdesk_num
, _windows[_handle].is_notonvdesk := false
} else {
loop, % _total_vdesk_num {
_desk_num := A_Index - 1
_is_on_vdesk := DllCall(this.IsWindowOnDesktopNumberProc, "UInt", _handle, "UInt", _desk_num)
if (_is_on_vdesk = 1)
_windows[_handle].vdesk := _desk_num
if (_desk_num = _current_vdesk_num)
_windows[_handle].is_notonvdesk := _is_on_vdesk != 1
}
}
WinGetPos, _wx, _wy, _ww, _wh, % "ahk_id " _handle
_windows[_handle].is_ghost := (_total_vdesk_num = 1 && !_windows[_handle].is_cloaked && (_ww > 1 || _wh > 1) || _windows[_handle].vdesk >= 0 && _windows[_handle].vdesk < _total_vdesk_num) ? false : true
if (_windows[_handle].exe = "explorer.exe" && RegExMatch(_windows[_handle].class, "(Progman|Shell_TrayWnd|#32768|MultitaskingViewFrame|ForegroundStaging|Windows.UI.Core.CoreWindow)") ) {
_windows[_handle].is_system := true ; desktop, taskbar, Right-Click menus, Alt-Tab, Win-Tab
} else if (_windows[_handle].exe = "StartMenuExperienceHost.exe" && _windows[_handle].class = "Windows.UI.Core.CoreWindow") {
_windows[_handle].is_system := true ; start menu
} else {
_windows[_handle].is_system := false
}
if (_total_vdesk_num = 1) {
_windows[_handle].vdesk := _current_vdesk_num
_windows[_handle].is_notonvdesk := false
}
;$log(_handle, _windows[_handle].exe, _windows[_handle].class, _wx, _wy, _ww, _wh, "Ghost: " _windows[_handle].is_ghost, "Cloaked: " _windows[_handle].is_cloaked, "System: " _windows[_handle].is_system)
}
;$dump(_windows)
_results := {}
if (!pSkipNotOnCurrentVDesk && !pSkipGhosts && !pSkipSystemWindows && !pSkipCloaked) {
_results := _windows
$dbg("No filtering")
} else {
for _handle, _obj in _windows {
if ( (pSkipNotOnCurrentVDesk && _obj.is_notonvdesk)
|| (pSkipGhosts && _obj.is_ghost)
|| (pSkipSystemWindows && _obj.is_system)
|| (pSkipCloaked && _obj.is_cloaked) ) {
$dbg("Skipping " _windows[_handle].exe "/" _windows[_handle].class " because", "NotOnVDesk: " _obj.is_notonvdesk, "Ghost: " _obj.is_ghost, "SystemWin: " _obj.is_system, "Cloaked: " _obj.is_cloaked)
continue
}
_results[_handle] := _obj
}
}
$verbose($dump(_results, A_ThisFunc, false))
return _results
}
; https://www.autohotkey.com/boards/viewtopic.php?t=40800
IsWindowCloaked(hwnd) {
; Windows of suspended apps and windows on other virtual desktops return 2!
; Resolve dwmapi\DwmGetWindowAttribute once only, for performance.
static gwa := DllCall("GetProcAddress", "ptr", DllCall("LoadLibrary", "str", "dwmapi", "ptr"), "astr", "DwmGetWindowAttribute", "ptr")
; DWMWA_CLOAKED := 14
return (gwa && DllCall(gwa, "ptr", hwnd, "int", 14, "int*", cloaked, "int", 4) = 0) ? cloaked : 0
; Return values:
; 0: not cloaked
; 1: cloaked by the application
; 2: cloaked by the shell
; 4: cloak value inherited from its owner
}
VWMess(wParam, lParam, msg, hwnd) {
Critical
this.OnDesktopSwitch(lParam + 1)
}
OnDesktopSwitch() {
this.prev_vdesk := this.current_vdesk
this.current_vdesk := this.GetCurrentDesktopNumber()
$verbose(A_ThisFunc, "Desktop Switch detected", "Prev: " this.prev_vdesk, "Current: " this.current_vdesk)
_list_windows := this.GetPropertiesOfAllWindows(false)
$verbose($dump(_list_windows, A_ThisFunc, false))
;return
;for _i, _handle in _list_windows {
; _win_exe := StringLower(WinGetProcessName("ahk_id " _handle))
; _win_class := WinGetClass("ahk_id " _handle)
; _win_title := WinGetTitle("ahk_id " _handle)
; $log(A_ThisFunc, _win_exe, _win_class, _win_title)
;}
;;$dump(_list_windows)
}
GetCurrentDesktopNumber() {
return DllCall(this.GetCurrentDesktopNumberProc) ; 0-based
}
GetNumberOfDesktops() {
return DllCall(this.GetDesktopCountProc)
}
SettingsHandler() {
$dbg(A_ThisFunc)
if (!this.wSettingsGUI) {
this.wSettingsGUI := new cZenWindowsGUI(this)
} else {
this.CloseSettingsGUI()
}
}
ReloadHandler() {
Reload
}
RestartAsAdminHandler() {
$dbg(A_ThisFunc)
; Instead, use Run in conjunction with A_AhkPath and A_ScriptFullPath (and A_IsCompiled if the script is ever used in compiled form).
; Also, include the string /restart as the first parameter (i.e. after the name of the executable), which tells the program to use the same behavior as Reload.
if (this.opt.RunAsAdmin) {
if (A_IsCompiled = true) {
$dbg("Compiled Run", A_ScriptFullPath " /restart admin", A_WorkingDir)
Run("*RunAs " A_ScriptFullPath " /restart admin", A_WorkingDir)
} else {
$dbg("Script Run", A_AhkPath " /restart " A_ScriptFullPath " admin", A_WorkingDir)
Run("*RunAs " A_AhkPath " /restart " A_ScriptFullPath " admin", A_WorkingDir)
}
} else {
this.cUtil.ShowError("Please exit the program via tray menu and start again", "Restart required")
}
$dbg(A_ThisFunc, A_IsCompiled, A_AhkPath, A_ScriptFullPath)
}
ExitHandler() {
this.Stop()
this.SaveSettings()
ExitApp
}
/*
Misc utils
Functions copied from my private AHK libraries
*/
class cUtil {
ShowError(pMessage, pTitle := "") {
_title := pTitle ? pTitle : A_ThisFunc
MsgBox("", _title, pMessage)
}
ShowToast(pMessage, pTitle := "", pOptions := 17) {
TrayTip(pTitle ? pTitle : APP_TITLE, pMessage, , pOptions) ; 1+16: info icon + no sound
}
TranslateHotkey(pHotkeyString) {
; translates to readable format, e.g. !e --> Alt-E
_translated := StringUpper(pHotkeyString)
_translated := StrReplace(_translated, "^", "Ctrl-")
_translated := StrReplace(_translated, "!", "Alt-")
_translated := StrReplace(_translated, "+", "Shift-")
_translated := StrReplace(_translated, "#", "Win-")
return _translated
}
TranslateToAHKHotkey(pHumanReadableString) {
_translated := pHumanReadableString
_translated := RegExReplace(_translated, "i)Win-", "#")
_translated := RegExReplace(_translated, "i)Ctrl-", "^")
_translated := RegExReplace(_translated, "i)Alt-", "!")
_translated := RegExReplace(_translated, "i)Shift-", "+")
_translated := StringLower(_translated)
return _translated
}
}
}
ToggleFunction(neutron) {
; kept as a fallback check
$log(A_ThisFunc)
}
/*
8888888888P 8888888888 888b 888 .d8888b. 888 888 8888888
d88P 888 8888b 888 d88P Y88b 888 888 888
d88P 888 88888b 888 888 888 888 888 888
d88P 8888888 888Y88b 888 888 888 888 888
d88P 888 888 Y88b888 888 88888 888 888 888
d88P 888 888 Y88888 888 888 888 888 888
d88P 888 888 Y8888 Y88b d88P Y88b. .d88P 888
d8888888888 8888888888 888 Y888 "Y8888P88 "Y88888P" 8888888
*/
class cZenWindowsGUI extends cZenWindows {
__New(pParent) {
this.zen_parent := pParent
this.neutron := new NeutronWindow("", "", "") ; Create a new NeutronWindow and navigate to our HTML page
this.id := "cZenWindowsGUI--ID" ; just a dummy for debugging
; register Injector for browser events
ComObjConnect(this.neutron.wb, new this.cInjector(this))
; load page
this.neutron.Load("CuZenWindowsSettings.html")
; Use the Gui method to set a custom label prefix for GUI events. This code is
; equivalent to the line `Gui, name:+LabelNeutron` for a normal GUI.
this.neutron.Gui("+LabelZenWindows")
;this.neutron.Gui("-MaximizeBox -Resize")
this.neutron.Gui("-MaximizeBox -MinimizeBox -Resize")
; using the title in new NeutronWindow("", "", "") sets the HTML title, not the Windows window title
WinSetTitle, % "ahk_id " this.neutron.hWnd, % "", % APP_TITLE
oCurrentSettings := this.zen_parent.GetCurrentSettings()
$dbg($dump(oCurrentSettings, A_ThisFunc, false))
for _key, _val in oCurrentSettings {
oHTMLObject := this.neutron.qs("#opt\." _key)
if (oHTMLObject) {
if (_val = 1) {
oHTMLObject.checked := true
} else if (_val = 0) {
oHTMLObject.checked := false
} else {
oHTMLObject.value := StringUpper(_val)
}
$dbg("Found HTML object: " oHTMLObject.value, _key, _val)
} else {
$log("Cannot find HTML object")
}
}
Sleep(150) ; wait just a moment for MaterializeCSS to finish the animation, even if the window is still invisible
; Show the Neutron window
this.neutron.Show("w850 h675 y40") ; TODO - remove y40
}
class cInjector {
/*
helper class to dispatch incoming function calls from JS to the parent class, cZenWindowsGUI
copied from Neutron.ahk so that I can inject my own ZW object into the document
I might be wrong but the standard injected AHK object seems to supports only top level functions, i.e. without a class
but I want the callback methods to be routed via the cZenWindowsGUI class
*/
__New(pParent) {
$dbg(A_ThisFunc, pParent)
this.parent := pParent
}
DocumentComplete(wb) {
$dbg(A_ThisFunc, IsObject(wb))
; Inject this class into the JS scope
wb.document.parentWindow.zw := new this.parent.Dispatch(this.parent)
}
}
class Dispatch {
/*
helper class to dispatch incoming function calls from JS to the parent class, cZenWindowsGUI
copied from Neutron.ahk so that I can inject my own ZW object into the document
I might be wrong but the standard injected AHK object seems to supports only top level functions, i.e. without a class
but I want the callback methods to be routed via the cZenWindowsGUI class
*/
__New(pParent) {
this.parent := pParent
}
__Call(params*) {
/*
all incoming callbacks go through this method
1st parameter is the method name of the ZW object, the rest is the parameters passed to the method
e.g. if in HTML you use: zw.OpenLinkInSystemBrowser("https:....")
we get in params* ["OpenLinkInSystemBrowser", "https"]
and we need to call cZenWindowsGUI.OpenLinkInSystemBrowser("https...")
note that the rest of the parameters can be also JS objects like event
so they might not be always printed out via DebugOutput
*/
fnMethod := params[1]
_params := []
for _i, _k in params {
if (_i = 1)
continue
_params.push(_k)
}
$dbg(A_ThisFunc, "Calling: " fnMethod, _params*)
return ObjBindMethod(this.parent, fnMethod, _params*).Call()
}
}
Close() {
; called when user closes the settings window
$dbg(A_ThisFunc)
ComObjConnect(this.neutron.wb) ; disconnect the COM Object
this.neutron.Destroy() ; destroy the neutron GUI
}
ToggleMe() {
; toggle the transparency effects
$dbg(A_ThisFunc)
this.zen_parent.Toggle()
}
OpenLinkInSystemBrowser(link) {
; open the given URL in system browser instead of the embedded IE window
$dbg(A_ThisFunc, link)
Run, % link
}
GetSetting(pKey) {
; not used at the moment
; if you use id="opt.ShortcutToggle" format in HTML
; the query selectors (e.g. neutron.qs) must use something like 'qs("#opt\.ShortcutToggle")' instead of 'qs("#opt.ShortcutToggle")''
$dbg(A_ThisFunc, pKey)
$dbg($dump(this.zen_parent.GetCurrentSettings(),, false))
_arr := StrSplit(pKey, ".") ; split #opt\\.ShortcutToggle to 2 parts and get the 2nd part
return this.zen_parent.GetCurrentSettings()[_arr[2]]
}
ApplySettings(event) {
; apply the current settings and save to INI file
$dbg(A_ThisFunc, this.neutron.__class)
event.preventDefault()
formData := this.neutron.GetFormData(event.target)
$dbg($dump(formData, A_ThisFunc, false))
this.zen_parent.SetCurrentSettings(formData)
}
}