使用的工具

使用的工具

我使用 Windows 10 虚拟桌面来区分我的活动。我在桌面 1 上打开 Chrome,在桌面 2 上打开 MS Teams。

------          -----
Chrome          Teams
------          -----
Desktop 1       Desktop 2

当我点击团队中的链接时,我希望它会在现存的桌面 1 上的 Chrome 窗口,但它打开的是新的桌面 2 中的 Chrome 窗口。

我认为这个对话可能会有帮助,但是改变这些设置并没有什么区别: 多任务设置

我能做些什么来解决这个问题?

答案1

此解决方案将检测 Chrome 浏览器的新实例(新窗口),如果它位于您的 Teams 虚拟桌面上,则会将其移动到您的其他虚拟桌面。虽然可以对解决方案进行调整以使其发生变化,但在此移动过程中,焦点所在的虚拟桌面不会发生变化。

使用的工具

  1. 虚拟桌面
    • MScholtes 的 Github 存储库虚拟桌面包含一个免费的开源命令行工具,用于管理 Windows 10 和 Windows 11 中的虚拟桌面
    • 该工具包含许多功能,包括识别哪个虚拟桌面可见以及将特定 Windows 从一个虚拟桌面移动到另一个虚拟桌面的能力。
    • 安装:
      1. 下载Github 上的工具(ZIP)
      2. 解压到本地文件夹
      3. 在该文件夹中运行Compile.bat,将为不同版本的 Windows 创建多个可执行文件。
  2. 自动热键
    • 自动热键(AHK)是一种免费的、开源的、有据可查的 Windows 脚本语言,允许用户轻松地为各种任务创建从小型到复杂的脚本。
    • 对于您的用例,他们的WinHook命令会创建 Windows Shell Hooks 来监控 Windows 事件,同时使用很少的资源。这可以用来检测何时创建新的 Chrome 窗口。
    • 安装:导航至自动热键并按照网站上的说明进行操作。
    • 另请参阅安全浏览注意事项Norton 安全网页报告与有害程序的误报相比。

解决方案

注意:在此解决方案中,带有 MS Teams 应用程序的虚拟桌面被任意命名Teams,另一个虚拟桌面被命名为Main

  1. AutoHotKey 脚本detect_chrome_window.ahk监视 Chrome 启动的新窗口
  2. 当检测到新的 Chrome 实例时,脚本会检索其句柄/唯一 ID(窗口句柄
  3. 然后,脚本运行 Windows 批处理文件move_chrome_window.bat,同时将句柄作为参数传递
  4. 批处理脚本用途虚拟桌面检查当前虚拟桌面是否Teams
  5. 如果是,脚本会将新的 Chrome 实例从 移动TeamsMain

检测_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"
  
  )

)

相关内容