이호원 · airtaxi
Windows를 좋아해서 C#과 .NET을 시작했고,
지금은 WinUI·Uno Platform으로 Windows 앱을 만듭니다.
게임 개발자 · 아크스타
인디 리듬게임 회사에서
게임 서버·클라이언트 개발
컴퓨터공학 박사
선문대학교 · 논문은 멀티모달 LLM과
RAG 기반 화재 대응 조언 시스템
지금 · 서울 현충원
박사 졸업 후 사회복무요원으로 복무 중
개발은 퇴근 후에 합니다
Windows를 좋아해서 C#과 .NET을 시작했고,
지금은 WinUI·Uno Platform으로 Windows 앱을 만듭니다.
인디 리듬게임 회사에서
게임 서버·클라이언트 개발
선문대학교 · 논문은 멀티모달 LLM과
RAG 기반 화재 대응 조언 시스템
박사 졸업 후 사회복무요원으로 복무 중
개발은 퇴근 후에 합니다
전부 실제로 돌아가는 .NET 앱입니다. 오늘은 이걸 만드는 법을 이야기합니다.
오늘 소개할 작업표시줄 호스팅 라이브러리
미디어 위젯 · 라이브러리 검증용 PoC → 스토어 출시

시스템 모니터 · 기존 앱에 위젯 기능을 이식
AI CLI 계정·사용량 관리 · 마찬가지로 위젯 이식
이 밖에도 AT-Installer · DeskBorder · LidGuard 등 오픈소스 Windows 앱을 15개 넘게 만들어 공개했습니다.
github.com/airtaxi
"작업표시줄 위에 투명한 창을 얹으면 되잖아?"
WinUI 3 · WPF의
평범한 Window
작업표시줄(Shell_TrayWnd)의
자식 HWND로 입양
SetWindowPos + SetWindowRgn으로
빈 공간에 딱 맞춤
그래서 dotnet add package 한 줄이면 되도록 라이브러리로 다시 만들었습니다. 그게 Deskband11Lib입니다
// 1. 창 스타일 교체: 최상위 팝업 → 자식 창 var style = GetWindowLongPtr(hwnd, GWL_STYLE); style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME); style |= WS_CHILD; SetWindowLongPtr(hwnd, GWL_STYLE, style); // 2. 작업표시줄의 자식으로 입양 SetParent(hwnd, taskbarHwnd); // Shell_TrayWnd // 3. 빈 공간에 배치하고 클리핑 SetWindowPos(hwnd, x, y, width, height, ...); SetWindowRgn(hwnd, CreateRectRgn(0, 0, width, height), true);
XAML 트리도, 입력도, 렌더링도 전부 살아있는 진짜 창입니다. 스타일 비트만 바뀝니다
Detach하면 원래 스타일과 부모를 복원해서 평범한 창으로 돌아갑니다
Windows 11 작업표시줄의 콘텐츠는 XAML 컴포지션이라 자식 HWND가 버튼별로 존재하지 않습니다
시작 버튼·앱 버튼과 겹치지 않는 "빈 공간"을 계산해야 하니까요
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced TaskbarAl = 0 → 왼쪽 정렬 TaskbarAl = 1 → 중앙 정렬 (기본값) // 값이 없다면? 시작 버튼의 X 좌표로 추론
정렬에 따라 빈 공간이 생기는 위치가 완전히 달라집니다. 원본 Deskband11이 놓친 부분이었죠.
시작 버튼·앱 버튼·위젯 버튼·알림 영역과는 절대 겹치지 않습니다
ThreadPool에서 실행
느려도 괜찮은 곳으로 격리
측정 결과를 lock으로 보호해
스냅샷으로 보관
캐시만 읽어서 배치 계산
UI 스레드는 대기 없음
$ dotnet add package Deskband11Lib.WinUI
var window = new MainWindow(); var host = new TaskbarContentHost(window, rootElement, new TaskbarContentHostOptions { PreferredWidth = 360, PreferredHeight = 48 }); await host.AttachWhenLayoutReadyAsync(); window.Activate();
두 Facade의 API는 완전히 같고, Window와 FrameworkElement의 네임스페이스만 다릅니다
폭·높이·배치·모니터·애니메이션 전부 TaskbarContentHostOptions 하나로
Windows App SDK · WinUI 3 Facade
WPF Facade
Facade만 추가하면 확장
Facade가 얇을수록 새 프레임워크 지원이 쉬워집니다. 엔진은 하나뿐입니다
작업표시줄에
창 입양
작업표시줄 HWND
생존 확인
HWND가 통째로 교체됨
내 창은 이미 파괴된 뒤
TaskbarWindowRecreated
→ 앱이 창을 재생성
host.TaskbarWindowRecreated += async (_, _) => { await RecreateMainWindowAsync(); // 창을 새로 만들어 다시 Attach };
보조 모니터가 분리됐다 재연결되어도 자동으로 원래 작업표시줄로 복귀합니다
폭 380 요청
폭 260 요청
폭 200 요청
Local\Deskband11Lib.SlotRegistry에 최대 64슬롯, 슬롯마다 HWND · 요청 폭 · 우선순위 · 하트비트 기록
Linear · Sine · Cubic · Expo · Circle… 기본은 CircleOut, Func<double,double>이면 뭐든 OK
애니메이션 타이머를 60FPS 고정 대신 모니터의 실제 주사율에 맞춰 구동
옆 위젯이 등장·퇴장해 자리가 바뀌어도 순간이동 대신 슬라이드 애니메이션
작업표시줄의 일부처럼 보이려면, 움직임도 OS처럼 자연스러워야 합니다
SetParent SetWindowPos FindWindowEx GetWindowLongPtrW SetWindowRgn
PInvoke.SetParent(childHwnd, taskbarHwnd); // 시그니처·마샬링·SafeHandle까지 // Microsoft.Windows.CsWin32가 전부 생성
[GeneratedComInterface] [Guid("30cbe57d-d9d0-452a-ab13-7ac5ac4825ee")] internal partial interface IUIAutomation { IUIAutomationElement GetRootElement(); IUIAutomationElement ElementFromHandle(nint hwnd); // COM vtable 호출 코드를 컴파일 타임에 생성 }
무거운 관리 어셈블리 대신 UIA COM 인터페이스를 직접, 그것도 필요한 멤버만
런타임 마샬링을 제거해서 상호 운용이 전부 컴파일 타임에 결정됩니다
고전 COM 마샬러는 런타임에 코드를 만들어서 AOT와 같이 쓸 수 없습니다. 그래서 소스 생성을 택했습니다
JIT 없는 네이티브 바이너리라 부팅 직후 뜨는 위젯에 잘 맞습니다
항상 떠 있는 앱이라 메모리 사용량을 최대한 줄였습니다
라이브러리 전체가 AOT 분석 경고 0, 쓰는 앱도 안심하고 PublishAot 가능
두 앱 모두 통합 코드는 옵션 몇 개 + AttachWhenLayoutReadyAsync 한 줄이 전부였습니다.
taskkill /f /im explorer.exe
작업표시줄이 죽어도 위젯은 스스로 돌아옵니다
시작 버튼을 중앙 ↔ 왼쪽으로 바꾸면, 위젯이 애니메이션과 함께 새 빈 공간으로 이사합니다
위젯 두 개를 동시에 띄우면 서로 겹치지 않고 공간을 나눠 갖습니다
데모의 신이 노하시면 준비된 영상으로 대체합니다 :)
github.com/airtaxi/DeskBand11Lib-Dot4-Slides이 슬라이드의 소스 · MITgithub.com/airtaxi/Deskband11LibMIT · 샘플 프로젝트 복사로 바로 시작Deskband11Lib.WinUI · Deskband11Lib.WpfBarPlay · CLI Account Switcher전부 이 라이브러리로 만든 앱들감사합니다. 질문 받겠습니다.
이호원 · airtaxi