MainPage.cpp 13 KB

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