Deskband11Lib
Dot4 밋업 with 마이크로소프트 코리아
WINDOWS 11 · TASKBAR · WIN32 × WINUI × WPF

윈도우 11에서 사라진작업표시줄 데스크밴드,WinUI·WPF로 되살려봤습니다

SetParent 한 번으로 시작된 라이브러리 개발기 — 그리고 스토어에 올라간 위젯들
H
이호원 · Howon Leeairtaxi · .NET 데스크톱 개발자
오늘의 주인공
SPEAKER

이호원 · airtaxi

Windows를 좋아해서 C#과 .NET을 시작했고,
지금은 WinUI·Uno Platform으로 Windows 앱을 만듭니다.

게임 개발자 — 아크스타

인디 리듬게임 회사에서
게임 서버·클라이언트 개발

공학박사 — 컴퓨터공학

선문대학교 · 논문은 멀티모달 LLM과
RAG 기반 화재 대응 조언 시스템

지금 — 서울 현충원

박사 졸업 후
사회복무요원으로 복무 중
— 그래도 개발은 계속됩니다

LIVE ON MY TASKBAR

지금, 제 작업표시줄입니다

PinStats와 CLI Account Switcher 위젯이 함께 떠 있는 작업표시줄
CLI Account Switcher · AI CLI 사용량PinStats · 시스템 모니터 — 한 작업표시줄에 동시에
BarPlay 미디어 위젯이 떠 있는 작업표시줄
BarPlay · 지금 재생 중인 미디어와 컨트롤

전부 실제로 돌아가는 .NET 앱입니다. 오늘은 이걸 만드는 법을 이야기합니다.

PROJECTS

오늘 나오는 네 프로젝트, 전부 직접 만들었습니다

D

Deskband11Lib

오늘의 주인공 — 작업표시줄 호스팅 라이브러리

B

BarPlay

미디어 위젯 · 라이브러리 검증용 PoC → 스토어 출시

P

PinStats

시스템 모니터 — 기존 앱에 위젯 기능을 이식

C

CLI Account Switcher

AI CLI 계정·사용량 관리 — 역시 위젯 이식

이 밖에도 AT-Installer · DeskBorder · LidGuard 등 오픈소스 Windows 앱을 15개 넘게 만들어 공개했습니다.

github.com/airtaxi

AGENDA · 50분

오늘 이야기

01사라진 데스크밴드Windows 11은 무엇을 지웠나 · 4분
02발견 — "그냥 창이었다"zadjii/Deskband11 · 4분
03작업표시줄에 창 심기Win32 + UI Automation · 8분
04라이브러리로 만들기설계 · 복구 · 멀티 인스턴스 · 8분
05사실 전부 .NET 이야기였습니다CsWin32 · 소스 생성 COM · NativeAOT · 5분
06PoC에서 스토어까지 + 라이브 데모BarPlay · PinStats · CAS · 10분
07한계와 교훈그리고 Q&A · 4분
01
CHAPTER 01

사라진
데스크밴드

Windows 95부터 함께였던 작업표시줄 도구들은 어디로 갔나
WINDOWS 95 → 10

데스크밴드: 작업표시줄에 살던 도구들

Windows 10까지의 작업표시줄
데스크밴드 — 도구 모음이 살던 자리
가 A12:58 PM
  • IDeskBand COM 확장 — 작업표시줄 안에 서드파티 UI를 넣는 공식 통로탐색기가 내 DLL을 로드해서 작업표시줄 안에 그려줬습니다
  • 미디어 컨트롤 · 주소창 · 시스템 모니터 · 퀵런처가 여기 살았습니다
WINDOWS 11

작업표시줄 전면 재작성, 그리고 사망선고

Windows 11의 작업표시줄 — XAML 기반으로 새로 작성
 
 
한 A12:58 PM
  • Deskband API 제거 — 공식 대체재 없음도구 모음 메뉴 자체가 사라졌습니다
  • Windows 10까지의 서드파티 작업표시줄 도구, 전부 전멸
그런데 "항상 보이는 작은 자리"
수요는 사라지지 않았습니다
미디어 컨트롤 시스템 모니터 빌드 · CI 상태 사용량 표시기 타이머 빠른 실행
02
CHAPTER 02

발견 —
"그냥 창이었다"

zadjii/Deskband11이 보여준 영리한 발상
INSPIRATION

어느 날 GitHub에서 본 것

  • zadjii/Deskband11 — Windows 11 작업표시줄에 미디어 컨트롤을 얹은 해커톤 프로젝트
  • 데스크밴드 API 없이, 작업표시줄 안에 UI가 실제로 떠 있었습니다

"작업표시줄 위에 투명한 창을 얹으면 되잖아?"

원본 Deskband11이 작업표시줄에 미디어 컨트롤을 표시한 모습
원본 Deskband11 실행 모습 — 지금 재생 중인 곡과 컨트롤
THE TRICK

핵심 트릭: 전부 평범한 Win32였습니다

① 투명한 창을 만든다

WinUI 3 · WPF의
평범한 Window

② SetParent

작업표시줄(Shell_TrayWnd)의
자식 HWND로 입양

③ 이동하고 클립

SetWindowPos + SetWindowRgn으로
빈 공간에 딱 맞춤

결과 — 작업표시줄의 일부처럼 보이는 내 창
 
▷ 내 앱의 UI
한 A12:58 PM
BUT…

그대로 쓰기엔 아까웠습니다

  • 스스로 밝힌 "해커톤급 코드" — 현재 Windows 11 빌드에선 위치·크기가 어긋남
  • 시작 버튼 중앙 정렬도, 위젯 버튼도 고려되지 않음
  • 한 앱에 박제된 구조 — 다른 앱에서 재사용 불가

그래서 dotnet add package 한 줄이면 되도록, 라이브러리로 다시 짰습니다 — Deskband11Lib

03
CHAPTER 03

작업표시줄에
창 심기

Win32 창 수술, 그리고 UI Automation 측량
STEP 1 · WIN32

팝업 창을 자식 창으로 — 창 수술

TaskbarContentHostBase.cs─ ▢ ✕
// 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 시 원래 스타일과 부모를 복원 — 평범한 창으로 귀환

STEP 2 · THE PROBLEM

그런데, 버튼 폭을 알 수 없습니다

Shell_TrayWnd작업표시줄 본체
├─TrayNotifyWnd알림 영역
└─ReBarWindow32
└─MSTaskSwWClass
└─MSTaskListWClass앱 버튼 리스트
시작 버튼? 앱 버튼 폭? 위젯 버튼?  →  어디에도 없음

버튼들은 XAML로 렌더링

Windows 11 작업표시줄의 콘텐츠는 XAML 컴포지션 — 자식 HWND가 버튼별로 존재하지 않습니다

그런데 폭은 알아야 합니다

시작 버튼·앱 버튼과 겹치지 않는 "빈 공간"을 계산해야 하니까요

STEP 2 · THE FIX

해법: UI Automation으로 직접 잰다

UIA가 읽어낸 실제 지오메트리 (중앙 정렬 기준)
위젯
왼쪽 빈 공간
시작
앱 버튼 그룹
오른쪽 빈 공간
알림 영역 · 시계
AutomationId = StartButton AutomationId = WidgetsButton ControlType.Button 연속 그룹 = 앱 버튼 SystemTrayIcon · NotifyItemIcon = 알림 영역
STEP 3 · ALIGNMENT

정렬은 레지스트리가 알려줍니다

registry─ ▢ ✕
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced

TaskbarAl = 0   → 왼쪽 정렬
TaskbarAl = 1   → 중앙 정렬 (기본값)

// 값이 없다면? 시작 버튼의 X 좌표로 추론
중앙 정렬 — 빈 공간이 양쪽에 생김
 
 
12:58
왼쪽 정렬 — 빈 공간은 오른쪽 하나
 
12:58

정렬에 따라 빈 공간이 생기는 위치가 완전히 달라집니다 — 원본이 놓친 부분이었죠.

STEP 4 · PLACEMENT

빈 공간 고르기 — Placement

Placement = Auto : 중앙 정렬이면 더 넓은 쪽이 승리
위젯
좁은 쪽 ✕
시작
앱 버튼 그룹
더 넓은 쪽 ✓ — 여기 배치
알림 영역
Auto — 더 넓은 gap 자동 선택 LeftEdge — 왼쪽 끝(위젯 버튼 옆) BeforeNotificationArea — 알림 영역 앞 BeforeStartButton — 시작 버튼 옆

시작 버튼·앱 버튼·위젯 버튼·알림 영역과는 절대 겹치지 않습니다

PERFORMANCE

측정이 UI를 막지 않게

UIA 스캔

ThreadPool에서 실행
느려도 괜찮은 곳으로 격리

지오메트리 캐시

측정 결과를 lock으로 보호해
스냅샷으로 보관

레이아웃 타이머 (500ms)

캐시만 읽어서 배치 계산
UI 스레드는 대기 없음

  • 스캔이 실패하면? 이전 측정값 유지 — 위젯은 아무 일 없이 계속 동작UIA는 "최선 노력" 프로브 — 실패가 기능 정지로 이어지지 않는 설계
04
CHAPTER 04

라이브러리로
만들기

3줄짜리 API 뒤에 숨긴 것들
API DESIGN

목표: 설치 한 줄, 코드 세 줄

pwsh─ ▢ ✕
$ dotnet add package Deskband11Lib.WinUI
App.xaml.cs─ ▢ ✕
var window = new MainWindow();
var host = new TaskbarContentHost(window, rootElement,
new TaskbarContentHostOptions
{
    PreferredWidth  = 360,
    PreferredHeight = 48
});

await host.AttachWhenLayoutReadyAsync();
window.Activate();

WinUI = WPF

두 Facade의 API가 완전히 동일 — Window와 FrameworkElement의 네임스페이스만 다릅니다

옵션은 한 곳에

폭·높이·배치·모니터·애니메이션 전부 TaskbarContentHostOptions 하나로

ARCHITECTURE

Core 하나, Facade 여럿

Deskband11Lib.WinUI

Windows App SDK · WinUI 3 Facade

Deskband11Lib.Wpf

WPF Facade

(미래) Avalonia…

Facade만 추가하면 확장

▲ 창 핸들 · 디스패처 · 타이머만 제공  |  ITaskbarHostPlatformAdapter  |  레이아웃 결과만 수신 ▼

Deskband11Lib.Core — UI 프레임워크 무관 엔진

작업표시줄 창 탐색레이아웃 계산UIA 측정 정렬 감지Explorer 재시작 감시멀티 인스턴스 슬롯 조율

Facade가 얇을수록 새 프레임워크 지원이 쉬워집니다 — 엔진은 단 하나

LIFECYCLE

Explorer는 언젠가 재시작됩니다

Attach

작업표시줄에
창 입양

감시 (1초 폴링)

작업표시줄 HWND
생존 확인

재시작 감지

HWND가 교체됨 —
내 창은 이미 파괴됨

이벤트 통지

TaskbarWindowRecreated
→ 앱이 창을 재생성

App.xaml.cs─ ▢ ✕
host.TaskbarWindowRecreated += async (_, _) =>
{
    await RecreateMainWindowAsync();  // 창을 새로 만들어 다시 Attach
};

보조 모니터가 분리됐다 재연결되어도 자동으로 원래 작업표시줄로 복귀합니다

MULTI-INSTANCE

여러 앱이 같은 자리를 원한다면?

BarPlay

폭 380 요청

PinStats

폭 260 요청

CLI Account Switcher

폭 200 요청

▼ 서로의 존재를 모르는 별개 프로세스들 ▼

공유 메모리(MMF) 슬롯 레지스트리 + Named Mutex

Local\Deskband11Lib.SlotRegistry — 최대 64슬롯 · 슬롯마다 HWND · 요청 폭 · 우선순위 · 하트비트 기록

  • 중앙 서버도 IPC 채널도 없음 — 죽은 프로세스는 IsWindow 검사로 자동 퇴출
SLOT ALLOCATION

Fixed-then-Stretch 배분

공간이 충분할 때 — Fixed는 요청 폭 그대로, Stretch가 나머지를 나눠 가짐
CAS
Fixed 200
BarPlay
Fixed 380
Stretch (PreferredWidth = double.MaxValue) — 남는 공간 전부
공간이 모자랄 때 — 크기 조절을 허용한 Fixed는 요청 폭에 비례해 축소 (0으로 굶기지 않음)
CAS
200 → 150
BarPlay
380 → 285
앱 버튼이 늘어나 좁아진 공간…
  • ManualSlotPriority가 낮을수록 앞자리 — 기본값 65535는 "도착 순서대로"
MOTION

마지막 500ms의 디테일

이징 25종 내장

Linear · Sine · Cubic · Expo · Circle… 기본은 CircleOut, Func<double,double>이면 뭐든 OK

144 Hz

고주사율 모드

애니메이션 타이머를 60FPS 고정 대신 모니터의 실제 주사율에 맞춰 구동

재배치도 부드럽게

옆 위젯이 등장·퇴장해 자리가 바뀌어도 순간이동 대신 슬라이드 애니메이션

작업표시줄의 일부처럼 보이려면, 움직임도 OS처럼 자연스러워야 합니다

05
CHAPTER 05

사실 전부
.NET 이야기였습니다

CsWin32 · 소스 생성 COM · 그리고 NativeAOT
.NET INTEROP ①

P/Invoke를 손으로 쓰지 않습니다

NativeMethods.txt─ ▢ ✕
SetParent
SetWindowPos
FindWindowEx
GetWindowLongPtrW
SetWindowRgn
→ 소스 생성 →
생성된 코드 사용─ ▢ ✕
PInvoke.SetParent(childHwnd, taskbarHwnd);
// 시그니처·마샬링·SafeHandle까지
// Microsoft.Windows.CsWin32가 전부 생성
  • Win32 메타데이터에서 정확한 시그니처를 생성 — 손 P/Invoke의 마샬링 실수 원천 차단
  • 생성된 코드는 분석 가능한 정적 코드 — NativeAOT·트리밍과 궁합이 좋습니다
.NET INTEROP ②

UI Automation도 소스 생성 COM으로

GeneratedUIAutomation.cs─ ▢ ✕
[GeneratedComInterface]
[Guid("30cbe57d-d9d0-452a-ab13-7ac5ac4825ee")]
internal partial interface IUIAutomation
{
    IUIAutomationElement GetRootElement();
    IUIAutomationElement ElementFromHandle(nint hwnd);
    // COM vtable 호출 코드를 컴파일 타임에 생성
}

System.Windows.Automation 없음

무거운 관리 어셈블리 대신 UIA COM 인터페이스를 직접 — 필요한 멤버만

DisableRuntimeMarshalling

런타임 마샬링 제거 — 상호 운용이 전부 컴파일 타임에 결정됩니다

런타임에 코드를 만들어내는 고전 COM 마샬러는 AOT의 적 — 소스 생성이 답이었습니다

NATIVEAOT

결과: NativeAOT로 컴파일되는 위젯

⚡ 즉시 시작

JIT 없이 네이티브 바이너리 — 부팅 직후 뜨는 위젯에 딱 맞는 특성

🪶 가벼운 상주

항상 떠 있는 앱이니 메모리 발자국이 곧 매너입니다

📦 IsAotCompatible

라이브러리 전체가 AOT 분석 경고 0 — 소비 앱이 안심하고 PublishAot

NativeAOT로 배포 중인 BarPlay가 데스크톱에서 실행 중인 모습
BarPlay — 실제로 NativeAOT로 스토어에 배포 중인 PoC
06
CHAPTER 06

PoC에서
스토어까지

BarPlay · PinStats · CLI Account Switcher — 그리고 라이브 데모
PROOF OF CONCEPT

BarPlay — 검증용으로 만든 미디어 위젯

  • SMTC 연동 — Spotify · 브라우저 · YouTube, 시스템 미디어면 전부
  • 위젯 클릭 → 플라이아웃으로 탐색 슬라이더 · 설정까지
  • NativeAOT + Microsoft Store 출시 — "라이브러리가 진짜 쓸만한가?"의 답
BarPlay 위젯과 펼쳐진 플라이아웃
작업표시줄 위젯 + 플라이아웃 — 전부 WinUI 3 컨트롤 그대로
ADOPTION

기존 앱에도 며칠 만에 붙습니다

PinStats 작업표시줄 위젯과 하드웨어 상세 팝업
PinStats — CPU·GPU·메모리 링이 작업표시줄에 상주
CLI Account Switcher 위젯과 활성 계정 할당량 팝업
CLI Account Switcher — AI CLI 사용량이 항상 눈앞에

두 앱 모두 통합 코드는 옵션 몇 개 + AttachWhenLayoutReadyAsync 한 줄이 전부였습니다.

SHOWTIME
LIVE DEMO
DEMO 01

Explorer 재시작 생존기

taskkill /f /im explorer.exe

작업표시줄이 죽어도 위젯은 스스로 돌아옵니다

DEMO 02

정렬 전환

시작 버튼을 중앙 ↔ 왼쪽으로 바꾸면, 위젯이 애니메이션과 함께 새 빈 공간으로 이사합니다

DEMO 03

슬롯 조정

위젯 두 개를 동시에 띄우면 서로 겹치지 않고 공간을 나눠 갖습니다

데모의 신이 노하시면 준비된 영상으로 대체합니다 :)

HONEST TALK

솔직한 한계, 그리고 교훈

  • 여긴 문서화되지 않은 영역입니다 — Windows 업데이트가 언제든 깨뜨릴 수 있습니다공식 API가 사라진 자리를 채우는 일엔 그만한 대가가 따릅니다
  • 그래서 감시와 복구를 처음부터 설계에 내장했습니다폴링 감시 · TaskbarWindowRecreated · 모니터 재연결 자동 복귀 — "깨질 수 있음"을 전제한 설계
  • UIA 기반 측정은 내부 구현 변화에 비교적 유연합니다AutomationId는 접근성 계약 — 픽셀 좌표 하드코딩보다 오래 살아남습니다
WRAP-UP
여러분의 작업표시줄 위젯
기다립니다

감사합니다 — 질문 받겠습니다. 이호원 · airtaxi