答案1
此解决方案将检测 Chrome 浏览器的新实例(新窗口),如果它位于您的 Teams 虚拟桌面上,则会将其移动到您的其他虚拟桌面。虽然可以对解决方案进行调整以使其发生变化,但在此移动过程中,焦点所在的虚拟桌面不会发生变化。
使用的工具
- 虚拟桌面
- MScholtes 的 Github 存储库虚拟桌面包含一个免费的开源命令行工具,用于管理 Windows 10 和 Windows 11 中的虚拟桌面
- 该工具包含许多功能,包括识别哪个虚拟桌面可见以及将特定 Windows 从一个虚拟桌面移动到另一个虚拟桌面的能力。
- 安装:
- 下载Github 上的工具(ZIP)
- 解压到本地文件夹
- 在该文件夹中运行
Compile.bat
,将为不同版本的 Windows 创建多个可执行文件。
- 自动热键
- 自动热键(AHK)是一种免费的、开源的、有据可查的 Windows 脚本语言,允许用户轻松地为各种任务创建从小型到复杂的脚本。
- 对于您的用例,他们的
WinHook
命令会创建 Windows Shell Hooks 来监控 Windows 事件,同时使用很少的资源。这可以用来检测何时创建新的 Chrome 窗口。 - 安装:导航至自动热键并按照网站上的说明进行操作。
- 另请参阅安全浏览注意事项和Norton 安全网页报告与有害程序的误报相比。
解决方案
注意:在此解决方案中,带有 MS Teams 应用程序的虚拟桌面被任意命名Teams
,另一个虚拟桌面被命名为Main
- AutoHotKey 脚本
detect_chrome_window.ahk
监视 Chrome 启动的新窗口 - 当检测到新的 Chrome 实例时,脚本会检索其句柄/唯一 ID(窗口句柄)
- 然后,脚本运行 Windows 批处理文件
move_chrome_window.bat
,同时将句柄作为参数传递 - 批处理脚本用途虚拟桌面检查当前虚拟桌面是否
Teams
- 如果是,脚本会将新的 Chrome 实例从 移动
Teams
到Main
检测_chrome_window.ahk
- 该脚本改编自脚本作者:FanaticGuru。它们包含了大约 200 行的评论,这是一笔极其宝贵的资源。
- 您只需将
C:\path\to\batch\move_chrome_window.bat
第五行替换为您自己的路径和文件名(如果不同)
WinHook.Shell.Add("NameOfFuncToRun",,, "chrome.exe",1) ; Chrome Window Created (1 at end means created)
NameOfFuncToRun(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
{
run C:\path\to\batch\move_chrome_window.bat "%Win_Hwnd%",,Hide
}
;
;{============================
;
; Class (Nested): WinHook.Shell
;
; Method:
; Add(Func, wTitle:="", wClass:="", wExe:="", Event:=0)
;
; Desc: Add Shell Hook
;
; Parameters:
; 1) {Func} Function name or Function object to call on event
; 2) {wTitle} window Title to watch for event (default = "", all windows)
; 3) {wClass} window Class to watch for event (default = "", all windows)
; 4) {wExe} window Exe to watch for event (default = "", all windows)
; 5) {Event} Event (default = 0, all events)
;
; Returns: {Index} index to hook that can be used to Remove hook
;
; Shell Hook Events:
; 1 = HSHELL_WINDOWCREATED
; 2 = HSHELL_WINDOWDESTROYED
; 3 = HSHELL_ACTIVATESHELLWINDOW
; 4 = HSHELL_WINDOWACTIVATED
; 5 = HSHELL_GETMINRECT
; 6 = HSHELL_REDRAW
; 7 = HSHELL_TASKMAN
; 8 = HSHELL_LANGUAGE
; 9 = HSHELL_SYSMENU
; 10 = HSHELL_ENDTASK
; 11 = HSHELL_ACCESSIBILITYSTATE
; 12 = HSHELL_APPCOMMAND
; 13 = HSHELL_WINDOWREPLACED
; 14 = HSHELL_WINDOWREPLACING
; 32768 = 0x8000 = HSHELL_HIGHBIT
; 32772 = 0x8000 + 4 = 0x8004 = HSHELL_RUDEAPPACTIVATED (HSHELL_HIGHBIT + HSHELL_WINDOWACTIVATED)
; 32774 = 0x8000 + 6 = 0x8006 = HSHELL_FLASH (HSHELL_HIGHBIT + HSHELL_REDRAW)
;
; Note: ObjBindMethod(obj, Method) can be used to create a function object to a class method
; WinHook.Shell.Add(ObjBindMethod(TestClass.TestNestedClass, "MethodName"), wTitle, wClass, wExe, Event)
;
; ----------
;
; Desc: Function Called on Event
; FuncOrMethod(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
;
; Parameters:
; 1) {Win_Hwnd} window handle ID of window with event
; 2) {Win_Title} window Title of window with event
; 3) {Win_Class} window Class of window with event
; 4) {Win_Exe} window Exe of window with event
; 5) {Win_Event} window Event
;
; Note: FuncOrMethod will be called with DetectHiddenWindows On.
;
; --------------------
;
; Method: Report(ByRef Object)
;
; Desc: Report Shell Hooks
;
; Returns: string report
; ByRef Object[Index].{Func, Title:, Class, Exe, Event}
;
; --------------------
;
; Method: Remove(Index)
; Method: Deregister()
;
;{============================
;
; Class (Nested): WinHook.Event
;
; Method:
; Add(eventMin, eventMax, eventProc, idProcess, WinTitle := "")
;
; Desc: Add Event Hook
;
; Parameters:
; 1) {eventMin} lowest Event value handled by the hook function
; 2) {eventMax} highest event value handled by the hook function
; 3) {eventProc} event hook function, call be function name or function object
; 4) {idProcess} ID of the process from which the hook function receives events (default = 0, all processes)
; 5) {WinTitle} WinTitle to identify which windows to operate on, (default = "", all windows)
;
; Returns: {hWinEventHook} handle to hook that can be used to unhook
;
; Event Hook Events:
; 0x8012 = EVENT_OBJECT_ACCELERATORCHANGE
; 0x8017 = EVENT_OBJECT_CLOAKED
; 0x8015 = EVENT_OBJECT_CONTENTSCROLLED
; 0x8000 = EVENT_OBJECT_CREATE
; 0x8011 = EVENT_OBJECT_DEFACTIONCHANGE
; 0x800D = EVENT_OBJECT_DESCRIPTIONCHANGE
; 0x8001 = EVENT_OBJECT_DESTROY
; 0x8021 = EVENT_OBJECT_DRAGSTART
; 0x8022 = EVENT_OBJECT_DRAGCANCEL
; 0x8023 = EVENT_OBJECT_DRAGCOMPLETE
; 0x8024 = EVENT_OBJECT_DRAGENTER
; 0x8025 = EVENT_OBJECT_DRAGLEAVE
; 0x8026 = EVENT_OBJECT_DRAGDROPPED
; 0x80FF = EVENT_OBJECT_END
; 0x8005 = EVENT_OBJECT_FOCUS
; 0x8010 = EVENT_OBJECT_HELPCHANGE
; 0x8003 = EVENT_OBJECT_HIDE
; 0x8020 = EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED
; 0x8028 = EVENT_OBJECT_IME_HIDE
; 0x8027 = EVENT_OBJECT_IME_SHOW
; 0x8029 = EVENT_OBJECT_IME_CHANGE
; 0x8013 = EVENT_OBJECT_INVOKED
; 0x8019 = EVENT_OBJECT_LIVEREGIONCHANGED
; 0x800B = EVENT_OBJECT_LOCATIONCHANGE
; 0x800C = EVENT_OBJECT_NAMECHANGE
; 0x800F = EVENT_OBJECT_PARENTCHANGE
; 0x8004 = EVENT_OBJECT_REORDER
; 0x8006 = EVENT_OBJECT_SELECTION
; 0x8007 = EVENT_OBJECT_SELECTIONADD
; 0x8008 = EVENT_OBJECT_SELECTIONREMOVE
; 0x8009 = EVENT_OBJECT_SELECTIONWITHIN
; 0x8002 = EVENT_OBJECT_SHOW
; 0x800A = EVENT_OBJECT_STATECHANGE
; 0x8030 = EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED
; 0x8014 = EVENT_OBJECT_TEXTSELECTIONCHANGED
; 0x8018 = EVENT_OBJECT_UNCLOAKED
; 0x800E = EVENT_OBJECT_VALUECHANGE
; 0x0002 = EVENT_SYSTEM_ALERT
; 0x8016 = EVENT_SYSTEM_ARRANGMENTPREVIEW
; 0x0009 = EVENT_SYSTEM_CAPTUREEND
; 0x0008 = EVENT_SYSTEM_CAPTURESTART
; 0x000D = EVENT_SYSTEM_CONTEXTHELPEND
; 0x000C = EVENT_SYSTEM_CONTEXTHELPSTART
; 0x0020 = EVENT_SYSTEM_DESKTOPSWITCH
; 0x0011 = EVENT_SYSTEM_DIALOGEND
; 0x0010 = EVENT_SYSTEM_DIALOGSTART
; 0x000F = EVENT_SYSTEM_DRAGDROPEND
; 0x000E = EVENT_SYSTEM_DRAGDROPSTART
; 0x00FF = EVENT_SYSTEM_END
; 0x0003 = EVENT_SYSTEM_FOREGROUND
; 0x0007 = EVENT_SYSTEM_MENUPOPUPEND
; 0x0006 = EVENT_SYSTEM_MENUPOPUPSTART
; 0x0005 = EVENT_SYSTEM_MENUEND
; 0x0004 = EVENT_SYSTEM_MENUSTART
; 0x0017 = EVENT_SYSTEM_MINIMIZEEND
; 0x0016 = EVENT_SYSTEM_MINIMIZESTART
; 0x000B = EVENT_SYSTEM_MOVESIZEEND
; 0x000A = EVENT_SYSTEM_MOVESIZESTART
; 0x0013 = EVENT_SYSTEM_SCROLLINGEND
; 0x0012 = EVENT_SYSTEM_SCROLLINGSTART
; 0x0001 = EVENT_SYSTEM_SOUND
; 0x0015 = EVENT_SYSTEM_SWITCHEND
; 0x0014 = EVENT_SYSTEM_SWITCHSTART
;
; Note: ObjBindMethod(obj, Method) can be used to create a function object to a class method
; WinHook.Event.Add((eventMin, eventMax, ObjBindMethod(TestClass.TestNestedClass, "MethodName"), idProcess, WinTitle := "")
;
; ----------
;
; Desc: Function Called on Event
; FuncOrMethod(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
;
; Parameters:
; 1) {hWinEventHook} Handle to an event hook instance.
; 2) {event} Event that occurred. This value is one of the event constants
; 3) {hwnd} Handle to the window that generates the event.
; 4) {idObject} Identifies the object that is associated with the event.
; 5) {idChild} Child ID if the event was triggered by a child element.
; 6) {dwEventThread} Identifies the thread that generated the event.
; 7) {dwmsEventTime} Specifies the time, in milliseconds, that the event was generated.
;
; Note: FuncOrMethod will be called with DetectHiddenWindows On.
;
; --------------------
;
; Method: Report(ByRef Object)
;
; Returns: string report
; ByRef Object[hWinEventHook].{eventMin, eventMax, eventProc, idProcess, WinTitle}
;
; --------------------
;
; Method: UnHook(hWinEventHook)
; Method: UnHookAll()
;
;{============================
class WinHook
{
class Shell
{
Add(Func, wTitle:="", wClass:="", wExe:="", Event:=0)
{
if !WinHook.Shell.Hooks
{
WinHook.Shell.Hooks := {}, WinHook.Shell.Events := {}
DllCall("RegisterShellHookWindow", UInt, A_ScriptHwnd)
MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
OnMessage(MsgNum, ObjBindMethod(WinHook.Shell, "Message"))
}
if !IsObject(Func)
Func := Func(Func)
WinHook.Shell.Hooks.Push({Func: Func, Title: wTitle, Class: wClass, Exe: wExe, Event: Event})
WinHook.Shell.Events[Event] := true
return WinHook.Shell.Hooks.MaxIndex()
}
Remove(Index)
{
WinHook.Shell.Hooks.Delete(Index)
WinHook.Shell.Events[Event] := {} ; delete and rebuild Event list
For key, Hook in WinHook.Shell.Hooks
WinHook.Shell.Events[Hook.Event] := true
}
Report(ByRef Obj:="")
{
Obj := WinHook.Shell.Hooks
For key, Hook in WinHook.Shell.Hooks
Display .= key "|" Hook.Event "|" Hook.Func.Name "|" Hook.Title "|" Hook.Class "|" Hook.Exe "`n"
return Trim(Display, "`n")
}
Deregister()
{
DllCall("DeregisterShellHookWindow", UInt, A_ScriptHwnd)
WinHook.Shell.Hooks := "", WinHook.Shell.Events := ""
}
Message(Event, Hwnd) ; Private Method
{
DetectHiddenWindows, On
If (WinHook.Shell.Events[Event] or WinHook.Shell.Events[0])
{
WinGetTitle, wTitle, ahk_id %Hwnd%
WinGetClass, wClass, ahk_id %Hwnd%
WinGet, wExe, ProcessName, ahk_id %Hwnd%
for key, Hook in WinHook.Shell.Hooks
if ((Hook.Title = wTitle or Hook.Title = "") and (Hook.Class = wClass or Hook.Class = "") and (Hook.Exe = wExe or Hook.Exe = "") and (Hook.Event = Event or Hook.Event = 0))
return Hook.Func.Call(Hwnd, wTitle, wClass, wExe, Event)
}
}
}
class Event
{
Add(eventMin, eventMax, eventProc, idProcess := 0, WinTitle := "")
{
if !WinHook.Event.Hooks
{
WinHook.Event.Hooks := {}
static CB_WinEventProc := RegisterCallback(WinHook.Event.Message)
OnExit(ObjBindMethod(WinHook.Event, "UnHookAll"))
}
hWinEventHook := DllCall("SetWinEventHook"
, "UInt", eventMin ; UINT eventMin
, "UInt", eventMax ; UINT eventMax
, "Ptr" , 0x0 ; HMODULE hmodWinEventProc
, "Ptr" , CB_WinEventProc ; WINEVENTPROC lpfnWinEventProc
, "UInt" , idProcess ; DWORD idProcess
, "UInt", 0x0 ; DWORD idThread
, "UInt", 0x0|0x2) ; UINT dwflags, OutOfContext|SkipOwnProcess
if !IsObject(eventProc)
eventProc := Func(eventProc)
WinHook.Event.Hooks[hWinEventHook] := {eventMin: eventMin, eventMax: eventMax, eventProc: eventProc, idProcess: idProcess, WinTitle: WinTitle}
return hWinEventHook
}
Report(ByRef Obj:="")
{
Obj := WinHook.Event.Hooks
For hWinEventHook, Hook in WinHook.Event.Hooks
Display .= hWinEventHook "|" Hook.eventMin "|" Hook.eventMax "|" Hook.eventProc.Name "|" Hook.idProcess "|" Hook.WinTitle "`n"
return Trim(Display, "`n")
}
UnHook(hWinEventHook)
{
DllCall("UnhookWinEvent", "Ptr", hWinEventHook)
WinHook.Event.Hooks.Delete(hWinEventHook)
}
UnHookAll()
{
for hWinEventHook, Hook in WinHook.Event.Hooks
DllCall("UnhookWinEvent", "Ptr", hWinEventHook)
WinHook.Event.Hooks := "", CB_WinEventProc := ""
}
Message(event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) ; 'Private Method
{
DetectHiddenWindows, On
Hook := WinHook.Event.Hooks[hWinEventHook := this] ; this' is hidden param1 because method is called as func
WinGet, List, List, % Hook.WinTitle
Loop % List
if (List%A_Index% = hwnd)
return Hook.eventProc.Call(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
}
}
}
移动_chrome_窗口
- 虚拟桌面由于 Windows 10、Windows 11 以及每个版本之间存在重大更改,因此将编译可执行文件的多个版本。我使用的是 Windows 11,因此我的 VirtualDesktop 可执行文件
VirtualDesktop11.exe
可能是VirtualDesktop.exe
。如果有所不同,则应更新批处理文件以反映这一点。 - 我添加了路径到
VirtualDesktop.exe
我的小路环境变量,所以我不需要在批处理文件中指定路径。如果不这样做,则需要在批处理文件中添加路径 - 如果您对虚拟桌面使用除
Main
和以外的其他名称Teams
,则需要修改批处理文件来反映这一点。
@echo off
setlocal enabledelayedexpansion
rem Check if 'Teams' is visible
FOR /F "tokens=*" %%F IN ('VirtualDesktop /IsVisible:Teams') DO (
set myvar=%%F
if not "!myvar:is visible=!"=="!myvar!" (
rem move Chrome window from 'Teams' to 'Main'
VirtualDesktop "/GetDesktop:main" "/MoveWindowHandle:%1"
)
)