How Telegram Desktop Manages Multi-Account Sessions and the Tray Accounts Menu
Telegram Desktop handles multi-account sessions by maintaining separate MTProto authentication instances for each account in memory while serializing state to disk, with the tray accounts menu providing the UI layer for instantaneous switching between active sessions.
Telegram Desktop allows users to operate multiple accounts simultaneously without restarting the application. This capability relies on a strict separation between runtime session management and persistent storage, coordinated through the tray accounts menu. Understanding how the client manages these multi-account sessions reveals the architecture behind seamless account switching and system tray integration.
Session Architecture: Runtime vs. Persistent Storage
The application distinguishes between the active in-memory representation of an account and its on-disk persistence, enabling fast switching while maintaining data integrity across launches.
Main::Account: The Runtime Session Object
Each logged-in session is represented by Main::Account, defined in [main_account.h](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/main/main_account.h) and implemented in [main_account.cpp](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/main/main_account.cpp). This class encapsulates a single MTProto connection and holds the MTP::AuthKey, MTP::Instance, network connection handler, updates manager, and a view into Core::Settings specific to that account.
All active accounts reside in a base::flat_map<PeerId, std::unique_ptr<Main::Account>> maintained by Main::Session. The currently active account is accessible via Main::Session::current(), which returns a pointer to the focused session. Switching accounts updates this pointer and propagates changes throughout the UI layer.
Storage::Account: Persistent State Management
Persistent account data is handled by Storage::Account, declared in [storage_account.h](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/storage/storage_account.h) and implemented in [storage_account.cpp](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/storage/storage_account.cpp). This class manages serialization of authentication keys, user IDs, cached data, and account-specific settings to the local filesystem.
During application startup, Storage::Manager enumerates all stored account files and instantiates corresponding Main::Account objects, registering them within Main::Session to restore the previous session state.
Managing Account Lifecycles
The application provides programmatic and UI-driven mechanisms for adding and removing accounts, both guarded by system limits.
Adding New Accounts
The "Add Account" entry in the tray menu triggers Main::Account::addNew(), which creates a fresh Storage::Account, generates a new authentication key, and initiates the MTProto login flow. This method accepts default authentication parameters and UI show requests to handle the onboarding interface.
Logging Out and Account Removal
The "Log out" action invokes Main::Account::logout() on the current session. This method clears the corresponding Storage::Account file from disk, removes the entry from the session list, and optionally purges the local data folder. The runtime object is destroyed and removed from the Main::Session container.
Account Limits Enforcement
Both adding and removing actions respect the limits::accounts constant. The UI layer disables the "Add Account" menu entry when the maximum number of accounts is reached, preventing unauthorized session creation.
Tray Accounts Menu Implementation
The system tray integration lives in [tray_accounts_menu.cpp](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/tray_accounts_menu.cpp) with its interface defined in [tray_accounts_menu.h](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/tray_accounts_menu.h).
Menu Construction and Population
Tray::AccountsMenu::create() constructs a platform-native menu (typically QMenu) and iterates over Main::Session::list() to generate entries for each account. Each menu item displays the account avatar, username via UserData::userName(), and a checkmark indicator (st::trayAccountCheck) for the currently active session. The "Add Account" and "Log out" actions are appended as distinct entries at the bottom of the menu.
Handling User Interactions
Clicking an account entry invokes Main::Session::switchTo(accountId), where accountId is the PeerId of the target account. This updates Main::Session::current() and emits sessionChanged(), triggering UI refreshes across window titles, chat lists, and notification handlers. The "Add Account" and "Log out" items delegate to their respective Main::Account methods described above.
Automatic UI Synchronization
Tray::AccountsMenu subscribes to Main::Session::accountsChanged signals. Any addition, removal, or activation event triggers a complete menu rebuild, ensuring the tray interface remains synchronized with the underlying session list without manual refresh.
Cross-Session Persistence
When the application terminates, Storage::Manager::save() writes each Storage::Account to accounts/*.account files within the application data directory. On subsequent launches, the manager reads these files, recreates Main::Account instances, and restores the previously active account identifier from Core::Settings::lastActiveAccount. This mechanism ensures the tray accounts menu reflects the exact same state as the previous session, allowing instant switching without re-authentication.
Code Examples
Switch to a different account programmatically, mirroring tray click behavior:
auto accountId = PeerId(accountUserId); // PeerId of the target account
Main::Session::instance().switchTo(accountId); // updates UI, network, etc.
Programmatically add a new account equivalent to the tray menu action:
Main::Account::addNew(
Core::App::instance().settings().defaultAuthParameters(),
Core::App::instance().uiShowRequests());
Log out the currently active account:
auto *current = Main::Session::instance().current();
if (current) {
current->logout(); // clears storage, removes from session list
}
Summary
- Telegram Desktop maintains separate
Main::Accountinstances for each MTProto session, stored in a flat_map withinMain::Session. - Persistent state is managed by
Storage::Accountand serialized toaccounts/*.accountfiles viaStorage::Manager. - The tray accounts menu in
tray_accounts_menu.cpprenders active sessions and handles switching throughMain::Session::switchTo(). - Account limits enforced by
limits::accountsprevent exceeding maximum session counts. - Automatic synchronization occurs through
accountsChangedandsessionChangedsignals, keeping the tray menu consistent with runtime state.
Frequently Asked Questions
How does Telegram Desktop isolate authentication data between accounts?
Each account maintains a distinct MTP::AuthKey and MTP::Instance within its Main::Account object. The Storage::Account layer serializes these credentials to separate files on disk, ensuring cryptographic isolation. No authentication data is shared between accounts in memory or in persistent storage.
What is the maximum number of accounts supported in the tray menu?
The application enforces a hard limit defined by limits::accounts, checked before allowing new account creation. When this limit is reached, the "Add Account" option in the tray menu becomes disabled, and programmatic calls to addNew() will fail silently or return an error depending on the caller.
How does the UI update when switching accounts via the system tray?
Clicking an account in the tray menu triggers Main::Session::switchTo(), which updates the internal current account pointer and emits sessionChanged(). This signal propagates to all UI components, causing immediate refreshes of the window title, chat list, notification settings, and message history without requiring an application restart.
Where is multi-account session data stored between application launches?
Account data persists in the accounts/ subdirectory of the application data folder, with each account represented by a *.account file managed by Storage::Manager. The last active account ID is saved in Core::Settings::lastActiveAccount, enabling the application to restore the previous context on startup.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →