Sitemap
Colin Campbell

Colin Campbell

Technology leader who still gets his hands dirty. Building good systems and better teams. Engineer at heart. Ngāti Kahungunu | Ngāti Rangitāne. Pōneke.

I Didn’t Buy the KVM

Two laptops. Three monitors. One keyboard I actually like.

13 min readAug 31, 2026

--

This story was written with the assistance of an AI writing program — the ideas, observations, and opinions are mine; Claude helped me shape them into prose.

Not a Medium member? Read this story for free here.

For a long time, my solution was one dock and one cable: work laptop in it during the day, personal laptop in it at night, swapping the USB-C connector between the two. It works. It’s also annoying enough that I eventually did something about it.

So I went looking for a KVM, like you do. And then I looked at the price.

The price wall

A basic two-port KVM that drives a single monitor is around $110 in New Zealand. Fine. But I don’t have a single monitor; I have three, and that turns out to be the wall.

Triple-head KVMs do exist. StarTech makes one — the SV231TDPU34K, two ports, three DisplayPort outputs — and, at the time of writing, PB Tech wants $980 for it, with no stock at any branch in the country. That’s the New Zealand tax as much as anything: a small market means niche gear gets brought in on request, if at all. My screens are only 1080p, so bandwidth isn’t my problem. The problem is that switching three displays at once is specialist kit at a specialist price, whatever resolution you’re running.

A grand is a lot of money to solve a problem that mildly annoys me twice a day. So I didn’t.

What I bought instead was a UGREEN UG-30768. It’s a plastic box with a button. Two computers in, four USB devices out, about $64. My keyboard, mouse and webcam plug into it, and the button decides which laptop owns them.

Here’s what amused me: PB Tech files that plastic box in the same category as the $980 StarTech. Same shelf, fifteen times cheaper. The difference is entirely that I stopped trying to switch the video.

You don’t need to switch video

I’d had this idea sitting around for a while. The $980 was what finally made me try it.

A KVM switches video because that’s what the acronym promises. But my monitors already have two inputs each and a perfectly good input switch built into them. It’s in the on-screen display, behind four presses of a squishy button under the bezel, which is why nobody thinks of it as a switch.

That switch is addressable. It’s been addressable since the 90s. DDC/CI — Display Data Channel Command Interface — is an I2C bus riding along the spare pins of your video cable, and VESA’s MCCS spec defines a control code, 0x60, for input source. Your monitor has always been able to change its own input on command. It just never shipped with a keyboard shortcut.

So that’s what I built. A hotkey on each machine that tells all three monitors “you belong to me now”, and then a thumb on the UGREEN button to bring the keyboard, mouse and webcam across. Video path stays direct: laptop, dock, cable, panel. Nothing in the middle to degrade.

Press enter or click to view image in full size
Diagram of a two-computer, three-monitor desk setup. Laptop 1 (Windows) connects to Dock 1, and Laptop 2 (Linux) connects to Dock 2. Each dock runs its own video cable to all three monitors: Dock 1 uses DisplayPort to Monitor 1 and HDMI to Monitors 2 and 3, while Dock 2 uses the opposite — HDMI to Monitor 1 and DisplayPort to Monitors 2 and 3. Separately, a keyboard, mouse and webcam plug into a single USB switch, which connects by USB to both docks. Video paths run directly from dock to monitor
Both machines stay plugged into all three monitors. The only shared box on the desk is a $64 USB switch, and nothing but the keyboard, mouse and webcam goes through it.

One thing that diagram makes obvious: the wiring isn’t symmetrical. Monitor 1 takes DisplayPort from dock 1 and HDMI from dock 2. Monitors 2 and 3 are the other way round. That’s just how the cables reached, and it’s fine — it’s also the reason everything below toggles between DP and HDMI rather than setting one fixed input value everywhere. There is no single “switch to input 15” that would be correct on all three panels.

The Linux side: ddcutil

On Ubuntu, ddcutil does the talking. You need the I2C dev interface loaded and yourself in the right group, or every command comes back with a permissions error that looks like a hardware fault:

sudo apt install ddcutil
echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf
sudo modprobe i2c-dev
sudo usermod -aG i2c $USER # log out and back in

The modules-load.d line matters. modprobe alone gets you working until the next reboot, at which point your hotkeys silently do nothing and you spend ten minutes suspecting the monitors.

Then find out what you’ve got:

ddcutil detect
Invalid display
I2C bus: /dev/i2c-11
DRM_connector: card1-eDP-1
EDID synopsis:
Mfg id: <PANEL-VENDOR>
Product code: 16749 (0x416d)
This is a laptop display. Laptop displays do not support DDC/CI.
Display 1
I2C bus: /dev/i2c-13
DRM_connector: card1-DP-2
EDID synopsis:
Mfg id: <VENDOR>
Model: <MODEL>
Product code: 14597 (0x3905)
Serial number: SERIAL-CENTRE
VCP version: 2.2
Display 2
I2C bus: /dev/i2c-17
DRM_connector: card1-DP-5
EDID synopsis:
Mfg id: <VENDOR>
Model: <MODEL>
Product code: 14597 (0x3905)
Serial number: SERIAL-RIGHT
VCP version: 2.2
Display 3
I2C bus: /dev/i2c-18
DRM_connector: card1-DP-7
EDID synopsis:
Mfg id: <VENDOR>
Model: <MODEL>
Product code: 14598 (0x3906)
Serial number: SERIAL-LEFT
VCP version: 2.2

Three things in there are worth pausing on.

The laptop’s own panel comes back as Invalid display, because eDP has no DDC/CI. That's not a fault, it's just a boundary: everything in this article applies to external monitors only. If you want the laptop's built-in screen to join in, that's a compositor problem, not a DDC one.

All three externals report the same model name. Address them by model and you’ll be talking to whichever one ddcutil found first. Serial is the only thing that distinguishes them — which is why the config keys off serials, and why I gave up on anything more readable.

And ddcutil’s own numbering is not the same as my physical layout. Display 1 is my centre screen, Display 2 is the right, Display 3 is the left. The I2C buses aren’t contiguous either — 13, 17, 18. None of that is stable enough to script against.

Next, ask each monitor what input source it will accept. The spec standardises the code at 0x60, but the values are up to the vendor. This loop pulls the serials straight out of detect and asks each panel in turn:

ddcutil detect | awk '/^ +Serial number:/ && NF==3 {print $3}' | while read -r sn; do
printf '\n=== %s ===\n' "$sn"
ddcutil --sn "$sn" capabilities | grep -A5 'Feature: 60'
done
=== SERIAL-CENTRE ===
Feature: 60 (Input Source)
Values:
0f: DisplayPort-1
11: HDMI-1

All three came back identical, which is the answer I was hoping for: 0x0f is DisplayPort, 0x11 is HDMI, everywhere. That's what makes a simple two-value toggle viable. The asymmetric cabling stops mattering, because I'm never asking a panel to go to a particular machine — I'm asking it to go to its other input, and both panels agree on what those are called.

Run that before you write any config. Vendors are free to number inputs however they like, and if your three disagree you’ll want to know now rather than halfway through debugging a bind.

While you’re in there, the full dump is worth a look:

ddcutil --sn SERIAL-LEFT capabilities | head -40

Mine reports brightness at 0x10, contrast at 0x12, and power at 0xD6 with five valid values. It also reports a wall of vendor-specific features that ddcutil can't parse and prints noisy errors about. Ignore those — the standard codes are what matter, and they're all there.

Three monitors, one hotkey

Generating the binds in a loop beats hand-writing a dozen near-identical lines, so the whole thing is about sixty lines:

-- Requires: ddcutil, usermod -aG i2c $USER, i2c-dev at boot
--
-- SUPER + CTRL + KP 1/2/3 toggle that monitor's input between DP and HDMI
-- SUPER + CTRL + KP 0 toggle all three inputs
-- SUPER + ALT + KP 1/2/3 toggle that monitor between on and standby
-- SUPER + ALT + KP 0 toggle all three between on and standby
local monitorSerials = {
[1] = "SERIAL-LEFT",
[2] = "SERIAL-CENTRE",
[3] = "SERIAL-RIGHT",
}
-- Numpad sends different keysyms with Num Lock off, so bind both forms
local numpadKeys = {
[1] = { "KP_1", "KP_End" },
[2] = { "KP_2", "KP_Down" },
[3] = { "KP_3", "KP_Next" },
}
-- Generic VCP toggle: reads current value, flips to the other one.
-- Exits quietly if the read fails (monitor is on the other machine).
local function ddcToggle(serial, vcp, valueA, valueB)
return string.format(
'v=$(ddcutil --sn %s getvcp %s --terse 2>/dev/null | awk "{print \\$NF}"); ' ..
'v=${v##*x}; ' ..
'if [ -z "$v" ]; then exit 0; ' ..
'elif [ "$v" = "%s" ]; then n=0x%s; else n=0x%s; fi; ' ..
'ddcutil --sn %s --noverify setvcp %s $n 2>/dev/null',
serial, vcp, valueA, valueB, valueA, serial, vcp)
end
-- Input source: 0x0f = DisplayPort, 0x11 = HDMI
local function inputToggle(serial) return ddcToggle(serial, "60", "0f", "11") end
-- Power: 0x01 = on, 0x04 = standby
local function powerToggle(serial) return ddcToggle(serial, "D6", "01", "04") end
for i, serial in ipairs(monitorSerials) do
for _, key in ipairs(numpadKeys[i]) do
hl.bind(mainMod .. " + CTRL + " .. key, hl.dsp.exec_cmd(inputToggle(serial)))
hl.bind(mainMod .. " + ALT + " .. key, hl.dsp.exec_cmd(powerToggle(serial)))
end
end
-- All three at once, fired in parallel so they land together
local function toggleAll(fn)
local parts = {}
for _, serial in ipairs(monitorSerials) do
table.insert(parts, "{ " .. fn(serial) .. "; } &")
end
return table.concat(parts, " ") .. " wait"
end
for _, key in ipairs({ "KP_0", "KP_Insert" }) do
hl.bind(mainMod .. " + CTRL + " .. key, hl.dsp.exec_cmd(toggleAll(inputToggle)))
hl.bind(mainMod .. " + ALT + " .. key, hl.dsp.exec_cmd(toggleAll(powerToggle)))
end

There are four decisions in there worth pulling out.

Get Colin Campbell’s stories in your inbox

Join Medium for free to get updates from this writer.

One: read before you write, and fail quietly. The toggle asks the panel what input it’s on, then flips to the other one. If the read comes back empty, it exits rather than guessing — a monitor that’s asleep, unplugged or otherwise not answering shouldn’t get a blind setvcp fired at it.

I’d braced for a worse problem here. Plenty of monitors only answer DDC on the input they’re currently displaying, which would mean you can only ever shove a screen away from the machine you’re sitting at, never summon one back. Mine answers on both inputs regardless of which is active, so either machine can pull a panel across at any time. I don’t think that’s something to count on — check yours early, because the answer decides whether your hotkeys work in both directions or only one.

Two: address monitors by serial, not display number. --display 1 and I2C bus numbers renumber themselves across reboots and hotplug events. Serial is stable. Same reason the Windows side uses serials.

Three: --noverify and the &. By default setvcp reads the value back to confirm, which is another round trip on a slow bus. Skip the verification, fire all three in parallel, wait at the end. The difference between doing this serially and in parallel is the difference between a switch that feels laggy and one that doesn't.

Four: bind both numpad keysyms. KP_1 with Num Lock on, KP_End with it off. Bind one and your hotkeys will work perfectly until the morning you knock Num Lock and realise the whole setup is broken.

A free monitor power switch

The bonus in that config is 0xD6. Same mechanism, different VCP code: 0x01 is on, 0x04 is standby. SUPER+ALT and a numpad key puts a panel to sleep without touching a physical button or waiting on an idle timer.

I didn’t set out to build this. It fell out of having the plumbing already there, which is the nice thing about using the monitor’s own control protocol instead of a box in the middle — everything else the panel exposes is sitting right there too. Brightness on 0x10, contrast on 0x12, colour temperature, gain per channel. Once you can speak to the panel, you can speak to all of it, and none of it needed anything I hadn't already installed.

The Windows side: ControlMyMonitor

Windows has no ddcutil, but NirSoft’s ControlMyMonitor does the same job — and it’s got a nicer verb for this than /SetValue. /SwitchValue takes a VCP code and a list of values, and moves to the next one in the list each time it runs:

controlmymonitor.exe /SwitchValue "SERIAL-LEFT" 60 15 17

That’s one panel flipping between input 15 and input 17. Note the numbers are decimal here, where ddcutil talks in hex — 0x0F is 15 and 0x11 is 17, which are DisplayPort and HDMI on my panels. Spending twenty minutes convinced my monitors were broken because I'd typed 0x0F into a tool that wanted 15 is a rite of passage I'd rather you skipped.

You’ll also notice it addresses the monitor by serial number, same as on the Linux side. \\.\DISPLAY1\Monitor0 works, but it means whatever Windows currently thinks display 1 is, which is not a stable promise across dock reconnects.

Rather than a batch file, I made Windows shortcuts — one per monitor, plus one that does all three — and used the Shortcut key field in the properties dialog to bind them to Ctrl+Num1 through Ctrl+Num3, with Ctrl+Num0 for the lot. It’s a genuinely underrated corner of Windows: you get global hotkeys with no daemon and no AutoHotkey install.

The per-monitor bindings turn out to be more useful than expected. Sometimes I don’t want all three to move — leaving the right-hand panel on the work laptop while the other two follow me across is a good way to keep an eye on something without switching context entirely.

Two actions, in the sensible order

Hotkey first, button second.

Both orders technically work, since either machine can pull the panels across. But the keyboard is what runs the hotkey, and the button is what takes the keyboard away — so press the button first, and you’re typing blind on a machine whose screens haven’t arrived yet. Move the screens, then move the keyboard, and you can always see what you’re doing.

It’s two actions rather than one, and that’s the honest cost of this approach. A real KVM does both on a single press. The muscle memory took about a day.

The thing I expected to go wrong

I assumed switching inputs would tear down the displays and bring them back up, and that both machines would spend the day re-enumerating monitors and shuffling windows around. That’s the whole reason the old dock-swapping approach was tiresome.

It doesn’t happen. When a panel switches to the other input, this machine keeps the link and continues to believe three monitors are attached. Windows stay exactly where I left them on both machines. I switch away mid-afternoon, come back two hours later, and everything is where it was.

I don’t know that this holds on every panel — it’s the kind of behaviour that varies — but it’s the single best thing about the setup, and it falls out of never having interrupted the video path in the first place.

Kanshi still earns its place, just for a different job: layout and docking. It keys off the output description rather than the connector name, so it doesn’t matter which DP port a panel lands on:

profile home {
output "<VENDOR> <MODEL> SERIAL-LEFT" mode 1920x1080@60 position -1920,0
output "<VENDOR> <MODEL> SERIAL-CENTRE" mode 1920x1080@60 position 0,0
output "<VENDOR> <MODEL> SERIAL-RIGHT" mode 1920x1080@60 position 1920,0
output eDP-1 mode 2880x1800@120 disable
}
profile undocked {
output eDP-1 mode 2880x1800@120 scale 2.0 position 0,0
}

Same principle as the ddcutil config, for the same reason: three monitors reporting the same model string can only be told apart by serial. The home profile switches the laptop's own panel off, since it's redundant once three externals are live, and undocked brings it back scaled when I walk away with the machine.

Being fair to the cheap box

The UGREEN is not a premium product, and I’m not going to pretend otherwise.

The manual notes — and reviewers confirm — that 2.4GHz wireless dongles get comprehensively stomped by USB 3.0 hubs sitting next to them. If your keyboard or mouse uses a small USB receiver, plan for that. A wired keyboard or Bluetooth sidesteps it entirely.

There are also reports of intermittent disconnects, particularly early after power-up, and of power-hungry peripherals needing the external 5V supply that isn’t in the box. Mine has a webcam, as well as a keyboard and mouse, which is more of a draw than the “share your printer” use case this thing was designed for. It’s also worth noting that the switch feeds the two docks rather than the laptops directly, so everything is riding a dock’s USB hub on top of the switch — one more link in a chain that already has a cheap plastic box in it. It’s been fine, but I’d call that luck rather than engineering.

Cheap is doing some work in this story. It’s just doing about $900 less work than the alternative.

When you should just buy the KVM

Be honest about the cases where this doesn’t hold up.

If you switch machines constantly — dozens of times a day rather than twice — the couple of seconds and the two-step ritual will grind you down. If your monitors have DDC/CI disabled in their firmware, or you’re running through an MST hub that eats the I2C channel, this approach is dead on arrival, and no amount of scripting will save it. And if the whole point is that someone non-technical uses the desk, a physical box with one button beats a bash script every single time.

Was it worth the time?

Probably not, if you do the honest sum.

The box was $64. Against a $980 alternative that I’d have had to order in and wait for, that’s a clear win on paper. But then there’s the evening spent reading the MCCS spec, the hour working out why the hotkeys stopped after a reboot, the fiddling with numpad keysyms, and the ordinary drift of a thing you built yourself and now maintain. Price it at any sensible hourly rate, and the $980 box starts to look reasonable.

I’m not going to pretend that calculation motivated me. I did it because I was fairly sure it would work and I wanted to know if I was right. Stubbornness has been responsible for most of the useful things on my desk and nearly all of the useless ones, and the honest reason this exists is that it bothered me that the capability was already sitting there, in a spec from the nineties, on hardware I already owned, unused because nobody had bothered to put a keyboard shortcut on it.

The thing I didn’t expect was how little I think about it now. Two keystrokes and a button, several times a day, for months. The best outcome for a project like this is for it to disappear, and this one has.

So: not worth it, by the numbers. Worth it anyway.

--

--

Colin Campbell

Written by Colin Campbell

Technology leader who still gets his hands dirty. Building good systems and better teams. Engineer at heart. Ngāti Kahungunu | Ngāti Rangitāne. Pōneke.