choiminyeong 5e5ef8c36f 플랫폼 최초 커밋 + 자동 업데이트 배포 폴더(dist)
소스(Core/Shell/Launcher)와 실행 PC가 받아갈 업데이트 파일(dist)을 함께 올린다.
Launcher가 켤 때 이 저장소의 dist와 D:\UbisamPlatform\bin을 비교해 새 버전이면 받아간다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 21:56:35 +09:00

6.2 KiB

패치 12 — 화면 잠금

화면 전체를 불투명하게 가리고, 아이디 + 비밀번호로만 해제하는 오버레이입니다. 잠금 중에는 밑 화면이 보이지 않고 마우스·키보드도 통과하지 않습니다.

1. 새 파일 3개

이 프로젝트 리포지토리
code/UbisamBase.Core/Locking/LockService.cs src/UbisamBase.Core/Locking/LockService.cs
code/UbisamBase.Core/Locking/LockOverlay.xaml src/UbisamBase.Core/Locking/LockOverlay.xaml
code/UbisamBase.Core/Locking/LockOverlay.xaml.cs src/UbisamBase.Core/Locking/LockOverlay.xaml.cs
code/UbisamBase.Core/Locking/LockCredentialStore.cs src/UbisamBase.Core/Locking/LockCredentialStore.cs
code/UbisamBase.Core/Locking/LockSettingsView.xaml src/UbisamBase.Core/Locking/LockSettingsView.xaml
code/UbisamBase.Core/Locking/LockSettingsView.xaml.cs src/UbisamBase.Core/Locking/LockSettingsView.xaml.cs

2. 색 토큰 1개 추가

Colors.Light.xaml / Colors.Dark.xaml에 실패 안내판 배경을 추가했습니다 (code/의 두 파일에 이미 반영).

키 라이트 다크
Ubisam.Brush.Danger.Tint #12C93B3B #1AF07070

3. ShellWindow.xaml — 오버레이 얹기

Grid의 마지막 자식으로 넣으세요. 토스트보다 뒤에 와야 잠금 화면이 토스트를 덮습니다.

xmlns:locking="clr-namespace:UbisamBase.Core.Locking"
        <!-- 화면 잠금. 마지막 자식이라 다른 모든 것을 덮는다. -->
        <locking:LockOverlay
            Visibility="{Binding IsLocked, Source={x:Static locking:LockService.Current}, Converter={StaticResource BoolToVis}}"/>
    </Grid>
</Window>

Visibility가 Collapsed면 히트 테스트도 되지 않으므로, 잠금이 아닐 때는 아래 UI가 정상 동작합니다.

4. 하단 탭에 잠금 버튼

NavActionItem을 그대로 쓰면 됩니다 — "바탕화면"과 같은 방식입니다.

new NavActionItem("화면 잠금", Icon.Lock, () => LockService.Current.Lock());

Icon enum에 Lock이 없으면 추가하세요. Themes/Icons.xaml에는 Ubisam.Icon.Lock이 이미 있습니다.

5. 잠금 설정 화면 — 설정 탭에 등록

아이디·비밀번호를 바꾸는 화면입니다. 설정 탭의 서브탭으로 넣으세요.

manager.AddSub<LockSettingsView, object>(ViewIds.Settings, "화면 잠금");

ViewModel이 없는 화면입니다(코드비하인드가 LockCredentialStore를 직접 다룹니다). 등록 헬퍼가 ViewModel 타입을 요구하면 빈 클래스를 하나 넘기거나, ViewModel 없이 등록하는 오버로드를 쓰세요.

저장 방식 — LockCredentialStore가 처리합니다.

  • 비밀번호는 평문으로 두지 않습니다: PBKDF2 (SHA-256, 10만 회) 해시 + 16바이트 salt
  • 파일 위치: %ProgramData%\UbisamBase\lock.json — 사용자 계정이 아니라 장비 단위 설정이므로
  • 비교는 고정 시간 비교(FixedTimeEquals)로 — 응답 시간으로 비밀번호를 추측할 수 없게
  • 파일이 없거나 깨졌으면 기본값(operator / 0000)으로 동작하고, 설정 화면 상단에 경고 배너가 뜹니다
  • 변경에는 현재 비밀번호 확인이 필요합니다

LockService.Validator의 기본값이 이 저장소를 가리키므로 별도 배선은 필요 없습니다. 나중에 실제 계정 서비스가 생기면 델리게이트만 바꾸세요.

LockService.Current.Validator = (id, password) => accountService.Verify(id, password);

설정 화면 검증

조건 결과
새 비밀번호 4자 미만 Fail 토스트
새 비밀번호 ≠ 확인 확인 칸 아래 붉은 안내 (입력 중 실시간)
허용 횟수가 1 미만 또는 숫자 아님 Fail 토스트
현재 비밀번호 불일치 Error 토스트, 현재 비밀번호 칸 초기화
성공 Success 토스트, 입력칸 초기화

6. 동작

  • 잠금이 걸리면 아이디 칸에 자동으로 커서가 갑니다
  • Enter = 잠금 해제 (IsDefault)
  • Esc로는 빠져나갈 수 없습니다 — 잠금이므로 의도한 동작입니다
  • 틀리면 비밀번호만 지우고 남은 시도 횟수를 안내합니다
  • 3회(MaxAttempts) 초과하면 입력과 버튼이 비활성화됩니다. LockService.Current.ResetAttempts()로 관리자가 풀어야 합니다

7. 확인

  1. 하단 탭의 잠금 버튼을 누르면 화면 전체가 가려지는지 — 상단바·탭·컨텐츠가 전혀 보이지 않아야 합니다
  2. 잠금 중에 탭을 클릭해도 반응이 없는지
  3. 아이디 칸에 바로 타이핑이 되는지
  4. 틀린 비밀번호로 남은 횟수 안내가 뜨는지, 3회 후 비활성화되는지
  5. Esc를 눌러도 풀리지 않는지
  6. 다크 테마에서도 판이 어둡고 자물쇠가 accent인지
  7. 잠금 중에 토스트가 위로 뜨지 않는지 (오버레이가 덮어야 함)
  8. 설정 → 화면 잠금에서 비밀번호를 바꾸고, 새 비밀번호로 해제되는지
  9. 처음 실행 시 설정 화면에 "기본 비밀번호 사용 중" 경고가 뜨는지
  10. %ProgramData%\UbisamBase\lock.json에 평문 비밀번호가 없는지 (salt·hash만 있어야 합니다)

주의

  • ShellWindow가 WindowState="Maximized" + WindowStyle="None"이라 오버레이가 실제로 화면 전체를 덮습니다. 창 모드로 바꾸면 창 안쪽만 가려집니다 — OS 레벨 잠금이 필요하면 별도 작업입니다.
  • 잠금 중에도 ToastService는 계속 메시지를 큐에 넣습니다(가려서 안 보일 뿐). 잠금 중 알림을 오버레이에 보여주려면 알려주세요.
  • PasswordBox는 Controls.xaml의 암시적 TextBox 스타일을 받지 못합니다(다른 타입이라서). 두 화면에서 같은 모양을 각자 정의해 두었습니다 — 입력칸 모양을 바꾸실 때는 세 곳(Controls.xaml, LockOverlay.xaml, LockSettingsView.xaml)을 함께 고쳐야 합니다.
  • Alt+Tab이나 Ctrl+Alt+Del은 이 오버레이로 막을 수 없습니다. 그 수준의 잠금이 필요하면 키보드 훅이나 셸 대체가 필요합니다 — 요구사항이면 말씀해 주세요.