You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Electron Windows应用如何全局获取聚焦编辑器的编辑光标位置?

嘿,这个需求我之前帮人处理过,在Electron做Windows桌面应用的场景下,确实有可行的办法——毕竟要跨任意编辑器拿光标位置,纯JS肯定搞不定,得结合Windows原生API来搞,我给你拆解下具体方案:

核心思路:Windows原生API + Electron的Node.js能力

因为要获取任意聚焦编辑器的光标位置,我们得借助Windows系统提供的原生接口,再通过Electron的Node.js层去调用这些接口,毕竟Electron主进程拥有直接操作系统资源的权限。

方案一:手动调用User32.dll API(最灵活)

Windows的User32.dll里有几个关键函数能帮我们拿到光标坐标:

  • GetForegroundWindow():获取当前激活的窗口句柄
  • GetGUIThreadInfo():获取目标窗口线程的GUI信息,其中包含文本光标(caret)的窗口内相对位置
  • ClientToScreen():把窗口内的相对坐标转换成屏幕绝对坐标

具体步骤:

  1. 先给项目安装依赖,用来让Node.js调用原生DLL:
    npm install ffi-napi ref-napi
    
  2. 在Electron主进程里编写调用代码:
    const ffi = require('ffi-napi');
    const ref = require('ref-napi');
    
    // 定义Windows API需要的数据结构
    const POINT = ref.types.struct({
      x: ref.types.long,
      y: ref.types.long
    });
    const GUITHREADINFO = ref.types.struct({
      cbSize: ref.types.dword,
      flags: ref.types.dword,
      hwndActive: ref.types.void,
      hwndFocus: ref.types.void,
      hwndCapture: ref.types.void,
      hwndMenuOwner: ref.types.void,
      hwndMoveSize: ref.types.void,
      hwndCaret: ref.types.void,
      rcCaret: ref.types.rect
    });
    
    // 绑定User32.dll的函数
    const user32 = ffi.Library('user32', {
      'GetForegroundWindow': ['pointer', []],
      'GetGUIThreadInfo': ['bool', ['dword', ref.refType(GUITHREADINFO)]],
      'ClientToScreen': ['bool', ['pointer', ref.refType(POINT)]]
    });
    
    // 获取光标屏幕坐标的函数
    function getGlobalCaretPosition() {
      const guiThreadInfo = new GUITHREADINFO();
      guiThreadInfo.cbSize = ref.sizeof(GUITHREADINFO);
      
      const activeWindow = user32.GetForegroundWindow();
      // 获取当前活跃窗口的线程ID
      const threadId = ffi.Library('user32', { 
        'GetWindowThreadProcessId': ['dword', ['pointer', 'pointer']] 
      }).GetWindowThreadProcessId(activeWindow, null);
      
      // 获取GUI线程信息
      if (!user32.GetGUIThreadInfo(threadId, guiThreadInfo.ref())) {
        return null;
      }
    
      // 如果存在光标窗口,转换坐标为屏幕绝对位置
      if (guiThreadInfo.hwndCaret) {
        const caretPoint = new POINT();
        caretPoint.x = guiThreadInfo.rcCaret.left;
        caretPoint.y = guiThreadInfo.rcCaret.top;
        user32.ClientToScreen(guiThreadInfo.hwndCaret, caretPoint.ref());
        return { x: caretPoint.x, y: caretPoint.y };
      }
      return null;
    }
    
  3. 通过Electron的IPC通信,让渲染进程请求主进程调用这个函数,拿到坐标后就可以用来定位你的菜单或窗口了,比如:
    // 主进程里监听IPC请求
    ipcMain.handle('get-caret-position', async () => {
      return getGlobalCaretPosition();
    });
    
    // 渲染进程里调用
    const caretPos = await ipcRenderer.invoke('get-caret-position');
    if (caretPos) {
      // 把菜单窗口放在光标下方20px的位置,避免挡住光标
      yourMenuWindow.setPosition(caretPos.x, caretPos.y + 20);
    }
    

方案二:用封装好的第三方库(更省心)

如果你不想自己写FFI绑定的代码,可以用现成的第三方库,比如windows-caret-position,它本质上也是封装了User32的API,用起来更简单:

  1. 安装依赖:
    npm install windows-caret-position
    
  2. 在主进程里直接调用:
    const getCaretPos = require('windows-caret-position');
    const pos = getCaretPos();
    if (pos) {
      console.log(`光标坐标:x=${pos.x}, y=${pos.y}`);
      yourMenuWindow.setPosition(pos.x, pos.y + 20);
    }
    

几个要注意的点

  • 这个方案只适用于Windows平台,如果要做跨平台,Mac和Linux需要分别处理各自的系统API(比如Mac用Carbon框架,Linux用X11的接口)。
  • 少数自定义光标渲染的编辑器可能不兼容,但绝大多数标准文本编辑器(比如记事本、VS Code、Notepad++)都能正常获取到位置。
  • 一定要在Electron主进程里调用这些原生API,渲染进程直接调用会有安全限制,通过IPC通信是更规范的做法。

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

火山引擎 最新活动