阅读视图

发现新文章,点击刷新页面。

邪修!让显示器支持AI、远程、手势三种控制方式

大家好,我是石小石~


解锁明基RD270Q的新玩法

前不久,明基发布了最新款式的编程系列显示器 RD270Q,很荣幸我获得了优先体验资格。刚开箱,我就被它出众的颜值所吸引。

这款显示器保留了RD系列最核心也是我最喜欢的「编程模式」,而且它还升级到144Hz 高刷 并增加了彩纸模式。这使得在长时间编码下,它能极大缓解眼部疲劳,体验感非常舒适。

接下来,我会分享借助RD270Q配套的DisplayPilot2软件,结合AI与编码,如何玩转显示器的特色功能:

  • 用 Claude code 切换显示器编程模式

  • 用手机远程操控显示器锁屏

  • 用手势实现显示屏亮度调节 (动图帧率问题,图片效果不是很明显)

同时,我会结合长时间的编码体验,验证它是否能成为程序员必备的专业显示器。

显示器控制的核心——Display Pilot 2

无论是通过 AI、手机远程还是手势来控制显示器,核心本质都是依靠电脑上运行的 “脚本” 去操控显示器硬件。借助一些键鼠模拟脚本(如 Node 的robotjs、nut-js,或Python的keyboard),我们可以通过模拟鼠标事件来间接操控软件实现功能,比如通过 Node.js 脚本实现自动移动鼠标,并双击启动软件的自动化操作:

对应代码如下:

const { mouse,straightTo,Point,Button} = require("@nut-tree-fork/nut-js");
(async () => {
  // 移动鼠标到指定位置
  await mouse.move(straightTo(new Point(10, 10)));
  console.log("鼠标移动完成!");
  // 点击鼠标
  await mouse.doubleClick(Button.LEFT);
  console.log("执行完成!");
})();

可以看出,一些复杂的软件操作,通过模拟鼠标实现还是非常麻烦的,最重要的是脚本几乎无法控制硬件。

幸运的是,明基 RD270Q 自带了配套软件 Display Pilot 2,它可以直接通过软件快速调用显示器的硬件级操作能力,以满足我们编程中的个性化控制需求。参考软件截图,它拥有非常多的显示屏操作功能,且基本都支持通过快捷键操作。

思路到这里就很清晰了:我们完全可以编写脚本,模拟键盘事件触发 Display Pilot 2 的快捷操作,从而间接实现对显示器的控制。

使用Clade code+skills控制显示屏

编程模式切换效果演示

编程模式是明基 RD 系列显示器的特色功能,在深色模式下,显示器会通过硬件级算法强化语法高亮效果,以提升长期编程的舒适度;RD270Q新增的彩纸模式,则能让界面产生类纸感的细腻色彩,满足深度护眼需求。如下图,在黑暗模式下,明基对代码的显示优化非常明显,代码对比更加鲜明,不刺眼。

而且它还搭载了莱茵认证的抗反射抗面板,即便在强光环境下使用,屏幕也不会刺眼、不产生明显眩光,长时间观看依旧舒适。

在配套软件的基础上,我们能否借助 AI 实现这些显示模式的一键自动切换呢?答案是完全可以。 比如,直接通过 AI 对话下达指令,让显示器自动切换至电子书模式

或是通过指令让 AI 精准调节屏幕亮度、音频大小等参数

原理分析——RD270Q-Opera-skills

Claude Code 为例,我们来实现这一效果。需要明确的是:AI 本身并不能直接操控显示器硬件,即便它能生成脚本,也不知道如何与显示器交互。因此,我们可以通过自定义技能(Skills) —— 比如创建一个 RD270Q-operation-skills,来为 AI 扩展控制显示器的能力。

如果你不了解 Skills,请自行百度。

该技能的项目结构如下:

RD270Q-operation-skills/
├── SKILL.md              # 元数据与指令定义
├── index.js              # 主入口:命令解析与分发
├── package.json          # 项目依赖配置
├── test.js               # 功能测试脚本
├── scripts/              # 底层操作模块
│   ├── keyboard.js       # 键盘快捷键封装
│   └── mouse.js          # 鼠标操作封装
└── references/           # 参考文档
    └── 快捷键表.md        # Display Pilot 2 完整快捷键

整个技能的核心逻辑非常简单: 将 Display Pilot 2 的快捷键功能在代码中做映射,让 AI 可以通过函数调用触发。

示例核心代码(scripts/keyboard.js):

// 键盘快捷键模块 - 封装 Display Pilot 2 所有控制功能
const { keyboard, Key } = require("@computer-use/nut-js");

// 执行快捷键组合
async function executeShortcut(...keys) {
  await keyboard.pressKey(...keys);
  await new Promise(resolve => setTimeout(resolve, 100));
  await keyboard.releaseKey(...keys);
}

// ==================== 色彩模式 ====================
// 循环切换色彩模式 Ctrl+Alt+C
async function cycleColorModes() {
  await executeShortcut(Key.LeftControl, Key.LeftAlt, Key.C);
}
// 编程亮模式 Ctrl+Alt+1
async function setCodingLight() {
  await executeShortcut(Key.LeftControl, Key.LeftAlt, Key.Num1);
}
// 编程暗模式 Ctrl+Alt+2
async function setCodingDark() {
  await executeShortcut(Key.LeftControl, Key.LeftAlt, Key.Num2);
}
// 编程纸张模式 Ctrl+Alt+0
async function setCodingPaper() {
  await executeShortcut(Key.LeftControl, Key.LeftAlt, Key.Num0);
}
// ..... 其他快捷操作


// 导出所有方法
module.exports = {
  executeShortcut,
  cycleColorModes,
  setCodingLight,
  setCodingDark,
  setMBook,
  // ...
};

我们只需要在 SKILL.md 中规范好 AI 的调用方式与指令规则,完成整套技能开发后,Claude Code 就拥有了直接操控显示器模式的能力,使用体验直接拉满。

除了编程模式的切换,凡是 Display Pilot 2 能通过快捷键实现的显示器操控功能,这个skills都能完美胜任,甚至像Display Pilot 2屏幕分区这样的高级功能,也能通过控制鼠标来模拟实现。

使用手机远程控制显示屏

很多时候,我们可能临时有事需要离开工位,如果我们突然想锁屏或者想远程控制一下鼠标执行某个简单操作就必须立刻回到工位才行。基于这中场景,实现手机远程控制显示器就非常有意义。

如下图,就是根据明基RD270Q支持的快捷键开发的一个移动端操作界面,并增加了鼠标触摸移动控制功能。

远程锁屏、鼠标控制演示

如果外出忘记锁屏,通过手机实现这个功能非常方便实用。

此外,通过移动端界面的触控区域,我们还能远程操控鼠标移动、直接打开 VSCode 等软件。是不是有点Todesk青春版的感觉?

除此之外,其他快捷操作,如编程模式、亮度调节、夜间保护调节等功能都是支持的,这里也就不一一展示了。

原理分析——websoket+node控制快捷键

远程控制的方案其实非常简单:核心就是跑在本地的一个 Node 脚本,用来模拟键盘、鼠标操作,间接通过 Display Pilot 2 控制显示器。同时启动一个 Web 服务提供移动端操作界面,借助 WebSocket 实现手机与 Node 服务实时通信,最终完成远程控制。简单涞水,就是Web 端通过WebSocket 控制本地端Node服务模拟系统快捷键操作

前端就是一个普通的 Vue 项目 , 页面上放几个控制按钮,点击时通过 WebSocket 向 Node 服务发送对应指令:

function createWebSocketServer(server) {
  const wss = new WebSocket.Server({ server, path: "/ws" });
  wss.on("connection", (ws) => {
    console.log("移动端已连接");
    ws.on("message", async (msg) => {
        const { type, action, params } = JSON.parse(msg);
        // 鼠标操作
        if (type === "mouse") {
          if (action === "move") {
            // 鼠标移动
            await mouse.move(params.x, params.y);
          } else if (action === "click") {
            // 鼠标点击
            await mouse.click(params.button);
          }
        }
        // 键盘操作
        if (type === "keyboard"){
          
        }
    });
  });
}

Node 端主要搭建 WebSocket 服务,接收移动端指令并执行系统操作。

const app = express();
const server = http.createServer(app);

// 初始化 WebSocket 服务
createWebSocketServer(server);

server.listen(PORT, () => {
  console.log(`WS 服务已启动:ws://localhost:${PORT}/ws`);
});;

具体的鼠标移动、键盘快捷键等逻辑,统一封装在 mouse.jskeyboard.js 中,底层依赖node第三方库nut-js实现鼠标和快捷键控制。

使用手势控制显示屏

RD270Q 还有个我觉得特别实用的功能 ——Visual Optimizer 视觉优化。它通过内置光传感器,能根据环境光智能同步调节屏幕亮度与色温,降低屏幕与环境的明暗反差,配合编码深色模式,长时间看代码也更柔和护眼。

不仅如此,我们还可以通过Display Pilot 2进一步调整屏幕亮度,实现个性化需求。基于Display Pilot 2,我们还能实现通过手势控制实现显示器的隔空操作,作为技术创意尝鲜、趣味交互玩具,还是得研究和尝试的。

桌面版的手势识别存在一定技术难度,恰好之前我有写过类似的技术文章:油猴+手势识别:我实现了任意网页隔空控制!索性偷个懒,在网页上实现手势识别用来控制显示器。先看看Demo效果:

  • 左手张开 + 右手滑动,即可调低屏幕亮度(左手握拳 + 右手滑动,即可调高屏幕亮度)

  • 右手握拳,可以实现一键锁屏功能

它的核心实现是基于MediaPipe,这是一个是谷歌开源的跨平台、实时轻量级多媒体机器学习框架,支持 Python、JS 等多种编程语言,借助它能轻松实现桌面级的手势识别功能。

如果你对相关技术感兴趣,可以看看这个实现

Demo:油猴+手势识别:我实现了任意网页隔空控制!

代码:《有趣的手势识别、人脸识别脚本》

Flow 智能工作流

本来我还在琢磨,能不能通过 AI 指令或远程控制,自己搭一套编码时的专属显示方案,比如打开 VS Code 就自动切换到我习惯的亮度、护眼参数等。结果发现 RD270Q 早已自带了 Flow 智能工作流,在 Display Pilot 2 里提前预设好编程、文档、设计等场景后,打开对应软件就能自动切换显示参数,省去反复调节的麻烦,真正实现了 “打开即用” 的智能个性化体验。

结语

从借助 AI 指令、移动端远程控制显示器,到创意十足的手势隔空控制,这篇文章我通过三种个性化玩法,把RD270Q显示器的自定义操控能力发挥到了极致。这些功能实现的核心,离不开Display Pilot 2对显示器本身的 稳定操控能力。

当然,即便不借助这款软件,文中的思路也可以延伸到电脑本身的快捷操作、系统级功能调用上,大家不妨顺着这个方向自行尝试拓展。

写完这篇文章已是凌晨,144Hz 高刷屏搭配显示器的深色编码模式,长时间使用眼部依然舒适,没有出现干涩、疲劳感。实际体验下来,RD270Q 的护眼技术确实做得不错,整体感受很好。

总而言之,新款 RD270Q 不仅保留了核心优势,价格也很有诚意,三千出头,上市期间会更优惠!兄弟们,不用犹豫,这次可以放心冲了。当然,要是追求极致编程体验 RD280URD280UGRD320U也也都是非常不错的选择。

最后, 附上一张深夜codding的图,希望这篇分享能为大家带来一些实用参考。

油猴脚本实现生产环境加载本地qiankun子应用

大家好,我是石小石~


qiankun架构下的调试困境

如果你公司的前端架构基于 qiankun,你一定遇到过这样一个问题:由于子应用脱离主应用独立运行,在本地开发阶段,很多和主应用的操作联动、样式交互都无法直接验证,只能把子应用部署到开发或测试环境后,才能排查这类问题。

尤其是在一些不需要做 JS 沙箱隔离的业务场景里,主子应用需要通过 eventBus 这类方式实现交互,子应用不部署上线,调试起来就非常麻烦。

那有没有办法让生产环境直接加载本地子应用来实现代码调试?

方法肯定是有的,比如在主应用里写一套便于调试的逻辑。

import { registerMicroApps, start } from 'qiankun';

// ============== 核心:根据环境变量加载 本地/线上 子应用 ==============
const isDev = process.env.IS_DEV; // webpack 注入的环境变量

// 子应用配置列表
const microApps = [
  {
    name: 'subapp-vue', // 子应用唯一名称
    // 本地开发:加载 localhost 地址;生产:加载线上地址
    entry: isDev ? 'http://localhost:8080/gcshi-web-demo' : '/gcshi-web-demo',
    container: '#subapp-container', // 子应用挂载的容器 id
    activeRule: '/vue', // 路由匹配规则
  },
];

这种写法确实可以通过特定方式触发生产环境加载本地子应用,方便调试。但不可避免地需要修改主应用代码,如果没有主应用代码权限,那就很尴尬了。

其实,针对上面这个问题,用油猴脚本就能轻松解决!

油猴脚本简介

油猴(Tampermonkey)是一款浏览器插件,允许用户在网页加载时注入自定义的 JavaScript 脚本,来增强、修改或自动化网页行为

通俗地说,借助油猴,你可以将自己的 JavaScript 代码“植入”任意网页,实现自动登录、抢单、签到、数据爬取、广告屏蔽等各种“开挂级”功能,彻底掌控页面行为。

它和谷歌插件能实现的效果几乎一致,不过更加简单。如果你是前端开发,可以直接使用油猴,因为它本质就是针对网页写js。

如果你对油猴脚本感兴趣,可以看看: 《油猴脚本实战指南》

使用油猴脚本实现生产环境加载本地子应用

如图,我用 npm run dev 启动了一个本地子应用服务。

开启插件后,页面上会出现油猴脚本的调试工具。

点击【开启代理】,主应用会自动刷新,从而加载本地子应用,全程不需要做任何额外配置。

而且它完美支持热更新,这意味着你修改本地子应用代码后,生产环境页面会同步更新,调试非常方便。

核心原理

实现生产环境加载本地子应用其实很简单:

用油猴脚本在主应用加载时进行拦截,把原本要加载的线上子应用地址,替换成本地服务地址。

你可以这么理解:主应用原本要加载 http://baidu.com/gcshi-web-demo,被脚本替换成了 http://localhost:8080/gcshi-web-demo

重写fetch

qiankun 底层依赖 import-html-entry 这个库,核心流程是通过 fetch 加载子应用 HTML 模板,再解析 CSS、JS。 所以我们只需要在页面加载早期,拦截并重写 fetch 即可。

参考:juejin.cn/post/757214…

那么问题很好解决了, 我们只需要在页面加载早期,拦截并重写 fetch 即可。


const oldFetch = window.fetch;
window.fetch = (url, ...args) => {
  // 替换域名
  if (url === 'http://baidu.com/gcshi-web-demo') {
    url = 'http://localhost:8080/gcshi-web-demo';
  }
  return oldFetch(url, ...args);
};

保证脚本最早运行

重写 window.fetch 的前提,是脚本必须比页面其他逻辑更早执行,否则重写会失效。

在油猴脚本中,可以通过添加元信息实现:

// @run-at       document-start

参考:油猴脚本的运行生命周期

我在油猴脚本里的 fetch 重写逻辑如下:

import $ from "../../gmTool/index";
const { unsafeWindow } = $;

type FetchInterceptor = (url: RequestInfo | URL, options?: RequestInit) => [RequestInfo | URL, RequestInit?] | void | false;

const win = unsafeWindow;
const rawFetch = win.fetch.bind(win);

export function onFetch(interceptor: FetchInterceptor) {
  // 如果已经被代理过,先复用原来的
  if (!(win.fetch as any).rawFetch) {
    const proxyFetch: typeof fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
      let nextInput = input;
      let nextInit = init;
      // 执行 interceptor
      try {
        const result = interceptor(nextInput, nextInit);
        if (result === false) {
          console.warn("[winnex-web-proxy] 😭😭😭😭 fetch请求被用户阻止了===========================>", nextInput);
          return Promise.reject(new Error("[winnex-web-proxy] 😭😭😭😭 fetch请求被用户阻止了"));
        }
        if (result && Array.isArray(result)) {
          nextInput = result[0];
          nextInit = result[1];
        }
      } catch (err) {
        console.error("[fetch] interceptor error:", err);
      }
      // 处理 Request 对象情况
      if (nextInput instanceof Request && nextInit) {
        nextInput = new Request(nextInput, nextInit);
        nextInit = undefined;
      }
      return rawFetch(nextInput, nextInit);
    };

    (proxyFetch as any).rawFetch = rawFetch;
    win.fetch = proxyFetch;
  }

  // 返回取消方法
  return function unProxyFetch() {
    if ((win.fetch as any).rawFetch) {
      win.fetch = rawFetch;
    }
  };
}
  • 基础使用(替换接口地址)
// 注册拦截器
const unProxy = onFetch((url, options) => {
  const u = url.toString();
  // 匹配并替换地址
  if (u === 'http://baidu.com/gcshi-web-demo') {
    return ['http://localhost:8080/gcshi-web-demo', options];
  }
});
  • 阻止某个请求
onFetch((url) => {
  if (url.toString().includes('/black-api')) {
    return false; // 拦截并拒绝
  }
});

解决跨域问题

生产环境页面加载本地 localhost:8080 可能会出现跨域,导致子应用加载失败。解决方法很简单,在 vite 或 webpack 中添加响应头配置:

  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
  },

解决热更新

默认情况下,生产环境加载子应用时,热更新会失效。原因是热更新相关的 XHR 请求前缀被替换成了主应用域名。只需要拦截 XHR 请求,修正热更新接口前缀即可。以 webpack 热更新为例,修复 sockjs-nodehot-update 两个接口就行。

使用 ajax-hook 实现 XHR 拦截,代码如下:


const appOrigin = "http://localhost:8080"
const fixHotUpdateUrl = (config: any) => {
  if (config.url.includes("sockjs-node") && appOrigin) {
    config.url = fixSockJsUrl(config.url, appOrigin);
  }
  if (config.url.includes(appName) && config.url.includes("hot-update")) {
    config.url = fixHotUpdate(config.url, appName, appOrigin);
    console.log(`[winnex-web-proxy] 热更新🚀🚀===============================> ${config.url}`);
  }
};

export const xhrProxy = (enable: boolean) => {
  if (!enable) return;
  // xhr拦截
  proxy(
    {
      //请求发起前进入
      onRequest: (config, handler) => {
        fixHotUpdateUrl(config);
        handler.next(config);
      },
      //请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功
      onError: (err, handler) => {
        handler.next(err);
      },
      //请求成功后进入
      onResponse: (response, handler) => {
        handler.next(response);
      }
    },
    unsafeWindow
  );
};

总结

在 qiankun 微前端架构中,本地子应用想要直接在生产环境调试,不必修改主应用代码、不必申请权限,通过油猴脚本重写 fetch劫持子应用入口地址,配合跨域配置XHR 拦截修复热更新,就能实现线上环境加载本地子应用,并且支持热更新,极大提升微前端联调效率。整个方案轻量、无侵入、开箱即用,非常适合前端日常调试。

❌