You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用AutoHotkey实现两个浏览器窗口的操作同步?

问题描述

需要同步两个火狐浏览器中打开的Google Earth窗口操作(包括鼠标移动、缩放、拖拽、键盘输入等),现有AutoHotkey v2脚本中:

  • F8可正常检测并显示目标窗口详情
  • F9用于切换同步控制的开启/关闭状态
    但核心同步功能失效,输入事件无法镜像到第二个窗口。
现有脚本
#Requires AutoHotkey v2.0
#SingleInstance Force
SetTitleMatchMode 2  ; This makes it match "Google Earth" anywhere in title

global sync := false
global geTitle := "Google Earth"

global debugMode := true  ; NEW: Enable debug messages

; Helper function for debugging
DebugMsg(msg) {
    global debugMode
    if debugMode {
        ToolTip(msg)
        SetTimer(() => ToolTip(), -3000)
    }
}

; For debugging
F8:: {
    winList := WinGetList("Google Earth ahk_exe firefox.exe")
    MsgBox("Found " winList.Length " windows")
    ; Show details of each window
    for id in winList {
        title := WinGetTitle("ahk_id " id)
        MsgBox("Window ID: " id "`nTitle: " title)
    }
}

F9::
{
    global sync
    sync := !sync
    ToolTip("Google Earth SYNC " . (sync ? "ON" : "OFF"))
    SetTimer(() => ToolTip(), -800)
}

F12::ExitApp()

; Using your window title variable - THIS IS THE FIX
#HotIf sync && WinActive("Google Earth ahk_exe firefox.exe")

~LButton::MirrorKey("LButton")
~RButton::MirrorKey("RButton")
~MButton::MirrorKey("MButton")
~WheelUp::MirrorKey("WheelUp")
~WheelDown::MirrorKey("WheelDown")
~a::MirrorKey("a")
~d::MirrorKey("d")
~w::MirrorKey("w")
~s::MirrorKey("s")
~q::MirrorKey("q")
~e::MirrorKey("e")
~Left::MirrorKey("Left")
~Right::MirrorKey("Right")
~Up::MirrorKey("Up")
~Down::MirrorKey("Down")
~NumpadAdd::MirrorKey("NumpadAdd")
~NumpadSub::MirrorKey("NumpadSub")

#HotIf

MirrorKey(key)
{
    activeID := WinExist("A")
    winList := WinGetList("Google Earth ahk_exe firefox.exe")  ; Using your title variable

    if winList.Length < 2
        return

    for thisID in winList
    {
        if (thisID != activeID)
        {
            ControlSend "{Blind}{" key "}","MozillaCompositorWindowClass1" , "ahk_id " thisID
        }
    }
}
调试建议
  • 验证控件类名正确性:火狐浏览器的控件类名可能并非MozillaCompositorWindowClass1,使用AutoHotkey自带的Window Spy工具,定位目标Google Earth窗口的实际控件类名,替换到ControlSend的第二个参数中;也可以直接留空该参数,让ControlSend自动选择窗口的顶层控件。
  • 增强调试日志:在MirrorKey函数的ControlSend行前后添加调试输出,确认代码是否执行到发送逻辑,以及目标窗口ID是否正确:
    DebugMsg("Attempting to send " key " to window " thisID)
    ControlSend "{Blind}{" key "}","目标控件类名" , "ahk_id " thisID
    DebugMsg("Sent " key " to window " thisID)
    
  • 测试无~前缀的热键:暂时去掉热键前的~前缀,避免原事件干扰,确认同步功能生效后再恢复。
  • 检查#HotIf条件匹配:使用WinGetTitle("A")获取当前激活窗口的完整标题,确认是否包含"Google Earth";若标题有动态后缀,可调整SetTitleMatchMode 3(精确匹配)或修改WinActive条件为仅匹配进程:WinActive("ahk_exe firefox.exe")。
  • 替换ControlSend为更可靠的发送方式:对于鼠标移动、拖拽这类非键盘事件,ControlSend支持性较差,需单独处理:
    • 鼠标移动:获取当前窗口的相对鼠标坐标,转换为目标窗口的屏幕坐标后调用MouseMove
    • 拖拽:结合LButton的按下/释放事件,在目标窗口模拟按下、移动、释放的完整流程
替代实现方案

以下是更完善的同步脚本,支持鼠标移动、拖拽、键盘、滚轮事件的同步:

#Requires AutoHotkey v2.0
#SingleInstance Force
SetTitleMatchMode 2

global syncEnabled := false
global targetWinTitle := "Google Earth ahk_exe firefox.exe"

; 调试开关
global debug := true

DebugLog(msg) {
    if debug {
        ToolTip(msg)
        SetTimer(() => ToolTip(), -2000)
    }
}

; 调试:列出目标窗口
F8:: {
    winList := WinGetList(targetWinTitle)
    MsgBox("找到 " winList.Length " 个目标窗口")
    for idx, id in winList {
        MsgBox("窗口ID: " id "`n标题: " WinGetTitle("ahk_id " id))
    }
}

; 切换同步状态
F9:: {
    global syncEnabled
    syncEnabled := !syncEnabled
    ToolTip("同步状态: " (syncEnabled ? "开启" : "关闭"))
    SetTimer(() => ToolTip(), -1000)
}

F12::ExitApp()

; 同步键盘事件
#HotIf syncEnabled && WinActive(targetWinTitle)
~*a::~*d::~*w::~*s::~*q::~*e::
~*Left::~*Right::~*Up::~*Down::
~*NumpadAdd::~*NumpadSub::
    SyncKey(A_ThisHotkey)
return

; 同步鼠标按键
~*LButton::~*RButton::~*MButton::
    SyncMouseButton(A_ThisHotkey)
return

; 同步滚轮
~*WheelUp::~*WheelDown::
    SyncWheel(A_ThisHotkey)
return

#HotIf

; 同步鼠标移动(持续监听)
SetTimer(SyncMouseMove, 10)

SyncKey(key) {
    activeWin := WinExist("A")
    winList := WinGetList(targetWinTitle)
    if (winList.Length < 2) return

    key := StrReplace(key, "~*", "")
    for winId in winList {
        if (winId != activeWin) {
            ControlSend("{Blind}{" key "}",, "ahk_id " winId)
            DebugLog("发送按键 " key " 到窗口 " winId)
        }
    }
}

SyncMouseButton(btn) {
    activeWin := WinExist("A")
    winList := WinGetList(targetWinTitle)
    if (winList.Length < 2) return

    btn := StrReplace(btn, "~*", "")
    state := GetKeyState(btn, "P")
    for winId in winList {
        if (winId != activeWin) {
            if (state) {
                ControlSend("{" btn " down}",, "ahk_id " winId)
                DebugLog(btn " 按下 到窗口 " winId)
            } else {
                ControlSend("{" btn " up}",, "ahk_id " winId)
                DebugLog(btn " 释放 到窗口 " winId)
            }
        }
    }
}

SyncWheel(dir) {
    activeWin := WinExist("A")
    winList := WinGetList(targetWinTitle)
    if (winList.Length < 2) return

    dir := StrReplace(dir, "~*", "")
    for winId in winList {
        if (winId != activeWin) {
            ControlSend("{" dir "}",, "ahk_id " winId)
            DebugLog("滚轮 " dir " 到窗口 " winId)
        }
    }
}

SyncMouseMove() {
    if (!syncEnabled || !WinActive(targetWinTitle)) return
    
    activeWin := WinExist("A")
    winList := WinGetList(targetWinTitle)
    if (winList.Length < 2) return

    ; 获取当前窗口的相对鼠标坐标
    MouseGetPos(&x, &y, &activeWin)
    WinGetClientPos(&cx, &cy, &cw, &ch, "ahk_id " activeWin)
    relX := x - cx
    relY := y - cy

    for winId in winList {
        if (winId != activeWin) {
            ; 转换为目标窗口的屏幕坐标
            WinGetClientPos(&tx, &ty, &tw, &th, "ahk_id " winId)
            targetX := tx + relX
            targetY := ty + relY
            ; 移动鼠标到目标窗口对应位置
            MouseMove(targetX, targetY, 0, "R")
            DebugLog("鼠标移动到窗口 " winId " 坐标: " targetX "," targetY)
        }
    }
}

内容的提问来源于stack exchange,提问作者RaviS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 13:34:52