Architecture of the Passcode Lock System in Telegram Desktop's MainWindow

The passcode lock system in Telegram Desktop is a centralized UI subsystem coordinated by MainWindow, involving Application-level state management, cryptographic verification via Domain, and optional biometric integration through SystemUnlock.

The passcode lock architecture in telegramdesktop/tdesktop secures the application through a multi-layered design that separates global state management from UI presentation. When the global lock flag is set, MainWindow instantiates a PasscodeLockWidget that handles user input, cryptographic validation, and platform-specific biometric authentication. This architecture ensures that sensitive chat data remains encrypted and inaccessible until the correct passcode or system-approved biometric is provided.

Core Components of the Passcode Lock Architecture

The system spans six primary components across the codebase:

  • Application (core/application.cpp): Maintains the global _passcodeLock flag and orchestrates lock/unlock operations across all window instances.
  • Window::Controller (window/window_controller.cpp): Propagates Application commands to the UI layer via _widget.setupPasscodeLock().
  • MainWindow (mainwindow.cpp): Creates, animates, and destroys the lock widget while coordinating visibility of underlying UI layers (intro, main view).
  • PasscodeLockWidget (window/window_lock_widgets.cpp/.h): The concrete UI implementation that collects input, validates credentials, and manages system-unlock integration.
  • Domain (storage/storage_domain.cpp): Performs cryptographic verification of passcodes against stored encryption keys.
  • SystemUnlock (base/system_unlock.h/.cpp): Provides platform-specific biometric prompts including Windows Hello, Touch ID, and Apple Watch.

Global Lock State Management

The Application class maintains the canonical lock state. When the system detects inactivity or an explicit lock request, lockByPasscode() sets the internal flag and broadcasts the lock command to every active window.

// core/application.cpp
void Application::lockByPasscode() {
    _passcodeLock = true;
    enumerateWindows([&](not_null<Window::Controller*> w) {
        w->setupPasscodeLock();          // triggers UI creation
    });
    if (_mediaView) { _mediaView->close(); }
}

Conversely, unlockPasscode() clears the flag and destroys all lock widgets. This method is invoked both after successful passcode entry and after biometric authentication.

// core/application.cpp
void Application::unlockPasscode() {
    clearPasscodeLock();                 // resets flag
    enumerateWindows([&](not_null<Window::Controller*> w) {
        w->clearPasscodeLock();          // destroys UI
    });
}

Window Coordination Layer

The Window::Controller acts as a bridge between the global Application state and the concrete MainWindow UI. It forwards setup requests to its internal widget instance.

// window/window_controller.cpp
void Controller::setupPasscodeLock() {
    _widget.setupPasscodeLock();   // creates PasscodeLockWidget
}

This delegation pattern allows the Application to remain agnostic of UI implementation details while ensuring every window receives the lock command simultaneously.

MainWindow UI Instantiation

Within MainWindow::setupPasscodeLock(), the lock widget is constructed as a child of the window's body widget. The method handles animated transitions by capturing the current view state before hiding the intro or main content layers.

// mainwindow.cpp
void MainWindow::setupPasscodeLock() {
    auto animated = (_main || _intro);
    auto oldContentCache = animated ? grabForSlideAnimation() : QPixmap();
    _passcodeLock.create(bodyWidget(), &controller());   // PasscodeLockWidget
    updateControlsGeometry();
    ui_hideSettingsAndLayer(anim::type::instant);
    if (_main) { _main->hide(); }
    if (_intro) { _intro->hide(); }
    if (animated) {
        _passcodeLock->showAnimated(std::move(oldContentCache));
    } else {
        _passcodeLock->showFinished();
        setInnerFocus();
    }
}

When clearing the lock, MainWindow::clearPasscodeLock() destroys the widget and restores the previously active view layer (intro, main, or setup-email lock), reversing the animation process.

Passcode Validation and Cryptography

The PasscodeLockWidget handles user input through its submit() method. It validates the entry against flood-control limits before delegating cryptographic verification to the Domain class.

// window/window_lock_widgets.cpp
void PasscodeLockWidget::submit() {
    if (_passcode->text().isEmpty()) { _passcode->showError(); return; }
    if (!passcodeCanTry()) { _error = tr::lng_flood_error(tr::now); ... return; }
    const auto passcode = _passcode->text().toUtf8();
    auto &domain = Core::App().domain();
    const auto correct = domain.started()
        ? domain.local().checkPasscode(passcode)
        : (domain.start(passcode) == Storage::StartResult::Success);
    if (!correct) { cSetPasscodeBadTries(...); error(); return; }
    Core::App().unlockPasscode();   // destroys widget
}

The cryptographic check occurs in Domain::checkPasscode(), which recomputes the local encryption key from the supplied passcode and salt.

// storage/storage_domain.cpp
bool Domain::checkPasscode(const QByteArray &passcode) const {
    Expects(!_passcodeKeySalt.isEmpty());
    const auto checkKey = CreateLocalKey(passcode, _passcodeKeySalt);
    return checkKey->equals(_passcodeKey);
}

Bad-try counters (cSetPasscodeBadTries) and timestamps persist in the settings system to enforce rate limiting via passcodeCanTry().

Biometric and System Unlock Integration

On supported platforms, PasscodeLockWidget exposes a system-unlock button that triggers platform-native biometric prompts. The suggestSystemUnlock() method initiates the flow, while systemUnlockDone() handles the callback.

// window/window_lock_widgets.cpp
SuggestSystemUnlock(this,
    (::Platform::IsWindows()
        ? tr::lng_passcode_winhello_unlock(tr::now)
        : tr::lng_passcode_touchid_unlock(tr::now)),
    done);

A successful biometric validation immediately invokes Core::App().unlockPasscode(), bypassing manual passcode entry entirely. Flood errors from the system unlock layer are surfaced in the UI via the same error handling mechanism as manual entry failures.

Practical Implementation Examples

Manually Triggering a Lock After Inactivity

// Detection logic for idle timeout
if (!Core::App().passcodeLocked() && Core::App().settings().autoLock()) {
    Core::App().maybeLockByPasscode();   // invokes the whole flow
}

Programmatic Unlock After Biometric Success

// Called from system-unlock callback
void onBiometricSuccess() {
    Core::App().unlockPasscode();   // clears flag and destroys the widget
}

Extending Passcode Validation

To customize validation logic, extend the Domain class and override the check behavior. The widget calls this method consistently:

auto &domain = Core::App().domain();
bool ok = domain.local().checkPasscode(userInput);

Summary

  • Global coordination: The Application class maintains the _passcodeLock flag and broadcasts state changes to all windows via enumerateWindows().
  • UI separation: MainWindow handles widget lifecycle and animations while PasscodeLockWidget manages input collection and error display.
  • Cryptographic isolation: Actual passcode verification occurs in Domain::checkPasscode() within the storage layer, using salted key derivation.
  • Biometric support: SystemUnlock provides native platform integration (Windows Hello, Touch ID, Apple Watch) that bypasses manual entry on success.
  • State persistence: Bad-try counters and timestamps enable client-side flood control independent of the cryptographic verification.

Frequently Asked Questions

How does MainWindow animate the transition to the lock screen?

MainWindow captures the current view content using grabForSlideAnimation() before hiding the _main or _intro layers. It then passes this cache to PasscodeLockWidget::showAnimated(), which performs a smooth slide transition while the underlying content remains hidden but preserved in memory.

Where is the passcode cryptographically verified?

Verification occurs in Domain::checkPasscode() within storage/storage_domain.cpp. This method reconstructs the encryption key using CreateLocalKey(passcode, _passcodeKeySalt) and compares it against the stored _passcodeKey, ensuring the UI layer never handles raw cryptographic material directly.

What triggers the lock across all open windows simultaneously?

Application::lockByPasscode() sets the global flag and iterates through all active windows using enumerateWindows(), calling setupPasscodeLock() on each controller. This ensures consistent locking behavior regardless of how many windows or chats are currently open.

How does Telegram Desktop integrate with system biometric authentication?

The platform abstraction layer in base/system_unlock.h exposes SuggestSystemUnlock(), which launches native prompts (Windows Hello, Touch ID, Apple Watch). On success, the callback invokes Core::App().unlockPasscode(), providing a seamless unlock experience that bypasses the traditional passcode input field.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →