多显示器下,如何只在当前显示器打开的窗口中一键切换?

比如
显示器A,全屏打开了软件1,软件2
显示器B,全屏打开了软件3,软件4
如何实现当鼠标在显示器A时候,一键切换软件1和2
当鼠标在显示器B时候,一键切换软件3和4

多显示器管理软件。

收费的 displayFusion

免费的 Dual Monitor Tools

1 Like

下了小恐龙版的Dual Monitor Tools,没有找着相关的设置

毕竟是免费的 :rofl:

image

它只是提供快速切换窗口和光标的功能。

displayFusion 默认也没有类似的功能。

但是 displayFusion 好在支持代码脚本(C#和VB),你可以自己写, 也可以下载别人写好的。

1 Like

对了,如果只是单纯这个需求,固定的软件, AHK的效果可能更好。 但是缺乏普适性。

2 Likes

感谢小恐龙,其实我主要是想知道win10能不能设置alt+tab 只切换当前屏幕上的窗口。
比较喜欢所有软件全屏起来用,虽说开四个软件,按住alt,再按tab最多也就切三次。
但是设置单键等于alt+tab,一键切换最近两个窗口还是要爽很多。

我试着写个ahk,我就4个窗口,应该很简单,当前活动窗口是A的话,一键切B,活动窗口是C的话,一键切D……四小段就搞定了

这是 win11,win10 应该也有吧

1 Like

没有,也可能我的win10版本太低

啊不!我刚试了一下,这个也不行,这个只有虚拟桌面才有效。显示器 A 和显示器 B 会被一起当作一个桌面。

找到一个更通用的解决方案。
软件groupy,开启这个设置。

image

用groupy把软件1和2在显示器A组个group,把软件3和4在显示器B组个group。

这时在两个显示器上分别用ctrl + tab,切换的就只是该显示器上的软件了。

另外,组好group之后,软件1234还是可以全屏化的,不影响切换,可谓是完美啦(不过若用浏览器得把浏览器的ctrl + tab快捷键消掉)。


发现一个脚本,更加好用:

WheelSwitcher - 通过在屏幕边缘滚动鼠标滚轮来切换全屏应用程序(例如 RDP 客户端) - AutoHotkey 社区

#SingleInstance force
OutputDebug DBGVIEWCLEAR

CoordMode, Mouse, Screen
RunAsTask()

~WheelUp::
	DoWheel(1)
	return

~WheelDown::
	DoWheel(-1)
	return

DoWheel(dir){
	if (!IsMouseAtEdgeOfMonitor()){
		return
	}
	MouseGetPos, mx, my, active_hwnd
	;msgbox % "x" ax " y" ay " w" aw " h" ah
	;WinGet, l, list, % "ahk_exe " ProcessName " ahk_class " cls
	windows := []
	WinGet, l, list
	;WinGet, l, list, % "ahk_exe notepad.exe"
	Loop, %l%
	{
		w := l%A_Index%
		;if (alternate_mode || !alternate_mode && IsMouseAtEdgeOfMonitor()){
			windows.push(w)
		;}
	}
	windows := FilterWindows(active_hwnd, windows)
	w := GetNextWindow(active_hwnd, windows, dir)
	if (w){
		WinActivate, % "ahk_id " w
	}
}

IsMouseAtEdgeOfMonitor(){
	MouseGetPos, mx, my
	mon := WhichMonitorIsMouseOn()
	SysGet, monArea, Monitor, % mon
	if ( (mx >= monAreaLeft && mx <= monAreaLeft + 10) || (mx <= monAreaRight && mx >= monAreaRight - 10) || (my >= monAreaTop && my <= monAreaTop + 10) || (my <= monAreaBottom && my >= monAreaBottom - 10))
		return 1
}

; Which monitor is the mouse on?
WhichMonitorIsMouseOn(){
	MouseGetPos, mx, my
	SysGet, mc, MonitorCount
	Loop % mc {
		SysGet, monArea, Monitor, % A_Index
		if (mx >= monAreaLeft && mx <= monAreaRight && my >= monAreaTop && my <= monAreaBottom){
			return A_Index
		}
	}
	return 0
}

FilterWindows(active_hwnd, windows){
	static window_diff := 7
	WinGetPos, ax, ay, aw, ah, % "ahk_id " active_hwnd
	;OutputDebug % "AHK| Checking windows against ax: " ax ", ay: " ay ", ah: " ah ", aw: " aw ")"
	w := []
	for i, hwnd in windows {
		WinGetPos, wx, wy, ww, wh, % "ahk_id " hwnd
		WinGetTitle, title, % "ahk_id " hwnd
		;if (wx == ax && wy == ay && ww == aw && wh == ah){
		if (abs(wx-ax) <= window_diff && abs(wy-ay) <= window_diff && abs(ww - aw) <= (window_diff * 2) && abs(wh - ah) <= (window_diff * 2)){
			w.push(hwnd)
			;OutputDebug % "AHK| Adding window: " title
		} else {
			;OutputDebug % "AHK| Filtering out window: " title " ( wx: " wx ", wy:" wy ", wh: " wh ", ww: " ww 
		}
	}
	;return SortWindowsByHwnd(w)
	return SortWindowsByTitle(w)
}

; Sorts a windows array by Window Title
SortWindowsByTitle(array){
	for k, v in array {
		WinGetTitle, title, % "ahk_id " v
		str := title "|ws|" v "`n"
		;OutputDebug % "AHK| SortWindowsByTitle: " str
		sorted .= str
	}
	
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		hwnd := StrSplit(A_LoopField, "|ws|")
		hwnd := hwnd[2]
		out.push(hwnd)
	}
	return out
}

; Sorts a windows array by HWND
SortWindowsByHwnd(array){
	for k, v in array {
		sorted .= v "`n"
	}
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		out.push(A_LoopField)
	}
	return out
}

; Finds the window after the current one in a windows array
GetNextWindow(hwnd, windows, dir){
	; Find the current window in the list
	this_index := 0
	max := windows.length()
	Loop % max {
		if (windows[A_Index] == hwnd){
			this_index := A_Index
			break
		}
	}
	if (!this_index)
		return 0
	found := 0
	ct := 0
	new_index := this_index
	while (!found && (ct <= max)){
		ct++
		new_index += dir
		if (new_index < 1)
			new_index := max
		if (new_index > max)
			new_index := 1
		if windows[new_index] is number {
			;OutputDebug % "AHK| Found Window #" windows[new_index]
			found := 1
		}
	}
	if (found)
		return windows[new_index]
	else
		return 0
}

/*
 _      _    _               __ __      _      _                      _         _                        
| |__  | |_ | |_  _ __  _   / // /__ _ | |__  | | __ ___   ___  _ __ (_) _ __  | |_     ___   _ __  __ _ 
| '_ \ | __|| __|| '_ \(_) / // // _` || '_ \ | |/ // __| / __|| '__|| || '_ \ | __|   / _ \ | '__|/ _` |
| | | || |_ | |_ | |_) |_ / // /| (_| || | | ||   < \__ \| (__ | |   | || |_) || |_  _| (_) || |  | (_| |
|_| |_| \__| \__|| .__/(_)_//_/  \__,_||_| |_||_|\_\|___/ \___||_|   |_|| .__/  \__|(_)\___/ |_|   \__, |
                 |_|                                                    |_|                        |___/ 
RunAsTask() - Auto-elevates script without UAC prompt |  http://ahkscript.org/boards/viewtopic.php?t=4334        
_________________________________________________________________________________________________________
*/
 
RunAsTask() {                         ;  By SKAN,  http://goo.gl/yG6A1F,  CD:19/Aug/2014 | MD:22/Aug/2014
 
  Local CmdLine, TaskName, TaskExists, XML, TaskSchd, TaskRoot, RunAsTask
  Local TASK_CREATE := 0x2,  TASK_LOGON_INTERACTIVE_TOKEN := 3 
 
  Try TaskSchd  := ComObjCreate( "Schedule.Service" ),    TaskSchd.Connect()
    , TaskRoot  := TaskSchd.GetFolder( "\" )
  Catch
      Return "", ErrorLevel := 1    
 
  CmdLine       := ( A_IsCompiled ? "" : """"  A_AhkPath """" )  A_Space  ( """" A_ScriptFullpath """"  )
  TaskName      := "[RunAsTask] " A_ScriptName " @" SubStr( "000000000"  DllCall( "NTDLL\RtlComputeCrc32"
                   , "Int",0, "WStr",CmdLine, "UInt",StrLen( CmdLine ) * 2, "UInt" ), -9 )
 
  Try RunAsTask := TaskRoot.GetTask( TaskName )
  TaskExists    := ! A_LastError 
 
 
  If ( not A_IsAdmin and TaskExists )      { 
 
    RunAsTask.Run( "" )
    ExitApp
 
  }
 
  If ( not A_IsAdmin and not TaskExists )  { 
 
    Run *RunAs %CmdLine%, %A_ScriptDir%, UseErrorLevel
    ExitApp
 
  }
 
  If ( A_IsAdmin and not TaskExists )      {  
 
    XML := "
    ( LTrim Join
      <?xml version=""1.0"" ?><Task xmlns=""http://schemas.microsoft.com/windows/2004/02/mit/task""><Regi
      strationInfo /><Triggers /><Principals><Principal id=""Author""><LogonType>InteractiveToken</LogonT
      ype><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><MultipleInstancesPolic
      y>Parallel</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><
      StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate>
      <StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAva
      ilable><IdleSettings><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleS
      ettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><
      RunOnlyIfIdle>false</RunOnlyIfIdle><DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteApp
      Session><UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine><WakeToRun>false</WakeToRun><
      ExecutionTimeLimit>PT0S</ExecutionTimeLimit></Settings><Actions Context=""Author""><Exec>
      <Command>"   (  A_IsCompiled ? A_ScriptFullpath : A_AhkPath )       "</Command>
      <Arguments>" ( !A_IsCompiled ? """" A_ScriptFullpath  """" : "" )   "</Arguments>
      <WorkingDirectory>" A_ScriptDir "</WorkingDirectory></Exec></Actions></Task>
    )"    
 
    TaskRoot.RegisterTask( TaskName, XML, TASK_CREATE, "", "", TASK_LOGON_INTERACTIVE_TOKEN )
 
  }         
 
Return TaskName, ErrorLevel := 0
} ; _____________________________________________________________________________________________________
1 Like

如果是在打游戏, 会影响全屏的游戏吗

实测不会,到全屏游戏里就失效了,不知道为啥
ps 浏览器无边框化的全屏也会失效
应该是只针对最大化窗口,不针对全屏

发现不支持 wps 的识别

应坛友需求,这里发一下非全屏版,
分为两个版本:

alt + tab、alt + shift + tab切换当前激活窗口所在显示器窗口的代码。

alt + tab、alt + shift + tab切换当前鼠标所在显示器窗口的代码。

版本一

;alttab切换当前激活窗口所在显示器窗口
#SingleInstance force
OutputDebug DBGVIEWCLEAR

CoordMode, Mouse, Screen
RunAsTask()

!Tab::
	DoWheel(+1)
	return

!+TAB::
	DoWheel(-1)
	return

DoWheel(dir){
	MouseGetPos, mx, my
  active_hwnd := WinExist("A")
	;msgbox % "x" ax " y" ay " w" aw " h" ah
	;WinGet, l, list, % "ahk_exe " ProcessName " ahk_class " cls
	windows := []
  
  DetectHiddenWindows On
  arr := EnumerateAltTabWindows()
  for k, v in arr
  {
    WinGetClass, winClass, ahk_id %v%
    WinGetTitle, title, ahk_id %v%
    windows.push(v)
    ;altTabWindows .= A_Index "`nTitle: " title "`nClass: " winClass "`nhWnd: " v "`nLastActivePopup: " format("0x{:x}", GetLastActivePopup(v)) "`n`n"
  }
  
	windows := FilterWindows(active_hwnd, windows)
	w := GetNextWindow(active_hwnd, windows, dir)
  
  WinActivate, % "ahk_id " w
}

; Which monitor is the mouse on?
WhichMonitorIsMouseOn(){
	MouseGetPos, mx, my
	SysGet, mc, MonitorCount
	Loop % mc {
		SysGet, monArea, Monitor, % A_Index
		if (mx >= monAreaLeft && mx <= monAreaRight && my >= monAreaTop && my <= monAreaBottom){
			return A_Index
		}
	}
	return 0
}

FilterWindows(active_hwnd, windows){
	static window_diff := 7
	WinGetPos, ax, ay, aw, ah, % "ahk_id " active_hwnd
    ax_:=ax+aw/2,ay_:=ay+ah/2
    monA:=MWAGetMonitor(ax_, ay_)
	;OutputDebug % "AHK| Checking windows against ax: " ax ", ay: " ay ", ah: " ah ", aw: " aw ")"
	w := []
	for i, hwnd in windows {
    WinGetPos, wx, wy, ww, wh, % "ahk_id " hwnd
		WinGetTitle, title, % "ahk_id " hwnd
    wx_:=wx+ww/2,wy_:=wy+wh/2
    monW:=MWAGetMonitor(wx_, wy_)
    
		if ((monA=monW) and title!=""){
			w.push(hwnd)
      ;MsgBox,% monA monW title hwnd
			;OutputDebug % "AHK| Adding window: " title
		} else {
			;OutputDebug % "AHK| Filtering out window: " title " ( wx: " wx ", wy:" wy ", wh: " wh ", ww: " ww 
		}
	}
	;return SortWindowsByHwnd(w)
	return SortWindowsByTitle(w)
}

; Sorts a windows array by Window Title
SortWindowsByTitle(array){
	for k, v in array {
		WinGetTitle, title, % "ahk_id " v
		str := title "|ws|" v "`n"
		;OutputDebug % "AHK| SortWindowsByTitle: " str
		sorted .= str
	}
	
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		hwnd := StrSplit(A_LoopField, "|ws|")
		hwnd := hwnd[2]
		out.push(hwnd)
	}
	return out
}

; Sorts a windows array by HWND
SortWindowsByHwnd(array){
	for k, v in array {
		sorted .= v "`n"
	}
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		out.push(A_LoopField)
	}
	return out
}

; Finds the window after the current one in a windows array
GetNextWindow(hwnd, windows, dir){
	; Find the current window in the list
	this_index := 0
	max := windows.length()
	Loop % max {
		if (windows[A_Index] == hwnd){
			this_index := A_Index
      WinGetTitle, title, % "ahk_id " hwnd
			break
		}
	}
	if (!this_index)
		return 0
	new_index := this_index+dir
    if (new_index < 1)
			new_index := max
		if (new_index > max)
			new_index := 1
    
  ;MsgBox,% title " " this_index " " new_index
  return windows[new_index]
}

/*
 _      _    _               __ __      _      _                      _         _                        
| |__  | |_ | |_  _ __  _   / // /__ _ | |__  | | __ ___   ___  _ __ (_) _ __  | |_     ___   _ __  __ _ 
| '_ \ | __|| __|| '_ \(_) / // // _` || '_ \ | |/ // __| / __|| '__|| || '_ \ | __|   / _ \ | '__|/ _` |
| | | || |_ | |_ | |_) |_ / // /| (_| || | | ||   < \__ \| (__ | |   | || |_) || |_  _| (_) || |  | (_| |
|_| |_| \__| \__|| .__/(_)_//_/  \__,_||_| |_||_|\_\|___/ \___||_|   |_|| .__/  \__|(_)\___/ |_|   \__, |
                 |_|                                                    |_|                        |___/ 
RunAsTask() - Auto-elevates script without UAC prompt |  http://ahkscript.org/boards/viewtopic.php?t=4334        
_________________________________________________________________________________________________________
*/
 
RunAsTask() {                         ;  By SKAN,  http://goo.gl/yG6A1F,  CD:19/Aug/2014 | MD:22/Aug/2014
 
  Local CmdLine, TaskName, TaskExists, XML, TaskSchd, TaskRoot, RunAsTask
  Local TASK_CREATE := 0x2,  TASK_LOGON_INTERACTIVE_TOKEN := 3 
 
  Try TaskSchd  := ComObjCreate( "Schedule.Service" ),    TaskSchd.Connect()
    , TaskRoot  := TaskSchd.GetFolder( "\" )
  Catch
      Return "", ErrorLevel := 1    
 
  CmdLine       := ( A_IsCompiled ? "" : """"  A_AhkPath """" )  A_Space  ( """" A_ScriptFullpath """"  )
  TaskName      := "[RunAsTask] " A_ScriptName " @" SubStr( "000000000"  DllCall( "NTDLL\RtlComputeCrc32"
                   , "Int",0, "WStr",CmdLine, "UInt",StrLen( CmdLine ) * 2, "UInt" ), -9 )
 
  Try RunAsTask := TaskRoot.GetTask( TaskName )
  TaskExists    := ! A_LastError 
 
 
  If ( not A_IsAdmin and TaskExists )      { 
 
    RunAsTask.Run( "" )
    ExitApp
 
  }
 
  If ( not A_IsAdmin and not TaskExists )  { 
 
    Run *RunAs %CmdLine%, %A_ScriptDir%, UseErrorLevel
    ExitApp
 
  }
 
  If ( A_IsAdmin and not TaskExists )      {  
 
    XML := "
    ( LTrim Join
      <?xml version=""1.0"" ?><Task xmlns=""http://schemas.microsoft.com/windows/2004/02/mit/task""><Regi
      strationInfo /><Triggers /><Principals><Principal id=""Author""><LogonType>InteractiveToken</LogonT
      ype><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><MultipleInstancesPolic
      y>Parallel</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><
      StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate>
      <StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAva
      ilable><IdleSettings><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleS
      ettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><
      RunOnlyIfIdle>false</RunOnlyIfIdle><DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteApp
      Session><UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine><WakeToRun>false</WakeToRun><
      ExecutionTimeLimit>PT0S</ExecutionTimeLimit></Settings><Actions Context=""Author""><Exec>
      <Command>"   (  A_IsCompiled ? A_ScriptFullpath : A_AhkPath )       "</Command>
      <Arguments>" ( !A_IsCompiled ? """" A_ScriptFullpath  """" : "" )   "</Arguments>
      <WorkingDirectory>" A_ScriptDir "</WorkingDirectory></Exec></Actions></Task>
    )"    
 
    TaskRoot.RegisterTask( TaskName, XML, TASK_CREATE, "", "", TASK_LOGON_INTERACTIVE_TOKEN )
 
  }         
 
Return TaskName, ErrorLevel := 0
} ; _____________________________________________________________________________________________________

MWAGetMonitor(Mx := "", My := "")
{
	if  (!Mx or !My) 
	{
		; if Mx or My is empty, revert to the mouse cursor placement
		Coordmode, Mouse, Screen	; use Screen, so we can compare the coords with the sysget information`
		MouseGetPos, Mx, My
	}

	SysGet, MonitorCount, 80	; monitorcount, so we know how many monitors there are, and the number of loops we need to do
	Loop, %MonitorCount%
	{
		SysGet, mon%A_Index%, Monitor, %A_Index%	; "Monitor" will get the total desktop space of the monitor, including taskbars

		if ( Mx >= mon%A_Index%left ) && ( Mx < mon%A_Index%right ) && ( My >= mon%A_Index%top ) && ( My < mon%A_Index%bottom )
		{
			ActiveMon := A_Index
			break
		}
	}
	return ActiveMon
}

EnumerateAltTabWindows()
{
   AltTabList := []
   WinGet, list, List
   Loop % list
   {
      if IsAltTabWindow(list%A_Index%)
         AltTabList.Push(list%A_Index%)
   }
   Return AltTabList
}

IsAltTabWindow(hwnd)
{
   static ImmersiveShell, IApplicationViewCollection, MONITOR_DEFAULTTONULL := 0, VirtualDesktopAltTabFilter := "null", PropEnumProcEx := RegisterCallback("PropEnumProcEx", "Fast", 4)
   if (VirtualDesktopAltTabFilter = "null")
   {
      RegRead, VirtualDesktopAltTabFilter, HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced, VirtualDesktopAltTabFilter
      OSbuildNumber := StrSplit(A_OSVersion, ".")[3]
      if (OSbuildNumber < 14393)
      {
         msgbox Your %A_OSVersion% can not handle virtual desktops
         exitapp
      }
      else if (OSbuildNumber <= 17134)   ; Windows 10 1607 to 1803 and Windows Server 2016
         IID_IApplicationViewCollection := "{2C08ADF0-A386-4B35-9250-0FE183476FCC}"
      else
         IID_IApplicationViewCollection := "{1841C6D7-4F9D-42C0-AF41-8747538F10E5}"
      if !(ImmersiveShell := ComObjCreate(CLSID_ImmersiveShell := "{C2F03A33-21F5-47FA-B4BB-156362A2F239}", IID_IUnknown := "{00000000-0000-0000-C000-000000000046}"))
      {
         MsgBox ImmersiveShell not supported.
         ExitApp
      }
      if !(IApplicationViewCollection := ComObjQuery(ImmersiveShell, IID_IApplicationViewCollection, IID_IApplicationViewCollection))
      {
         MsgBox IApplicationViewCollection interface not supported.
         ExitApp	
      }
   }
   WinGetClass, winClass, ahk_id %hWnd%
   if (winClass = "Windows.UI.Core.CoreWindow")
      return
   if (winClass = "ApplicationFrameWindow")
   {
      varsetcapacity(ApplicationViewCloakType, 4, 0)
      DllCall("EnumPropsEx", "uptr", hWnd, "ptr", PropEnumProcEx, "ptr", &ApplicationViewCloakType)
      if (numget(ApplicationViewCloakType, 0, "int") = 1)   ; https://github.com/kvakulo/Switcheroo/commit/fa526606d52d5ba066ba0b2b5aa83ed04741390f
         return
   }
   ; if !DllCall("MonitorFromWindow", "uptr", hwnd, "uint", MONITOR_DEFAULTTONULL, "ptr")   ; test if window is shown on any monitor. alt-tab shows any window even if window is out of monitor.
   ;   return
   DllCall(NumGet(NumGet(IApplicationViewCollection+0)+6*A_PtrSize), "ptr", IApplicationViewCollection, "uptr", hwnd, "ptr*", pView)   ; GetViewForHwnd
   if pView
   {
      DllCall(NumGet(NumGet(pView+0)+27*A_PtrSize), "ptr", pView, "int*", ShowInSwitchers)   ; GetShowInSwitchers
      ObjRelease(pView)
   }
   if ShowInSwitchers and ((VirtualDesktopAltTabFilter = 0) or IsWindowOnCurrentVirtualDesktop(hwnd))
      return true
   return
}

GetLastActivePopup(hwnd)
{
   static GA_ROOTOWNER := 3
   hwnd := DllCall("GetAncestor", "uptr", hwnd, "uint", GA_ROOTOWNER, "ptr")
   hwnd := DllCall("GetLastActivePopup", "uptr", hwnd, "ptr")
   return hwnd
}

IsWindowOnCurrentVirtualDesktop(hwnd)
{
   static IVirtualDesktopManager
   if !IVirtualDesktopManager
      IVirtualDesktopManager := ComObjCreate(CLSID_VirtualDesktopManager := "{AA509086-5CA9-4C25-8F95-589D3C07B48A}", IID_IVirtualDesktopManager := "{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}")
   DllCall(NumGet(NumGet(IVirtualDesktopManager+0), 3*A_PtrSize), "ptr", IVirtualDesktopManager, "uptr", hwnd, "int*", onCurrentDesktop)   ; IsWindowOnCurrentVirtualDesktop
   return onCurrentDesktop
}

PropEnumProcEx(hWnd, lpszString, hData, dwData)
{
   if (strget(lpszString, "UTF-16") = "ApplicationViewCloakType")
   {
      numput(hData, dwData+0, 0, "int")
      return false
   }
   return true
}

版本二

;alttab切换当前鼠标所在显示器窗口
#SingleInstance force
OutputDebug DBGVIEWCLEAR

CoordMode, Mouse, Screen
RunAsTask()

!Tab::
	DoWheel(+1)
	return

!+TAB::
	DoWheel(-1)
	return

DoWheel(dir){
	MouseGetPos, mx, my
  active_hwnd := WinExist("A")
	;msgbox % "x" ax " y" ay " w" aw " h" ah
	;WinGet, l, list, % "ahk_exe " ProcessName " ahk_class " cls
	windows := []
  
  DetectHiddenWindows On
  arr := EnumerateAltTabWindows()
  for k, v in arr
  {
    WinGetClass, winClass, ahk_id %v%
    WinGetTitle, title, ahk_id %v%
    windows.push(v)
    ;altTabWindows .= A_Index "`nTitle: " title "`nClass: " winClass "`nhWnd: " v "`nLastActivePopup: " format("0x{:x}", GetLastActivePopup(v)) "`n`n"
  }
  
	windows := FilterWindows(active_hwnd, windows)
	w := GetNextWindow(active_hwnd, windows, dir)
  
  WinActivate, % "ahk_id " w
}

; Which monitor is the mouse on?
WhichMonitorIsMouseOn(){
	MouseGetPos, mx, my
	SysGet, mc, MonitorCount
	Loop % mc {
		SysGet, monArea, Monitor, % A_Index
		if (mx >= monAreaLeft && mx <= monAreaRight && my >= monAreaTop && my <= monAreaBottom){
			return A_Index
		}
	}
	return 0
}

FilterWindows(active_hwnd, windows){
	static window_diff := 7
	WinGetPos, ax, ay, aw, ah, % "ahk_id " active_hwnd
		ax_:=ax+aw/2,ay_:=ay+ah/2
	MouseGetPos,ax_,ay_
	monA:=MWAGetMonitor(ax_, ay_)
	;OutputDebug % "AHK| Checking windows against ax: " ax ", ay: " ay ", ah: " ah ", aw: " aw ")"
	w := []
	for i, hwnd in windows {
    WinGetPos, wx, wy, ww, wh, % "ahk_id " hwnd
		WinGetTitle, title, % "ahk_id " hwnd
    wx_:=wx+ww/2,wy_:=wy+wh/2
    monW:=MWAGetMonitor(wx_, wy_)
		
		if ((monA=monW) and title!=""){
			w.push(hwnd)
      ;MsgBox,% monA monW title hwnd
			;OutputDebug % "AHK| Adding window: " title
		} else {
			;OutputDebug % "AHK| Filtering out window: " title " ( wx: " wx ", wy:" wy ", wh: " wh ", ww: " ww 
		}
	}
	;return SortWindowsByHwnd(w)
	return SortWindowsByTitle(w)
}

; Sorts a windows array by Window Title
SortWindowsByTitle(array){
	for k, v in array {
		WinGetTitle, title, % "ahk_id " v
		str := title "|ws|" v "`n"
		;OutputDebug % "AHK| SortWindowsByTitle: " str
		sorted .= str
	}
	
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		hwnd := StrSplit(A_LoopField, "|ws|")
		hwnd := hwnd[2]
		out.push(hwnd)
	}
	return out
}

; Sorts a windows array by HWND
SortWindowsByHwnd(array){
	for k, v in array {
		sorted .= v "`n"
	}
	StringTrimRight,sorted,sorted,1
	Sort,sorted, D
	out := []
	Loop, Parse, sorted, `n
	{
		out.push(A_LoopField)
	}
	return out
}

; Finds the window after the current one in a windows array
GetNextWindow(hwnd, windows, dir){
	; Find the current window in the list
	this_index := 0
	max := windows.length()
	Loop % max {
		if (windows[A_Index] == hwnd){
			this_index := A_Index
      WinGetTitle, title, % "ahk_id " hwnd
			break
		}
	}
	if (!this_index)
		new_index:=1
	else
	{
		new_index := this_index+dir
    if (new_index < 1)
			new_index := max
		if (new_index > max)
			new_index := 1
	}
  ;MsgBox,% title " " this_index " " new_index
  return windows[new_index]
}

/*
 _      _    _               __ __      _      _                      _         _                        
| |__  | |_ | |_  _ __  _   / // /__ _ | |__  | | __ ___   ___  _ __ (_) _ __  | |_     ___   _ __  __ _ 
| '_ \ | __|| __|| '_ \(_) / // // _` || '_ \ | |/ // __| / __|| '__|| || '_ \ | __|   / _ \ | '__|/ _` |
| | | || |_ | |_ | |_) |_ / // /| (_| || | | ||   < \__ \| (__ | |   | || |_) || |_  _| (_) || |  | (_| |
|_| |_| \__| \__|| .__/(_)_//_/  \__,_||_| |_||_|\_\|___/ \___||_|   |_|| .__/  \__|(_)\___/ |_|   \__, |
                 |_|                                                    |_|                        |___/ 
RunAsTask() - Auto-elevates script without UAC prompt |  http://ahkscript.org/boards/viewtopic.php?t=4334        
_________________________________________________________________________________________________________
*/
 
RunAsTask() {                         ;  By SKAN,  http://goo.gl/yG6A1F,  CD:19/Aug/2014 | MD:22/Aug/2014
 
  Local CmdLine, TaskName, TaskExists, XML, TaskSchd, TaskRoot, RunAsTask
  Local TASK_CREATE := 0x2,  TASK_LOGON_INTERACTIVE_TOKEN := 3 
 
  Try TaskSchd  := ComObjCreate( "Schedule.Service" ),    TaskSchd.Connect()
    , TaskRoot  := TaskSchd.GetFolder( "\" )
  Catch
      Return "", ErrorLevel := 1    
 
  CmdLine       := ( A_IsCompiled ? "" : """"  A_AhkPath """" )  A_Space  ( """" A_ScriptFullpath """"  )
  TaskName      := "[RunAsTask] " A_ScriptName " @" SubStr( "000000000"  DllCall( "NTDLL\RtlComputeCrc32"
                   , "Int",0, "WStr",CmdLine, "UInt",StrLen( CmdLine ) * 2, "UInt" ), -9 )
 
  Try RunAsTask := TaskRoot.GetTask( TaskName )
  TaskExists    := ! A_LastError 
 
 
  If ( not A_IsAdmin and TaskExists )      { 
 
    RunAsTask.Run( "" )
    ExitApp
 
  }
 
  If ( not A_IsAdmin and not TaskExists )  { 
 
    Run *RunAs %CmdLine%, %A_ScriptDir%, UseErrorLevel
    ExitApp
 
  }
 
  If ( A_IsAdmin and not TaskExists )      {  
 
    XML := "
    ( LTrim Join
      <?xml version=""1.0"" ?><Task xmlns=""http://schemas.microsoft.com/windows/2004/02/mit/task""><Regi
      strationInfo /><Triggers /><Principals><Principal id=""Author""><LogonType>InteractiveToken</LogonT
      ype><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><MultipleInstancesPolic
      y>Parallel</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><
      StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate>
      <StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAva
      ilable><IdleSettings><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleS
      ettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><
      RunOnlyIfIdle>false</RunOnlyIfIdle><DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteApp
      Session><UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine><WakeToRun>false</WakeToRun><
      ExecutionTimeLimit>PT0S</ExecutionTimeLimit></Settings><Actions Context=""Author""><Exec>
      <Command>"   (  A_IsCompiled ? A_ScriptFullpath : A_AhkPath )       "</Command>
      <Arguments>" ( !A_IsCompiled ? """" A_ScriptFullpath  """" : "" )   "</Arguments>
      <WorkingDirectory>" A_ScriptDir "</WorkingDirectory></Exec></Actions></Task>
    )"    
 
    TaskRoot.RegisterTask( TaskName, XML, TASK_CREATE, "", "", TASK_LOGON_INTERACTIVE_TOKEN )
 
  }         
 
Return TaskName, ErrorLevel := 0
} ; _____________________________________________________________________________________________________

MWAGetMonitor(Mx := "", My := "")
{
	if  (!Mx or !My) 
	{
		; if Mx or My is empty, revert to the mouse cursor placement
		Coordmode, Mouse, Screen	; use Screen, so we can compare the coords with the sysget information`
		MouseGetPos, Mx, My
	}

	SysGet, MonitorCount, 80	; monitorcount, so we know how many monitors there are, and the number of loops we need to do
	Loop, %MonitorCount%
	{
		SysGet, mon%A_Index%, Monitor, %A_Index%	; "Monitor" will get the total desktop space of the monitor, including taskbars

		if ( Mx >= mon%A_Index%left ) && ( Mx < mon%A_Index%right ) && ( My >= mon%A_Index%top ) && ( My < mon%A_Index%bottom )
		{
			ActiveMon := A_Index
			break
		}
	}
	return ActiveMon
}

EnumerateAltTabWindows()
{
   AltTabList := []
   WinGet, list, List
   Loop % list
   {
      if IsAltTabWindow(list%A_Index%)
         AltTabList.Push(list%A_Index%)
   }
   Return AltTabList
}

IsAltTabWindow(hwnd)
{
   static ImmersiveShell, IApplicationViewCollection, MONITOR_DEFAULTTONULL := 0, VirtualDesktopAltTabFilter := "null", PropEnumProcEx := RegisterCallback("PropEnumProcEx", "Fast", 4)
   if (VirtualDesktopAltTabFilter = "null")
   {
      RegRead, VirtualDesktopAltTabFilter, HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced, VirtualDesktopAltTabFilter
      OSbuildNumber := StrSplit(A_OSVersion, ".")[3]
      if (OSbuildNumber < 14393)
      {
         msgbox Your %A_OSVersion% can not handle virtual desktops
         exitapp
      }
      else if (OSbuildNumber <= 17134)   ; Windows 10 1607 to 1803 and Windows Server 2016
         IID_IApplicationViewCollection := "{2C08ADF0-A386-4B35-9250-0FE183476FCC}"
      else
         IID_IApplicationViewCollection := "{1841C6D7-4F9D-42C0-AF41-8747538F10E5}"
      if !(ImmersiveShell := ComObjCreate(CLSID_ImmersiveShell := "{C2F03A33-21F5-47FA-B4BB-156362A2F239}", IID_IUnknown := "{00000000-0000-0000-C000-000000000046}"))
      {
         MsgBox ImmersiveShell not supported.
         ExitApp
      }
      if !(IApplicationViewCollection := ComObjQuery(ImmersiveShell, IID_IApplicationViewCollection, IID_IApplicationViewCollection))
      {
         MsgBox IApplicationViewCollection interface not supported.
         ExitApp	
      }
   }
   WinGetClass, winClass, ahk_id %hWnd%
   if (winClass = "Windows.UI.Core.CoreWindow")
      return
   if (winClass = "ApplicationFrameWindow")
   {
      varsetcapacity(ApplicationViewCloakType, 4, 0)
      DllCall("EnumPropsEx", "uptr", hWnd, "ptr", PropEnumProcEx, "ptr", &ApplicationViewCloakType)
      if (numget(ApplicationViewCloakType, 0, "int") = 1)   ; https://github.com/kvakulo/Switcheroo/commit/fa526606d52d5ba066ba0b2b5aa83ed04741390f
         return
   }
   ; if !DllCall("MonitorFromWindow", "uptr", hwnd, "uint", MONITOR_DEFAULTTONULL, "ptr")   ; test if window is shown on any monitor. alt-tab shows any window even if window is out of monitor.
   ;   return
   DllCall(NumGet(NumGet(IApplicationViewCollection+0)+6*A_PtrSize), "ptr", IApplicationViewCollection, "uptr", hwnd, "ptr*", pView)   ; GetViewForHwnd
   if pView
   {
      DllCall(NumGet(NumGet(pView+0)+27*A_PtrSize), "ptr", pView, "int*", ShowInSwitchers)   ; GetShowInSwitchers
      ObjRelease(pView)
   }
   if ShowInSwitchers and ((VirtualDesktopAltTabFilter = 0) or IsWindowOnCurrentVirtualDesktop(hwnd))
      return true
   return
}

GetLastActivePopup(hwnd)
{
   static GA_ROOTOWNER := 3
   hwnd := DllCall("GetAncestor", "uptr", hwnd, "uint", GA_ROOTOWNER, "ptr")
   hwnd := DllCall("GetLastActivePopup", "uptr", hwnd, "ptr")
   return hwnd
}

IsWindowOnCurrentVirtualDesktop(hwnd)
{
   static IVirtualDesktopManager
   if !IVirtualDesktopManager
      IVirtualDesktopManager := ComObjCreate(CLSID_VirtualDesktopManager := "{AA509086-5CA9-4C25-8F95-589D3C07B48A}", IID_IVirtualDesktopManager := "{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}")
   DllCall(NumGet(NumGet(IVirtualDesktopManager+0), 3*A_PtrSize), "ptr", IVirtualDesktopManager, "uptr", hwnd, "int*", onCurrentDesktop)   ; IsWindowOnCurrentVirtualDesktop
   return onCurrentDesktop
}

PropEnumProcEx(hWnd, lpszString, hData, dwData)
{
   if (strget(lpszString, "UTF-16") = "ApplicationViewCloakType")
   {
      numput(hData, dwData+0, 0, "int")
      return false
   }
   return true
}
1 Like