メーカー案内がないKVMモニターの入力切替を、GNOME・ddcutil・PowerShell標準APIで実装する手順と検証範囲を整理します。
関連記事としては
Blog全エンジニアに送る、JAPANNEXT KVMという選択肢:4K 120HzとUSB-C 65Wが4万円台以下に降りてきたJAPANNEXT JN-IPS27G120U2-HSPC6を、WindowsノートとLinuxノートを一日に何度も切り替える実運用でレビュー。4K 120Hz、USB-C 65W給電、KVM、ノングレアIPSを4万円前後で狙える意味と、KVM対応モニター10選の購入導線を整理します。→ もあわせて読むと、今回の論点とのつながりを把握しやすくなります。本記事で扱う JN-IPS27G120U2-HSPC6の製品ページ、他のKVM対応モニターを探すなら モニター一覧でKVM・USB-C・4Kを絞り込む も参照してください。
JAPANNEXTのKVMモニターをLinux、Windows、Macで共有していると、入力を切り替えるたびにモニター背面のボタンやOSDを操作することになります。製品レビューでは画面やKVM機能そのものを扱えても、メーカーがホットキー実装の手順まで案内しているとは限りません。
今回の実装では、モニターの入力ソースセレクターにあたるDDC/CIのVCPコード 0x60 を直接書き換えます。LinuxからWindowsへはGNOMEのカスタムショートカットと ddcutil を組み合わせ、WindowsからLinuxへはPowerShellから dxva2.dll の SetVCPFeature を呼び出しました。
Windows側にControlMyMonitorなどの外部CLIを入れないため、会社PCで追加のexe実行が禁止されている環境でも検討しやすい構成です。ただし、PowerShellそのものの実行ポリシーやAppLockerの制限を回避する方法ではありません。
ここで切り替えているのはOSではなく、モニターが表示する入力端子です。PC側のログイン状態やUSB機器の接続状態は別に残るため、画面切替後にキーボードやマウスがどのPCへ渡るかは、モニターのKVM実装とUSB配線にも左右されます。
判断の基準は、次の3点です。モニターがDDC/CIの入力切替に対応していること、現在の入力値と切替先の値を確認できること、そして画面だけでなくUSB KVMの復帰動作も自分の構成で確認できることです。どれか1つでも確認できない場合は、OSD操作を残したほうが安全です。
Linux側では、GNOME(Wayland)のカスタムショートカットにシェルスクリプトを割り当てます。実機では Ctrl+Alt+W に /home/kiichirou/Project/homelab/scripts/switch-monitor-to-windows.sh を登録し、次の内容でWindows接続側のUSB-C入力へ切り替えました。確認日は2026年7月13日です。
#!/usr/bin/env bash
set -euo pipefail
# VCP 0x60 is the monitor input source selector.
# JN-IPS27G120U2-HSPC6 on this setup reports Windows USB-C as 0x10.
WINDOWS_INPUT="${WINDOWS_INPUT:-0x10}"
if ! command -v ddcutil >/dev/null 2>&1; then
echo "ddcutil is not installed. Install it with: sudo apt install ddcutil" >&2
exit 127
fi
ddcutil setvcp 60 "$WINDOWS_INPUT"
60 はVCPコード 0x60、既定の 0x10 はこの実機でWindowsが接続されている入力値です。VCP値は端子名そのものではなく、モニター内部の入力ソース番号なので、別の機種や配線では同じ値にならない可能性があります。
まず ddcutil detect でディスプレイが見えているかを確認し、現在値を読める環境なら ddcutil getvcp 60 で記録してから書き込みます。getvcp が失敗する場合でも、モニターのOSDに表示された入力状態と、setvcp 実行後の画面を照合できます。ただし、OSDだけではVCPの数値そのものは確認できません。
ddcutil detect
ddcutil getvcp 60
ddcutil setvcp 60 0x10
ddcutil getvcp 60 の結果が、たとえば current value = 0x1A のように表示されれば、現在の入力値を数値として記録できます。setvcp 後に再度 getvcp 60 を実行し、値が変わったことと画面の入力表示が一致するかを確認してください。
GNOMEの設定では「設定」から「キーボード」へ進み、「カスタムショートカット」を追加します。コマンド欄にはスクリプトの絶対パスを指定してください。実行権限がない場合は、先に次を実行します。
chmod +x /home/kiichirou/Project/homelab/scripts/switch-monitor-to-windows.sh
Waylandでは、X11時代のキーフックツールをそのまま持ち込む方法が安定しないことがあります。今回の構成はキー入力を横取りするのではなく、GNOMEが提供するカスタムショートカットから通常のプロセスとしてスクリプトを起動するため、キー入力監視の仕組みを別途常駐させずに済みました。
Windows側では、外部CLIを使わずPowerShellのP/Invokeで user32.dll と dxva2.dll を直接呼び出します。EnumDisplayMonitors で表示モニターを列挙し、物理モニターのハンドルを取得してから SetVCPFeature でVCP 0x60 を書き込みます。
この実機ではLinux側の入力値が 0x1A、Windows側が 0x10 でした。値はモニターの機種や配線によって変わるため、下のスクリプトを別のモニターへそのまま移植できるとは限りません。
# switch-monitor-to-linux.ps1
[CmdletBinding()]
param(
[ValidateSet("Linux", "Windows")]
[string]$Target = "Linux"
)
$inputValues = @{
Linux = [uint32]0x1A
Windows = [uint32]0x10
}
if (-not ("KvmDdcNative" -as [type])) {
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
public static class KvmDdcNative
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct PHYSICAL_MONITOR
{
public IntPtr hPhysicalMonitor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szPhysicalMonitorDescription;
}
private delegate bool MonitorEnumProc(
IntPtr hMonitor,
IntPtr hdcMonitor,
ref RECT lprcMonitor,
IntPtr dwData
);
[DllImport("user32.dll")]
private static extern bool EnumDisplayMonitors(
IntPtr hdc,
IntPtr lprcClip,
MonitorEnumProc lpfnEnum,
IntPtr dwData
);
[DllImport("dxva2.dll", SetLastError = true)]
private static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor,
out uint pdwNumberOfPhysicalMonitors
);
[DllImport("dxva2.dll", SetLastError = true)]
private static extern bool GetPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor,
uint dwPhysicalMonitorArraySize,
[Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray
);
[DllImport("dxva2.dll", SetLastError = true)]
private static extern bool SetVCPFeature(
IntPtr hPhysicalMonitor,
byte bVCPCode,
uint dwNewValue
);
[DllImport("dxva2.dll", SetLastError = true)]
private static extern bool DestroyPhysicalMonitor(
IntPtr hPhysicalMonitor
);
public static bool SetInput(
byte vcpCode,
uint value,
out int total,
out int success
)
{
total = 0;
success = 0;
MonitorEnumProc callback = delegate(
IntPtr hMonitor,
IntPtr hdcMonitor,
ref RECT rect,
IntPtr data
)
{
uint count;
if (!GetNumberOfPhysicalMonitorsFromHMONITOR(
hMonitor, out count) || count == 0)
{
return true;
}
PHYSICAL_MONITOR[] monitors =
new PHYSICAL_MONITOR[(int)count];
if (!GetPhysicalMonitorsFromHMONITOR(
hMonitor, count, monitors))
{
return true;
}
foreach (PHYSICAL_MONITOR monitor in monitors)
{
total++;
if (SetVCPFeature(
monitor.hPhysicalMonitor,
vcpCode,
value))
{
success++;
}
if (monitor.hPhysicalMonitor != IntPtr.Zero)
{
DestroyPhysicalMonitor(
monitor.hPhysicalMonitor);
}
}
return true;
};
return EnumDisplayMonitors(
IntPtr.Zero,
IntPtr.Zero,
callback,
IntPtr.Zero
);
}
}
'@
}
$total = 0
$success = 0
$value = $inputValues[$Target]
$result = [KvmDdcNative]::SetInput(
0x60,
$value,
[ref]$total,
[ref]$success
)
if (-not $result -or $total -eq 0 -or $success -eq 0) {
throw "DDC/CIでモニター入力を切り替えられませんでした。DDC/CI設定を確認してください。"
}
Write-Host "モニター入力を $Target に切り替えました。"
Write-Host ("VCP 0x60 = 0x{0:X2} / 成功: {1}/{2}" -f $value, $success, $total)
EnumDisplayMonitors で見つかった物理モニターすべてに SetVCPFeature を試すため、複数台接続していても対象を取りこぼしにくい構成です。実行はこうなります。
powershell.exe -ExecutionPolicy Bypass -File .\switch-monitor-to-linux.ps1
Windowsへ戻す場合は -Target Windows を渡します。
powershell.exe -ExecutionPolicy Bypass -File .\switch-monitor-to-linux.ps1 -Target Windows
-ExecutionPolicy Bypass は、このPowerShellプロセスに限って未署名スクリプトの実行ブロックを一時的に無視するフラグです。AppLockerのようなアプリケーション実行制御は別レイヤーで動いているため、このフラグを付けてもAppLockerのルール自体を回避できるわけではありません。
ホットキーには、このスクリプトを呼び出すショートカットファイル(.lnk)を作成し、そのプロパティにある「ショートカットキー」欄を使いました。タスクスケジューラや常駐型のキー割り当てツールは使っていません。
⚠️ 複数台のモニターを接続している場合、このスクリプトは見つかった物理モニターすべてにVCP 0x60 を書き込みます。1台構成のこの実機では意図通りに動きますが、複数台環境では意図しないモニターまで切り替わる可能性があります。対象を絞り込む拡張案は次のセクションにまとめましたが、この実機では検証していません。
上のスクリプトは対象を限定せず、見つかった物理モニターすべてへ書き込みます。複数台のモニターを同時に使っている環境では、GetPhysicalMonitorsFromHMONITOR が返す物理モニターの説明文字列(szPhysicalMonitorDescription)で対象を絞り込む拡張が考えられます。
# ⚠️ 未検証の拡張案。この実機は1台構成のため動作確認していません
param(
[ValidateSet("Linux", "Windows")]
[string]$Target = "Linux",
[string]$MonitorDescription = "" # 空文字列なら全物理モニターが対象
)
# SetInput の第3引数を targetDescription として受け取り、
# szPhysicalMonitorDescription が部分一致した物理モニターだけに
# SetVCPFeature を実行するよう foreach 内へ条件分岐を追加する。
説明文字列は機種や接続構成によって空文字列になったり、複数台で同じ文字列になったりすることがあります。同じ文字列のモニターが複数ある場合はこの方法だけでは一意に識別できないため、EDIDや物理モニターのハンドルを使ったより厳密な識別が必要になります。この拡張を使う場合は、まず1台構成で SetVCPFeature の呼び出し回数と対象が期待通りかを確認してから、複数台構成へ展開してください。
上のPowerShell例は、指定した値を SetVCPFeature で書き込むだけです。Windows側で現在のVCP値を読み出しているわけではありません。OSDや説明書との照合だけでは、0x10 や 0x1A という数値が正しいかを確定できないため、数値を検証したい場合は GetVCPFeatureAndVCPFeatureReply を使います。
MicrosoftのAPIには、物理モニターのハンドルとVCPコードを渡して、現在値と最大値を取得する GetVCPFeatureAndVCPFeatureReply が用意されています。SetVCPFeature と同じく、先に GetPhysicalMonitorsFromHMONITOR で取得した各物理モニターのハンドルに対して呼び出します。
P/Invoke宣言は次の形です。
[DllImport("dxva2.dll", SetLastError = true)]
private static extern bool GetVCPFeatureAndVCPFeatureReply(
IntPtr hPhysicalMonitor,
byte bVCPCode,
out uint pvct,
out uint pdwCurrentValue,
out uint pdwMaximumValue
);
たとえば、SetVCPFeature の直前に次のような読み出しを行えば、書き込み前の値を記録できます。
uint valueType;
uint currentValue;
uint maximumValue;
bool readOk = GetVCPFeatureAndVCPFeatureReply(
monitor.hPhysicalMonitor,
0x60,
out valueType,
out currentValue,
out maximumValue
);
readOk が true なら、currentValue がその物理モニターから読み出したVCP 0x60 の現在値です。実装時は、対象の説明文字列、読み出し結果、書き込み後の結果を一緒に表示すると、複数台環境で別のモニターを操作していないか確認しやすくなります。
モニターや接続経路がDDC/CIを通さない場合、GetVCPFeatureAndVCPFeatureReply と SetVCPFeature は失敗します。また、VCP 0x60 を実装していても現在値の読み出しに対応しない機種はあり得ます。その場合、Windows側では数値をAPIで確認できないため、OSDの入力表示と、切替前後の画面を使った動作確認にとどまります。読み出せない環境で、説明書の値だけを根拠に「検証済み」とは扱わないでください。
switch-monitor-to-linux.ps1 の構造で宛先を増やすなら、$inputValues と ValidateSet に候補を追加します。SetInput はVCPコードと値を渡す汎用処理なので、宛先ごとにスクリプトを書き直す必要はありません。
[ValidateSet("Linux", "Windows", "WindowsB")]
[string]$Target = "Linux"
$inputValues = @{
Linux = [uint32]0x1A
Windows = [uint32]0x10
WindowsB = [uint32]0x11 # 例。2台目Windows機の入力値は環境ごとに要確認
}
0x11 はあくまで例示の値です。この実機は2台目のWindows機を接続していないため、実際の入力値は確認していません。KVMモニターの入力ポート一覧(OSDメニューや取扱説明書)と、Linuxの ddcutil getvcp 60、Windowsの GetVCPFeatureAndVCPFeatureReply の結果を突き合わせてから $inputValues に追加してください。
画面だけ先に切り替わり、USB入力が前のPCに残るケースもあります。VCP書き込みが成功しても、キーボード・マウスの操作先が想定通りに動くかは別途確認が必要です。
⚠️ Win→Macは未検証です。
Windows側からMac接続入力へ切り替える処理自体は、Mac専用のAPIを呼ぶ必要はなく、$inputValues にMac用のエントリを増やすだけで、同じ SetInput 呼び出しに乗るはずです。
$inputValues = @{
Linux = [uint32]0x1A
Windows = [uint32]0x10
Mac = [uint32]0x0F # 例示の値。実機のMac入力では未確認
}
ただし、この値と実際の切替結果はこの環境で確認していません。DDC/CIのVCP実装はモニター側の機能なので、OSの種類だけで可否が決まるわけではありません。実機検証なしに「動作確認済み」として扱うのは避けます。
⚠️ Mac→Linuxも未検証です。
Mac側でDDC/CIを送るには、Windowsの dxva2.dll に相当するOS標準APIがないため、別のツールが必要です。候補になり得るのは、Homebrew版の ddcutil(brew install ddcutil。Apple Siliconでは追加設定が必要な場合があります)や、Apple Silicon向けの軽量CLIである m1ddc です。どちらもこの環境では実機検証していないため、具体的なコマンド例の提示は避けます。
未検証の経路を追加するときは、まずモニターの入力値を1つずつ確認します。Windows側なら GetVCPFeatureAndVCPFeatureReply、Linux側なら ddcutil getvcp 60、Mac側なら採用するDDC/CI実装というように、OSごとに「VCP 0x60 を読み書きできるか」を分けて検証します。
OSの組み合わせを先に増やすと、画面切替の失敗とUSB KVMの復帰失敗が同じ問題に見えてしまいます。
パターンが増えても、処理そのものは SetInput(0x60, 値) から変わりません。宛先ごとにファイルを分けるより、$inputValues へ宛先を追加していくほうが、どこまで検証済みかを管理しやすくなります。
| 経路 | 実装 | 状況 |
|---|---|---|
| Linux→Windows | GNOMEカスタムショートカット + bash + ddcutil setvcp 60 0x10 | 実機確認済み(2026-07-13) |
| Windows→Linux | .lnk のショートカットキー + PowerShell SetVCPFeature(0x60, 0x1A) | 実機確認済み(2026-07-13) |
| Win→Win | $inputValues に宛先を追加する同じ処理 | コード構成として記載。接続構成ごとの実機確認が必要 |
| Win→Mac | $inputValues にMac用エントリを追加 | 未検証 |
| Mac→Linux | Homebrew版 ddcutil または m1ddc によるVCP書き込み | 未検証 |
本文やスクリプトに検証済みと未検証の値を混在させないことも重要です。未検証のエントリにはコメントで「未検証」と残し、検証後に確認日と実際のVCP値へ書き換えます。
複数台環境では、入力値だけでなく対象モニターも記録してください。VCP 0x10 が正しくても、別の物理モニターへ書き込めば、結果として意図しない画面が切り替わります。
Linux側では、画面ブランクから復帰したあとにKVM経由のキーボードとマウスが反応しなくなることがありました。これはVCP 0x60 の切替処理とは別に、PCI接続のUSB xHCIコントローラーが省電力状態から復帰する問題として切り分けます。
実機では、対象PCIデバイスの電源管理設定を auto から on へ変更しました。現在値は次のコマンドで確認できます。
cat /sys/bus/pci/devices/<PCI ID>/power/control
ここで on と表示されれば、そのデバイスについては省電力による停止を避ける設定になっています。<PCI ID> は環境ごとに異なるため、記事の値をそのままコピーしてはいけません。
まず lspci でUSB controllerやxHCIに該当するデバイスを確認し、自分の環境のPCI IDへ置き換えてください。
この対策を入れても、すべてのUSB復帰問題が解決するとは限りません。電源管理、KVM内部のUSB切替、カーネル、接続機器の相性が関係するため、画面切替のテストとは別に、ブランク復帰後の入力テストを行います。
この方法が向いているのは、KVMモニターのOSD操作を減らしたい人で、なおかつ接続先のVCP値と対象モニターを自分で確認できる人です。特に会社PCへ外部ツールを導入できない場合、Windows標準のPowerShellとショートカットファイルだけで切替経路を作れる点は実用的です。
一方、1台のPCしか使わない人や、モニターのボタン操作が数秒で済む環境では、スクリプトの保守コストのほうが大きくなります。複数台モニターを使っていて対象識別ができない場合も、すべてのモニターへ書き込む方式は避けるべきです。
⚠️ Win→MacとMac→Linuxは未検証なので、業務の主経路として採用する前に、入力値、画面復帰、USB入力の3点を自分の機材で確認してください。
メーカーのホットキー案内がなくても、VCP 0x60 へ入力値を書き込むという共通原理は使えます。ただし、実際に安定するかどうかは、OSのショートカット機構、DDC/CIの対応、対象モニターの識別、USB KVMの復帰動作まで含めて決まります。
この記事は、経路ごとに検証範囲を分けて書いています。
Linux→Windows(GNOMEカスタムショートカット + switch-monitor-to-windows.sh + ddcutil setvcp 60 0x10)は、実機で日常的に使っている構成をそのまま掲載しました。確認日は2026年7月13日です。Linux側の画面ブランク復帰後のUSB停止と、その対策(xHCIコントローラーの power/control 変更)も、実機で発生した不具合と実際に行った対処を記載しています。
Windows→Linux(PowerShellの KvmDdcNative::SetInput によるVCP書き込み)は、実際に運用しているスクリプトをそのまま掲載しました。ホットキー登録に「ショートカットのプロパティ」だけを使い、外部exeを入れていない点も実機の構成どおりです。
Windows側の現在値読み出し(GetVCPFeatureAndVCPFeatureReply)と、複数台モニター向けの対象限定拡張($MonitorDescription)は、この記事のために追加した応用例です。元の運用スクリプトは1台構成・書き込み専用のため、どちらもこの実機では動作確認していません。
Win→Winは、確認済みのWindows側API処理を土台に、値を追加すれば同じ型で拡張できることをコードで示していますが、2台目のWindows機を接続した実機検証はしていません。Win→MacとMac→Linuxも、DDC/CIの仕組み上は動く可能性があるという整理にとどまり、実機での動作確認はしていません。
setvcp の仕様と使用方法getvcp による現在値の読み出しまず ddcutil detect でモニターがDDC/CI経由で認識されているかを確認し、ddcutil getvcp 60 で現在の入力値を読みます。値が読めたら ddcutil setvcp 60 0x10 のように書き込み、画面の入力が切り替わるかを確認してください。
ddcutil detect
ddcutil getvcp 60
ddcutil setvcp 60 0x10
Linux側では、書き込み後にもう一度 ddcutil getvcp 60 を実行し、現在値が想定した値になったか確認します。Windows側は、PowerShellの GetVCPFeatureAndVCPFeatureReply を組み込んだ読み出し処理を使わない限り、現在値をAPIで確認しているわけではありません。OSD表示と画面切替だけで検証する場合は、その範囲を記録してください。
本記事のVCP値(Linux側 0x1A、Windows側 0x10)はこの実機での確認値です。モニターの機種や接続ポートが変われば別の値になるため、そのままコピーせず、Linuxでは ddcutil getvcp 60、Windowsでは GetVCPFeatureAndVCPFeatureReply またはOSDメニューで自分の環境の値を確認してください。
Windowsのサンプルは、見つかった物理モニターすべてへ書き込みます。複数台環境では、本文で紹介した対象限定の拡張案(未検証)を自分の環境で検証してから使い、書き込み後に対象以外の入力が変わっていないことを確認してください。
DDC/CI非対応、USB-CドックやKVMがDDC/CIを遮断する構成、VCP 0x60 の読み書きに対応しないモニターでは、この方法は使えません。Win→Mac・Mac→Linuxも未検証のまま案内しているため、業務の主経路として採用する前に自分の機材で確認してください。
Linux側は ddcutil getvcp 60 で現在値を読めます。Windows側は GetVCPFeatureAndVCPFeatureReply を使えば、物理モニターごとの現在値を読み出せます。読み出しに対応しない場合は、OSDメニューや取扱説明書の入力ポート一覧と、書き込み前後の画面を照合します。機種が変われば値も変わります。
実行自体はできますが、本文のスクリプトは対象を限定せず全物理モニターへ SetVCPFeature を送るため、意図しないモニターも切り替わる可能性があります。GetPhysicalMonitorsFromHMONITOR が返す説明文字列で対象を絞り込む拡張案を本文で紹介していますが、この実機では検証していません。説明文字列が一意でない場合は、その方法だけでは安全な識別になりません。
会社PCでは実行ポリシーやAppLockerで止められる場合があります。外部exeを使わない設計でも、PowerShellスクリプト自体の実行が許可されているかは別途、管理者への確認が必要です。
HW系エンジニアとして20年以上、10,000件を超える顧客訪問と2,000件を超える単独ソリューション実績。AIツールを使った個人開発やIoT農園など、Raspberry Piを使ったオートメーション化なども実践中です!エンジニア専門結婚相談所も運営中、ClaudeCodeで解決できない心の課題も解決いたします!
META-MARK
GPU・CPU・PCパーツをMetaScoreで比較できます。
1,000件超のパーツデータベース。性能・効率・コスパの3軸スコアで、カタログスペックに振り回されない選択を。