前言
入了台新电脑,揪心事情就来了。两台显示器、一套键鼠怎么在都开机的情况共用两台主机呢?而且还有来回切换的场景。
上网查,发现有东西叫KVM切换器,原理就是两台主机显示接口接入KVM的显示输入接口,USB输入接口,然后KVM在出两根视频线给显示器。切换的时候应把A主机视频信号切断,通过硬件让显示器接入B主机输出的视频信号。线多点无所谓,切换过程耗时也能接受,继续查各网友使用反馈。得到的结论就是国内这东西无异于工业垃圾...
二进二出KVM对高帧率支持有限,EDID(在Windows下切换屏幕后系统桌面程序窗口还是之前一样,没有EDID切回就乱了,mac就算拔掉一个显示器,插回去还会恢复...)寥寥无几,更别提各种黑屏闪屏乱七八糟的问题,倒是价格三五百也还好。反正网上没有说非常满意的,最多也就是个能用。
灵机一动,我买个USB切换器,写个程序监测鼠标键盘插入拔出,通过DDC/CI直接切换显示器输入源不久OJBK了吗?买个KVM我实在都没真心满意的。两台4K显示器,LG的144HZ,另一台60HZ。要放弃144HZ选择还多点,不然没办法。而且支持EDID只有一款双HDMI的支持,其他不支持。
搞起
显示器必须支持DDC/CI,不支持都不用搞了。大厂应该都支持,小作坊就不保证了。Dell、LG两个在查资料过程还是看到不少关于这两显示器问题的。下个control_my_monitor打开看看吧。
我本来想用go来写的,结果go在winodws监听USB输入输出事件没有很简单的方法,不成熟。不过找了这么多资料总有点结果,nodejs有usb包支持usb热插拔事件。既然能支持,还管它什么语言,能解决问题就是好语言。
当USB切换器切换当前主机的键盘和鼠标USB会拔出,反之另一台会插入。基本逻辑就是插入鼠标键盘,DDC/CI切换显示器输入源为当前主机视频接口,两台主机一边一套,这样理论上支持无限台主机数量。其实只要有切换器,不一定要鼠标或者键盘,随便插个什么USB都行。
LG显示器DDC/CI切换输入源比较麻烦,可以看我上一篇文章解决LG显示器无法通过DDC/CL切换输入源,其他显示器可以看看输入源端口代码。
windows这边好弄,但是mac有点麻烦。因为macBetterDisplay只能登陆后启动,不能开机直接启动(可能有办法解决,没深入研究)。然后通过USB扩展坞扩展的视频接口是个残缺DDC/CI接,没法切换输入源,调节他亮度声音倒没什么问题。难道又买一根type-C to HDMI的线?另一台直连HDMI接口的显示没有问题。
干脆就到windows上跑nodejs好了,反正只有两台主机,插入就切windows主机,拔出切回去。
我用到了winddcutil(control_my_monitor也可以,无非就是换下命令)。nodejs需要安装usb包。
直接上代码:
const usb = require('usb').usb;
const { exec } = require('child_process');
const os = require('os');
// const fs = require('fs');
// const path = require('path');
const platform = os.platform();
// const LOG_FILE = path.join(__dirname, 'usb_switch_monitor.log');
const DEVICE_VENDOR_ID = 1507;
const DEVICE_PRODUCT_ID = 1552;
const WINDOWS_DIR = 'D:\\xxx\\xxx\\'; //
console.log('USB hot plug switch monitor service started...');
// console.log(usb.getDeviceList());
// 写入日志函数
// function writeLog(message) {
// const timestamp = new Date().toISOString();
// const logMessage = `[${timestamp}] ${message}\n`;
// fs.appendFile(LOG_FILE, logMessage, (err) => {
// if (err) {
// console.error(`无法写入日志: ${err}`);
// }
// });
// }
//执行命令函数
async function executeCommand(command) {
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
// writeLog(`执行错误: ${error}`);
// return;
}
console.log(`输出: ${stdout}`);
// writeLog(`执行命令: ${command}`);
// writeLog(`输出: ${stdout}`);
});
}
// Listen for attach event
usb.on('attach', (device) => {
// console.log('USB attached:', device);
console.log('attach Vendor ID:', device.deviceDescriptor.idVendor);
console.log('attach Product ID:', device.deviceDescriptor.idProduct);
if (device.deviceDescriptor.idVendor === DEVICE_VENDOR_ID && device.deviceDescriptor.idProduct === DEVICE_PRODUCT_ID) {
console.log('检测到设备插入');
// writeLog('检测到设备插入');
if (platform === 'win32') {
// 模拟鼠标移动唤醒屏幕
executeCommand('cmd /c '+ WINDOWS_DIR + 'nircmd-x64\\nircmd.exe sendmouse move 10 10');
// writeLog('移动鼠标唤醒屏幕');
//切换LG显示器 DP端口
executeCommand('cmd /c '+ WINDOWS_DIR +'amdddc-windows.exe --i2c-source-addr 0x50 setvcp 5 0 0xD0');
// writeLog('切换LG屏幕已执行');
// 查看所有显示器
// executeCommand('cmd /c '+ WINDOWS_DIR +'winddcutil.exe detect');
executeCommand('cmd /c '+ WINDOWS_DIR +'winddcutil.exe setvcp 1 0x60 15');
// writeLog('切换屏幕已执行');
}
// else if (platform === 'darwin') {
// // macOS 的唤醒命令
// executeCommand('caffeinate -u -t 1');
// //切换HDMI-2
// executeCommand('ddcutil -d 1 setvcp xF4 x0091 --i2c-source-addr=x50 --noverify');
// //切换HDMI-1
// executeCommand('ddcutil -d 2 setvcp 60 0x11');
// // macOS 的熄屏命令
// // executeCommand('pmset displaysleepnow');
// }
else {
console.log('不支持的操作系统');
}
}
});
// Listen for detach event
usb.on('detach', (device) => {
// console.log('USB detached:', device);
console.log('detach Vendor ID:', device.deviceDescriptor.idVendor);
console.log('detach Product ID:', device.deviceDescriptor.idProduct);
if (device.deviceDescriptor.idVendor === DEVICE_VENDOR_ID && device.deviceDescriptor.idProduct === DEVICE_PRODUCT_ID) {
console.log('检测到设备拔出');
// writeLog('检测到设备插入');
if (platform === 'win32') {
// writeLog('移动鼠标唤醒屏幕');
//切换DP
executeCommand('cmd /c '+ WINDOWS_DIR +'amdddc-windows.exe --i2c-source-addr 0x50 setvcp 5 0 0x91');
// writeLog('切换LG屏幕已执行');
// executeCommand('cmd /c '+ WINDOWS_DIR +'winddcutil.exe detect');
executeCommand('cmd /c '+ WINDOWS_DIR +'winddcutil.exe setvcp 1 0x60 17');
// writeLog('切换屏幕已执行');
}
// else if (platform === 'darwin') {
// // macOS 的唤醒命令
// executeCommand('caffeinate -u -t 1');
// //切换HDMI-2
// executeCommand('ddcutil -d 1 setvcp xF4 x0091 --i2c-source-addr=x50 --noverify');
// //切换HDMI-1
// executeCommand('ddcutil -d 2 setvcp 60 0x11');
// // macOS 的熄屏命令
// // executeCommand('pmset displaysleepnow');
// }
else {
console.log('不支持的操作系统');
}
}
});
开机启动
接下来就是开机启动脚本了。windows自定义开机启动还是比较方便的,有很多种方法。注册服务、任务调度、放到启动目录,很可惜都不行。前两个开机启动后后台运行,没有黑窗口,但是检测不到显示器了...放到启动目录只能开机登陆后启动,万一在另一台主机上,这台才开机切回来屏幕还是上一台主机的。
新建个文本文档:
@echo off
start /min cmd /k "node D:\xx\xx\脚本名字.js"
改名为xxx.bat
找到了个改注册表开机启动的方法(原文):
- 用户登陆时运行: 计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
- 不管用户是否登录都要运行: 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
两者配置方式相同,只是路径不同,以下是示例:
- 右键创建 "字符串值"
- 数值名称 随意
- 数值数据 指定文件路径(把bat路径填进去)
完美解决,就是会一直有一个黑窗口。不过也好,不用单独也日志,出现问题方便查看。
结语
终于都实现了,不用KVM,也不用手动切换显示器。总算完美了,切换过程丝和KVM一样会黑屏,全靠显示器切换速度。比KVM少两跟线,没有奇奇怪怪的显示问题,USB切换器也比KVM便宜,也支持高分辨率。windows屏幕没有断开,不需要EDID维持原窗口,随便怎么切都是原来的样子。缺点就是只支持两台主机,如果没有mac,那完全可以一台主机跑一套,只监听usb插入事件,支持<=USB扩展坞支持数量。
研究了好几天,中途都想放弃了,LG显示器太磨人了。不过好在还是找到了解决方法,简直大海捞针...
妹子威武!!!
就喜欢有钱又能折腾的大妹子!!!