MyKVM (1) 키보드 하나로 맥과 윈도우를 오가는 앱의 전체 구조
이 시리즈는 내가 직접 쓴 개발기가 아니다. 무엇을 만들지 정해 Claude Code 에게 앱을 만들게 했고, 만든 과정과 쓰인 기술은 Claude Code 가 정리했다. 나는 방향을 정하고, 실기기에서 써 보며 고칠 곳을 찾아 다시 맡겼다. 그래도 글로 남기는 건 공부 때문이다. 아직 내가 아는 프레임워크가 몇 개 없어서, 이 앱에 쓰인 여러 프레임워크를 정리해 두고 나중에 하나씩 찾아보며 익히려고 한다.
책상에 맥북과 윈도우 PC가 같이 있고, 키보드와 마우스는 하드웨어 KVM 장비의 버튼으로 오간다. 그런데 버튼을 누를 때 USB 키보드가 가끔 인식되지 않는다. 케이블을 뽑았다 꽂아도 안 되면 CalDigit 독을 맥북에 다시 꽂아야 겨우 돌아온다. Synergy도 써 봤지만 연결이 불안정하다는 느낌이 들었다. 로지텍 Flow처럼 마우스를 화면 끝으로 밀면 옆 컴퓨터로 매끄럽게 넘어가는 걸 직접 만들어 보기로 했다.
이 시리즈는 그렇게 만든 MyKVM을 주제별로 나눠 정리한다. 이번 글은 전체 구조와 저장소 구성, 그리고 윈도우 앱을 맥에서 어떻게 시험했는지까지다.
선을 옮기지 않는 KVM
KVM은 Keyboard, Video, Mouse의 첫 글자다. 한 벌의 키보드, 모니터, 마우스로 컴퓨터 여러 대를 쓰게 해 주는 장치를 말한다.
소프트웨어 KVM이란?
하드웨어 KVM은 키보드와 마우스 USB 선을 실제로 이쪽 컴퓨터에서 저쪽 컴퓨터로 옮겨 꽂아 주는 상자다. 선을 옮기는 순간 컴퓨터는 USB 장치를 새로 인식해야 하고, 내 경우 거기서 가끔 키보드를 못 찾았다.
소프트웨어 KVM은 선을 옮기지 않는다. 키보드는 늘 한 컴퓨터에 꽂혀 있고, 그 컴퓨터의 앱이 누른 키를 네트워크로 상대 컴퓨터에 보낸다. 상대 컴퓨터의 앱은 받은 키를 “방금 이 키가 눌렸다”고 자기 컴퓨터에 알려 준다. 전화로 불러 주면 받아 적는 것과 비슷하다. USB를 다시 인식하는 일 자체가 없어서 처음 문제였던 키보드 인식 불량이 생길 자리가 없다.
모니터는 옮기지 않는다. 모니터 입력은 지금처럼 모니터 버튼으로 바꾼다. 맥에서 모니터에 “입력을 HDMI로 바꿔” 명령을 보내는 방식(DDC)도 시험해 봤지만, 내 LG 모니터는 표준 방식과 LG 전용 방식 모두 반영하지 않았다.
맥이 판단하고 윈도우는 따르는 구조
맥에는 메뉴 막대 앱이, 윈도우에는 트레이 아이콘으로 떠 있는 앱이 하나씩 있다. 둘은 같은 공유기에 랜선으로 붙어 있다.
어느 컴퓨터가 입력을 받을지는 전부 맥이 정한다. 커서가 지금 어느 화면에 있는지, 화면 끝에 닿았는지, 어느 모니터가 지금 맥 화면이고 어느 모니터가 윈도우 화면인지를 맥만 계산한다. 윈도우 앱은 받은 대로 키와 마우스를 넣고, 자기 화면 목록과 잠김 여부만 알려 준다.
판단을 한쪽으로 몰아 둔 이유는 윈도우 쪽 코드를 바로 돌려 볼 수 없어서다. 윈도우 앱은 맥에서 빌드해서 NAS로 옮겨 실행한다. 윈도우 쪽이 단순할수록 옮기고 확인하는 횟수가 줄어든다.
나중에 반대 방향(키보드를 윈도우에 꽂고 맥 조작)을 넣을 때도 이 구조는 그대로 뒀다. 그 이야기는 11편과 12편에 있다.
맥 쪽과 윈도우 쪽 저장소 구성
저장소 하나에 맥 코드와 윈도우 코드를 같이 둔다.
1
2
3
4
5
6
7
8
9
10
11
MyKVM/
mac/
Packages/KVMCore/ 계산 로직 (Swift 패키지, 화면과 OS를 모름)
MyKVMMac/ 메뉴 막대 앱 (이벤트 탭, 커서, 화면 감시)
windows/
src/MyKVM.Core/ 계산 로직과 연결 (.NET 라이브러리, 윈도우 기능 안 씀)
src/MyKVM.Win/ 트레이 앱 (입력 넣기, 훅, 화면 정보)
src/MyKVM.Cli/ 맥에서 윈도우 클라이언트를 시험하는 콘솔
tests/MyKVM.Core.Tests/
protocol/vectors/ 맥과 윈도우가 같은 값을 내는지 확인하는 고정 값
scripts/e2e.sh 맥에서 양쪽을 실제로 붙여 보는 시험
양쪽 다 “계산 로직”과 “OS에 붙는 부분”을 나눴다. 화면 배치 계산, 키 변환, 암호화, 메시지 형식, 연결 상태 같은 판단은 OS 기능을 전혀 쓰지 않는 코드로 모았다. OS에 붙는 부분은 그 판단을 받아 실제로 커서를 옮기거나 키를 넣는 얇은 층이다.
Swift 패키지란?
Swift 코드를 앱과 따로 묶어 두는 단위다. Package.swift 파일 하나에 이름, 지원하는 OS, 안에 든 라이브러리와 테스트를 적는다. 앱 없이 터미널에서 swift test 한 줄로 테스트를 돌릴 수 있어서, 화면이 필요 없는 계산 로직을 두기 좋다. MyKVM의 KVMCore가 이 패키지이고, 메뉴 막대 앱은 이 패키지를 가져다 쓴다.
.NET 솔루션과 프로젝트란?
C# 쪽에서는 프로젝트(.csproj)가 하나의 결과물(라이브러리나 실행 파일)이고, 솔루션(.sln)이 여러 프로젝트를 묶는다. 프로젝트마다 어떤 .NET 버전과 어떤 OS를 대상으로 하는지 적는다.
| 프로젝트 | 대상 | 하는 일 |
|---|---|---|
| MyKVM.Core | net10.0 | 판단과 연결. 윈도우 기능을 안 써서 맥에서도 돈다 |
| MyKVM.Win | net10.0-windows | 트레이 앱. 윈도우 전용 기능을 쓴다 |
| MyKVM.Cli | net10.0 | 맥에서 Core를 실제로 돌려 보는 콘솔 |
| MyKVM.Core.Tests | net10.0 | Core 테스트. 맥에서 dotnet test로 돈다 |
대상이 net10.0인 프로젝트는 맥, 윈도우, 리눅스 어디서나 돈다. 그래서 윈도우 앱의 연결 코드와 판단 코드를 Core에 두고 맥에서 테스트했다.
1
2
3
4
5
6
7
8
9
10
<!-- MyKVM.Win.csproj -->
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>app.ico</ApplicationIcon>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
EnableWindowsTargeting으로 맥에서 윈도우 앱 빌드하기
| 속성 | 하는 일 |
|---|---|
OutputType | WinExe는 콘솔 창 없이 뜨는 윈도우 프로그램이라는 뜻이다 |
TargetFramework | net10.0-windows는 .NET 10에 윈도우 전용 기능까지 쓰겠다는 뜻이다 |
UseWindowsForms | 트레이 아이콘과 작은 창을 만드는 WinForms를 쓴다 |
EnableWindowsTargeting | 윈도우가 아닌 컴퓨터(맥)에서도 이 프로젝트를 빌드하게 허락한다 |
ApplicationManifest | 관리자 권한과 화면 배율 처리를 적은 파일(9편) |
ApplicationIcon | exe 파일과 작업 표시줄에 보일 아이콘 |
ApplicationHighDpiMode | 모니터마다 배율이 달라도 좌표를 실제 픽셀로 다룬다(10편) |
EnableWindowsTargeting이 없으면 맥에서 net10.0-windows 프로젝트를 빌드하려 할 때 “윈도우에서만 빌드할 수 있다”는 오류가 난다. 빌드는 맥에서 하고, 실행만 윈도우에서 하는 이 프로젝트에 꼭 필요했다. .NET 프로젝트 속성 Docs에 전체 목록이 있다.
윈도우 코드를 맥에서 시험하는 방법
윈도우 앱은 실제 윈도우에서 돌려 보기 전까지 동작을 알 수 없다. 그렇다고 고칠 때마다 NAS로 옮기면 한 번 확인하는 데 시간이 오래 걸린다. 그래서 시험을 세 겹으로 나눴다.
| 무엇을 | 어디서 | 어떻게 |
|---|---|---|
| 화면 배치, 키 변환, 원격 조작 판단 | 맥 | Swift 테스트(swift test) |
| 윈도우 쪽 판단, 암호화, 메시지 형식 | 맥 | C# 테스트(dotnet test) |
| 맥 서버와 윈도우 클라이언트 실제 연결 | 맥 | 두 프로그램을 띄워 붙여 보는 스크립트 |
| 입력 넣기, 훅, 트레이 아이콘 | 윈도우 | 점검 목록을 들고 직접 확인 |
세 번째 줄이 쓸모가 컸다. 윈도우 클라이언트의 연결 코드는 윈도우 기능을 안 쓰니 맥에서도 그대로 돈다. 그래서 맥 서버와 윈도우 클라이언트를 이 맥 안에서 실제 소켓으로 붙여 페어링, 끊겼다 다시 붙기, 서버 재시작 같은 장면을 스크립트로 확인했다. 8편에서 자세히 다룬다.
마지막 줄은 맥에서 확인할 수 없어서, 점검 항목을 목록으로 만들어 윈도우에서 하나씩 확인했다. 실제로 맞닥뜨린 문제(커서가 튀거나 포인터가 사라지는 것 등)는 대부분 이 단계에서 나왔고, 각 편에서 그 원인과 함께 정리한다.
