123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- #include "pch.h"
- #include "MainPage.h"
- #include "MainPage.g.cpp"
- #include <filesystem>
- #include <winrt/Windows.ApplicationModel.Core.h>
- #include <winrt/Windows.UI.ViewManagement.h>
- #include <winrt/Windows.UI.Xaml.Media.h>
- #include "Model\Netif.h"
- #include "UI.h"
- namespace winrt::Maple_App::implementation
- {
- using namespace std::chrono_literals;
- using namespace winrt::Windows::UI::ViewManagement;
- using namespace winrt::Windows::UI::Xaml;
- using namespace winrt::Windows::UI::Xaml::Media;
- std::string getNormalizedExtentionFromPath(const winrt::hstring& path) {
- auto ext = std::filesystem::path(std::wstring_view(path)).extension().string();
- std::transform(ext.begin(), ext.end(), ext.begin(), [](const auto ch) {
- return static_cast<char>(std::tolower(ch));
- });
- return ext;
- }
- MainPage::MainPage()
- {
- InitializeComponent();
- const auto coreTitleBar{ CoreApplication::GetCurrentView().TitleBar() };
- const auto window{ Window::Current() };
- coreTitleBar.ExtendViewIntoTitleBar(true);
- window.SetTitleBar(CustomTitleBar());
- coreTitleBar.LayoutMetricsChanged({ this,&MainPage::CoreTitleBar_LayoutMetricsChanged });
- window.Activated({ this, &MainPage::CoreWindow_Activated });
- }
- void MainPage::CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar const& coreTitleBar, IInspectable const&)
- {
- LeftPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayLeftInset(), .GridUnitType = GridUnitType::Pixel });
- RightPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayRightInset(), .GridUnitType = GridUnitType::Pixel });
- }
- void MainPage::CoreWindow_Activated(IInspectable const&, WindowActivatedEventArgs const& args)
- {
- UISettings settings{};
- if (args.WindowActivationState() == CoreWindowActivationState::Deactivated)
- {
- AppTitleTextBlock().Foreground(
- SolidColorBrush{ settings.UIElementColor(UIElementType::GrayText) });
- }
- else
- {
- AppTitleTextBlock().Foreground(
- SolidColorBrush{ settings.GetColorValue(UIColorType::Foreground) });
- }
- }
- DependencyProperty MainPage::ConfigItemsProperty()
- {
- return m_configItemsProperty;
- }
- IObservableVector<Maple_App::ConfigViewModel> MainPage::ConfigItems()
- {
- return GetValue(m_configItemsProperty).as<IObservableVector<Maple_App::ConfigViewModel>>();
- }
- void MainPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
- {
- const auto _ = LoadConfigs();
- NavigationManager = SystemNavigationManager::GetForCurrentView();
- NavigationManager.AppViewBackButtonVisibility(MainSplitView().IsPaneOpen()
- ? AppViewBackButtonVisibility::Collapsed
- : AppViewBackButtonVisibility::Visible);
- const auto weakThis = get_weak();
- NavigationManager.BackRequested([weakThis](const auto&, const auto&) {
- if (const auto self{ weakThis.get() }) {
- self->MainSplitView().IsPaneOpen(true);
- const auto currentVisibility = NavigationManager.AppViewBackButtonVisibility();
- if (currentVisibility == AppViewBackButtonVisibility::Visible) {
- NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Disabled);
- }
- }
- });
- StartConnectionCheck();
- }
- IAsyncAction MainPage::NotifyUser(const hstring& message) {
- ContentDialog dialog;
- dialog.Content(box_value(message));
- dialog.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
- co_await dialog.ShowAsync();
- }
- IAsyncOperation<IStorageFolder> MainPage::InitializeConfigFolder()
- {
- const auto& appData = ApplicationData::Current();
- const auto& localFolder = appData.LocalFolder();
- auto configItem = co_await localFolder.TryGetItemAsync(L"config");
- if (configItem == nullptr || configItem.IsOfType(StorageItemTypes::File)) {
- configItem = co_await localFolder.CreateFolderAsync(L"config", CreationCollisionOption::ReplaceExisting);
- }
- co_return configItem.as<IStorageFolder>();
- }
- IAsyncOperation<StorageFile> MainPage::CopyDefaultConfig(const IStorageFolder& configFolder, const hstring& desiredName)
- {
- const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.conf" });
- co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
- }
- IAsyncOperation<StorageFile> MainPage::CopyDefaultJsonConfig(const IStorageFolder& configFolder, const hstring& desiredName)
- {
- const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.json" });
- co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
- }
- IAsyncAction MainPage::LoadConfigs()
- {
- const auto lifetime = get_strong();
- const auto& appData = ApplicationData::Current();
- m_configFolder = co_await InitializeConfigFolder();
- auto configFiles = co_await m_configFolder.GetFilesAsync();
- if (configFiles.Size() == 0) {
- const auto& defaultConfigDst = co_await CopyDefaultConfig(m_configFolder, L"default.conf");
- appData.LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(defaultConfigDst.Path()));
- configFiles = co_await m_configFolder.GetFilesAsync();
- }
- std::vector<Maple_App::ConfigViewModel> configModels;
- configModels.reserve(static_cast<size_t>(configFiles.Size()));
- const auto& defaultConfigPath = appData.LocalSettings().Values().TryLookup(CONFIG_PATH_SETTING_KEY).try_as<hstring>();
- for (const auto& file : configFiles) {
- const auto isDefault = file.Path() == defaultConfigPath;
- const auto& instance = configModels.emplace_back(co_await ConfigViewModel::FromFile(file, isDefault));
- if (isDefault) {
- m_defaultConfig = instance;
- }
- }
- SetValue(m_configItemsProperty, single_threaded_observable_vector<Maple_App::ConfigViewModel>(std::move(configModels)));
- ConfigListView().SelectedItem(m_defaultConfig);
- }
- void MainPage::ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
- {
- try {
- auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
- }
- SetAsDefault(item);
- }
- catch (...)
- {
- UI::NotifyException(L"Setting default");
- }
- }
- void MainPage::ConfigItem_DoubleTapped(IInspectable const& sender, DoubleTappedRoutedEventArgs const&)
- {
- const auto& item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
- SetAsDefault(item);
- }
- void MainPage::SetAsDefault(const Maple_App::ConfigViewModel& item)
- {
- try {
- const auto name = getNormalizedExtentionFromPath(item.Name());
- if (name != ".conf" && name != ".json") {
- NotifyUser(L"A valid configuration file must end with .conf or .json.");
- return;
- }
- ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
- m_defaultConfig.IsDefault(false);
- item.IsDefault(true);
- m_defaultConfig = item;
- }
- catch (...)
- {
- UI::NotifyException(L"Setting default");
- }
- }
- void MainPage::ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
- {
- try {
- auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
- }
- RequestRenameItem(item);
- }
- catch (...)
- {
- UI::NotifyException(L"Requesting rename");
- }
- }
- void MainPage::RequestRenameItem(const Maple_App::ConfigViewModel& item)
- {
- const auto& name = item.Name();
- const auto& renameDialog = RenameDialog();
- const auto& renameDialogText = RenameDialogText();
- renameDialogText.Text(name);
- auto it = std::find(name.rbegin(), name.rend(), '.');
- if (it != name.rend()) {
- RenameDialogText().Select(0, static_cast<int32_t>(name.rend() - it) - 1);
- }
- renameDialog.DataContext(item);
- renameDialog.ShowAsync();
- }
- fire_and_forget MainPage::ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
- {
- try {
- const auto lifetime = get_strong();
- auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
- }
- if (item.IsDefault()) {
- co_await NotifyUser(L"Default configuration cannot be deleted.");
- co_return;
- }
- uint32_t index;
- auto configItems = ConfigItems();
- if (!configItems.IndexOf(item, index)) {
- co_return;
- }
- ContentDialog c{};
- c.Title(box_value(L"Delete this configuration file?"));
- c.Content(box_value(L"This operation cannot be undone."));
- c.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Delete });
- c.SecondaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
- const auto& result = co_await c.ShowAsync();
- if (result != ContentDialogResult::Primary) {
- co_return;
- }
- if (ConfigListView().SelectedItem() == item) {
- ConfigListView().SelectedIndex(ConfigListView().SelectedIndex() == 0 ? 1 : 0);
- }
- configItems.RemoveAt(index);
- co_await item.Delete();
- if (configItems.Size() == 0) {
- LoadConfigs();
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Deleting file");
- }
- }
- void MainPage::RenameDialogPrimaryButton_Click(IInspectable const&, ContentDialogButtonClickEventArgs const&)
- {
- ConfirmRename();
- }
- void MainPage::RenameDialogText_KeyDown(IInspectable const&, KeyRoutedEventArgs const& e)
- {
- if (e.Key() == Windows::System::VirtualKey::Enter) {
- ConfirmRename();
- RenameDialog().Hide();
- }
- }
- fire_and_forget MainPage::ConfirmRename() {
- try {
- const auto lifetime = get_strong();
- const auto& renameDialog = RenameDialog();
- const auto& item = renameDialog.DataContext().as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- co_return;
- }
- co_await item.Rename(RenameDialogText().Text());
- if (item == m_defaultConfig) {
- ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Renaming");
- }
- }
- fire_and_forget MainPage::ConfigCreateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
- {
- try {
- const auto lifetime = get_strong();
- const auto& buttonText = sender.as<MenuFlyoutItem>().Text();
- StorageFile newFile{ nullptr };
- if (buttonText == L"Conf") {
- newFile = co_await CopyDefaultConfig(m_configFolder, L"New Config.conf");
- }
- else if (buttonText == L"JSON") {
- newFile = co_await CopyDefaultJsonConfig(m_configFolder, L"New Config.json");
- }
- else {
- co_return;
- }
- const auto& item = co_await ConfigViewModel::FromFile(newFile, false);
- ConfigItems().Append(item);
- RequestRenameItem(item);
- }
- catch (...)
- {
- UI::NotifyException(L"Creating file");
- }
- }
- fire_and_forget MainPage::ConfigImportMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
- {
- try {
- const auto lifetime = get_strong();
- bool unsnapped = ((ApplicationView::Value() != ApplicationViewState::Snapped) || ApplicationView::TryUnsnap());
- if (!unsnapped)
- {
- co_await NotifyUser(L"Cannot unsnap the app.");
- co_return;
- }
- ImportFilePicker().FileTypeFilter().ReplaceAll({ L".conf", L".json", L".mmdb", L".dat", L".cer", L".crt" });
- const auto& files = co_await ImportFilePicker().PickMultipleFilesAsync();
- co_await ImportFiles(files);
- }
- catch (...)
- {
- UI::NotifyException(L"Importing files");
- }
- }
- fire_and_forget MainPage::ConfigDuplicateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
- {
- try {
- const auto lifetime = get_strong();
- auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
- }
- const auto& file = item.File();
- const auto& parent = co_await file.GetParentAsync();
- const auto& newFile = co_await file.CopyAsync(parent, file.Name(), NameCollisionOption::GenerateUniqueName);
- ConfigItems().Append(co_await ConfigViewModel::FromFile(newFile, false));
- }
- catch (...)
- {
- UI::NotifyException(L"Duplicating");
- }
- }
- void MainPage::MainPivot_PivotItemLoaded(Pivot const&, PivotItemEventArgs const& args)
- {
- try {
- if (args.Item().Header().as<hstring>() == L"Setting") {
- const auto& netifs = Netif::EnumerateInterfaces();
- std::vector<IInspectable> boxed_netifs;
- boxed_netifs.reserve(netifs.size());
- std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
- return netif;
- });
- NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
- const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
- if (currentNetif.has_value()) {
- NetifCombobox().SelectedValue(box_value(currentNetif.value()));
- }
- else {
- const auto it = std::find_if(netifs.begin(), netifs.end(), [](const auto& netif) -> bool {
- return netif.Desc().size() > 0 && netif.Desc()[0] == L'★';
- });
- if (it != netifs.end()) {
- NetifCombobox().SelectedItem(*it);
- }
- }
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Loading settings");
- }
- }
- void MainPage::NetifCombobox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
- {
- try {
- const auto it = e.AddedItems().First();
- if (!it.HasCurrent() || it.Current().try_as<Maple_App::Netif>() == nullptr) {
- return;
- }
- const auto& netif = it.Current().as<Maple_App::Netif>();
- ApplicationData::Current().LocalSettings().Values().Insert(NETIF_SETTING_KEY, box_value(netif.Addr()));
- }
- catch (...)
- {
- UI::NotifyException(L"Setting interface");
- }
- }
- void MainPage::ConfigListView_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
- {
- try {
- const auto& item = e.AddedItems().First().Current();
- auto targetPage = xaml_typename<MonacoEditPage>();
- const auto& config = item.try_as<Maple_App::ConfigViewModel>();
- if (config != nullptr) {
- const auto ext = getNormalizedExtentionFromPath(config.Name());
- if (ext == ".mmdb") {
- targetPage = xaml_typename<MmdbPage>();
- }
- else if (ext == ".dat") {
- targetPage = xaml_typename<DatPage>();
- }
- else if (ext == ".cer" || ext == ".crt") {
- targetPage = xaml_typename<CertPage>();
- }
- }
- if (targetPage.Name != xaml_typename<MonacoEditPage>().Name)
- {
- MainContentFrame().BackStack().Clear();
- }
- MainContentFrame().Navigate(targetPage, item);
- }
- catch (...)
- {
- UI::NotifyException(L"Opening file");
- }
- }
- void MainPage::ConfigListView_DragItemsStarting(IInspectable const&, DragItemsStartingEventArgs const& e)
- {
- try {
- std::vector<IStorageItem> files;
- files.reserve(static_cast<size_t>(e.Items().Size()));
- for (const auto& obj : e.Items()) {
- const auto& item = obj.try_as<Maple_App::ConfigViewModel>();
- if (item == nullptr) {
- continue;
- }
- files.push_back(item.File());
- }
- const auto& data = e.Data();
- data.SetStorageItems(files);
- data.RequestedOperation(DataPackageOperation::Copy);
- }
- catch (...)
- {
- UI::NotifyException(L"Preparing drag items");
- }
- }
- void MainPage::ConfigListView_DragOver(IInspectable const&, DragEventArgs const& e)
- {
- try {
- if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
- || !e.DataView().Contains(StandardDataFormats::StorageItems())) {
- e.AcceptedOperation(DataPackageOperation::None);
- return;
- }
- e.AcceptedOperation(DataPackageOperation::Copy);
- }
- catch (...)
- {
- UI::NotifyException(L"Dragging");
- }
- }
- fire_and_forget MainPage::ConfigListView_Drop(IInspectable const&, DragEventArgs const& e)
- {
- try {
- const auto lifetime = get_strong();
- const auto& dataView = e.DataView();
- if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
- || !dataView.Contains(StandardDataFormats::StorageItems())) {
- co_return;
- }
- const auto& items = co_await dataView.GetStorageItemsAsync();
- co_await ImportFiles(items);
- }
- catch (...)
- {
- UI::NotifyException(L"Pasting files");
- }
- }
- void MainPage::WindowWidth_CurrentStateChanged(IInspectable const&, VisualStateChangedEventArgs const& e)
- {
- const auto& state = e.NewState();
- NavigationManager.AppViewBackButtonVisibility(state == nullptr
- ? AppViewBackButtonVisibility::Visible
- : AppViewBackButtonVisibility::Collapsed);
- }
- void MainPage::MainSplitView_PaneClosing(SplitView const&, SplitViewPaneClosingEventArgs const&)
- {
- NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Visible);
- }
- fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
- {
- try {
- const auto lifetime = get_strong();
- const auto& profile = VpnPlugInProfile{};
- profile.AlwaysOn(false);
- profile.ProfileName(L"Maple");
- profile.RequireVpnClientAppUI(true);
- profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
- profile.RememberCredentials(false);
- profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
- const auto& result = co_await VpnMgmtAgent.AddProfileFromObjectAsync(profile);
- if (result == VpnManagementErrorStatus::Ok) {
- co_await NotifyUser(L"Profile generated.");
- }
- else {
- co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Generating profile");
- }
- }
- fire_and_forget MainPage::ConnectionToggleSwitch_Toggled(IInspectable const&, RoutedEventArgs const&)
- {
- try {
- const auto lifetime{ get_strong() };
- if (!ApplicationData::Current().LocalSettings().Values().HasKey(NETIF_SETTING_KEY)) {
- MainPivot().SelectedIndex(1);
- co_await 400ms;
- co_await resume_foreground(Dispatcher());
- NetifCombobox().IsDropDownOpen(true);
- co_return;
- }
- const auto connect = ConnectionToggleSwitch().IsOn();
- ConnectionToggleSwitch().IsEnabled(false);
- VpnManagementErrorStatus status = VpnManagementErrorStatus::Ok;
- if (connect) {
- status = co_await VpnMgmtAgent.ConnectProfileAsync(m_vpnProfile);
- }
- else {
- status = co_await VpnMgmtAgent.DisconnectProfileAsync(m_vpnProfile);
- }
- if (status == VpnManagementErrorStatus::Ok)
- {
- ConnectionToggleSwitch().IsEnabled(true);
- }
- else {
- NotifyUser(L"Could not perform the requested operation. Please try again from system VPN settings for detailed error messages.");
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Connecting");
- }
- }
- fire_and_forget MainPage::StartConnectionCheck()
- {
- try {
- const auto lifetime{ get_strong() };
- IVectorView<IVpnProfile> profiles{ nullptr };
- auto event_token{ ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled }) };
- while (true) {
- if (m_vpnProfile == nullptr) {
- profiles = co_await VpnMgmtAgent.GetProfilesAsync();
- for (auto const p : profiles) {
- if (p.ProfileName() == L"Maple" || p.ProfileName() == L"maple") {
- m_vpnProfile = p.try_as<VpnPlugInProfile>();
- break;
- }
- }
- }
- if (m_vpnProfile == nullptr) {
- ConnectionToggleSwitch().IsEnabled(false);
- }
- else {
- ToolTipService::SetToolTip(ConnectionToggleSwitchContainer(), nullptr);
- auto status = VpnManagementConnectionStatus::Disconnected;
- try {
- status = m_vpnProfile.ConnectionStatus();
- }
- catch (...) {}
- ConnectionToggleSwitch().IsEnabled(status == VpnManagementConnectionStatus::Connected
- || status == VpnManagementConnectionStatus::Disconnected);
- ConnectionToggleSwitch().Toggled(event_token);
- ConnectionToggleSwitch().IsOn(status == VpnManagementConnectionStatus::Connected
- || status == VpnManagementConnectionStatus::Connecting);
- event_token = ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled });
- }
- co_await 1s;
- co_await resume_foreground(Dispatcher());
- }
- }
- catch (...)
- {
- UI::NotifyException(L"Checking VPN status");
- }
- }
- }
|