Suggestion: MonitorFromWindow function

Discuss the future of the AutoHotkey language
User avatar
the1corrupted
Posts: 36
Joined: 25 Jan 2014, 23:01

Suggestion: MonitorFromWindow function

19 Feb 2020, 00:15

I wrote this function in AHK 2.0 as it is, already using a Window ID to determine which monitor a window is displayed on. The only change I would want to make is the incoming argument to be parsed as 'Window Title' as opposed to the current version "Window ID"

I was wondering if I could contribute this directly to GitHub, or if someone else could trans-literate the code?

Code: Select all

GetMonitorFromHwnd(winId)
{
	/*
	This is optional code. Commenting out due to irrelevance to the current post
	if !WinExist("ahk_id " . winId)
	{
		return false
	}
	else if (!RegExMatch(winId, Runtime.hwnd_regex))
	{
		Throw Exception("Invalid window id passed: " . winId)
	}
	*/
	mCount := MonitorGetCount()
	if (mCount = 1)
	{
		/*
			If there is only one monitor, there is only one answer.
			moveWinToScreen is also assuming more than one monitor.
		*/
		return 1
	}
	/*
		Refer to microsoft documentation for user32.dll method MonitorFromWindow
		https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
		MONITOR_DEFAULTTONULL = 0
		MONITOR_DEFAULTTOPRIMARY = 1
		MONITOR_DEFAULTTONEAREST = 2
	*/
	if !(monitorHandle := DllCall("User32.dll\MonitorFromWindow", "UInt", winId, "UInt", 2))
	{
		Throw Exception("Error in DllCall - Unable to set monitorHandle.")
	}
	/*
		Refer to microsoft documentation for user32.dll method GetMonitorInfo
		https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmonitorinfoa
		BOOL GetMonitorInfoA(
		  HMONITOR      hMonitor,
		  LPMONITORINFO lpmi
		);
	*/
	VarSetCapacity(monitorInfo, 40)
	NumPut(40, monitorInfo)
	
	if (SubStr(A_OSVersion, 1, 2) = 10)
	{
		;	Windows 10
		DllCall("User32.dll\GetMonitorInfoA", "UInt", monitorHandle, "UInt", &monitorInfo)
	}
	else
	{
		;	Not Windows 10
		DllCall("User32.dll\GetMonitorInfo", "UInt", monitorHandle, "UInt", &monitorInfo)
	}
	;	Create a bounding rectangle
	monitorLeft   := NumGet(monitorInfo,  4, "Int")
	monitorTop    := NumGet(monitorInfo,  8, "Int")
	monitorRight  := NumGet(monitorInfo, 12, "Int")
	monitorBottom := NumGet(monitorInfo, 16, "Int")
	Loop mCount
	{
		MonitorGet(A_Index, tempMonLeft, tempMonTop, tempMonRight, tempMonBottom)
		if monitorLeft = tempMonLeft && monitorTop = tempMonTop && monitorRight = tempMonRight && monitorBottom = tempMonBottom
		{
			;	Hey we found the monitor by bounding rectangle!
			return A_Index
		}
	}
	;	A value is expected.
	return false
	;	End of function :D
}
User avatar
Larrimus
Posts: 1
Joined: 01 Apr 2022, 14:48

Re: Suggestion: MonitorFromWindow function

01 Apr 2022, 17:09

Firstly, to answer your question: you can incorporate WinExist() to grab the window ID by its title, the name of its executable, or a substring of text in the window title. You will have to decide which of these are best, as they're not always unique. For simplicities sake, I would recommend against building something like this into your function, and instead save the return value of WinExist() into a variable that you pass into this function so that you only have to call it (and make sure that it doesn't return 0) once for each window.

Now, since I had a hell of a time refactoring this for AutoHotkey v1, I will also include my refactored version of this:

Code: Select all

GetMonitorFromHwnd(winId)
{
	/*
	This is optional code. Commenting out due to the fact that it's probably better to make sure that winId references a valid window before calling this function.
	if !WinExist("ahk_id " . winId) {
		return false
	}
	else if (!RegExMatch(winId, Runtime.hwnd_regex)) {
		Throw Exception("Invalid window id passed: " . winId)
	}
	*/
	SysGet mCount, MonitorCount
	if (mCount = 1)
	{	; If there is only one monitor, there is only one answer.
		return 1
	}
	
	/*
		Refer to microsoft documentation for user32.dll method MonitorFromWindow
		https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
		MONITOR_DEFAULTTONULL = 0
		MONITOR_DEFAULTTOPRIMARY = 1
		MONITOR_DEFAULTTONEAREST = 2
	*/
	if !(monitorHandle := DllCall("User32.dll\MonitorFromWindow", "UInt", winId, "UInt", 2))
	{
		Throw Exception("Error in DllCall - Unable to set monitorHandle.")
	}
	/*
		Refer to microsoft documentation for user32.dll method GetMonitorInfo
		https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getmonitorinfoa
		BOOL GetMonitorInfoA(
		  HMONITOR	  hMonitor,
		  LPMONITORINFO lpmi
		);
	*/
	VarSetCapacity(monitorInfo, 40)
	NumPut(40, monitorInfo)
	
	if (SubStr(A_OSVersion, 1, 2) = 10)
	{	;	Windows 10
		DllCall("User32.dll\GetMonitorInfoA", "UInt", monitorHandle, "UInt", &monitorInfo)
	}
	else
	{	;	Not Windows 10
		DllCall("User32.dll\GetMonitorInfo", "UInt", monitorHandle, "UInt", &monitorInfo)
	}
	;	Even for debugging purposes this isn't very helpful. I'm pretty sure it's just a pointer to monitorInfo.
	;monitorZero		:= NumGet(monitorInfo,  0, "Int")
	;	Create a bounding rectangle
	monitorLeft		:= NumGet(monitorInfo,  4, "Int")
	monitorTop		:= NumGet(monitorInfo,  8, "Int")
	monitorRight	:= NumGet(monitorInfo, 12, "Int")
	monitorBottom	:= NumGet(monitorInfo, 16, "Int")
	/*
	; 	These point to the dimensions of the monitor with the taskbar taken out.
	workLeft	:= NumGet(monitorInfo, 20, "Int")
	workTop		:= NumGet(monitorInfo, 24, "Int")
	workRight	:= NumGet(monitorInfo, 28, "Int")
	workBottom	:= NumGet(monitorInfo, 32, "Int")
	;	If this is the primary monitor (the monitor with all your desktop icons) this should be 1.
	isPrimary	:= NumGet(monitorInfo, 36, "Int") ; & 1
	*/
	Loop %mCount%
	{
		; MonitorGet(A_Index, tempMonLeft, tempMonTop, tempMonRight, tempMonBottom)
		SysGet, tempMon, Monitor, %A_Index%
		/* ; This can be really helpful for debugging purposes to see
		MsgBox, % "monitorLeft:`t"					monitorLeft						"`n"
			. "monitorTop:`t"						monitorTop						"`n"
			. "monitorRight:`t"						monitorRight					"`n"
			. "monitorBottom:`t"					monitorBottom					"`n`n"
			. "workLeft:`t"							workLeft						"`n"
			. "workTop:`t"							workTop							"`n"
			. "workRight:`t"						workRight						"`n"
			. "workBottom:`t"						workBottom						"`n`n"
			. "isPrimary:`t"						isPrimary						"`n`n"
			. "Monitor Number:`t"					A_Index							"`n"
			. "tempMonLeft:`t"						tempMonLeft						"`n"
			. "tempMonTop:`t"						tempMonTop						"`n"
			. "tempMonRight:`t"						tempMonRight					"`n"
			. "tempMonBottom:`t"					tempMonBottom					"`n`n"
			. "monitorLeft = tempMonLeft:`t"		(monitorLeft = tempMonLeft)		"`n"
			. "monitorTop = tempMonTop:`t"			(monitorTop = tempMonTop)		"`n"
			. "monitorRight = tempMonRight:`t"		(monitorRight = tempMonRight)	"`n"
			. "monitorBottom = tempMonBottom:`t" 	(monitorBottom = tempMonBottom)
		*/
		if (monitorLeft = tempMonLeft && monitorTop = tempMonTop && monitorRight = tempMonRight && monitorBottom = tempMonBottom)
		{	;	We found the monitor by matching its bounding rectangle.
			return A_Index
		}
	}
	;	A value is expected.
	return 0
}

Return to “AutoHotkey Development”

Who is online

Users browsing this forum: No registered users and 15 guests