Detect display configuration change? Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Detect display configuration change?

Post by mmmax » 27 Feb 2023, 20:45

I have multiple displays connected to my PC. Sometimes, either after waking from sleep, switching from one machine to another via KVM switch, or after starting up the PC and logging in, a bunch of apps/windows will get resized and all shifted to one display.

I have a hotkey macro that I can run to move windows back where I want them. But is there some way I can use ahk to check the current display configuration, and then automatically execute the macro when the configuration changes back to 2 displays instead of one (or no displays connected, which is the case when using the KVM switch)?

User avatar
mikeyww
Posts: 26588
Joined: 09 Sep 2014, 18:38

Re: Detect display configuration change?  Topic is solved

Post by mikeyww » 27 Feb 2023, 20:51

Hello,

This may help. :arrow: MonitorGetCountSetTimer

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 24 Mar 2023, 09:40

mikeyww wrote:
27 Feb 2023, 20:51
Hello,

This may help. :arrow: MonitorGetCountSetTimer
Finally got some time to dig into this.

Looks like the example #1 linked at the bottom of the MonitorGetCount page isn't working, reports an error that line 6 does not contain a valid action.

Any idea why?

iPhilip
Posts: 796
Joined: 02 Oct 2013, 12:21

Re: Detect display configuration change?

Post by iPhilip » 24 Mar 2023, 10:07

That example works for me. Are you running AutoHotkey v2?
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Detect display configuration change?

Post by iseahound » 24 Mar 2023, 12:54

You should check for a windows message via OnMessage. Check for 0x7E.

See:
viewtopic.php?t=27582
https://www.autohotkey.com/board/topic/59846-detect-resolution-change/

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 20 Apr 2023, 18:04

Finally got this working consistently after a fair amount of trial and error. Here's the script I'm using in case it might be helpful to anyone else.

Code: Select all

/*
The following watches Windows 10 messages for changes to display configuration, writes observed changes
to a log file on the desktop, and if the monitor count and resolutions match specified parameters,
executes a given function (in my use case, DisplayConfigReset(), which is a function I wrote to move/restore
the size and position of various windows according to my desired workspace layout)
*/
OnMessage 0x007E, WM_DISPLAYCHANGE
Persistent

WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd)
{
	MsgBox("WM_DISPLAYCHANGE!", "", "T5") ; 5 sec delay is sometimes necessary for the displays to load the correct resolution/config
	; sleep 5000 ; if desired, the message box line above can be removed, just uncomment this line instead to add a 5 sec delay
	TimeString := FormatTime(,"yy-MM-dd HH:mm:ss")
	FileAppend "========== WM_DISPLAYCHANGE! " TimeString "`nwParam: " wParam "`nlParam: " lParam "`n", A_Desktop "\DisplayChangeLog.txt"
	MonitorCount := MonitorGetCount()
	MonitorPrimary := MonitorGetPrimary()
	FileAppend "Monitor Count:`t" MonitorCount "`nPrimary Monitor:`t" MonitorPrimary "`n`n", A_Desktop "\DisplayChangeLog.txt"
	i := 1
	resolution1 := "", resolution2 := ""
	Loop MonitorCount
	{
		Try MonitorGet A_Index, &L, &T, &R, &B
		Try MonitorGetWorkArea A_Index, &WL, &WT, &WR, &WB
		resolution%i% := (R-L "x" B-T)
		FileAppend
		(
			"Monitor:`t#" A_Index
			"`nName:`t`t" MonitorGetName(A_Index)
			"`nResolution" i ":`t" R-L "x" B-T
			"`nLeft: `t`t" L " (" WL ")"
			"`nTop: `t`t" T " (" WT ")"
			"`nRight:`t`t" R " (" WR ")"
			"`nBottom:`t`t" B " (" WB ")"
			"`n`n"

			, A_Desktop "\DisplayChangeLog.txt"
		)
		i := i+1
	}
	if (MonitorCount = 2) AND (resolution1 = "3072x1728") AND (resolution2 = "5120x1440")
	{
		FileAppend "+ Parameters match preferred monitor configuration! +`nRunning Display Reset Script...`n`n", A_Desktop "\DisplayChangeLog.txt"
		Sleep 5000

		DisplayConfigReset("") ; triggers hotkey named function in main ahk script to reset window size/position for various apps/windows
	}
	Else
	{
		FileAppend "- Parameters don't match! -`nWaiting for preferred monitor configuration...`n`n", A_Desktop "\DisplayChangeLog.txt"
	}
}
Last edited by mmmax on 21 Apr 2023, 01:43, edited 1 time in total.

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 20 Apr 2023, 18:06

iPhilip wrote:
24 Mar 2023, 10:07
That example works for me. Are you running AutoHotkey v2?
Turns out I wasn't on v2, but have since upgraded, and am now. :)

Taking a while to get used to all the syntax changes, but definitely happy with the changes/improvements.

iPhilip
Posts: 796
Joined: 02 Oct 2013, 12:21

Re: Detect display configuration change?

Post by iPhilip » 20 Apr 2023, 23:58

Great! I am glad you got it working. :thumbup:

Something from your post caught my attention:
mmmax wrote:
20 Apr 2023, 18:04

Code: Select all

	resolution1 := "" && resolution2 := ""
Note that resolution2 will never get initialized because the first part of the AND statement is false. Perhaps you meant for it to be

Code: Select all

	resolution1 := "", resolution2 := ""
Cheers!
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 21 Apr 2023, 01:43

iPhilip wrote:
20 Apr 2023, 23:58
Great! I am glad you got it working. :thumbup:

Something from your post caught my attention:
mmmax wrote:
20 Apr 2023, 18:04

Code: Select all

	resolution1 := "" && resolution2 := ""
Note that resolution2 will never get initialized because the first part of the AND statement is false. Perhaps you meant for it to be

Code: Select all

	resolution1 := "", resolution2 := ""
Cheers!
Oh interesting. Still learning the way to properly do stuff like this in v2. I thought that line was just assigning both resolution1 and resolution2 to "" to initialize the variables. prior to doing that, i was seeing a warning (or error, can't remember) for the variable within the loop.

So I guess using the comma is the right syntax here to separate initializing both variables on the same line, and using && should be reserved for logical conditional statements.

User avatar
mikeyww
Posts: 26588
Joined: 09 Sep 2014, 18:38

Re: Detect display configuration change?

Post by mikeyww » 21 Apr 2023, 05:38

That is correct. The documentation explains these operators. A few quick reads can save you a lot of debugging time.

https://www.autohotkey.com/docs/v2/Variables.htm#comma

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 21 Apr 2023, 16:44

mikeyww wrote:
21 Apr 2023, 05:38
That is correct. The documentation explains these operators. A few quick reads can save you a lot of debugging time.

https://www.autohotkey.com/docs/v2/Variables.htm#comma
Indeed. Although, in this case, I basically inherently knew this, but my brain mixed up working in AHK script vs adding 2 commands on the same line in a Windows batch script.

So I think the real problem here was lack of proper sleep leading to human error. :oops: :headwall:

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 21 Apr 2023, 17:00

Okay. Now I'm trying to take this a step further.

As long as I don't lock my system, everything works fine as I had it written. However, if the PC goes to sleep, or if I lock it and the monitor configuration changes, it seems like the script runs in the background after the display config changes, but before I am able to unlock the PC. This causes the script I am running to fail since none of the windows it is trying to get the title of or resize are visible while the system is locked.

Is there some way I can simultaneously/asynchronously monitor for OnMessage 0x02B1 while the WM_DISPLAYCHANGE() function is stuck in a loop, waiting for a variable that's based on the system lock state, and only continue the displaychange script after the system is unlocked?

Here's as far as I got with trying to make this work, but haven't been able to get it to work successfully thus far.

Code: Select all

DllCall("wtsapi32.dll\WTSRegisterSessionNotification", "ptr", A_ScriptHwnd, "uint", NOTIFY_FOR_ALL_SESSIONS := 1)
OnMessage 0x02B1, MsgMonitor
OnMessage 0x007E, MsgMonitor
Persistent

MsgMonitor(wParam, lParam, msg, hwnd){
	; Since returning quickly is often important, it is better to use ToolTip than
	; something like MsgBox that would prevent the function from finishing:
	ToolTip "Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam
	SetTimer () => ToolTip(), -3000
	if msg = 0x02B1 {
		WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd)
	}
	else if msg = 0x007E {
		WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd)
	}
}

WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd) {
	static notification := Map(
	(Join,
		0x1, "WTS_CONSOLE_CONNECT"
		0x2, "WTS_CONSOLE_DISCONNECT"
		0x3, "WTS_REMOTE_CONNECT"
		0x4, "WTS_REMOTE_DISCONNECT"
		0x5, "WTS_SESSION_LOGON"
		0x6, "WTS_SESSION_LOGOFF"
		0x7, "WTS_SESSION_LOCK"
		0x8, "WTS_SESSION_UNLOCK"
		0x9, "WTS_SESSION_REMOTE_CONTROL"
	))

	if(wParam=0x07){ ; session was locked
		SysLocked := "yes"
		MsgBox "WM_WTSSESSION_CHANGE: SysLocked = " SysLocked ,, "0x40000 T3"
	}
	else if(wParam=0x08){ ; session was unlocked
		SysLocked := "no"
		ToolTip "WM_WTSSESSION_CHANGE: SysLocked = " SysLocked
		SetTimer () => ToolTip(), -3000
	}
}

WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd){
	; MsgBox("WM_DISPLAYCHANGE!", "", "T5")
	sleep 5000
	TimeString := FormatTime(,"yy-MM-dd HH:mm:ss")
	FileAppend "========== WM_DISPLAYCHANGE! " TimeString "`nwParam: " wParam "`nlParam: " lParam "`n", A_Desktop "\DisplayChangeLog.txt"
	MonitorCount := MonitorGetCount()
	MonitorPrimary := MonitorGetPrimary()
	FileAppend "Monitor Count:`t" MonitorCount "`nPrimary Monitor:`t" MonitorPrimary "`n`n", A_Desktop "\DisplayChangeLog.txt"
	i := 1
	resolution1 := 0 , resolution2 := 0
	Loop
	{
		if SysLocked = "no"
		{
			break
		}
		else
		{
			sleep 1000
		}
	}
	Loop MonitorCount
	{
		Try MonitorGet A_Index, &L, &T, &R, &B
		Try MonitorGetWorkArea A_Index, &WL, &WT, &WR, &WB
		resolution%i% := (R-L "x" B-T)
		FileAppend
		(
			"Monitor:`t#" A_Index
			"`nName:`t`t" MonitorGetName(A_Index)
			"`nResolution" i ":`t" R-L "x" B-T
			"`nLeft: `t`t" L " (" WL ")"
			"`nTop: `t`t" T " (" WT ")"
			"`nRight:`t`t" R " (" WR ")"
			"`nBottom:`t`t" B " (" WB ")"
			"`n`n"

			, A_Desktop "\DisplayChangeLog.txt"
		)
		i := i+1
	}
	if (MonitorCount = 2) AND (resolution1 = "3072x1728") AND (resolution2 = "5120x1440")
	{
		FileAppend "+ Parameters match preferred monitor configuration! +`n", A_Desktop "\DisplayChangeLog.txt"
		FileAppend "+ Unlocked! +`nRunning Display Reset Script...`n`n", A_Desktop "\DisplayChangeLog.txt"
		Sleep 5000

		DisplayConfigReset() ; triggers hotkey named function in main ahk script to reset window size/position for various apps/windows
	}
	Else
	{
		FileAppend "- Parameters don't match! -`nWaiting for preferred monitor configuration...`n`n", A_Desktop "\DisplayChangeLog.txt"
	}
}
Hopefully, it's clear enough what I'm trying to do here to figure out what I'm doing wrong.

The end goal is having a script that will automatically reset all window positions whenever they get messed up (due to Windows not being smart enough to automatically store and recall window sizes/positions based on previous display configuration). So if the count or resolution of the displays change, as soon as they are back to the desired configuration, AND if the system is unlocked, the DisplayConfigReset() function will execute to fix all the window sizes/positions.

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Detect display configuration change?

Post by iseahound » 21 Apr 2023, 17:17

Hey sorry I didn't include code with my suggestion, but I'm glad you got it working regardless. In this case you need a mutex, or a lock, which helps synchronize asynchronous threads. (Note: AutoHotkey does not really support multithreading)

I would first design a self-enclosed loop:

Code: Select all

Persistent

; The following loop will become "critical" and when it does will launch your app:
SetTimer mutex, 3000
mutex(WM_WTSSESSION_CHANGE?, WM_DISPLAYCHANGE?) { ; not really a mutex but whatever
   static mutex_WM_WTSSESSION_CHANGE := False
   static mutex_WM_DISPLAYCHANGE := False
   
   mutex_WM_WTSSESSION_CHANGE := WM_WTSSESSION_CHANGE ?? mutex_WM_WTSSESSION_CHANGE 
   mutex_WM_DISPLAYCHANGE := WM_DISPLAYCHANGE ?? mutex_WM_DISPLAYCHANGE 
   
   if mutex_WM_WTSSESSION_CHANGE and mutex_WM_DISPLAYCHANGE {
       mutex_WM_WTSSESSION_CHANGE := false
       mutex_WM_DISPLAYCHANGE := False
       MsgBox "do something"
   }
}
   
DllCall("wtsapi32.dll\WTSRegisterSessionNotification", "ptr", A_ScriptHwnd, "uint", NOTIFY_FOR_ALL_SESSIONS := 1)
OnMessage 0x02B1, MsgMonitor
OnMessage 0x007E, WM_DISPLAYCHANGE

WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd){
    mutex(, True)
}

WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd){
    mutex(True)
}
This is the "idea" behind what you should do, and I havent had time to test or talior the code. But I hope you can see what the idea here is.You have this setTimer that runs in a loop that waits for both window messages to be true. When they are it will execute a MsgBox.

EDIT: Thinking about it, you could have it trigger off WM_DisplayChange only, and remove the SetTimer. You need to have your MSgMonitor in the original use the mutex / locks (basically a static variable here inside the function) that I described.

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 23 Apr 2023, 17:53

@iseahound I think I managed to get it working properly in a different way.

I'm not sure I know what the mutex/lock is for, and don't understand¹ some of the syntax in the code in your previous post. But I don't think the messages will necessarily ever both arrive at the same time. In fact, the WM_WTSSESSION_CHANGE with the parameter I am looking for (wParam=0x08, for WTS_SESSION_UNLOCK) will always arrive at least a few seconds after the WM_DISPLAYCHANGE message for the correct display configuration has been received (which seems to occur while the system is still locked).

Also, sometimes the script will see 2 or 3 WM_DISPLAYCHANGE messages in quick succession as the displays reconnect at different times/resolutions than the preferred settings, which eventually will get restored to them properly (the main reason I need this script so that the window positions/sizes aren't reset until the proper display configuration is finally restored).

Furthermore, sometimes the system won't get locked when I switch over to my other machine for just a few minutes, so no WM_WTSSESSION_CHANGE message will ever arrive between the WM_DISPLAYCHANGE messages (and corresponding display config changes). So for these cases, I'm basically monitoring a variable to see if/when the system gets locked, and then checking to make sure it is unlocked before restoring the all the window configurations.

The only remaining thing I need to verify still is that - even at times when the system goes to sleep on its own (i.e. based on power settings, as opposed to me locking/sleeping it, and setting the variable properly via an AHK hotkey) - the WM_WTSSESSION_CHANGE:WTS_SESSION_LOCK message is received by the ahk script so that the SysLocked variable is set correctly before I unlock the system/resume my session the next time. Otherwise, some aspects of the [elaborate] DisplayConfigReset() function I am running to handle all of the various running apps' window restorations will likely fail due to everything being hidden behind the Windows LogonUI screen.

¹Regarding your previous post, specifically, I wasn't able to understand the ??, and searching for ?? in the help doc doesn't yield any results. So I tried a Google Advanced Search for it: https://www.google.com/search?as_q=&as_ ... &tbs=#ip=1

That led me to https://www.autohotkey.com/docs/v2/Variables.htm.

Looks like this operator wasn't in AHK v1. I just recently switched to v2, so makes sense why I wasn't aware of it. But now I think I understand where you were going with the whole mutex/locking setup running on a timer. It's basically similar to what I ended up doing, except I'm not waiting until both messages are received together - just waiting on the SysLock variable to be set properly (indicating the system is unlocked).

Here's the finished script (at least until/unless I catch something else wrong with it later on, in which case I will update it here as well):

Code: Select all

SysLocked := false
TSCounterRunning := false

StopTSCounter(){
	global TSCounterRunning := false
}

StartTSCounter(){
	if not TSCounterRunning {
		sleep (1000 - A_MSec)
		global TSCounterRunning := true
		global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
		SetTimer TSCounter, 1000
		FileAppend TimeStamp ": // TSCounter: START`n", A_Desktop "\DisplayChangeLog.txt"
	}
	else {
		ToolTip "TSCounter already running!"
		SetTimer () => ToolTip(), -1000
		; MsgBox("TSCounter already running!", "", "T0.5")
	}
}

TSCounter(){
	if TSCounterRunning {
		global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
		; FileAppend TimeStamp ": ~~ SysLocked = " SysLocked "`n", A_Desktop "\DisplayChangeLog.txt"
		return
	}
	; Otherwise, TSCounterRunning = false
	SetTimer , 0  ; i.e. the timer turns itself off here.
	global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
	FileAppend TimeStamp ": // TSCounter: STOP`n", A_Desktop "\DisplayChangeLog.txt"
}

DllCall("wtsapi32.dll\WTSRegisterSessionNotification", "ptr", A_ScriptHwnd, "uint", NOTIFY_FOR_ALL_SESSIONS := 1)
OnMessage 0x02B1, MsgMonitor
OnMessage 0x007E, MsgMonitor
Persistent

MsgMonitor(wParam, lParam, msg, hwnd){
	; Since returning quickly is often important, it is better to use ToolTip than
	; something like MsgBox that would prevent the function from finishing:
	if msg = 0x02B1
	{
		ToolTip "Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam "`nWM_WTSSESSION_CHANGE"
		SetTimer () => ToolTip(), -3000
		WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd)
	}
	else if msg = 0x007E
	{
		ToolTip "Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam "`nWM_DISPLAYCHANGE"
		SetTimer () => ToolTip(), -3000
		WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd)
	}
}

WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd){
	StartTSCounter()
	static notification := Map(
	(Join,
		0x1, "WTS_CONSOLE_CONNECT"
		0x2, "WTS_CONSOLE_DISCONNECT"
		0x3, "WTS_REMOTE_CONNECT"
		0x4, "WTS_REMOTE_DISCONNECT"
		0x5, "WTS_SESSION_LOGON"
		0x6, "WTS_SESSION_LOGOFF"
		0x7, "WTS_SESSION_LOCK"
		0x8, "WTS_SESSION_UNLOCK"
		0x9, "WTS_SESSION_REMOTE_CONTROL"
	))

	; msgbox notification[wParam],, 0x40000
	if(wParam=0x07)
	{ ; session was locked
		global SysLocked := true
		FileAppend TimeStamp ": -- WTS_SESSION_LOCK: SysLocked = true`n", A_Desktop "\DisplayChangeLog.txt"
		; MsgBox "WTS_SESSION_LOCK: SysLocked = " SysLocked ,, "0x40000 T3" ; need msgbox here for debug, since tooltip won't be visible after system is locked.
	}
	else if(wParam=0x08)
	{ ; session was unlocked
		global SysLocked := false
		FileAppend TimeStamp ": ++ WTS_SESSION_UNLOCK: SysLocked = false`n", A_Desktop "\DisplayChangeLog.txt"
		StopTSCounter()
		; ToolTip "WTS_SESSION_UNLOCK: SysLocked = " SysLocked
		; SetTimer () => ToolTip(), -3000
	}
}

/*
0x1, "WTS_CONSOLE_CONNECT" ; A session was connected to the console terminal.
0x2, "WTS_CONSOLE_DISCONNECT" ; A session was disconnected from the console terminal.
0x3, "WTS_REMOTE_CONNECT" ; A session was connected to the remote terminal.
0x4, "WTS_REMOTE_DISCONNECT" ; A session was disconnected from the remote terminal.
0x5, "WTS_SESSION_LOGON" ; A user has logged on to the session.
0x6, "WTS_SESSION_LOGOFF" ; A user has logged off the session.
0x7, "WTS_SESSION_LOCK" ; A session has been locked.
0x8, "WTS_SESSION_UNLOCK" ; A session has been unlocked.
0x9, "WTS_SESSION_REMOTE_CONTROL" ; A session has changed its remote controlled status. To determine the status, call GetSystemMetrics and check the SM_REMOTECONTROL metric.
*/

WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd){
	StartTSCounter()
	; MsgBox("WM_DISPLAYCHANGE!", "", "T5")
	FileAppend TimeStamp ": ========== WM_DISPLAYCHANGE!`nwParam: `t`t" wParam "`nlParam: `t`t" lParam "`n", A_Desktop "\DisplayChangeLog.txt"
	MonitorCount := MonitorGetCount()
	MonitorPrimary := MonitorGetPrimary()
	FileAppend "Monitor Count:`t" MonitorCount "`nPrimary Monitor:" MonitorPrimary "`n`n", A_Desktop "\DisplayChangeLog.txt"
	i := 1
	resolution1 := 0 , resolution2 := 0
	Loop MonitorCount
	{
		Try MonitorGet A_Index, &L, &T, &R, &B
		Try MonitorGetWorkArea A_Index, &WL, &WT, &WR, &WB
		resolution%i% := (R-L "x" B-T)
		FileAppend
		(
			"Monitor:`t`t#" A_Index
			"`nName:`t`t`t" MonitorGetName(A_Index)
			"`nResolution" i ":`t" R-L "x" B-T
			"`nLeft: `t`t`t" L " (" WL ")"
			"`nTop: `t`t`t" T " (" WT ")"
			"`nRight:`t`t`t" R " (" WR ")"
			"`nBottom:`t`t`t" B " (" WB ")"
			"`n`n"

			, A_Desktop "\DisplayChangeLog.txt"
		)
		i := i+1
	}
	if (MonitorCount = 2) AND (resolution1 = "3072x1728") AND (resolution2 = "5120x1440")
	{
		FileAppend TimeStamp ": ++ Parameters match preferred monitor configuration! +`n", A_Desktop "\DisplayChangeLog.txt"
		SetTimer SysLockCheck, 1000

		SysLockCheck()
		{
			if (SysLocked != false)
			{
				FileAppend TimeStamp ": xx SysLocked = true, waiting for SysLocked = false...`n", A_Desktop "\DisplayChangeLog.txt"
				return
			}
			; Otherwise, SysLocked = false
			SetTimer , 0  ; i.e. the timer turns itself off here.
			FileAppend TimeStamp ": ++ Running Display Reset Script...`n`n", A_Desktop "\DisplayChangeLog.txt"
			DisplayConfigReset() ; triggers hotkey named function in main ahk script to reset window size/position for various apps/windows
			StopTSCounter()
		}
	}
	Else
	{
		FileAppend TimeStamp ": -- Parameters don't match! Waiting for preferred monitor configuration...`n`n", A_Desktop "\DisplayChangeLog.txt"
	}
}

iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Detect display configuration change?

Post by iseahound » 15 May 2023, 12:31

Yep, your final script ended up being similar to my suggestions! It looks good and triggers off of WM_DIsplayChange, which again, I wasn't sure of, but as long as your testing seems to work, should be fine.

They only question is why you have a trailing thread SysLockCheck? It should be fine as long as you are only monitoring 2 messages. Just note that this approach won't work well for 3+ messages, which seemed to be what your MsgMonitor function implied.

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 25 May 2023, 11:54

@iseahound Sometimes. I noticed that when my PC is locked (screensaver starts, or I manually lock and switch to my other machine), when I unlocked it, the AHK script had already tried to run and failed after detecting the display change while the system was still locked.

I've actually since updated the script a bit more to handle various edge cases with screensaver being active, system being locked, and some other conditions that were causing errors to be thrown & the script failing to execute properly. This is the most current iteration.

Code: Select all

;█████████████████████████████████████████ SETUP & GLOBALS █████████████████████████████████████████

If !A_IsAdmin
	Try 	Run("*RunAs `"" A_ScriptFullPath "`"")
	KeyHistory
	MsgBox("AHK script:" (A_IsAdmin?" running as admin.":" WARNING: running as standard user script!"), "", "T3")

InstallDir := RegRead("HKLM\SOFTWARE\AutoHotkey", "InstallDir")
#SingleInstance force
InstallKeybdHook()
A_HotkeyInterval := 0
CoordMode("Mouse", "Window")
SendMode("Input")
#WinActivateForce
SetControlDelay(1)
SetTitleMatchMode("RegEx")

SysLocked := false
TSCounterRunning := false

DllCall("wtsapi32.dll\WTSRegisterSessionNotification", "ptr", A_ScriptHwnd, "uint", NOTIFY_FOR_ALL_SESSIONS := 1)
OnMessage 0x02B1, MsgMonitor
OnMessage 0x007E, MsgMonitor

Persistent

;████████████████████████████████████████████ FUNCTIONS ████████████████████████████████████████████

StopTSCounter(){ ;stops the debug timestamp counter
	global TSCounterRunning := false
}

StartTSCounter(){ ;just starts a counter running for log timestamps for debug/troubleshooting purposes
	if not TSCounterRunning {
		sleep (1000 - A_MSec)
		global TSCounterRunning := true
		global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
		SetTimer TSCounter, 1000
		FileAppend TimeStamp ": // TSCounter: START`n", A_Desktop "\AHK_Log.txt"
	}
	else {
		ToolTip "TSCounter already running!"
		SetTimer () => ToolTip(), -1000
	}
}

TSCounter(){ ;sets/updates global TimeStamp variable for debug timestamps
	if TSCounterRunning {
		global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
		return
	}
	; Otherwise, TSCounterRunning = false
	SetTimer , 0  ; i.e. the timer turns itself off here.	
	global TimeStamp := FormatTime(,"yy-MM-dd HH:mm:ss") "." A_MSec
	FileAppend TimeStamp ": // TSCounter: STOP`n", A_Desktop "\AHK_Log.txt"
}


MsgMonitor(wParam, lParam, msg, hwnd){ ;monitors for Windows system messages, runs functions corresponding to messages received
	; Since returning quickly is often important, it is better to use ToolTip than
	; something like MsgBox that would prevent the function from finishing:
	if msg = 0x02B1
	{
		tooltip("Message: " msg ", WPARAM: " wParam ", LPARAM: " lParam ", hwnd: " hwnd " == WM_WTSSESSION_CHANGE")
		SetTimer () => ToolTip(), -1500
		WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd)
	}
	else if msg = 0x007E
	{
		tooltip("Message: " msg ", WPARAM: " wParam ", LPARAM: " lParam ", hwnd: " hwnd " == WM_DISPLAYCHANGE")
		SetTimer () => ToolTip(), -1500
		WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd)
	}
}

WM_WTSSESSION_CHANGE(wParam, lParam, msg, hwnd){
	StartTSCounter()
	static notification := Map(
	(Join,
		0x1, "WTS_CONSOLE_CONNECT"
		0x2, "WTS_CONSOLE_DISCONNECT"
		0x3, "WTS_REMOTE_CONNECT"
		0x4, "WTS_REMOTE_DISCONNECT"
		0x5, "WTS_SESSION_LOGON"
		0x6, "WTS_SESSION_LOGOFF"
		0x7, "WTS_SESSION_LOCK"
		0x8, "WTS_SESSION_UNLOCK"
		0x9, "WTS_SESSION_REMOTE_CONTROL"
	))

	if(wParam=0x07)
	{ ; session was locked
		global SysLocked := true
		FileAppend TimeStamp ": -- WTS_SESSION_LOCK: SysLocked = true`n", A_Desktop "\AHK_Log.txt"
	}
	else if(wParam=0x08)
	{ ; session was unlocked
		global SysLocked := false
		FileAppend TimeStamp ": ++ WTS_SESSION_UNLOCK: SysLocked = false`n", A_Desktop "\AHK_Log.txt"
		StopTSCounter()
	}
}

WM_DISPLAYCHANGE(wParam, lParam, msg, hwnd){
	StartTSCounter()
	FileAppend TimeStamp ": ========== WM_DISPLAYCHANGE!`nwParam: `t`t" wParam "`nlParam: `t`t" lParam "`n", A_Desktop "\AHK_Log.txt"
	MonitorCount := MonitorGetCount()
	MonitorPrimary := MonitorGetPrimary()
	FileAppend "Monitor Count:`t" MonitorCount "`nPrimary Monitor:" MonitorPrimary "`n`n", A_Desktop "\AHK_Log.txt"
	i := 1
	resolution1 := 0 , resolution2 := 0
	Loop MonitorCount
	{
		Try MonitorGet A_Index, &L, &T, &R, &B
		Try MonitorGetWorkArea A_Index, &WL, &WT, &WR, &WB
		resolution%i% := (R-L "x" B-T)
		FileAppend
		(
			"Monitor:`t`t#" A_Index
			"`nName:`t`t`t" MonitorGetName(A_Index)
			"`nResolution" i ":`t" R-L "x" B-T
			"`nLeft: `t`t`t" L " (" WL ")"
			"`nTop: `t`t`t" T " (" WT ")"
			"`nRight:`t`t`t" R " (" WR ")"
			"`nBottom:`t`t`t" B " (" WB ")"
			"`n`n"

			, A_Desktop "\AHK_Log.txt"
		)
		i := i+1
	}
	if (MonitorCount = 2) AND (resolution1 = "3072x1728") AND (resolution2 = "5120x1440")
	{
		FileAppend(TimeStamp ": ++ Parameters match preferred monitor configuration! +`n", A_Desktop "\AHK_Log.txt")
		global SLCheck := 1
		global screensaver := RegRead("HKCU\Control Panel\Desktop", "SCRNSAVE.EXE")
		SplitPath(screensaver,&fName)
		SetTimer SysLockCheck, 1000
		
		SysLockCheck()
		{
			if (SysLocked != false) or (ProcessExist(fName))
			{
				if ProcessExist(fName){
					global SysLocked := true
					(SLCheck++ = 1) && FileAppend(TimeStamp ": xx SysLocked = true, & screensaver running. waiting for session unlock...`n", A_Desktop "\AHK_Log.txt")
				}
				else{
					(SLCheck++ = 1) && FileAppend(TimeStamp ": xx SysLocked = true, waiting for session unlock...`n", A_Desktop "\AHK_Log.txt")
				}
				return
			}
			; Otherwise, SysLocked = false
			SetTimer , 0  ; i.e. the timer turns itself off here.
			FileAppend(FormatTime(,"yy-MM-dd HH:mm:ss" "." A_MSec) ": ++ Running Display Reset Script...`n", A_Desktop "\AHK_Log.txt")
			global SLCheck := 1
			DisplayConfigReset() ; triggers hotkey named function in main ahk script to reset window size/position for various apps/windows
		}
	}
	Else
	{
		FileAppend TimeStamp ": -- Parameters don't match! Waiting for preferred monitor configuration...`n`n", A_Desktop "\AHK_Log.txt"
	}
}


iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: Detect display configuration change?

Post by iseahound » 26 May 2023, 02:01

Yes your SysLockCheck can be spawned multiple times which may cause errors. That's why I don't recommend making threads that create other threads, if you have a global synchronization object it's much easier to reason about. But you need to make your SysLockCheck thread check for multple insances and off itself if it finds another lol.

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 27 May 2023, 14:48

are you maybe talking about the StartTSCounter() function? I can see where I'm calling that from two different WM message functions, since sometimes the screensaver or system lock occurs independently. but even there, the first line of that function is: if not TSCounterRunning{

Maybe you could point out where you can see the potential error or how it might end up running more than one instance of the TScounter or SysLockCheck?

AFAIK, the functions that run these & the functions themselves set/check variables to see whether either is running, or for system lock state and screensaver running, so that they only run when needed, and only if they are not already running.

in fact, the reason I added these (timer and lock check) functions were for debugging (reasons I described previously re: needing to check/wait for the right time/state to actually run the DisplayConfigReset() function, or it would occasionally fall).

I haven't seen any errors from this version of the script, and have been using it daily for a while now.

IMTheNachoMan
Posts: 59
Joined: 01 Mar 2022, 17:07
Contact:

Re: Detect display configuration change?

Post by IMTheNachoMan » 24 Sep 2023, 08:45

@mmmax Were you able to get this to work? Mind sharing your final code?

mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Detect display configuration change?

Post by mmmax » 30 Sep 2023, 07:10

IMTheNachoMan wrote:
24 Sep 2023, 08:45
@mmmax Were you able to get this to work? Mind sharing your final code?
Yes. The code in my previous post is what I'm using now.

viewtopic.php?p=540520&sid=a9b81a887045a9372e668ca84535bf53#p523250

Post Reply

Return to “Ask for Help (v2)”