MainPage.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. #include "pch.h"
  2. #include "MainPage.h"
  3. #include "MainPage.g.cpp"
  4. #include <filesystem>
  5. #include <winrt/Windows.Networking.Vpn.h>
  6. #include <winrt/Windows.UI.ViewManagement.h>
  7. #include "Model\Netif.h"
  8. namespace winrt::Maple_App::implementation
  9. {
  10. using namespace winrt::Windows::UI::ViewManagement;
  11. std::string getNormalizedExtentionFromPath(const winrt::hstring& path) {
  12. auto ext = std::filesystem::path(std::wstring_view(path)).extension().string();
  13. std::transform(ext.begin(), ext.end(), ext.begin(), [](const auto ch) {
  14. return static_cast<char>(std::tolower(ch));
  15. });
  16. return ext;
  17. }
  18. MainPage::MainPage()
  19. {
  20. InitializeComponent();
  21. }
  22. DependencyProperty MainPage::ConfigItemsProperty()
  23. {
  24. return m_configItemsProperty;
  25. }
  26. IObservableVector<Maple_App::ConfigViewModel> MainPage::ConfigItems()
  27. {
  28. return GetValue(m_configItemsProperty).as<IObservableVector<Maple_App::ConfigViewModel>>();
  29. }
  30. void MainPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
  31. {
  32. const auto _ = LoadConfigs();
  33. NavigationManager = SystemNavigationManager::GetForCurrentView();
  34. NavigationManager.AppViewBackButtonVisibility(MainSplitView().IsPaneOpen()
  35. ? AppViewBackButtonVisibility::Collapsed
  36. : AppViewBackButtonVisibility::Visible);
  37. const auto weakThis = get_weak();
  38. NavigationManager.BackRequested([weakThis](const auto&, const auto&) {
  39. if (const auto self{ weakThis.get() }) {
  40. self->MainSplitView().IsPaneOpen(true);
  41. const auto currentVisibility = NavigationManager.AppViewBackButtonVisibility();
  42. if (currentVisibility == AppViewBackButtonVisibility::Visible) {
  43. NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Disabled);
  44. }
  45. }
  46. });
  47. }
  48. IAsyncAction MainPage::NotifyUser(const hstring& message) {
  49. ContentDialog dialog;
  50. dialog.Content(box_value(message));
  51. dialog.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
  52. co_await dialog.ShowAsync();
  53. }
  54. IAsyncOperation<IStorageFolder> MainPage::InitializeConfigFolder()
  55. {
  56. const auto& appData = ApplicationData::Current();
  57. const auto& localFolder = appData.LocalFolder();
  58. auto configItem = co_await localFolder.TryGetItemAsync(L"config");
  59. if (configItem == nullptr || configItem.IsOfType(StorageItemTypes::File)) {
  60. configItem = co_await localFolder.CreateFolderAsync(L"config", CreationCollisionOption::ReplaceExisting);
  61. }
  62. co_return configItem.as<IStorageFolder>();
  63. }
  64. IAsyncOperation<StorageFile> MainPage::CopyDefaultConfig(const IStorageFolder& configFolder, const hstring& desiredName)
  65. {
  66. const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.conf" });
  67. co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
  68. }
  69. IAsyncOperation<StorageFile> MainPage::CopyDefaultJsonConfig(const IStorageFolder& configFolder, const hstring& desiredName)
  70. {
  71. const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ L"ms-appx:///Config/default.json" });
  72. co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
  73. }
  74. IAsyncAction MainPage::LoadConfigs()
  75. {
  76. const auto lifetime = get_strong();
  77. const auto& appData = ApplicationData::Current();
  78. m_configFolder = co_await InitializeConfigFolder();
  79. auto configFiles = co_await m_configFolder.GetFilesAsync();
  80. if (configFiles.Size() == 0) {
  81. const auto& defaultConfigDst = co_await CopyDefaultConfig(m_configFolder, L"default.conf");
  82. appData.LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(defaultConfigDst.Path()));
  83. configFiles = co_await m_configFolder.GetFilesAsync();
  84. }
  85. std::vector<Maple_App::ConfigViewModel> configModels;
  86. configModels.reserve(static_cast<size_t>(configFiles.Size()));
  87. const auto& defaultConfigPath = appData.LocalSettings().Values().TryLookup(CONFIG_PATH_SETTING_KEY).try_as<hstring>();
  88. for (const auto& file : configFiles) {
  89. const auto isDefault = file.Path() == defaultConfigPath;
  90. const auto& instance = configModels.emplace_back(co_await ConfigViewModel::FromFile(file, isDefault));
  91. if (isDefault) {
  92. m_defaultConfig = instance;
  93. }
  94. }
  95. SetValue(m_configItemsProperty, single_threaded_observable_vector<Maple_App::ConfigViewModel>(std::move(configModels)));
  96. ConfigListView().SelectedItem(m_defaultConfig);
  97. }
  98. void MainPage::ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  99. {
  100. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  101. if (item == nullptr) {
  102. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  103. }
  104. SetAsDefault(item);
  105. }
  106. void MainPage::ConfigItem_DoubleTapped(IInspectable const& sender, DoubleTappedRoutedEventArgs const&)
  107. {
  108. const auto& item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  109. SetAsDefault(item);
  110. }
  111. void MainPage::SetAsDefault(const Maple_App::ConfigViewModel& item)
  112. {
  113. const auto name = getNormalizedExtentionFromPath(item.Name());
  114. if (name != ".conf" && name != ".json") {
  115. NotifyUser(L"A valid configuration file must end with .conf or .json.");
  116. return;
  117. }
  118. ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
  119. m_defaultConfig.IsDefault(false);
  120. item.IsDefault(true);
  121. m_defaultConfig = item;
  122. }
  123. void MainPage::ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  124. {
  125. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  126. if (item == nullptr) {
  127. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  128. }
  129. RequestRenameItem(item);
  130. }
  131. void MainPage::RequestRenameItem(const Maple_App::ConfigViewModel& item)
  132. {
  133. const auto& name = item.Name();
  134. const auto& renameDialog = RenameDialog();
  135. const auto& renameDialogText = RenameDialogText();
  136. renameDialogText.Text(name);
  137. auto it = std::find(name.rbegin(), name.rend(), '.');
  138. if (it != name.rend()) {
  139. RenameDialogText().Select(0, static_cast<int32_t>(name.rend() - it) - 1);
  140. }
  141. renameDialog.DataContext(item);
  142. renameDialog.ShowAsync();
  143. }
  144. fire_and_forget MainPage::ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
  145. {
  146. const auto lifetime = get_strong();
  147. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  148. if (item == nullptr) {
  149. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  150. }
  151. if (item.IsDefault()) {
  152. co_await NotifyUser(L"Default configuration cannot be deleted.");
  153. co_return;
  154. }
  155. uint32_t index;
  156. auto configItems = ConfigItems();
  157. if (!configItems.IndexOf(item, index)) {
  158. co_return;
  159. }
  160. ContentDialog c{};
  161. c.Title(box_value(L"Delete this configuration file?"));
  162. c.Content(box_value(L"This operation cannot be undone."));
  163. c.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Delete });
  164. c.SecondaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
  165. const auto& result = co_await c.ShowAsync();
  166. if (result != ContentDialogResult::Primary) {
  167. co_return;
  168. }
  169. if (ConfigListView().SelectedItem() == item) {
  170. ConfigListView().SelectedIndex(!ConfigListView().SelectedIndex());
  171. }
  172. configItems.RemoveAt(index);
  173. co_await item.Delete();
  174. if (configItems.Size() == 0) {
  175. LoadConfigs();
  176. }
  177. }
  178. void MainPage::RenameDialogPrimaryButton_Click(IInspectable const&, ContentDialogButtonClickEventArgs const&)
  179. {
  180. ConfirmRename();
  181. }
  182. void MainPage::RenameDialogText_KeyDown(IInspectable const&, KeyRoutedEventArgs const& e)
  183. {
  184. if (e.Key() == Windows::System::VirtualKey::Enter) {
  185. ConfirmRename();
  186. RenameDialog().Hide();
  187. }
  188. }
  189. fire_and_forget MainPage::ConfirmRename() {
  190. const auto lifetime = get_strong();
  191. const auto& renameDialog = RenameDialog();
  192. const auto& item = renameDialog.DataContext().as<Maple_App::ConfigViewModel>();
  193. if (item == nullptr) {
  194. co_return;
  195. }
  196. co_await item.Rename(RenameDialogText().Text());
  197. if (item == m_defaultConfig) {
  198. ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
  199. }
  200. }
  201. fire_and_forget MainPage::ConfigCreateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  202. {
  203. const auto lifetime = get_strong();
  204. const auto& buttonText = sender.as<MenuFlyoutItem>().Text();
  205. StorageFile newFile{ nullptr };
  206. if (buttonText == L"Conf") {
  207. newFile = co_await CopyDefaultConfig(m_configFolder, L"New Config.conf");
  208. }
  209. else if (buttonText == L"JSON") {
  210. newFile = co_await CopyDefaultJsonConfig(m_configFolder, L"New Config.json");
  211. }
  212. else {
  213. co_return;
  214. }
  215. const auto& item = co_await ConfigViewModel::FromFile(newFile, false);
  216. ConfigItems().Append(item);
  217. RequestRenameItem(item);
  218. }
  219. fire_and_forget MainPage::ConfigImportMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
  220. {
  221. const auto lifetime = get_strong();
  222. bool unsnapped = ((ApplicationView::Value() != ApplicationViewState::Snapped) || ApplicationView::TryUnsnap());
  223. if (!unsnapped)
  224. {
  225. co_await NotifyUser(L"Cannot unsnap the app.");
  226. co_return;
  227. }
  228. ImportFilePicker().FileTypeFilter().ReplaceAll({ L".conf", L".json", L".mmdb", L".dat", L".cer", L".crt" });
  229. const auto& files = co_await ImportFilePicker().PickMultipleFilesAsync();
  230. co_await ImportFiles(files);
  231. }
  232. fire_and_forget MainPage::ConfigDuplicateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  233. {
  234. const auto lifetime = get_strong();
  235. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  236. if (item == nullptr) {
  237. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  238. }
  239. const auto& file = item.File();
  240. const auto& parent = co_await file.GetParentAsync();
  241. const auto& newFile = co_await file.CopyAsync(parent, file.Name(), NameCollisionOption::GenerateUniqueName);
  242. ConfigItems().Append(co_await ConfigViewModel::FromFile(newFile, false));
  243. }
  244. void MainPage::MainPivot_PivotItemLoaded(Pivot const&, PivotItemEventArgs const& args)
  245. {
  246. if (args.Item().Header().as<hstring>() == L"Setting") {
  247. const auto& netifs = Netif::EnumerateInterfaces();
  248. std::vector<IInspectable> boxed_netifs;
  249. boxed_netifs.reserve(netifs.size());
  250. std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
  251. return netif;
  252. });
  253. NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
  254. const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
  255. if (currentNetif.has_value()) {
  256. NetifCombobox().SelectedValue(box_value(currentNetif.value()));
  257. }
  258. else {
  259. const auto it = std::find_if(netifs.begin(), netifs.end(), [](const auto& netif) -> bool {
  260. return netif.Desc().size() > 0 && netif.Desc()[0] == L'★';
  261. });
  262. if (it != netifs.end()) {
  263. NetifCombobox().SelectedItem(*it);
  264. }
  265. }
  266. }
  267. }
  268. void MainPage::NetifCombobox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
  269. {
  270. const auto it = e.AddedItems().First();
  271. if (!it.HasCurrent() || it.Current().try_as<Maple_App::Netif>() == nullptr) {
  272. return;
  273. }
  274. const auto& netif = it.Current().as<Maple_App::Netif>();
  275. ApplicationData::Current().LocalSettings().Values().Insert(NETIF_SETTING_KEY, box_value(netif.Addr()));
  276. }
  277. void MainPage::ConfigListView_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
  278. {
  279. const auto& item = e.AddedItems().First().Current();
  280. auto targetPage = xaml_typename<EditPage>();
  281. const auto& config = item.try_as<Maple_App::ConfigViewModel>();
  282. if (config != nullptr) {
  283. const auto ext = getNormalizedExtentionFromPath(config.Name());
  284. if (ext == ".mmdb") {
  285. targetPage = xaml_typename<MmdbPage>();
  286. }
  287. else if (ext == ".dat") {
  288. targetPage = xaml_typename<DatPage>();
  289. }
  290. else if (ext == ".cer" || ext == ".crt") {
  291. targetPage = xaml_typename<CertPage>();
  292. }
  293. }
  294. MainContentFrame().BackStack().Clear();
  295. MainContentFrame().Navigate(targetPage, item);
  296. }
  297. void MainPage::ConfigListView_DragItemsStarting(IInspectable const&, DragItemsStartingEventArgs const& e)
  298. {
  299. std::vector<IStorageItem> files;
  300. files.reserve(static_cast<size_t>(e.Items().Size()));
  301. for (const auto& obj : e.Items()) {
  302. const auto& item = obj.try_as<Maple_App::ConfigViewModel>();
  303. if (item == nullptr) {
  304. continue;
  305. }
  306. files.push_back(item.File());
  307. }
  308. const auto& data = e.Data();
  309. data.SetStorageItems(files);
  310. data.RequestedOperation(DataPackageOperation::Copy);
  311. }
  312. void MainPage::ConfigListView_DragOver(IInspectable const&, DragEventArgs const& e)
  313. {
  314. if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
  315. || !e.DataView().Contains(StandardDataFormats::StorageItems())) {
  316. e.AcceptedOperation(DataPackageOperation::None);
  317. return;
  318. }
  319. e.AcceptedOperation(DataPackageOperation::Copy);
  320. }
  321. fire_and_forget MainPage::ConfigListView_Drop(IInspectable const&, DragEventArgs const& e)
  322. {
  323. const auto lifetime = get_strong();
  324. const auto& dataView = e.DataView();
  325. if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
  326. || !dataView.Contains(StandardDataFormats::StorageItems())) {
  327. co_return;
  328. }
  329. const auto& items = co_await dataView.GetStorageItemsAsync();
  330. co_await ImportFiles(items);
  331. }
  332. void MainPage::WindowWidth_CurrentStateChanged(IInspectable const&, VisualStateChangedEventArgs const& e)
  333. {
  334. const auto& state = e.NewState();
  335. NavigationManager.AppViewBackButtonVisibility(state == nullptr
  336. ? AppViewBackButtonVisibility::Visible
  337. : AppViewBackButtonVisibility::Collapsed);
  338. }
  339. void MainPage::MainSplitView_PaneClosing(SplitView const&, SplitViewPaneClosingEventArgs const&)
  340. {
  341. NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Visible);
  342. }
  343. fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
  344. {
  345. const auto lifetime = get_strong();
  346. using namespace winrt::Windows::Networking::Vpn;
  347. const auto& agent = VpnManagementAgent{};
  348. const auto& profile = VpnPlugInProfile{};
  349. profile.AlwaysOn(false);
  350. profile.ProfileName(L"Maple");
  351. profile.RequireVpnClientAppUI(true);
  352. profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
  353. profile.RememberCredentials(false);
  354. profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
  355. const auto& result = co_await agent.AddProfileFromObjectAsync(profile);
  356. if (result == VpnManagementErrorStatus::Ok) {
  357. co_await NotifyUser(L"Profile generated.");
  358. }
  359. else {
  360. co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
  361. }
  362. }
  363. }