/* * Copyright (c) 2025 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using CommunityToolkit.Mvvm.Input; using Microsoft.UI.Xaml.Media; using ProtonVPN.Client.Contracts.Services.Lifecycle; using ProtonVPN.Client.Core.Bases; using ProtonVPN.Client.Core.Bases.ViewModels; using ProtonVPN.Client.Core.Messages; using ProtonVPN.Client.Core.Services.Activation; using ProtonVPN.Client.Core.Services.Selection; using ProtonVPN.Client.EventMessaging.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts; using ProtonVPN.Client.Logic.Auth.Contracts.Messages; using ProtonVPN.Client.Logic.Connection.Contracts; using ProtonVPN.Client.Logic.Connection.Contracts.Messages; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents; using ProtonVPN.Client.Logic.Recents.Contracts; using ProtonVPN.Client.Logic.Servers.Cache; using ProtonVPN.Common.Core.Helpers; using ProtonVPN.StatisticalEvents.Contracts.Dimensions; namespace ProtonVPN.Client.UI.Tray; public partial class TrayIconComponentViewModel : ViewModelBase, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver { private readonly IAppExitInvoker _appExitInvoker; private readonly IServersCache _serversCache; private readonly IMainWindowActivator _mainWindowActivator; private readonly ITrayAppWindowActivator _trayAppWindowActivator; private readonly IRecentConnectionsManager _recentConnectionsManager; private readonly IConnectionManager _connectionManager; private readonly IUserAuthenticator _userAuthenticator; private readonly IApplicationIconSelector _applicationIconSelector; public ImageSource IconSource => _applicationIconSelector.GetStatusIcon(); // GUID value generated by hashing "ProtonVPN" with SHA1 and taking first 16 bytes public Guid TrayIconGuid => OSVersion.IsWindows11OrHigher() ? new("{50487227-4f3b-071d-baec-cd22bfff900d}") : Guid.Empty; public string ApplicationName { get; } = App.APPLICATION_NAME; public string OpenApplicationLabel => Localizer.GetFormat("System_Tray_Actions_OpenApplication", ApplicationName); public TrayIconComponentViewModel( IAppExitInvoker appExitInvoker, IServersCache serversCache, IMainWindowActivator mainWindowActivator, ITrayAppWindowActivator trayAppWindowActivator, IRecentConnectionsManager recentConnectionsManager, IConnectionManager connectionManager, IUserAuthenticator userAuthenticator, IApplicationIconSelector applicationIconSelector, IViewModelHelper viewModelHelper) : base(viewModelHelper) { _appExitInvoker = appExitInvoker; _serversCache = serversCache; _mainWindowActivator = mainWindowActivator; _trayAppWindowActivator = trayAppWindowActivator; _recentConnectionsManager = recentConnectionsManager; _connectionManager = connectionManager; _userAuthenticator = userAuthenticator; _applicationIconSelector = applicationIconSelector; } [RelayCommand] public void ShowApplication() { _mainWindowActivator.Activate(); } [RelayCommand] public void ShowTrayApplication() { // When not logged in, skip the tray app and open the application instead if (!_userAuthenticator.IsLoggedIn || _serversCache.HasNoServers()) { ShowApplication(); return; } _trayAppWindowActivator.Activate(); } [RelayCommand] public async Task ExitApplicationAsync() { await _appExitInvoker.ExitWithConfirmationAsync(); } [RelayCommand(CanExecute = nameof(CanConnect))] public async Task ConnectAsync() { IConnectionIntent defaultConnectionIntent = _recentConnectionsManager.GetDefaultConnection(); await _connectionManager.ConnectAsync(VpnTriggerDimension.Tray, defaultConnectionIntent); } [RelayCommand(CanExecute = nameof(CanDisconnect))] public async Task DisconnectAsync() { await _connectionManager.DisconnectAsync(VpnTriggerDimension.Tray); } public void Receive(ConnectionStatusChangedMessage message) { ExecuteOnUIThread(InvalidateTray); } public void Receive(LoggedInMessage message) { ExecuteOnUIThread(InvalidateTray); } public void Receive(LoggedOutMessage message) { ExecuteOnUIThread(InvalidateTray); } public void Receive(AppIconStatusChangedMessage message) { ExecuteOnUIThread(InvalidateTray); } protected override void OnLanguageChanged() { base.OnLanguageChanged(); OnPropertyChanged(nameof(OpenApplicationLabel)); } private bool CanConnect() { return _userAuthenticator.IsLoggedIn && _connectionManager.IsDisconnected && _connectionManager.IsConnectAllowed && !_serversCache.HasNoServers(); } private bool CanDisconnect() { return _userAuthenticator.IsLoggedIn && !_connectionManager.IsDisconnected; } private void InvalidateTray() { ConnectCommand.NotifyCanExecuteChanged(); DisconnectCommand.NotifyCanExecuteChanged(); OnPropertyChanged(nameof(IconSource)); } }