diff --git a/src/Magpie.App/App.idl b/src/Magpie.App/App.idl index f5c2f28bc..62acbe9a0 100644 --- a/src/Magpie.App/App.idl +++ b/src/Magpie.App/App.idl @@ -18,7 +18,8 @@ #include "ProfileViewModel.idl" #include "SettingsViewModel.idl" #include "CandidateWindowItem.idl" -#include "NewProfileViewModel.idl" +#include "NewApplicationViewModel.idl" +#include "ProfileApplicationItem.idl" #include "AboutViewModel.idl" #include "MainPage.idl" #include "AboutPage.idl" @@ -31,6 +32,7 @@ namespace Magpie.App { enum ShortcutAction { Scale, Overlay, + Is3DGameMode, COUNT_OR_NONE }; diff --git a/src/Magpie.App/AppSettings.cpp b/src/Magpie.App/AppSettings.cpp index 6b23fe22d..570a3b2c6 100644 --- a/src/Magpie.App/AppSettings.cpp +++ b/src/Magpie.App/AppSettings.cpp @@ -62,16 +62,23 @@ static void WriteProfile(rapidjson::PrettyWriter& write if (!profile.name.empty()) { writer.Key("name"); writer.String(StrUtils::UTF16ToUTF8(profile.name).c_str()); - writer.Key("packaged"); - writer.Bool(profile.isPackaged); - writer.Key("pathRule"); - writer.String(StrUtils::UTF16ToUTF8(profile.pathRule).c_str()); - writer.Key("classNameRule"); - writer.String(StrUtils::UTF16ToUTF8(profile.classNameRule).c_str()); + writer.Key("applications"); + writer.StartArray(); + for (const ProfileApplication& application : profile.applications) { + writer.StartObject(); + writer.Key("packaged"); + writer.Bool(application.isPackaged); + writer.Key("pathRule"); + writer.String(StrUtils::UTF16ToUTF8(application.pathRule).c_str()); + writer.Key("classNameRule"); + writer.String(StrUtils::UTF16ToUTF8(application.classNameRule).c_str()); + writer.Key("launchParameters"); + writer.String(StrUtils::UTF16ToUTF8(application.launchParameters).c_str()); + writer.EndObject(); + } + writer.EndArray(); writer.Key("autoScale"); writer.Bool(profile.isAutoScale); - writer.Key("launchParameters"); - writer.String(StrUtils::UTF16ToUTF8(profile.launchParameters).c_str()); } writer.Key("scalingMode"); @@ -168,7 +175,7 @@ static void ShowErrorMessage(const wchar_t* mainInstruction, const wchar_t* cont tdc.pszContent = content; tdc.pfCallback = TaskDialogCallback; tdc.cButtons = 1; - TASKDIALOG_BUTTON button{ IDCANCEL, exitStr.c_str()}; + TASKDIALOG_BUTTON button{ IDCANCEL, exitStr.c_str() }; tdc.pButtons = &button; TaskDialogIndirect(&tdc, nullptr, nullptr, nullptr); @@ -221,7 +228,7 @@ bool AppSettings::Initialize() { } // 此时 ResourceLoader 使用“首选语言” - + std::string configText; if (!Win32Utils::ReadTextFile(_configPath.c_str(), configText)) { logger.Error("读取配置文件失败"); @@ -279,7 +286,7 @@ bool AppSettings::Initialize() { L"AppSettings_PortableModeUnkownConfiguration_Exit"); if (!ShowOkCancelWarningMessage(nullptr, contentStr.c_str(), continueStr.c_str(), exitStr.c_str()) - ) { + ) { return false; } } else { @@ -291,7 +298,7 @@ bool AppSettings::Initialize() { L"AppSettings_UnkownConfiguration_EnablePortableMode"); if (!ShowOkCancelWarningMessage(nullptr, contentStr.c_str(), continueStr.c_str(), enablePortableModeStr.c_str()) - ) { + ) { IsPortableMode(true); _SetDefaultScalingModes(); _SetDefaultShortcuts(); @@ -488,6 +495,8 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept { writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Scale])); writer.Key("overlay"); writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Overlay])); + writer.Key("3DGameMode"); + writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Is3DGameMode])); writer.EndObject(); writer.Key("autoRestore"); @@ -612,7 +621,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObjectvalue.IsObject()) { const auto& shortcutsObj = shortcutsNode->value.GetObj(); @@ -626,6 +635,11 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObjectvalue.IsUint()) { DecodeShortcut(overlayNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::Overlay]); } + + auto is3DGameModeNode = shortcutsObj.FindMember("3DGameMode"); + if (is3DGameModeNode != shortcutsObj.MemberEnd() && is3DGameModeNode->value.IsUint()) { + DecodeShortcut(is3DGameModeNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::Is3DGameMode]); + } } JsonHelper::ReadBool(root, "autoRestore", _isAutoRestore); @@ -733,22 +747,35 @@ bool AppSettings::_LoadProfile( } } - if (!JsonHelper::ReadBool(profileObj, "packaged", profile.isPackaged, true)) { - return false; - } + auto applicationsNode = profileObj.FindMember("applications"); + if (applicationsNode != profileObj.MemberEnd() && applicationsNode->value.IsArray()) { + const auto& applicationsArray = applicationsNode->value.GetArray(); - if (!JsonHelper::ReadString(profileObj, "pathRule", profile.pathRule, true) - || profile.pathRule.empty()) { - return false; - } + const rapidjson::SizeType size = applicationsArray.Size(); + if (size > 0) { + profile.applications.reserve((size_t)size); + for (rapidjson::SizeType i = 0; i < size; ++i) { + if (!applicationsArray[i].IsObject()) { + continue; + } - if (!JsonHelper::ReadString(profileObj, "classNameRule", profile.classNameRule, true) - || profile.classNameRule.empty()) { - return false; + ProfileApplication& application = profile.applications.emplace_back(); + if (!_LoadProfileApplication(applicationsArray[i].GetObj(), application)) { + profile.applications.pop_back(); + continue; + } + } + } + } else { + // v0.10.0 的程序配置直接在Profile下 + ProfileApplication& application = profile.applications.emplace_back(); + + if (!_LoadProfileApplication(profileObj, application)) { + return false; + } } JsonHelper::ReadBool(profileObj, "autoScale", profile.isAutoScale); - JsonHelper::ReadString(profileObj, "launchParameters", profile.launchParameters); } JsonHelper::ReadInt(profileObj, "scalingMode", profile.scalingMode); @@ -762,7 +789,7 @@ bool AppSettings::_LoadProfile( // v0.10.0-preview1 使用 captureMode JsonHelper::ReadUInt(profileObj, "captureMode", captureMethod); } - + if (captureMethod > 3) { captureMethod = (uint32_t)CaptureMethod::GraphicsCapture; } else if (captureMethod == (uint32_t)CaptureMethod::DesktopDuplication) { @@ -782,7 +809,7 @@ bool AppSettings::_LoadProfile( } profile.multiMonitorUsage = (MultiMonitorUsage)multiMonitorUsage; } - + if (!JsonHelper::ReadInt(profileObj, "graphicsCard", profile.graphicsCard, true)) { // v0.10.0-preview1 使用 graphicsAdapter uint32_t graphicsAdater = 0; @@ -791,7 +818,6 @@ bool AppSettings::_LoadProfile( } JsonHelper::ReadBoolFlag(profileObj, "disableWindowResizing", MagFlags::DisableWindowResizing, profile.flags); - JsonHelper::ReadBoolFlag(profileObj, "3DGameMode", MagFlags::Is3DGameMode, profile.flags); JsonHelper::ReadBoolFlag(profileObj, "showFPS", MagFlags::ShowFPS, profile.flags); JsonHelper::ReadBoolFlag(profileObj, "VSync", MagFlags::VSync, profile.flags); JsonHelper::ReadBoolFlag(profileObj, "tripleBuffering", MagFlags::TripleBuffering, profile.flags); @@ -811,7 +837,7 @@ bool AppSettings::_LoadProfile( } profile.cursorScaling = (CursorScaling)cursorScaling; } - + JsonHelper::ReadFloat(profileObj, "customCursorScaling", profile.customCursorScaling); if (profile.customCursorScaling < 0) { profile.customCursorScaling = 1.0f; @@ -840,7 +866,7 @@ bool AppSettings::_LoadProfile( || profile.cropping.Right < 0 || !JsonHelper::ReadFloat(croppingObj, "bottom", profile.cropping.Bottom, true) || profile.cropping.Bottom < 0 - ) { + ) { profile.cropping = {}; } } @@ -848,6 +874,64 @@ bool AppSettings::_LoadProfile( return true; } +bool AppSettings::_LoadProfileApplication( + const rapidjson::GenericObject& applicationObj, + ProfileApplication& application +) { + if (!JsonHelper::ReadBool(applicationObj, "packaged", application.isPackaged, true)) { + return false; + } + + if (!JsonHelper::ReadString(applicationObj, "pathRule", application.pathRule, true) + || application.pathRule.empty()) { + return false; + } + + if (!JsonHelper::ReadString(applicationObj, "classNameRule", application.classNameRule, true) + || application.classNameRule.empty()) { + return false; + } + + JsonHelper::ReadString(applicationObj, "launchParameters", application.launchParameters); + + if (!application.isPackaged) { + _SetTruePath(application); + } + + return true; +} + +fire_and_forget AppSettings::_SetTruePath(ProfileApplication& application) { + HANDLE handle = CreateFile( + application.pathRule.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if (handle == INVALID_HANDLE_VALUE) { + co_return; + } + + TCHAR path[MAX_PATH]; + DWORD length = GetFinalPathNameByHandle( + handle, + path, + MAX_PATH, + 0 + ); + + if (length > 0) { + // Skip `\\?\` prefix + application.truePath = &path[4]; + } + + CloseHandle(handle); +} + bool AppSettings::_SetDefaultShortcuts() { bool changed = false; @@ -869,6 +953,15 @@ bool AppSettings::_SetDefaultShortcuts() { changed = true; } + Shortcut& is3DGameModeShortcut = _shortcuts[(size_t)ShortcutAction::Is3DGameMode]; + if (is3DGameModeShortcut.IsEmpty()) { + is3DGameModeShortcut.win = true; + is3DGameModeShortcut.shift = true; + is3DGameModeShortcut.code = 'E'; + + changed = true; + } + return changed; } diff --git a/src/Magpie.App/AppSettings.h b/src/Magpie.App/AppSettings.h index 05c6253a8..abd0aed6a 100644 --- a/src/Magpie.App/AppSettings.h +++ b/src/Magpie.App/AppSettings.h @@ -45,7 +45,7 @@ struct _AppSettingsData { // 上一次自动检查更新的日期 std::chrono::system_clock::time_point _updateCheckDate; - + bool _isPortableMode = false; bool _isAlwaysRunAsAdmin = false; bool _isDebugMode = false; @@ -337,6 +337,11 @@ class AppSettings : private _AppSettingsData { Profile& profile, bool isDefault = false ); + bool _LoadProfileApplication( + const rapidjson::GenericObject& applicationObj, + ProfileApplication& application + ); + fire_and_forget _SetTruePath(ProfileApplication& application); bool _SetDefaultShortcuts(); void _SetDefaultScalingModes(); diff --git a/src/Magpie.App/HomePage.xaml b/src/Magpie.App/HomePage.xaml index 960a1389d..7c70e0dda 100644 --- a/src/Magpie.App/HomePage.xaml +++ b/src/Magpie.App/HomePage.xaml @@ -6,75 +6,87 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d"> - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.5x + 0.75x + + 1.25x + 1.5x + 2x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Magpie.App/ProfileService.cpp b/src/Magpie.App/ProfileService.cpp index 0833aab2d..1efce5096 100644 --- a/src/Magpie.App/ProfileService.cpp +++ b/src/Magpie.App/ProfileService.cpp @@ -4,6 +4,8 @@ #include "AppSettings.h" #include "AppXReader.h" #include +#include "Logger.h" +#include "StrUtils.h" namespace winrt::Magpie::App { @@ -40,15 +42,19 @@ static bool RealTestNewProfile( const std::vector& profiles = AppSettings::Get().Profiles(); if (isPackaged) { - for (const Profile& rule : profiles) { - if (rule.isPackaged && rule.pathRule == pathOrAumid && rule.classNameRule == realClassName) { - return false; + for (const Profile& profile : profiles) { + for (const ProfileApplication& rule : profile.applications) { + if (rule.isPackaged && rule.pathRule == pathOrAumid && rule.classNameRule == realClassName) { + return false; + } } } } else { - for (const Profile& rule : profiles) { - if (!rule.isPackaged && rule.pathRule == pathOrAumid && rule.classNameRule == realClassName) { - return false; + for (const Profile& profile : profiles) { + for (const ProfileApplication& rule : profile.applications) { + if (!rule.isPackaged && rule.GetTruePath() == pathOrAumid && rule.classNameRule == realClassName) { + return false; + } } } } @@ -85,9 +91,11 @@ bool ProfileService::AddProfile( profile.Copy(copyFrom < 0 ? DefaultProfile() : profiles[copyFrom]); profile.name = name; - profile.isPackaged = isPackaged; - profile.pathRule = pathOrAumid; - profile.classNameRule = realClassName; + + ProfileApplication& application = profile.applications.emplace_back(); + application.isPackaged = isPackaged; + application.pathRule = pathOrAumid; + application.classNameRule = realClassName; _profileAddedEvent(std::ref(profile)); @@ -122,6 +130,43 @@ bool ProfileService::MoveProfile(uint32_t profileIdx, bool isMoveUp) { return true; } +bool ProfileService::AddApplication(uint32_t profileIdx, bool isPackaged, std::wstring_view pathOrAumid, std::wstring_view className) { + assert(profileIdx >= 0 && !pathOrAumid.empty() && !className.empty()); + + std::wstring_view realClassName = GetRealClassName(className); + + if (!RealTestNewProfile(isPackaged, pathOrAumid, realClassName)) { + return false; + } + + Profile& profile = GetProfile(profileIdx); + ProfileApplication& application = profile.applications.emplace_back(); + application.isPackaged = isPackaged; + application.pathRule = pathOrAumid; + application.classNameRule = realClassName; + + uint32_t applicationIdx = static_cast(profile.applications.size() - 1); + _applicationAddedEvent(profileIdx, applicationIdx); + + AppSettings::Get().SaveAsync(); + return true; +} + +void ProfileService::RemoveApplication(uint32_t profileIdx, uint32_t applicationIdx) { + Profile& profile = GetProfile(profileIdx); + profile.applications.erase(profile.applications.begin() + applicationIdx); + _applicationRemovedEvent(profileIdx, applicationIdx); + AppSettings::Get().SaveAsync(); +} + +void ProfileService::MoveApplication(uint32_t profileIdx, uint32_t fromIdx, uint32_t toIdx) { + std::vector& applications = GetProfile(profileIdx).applications; + ProfileApplication application = std::move(applications[fromIdx]); + applications.erase(applications.begin() + fromIdx); + applications.emplace(applications.begin() + toIdx, std::move(application)); + AppSettings::Get().SaveAsync(); +} + Profile& ProfileService::GetProfileForWindow(HWND hWnd) { std::wstring className = Win32Utils::GetWndClassName(hWnd); std::wstring_view realClassName = GetRealClassName(className); @@ -130,18 +175,22 @@ Profile& ProfileService::GetProfileForWindow(HWND hWnd) { if (appXReader.Initialize(hWnd)) { // 打包的应用程序匹配 AUMID 和 类名 const std::wstring& aumid = appXReader.AUMID(); - for (Profile& rule : AppSettings::Get().Profiles()) { - if (rule.isPackaged && rule.pathRule == aumid && rule.classNameRule == realClassName) { - return rule; + for (Profile& profile : AppSettings::Get().Profiles()) { + for (ProfileApplication& rule : profile.applications) { + if (rule.isPackaged && rule.pathRule == aumid && rule.classNameRule == realClassName) { + return profile; + } } } } else { // 桌面程序匹配类名和可执行文件名 std::wstring path = Win32Utils::GetPathOfWnd(hWnd); - for (Profile& rule : AppSettings::Get().Profiles()) { - if (!rule.isPackaged && rule.pathRule == path && rule.classNameRule == realClassName) { - return rule; + for (Profile& profile : AppSettings::Get().Profiles()) { + for (ProfileApplication& rule : profile.applications) { + if (!rule.isPackaged && rule.GetTruePath() == path && rule.classNameRule == realClassName) { + return profile; + } } } } diff --git a/src/Magpie.App/ProfileService.h b/src/Magpie.App/ProfileService.h index 818e241b1..83ad39029 100644 --- a/src/Magpie.App/ProfileService.h +++ b/src/Magpie.App/ProfileService.h @@ -4,6 +4,7 @@ namespace winrt::Magpie::App { struct Profile; +struct ProfileApplication; class ProfileService { public: @@ -86,6 +87,42 @@ class ProfileService { _profileReorderedEvent.remove(token); } + bool AddApplication(uint32_t profileIdx, bool isPackaged, std::wstring_view pathOrAumid, std::wstring_view className); + + event_token ApplicationAdded(delegate const& handler) { + return _applicationAddedEvent.add(handler); + } + + WinRTUtils::EventRevoker ApplicationAdded(auto_revoke_t, delegate const& handler) { + event_token token = ApplicationAdded(handler); + return WinRTUtils::EventRevoker([this, token]() { + ApplicationAdded(token); + }); + } + + void ApplicationAdded(event_token const& token) { + _applicationAddedEvent.remove(token); + } + + void RemoveApplication(uint32_t profileIdx, uint32_t applicationIdx); + + event_token ApplicationRemoved(delegate const& handler) { + return _applicationRemovedEvent.add(handler); + } + + WinRTUtils::EventRevoker ApplicationRemoved(auto_revoke_t, delegate const& handler) { + event_token token = ApplicationRemoved(handler); + return WinRTUtils::EventRevoker([this, token]() { + ApplicationRemoved(token); + }); + } + + void ApplicationRemoved(event_token const& token) { + _applicationRemovedEvent.remove(token); + } + + void MoveApplication(uint32_t profileIdx, uint32_t fromIdx, uint32_t toIdx); + Profile& GetProfileForWindow(HWND hWnd); Profile& DefaultProfile(); @@ -101,6 +138,8 @@ class ProfileService { event> _profileRenamedEvent; event> _profileRemovedEvent; event> _profileReorderedEvent; + event> _applicationAddedEvent; + event> _applicationRemovedEvent; }; } diff --git a/src/Magpie.App/ProfileViewModel.cpp b/src/Magpie.App/ProfileViewModel.cpp index d16bebc85..11152f4ec 100644 --- a/src/Magpie.App/ProfileViewModel.cpp +++ b/src/Magpie.App/ProfileViewModel.cpp @@ -60,37 +60,6 @@ ProfileViewModel::ProfileViewModel(int profileIdx) : _isDefaultProfile(profileId } else { _index = (uint32_t)profileIdx; _data = &ProfileService::Get().GetProfile(profileIdx); - - // 占位 - _icon = FontIcon(); - - App app = Application::Current().as(); - MainPage mainPage = app.MainPage(); - _themeChangedRevoker = mainPage.ActualThemeChanged( - auto_revoke, - [this](FrameworkElement const& sender, IInspectable const&) { - _LoadIcon(sender); - } - ); - - _displayInformation = DisplayInformation::GetForCurrentView(); - _dpiChangedRevoker = _displayInformation.DpiChanged( - auto_revoke, - [this](DisplayInformation const&, IInspectable const&) { - if (MainPage mainPage = Application::Current().as().MainPage()) { - _LoadIcon(mainPage); - } - } - ); - - if (_data->isPackaged) { - AppXReader appxReader; - _isProgramExist = appxReader.Initialize(_data->pathRule); - } else { - _isProgramExist = Win32Utils::FileExists(_data->pathRule.c_str()); - } - - _LoadIcon(mainPage); } ResourceLoader resourceLoader = ResourceLoader::GetForCurrentView(); @@ -117,86 +86,76 @@ ProfileViewModel::ProfileViewModel(int profileIdx) : _isDefaultProfile(profileId _captureMethods = single_threaded_vector(std::move(captureMethods)); } + { + std::vector applications; + applications.reserve(_data->applications.size()); + for (UINT i = 0; i < _data->applications.size(); i++) { + _applications.Append(ProfileApplicationItem(profileIdx, i)); + } + _applications.VectorChanged({ this, &ProfileViewModel::_Applications_VectorChanged }); + } + _graphicsCards = GetAllGraphicsCards(); if (_data->graphicsCard >= _graphicsCards.size()) { _data->graphicsCard = -1; } + + _applicationAddedRevoker = ProfileService::Get().ApplicationAdded( + auto_revoke, { this, &ProfileViewModel::_ProfileService_ApplicationAdded }); + _applicationRemovedRevoker = ProfileService::Get().ApplicationRemoved( + auto_revoke, { this, &ProfileViewModel::_ProfileService_ApplicationRemoved }); + _is3DGameModeChangedRevoker = MagService::Get().Is3DGameModeChanged( + auto_revoke, { this, &ProfileViewModel::_MagService_Is3DGameModeChanged }); } ProfileViewModel::~ProfileViewModel() {} -bool ProfileViewModel::IsNotDefaultProfile() const noexcept { - return !_data->name.empty(); +void ProfileViewModel::_ProfileService_ApplicationAdded(uint32_t profileIdx, uint32_t applicationIdx) { + _isMovingApplication = false; + _applications.Append(ProfileApplicationItem(profileIdx, applicationIdx)); + _isMovingApplication = true; } -fire_and_forget ProfileViewModel::OpenProgramLocation() const noexcept { - if (!_isProgramExist) { - co_return; - } - - std::wstring programLocation; - if (_data->isPackaged) { - AppXReader appxReader; - [[maybe_unused]] bool result = appxReader.Initialize(_data->pathRule); - assert(result); - - programLocation = appxReader.GetExecutablePath(); - if (programLocation.empty()) { - // 找不到可执行文件则打开应用文件夹 - Win32Utils::ShellOpen(appxReader.GetPackagePath().c_str()); - co_return; - } - } else { - programLocation = _data->pathRule; - } - - co_await resume_background(); - Win32Utils::OpenFolderAndSelectFile(programLocation.c_str()); +void ProfileViewModel::_ProfileService_ApplicationRemoved(uint32_t, uint32_t applicationIdx) { + _isMovingApplication = false; + _applications.RemoveAt(applicationIdx); + _isMovingApplication = true; } -hstring ProfileViewModel::Name() const noexcept { - if (_data->name.empty()) { - return ResourceLoader::GetForCurrentView().GetString(L"Main_Defaults/Content"); - } else { - return hstring(_data->name); +void ProfileViewModel::_Applications_VectorChanged(IObservableVector const&, IVectorChangedEventArgs const& args) { + if (!_isMovingApplication) { + return; } -} -static void LaunchPackagedApp(const Profile& profile) { - // 关于启动打包应用的讨论: - // https://github.com/microsoft/WindowsAppSDK/issues/2856#issuecomment-1224409948 - // 使用 CLSCTX_LOCAL_SERVER 以在独立的进程中启动应用 - // 见 https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-iapplicationactivationmanager - com_ptr aam = - try_create_instance(CLSID_ApplicationActivationManager, CLSCTX_LOCAL_SERVER); - if (!aam) { - Logger::Get().Error("创建 ApplicationActivationManager 失败"); + if (args.CollectionChange() == CollectionChange::ItemRemoved) { + _applicationMovingFromIdx = args.Index(); return; } - // 确保启动为前台窗口 - HRESULT hr = CoAllowSetForegroundWindow(aam.get(), nullptr); - if (FAILED(hr)) { - Logger::Get().ComError("创建 CoAllowSetForegroundWindow 失败", hr); - } + assert(args.CollectionChange() == CollectionChange::ItemInserted); + uint32_t movingToIdx = args.Index(); + ProfileService::Get().MoveApplication(_index, _applicationMovingFromIdx, movingToIdx); - DWORD procId; - hr = aam->ActivateApplication(profile.pathRule.c_str(), profile.launchParameters.c_str(), AO_NONE, &procId); - if (FAILED(hr)) { - Logger::Get().ComError("IApplicationActivationManager::ActivateApplication 失败", hr); - return; + uint32_t minIdx = std::min(_applicationMovingFromIdx, movingToIdx); + uint32_t maxIdx = std::max(_applicationMovingFromIdx, movingToIdx); + for (uint32_t i = minIdx; i <= maxIdx; ++i) { + _applications.GetAt(i).as().ApplicationIdx(i); } } -void ProfileViewModel::Launch() const noexcept { - if (!_isProgramExist) { - return; - } +void ProfileViewModel::_MagService_Is3DGameModeChanged(bool) { + _propertyChangedEvent(*this, PropertyChangedEventArgs(L"Is3DGameMode")); +} - if (_data->isPackaged) { - LaunchPackagedApp(*_data); +bool ProfileViewModel::IsNotDefaultProfile() const noexcept { + return !_data->name.empty(); +} + +hstring ProfileViewModel::Name() const noexcept { + if (_data->name.empty()) { + return ResourceLoader::GetForCurrentView().GetString(L"Main_Defaults/Content"); } else { - Win32Utils::ShellOpen(_data->pathRule.c_str(), _data->launchParameters.c_str()); + return hstring(_data->name); } } @@ -652,28 +611,6 @@ void ProfileViewModel::CursorInterpolationMode(int value) { AppSettings::Get().SaveAsync(); } -hstring ProfileViewModel::LaunchParameters() const noexcept { - return hstring(_data->launchParameters); -} - -void ProfileViewModel::LaunchParameters(const hstring& value) { - std::wstring_view trimmed(value); - StrUtils::Trim(trimmed); - _data->launchParameters = trimmed; - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"LaunchParameters")); - - AppSettings::Get().SaveAsync(); -} - -void ProfileViewModel::IsEditingLaunchParameters(bool value) { - if (_isEditingLaunchParameters == value) { - return; - } - - _isEditingLaunchParameters = value; - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsEditingLaunchParameters")); -} - bool ProfileViewModel::IsDisableDirectFlip() const noexcept { return _data->IsDisableDirectFlip(); } @@ -689,63 +626,4 @@ void ProfileViewModel::IsDisableDirectFlip(bool value) { AppSettings::Get().SaveAsync(); } -fire_and_forget ProfileViewModel::_LoadIcon(FrameworkElement const& mainPage) { - std::wstring iconPath; - SoftwareBitmap iconBitmap{ nullptr }; - - if (_isProgramExist) { - auto weakThis = get_weak(); - - const bool preferLightTheme = mainPage.ActualTheme() == ElementTheme::Light; - const bool isPackaged = _data->isPackaged; - const std::wstring path = _data->pathRule; - CoreDispatcher dispatcher = mainPage.Dispatcher(); - const uint32_t dpi = (uint32_t)std::lroundf(_displayInformation.LogicalDpi()); - - co_await resume_background(); - - static constexpr const UINT ICON_SIZE = 32; - if (isPackaged) { - AppXReader appxReader; - [[maybe_unused]] bool result = appxReader.Initialize(path); - assert(result); - - std::variant uwpIcon = - appxReader.GetIcon((uint32_t)std::ceil(dpi * ICON_SIZE / double(USER_DEFAULT_SCREEN_DPI)), preferLightTheme); - if (uwpIcon.index() == 0) { - iconPath = std::get<0>(uwpIcon); - } else { - iconBitmap = std::get<1>(uwpIcon); - } - } else { - iconBitmap = IconHelper::ExtractIconFromExe(path.c_str(), ICON_SIZE, dpi); - } - - co_await dispatcher; - if (!weakThis.get()) { - co_return; - } - } - - if (!iconPath.empty()) { - BitmapIcon icon; - icon.ShowAsMonochrome(false); - icon.UriSource(Uri(iconPath)); - - _icon = std::move(icon); - } else if (iconBitmap) { - SoftwareBitmapSource imageSource; - co_await imageSource.SetBitmapAsync(iconBitmap); - - MUXC::ImageIcon imageIcon; - imageIcon.Source(imageSource); - - _icon = std::move(imageIcon); - } else { - _icon = nullptr; - } - - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"Icon")); -} - } diff --git a/src/Magpie.App/ProfileViewModel.h b/src/Magpie.App/ProfileViewModel.h index f7e5c17ff..191286de4 100644 --- a/src/Magpie.App/ProfileViewModel.h +++ b/src/Magpie.App/ProfileViewModel.h @@ -1,9 +1,11 @@ #pragma once #include "ProfileViewModel.g.h" #include "SmallVector.h" +#include "WinRTUtils.h" namespace winrt::Magpie::App { struct Profile; +struct ProfileApplication; } namespace winrt::Magpie::App::implementation { @@ -20,22 +22,10 @@ struct ProfileViewModel : ProfileViewModelT { _propertyChangedEvent.remove(token); } - Controls::IconElement Icon() const noexcept { - return _icon; - } - bool IsNotDefaultProfile() const noexcept; - bool IsProgramExist() const noexcept { - return _isProgramExist; - } - - fire_and_forget OpenProgramLocation() const noexcept; - hstring Name() const noexcept; - void Launch() const noexcept; - hstring RenameText() const noexcept { return _renameText; } @@ -58,6 +48,10 @@ struct ProfileViewModel : ProfileViewModelT { void Delete(); + IObservableVector Applications() const noexcept { + return _applications; + } + IVector ScalingModes() const noexcept { return _scalingModes; } @@ -137,26 +131,21 @@ struct ProfileViewModel : ProfileViewModelT { int CursorInterpolationMode() const noexcept; void CursorInterpolationMode(int value); - hstring LaunchParameters() const noexcept; - void LaunchParameters(const hstring& value); - - bool IsEditingLaunchParameters() const noexcept { - return _isEditingLaunchParameters; - } - - void IsEditingLaunchParameters(bool value); - bool IsDisableDirectFlip() const noexcept; void IsDisableDirectFlip(bool value); private: - fire_and_forget _LoadIcon(FrameworkElement const& mainPage); + void _ProfileService_ApplicationAdded(uint32_t profileIdx, uint32_t applicationIdx); + void _ProfileService_ApplicationRemoved(uint32_t profileIdx, uint32_t applicationIdx); + + void _Applications_VectorChanged(IObservableVector const&, IVectorChangedEventArgs const& args); - bool _isProgramExist = true; + void _MagService_Is3DGameModeChanged(bool value); hstring _renameText; std::wstring_view _trimedRenameText; + IObservableVector _applications = single_threaded_observable_vector(); IVector _scalingModes{ nullptr }; IVector _captureMethods{ nullptr }; SmallVector _graphicsCards; @@ -170,12 +159,14 @@ struct ProfileViewModel : ProfileViewModelT { MainPage::ActualThemeChanged_revoker _themeChangedRevoker; Windows::Graphics::Display::DisplayInformation _displayInformation{ nullptr }; Windows::Graphics::Display::DisplayInformation::DpiChanged_revoker _dpiChangedRevoker; - - Controls::IconElement _icon{ nullptr }; + WinRTUtils::EventRevoker _applicationAddedRevoker; + WinRTUtils::EventRevoker _applicationRemovedRevoker; + WinRTUtils::EventRevoker _is3DGameModeChangedRevoker; const bool _isDefaultProfile = true; bool _isRenameConfirmButtonEnabled = false; - bool _isEditingLaunchParameters = false; + bool _isMovingApplication = true; + uint32_t _applicationMovingFromIdx = 0; }; } diff --git a/src/Magpie.App/ProfileViewModel.idl b/src/Magpie.App/ProfileViewModel.idl index 5363766de..46a25cd18 100644 --- a/src/Magpie.App/ProfileViewModel.idl +++ b/src/Magpie.App/ProfileViewModel.idl @@ -2,15 +2,9 @@ namespace Magpie.App { runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { ProfileViewModel(Int32 profileIdx); - Windows.UI.Xaml.Controls.IconElement Icon { get; }; String Name { get; }; Boolean IsNotDefaultProfile { get; }; - void Launch(); - - Boolean IsProgramExist { get; }; - void OpenProgramLocation(); - String RenameText; Boolean IsRenameConfirmButtonEnabled { get; }; void Rename(); @@ -21,7 +15,9 @@ namespace Magpie.App { void MoveDown(); void Delete(); - + + IObservableVector Applications { get; }; + IVector ScalingModes { get; }; Int32 ScalingMode; @@ -57,8 +53,6 @@ namespace Magpie.App { Double CustomCursorScaling; Int32 CursorInterpolationMode; - String LaunchParameters; - Boolean IsEditingLaunchParameters; Boolean IsDisableDirectFlip; } } diff --git a/src/Magpie.App/Resources.language-en-US.resw b/src/Magpie.App/Resources.language-en-US.resw index cc38df44b..1201e6ef1 100644 --- a/src/Magpie.App/Resources.language-en-US.resw +++ b/src/Magpie.App/Resources.language-en-US.resw @@ -456,6 +456,24 @@ Set the scaling factor after filling the screen with proportional scaling + + Add application + + + Add application + + + Add + + + Applications + + + Launch + + + Delete + General @@ -483,9 +501,6 @@ Scaling mode - - Launch - More options diff --git a/src/Magpie.App/Resources.language-ru.resw b/src/Magpie.App/Resources.language-ru.resw index 38732c94f..205ccc8a6 100644 --- a/src/Magpie.App/Resources.language-ru.resw +++ b/src/Magpie.App/Resources.language-ru.resw @@ -483,6 +483,24 @@ Выставить множитель увеличения после заполнения экрана пропорциональным увеличением + + Add application + + + Add application + + + Add + + + Applications + + + Запуск + + + Удалить + Общие @@ -510,9 +528,6 @@ Режим увеличения - - Запуск - Больше настроек diff --git a/src/Magpie.App/Resources.language-zh-Hans.resw b/src/Magpie.App/Resources.language-zh-Hans.resw index 17d3bce62..b44c33114 100644 --- a/src/Magpie.App/Resources.language-zh-Hans.resw +++ b/src/Magpie.App/Resources.language-zh-Hans.resw @@ -456,6 +456,24 @@ 指定等比缩放到充满屏幕后的缩放倍数 + + Add application + + + Add application + + + Add + + + Applications + + + 启动 + + + 删除 + 常规 @@ -483,9 +501,6 @@ 缩放模式 - - 启动 - 更多选项 diff --git a/src/Magpie.App/Resources.language-zh-Hant.resw b/src/Magpie.App/Resources.language-zh-Hant.resw index a8c8c1fa9..648a181ad 100644 --- a/src/Magpie.App/Resources.language-zh-Hant.resw +++ b/src/Magpie.App/Resources.language-zh-Hant.resw @@ -733,9 +733,24 @@ 刪除 - + + Add application + + + Add application + + + Add + + + Applications + + 啟動 + + 刪除 + 重新命名 diff --git a/src/Magpie.App/ShortcutHelper.cpp b/src/Magpie.App/ShortcutHelper.cpp index da0f1973f..48333dd2b 100644 --- a/src/Magpie.App/ShortcutHelper.cpp +++ b/src/Magpie.App/ShortcutHelper.cpp @@ -15,6 +15,8 @@ std::string ShortcutHelper::ToString(winrt::Magpie::App::ShortcutAction action) return "Overlay"; case ShortcutAction::COUNT_OR_NONE: return "None"; + case ShortcutAction::Is3DGameMode: + return "3DGameMode"; default: break; } @@ -128,6 +130,8 @@ hstring to_hstring(ShortcutAction action) { return L"Overlay"; case ShortcutAction::COUNT_OR_NONE: return L"None"; + case ShortcutAction::Is3DGameMode: + return L"3DGameMode"; default: break; } diff --git a/src/Magpie.App/ShortcutService.cpp b/src/Magpie.App/ShortcutService.cpp index 0d836daa1..fd63cab52 100644 --- a/src/Magpie.App/ShortcutService.cpp +++ b/src/Magpie.App/ShortcutService.cpp @@ -35,6 +35,7 @@ void ShortcutService::Initialize() { _RegisterShortcut(ShortcutAction::Scale); _RegisterShortcut(ShortcutAction::Overlay); + _RegisterShortcut(ShortcutAction::Is3DGameMode); AppSettings::Get().ShortcutChanged({ this, &ShortcutService::_AppSettings_OnShortcutChanged }); @@ -205,9 +206,9 @@ LRESULT CALLBACK ShortcutService::_LowLevelKeyboardProc(int nCode, WPARAM wParam co_await CoreWindow::GetForCurrentThread().Dispatcher().TryRunAsync( CoreDispatcherPriority::Normal, [action]() { - Logger::Get().Info(fmt::format("热键 {} 激活(Keyboard Hook)", ShortcutHelper::ToString(action))); - Get()._FireShortcut(action); - } + Logger::Get().Info(fmt::format("热键 {} 激活(Keyboard Hook)", ShortcutHelper::ToString(action))); + Get()._FireShortcut(action); + } ); }(action); diff --git a/src/Magpie.Core/MagApp.h b/src/Magpie.Core/MagApp.h index bf334479f..2b8fc4158 100644 --- a/src/Magpie.Core/MagApp.h +++ b/src/Magpie.Core/MagApp.h @@ -61,6 +61,10 @@ class MagApp { return _options; } + void SetOptions(const MagOptions& options) noexcept { + _options = options; + } + winrt::DispatcherQueue Dispatcher() const noexcept { return _dispatcher; } diff --git a/src/Magpie.Core/MagRuntime.cpp b/src/Magpie.Core/MagRuntime.cpp index 7a534d652..66cc45281 100644 --- a/src/Magpie.Core/MagRuntime.cpp +++ b/src/Magpie.Core/MagRuntime.cpp @@ -60,6 +60,12 @@ void MagRuntime::Stop() { }); } +void MagRuntime::Set3DGameMode(bool value) noexcept { + MagOptions& options = MagApp::Get().GetOptions(); + options.Is3DGameMode(value); + MagApp::Get().SetOptions(options); +} + void MagRuntime::_MagWindThreadProc() noexcept { winrt::init_apartment(winrt::apartment_type::single_threaded); diff --git a/src/Magpie.Core/MagRuntime.h b/src/Magpie.Core/MagRuntime.h index b3109acaa..c82717751 100644 --- a/src/Magpie.Core/MagRuntime.h +++ b/src/Magpie.Core/MagRuntime.h @@ -24,6 +24,8 @@ class API_DECLSPEC MagRuntime { void Stop(); + void Set3DGameMode(bool value) noexcept; + bool IsRunning() const { return _running; }