diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a0cea6716..a2fd337b08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,17 +9,19 @@ on: jobs: build: - runs-on: windows-latest + runs-on: windows-2022 steps: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - name: Build - run: dotnet build --no-restore - #- name: Test - # run: dotnet test --no-build --verbosity normal + run: .\make.bat + - uses: actions/upload-artifact@v3 + with: + name: installer + path: build_installer\LenovoLegionToolkitSetup.exe diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 4472f84d21..0000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,70 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '40 10 * * 6' - -jobs: - analyze: - name: Analyze - runs-on: windows-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'csharp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54fde7b9c3..b0066ede54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,20 +8,24 @@ on: jobs: build: - runs-on: windows-latest + runs-on: windows-2022 steps: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 6.0.x - name: Restore dependencies run: dotnet restore - - name: Publish - run: dotnet publish LenovoLegionToolkit.WPF --no-restore -c release -o build /p:DebugType=None /p:FileVersion=${{ github.ref_name }} /p:Version=${{ github.ref_name }} + - name: Build + run: .\make.bat ${{ github.ref_name }} - name: Release uses: softprops/action-gh-release@v1 with: draft: true - files: build\Lenovo Legion Toolkit.exe + files: build_installer\LenovoLegionToolkitSetup.exe + - uses: actions/upload-artifact@v3 + with: + name: installer + path: build_installer\LenovoLegionToolkitSetup.exe diff --git a/.gitignore b/.gitignore index fcf1463262..5c25cce6dc 100644 --- a/.gitignore +++ b/.gitignore @@ -578,3 +578,5 @@ obj/ # Additional files built by Visual Studio # End of https://www.toptal.com/developers/gitignore/api/visualstudio,dotnetcore,csharp + +build_installer diff --git a/CodeDependencies/CodeDependencies.iss b/CodeDependencies/CodeDependencies.iss new file mode 100644 index 0000000000..cf5be59ed3 --- /dev/null +++ b/CodeDependencies/CodeDependencies.iss @@ -0,0 +1,807 @@ +; -- CodeDependencies.iss -- +; +; This script shows how to download and install any dependency such as .NET, +; Visual C++ or SQL Server during your application's installation process. +; +; contribute: https://github.com/DomGries/InnoDependencyInstaller + + +; ----------- +; SHARED CODE +; ----------- +[Code] +// types and variables +type + TDependency_Entry = record + Filename: String; + Parameters: String; + Title: String; + URL: String; + Checksum: String; + ForceSuccess: Boolean; + RestartAfter: Boolean; + end; + +var + Dependency_Memo: String; + Dependency_List: array of TDependency_Entry; + Dependency_NeedRestart, Dependency_ForceX86: Boolean; + Dependency_DownloadPage: TDownloadWizardPage; + +procedure Dependency_Add(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, RestartAfter: Boolean); +var + Dependency: TDependency_Entry; + DependencyCount: Integer; +begin + Dependency_Memo := Dependency_Memo + #13#10 + '%1' + Title; + + Dependency.Filename := Filename; + Dependency.Parameters := Parameters; + Dependency.Title := Title; + + if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin + Dependency.URL := ''; + end else begin + Dependency.URL := URL; + end; + + Dependency.Checksum := Checksum; + Dependency.ForceSuccess := ForceSuccess; + Dependency.RestartAfter := RestartAfter; + + DependencyCount := GetArrayLength(Dependency_List); + SetArrayLength(Dependency_List, DependencyCount + 1); + Dependency_List[DependencyCount] := Dependency; +end; + + +procedure Dependency_Internal1; +begin + Dependency_DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil); +end; + + +function Dependency_Internal2(var NeedsRestart: Boolean): String; +var + DependencyCount, DependencyIndex, ResultCode: Integer; + Retry: Boolean; + TempValue: String; +begin + DependencyCount := GetArrayLength(Dependency_List); + + if DependencyCount > 0 then begin + Dependency_DownloadPage.Show; + + for DependencyIndex := 0 to DependencyCount - 1 do begin + if Dependency_List[DependencyIndex].URL <> '' then begin + Dependency_DownloadPage.Clear; + Dependency_DownloadPage.Add(Dependency_List[DependencyIndex].URL, Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Checksum); + + Retry := True; + while Retry do begin + Retry := False; + + try + Dependency_DownloadPage.Download; + except + if Dependency_DownloadPage.AbortedByUser then begin + Result := Dependency_List[DependencyIndex].Title; + DependencyIndex := DependencyCount; + end else begin + case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of + IDABORT: begin + Result := Dependency_List[DependencyIndex].Title; + DependencyIndex := DependencyCount; + end; + IDRETRY: begin + Retry := True; + end; + end; + end; + end; + end; + end; + end; + + if Result = '' then begin + for DependencyIndex := 0 to DependencyCount - 1 do begin + Dependency_DownloadPage.SetText(Dependency_List[DependencyIndex].Title, ''); + Dependency_DownloadPage.SetProgress(DependencyIndex + 1, DependencyCount + 1); + + while True do begin + ResultCode := 0; + if ShellExec('', ExpandConstant('{tmp}{\}') + Dependency_List[DependencyIndex].Filename, Dependency_List[DependencyIndex].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin + if Dependency_List[DependencyIndex].RestartAfter then begin + if DependencyIndex = DependencyCount - 1 then begin + Dependency_NeedRestart := True; + end else begin + NeedsRestart := True; + Result := Dependency_List[DependencyIndex].Title; + end; + break; + end else if (ResultCode = 0) or Dependency_List[DependencyIndex].ForceSuccess then begin // ERROR_SUCCESS (0) + break; + end else if ResultCode = 1641 then begin // ERROR_SUCCESS_REBOOT_INITIATED (1641) + NeedsRestart := True; + Result := Dependency_List[DependencyIndex].Title; + break; + end else if ResultCode = 3010 then begin // ERROR_SUCCESS_REBOOT_REQUIRED (3010) + Dependency_NeedRestart := True; + break; + end; + end; + + case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependency_List[DependencyIndex].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of + IDABORT: begin + Result := Dependency_List[DependencyIndex].Title; + break; + end; + IDIGNORE: begin + break; + end; + end; + end; + + if Result <> '' then begin + break; + end; + end; + + if NeedsRestart then begin + TempValue := '"' + ExpandConstant('{srcexe}') + '" /restart=1 /LANG="' + ExpandConstant('{language}') + '" /DIR="' + WizardDirValue + '" /GROUP="' + WizardGroupValue + '" /TYPE="' + WizardSetupType(False) + '" /COMPONENTS="' + WizardSelectedComponents(False) + '" /TASKS="' + WizardSelectedTasks(False) + '"'; + if WizardNoIcons then begin + TempValue := TempValue + ' /NOICONS'; + end; + RegWriteStringValue(HKA, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', '{#SetupSetting("AppName")}', TempValue); + end; + end; + + Dependency_DownloadPage.Hide; + end; +end; + + +function Dependency_Internal3(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; +begin + Result := ''; + if MemoUserInfoInfo <> '' then begin + Result := Result + MemoUserInfoInfo + Newline + NewLine; + end; + if MemoDirInfo <> '' then begin + Result := Result + MemoDirInfo + Newline + NewLine; + end; + if MemoTypeInfo <> '' then begin + Result := Result + MemoTypeInfo + Newline + NewLine; + end; + if MemoComponentsInfo <> '' then begin + Result := Result + MemoComponentsInfo + Newline + NewLine; + end; + if MemoGroupInfo <> '' then begin + Result := Result + MemoGroupInfo + Newline + NewLine; + end; + if MemoTasksInfo <> '' then begin + Result := Result + MemoTasksInfo; + end; + + if Dependency_Memo <> '' then begin + if MemoTasksInfo = '' then begin + Result := Result + SetupMessage(msgReadyMemoTasks); + end; + Result := Result + FmtMessage(Dependency_Memo, [Space]); + end; +end; + + +function Dependency_Internal4: Boolean; +begin + Result := Dependency_NeedRestart; +end; + +function Dependency_IsX64: Boolean; +begin + Result := not Dependency_ForceX86 and Is64BitInstallMode; +end; + +function Dependency_String(const x86, x64: String): String; +begin + if Dependency_IsX64 then begin + Result := x64; + end else begin + Result := x86; + end; +end; + +function Dependency_ArchSuffix: String; +begin + Result := Dependency_String('', '_x64'); +end; + +function Dependency_ArchTitle: String; +begin + Result := Dependency_String(' (x86)', ' (x64)'); +end; + +function Dependency_IsNetCoreInstalled(const Version: String): Boolean; +var + ResultCode: Integer; +begin + // source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck + if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe') then begin + ExtractTemporaryFile('netcorecheck' + Dependency_ArchSuffix + '.exe'); + end; + Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0); +end; + +procedure Dependency_AddDotNet35; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net35-sp1 + if not IsDotNetInstalled(net35, 1) then begin + Dependency_Add('dotnetfx35.exe', + '/lang:enu /passive /norestart', + '.NET Framework 3.5 Service Pack 1', + 'https://download.microsoft.com/download/2/0/E/20E90413-712F-438C-988E-FDAA79A8AC3D/dotnetfx35.exe', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet40; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net40 + if not IsDotNetInstalled(net4full, 0) then begin + Dependency_Add('dotNetFx40_Full_setup.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.0', + 'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet45; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net452 + if not IsDotNetInstalled(net452, 0) then begin + Dependency_Add('dotnetfx45.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.5.2', + 'https://go.microsoft.com/fwlink/?LinkId=397707', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet46; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net462 + if not IsDotNetInstalled(net462, 0) then begin + Dependency_Add('dotnetfx46.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.6.2', + 'https://go.microsoft.com/fwlink/?linkid=780596', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet47; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net472 + if not IsDotNetInstalled(net472, 0) then begin + Dependency_Add('dotnetfx47.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.7.2', + 'https://go.microsoft.com/fwlink/?LinkId=863262', + '', False, False); + end; +end; + +procedure Dependency_AddDotNet48; +begin + // https://dotnet.microsoft.com/download/dotnet-framework/net48 + if not IsDotNetInstalled(net48, 0) then begin + Dependency_Add('dotnetfx48.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Framework 4.8', + 'https://go.microsoft.com/fwlink/?LinkId=2085155', + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 3.1.22') then begin + Dependency_Add('netcore31' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Core Runtime 3.1.22' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c2437aed-8cc4-41d0-a239-d6c7cf7bddae/062c37e8b06df740301c0bca1b0b7b9a/dotnet-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/4e95705e-1bb6-4764-b899-1b97eb70ea1d/dd311e073bd3e25b2efe2dcf02727e81/dotnet-runtime-3.1.22-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31Asp; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 3.1.22') then begin + Dependency_Add('netcore31asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 3.1.22' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/0a1a2ee5-b8ed-4f0d-a4af-a7bce9a9ac2b/d452039b49d79e8897f272c3ab34b875/aspnetcore-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/80e52143-31e8-450e-aa94-b3f8484aaba9/4b69e5c77d50e7b367960a0079c90a99/aspnetcore-runtime-3.1.22-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddNetCore31Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet-core/3.1 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 3.1.22') then begin + Dependency_Add('netcore31desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 3.1.22' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/e4fcd574-4487-4b4b-8ca8-c23177c6f59f/c6d67a04956169dc21895cdcb42bf344/windowsdesktop-runtime-3.1.22-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1c14e24b-7f31-42dc-ba3c-83295a2d6f7e/41b93591162dfe556cc160ae44fbe75e/windowsdesktop-runtime-3.1.22-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 5.0.13') then begin + Dependency_Add('dotnet50' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 5.0.13' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/4a79fcd5-d61b-4606-8496-68071c8099c6/2bf770ca40521e8c4563072592eadd06/dotnet-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/fccf43d2-3e62-4ede-b5a5-592a7ccded7b/6339f1fdfe3317df5b09adf65f0261ab/dotnet-runtime-5.0.13-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 5.0.13') then begin + Dependency_Add('dotnet50asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 5.0.13' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/340f9482-fc43-4ef7-b434-e2ed57f55cb3/c641b805cef3823769409a6dbac5746b/aspnetcore-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/aac560f3-eac8-437e-aebd-9830119deb10/6a3880161cf527e4ec71f67efe4d91ad/aspnetcore-runtime-5.0.13-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet50Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/5.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.13') then begin + Dependency_Add('dotnet50desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 5.0.13' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c8125c6b-d399-4be3-b201-8f1394fc3b25/724758f754fc7b67daba74db8d6d91d9/windowsdesktop-runtime-5.0.13-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/2bfb80f2-b8f2-44b0-90c1-d3c8c1c8eac8/409dd3d3367feeeda048f4ff34b32e82/windowsdesktop-runtime-5.0.13-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 6.0.4') then begin + Dependency_Add('dotnet60' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Runtime 6.0.4' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/08e41641-f1b4-47b4-9ed9-c8672614f093/ea66a30f9f8ac7320ea0d7f6e4d5d2d9/dotnet-runtime-6.0.4-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/2e97f1f0-f321-4baf-8d02-0be5f08afc4e/2a011c8f9b2792e17d363a21c0ed8fdc/dotnet-runtime-6.0.4-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60Asp; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.AspNetCore.App 6.0.4') then begin + Dependency_Add('dotnet60asp' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + 'ASP.NET Core Runtime 6.0.4' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/c2093d31-b27e-4876-891c-750247cf1faa/33b9191b128a1d33671549972403994e/aspnetcore-runtime-6.0.4-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/2162932c-987a-4de8-ae2a-f7d327bb39a8/97fe1cb950c2bccf44b7c3fe6aa45b53/aspnetcore-runtime-6.0.4-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDotNet60Desktop; +begin + // https://dotnet.microsoft.com/download/dotnet/6.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 6.0.4') then begin + Dependency_Add('dotnet60desktop' + Dependency_ArchSuffix + '.exe', + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '.NET Desktop Runtime 6.0.4' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/05b30243-5cd2-48c3-a9bb-6ac83d7d481b/03a25aecb5cf4ba53c8b9cf5194e3c86/windowsdesktop-runtime-6.0.4-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/f13d7b5c-608f-432b-b7ec-8fe84f4030a1/5e06998f9ce23c620b9d6bac2dae6c1d/windowsdesktop-runtime-6.0.4-win-x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2005; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26347 + if not IsMsiProductInstalled(Dependency_String('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), PackVersionComponents(8, 0, 61000, 0)) then begin + Dependency_Add('vcredist2005' + Dependency_ArchSuffix + '.exe', + '/q', + 'Visual C++ 2005 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2008; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26368 + if not IsMsiProductInstalled(Dependency_String('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), PackVersionComponents(9, 0, 30729, 6161)) then begin + Dependency_Add('vcredist2008' + Dependency_ArchSuffix + '.exe', + '/q', + 'Visual C++ 2008 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2010; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=26999 + if not IsMsiProductInstalled(Dependency_String('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), PackVersionComponents(10, 0, 40219, 0)) then begin + Dependency_Add('vcredist2010' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2010 Service Pack 1 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2012; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=30679 + if not IsMsiProductInstalled(Dependency_String('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), PackVersionComponents(11, 0, 61030, 0)) then begin + Dependency_Add('vcredist2012' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2012 Update 4 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2013; +begin + // https://support.microsoft.com/en-us/help/4032938 + if not IsMsiProductInstalled(Dependency_String('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), PackVersionComponents(12, 0, 40664, 0)) then begin + Dependency_Add('vcredist2013' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2013 Update 5 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddVC2015To2022; +begin + // https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist + if not IsMsiProductInstalled(Dependency_String('{65E5BD06-6392-3027-8C26-853107D3CF1A}', '{36F68A90-239C-34DF-B58C-64B30153CE35}'), PackVersionComponents(14, 30, 30704, 0)) then begin + Dependency_Add('vcredist2022' + Dependency_ArchSuffix + '.exe', + '/passive /norestart', + 'Visual C++ 2015-2022 Redistributable' + Dependency_ArchTitle, + Dependency_String('https://aka.ms/vs/17/release/vc_redist.x86.exe', 'https://aka.ms/vs/17/release/vc_redist.x64.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddDirectX; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=35 + Dependency_Add('dxwebsetup.exe', + '/q', + 'DirectX Runtime', + 'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe', + '', True, False); +end; + +procedure Dependency_AddSql2008Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=30438 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(10, 50, 4000, 0)) < 0) then begin + Dependency_Add('sql2008express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2008 R2 Service Pack 2 Express', + Dependency_String('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2012Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=56042 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(11, 0, 7001, 0)) < 0) then begin + Dependency_Add('sql2012express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2012 Service Pack 4 Express', + Dependency_String('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2014Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=57473 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(12, 0, 6024, 0)) < 0) then begin + Dependency_Add('sql2014express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2014 Service Pack 3 Express', + Dependency_String('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'), + '', False, False); + end; +end; + +procedure Dependency_AddSql2016Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=56840 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(13, 0, 5026, 0)) < 0) then begin + Dependency_Add('sql2016express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2016 Service Pack 2 Express', + 'https://download.microsoft.com/download/3/7/6/3767D272-76A1-4F31-8849-260BD37924E4/SQLServer2016-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddSql2017Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=55994 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(14, 0, 0, 0)) < 0) then begin + Dependency_Add('sql2017express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2017 Express', + 'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddSql2019Express; +var + Version: String; + PackedVersion: Int64; +begin + // https://www.microsoft.com/en-us/download/details.aspx?id=101064 + if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or not StrToVersion(Version, PackedVersion) or (ComparePackedVersion(PackedVersion, PackVersionComponents(15, 0, 0, 0)) < 0) then begin + Dependency_Add('sql2019express' + Dependency_ArchSuffix + '.exe', + '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER', + 'SQL Server 2019 Express', + 'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe', + '', False, False); + end; +end; + +procedure Dependency_AddWebView2; +begin + if not RegValueExists(HKLM, Dependency_String('SOFTWARE', 'SOFTWARE\WOW6432Node') + '\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv') then begin + Dependency_Add('MicrosoftEdgeWebview2Setup.exe', + '/silent /install', + 'WebView2 Runtime', + 'https://go.microsoft.com/fwlink/p/?LinkId=2124703', + '', False, False); + end; +end; + + +[Setup] +; ------------- +; EXAMPLE SETUP +; ------------- +#ifndef Dependency_NoExampleSetup + +; comment out dependency defines to disable installing them +#define UseDotNet35 +#define UseDotNet40 +#define UseDotNet45 +#define UseDotNet46 +#define UseDotNet47 +#define UseDotNet48 + +; requires netcorecheck.exe and netcorecheck_x64.exe (see download link below) +#define UseNetCoreCheck +#ifdef UseNetCoreCheck + #define UseNetCore31 + #define UseNetCore31Asp + #define UseNetCore31Desktop + #define UseDotNet50 + #define UseDotNet50Asp + #define UseDotNet50Desktop + #define UseDotNet60 + #define UseDotNet60Asp + #define UseDotNet60Desktop +#endif + +#define UseVC2005 +#define UseVC2008 +#define UseVC2010 +#define UseVC2012 +#define UseVC2013 +#define UseVC2015To2022 + +; requires dxwebsetup.exe (see download link below) +;#define UseDirectX + +#define UseSql2008Express +#define UseSql2012Express +#define UseSql2014Express +#define UseSql2016Express +#define UseSql2017Express +#define UseSql2019Express + +#define UseWebView2 + +#define MyAppSetupName 'MyProgram' +#define MyAppVersion '1.0' +#define MyAppPublisher 'Inno Setup' +#define MyAppCopyright 'Copyright Inno Setup' +#define MyAppURL 'https://jrsoftware.org/isinfo.php' + +AppName={#MyAppSetupName} +AppVersion={#MyAppVersion} +AppVerName={#MyAppSetupName} {#MyAppVersion} +AppCopyright={#MyAppCopyright} +VersionInfoVersion={#MyAppVersion} +VersionInfoCompany={#MyAppPublisher} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +OutputBaseFilename={#MyAppSetupName}-{#MyAppVersion} +DefaultGroupName={#MyAppSetupName} +DefaultDirName={autopf}\{#MyAppSetupName} +UninstallDisplayIcon={app}\MyProgram.exe +SourceDir=src +OutputDir={#SourcePath}\bin +AllowNoIcons=yes +PrivilegesRequired=admin + +; remove next line if you only deploy 32-bit binaries and dependencies +ArchitecturesInstallIn64BitMode=x64 + +[Languages] +Name: en; MessagesFile: "compiler:Default.isl" +Name: nl; MessagesFile: "compiler:Languages\Dutch.isl" +Name: de; MessagesFile: "compiler:Languages\German.isl" + +[Files] +#ifdef UseNetCoreCheck +; download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256 +; download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504 +Source: "netcorecheck.exe"; Flags: dontcopy noencryption +Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption +#endif + +#ifdef UseDirectX +Source: "dxwebsetup.exe"; Flags: dontcopy noencryption +#endif + +Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: Dependency_IsX64; Flags: ignoreversion +Source: "MyProg.exe"; DestDir: "{app}"; Check: not Dependency_IsX64; Flags: ignoreversion + +[Icons] +Name: "{group}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe" +Name: "{group}\{cm:UninstallProgram,{#MyAppSetupName}}"; Filename: "{uninstallexe}" +Name: "{commondesktop}\{#MyAppSetupName}"; Filename: "{app}\MyProg.exe"; Tasks: desktopicon + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}" + +[Run] +Filename: "{app}\MyProg.exe"; Description: "{cm:LaunchProgram,{#MyAppSetupName}}"; Flags: nowait postinstall skipifsilent + +[Code] +function InitializeSetup: Boolean; +begin +#ifdef UseDotNet35 + Dependency_AddDotNet35; +#endif +#ifdef UseDotNet40 + Dependency_AddDotNet40; +#endif +#ifdef UseDotNet45 + Dependency_AddDotNet45; +#endif +#ifdef UseDotNet46 + Dependency_AddDotNet46; +#endif +#ifdef UseDotNet47 + Dependency_AddDotNet47; +#endif +#ifdef UseDotNet48 + Dependency_AddDotNet48; +#endif + +#ifdef UseNetCore31 + Dependency_AddNetCore31; +#endif +#ifdef UseNetCore31Asp + Dependency_AddNetCore31Asp; +#endif +#ifdef UseNetCore31Desktop + Dependency_AddNetCore31Desktop; +#endif +#ifdef UseDotNet50 + Dependency_AddDotNet50; +#endif +#ifdef UseDotNet50Asp + Dependency_AddDotNet50Asp; +#endif +#ifdef UseDotNet50Desktop + Dependency_AddDotNet50Desktop; +#endif +#ifdef UseDotNet60 + Dependency_AddDotNet60; +#endif +#ifdef UseDotNet60Asp + Dependency_AddDotNet60Asp; +#endif +#ifdef UseDotNet60Desktop + Dependency_AddDotNet60Desktop; +#endif + +#ifdef UseVC2005 + Dependency_AddVC2005; +#endif +#ifdef UseVC2008 + Dependency_AddVC2008; +#endif +#ifdef UseVC2010 + Dependency_AddVC2010; +#endif +#ifdef UseVC2012 + Dependency_AddVC2012; +#endif +#ifdef UseVC2013 + //Dependency_ForceX86 := True; // force 32-bit install of next dependencies + Dependency_AddVC2013; + //Dependency_ForceX86 := False; // disable forced 32-bit install again +#endif +#ifdef UseVC2015To2022 + Dependency_AddVC2015To2022; +#endif + +#ifdef UseDirectX + ExtractTemporaryFile('dxwebsetup.exe'); + Dependency_AddDirectX; +#endif + +#ifdef UseSql2008Express + Dependency_AddSql2008Express; +#endif +#ifdef UseSql2012Express + Dependency_AddSql2012Express; +#endif +#ifdef UseSql2014Express + Dependency_AddSql2014Express; +#endif +#ifdef UseSql2016Express + Dependency_AddSql2016Express; +#endif +#ifdef UseSql2017Express + Dependency_AddSql2017Express; +#endif +#ifdef UseSql2019Express + Dependency_AddSql2019Express; +#endif + +#ifdef UseWebView2 + Dependency_AddWebView2; +#endif + + Result := True; +end; + +#endif \ No newline at end of file diff --git a/CodeDependencies/netcorecheck_x64.exe b/CodeDependencies/netcorecheck_x64.exe new file mode 100644 index 0000000000..6e7441d5af Binary files /dev/null and b/CodeDependencies/netcorecheck_x64.exe differ diff --git a/LICENSE.md b/LICENSE.md index ddc532f9c2..6f80ee1c68 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Bartosz Cichecki +Copyright (c) 2022 Bartosz Cichecki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LenovoLegionToolkit.Lib/Utils/CPUBoost.cs b/LenovoLegionToolkit.Lib/Controllers/CPUBoostController.cs similarity index 67% rename from LenovoLegionToolkit.Lib/Utils/CPUBoost.cs rename to LenovoLegionToolkit.Lib/Controllers/CPUBoostController.cs index bc67170c80..f5f9e59b15 100644 --- a/LenovoLegionToolkit.Lib/Utils/CPUBoost.cs +++ b/LenovoLegionToolkit.Lib/Controllers/CPUBoostController.cs @@ -3,54 +3,40 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using LenovoLegionToolkit.Lib.Extensions; +using LenovoLegionToolkit.Lib.Utils; -namespace LenovoLegionToolkit.Lib.Utils +namespace LenovoLegionToolkit.Lib.Controllers { - public class CPUBoostSettings - { - public PowerPlan PowerPlan { get; } - public List CPUBoostModes { get; } - public int ACSettingValue { get; } - public int DCSettingValue { get; } - - public CPUBoostSettings(PowerPlan powerPlan, List cpuBoostModes, int acSettingValue, int dcSettingValue) - { - PowerPlan = powerPlan; - CPUBoostModes = cpuBoostModes; - ACSettingValue = acSettingValue; - DCSettingValue = dcSettingValue; - } - } - - public static class CPUBoost + public class CPUBoostModeController { private const string ProcessorPowerManagementSubgroupGUID = "54533251-82be-4824-96c1-47b60b740d00"; private const string PowerSettingGUID = "be337238-0d82-4146-a960-4f3749d470c7"; - private static readonly Regex guidRegex = new(@"(?im)[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?"); - private static readonly Regex nameRegex = new(@"(?im)\((.*)\)"); - private static readonly Regex activeRegex = new(@"(?im)\*$"); + private static readonly Regex _guidRegex = new(@"(?im)[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?"); + private static readonly Regex _nameRegex = new(@"(?im)\((.*)\)"); + private static readonly Regex _activeRegex = new(@"(?im)\*$"); - public static List GetSettings() + public async Task> GetSettingsAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting perfboostmode settings..."); - EnsureAttributeVisible(); + await EnsureAttributeVisibleAsync(); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting power plans..."); - var powerPlans = GetPowerPlans(); + var powerPlans = await GetPowerPlansAsync(); - var result = new List(); + var result = new List(); foreach (var powerPlan in powerPlans) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting perfboostmodes for power plan {powerPlan.Name}... [powerPlan.instanceID={powerPlan.InstanceID}]"); - var settings = GetCPUBoostSettings(powerPlan); + var settings = await GetCPUBoostSettingsAsync(powerPlan); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Perfboostmodes settings retrieved for power plan {settings.PowerPlan.Name} [powerPlan.instanceID={settings.PowerPlan.InstanceID}, {string.Join(",", settings.CPUBoostModes.Select(cbm => $"{{{cbm.Name}:{cbm.Value}}}"))}, acSettingsValue={settings.ACSettingValue}, dcSettingValue={settings.DCSettingValue}]"); @@ -64,32 +50,32 @@ public static List GetSettings() return result; } - public static void SetSetting(PowerPlan powerPlan, CPUBoostMode cpuBoostMode, bool isAC) + public async Task SetSettingAsync(PowerPlan powerPlan, CPUBoostMode cpuBoostMode, bool isAC) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Setting perfboostmode to {cpuBoostMode.Name}... [powerPlan.name={powerPlan.Name}, powerPlan.instanceID={powerPlan.InstanceID}, cpuBoostMode.value={cpuBoostMode.Value}, isAC={isAC}]"); var option = isAC ? "/SETACVALUEINDEX" : "/SETDCVALUEINDEX"; - CMD.Run("powercfg", $"{option} {powerPlan.InstanceID} {ProcessorPowerManagementSubgroupGUID} {PowerSettingGUID} {cpuBoostMode.Value}"); + await CMD.RunAsync("powercfg", $"{option} {powerPlan.InstanceID} {ProcessorPowerManagementSubgroupGUID} {PowerSettingGUID} {cpuBoostMode.Value}"); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Perfboostmode set to {cpuBoostMode.Name} [powerPlan.name={powerPlan.Name}, powerPlan.instanceID={powerPlan.InstanceID}, cpuBoostMode.value={cpuBoostMode.Value}, isAC={isAC}]"); } - private static void EnsureAttributeVisible() + private async Task EnsureAttributeVisibleAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Ensuring perfboostmode is visible..."); - CMD.Run("powercfg", "/ATTRIBUTES sub_processor perfboostmode -ATTRIB_HIDE"); + await CMD.RunAsync("powercfg", "/ATTRIBUTES sub_processor perfboostmode -ATTRIB_HIDE"); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Perfboostmode is visible."); } - private static List GetPowerPlans() + private async Task> GetPowerPlansAsync() { - var output = CMD.Run("powercfg", "/LIST"); + var output = await CMD.RunAsync("powercfg", "/LIST"); var outputLines = output .Split(Environment.NewLine) .Where(s => s.StartsWith("Power Scheme GUID", StringComparison.InvariantCultureIgnoreCase)); @@ -98,9 +84,9 @@ private static List GetPowerPlans() foreach (var line in outputLines) { - var guid = guidRegex.Match(line).Groups[0].Value; - var name = nameRegex.Match(line).Groups[1].Value; - var active = activeRegex.Match(line).Success; + var guid = _guidRegex.Match(line).Groups[0].Value; + var name = _nameRegex.Match(line).Groups[1].Value; + var active = _activeRegex.Match(line).Success; if (string.IsNullOrWhiteSpace(guid) || string.IsNullOrWhiteSpace(name)) { continue; } @@ -110,9 +96,9 @@ private static List GetPowerPlans() return result.OrderBy(pp => pp.Name).ToList(); } - private static CPUBoostSettings GetCPUBoostSettings(PowerPlan powerPlan) + private async Task GetCPUBoostSettingsAsync(PowerPlan powerPlan) { - var output = CMD.Run("powercfg", $"/QUERY {powerPlan.InstanceID} {ProcessorPowerManagementSubgroupGUID} {PowerSettingGUID}"); + var output = await CMD.RunAsync("powercfg", $"/QUERY {powerPlan.InstanceID} {ProcessorPowerManagementSubgroupGUID} {PowerSettingGUID}"); var outputLines = output .Split(Environment.NewLine) .Select(s => s.Trim()); @@ -136,7 +122,7 @@ private static CPUBoostSettings GetCPUBoostSettings(PowerPlan powerPlan) var acSettingValue = int.Parse(acSettingValueString, NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier); var dcSettingValue = int.Parse(dcSettingValueString, NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier); - return new CPUBoostSettings(powerPlan, cpuBoostModes, acSettingValue, dcSettingValue); + return new CPUBoostModeSettings(powerPlan, cpuBoostModes, acSettingValue, dcSettingValue); } } } diff --git a/LenovoLegionToolkit.Lib/Utils/GPUManager.cs b/LenovoLegionToolkit.Lib/Controllers/GPUController.cs similarity index 70% rename from LenovoLegionToolkit.Lib/Utils/GPUManager.cs rename to LenovoLegionToolkit.Lib/Controllers/GPUController.cs index e98e031216..5f453ce1a1 100644 --- a/LenovoLegionToolkit.Lib/Utils/GPUManager.cs +++ b/LenovoLegionToolkit.Lib/Controllers/GPUController.cs @@ -1,11 +1,14 @@ using System; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using LenovoLegionToolkit.Lib.Utils; +using NeoSmart.AsyncLock; -namespace LenovoLegionToolkit.Lib.Utils +namespace LenovoLegionToolkit.Lib.Controllers { - public class GPUManager + public class GPUController { public enum Status { @@ -33,24 +36,37 @@ public RefreshedEventArgs(bool isActive, bool canBeDeactivated, Status status, s } } - private readonly object _lock = new(); + private readonly AsyncLock _lock = new(); - private Task _refreshTask = null; - private CancellationTokenSource _refreshCancellationTokenSource = null; + private Task? _refreshTask = null; + private CancellationTokenSource? _refreshCancellationTokenSource = null; private Status _status = Status.Unknown; private string[] _processNames = Array.Empty(); - private string _gpuInstanceId = null; + private string? _gpuInstanceId = null; private bool IsActive => _status == Status.MonitorsConnected || _status == Status.DeactivatePossible; private bool CanBeDeactivated => _status == Status.DeactivatePossible; - public event EventHandler WillRefresh; - public event EventHandler Refreshed; + public event EventHandler? WillRefresh; + public event EventHandler? Refreshed; - public void Start(int delay = 2_500, int interval = 5_000) + public bool IsSupported() { - Stop(true); + try + { + NVAPI.Initialize(); + return NVAPI.GetGPU() != null; + } + finally + { + NVAPI.Unload(); + } + } + + public async Task StartAsync(int delay = 1_000, int interval = 5_000) + { + await StopAsync(true).ConfigureAwait(false); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Starting... [delay={delay}, interval={interval}]"); @@ -70,20 +86,20 @@ public void Start(int delay = 2_500, int interval = 5_000) if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Initialized NVAPI"); - await Task.Delay(delay, token); + await Task.Delay(delay, token).ConfigureAwait(false); while (true) { token.ThrowIfCancellationRequested(); - lock (_lock) + using (await _lock.LockAsync().ConfigureAwait(false)) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Will refresh..."); WillRefresh?.Invoke(this, EventArgs.Empty); - Refresh(); + await RefreshAsync().ConfigureAwait(false); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Refreshed"); @@ -91,13 +107,13 @@ public void Start(int delay = 2_500, int interval = 5_000) Refreshed?.Invoke(this, new RefreshedEventArgs(IsActive, CanBeDeactivated, _status, _processNames)); } - await Task.Delay(interval, token); + await Task.Delay(interval, token).ConfigureAwait(false); } } catch (Exception ex) when (ex is not TaskCanceledException) { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Exception: {ex}"); + Log.Instance.Trace($"Exception: {ex.Demystify()}"); throw; } @@ -114,7 +130,7 @@ public void Start(int delay = 2_500, int interval = 5_000) }, token); } - public void Stop(bool waitForFinish = false) + public async Task StopAsync(bool waitForFinish = false) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Stopping... [refreshTask.isNull={_refreshTask == null}, _refreshCancellationTokenSource.IsCancellationRequested={_refreshCancellationTokenSource?.IsCancellationRequested}]"); @@ -126,7 +142,8 @@ public void Stop(bool waitForFinish = false) if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Waiting to finish..."); - _refreshTask?.Wait(); + if (_refreshTask != null) + await _refreshTask.ConfigureAwait(false); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Finished"); @@ -139,24 +156,23 @@ public void Stop(bool waitForFinish = false) Log.Instance.Trace($"Stopped"); } - public void DeactivateGPU() + public async Task DeactivateGPUAsync() { - lock (_lock) - { + using (await _lock.LockAsync().ConfigureAwait(false)) + if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Deactivating... [isActive={IsActive}, canBeDeactivated={CanBeDeactivated}, gpuInstanceId={_gpuInstanceId}]"); - if (!IsActive || !CanBeDeactivated || string.IsNullOrEmpty(_gpuInstanceId)) - return; + if (!IsActive || !CanBeDeactivated || string.IsNullOrEmpty(_gpuInstanceId)) + return; - CMD.Run("pnputil", $"/restart-device \"{_gpuInstanceId}\""); + await CMD.RunAsync("pnputil", $"/restart-device \"{_gpuInstanceId}\"").ConfigureAwait(false); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Deactivated [isActive={IsActive}, canBeDeactivated={CanBeDeactivated}, gpuInstanceId={_gpuInstanceId}]"); - } + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Deactivated [isActive={IsActive}, canBeDeactivated={CanBeDeactivated}, gpuInstanceId={_gpuInstanceId}]"); } - private void Refresh() + private async Task RefreshAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Refresh in progress..."); @@ -165,7 +181,8 @@ private void Refresh() _processNames = Array.Empty(); _gpuInstanceId = null; - if (!NVAPI.IsGPUPresent(out var gpu)) + var gpu = NVAPI.GetGPU(); + if (gpu == null) { _status = Status.NVIDIAGPUNotFound; @@ -199,7 +216,11 @@ private void Refresh() } var pnpDeviceId = NVAPI.GetGPUId(gpu); - var gpuInstanceId = GetDeviceInstanceID(pnpDeviceId); + + if (string.IsNullOrEmpty(pnpDeviceId)) + throw new InvalidOperationException("pnpDeviceId is null or empty"); + + var gpuInstanceId = await GetDeviceInstanceIDAsync(pnpDeviceId).ConfigureAwait(false); _gpuInstanceId = gpuInstanceId; _status = Status.DeactivatePossible; @@ -208,11 +229,12 @@ private void Refresh() Log.Instance.Trace($"Deactivate possible [status={_status}, processNames.Length={_processNames.Length}, gpuInstanceId={_gpuInstanceId}, pnpDeviceId={pnpDeviceId}]"); } - private static string GetDeviceInstanceID(string pnpDeviceId) + private static async Task GetDeviceInstanceIDAsync(string pnpDeviceId) { - return WMI.Read("root\\CIMV2", + var results = await WMI.ReadAsync("root\\CIMV2", $"SELECT * FROM Win32_PnpEntity WHERE DeviceID LIKE '{pnpDeviceId}%'", - pdc => (string)pdc["DeviceID"].Value).FirstOrDefault(); + pdc => (string)pdc["DeviceID"].Value).ConfigureAwait(false); + return results?.FirstOrDefault(); } } } diff --git a/LenovoLegionToolkit.Lib/Enums.cs b/LenovoLegionToolkit.Lib/Enums.cs index 9210b6c781..12e8008cf8 100644 --- a/LenovoLegionToolkit.Lib/Enums.cs +++ b/LenovoLegionToolkit.Lib/Enums.cs @@ -1,16 +1,22 @@ -namespace LenovoLegionToolkit.Lib +using System.ComponentModel.DataAnnotations; + +namespace LenovoLegionToolkit.Lib { - public enum AlwaysOnUsbState + public enum AlwaysOnUSBState { Off, + [Display(Name = "On, when sleeping")] OnWhenSleeping, - OnAlways + [Display(Name = "On, always")] + OnAlways, } + public enum BatteryState { Conservation, Normal, + [Display(Name = "Rapid Charge")] RapidCharge } @@ -45,6 +51,23 @@ public enum PowerModeState Performance } + public enum SpecialKey + { + Unknown = 0, + Fn_F9 = 1, + Fn_LockOn = 2, + Fn_LockOff = 3, + Fn_PrtSc = 4, + Fn_R = 16 + } + + public enum Theme + { + System, + Light, + Dark + } + public enum TouchpadLockState { Off, @@ -57,14 +80,4 @@ public enum VantageStatus Disabled, NotFound } - - public enum Key - { - Unknown = 0, - Fn_F9 = 1, - Fn_LockOn = 2, - Fn_LockOff = 3, - Fn_PrtSc = 4, - Fn_R = 16 - } } diff --git a/LenovoLegionToolkit.Lib/Extensions/EnumExtensions.cs b/LenovoLegionToolkit.Lib/Extensions/EnumExtensions.cs new file mode 100644 index 0000000000..60c14eb558 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Extensions/EnumExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace LenovoLegionToolkit.Lib.Extensions +{ + public static class EnumExtensions + { + public static string GetDisplayName(this Enum enumValue) + { + var displayAttribute = enumValue.GetType() + .GetMember(enumValue.ToString()) + .First() + .GetCustomAttributes(false) + .OfType() + .FirstOrDefault(); + return displayAttribute?.Name ?? enumValue.ToString(); + } + } +} diff --git a/LenovoLegionToolkit.Lib/Extensions/HttpClientExtensions.cs b/LenovoLegionToolkit.Lib/Extensions/HttpClientExtensions.cs new file mode 100644 index 0000000000..c153a0a941 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Extensions/HttpClientExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace LenovoLegionToolkit.Lib.Extensions +{ + public static class HttpClientExtensions + { + public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress? progress = null, CancellationToken cancellationToken = default) + { + using var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + var contentLength = response.Content.Headers.ContentLength; + + using var download = await response.Content.ReadAsStreamAsync(cancellationToken); + + if (progress == null || !contentLength.HasValue) + { + await download.CopyToAsync(destination, cancellationToken); + return; + } + + progress.Report(0); + var relativeProgress = new Progress(totalBytes => progress.Report((float)totalBytes / contentLength.Value)); + await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); + progress.Report(1); + } + } +} diff --git a/LenovoLegionToolkit.Lib/Extensions/StreamExtensions.cs b/LenovoLegionToolkit.Lib/Extensions/StreamExtensions.cs new file mode 100644 index 0000000000..5ab5f57697 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Extensions/StreamExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace LenovoLegionToolkit.Lib.Extensions +{ + public static class StreamExtensions + { + public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress? progress = null, CancellationToken cancellationToken = default) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new ArgumentException("Has to be readable", nameof(source)); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new ArgumentException("Has to be writable", nameof(destination)); + if (bufferSize < 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + + while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); + } + } + } +} diff --git a/LenovoLegionToolkit.Lib/Features/AbstractDriverFeature.cs b/LenovoLegionToolkit.Lib/Features/AbstractDriverFeature.cs index 77307f0605..01cedabb21 100644 --- a/LenovoLegionToolkit.Lib/Features/AbstractDriverFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/AbstractDriverFeature.cs @@ -1,30 +1,32 @@ using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; using LenovoLegionToolkit.Lib.Utils; using Microsoft.Win32.SafeHandles; namespace LenovoLegionToolkit.Lib.Features { - public abstract class AbstractDriverFeature : IFeature where T : struct, IComparable + public abstract class AbstractDriverFeature : IFeature where T : struct, Enum, IComparable { private readonly uint _controlCode; - private readonly SafeFileHandle _driverHandle; + private readonly Func _driverHandle; protected T LastState; - protected AbstractDriverFeature(SafeFileHandle driverHandleHandle, uint controlCode) + protected AbstractDriverFeature(Func driverHandleHandle, uint controlCode) { _driverHandle = driverHandleHandle; _controlCode = controlCode; } + public Task GetAllStatesAsync() => Task.FromResult(Enum.GetValues()); - public T GetState() + public async Task GetStateAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting state... [feature={GetType().Name}]"); - SendCode(_driverHandle, _controlCode, GetInternalStatus(), out var result); - var state = FromInternal(result); + var (_, outBuffer) = await SendCodeAsync(_driverHandle(), _controlCode, GetInternalStatus()).ConfigureAwait(false); + var state = FromInternal(outBuffer); LastState = state; if (Log.Instance.IsTraceEnabled) @@ -33,14 +35,14 @@ public T GetState() return state; } - public void SetState(T state) + public async Task SetStateAsync(T state) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Setting state to {state}... [feature={GetType().Name}]"); var codes = ToInternal(state); foreach (var code in codes) - SendCode(_driverHandle, _controlCode, code, out _); + await SendCodeAsync(_driverHandle(), _controlCode, code).ConfigureAwait(false); LastState = state; if (Log.Instance.IsTraceEnabled) @@ -51,19 +53,22 @@ public void SetState(T state) protected abstract byte GetInternalStatus(); protected abstract byte[] ToInternal(T state); - private int SendCode(SafeFileHandle handle, uint controlCode, byte inBuffer, out uint outBuffer) + private Task<(int bytesReturned, uint outBuffer)> SendCodeAsync(SafeFileHandle handle, uint controlCode, byte inBuffer) { - if (!Native.DeviceIoControl(handle, controlCode, ref inBuffer, sizeof(byte), out outBuffer, sizeof(uint), out var bytesReturned, IntPtr.Zero)) + return Task.Run(() => { - var error = Marshal.GetLastWin32Error(); + if (!Native.DeviceIoControl(handle, controlCode, ref inBuffer, sizeof(byte), out uint outBuffer, sizeof(uint), out var bytesReturned, IntPtr.Zero)) + { + var error = Marshal.GetLastWin32Error(); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"DeviceIoControl returned 0, last error: {error} [feature={GetType().Name}]"); + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"DeviceIoControl returned 0, last error: {error} [feature={GetType().Name}]"); - throw new InvalidOperationException($"DeviceIoControl returned 0, last error: {error}"); - } + throw new InvalidOperationException($"DeviceIoControl returned 0, last error: {error}"); + } - return bytesReturned; + return (bytesReturned, outBuffer); + }); } protected static uint ReverseEndianness(uint state) diff --git a/LenovoLegionToolkit.Lib/Features/AbstractUEFIFeature.cs b/LenovoLegionToolkit.Lib/Features/AbstractUEFIFeature.cs index 7380408c11..d0fd53d4c4 100644 --- a/LenovoLegionToolkit.Lib/Features/AbstractUEFIFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/AbstractUEFIFeature.cs @@ -1,10 +1,12 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading.Tasks; using LenovoLegionToolkit.Lib.Utils; namespace LenovoLegionToolkit.Lib.Features { - public abstract class AbstractUEFIFeature : IFeature + public abstract class AbstractUEFIFeature : IFeature where T : struct, Enum, IComparable { private readonly string _guid; private readonly string _scopeName; @@ -17,103 +19,113 @@ protected AbstractUEFIFeature(string guid, string scopeName, int scopeAttribute) _scopeAttribute = scopeAttribute; } - public abstract T GetState(); - public abstract void SetState(T state); + public Task GetAllStatesAsync() => Task.FromResult(Enum.GetValues()); - protected S ReadFromUefi(S structure) where S : struct - { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Reading from UEFI... [feature={GetType().Name}]"); + public abstract Task GetStateAsync(); - if (!IsUefiMode()) - { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"UEFI mode is not enabled. [feature={GetType().Name}]"); + public abstract Task SetStateAsync(T state); - throw new InvalidOperationException("UEFI mode is not enabled"); - } + protected Task ReadFromUefiAsync(S structure) where S : struct + { + return Task.Run(() => + { - var hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf()); + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Reading from UEFI... [feature={GetType().Name}]"); - try - { - if (!SetPrivilege(true)) + if (!IsUefiMode()) { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Cannot set UEFI privilages [feature={GetType().Name}]"); + Log.Instance.Trace($"UEFI mode is not enabled. [feature={GetType().Name}]"); - throw new InvalidOperationException($"Cannot set privilages UEFI"); + throw new InvalidOperationException("UEFI mode is not enabled"); } - var ptr = hGlobal; + var hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf()); - Marshal.StructureToPtr(structure, ptr, false); - if (Native.GetFirmwareEnvironmentVariableExW(_scopeName, _guid, hGlobal, Marshal.SizeOf(), IntPtr.Zero) != 0) + try { - var result = (S)Marshal.PtrToStructure(hGlobal, typeof(S)); + if (!SetPrivilege(true)) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Cannot set UEFI privilages [feature={GetType().Name}]"); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Read from UEFI successful [feature={GetType().Name}]"); + throw new InvalidOperationException($"Cannot set privilages UEFI"); + } + + var ptr = hGlobal; + + Marshal.StructureToPtr(structure, ptr, false); + if (Native.GetFirmwareEnvironmentVariableExW(_scopeName, _guid, hGlobal, Marshal.SizeOf(), IntPtr.Zero) != 0) + { + var result = Marshal.PtrToStructure(hGlobal); - return result; + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Read from UEFI successful [feature={GetType().Name}]"); + + return result; + } + else + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Cannot read variable {_scopeName} from UEFI [feature={GetType().Name}]"); + + throw new InvalidOperationException($"Cannot read variable {_scopeName} from UEFI"); + } } - else + finally { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Cannot read variable {_scopeName} from UEFI [feature={GetType().Name}]"); - - throw new InvalidOperationException($"Cannot read variable {_scopeName} from UEFI"); + Marshal.FreeHGlobal(hGlobal); + SetPrivilege(false); } - } - finally - { - Marshal.FreeHGlobal(hGlobal); - SetPrivilege(false); - } + }); } - protected void WriteToUefi(S structure) where S : struct + protected Task WriteToUefiAsync(S structure) where S : struct { - if (!IsUefiMode()) + return Task.Run(() => { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"UEFI mode is not enabled. [feature={GetType().Name}]"); - - throw new InvalidOperationException("UEFI mode is not enabled."); - } - - var hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf()); - - try - { - if (!SetPrivilege(true)) + if (!IsUefiMode()) { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Cannot set UEFI privilages [feature={GetType().Name}]"); + Log.Instance.Trace($"UEFI mode is not enabled. [feature={GetType().Name}]"); - throw new InvalidOperationException($"Cannot set privilages UEFI"); + throw new InvalidOperationException("UEFI mode is not enabled"); } - var ptr = hGlobal; - Marshal.StructureToPtr(structure, ptr, false); - if (Native.SetFirmwareEnvironmentVariableExW(_scopeName, _guid, hGlobal, Marshal.SizeOf(), _scopeAttribute) != 1) - { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Cannot write variable {_scopeName} to UEFI [feature={GetType().Name}]"); + var hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf()); - throw new InvalidOperationException($"Cannot write variable {_scopeName} to UEFI"); + try + { + if (!SetPrivilege(true)) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Cannot set UEFI privilages [feature={GetType().Name}]"); + + throw new InvalidOperationException($"Cannot set privilages UEFI"); + } + + var ptr = hGlobal; + Marshal.StructureToPtr(structure, ptr, false); + if (Native.SetFirmwareEnvironmentVariableExW(_scopeName, _guid, hGlobal, Marshal.SizeOf(), _scopeAttribute) != 1) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Cannot write variable {_scopeName} to UEFI [feature={GetType().Name}]"); + + throw new InvalidOperationException($"Cannot write variable {_scopeName} to UEFI"); + } + else + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Write to UEFI successful [feature={GetType().Name}]"); + } } - else + finally { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Write to UEFI successful [feature={GetType().Name}]"); + Marshal.FreeHGlobal(hGlobal); + SetPrivilege(false); } - } - finally - { - Marshal.FreeHGlobal(hGlobal); - SetPrivilege(false); - } + }); } private bool IsUefiMode() @@ -173,7 +185,7 @@ private bool SetPrivilege(bool enable) catch (Exception ex) { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Exception: {ex} [feature={GetType().Name}]"); + Log.Instance.Trace($"Exception: {ex.Demystify()} [feature={GetType().Name}]"); return false; } diff --git a/LenovoLegionToolkit.Lib/Features/AbstractWmiFeature.cs b/LenovoLegionToolkit.Lib/Features/AbstractWmiFeature.cs index 9ff88fc3e4..2b93c9e2a6 100644 --- a/LenovoLegionToolkit.Lib/Features/AbstractWmiFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/AbstractWmiFeature.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; using System.Management; +using System.Threading.Tasks; using LenovoLegionToolkit.Lib.Utils; namespace LenovoLegionToolkit.Lib.Features { - public abstract class AbstractWmiFeature : IFeature where T : struct, IComparable + public abstract class AbstractWmiFeature : IFeature where T : struct, Enum, IComparable { private readonly string _methodNameSuffix; private readonly int _offset; - private readonly string _supportMethodName; + private readonly string? _supportMethodName; private readonly int _supportOffset; - protected AbstractWmiFeature(string methodNameSuffix, int offset, string supportMethodName = null, int supportOffset = 0) + protected AbstractWmiFeature(string methodNameSuffix, int offset, string? supportMethodName = null, int supportOffset = 0) { _methodNameSuffix = methodNameSuffix; _offset = offset; @@ -20,12 +21,14 @@ protected AbstractWmiFeature(string methodNameSuffix, int offset, string support _supportOffset = supportOffset; } - public virtual T GetState() + public Task GetAllStatesAsync() => Task.FromResult(Enum.GetValues()); + + public virtual async Task GetStateAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting state... [feature={GetType().Name}]"); - if (!IsSupported()) + if (!await IsSupportedAsync().ConfigureAwait(false)) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Feature {_methodNameSuffix} is not supported [feature={GetType().Name}]"); @@ -33,7 +36,7 @@ public virtual T GetState() throw new NotSupportedException($"Feature {_methodNameSuffix} is not supported."); } - var result = FromInternal(ExecuteGamezone("Get" + _methodNameSuffix, "Data")); + var result = FromInternal(await ExecuteGamezoneAsync("Get" + _methodNameSuffix, "Data").ConfigureAwait(false)); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"State is {result} [feature={GetType().Name}]"); @@ -41,12 +44,12 @@ public virtual T GetState() return result; } - public virtual void SetState(T state) + public virtual Task SetStateAsync(T state) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Setting state to {state}... [feature={GetType().Name}]"); - ExecuteGamezone("Set" + _methodNameSuffix, "Data", + ExecuteGamezoneAsync("Set" + _methodNameSuffix, "Data", new Dictionary { {"Data", ToInternal(state).ToString()} @@ -54,14 +57,16 @@ public virtual void SetState(T state) if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Set state to {state} [feature={GetType().Name}]"); + + return Task.CompletedTask; } - private bool IsSupported() + private async Task IsSupportedAsync() { if (_supportMethodName == null) return true; - var value = ExecuteGamezone(_supportMethodName, "Data"); + var value = await ExecuteGamezoneAsync(_supportMethodName, "Data").ConfigureAwait(false); return value > _supportOffset; } @@ -69,41 +74,44 @@ private bool IsSupported() private T FromInternal(int state) => (T)(object)(state - _offset); - private int ExecuteGamezone(string methodName, string resultPropertyName, Dictionary methodParams = null) + private Task ExecuteGamezoneAsync(string methodName, string resultPropertyName, Dictionary? methodParams = null) { - return Execute("SELECT * FROM LENOVO_GAMEZONE_DATA", methodName, resultPropertyName, methodParams); + return ExecuteAsync("SELECT * FROM LENOVO_GAMEZONE_DATA", methodName, resultPropertyName, methodParams); } - private int Execute(string queryString, + private Task ExecuteAsync(string queryString, string methodName, string resultPropertyName, - Dictionary methodParams = null) + Dictionary? methodParams = null) { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Executing WMI query... [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); - - using var enumerator = new ManagementObjectSearcher("ROOT\\WMI", queryString).Get().GetEnumerator(); - if (!enumerator.MoveNext()) + return Task.Run(() => { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"No results in query [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); + Log.Instance.Trace($"Executing WMI query... [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); - throw new InvalidOperationException("No results in query"); - } + using var enumerator = new ManagementObjectSearcher("ROOT\\WMI", queryString).Get().GetEnumerator(); + if (!enumerator.MoveNext()) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"No results in query [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); - var mo = (ManagementObject)enumerator.Current; - var methodParamsObject = mo.GetMethodParameters(methodName); - if (methodParams != null) - foreach (var pair in methodParams) - methodParamsObject[pair.Key] = pair.Value; + throw new InvalidOperationException("No results in query"); + } - var result = mo.InvokeMethod(methodName, methodParamsObject, null)?.Properties[resultPropertyName].Value; - var intResult = Convert.ToInt32(result); + var mo = (ManagementObject)enumerator.Current; + var methodParamsObject = mo.GetMethodParameters(methodName); + if (methodParams != null) + foreach (var pair in methodParams) + methodParamsObject[pair.Key] = pair.Value; - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Executed WMI query with result {intResult} [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); + var result = mo.InvokeMethod(methodName, methodParamsObject, null)?.Properties[resultPropertyName].Value; + var intResult = Convert.ToInt32(result); + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Executed WMI query with result {intResult} [feature={GetType().Name}, queryString={queryString}, methodName={methodName}, resultPropertyName={resultPropertyName}, methodParams.Count={methodParams?.Count}]"); - return intResult; + return intResult; + }); } } } \ No newline at end of file diff --git a/LenovoLegionToolkit.Lib/Features/AlwaysOnUsbFeature.cs b/LenovoLegionToolkit.Lib/Features/AlwaysOnUsbFeature.cs index 60ebd59978..8b4fda132e 100644 --- a/LenovoLegionToolkit.Lib/Features/AlwaysOnUsbFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/AlwaysOnUsbFeature.cs @@ -3,34 +3,34 @@ namespace LenovoLegionToolkit.Lib.Features { - public class AlwaysOnUsbFeature : AbstractDriverFeature + public class AlwaysOnUSBFeature : AbstractDriverFeature { - public AlwaysOnUsbFeature() : base(Drivers.Energy, 0x831020E8) { } + public AlwaysOnUSBFeature() : base(Drivers.GetEnergy, 0x831020E8) { } protected override byte GetInternalStatus() => 0x2; - protected override byte[] ToInternal(AlwaysOnUsbState state) + protected override byte[] ToInternal(AlwaysOnUSBState state) { return state switch { - AlwaysOnUsbState.Off => new byte[] { 0xB, 0x12 }, - AlwaysOnUsbState.OnWhenSleeping => new byte[] { 0xA, 0x12 }, - AlwaysOnUsbState.OnAlways => new byte[] { 0xA, 0x13 }, + AlwaysOnUSBState.Off => new byte[] { 0xB, 0x12 }, + AlwaysOnUSBState.OnWhenSleeping => new byte[] { 0xA, 0x12 }, + AlwaysOnUSBState.OnAlways => new byte[] { 0xA, 0x13 }, _ => throw new InvalidOperationException("Invalid state"), }; } - protected override AlwaysOnUsbState FromInternal(uint state) + protected override AlwaysOnUSBState FromInternal(uint state) { state = ReverseEndianness(state); if (GetNthBit(state, 31)) // is on? { if (GetNthBit(state, 23)) - return AlwaysOnUsbState.OnAlways; - return AlwaysOnUsbState.OnWhenSleeping; + return AlwaysOnUSBState.OnAlways; + return AlwaysOnUSBState.OnWhenSleeping; } - return AlwaysOnUsbState.Off; + return AlwaysOnUSBState.Off; } } } \ No newline at end of file diff --git a/LenovoLegionToolkit.Lib/Features/BatteryFeature.cs b/LenovoLegionToolkit.Lib/Features/BatteryFeature.cs index 8a9b0e57fc..dbb3748ee8 100644 --- a/LenovoLegionToolkit.Lib/Features/BatteryFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/BatteryFeature.cs @@ -5,7 +5,7 @@ namespace LenovoLegionToolkit.Lib.Features { public class BatteryFeature : AbstractDriverFeature { - public BatteryFeature() : base(Drivers.Energy, 0x831020F8) { } + public BatteryFeature() : base(Drivers.GetEnergy, 0x831020F8) { } protected override byte GetInternalStatus() => 0xFF; @@ -49,7 +49,7 @@ protected override BatteryState FromInternal(uint state) if (GetNthBit(state, 29)) return BatteryState.Conservation; - throw new InvalidOperationException($"Unknown battery state: {state}."); + throw new InvalidOperationException($"Unknown battery state: {state}"); } } } \ No newline at end of file diff --git a/LenovoLegionToolkit.Lib/Features/FlipToStartFeature.cs b/LenovoLegionToolkit.Lib/Features/FlipToStartFeature.cs index f8ba071fe3..e3578a1ffd 100644 --- a/LenovoLegionToolkit.Lib/Features/FlipToStartFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/FlipToStartFeature.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace LenovoLegionToolkit.Lib.Features { @@ -19,20 +20,20 @@ public struct FlipToBootStruct public FlipToStartFeature() : base("{D743491E-F484-4952-A87D-8D5DD189B70C}", "FBSWIF", 7) { } - public override FlipToStartState GetState() + public override async Task GetStateAsync() { - var result = ReadFromUefi(new FlipToBootStruct + var result = await ReadFromUefiAsync(new FlipToBootStruct { FlipToBootEn = 0, Reserved1 = 0, Reserved2 = 0, Reserved3 = 0 - }); + }).ConfigureAwait(false); return result.FlipToBootEn == 0 ? FlipToStartState.Off : FlipToStartState.On; } - public override void SetState(FlipToStartState state) + public override async Task SetStateAsync(FlipToStartState state) { var structure = new FlipToBootStruct { @@ -41,7 +42,7 @@ public override void SetState(FlipToStartState state) Reserved2 = 0, Reserved3 = 0 }; - WriteToUefi(structure); + await WriteToUefiAsync(structure).ConfigureAwait(false); } } } diff --git a/LenovoLegionToolkit.Lib/Features/FnLockFeature.cs b/LenovoLegionToolkit.Lib/Features/FnLockFeature.cs index 225cd5a828..f019d9d856 100644 --- a/LenovoLegionToolkit.Lib/Features/FnLockFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/FnLockFeature.cs @@ -5,7 +5,7 @@ namespace LenovoLegionToolkit.Lib.Features { public class FnLockFeature : AbstractDriverFeature { - public FnLockFeature() : base(Drivers.Energy, 0x831020E8) { } + public FnLockFeature() : base(Drivers.GetEnergy, 0x831020E8) { } protected override byte GetInternalStatus() => 0x2; diff --git a/LenovoLegionToolkit.Lib/Features/IDynamicFeature.cs b/LenovoLegionToolkit.Lib/Features/IDynamicFeature.cs deleted file mode 100644 index feb223ced1..0000000000 --- a/LenovoLegionToolkit.Lib/Features/IDynamicFeature.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LenovoLegionToolkit.Lib.Features -{ - public interface IDynamicFeature : IFeature - { - public T[] GetAllStates(); - } -} diff --git a/LenovoLegionToolkit.Lib/Features/IFeature.cs b/LenovoLegionToolkit.Lib/Features/IFeature.cs index e17d104544..8084417f8b 100644 --- a/LenovoLegionToolkit.Lib/Features/IFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/IFeature.cs @@ -1,8 +1,11 @@ -namespace LenovoLegionToolkit.Lib.Features +using System.Threading.Tasks; + +namespace LenovoLegionToolkit.Lib.Features { - public interface IFeature + public interface IFeature where T : struct { - T GetState(); - void SetState(T state); + public Task GetAllStatesAsync(); + Task GetStateAsync(); + Task SetStateAsync(T state); } -} \ No newline at end of file +} diff --git a/LenovoLegionToolkit.Lib/Features/PowerModeFeature.cs b/LenovoLegionToolkit.Lib/Features/PowerModeFeature.cs index 85e79a3158..77b7ad04c3 100644 --- a/LenovoLegionToolkit.Lib/Features/PowerModeFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/PowerModeFeature.cs @@ -1,4 +1,5 @@ -using LenovoLegionToolkit.Lib.Utils; +using System.Threading.Tasks; +using LenovoLegionToolkit.Lib.Utils; namespace LenovoLegionToolkit.Lib.Features { @@ -6,16 +7,16 @@ public class PowerModeFeature : AbstractWmiFeature { public PowerModeFeature() : base("SmartFanMode", 1, "IsSupportSmartFan") { } - public override void SetState(PowerModeState state) + public override async Task SetStateAsync(PowerModeState state) { - base.SetState(state); - Power.ActivatePowerPlan(state, true); + await base.SetStateAsync(state).ConfigureAwait(false); + await Power.ActivatePowerPlanAsync(state, true).ConfigureAwait(false); } - public void EnsureCorrectPowerPlanIsSet() + public async Task EnsureCorrectPowerPlanIsSetAsync() { - var state = GetState(); - Power.ActivatePowerPlan(state, true); + var state = await GetStateAsync().ConfigureAwait(false); + await Power.ActivatePowerPlanAsync(state, true).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/LenovoLegionToolkit.Lib/Features/RefreshRateFeature.cs b/LenovoLegionToolkit.Lib/Features/RefreshRateFeature.cs index fccd54462b..2267e4c0cf 100644 --- a/LenovoLegionToolkit.Lib/Features/RefreshRateFeature.cs +++ b/LenovoLegionToolkit.Lib/Features/RefreshRateFeature.cs @@ -1,24 +1,25 @@ using System; using System.Linq; +using System.Threading.Tasks; using LenovoLegionToolkit.Lib.Utils; using WindowsDisplayAPI; namespace LenovoLegionToolkit.Lib.Features { - public class RefreshRateFeature : IDynamicFeature + public class RefreshRateFeature : IFeature { - public RefreshRate[] GetAllStates() + public async Task GetAllStatesAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting all refresh rates..."); - var display = GetBuiltInDisplay(); + var display = await GetBuiltInDisplayAsync().ConfigureAwait(false); if (display == null) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Built in display not found"); - throw new InvalidOperationException("Built in display not found"); + return Array.Empty(); } if (Log.Instance.IsTraceEnabled) @@ -45,24 +46,32 @@ public RefreshRate[] GetAllStates() .Select(freq => new RefreshRate(freq)) .ToArray(); + if (result.Length == 1) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Single display mode found"); + + return Array.Empty(); + } + if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Possible refresh rates are {string.Join(", ", result)}"); return result; } - public RefreshRate GetState() + public async Task GetStateAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Getting current refresh rate..."); - var display = GetBuiltInDisplay(); + var display = await GetBuiltInDisplayAsync().ConfigureAwait(false); if (display == null) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Built in display not found"); - throw new InvalidOperationException("Built in display not found"); + return default; } var currentSettings = display.CurrentSetting; @@ -74,9 +83,9 @@ public RefreshRate GetState() return result; } - public void SetState(RefreshRate state) + public async Task SetStateAsync(RefreshRate state) { - var display = GetBuiltInDisplay(); + var display = await GetBuiltInDisplayAsync().ConfigureAwait(false); if (display == null) { if (Log.Instance.IsTraceEnabled) @@ -113,7 +122,7 @@ public void SetState(RefreshRate state) } } - private static Display GetBuiltInDisplay() + private static async Task GetBuiltInDisplayAsync() { var displays = Display.GetDisplays(); @@ -124,10 +133,13 @@ private static Display GetBuiltInDisplay() Log.Instance.Trace($" - {display}"); } - return displays.Where(IsInternal).FirstOrDefault(); + foreach (var display in Display.GetDisplays()) + if (await IsInternalAsync(display)) + return display; + return null; } - private static bool IsInternal(Display display) + private static async Task IsInternalAsync(Display display) { var instanceName = display.DevicePath .Split("#") @@ -135,9 +147,10 @@ private static bool IsInternal(Display display) .Take(2) .Aggregate((s1, s2) => s1 + "\\" + s2); - var vot = WMI.Read("root\\WMI", - $"SELECT * FROM WmiMonitorConnectionParams WHERE InstanceName LIKE '%{instanceName}%'", - pdc => (uint)pdc["VideoOutputTechnology"].Value).FirstOrDefault(); + var result = await WMI.ReadAsync("root\\WMI", + $"SELECT * FROM WmiMonitorConnectionParams WHERE InstanceName LIKE '%{instanceName}%'", + pdc => (uint)pdc["VideoOutputTechnology"].Value).ConfigureAwait(false); + var vot = result.FirstOrDefault(); const uint votInternal = 0x80000000; const uint votDisplayPortEmbedded = 11; diff --git a/LenovoLegionToolkit.Lib/Folders.cs b/LenovoLegionToolkit.Lib/Folders.cs new file mode 100644 index 0000000000..a529baf68c --- /dev/null +++ b/LenovoLegionToolkit.Lib/Folders.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace LenovoLegionToolkit.Lib.Utils +{ + public static class Folders + { + public static string AppData + { + get + { + var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var folderPath = Path.Combine(appData, "LenovoLegionToolkit"); + Directory.CreateDirectory(folderPath); + return folderPath; + } + } + } +} diff --git a/LenovoLegionToolkit.Lib/GlobalSuppressions.cs b/LenovoLegionToolkit.Lib/GlobalSuppressions.cs new file mode 100644 index 0000000000..85a31fe48c --- /dev/null +++ b/LenovoLegionToolkit.Lib/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static")] diff --git a/LenovoLegionToolkit.Lib/Interfaces.cs b/LenovoLegionToolkit.Lib/Interfaces.cs new file mode 100644 index 0000000000..1e6be885b8 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Interfaces.cs @@ -0,0 +1,7 @@ +namespace LenovoLegionToolkit.Lib +{ + public interface IDisplayName + { + string DisplayName { get; } + } +} diff --git a/LenovoLegionToolkit.Lib/LenovoLegionToolkit.Lib.csproj b/LenovoLegionToolkit.Lib/LenovoLegionToolkit.Lib.csproj index 88235f070e..defdff33d9 100644 --- a/LenovoLegionToolkit.Lib/LenovoLegionToolkit.Lib.csproj +++ b/LenovoLegionToolkit.Lib/LenovoLegionToolkit.Lib.csproj @@ -1,14 +1,20 @@  - net5.0-windows + net6.0-windows win-x64 + enable + - - - - + + + + + + + + diff --git a/LenovoLegionToolkit.Lib/Listeners/AbstractWMIListener.cs b/LenovoLegionToolkit.Lib/Listeners/AbstractWMIListener.cs index b16bdf15bd..ad2da38881 100644 --- a/LenovoLegionToolkit.Lib/Listeners/AbstractWMIListener.cs +++ b/LenovoLegionToolkit.Lib/Listeners/AbstractWMIListener.cs @@ -4,15 +4,16 @@ namespace LenovoLegionToolkit.Lib.Listeners { - public abstract class AbstractWMIListener where T : struct, IComparable + public abstract class AbstractWMIListener : IListener where T : struct, Enum, IComparable { private readonly string _eventName; private readonly string _property; private readonly int _offset; - private IDisposable _disposable; + private IDisposable? _disposable; + + public event EventHandler? Changed; - public event EventHandler Changed; public AbstractWMIListener(string eventName, string property, int offset) { _eventName = eventName; diff --git a/LenovoLegionToolkit.Lib/Listeners/DisplayConfigurationListener.cs b/LenovoLegionToolkit.Lib/Listeners/DisplayConfigurationListener.cs new file mode 100644 index 0000000000..e108d6e463 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Listeners/DisplayConfigurationListener.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Win32; + +namespace LenovoLegionToolkit.Lib.Listeners +{ + public class DisplayConfigurationListener : IListener + { + public event EventHandler? Changed; + + public void Start() + { + Stop(); + + SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; + } + + public void Stop() => SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged; + + private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) => Changed?.Invoke(this, EventArgs.Empty); + } +} diff --git a/LenovoLegionToolkit.Lib/Listeners/IListener.cs b/LenovoLegionToolkit.Lib/Listeners/IListener.cs new file mode 100644 index 0000000000..a5118e95c1 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Listeners/IListener.cs @@ -0,0 +1,13 @@ +using System; + +namespace LenovoLegionToolkit.Lib.Listeners +{ + public interface IListener + { + event EventHandler? Changed; + + void Start(); + + void Stop(); + } +} \ No newline at end of file diff --git a/LenovoLegionToolkit.Lib/Listeners/KeyListener.cs b/LenovoLegionToolkit.Lib/Listeners/KeyListener.cs deleted file mode 100644 index f2d1f4d05b..0000000000 --- a/LenovoLegionToolkit.Lib/Listeners/KeyListener.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LenovoLegionToolkit.Lib.Listeners -{ - public class KeyListener : AbstractWMIListener - { - public KeyListener() : base("LENOVO_UTILITY_EVENT", "PressTypeDataVal", 0) { } - - protected override void OnChanged(Key value) - { - } - } -} diff --git a/LenovoLegionToolkit.Lib/Listeners/PowerAdapterListener.cs b/LenovoLegionToolkit.Lib/Listeners/PowerAdapterListener.cs new file mode 100644 index 0000000000..4ac1c357c9 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Listeners/PowerAdapterListener.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.Win32; + +namespace LenovoLegionToolkit.Lib.Listeners +{ + public class PowerAdapterListener : IListener + { + public event EventHandler? Changed; + + public void Start() + { + Stop(); + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + } + + public void Stop() => SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged; + + private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) => Changed?.Invoke(this, EventArgs.Empty); + } +} diff --git a/LenovoLegionToolkit.Lib/Listeners/PowerModeListener.cs b/LenovoLegionToolkit.Lib/Listeners/PowerModeListener.cs index f84c058e20..409f3a5aa6 100644 --- a/LenovoLegionToolkit.Lib/Listeners/PowerModeListener.cs +++ b/LenovoLegionToolkit.Lib/Listeners/PowerModeListener.cs @@ -6,9 +6,9 @@ public class PowerModeListener : AbstractWMIListener { public PowerModeListener() : base("LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT", "mode", 1) { } - protected override void OnChanged(PowerModeState value) + protected override async void OnChanged(PowerModeState value) { - Power.ActivatePowerPlan(value); + await Power.ActivatePowerPlanAsync(value).ConfigureAwait(false); } } } diff --git a/LenovoLegionToolkit.Lib/Listeners/SpecialKeyListener.cs b/LenovoLegionToolkit.Lib/Listeners/SpecialKeyListener.cs new file mode 100644 index 0000000000..204b9a653e --- /dev/null +++ b/LenovoLegionToolkit.Lib/Listeners/SpecialKeyListener.cs @@ -0,0 +1,11 @@ +namespace LenovoLegionToolkit.Lib.Listeners +{ + public class SpecialKeyListener : AbstractWMIListener + { + public SpecialKeyListener() : base("LENOVO_UTILITY_EVENT", "PressTypeDataVal", 0) { } + + protected override void OnChanged(SpecialKey value) + { + } + } +} diff --git a/LenovoLegionToolkit.Lib/Structs.cs b/LenovoLegionToolkit.Lib/Structs.cs index baf260bc72..4761486f0f 100644 --- a/LenovoLegionToolkit.Lib/Structs.cs +++ b/LenovoLegionToolkit.Lib/Structs.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Octokit; namespace LenovoLegionToolkit.Lib { @@ -13,23 +16,87 @@ public CPUBoostMode(int value, string name) Name = name; } } - public struct RefreshRate + + public struct CPUBoostModeSettings + { + public PowerPlan PowerPlan { get; } + public List CPUBoostModes { get; } + public int ACSettingValue { get; } + public int DCSettingValue { get; } + + public CPUBoostModeSettings(PowerPlan powerPlan, List cpuBoostModes, int acSettingValue, int dcSettingValue) + { + PowerPlan = powerPlan; + CPUBoostModes = cpuBoostModes; + ACSettingValue = acSettingValue; + DCSettingValue = dcSettingValue; + } + } + public struct MachineInformation + { + public string Vendor { get; } + public string Model { get; } + + public MachineInformation(string vendor, string model) + { + Vendor = vendor; + Model = model; + } + } + + public struct PowerPlan { - public static bool operator ==(RefreshRate left, RefreshRate right) => left.Equals(right); + public string InstanceID { get; } + public string Name { get; } + public bool IsActive { get; } + public string Guid => InstanceID.Split("\\").Last().Replace("{", "").Replace("}", ""); + + public PowerPlan(string instanceID, string name, bool isActive) + { + InstanceID = instanceID; + Name = name; + IsActive = isActive; + } - public static bool operator !=(RefreshRate left, RefreshRate right) => !(left == right); + public override string ToString() => Name; + } + public struct RefreshRate : IDisplayName + { public int Frequency { get; } + public string DisplayName => $"{Frequency} Hz"; + public RefreshRate(int frequency) { Frequency = frequency; } + } + public struct Update + { + public Version Version { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string? Url { get; set; } - public override string ToString() => $"{Frequency} Hz"; + public Update(Release release) + { + Version = Version.Parse(release.TagName); + Title = release.Name; + Description = release.Body; + Url = release.Assets.Where(ra => ra.Name.EndsWith("setup.exe", StringComparison.InvariantCultureIgnoreCase)).Select(ra => ra.BrowserDownloadUrl).FirstOrDefault(); + } + } - public override int GetHashCode() => HashCode.Combine(Frequency); + public struct WindowSize + { + public double Width { get; set; } + public double Height { get; set; } - public override bool Equals(object obj) => obj is RefreshRate rate && Frequency == rate.Frequency; + public WindowSize(double width, double height) + { + Width = width; + Height = height; + } } } diff --git a/LenovoLegionToolkit.Lib/Utils/Autorun.cs b/LenovoLegionToolkit.Lib/Utils/Autorun.cs index 1681396c5e..2dfcf83f7e 100644 --- a/LenovoLegionToolkit.Lib/Utils/Autorun.cs +++ b/LenovoLegionToolkit.Lib/Utils/Autorun.cs @@ -11,18 +11,73 @@ public static class Autorun public static bool IsEnabled => TaskService.Instance.GetTask(TaskName) != null; + public static void Validate() + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Validating autorun..."); + + var currentTask = TaskService.Instance.GetTask(TaskName); + if (currentTask == null) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Autorun is not enabled."); + return; + } + + var mainModule = Process.GetCurrentProcess().MainModule; + if (mainModule == null) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Main module is null."); + return; + } + + var fileVersion = mainModule.FileVersionInfo.FileVersion; + if (fileVersion == null) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"File version is null."); + return; + } + + if (currentTask.Definition.Data == fileVersion) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Autorun settings seems to be fine."); + return; + } + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Enabling autorun again..."); + + Enable(); + } + public static void Enable() { Disable(); + var mainModule = Process.GetCurrentProcess().MainModule; + if (mainModule == null) + throw new InvalidOperationException("Main Module cannot be null"); + + var filename = mainModule.FileName; + if (filename == null) + throw new InvalidOperationException("Current process file name cannot be null"); + + var fileVersion = mainModule.FileVersionInfo.FileVersion; + if (fileVersion == null) + throw new InvalidOperationException("Current process file version cannot be null"); + var currentUser = WindowsIdentity.GetCurrent().Name; var ts = TaskService.Instance; var td = ts.NewTask(); + td.Data = fileVersion; td.Principal.UserId = currentUser; td.Principal.RunLevel = TaskRunLevel.Highest; td.Triggers.Add(new LogonTrigger { UserId = currentUser, Delay = new TimeSpan(0, 1, 0) }); - td.Actions.Add($"\"{Process.GetCurrentProcess().MainModule.FileName}\"", "--minimized"); + td.Actions.Add($"\"{filename}\"", "--minimized"); td.Settings.DisallowStartIfOnBatteries = false; td.Settings.StopIfGoingOnBatteries = false; ts.RootFolder.RegisterTaskDefinition(TaskName, td); diff --git a/LenovoLegionToolkit.Lib/Utils/CMD.cs b/LenovoLegionToolkit.Lib/Utils/CMD.cs index 8b72c9abd2..cd23a3ec60 100644 --- a/LenovoLegionToolkit.Lib/Utils/CMD.cs +++ b/LenovoLegionToolkit.Lib/Utils/CMD.cs @@ -1,10 +1,11 @@ using System.Diagnostics; +using System.Threading.Tasks; namespace LenovoLegionToolkit.Lib.Utils { internal static class CMD { - public static string Run(string file, string arguments) + public static async Task RunAsync(string file, string arguments) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Running... [file={file}, argument={arguments}]"); @@ -17,8 +18,8 @@ public static string Run(string file, string arguments) cmd.StartInfo.FileName = file; cmd.StartInfo.Arguments = arguments; cmd.Start(); - var output = cmd.StandardOutput.ReadToEnd(); - cmd.WaitForExit(); + var output = await cmd.StandardOutput.ReadToEndAsync().ConfigureAwait(false); + await cmd.WaitForExitAsync().ConfigureAwait(false); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Ran [file={file}, argument={arguments}, output={output}]"); diff --git a/LenovoLegionToolkit.Lib/Utils/Compatibility.cs b/LenovoLegionToolkit.Lib/Utils/Compatibility.cs index 4146adc740..9562bb56ae 100644 --- a/LenovoLegionToolkit.Lib/Utils/Compatibility.cs +++ b/LenovoLegionToolkit.Lib/Utils/Compatibility.cs @@ -1,21 +1,10 @@ using System; using System.Linq; using System.Management; +using System.Threading.Tasks; namespace LenovoLegionToolkit.Lib.Utils { - public class MachineInformation - { - public string Vendor { get; } - public string Model { get; } - - public MachineInformation(string vendor, string model) - { - Vendor = vendor; - Model = model; - } - } - public static class Compatibility { private static readonly string _allowedVendor = "LENOVO"; @@ -46,20 +35,21 @@ public static class Compatibility "17IRH", // Legion Y540 - Intel, nVidia }; - public static bool IsCompatible(out MachineInformation machineInformation) + public static async Task<(bool isCompatible, MachineInformation machineInformation)> IsCompatibleAsync() { - machineInformation = WMI.Read("root\\CIMV2", - $"SELECT * FROM Win32_ComputerSystemProduct", - Create).First(); + var result = await WMI.ReadAsync("root\\CIMV2", + $"SELECT * FROM Win32_ComputerSystemProduct", + Create).ConfigureAwait(false); + var machineInformation = result.First(); - if (!machineInformation.Vendor.Equals(_allowedVendor, StringComparison.OrdinalIgnoreCase)) - return false; + if (!machineInformation.Vendor.Equals(_allowedVendor, StringComparison.InvariantCultureIgnoreCase)) + return (false, machineInformation); foreach (var allowedModel in _allowedModels) - if (machineInformation.Model.Contains(allowedModel, StringComparison.OrdinalIgnoreCase)) - return true; + if (machineInformation.Model.Contains(allowedModel, StringComparison.InvariantCultureIgnoreCase)) + return (true, machineInformation); - return false; + return (false, machineInformation); } private static MachineInformation Create(PropertyDataCollection properties) diff --git a/LenovoLegionToolkit.Lib/Utils/Drivers.cs b/LenovoLegionToolkit.Lib/Utils/Drivers.cs index 65823ab012..04717ceb4c 100644 --- a/LenovoLegionToolkit.Lib/Utils/Drivers.cs +++ b/LenovoLegionToolkit.Lib/Utils/Drivers.cs @@ -5,20 +5,26 @@ namespace LenovoLegionToolkit.Lib.Utils { internal static class Drivers { - private static SafeFileHandle _energy; - public static SafeFileHandle Energy + private static readonly object _locker = new(); + + private static SafeFileHandle? _energy; + + public static SafeFileHandle GetEnergy() { - get + if (_energy == null) { - if (_energy == null) + lock (_locker) { - var fileHandle = Native.CreateFileW("\\\\.\\EnergyDrv", 0xC0000000, 3u, IntPtr.Zero, 3u, 0x80, IntPtr.Zero); - if (fileHandle == new IntPtr(-1)) - throw new InvalidOperationException("fileHandle is 0"); - _energy = new SafeFileHandle(fileHandle, true); + if (_energy == null) + { + var fileHandle = Native.CreateFileW("\\\\.\\EnergyDrv", 0xC0000000, 3u, IntPtr.Zero, 3u, 0x80, IntPtr.Zero); + if (fileHandle == new IntPtr(-1)) + throw new InvalidOperationException("fileHandle is 0"); + _energy = new SafeFileHandle(fileHandle, true); + } } - return _energy; } + return _energy; } } } diff --git a/LenovoLegionToolkit.Lib/Utils/Log.cs b/LenovoLegionToolkit.Lib/Utils/Log.cs index 2f518c42f3..da95161aa1 100644 --- a/LenovoLegionToolkit.Lib/Utils/Log.cs +++ b/LenovoLegionToolkit.Lib/Utils/Log.cs @@ -6,7 +6,7 @@ namespace LenovoLegionToolkit.Lib.Utils { public class Log { - private static Log _instance; + private static Log? _instance; public static Log Instance { get @@ -20,6 +20,7 @@ public static Log Instance private readonly object _lock = new(); private readonly string _folderPath; private readonly string _logPath; + public bool IsTraceEnabled { get; set; } = false; public string LogPath => _logPath; @@ -33,9 +34,9 @@ public Log() } public void Trace(FormattableString message, - [CallerFilePath] string file = null, + [CallerFilePath] string? file = null, [CallerLineNumber] int lineNumber = -1, - [CallerMemberName] string caller = null) + [CallerMemberName] string? caller = null) { if (!IsTraceEnabled) return; @@ -44,7 +45,7 @@ public void Trace(FormattableString message, { var date = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); var fileName = Path.GetFileName(file); - var line = $"[{date}] [{fileName}#{lineNumber}:{caller}] {message}"; + var line = $"[{date}] [{Environment.CurrentManagedThreadId}] [{fileName}#{lineNumber}:{caller}] {message}"; File.AppendAllLines(_logPath, new[] { line }); } } diff --git a/LenovoLegionToolkit.Lib/Utils/NVAPI.cs b/LenovoLegionToolkit.Lib/Utils/NVAPI.cs index 5122864df8..5f97a791e7 100644 --- a/LenovoLegionToolkit.Lib/Utils/NVAPI.cs +++ b/LenovoLegionToolkit.Lib/Utils/NVAPI.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using NvAPIWrapper; using NvAPIWrapper.Display; @@ -16,17 +15,15 @@ internal static class NVAPI public static void Unload() => NVIDIA.Unload(); - public static bool IsGPUPresent(out PhysicalGPU gpu) + public static PhysicalGPU? GetGPU() { try { - gpu = PhysicalGPU.GetPhysicalGPUs().FirstOrDefault(gpu => gpu.SystemType == SystemType.Laptop); - return gpu != null; + return PhysicalGPU.GetPhysicalGPUs().FirstOrDefault(gpu => gpu.SystemType == SystemType.Laptop); } catch (NVIDIAApiException) { - gpu = null; - return false; + return null; } } @@ -34,8 +31,6 @@ public static bool IsGPUActive(PhysicalGPU gpu) { try { - if (gpu == null) - return false; _ = gpu.PerformanceStatesInfo; return true; } @@ -49,9 +44,6 @@ public static bool IsDisplayConnected(PhysicalGPU gpu) { try { - if (gpu == null) - return false; - return Display.GetDisplays().Any(d => d.PhysicalGPUs.Contains(gpu, PhysicalGPUEqualityComparer.Instance)); } catch (NVIDIAApiException) @@ -60,12 +52,10 @@ public static bool IsDisplayConnected(PhysicalGPU gpu) } } - public static string GetGPUId(PhysicalGPU gpu) + public static string? GetGPUId(PhysicalGPU gpu) { try { - if (gpu == null) - return null; return gpu.BusInformation.PCIIdentifiers.ToString(); } catch (NVIDIAApiException) @@ -78,9 +68,6 @@ public static string[] GetActiveApps(PhysicalGPU gpu) { try { - if (gpu == null) - return Array.Empty(); - return gpu.GetActiveApplications().Select(p => p.ProcessName).ToArray(); } catch (NVIDIAApiException) @@ -95,9 +82,9 @@ private class PhysicalGPUEqualityComparer : IEqualityComparer private PhysicalGPUEqualityComparer() { } - public bool Equals(PhysicalGPU x, PhysicalGPU y) => x.GPUId == y.GPUId; + public bool Equals(PhysicalGPU? x, PhysicalGPU? y) => x?.GPUId == y?.GPUId; - public int GetHashCode([DisallowNull] PhysicalGPU obj) => obj.GPUId.GetHashCode(); + public int GetHashCode(PhysicalGPU obj) => obj.GPUId.GetHashCode(); } } } diff --git a/LenovoLegionToolkit.Lib/Utils/Native.cs b/LenovoLegionToolkit.Lib/Utils/Native.cs index e431284c2a..3c001a0ac2 100644 --- a/LenovoLegionToolkit.Lib/Utils/Native.cs +++ b/LenovoLegionToolkit.Lib/Utils/Native.cs @@ -22,8 +22,39 @@ internal struct TokenPrivelege public int Attr; } + internal enum ACLineStatus : byte + { + Offline = 0, + Online = 1, + Unknown = 255 + } + + internal enum BatteryFlag : byte + { + High = 1, + Low = 2, + Critical = 4, + Charging = 8, + NoSystemBattery = 128, + Unknown = 255 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SystemPowerStatus + { + public ACLineStatus ACLineStatus; + public BatteryFlag BatteryFlag; + public byte BatteryLifePercent; + public byte Reserved1; + public int BatteryLifeTime; + public int BatteryFullLifeTime; + } + internal static class Native { + [DllImport("Kernel32")] + public static extern bool GetSystemPowerStatus(out SystemPowerStatus sps); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr CreateFileW( [MarshalAs(UnmanagedType.LPWStr)] string filename, @@ -69,7 +100,7 @@ public static extern int SetFirmwareEnvironmentVariableExW( [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] public static extern bool LookupPrivilegeValue( - string lpSystemName, + string? lpSystemName, string lpName, ref long lpLuid); @@ -96,19 +127,19 @@ public static extern bool ChangeServiceConfig( uint nServiceType, uint nStartType, uint nErrorControl, - string lpBinaryPathName, - String lpLoadOrderGroup, + string? lpBinaryPathName, + string? lpLoadOrderGroup, IntPtr lpdwTagId, - [In] char[] lpDependencies, - string lpServiceStartName, - string lpPassword, - string lpDisplayName); + [In] char[]? lpDependencies, + string? lpServiceStartName, + string? lpPassword, + string? lpDisplayName); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); + public static extern IntPtr OpenSCManager(string? machineName, string? databaseName, uint dwAccess); [DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")] public static extern int CloseServiceHandle(IntPtr hSCObject); diff --git a/LenovoLegionToolkit.Lib/Utils/Power.cs b/LenovoLegionToolkit.Lib/Utils/Power.cs index 89ac681259..b6c908d779 100644 --- a/LenovoLegionToolkit.Lib/Utils/Power.cs +++ b/LenovoLegionToolkit.Lib/Utils/Power.cs @@ -2,42 +2,35 @@ using System.Collections.Generic; using System.Linq; using System.Management; +using System.Threading.Tasks; namespace LenovoLegionToolkit.Lib.Utils { - public class PowerPlan + public static class Power { - public string InstanceID { get; } - public string Name { get; } - public bool IsActive { get; } - public string Guid => InstanceID.Split("\\").Last().Replace("{", "").Replace("}", ""); - - public PowerPlan(string instanceID, string name, bool isActive) + public static bool IsPowerAdapterConnected() { - InstanceID = instanceID; - Name = name; - IsActive = isActive; + Native.GetSystemPowerStatus(out SystemPowerStatus sps); + return sps.ACLineStatus == ACLineStatus.Online; } - } - public static class Power - { - public static void Restart() + public static async Task RestartAsync() { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Restarting..."); - CMD.Run("shutdown", "/r /t 0"); + await CMD.RunAsync("shutdown", "/r /t 0").ConfigureAwait(false); } - public static PowerPlan[] GetPowerPlans() + public static async Task GetPowerPlansAsync() { - return WMI.Read("root\\CIMV2\\power", - $"SELECT * FROM Win32_PowerPlan", - Create).ToArray(); + var result = await WMI.ReadAsync("root\\CIMV2\\power", + $"SELECT * FROM Win32_PowerPlan", + Create).ConfigureAwait(false); + return result.ToArray(); } - public static void ActivatePowerPlan(PowerModeState powerModeState, bool alwaysActivateDefaults = false) + public static async Task ActivatePowerPlanAsync(PowerModeState powerModeState, bool alwaysActivateDefaults = false) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Activating... [powerModeState={powerModeState}, alwaysActivateDefaults={alwaysActivateDefaults}]"); @@ -57,7 +50,7 @@ public static void ActivatePowerPlan(PowerModeState powerModeState, bool alwaysA if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Power plan to be activated is {powerPlanId} [isDefault={isDefault}]"); - if (!ShouldActivate(alwaysActivateDefaults, isDefault)) + if (!await ShouldActivateAsync(alwaysActivateDefaults, isDefault).ConfigureAwait(false)) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Power plan {powerPlanId} will not be activated [isDefault={isDefault}]"); @@ -65,8 +58,8 @@ public static void ActivatePowerPlan(PowerModeState powerModeState, bool alwaysA return; } - var powerPlan = GetPowerPlans().FirstOrDefault(pp => pp.InstanceID.Contains(powerPlanId)); - if (powerPlan == null) + var powerPlan = (await GetPowerPlansAsync()).FirstOrDefault(pp => pp.InstanceID.Contains(powerPlanId)); + if (powerPlan.Equals(default(PowerPlan))) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Power plan {powerPlanId} was not found"); @@ -81,13 +74,13 @@ public static void ActivatePowerPlan(PowerModeState powerModeState, bool alwaysA return; } - CMD.Run("powercfg", $"/s {powerPlan.Guid}"); + await CMD.RunAsync("powercfg", $"/s {powerPlan.Guid}").ConfigureAwait(false); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Power plan {powerPlan.Guid} activated"); } - private static bool ShouldActivate(bool alwaysActivateDefaults, bool isDefault) + private static async Task ShouldActivateAsync(bool alwaysActivateDefaults, bool isDefault) { var overide = Settings.Instance.ActivatePowerProfilesWithVantageEnabled; if (overide) @@ -106,7 +99,7 @@ private static bool ShouldActivate(bool alwaysActivateDefaults, bool isDefault) return true; } - var status = Vantage.Status; + var status = await Vantage.GetStatusAsync().ConfigureAwait(false); if (status == VantageStatus.NotFound || status == VantageStatus.Disabled) { if (Log.Instance.IsTraceEnabled) @@ -134,7 +127,7 @@ private static PowerPlan Create(PropertyDataCollection properties) PowerModeState.Quiet => "16edbccd-dee9-4ec4-ace5-2f0b5f2a8975", PowerModeState.Balance => "85d583c5-cf2e-4197-80fd-3789a227a72c", PowerModeState.Performance => "52521609-efc9-4268-b9ba-67dea73f18b2", - _ => throw new InvalidOperationException("Unknown state."), + _ => throw new InvalidOperationException("Unknown state"), }; } } diff --git a/LenovoLegionToolkit.Lib/Utils/Registry.cs b/LenovoLegionToolkit.Lib/Utils/Registry.cs new file mode 100644 index 0000000000..57b010394b --- /dev/null +++ b/LenovoLegionToolkit.Lib/Utils/Registry.cs @@ -0,0 +1,48 @@ +using System; +using System.Management; +using System.Security.Principal; + +namespace LenovoLegionToolkit.Lib.Utils +{ + public static class Registry + { + public static IDisposable Listen(string hive, string path, string key, Action handler) + { + if (hive == "HKEY_CURRENT_USER") + { + var currentUserValue = WindowsIdentity.GetCurrent()?.User?.Value; + if (currentUserValue == null) + throw new InvalidOperationException("Current user value is null"); + hive = currentUserValue; + } + + var pathFormatted = @$"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{hive}\\{path.Replace(@"\", @"\\")}' AND ValueName = '{key}'"; + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Starting listener... [hive={hive}, pathFormatted ={pathFormatted}, key={key}]"); + + var watcher = new ManagementEventWatcher(pathFormatted); + watcher.EventArrived += (s, e) => + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Event arrived [classPath={e.NewEvent.ClassPath}, hive={hive}, pathFormatted={pathFormatted}, key={key}]"); + + handler(); + }; + watcher.Start(); + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Started listener [hive={hive}, pathFormatted={pathFormatted}, key={key}]"); + + return watcher; + } + + public static T Read(string hive, string path, string key, T defaultValue) + { + var result = Microsoft.Win32.Registry.GetValue(@$"{hive}\{path}", key, defaultValue); + if (result == null) + return defaultValue; + return (T)result; + } + } +} diff --git a/LenovoLegionToolkit.Lib/Utils/Settings.cs b/LenovoLegionToolkit.Lib/Utils/Settings.cs index 80388d2939..9325ac279b 100644 --- a/LenovoLegionToolkit.Lib/Utils/Settings.cs +++ b/LenovoLegionToolkit.Lib/Utils/Settings.cs @@ -1,7 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; -using System.Text.Json; +using LenovoLegionToolkit.Lib.Utils; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace LenovoLegionToolkit.Lib { @@ -9,12 +10,14 @@ public class Settings { private class SettingsStore { + public WindowSize WindowSize { get; set; } + public Theme Theme { get; set; } = Theme.Dark; public Dictionary PowerPlans { get; set; } = new(); - public bool MinimizeOnClose { get; set; } = true; + public bool MinimizeOnClose { get; set; } = false; public bool ActivatePowerProfilesWithVantageEnabled { get; set; } = false; } - private static Settings _instance; + private static Settings? _instance; public static Settings Instance { get @@ -25,11 +28,23 @@ public static Settings Instance } } - private SettingsStore _settingsStore; + private readonly SettingsStore _settingsStore; - private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly JsonSerializerSettings _jsonSerializerSettings; private readonly string _settingsStorePath; + public WindowSize WindowSize + { + get => _settingsStore.WindowSize; + set => _settingsStore.WindowSize = value; + } + + public Theme Theme + { + get => _settingsStore.Theme; + set => _settingsStore.Theme = value; + } + public Dictionary PowerPlans { get => _settingsStore.PowerPlans; @@ -50,35 +65,32 @@ public bool ActivatePowerProfilesWithVantageEnabled private Settings() { - var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var folderPath = Path.Combine(appData, "LenovoLegionToolkit"); - Directory.CreateDirectory(folderPath); - - _jsonSerializerOptions = new() { WriteIndented = true }; - _settingsStorePath = Path.Combine(folderPath, "settings.json"); - - Deserialize(); - } - - public void Synchronize() => Serialize(); + _jsonSerializerSettings = new() + { + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.Auto, + Converters = + { + new StringEnumConverter(), + } + }; + _settingsStorePath = Path.Combine(Folders.AppData, "settings.json"); - private void Deserialize() - { try { var settingsSerialized = File.ReadAllText(_settingsStorePath); - _settingsStore = JsonSerializer.Deserialize(settingsSerialized, _jsonSerializerOptions); + _settingsStore = JsonConvert.DeserializeObject(settingsSerialized, _jsonSerializerSettings) ?? new(); } catch { _settingsStore = new(); - Serialize(); + Synchronize(); } } - private void Serialize() + public void Synchronize() { - var settingsSerialized = JsonSerializer.Serialize(_settingsStore, _jsonSerializerOptions); + var settingsSerialized = JsonConvert.SerializeObject(_settingsStore, _jsonSerializerSettings); File.WriteAllText(_settingsStorePath, settingsSerialized); } } diff --git a/LenovoLegionToolkit.Lib/Utils/UpdateChecker.cs b/LenovoLegionToolkit.Lib/Utils/UpdateChecker.cs new file mode 100644 index 0000000000..f9750bbb20 --- /dev/null +++ b/LenovoLegionToolkit.Lib/Utils/UpdateChecker.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using LenovoLegionToolkit.Lib.Extensions; +using NeoSmart.AsyncLock; +using Octokit; + +namespace LenovoLegionToolkit.Lib.Utils +{ + public class UpdateChecker + { + private readonly TimeSpan _minimumTimeSpanForRefresh = new(hours: 3, minutes: 0, seconds: 0); + private readonly AsyncLock _updateSemaphore = new(); + + private DateTime _lastUpdate = DateTime.MinValue; + private Update[] _updates = Array.Empty(); + + public async Task Check() + { + using (await _updateSemaphore.LockAsync()) + { + try + { + var timeSpaneSinceLastUpdate = DateTime.Now - _lastUpdate; + var shouldCheck = timeSpaneSinceLastUpdate > _minimumTimeSpanForRefresh; + + if (!shouldCheck) + return _updates.Any(); + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Checking..."); + + var githubClient = new GitHubClient(new ProductHeaderValue("LenovoLegionToolkit-UpdateChecker")); + var releases = await githubClient.Repository.Release.GetAll("BartoszCichecki", "LenovoLegionToolkit", new ApiOptions { PageSize = 5 }).ConfigureAwait(false); + + var thisReleaseVersion = Assembly.GetEntryAssembly()?.GetName().Version; + + var updates = releases + .Where(r => !r.Draft) + .Select(r => new Update(r)) + .Where(r => r.Version > thisReleaseVersion) + .OrderByDescending(r => r.Version) + .ToArray(); + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Checked [_updates.Length={updates.Length}]"); + + _updates = updates; + + return updates.Any(); + } + catch (Exception ex) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Error: {ex.Demystify()}"); + return false; + } + finally + { + _lastUpdate = DateTime.Now; + } + } + } + + public async Task GetUpdates() + { + using (await _updateSemaphore.LockAsync()) + return _updates; + } + + public async Task DownloadLatestUpdate(IProgress? progress = null, CancellationToken cancellationToken = default) + { + using (await _updateSemaphore.LockAsync(cancellationToken)) + { + var tempPath = Path.Combine(Path.GetTempPath(), $"LenovoLegionToolkitSetup_{Guid.NewGuid()}.exe"); + var latestUpdate = _updates.FirstOrDefault(); + + if (latestUpdate.Equals(default(Update))) + throw new InvalidOperationException("No _updates available"); + + if (latestUpdate.Url == null) + throw new InvalidOperationException("Setup file URL could not be found"); + + using var fileStream = File.OpenWrite(tempPath); + using var httpClient = new HttpClient(); + await httpClient.DownloadAsync(latestUpdate.Url, fileStream, progress, cancellationToken); + + return tempPath; + } + } + } +} diff --git a/LenovoLegionToolkit.Lib/Utils/Updates.cs b/LenovoLegionToolkit.Lib/Utils/Updates.cs deleted file mode 100644 index 3d7be393fd..0000000000 --- a/LenovoLegionToolkit.Lib/Utils/Updates.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Octokit; - -namespace LenovoLegionToolkit.Lib.Utils -{ - public static class Updates - { - public static async Task Check() - { - try - { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Checking..."); - - var githubClient = new GitHubClient(new ProductHeaderValue("LenovoLegionToolkit-UpdateChecker")); - var releases = await githubClient.Repository.Release.GetAll("BartoszCichecki", "LenovoLegionToolkit"); - - var newestRelease = releases - .Select(r => Version.Parse(r.TagName)) - .OrderByDescending(r => r) - .FirstOrDefault(); - - var thisRelease = Assembly.GetEntryAssembly().GetName().Version; - - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Checked [thisRelease={thisRelease}, newestRelease={newestRelease}]"); - - return thisRelease < newestRelease; - } - catch (Exception ex) - { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Error: {ex}"); - return false; - } - } - } -} diff --git a/LenovoLegionToolkit.Lib/Utils/Vantage.cs b/LenovoLegionToolkit.Lib/Utils/Vantage.cs index ab858d45ff..706fd1f163 100644 --- a/LenovoLegionToolkit.Lib/Utils/Vantage.cs +++ b/LenovoLegionToolkit.Lib/Utils/Vantage.cs @@ -1,7 +1,8 @@ using System; using System.ComponentModel; using System.ServiceProcess; -using Microsoft.Win32.TaskScheduler; +using System.Threading.Tasks; +using TaskService = Microsoft.Win32.TaskScheduler.TaskService; namespace LenovoLegionToolkit.Lib.Utils { @@ -28,9 +29,9 @@ public static class Vantage "LenovoVantageService", }; - public static VantageStatus Status + public static Task GetStatusAsync() { - get + return Task.Run(() => { try { @@ -40,31 +41,37 @@ public static VantageStatus Status { return VantageStatus.NotFound; } - } + }); } - public static void Enable() + public static Task EnableAsync() { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Enabling..."); + return Task.Run(() => + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Enabling..."); - SetScheduledTasksEnabled(true); - SetServicesEnabled(true); + SetScheduledTasksEnabled(true); + SetServicesEnabled(true); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Enabled"); + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Enabled"); + }); } - public static void Disable() + public static Task DisableAsync() { - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Disabling..."); + return Task.Run(() => + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Disabling..."); - SetScheduledTasksEnabled(false); - SetServicesEnabled(false); + SetScheduledTasksEnabled(false); + SetServicesEnabled(false); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Disabled"); + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Disabled"); + }); } private static void SetScheduledTasksEnabled(bool enabled) diff --git a/LenovoLegionToolkit.Lib/Utils/WMI.cs b/LenovoLegionToolkit.Lib/Utils/WMI.cs index cd91ddeddc..c40a4704c3 100644 --- a/LenovoLegionToolkit.Lib/Utils/WMI.cs +++ b/LenovoLegionToolkit.Lib/Utils/WMI.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Management; +using System.Threading.Tasks; namespace LenovoLegionToolkit.Lib.Utils { @@ -29,40 +30,26 @@ public static IDisposable Listen(string scope, FormattableString query, Action

Read(string scope, FormattableString query, Func converter) + public static Task> ReadAsync(string scope, FormattableString query, Func converter) { - var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); - - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Reading... [scope={scope}, queryFormatted={queryFormatted}]"); - - using var searcher = new ManagementObjectSearcher(scope, queryFormatted); - foreach (var queryObj in searcher.Get()) - yield return converter(queryObj.Properties); - - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Read [scope={scope}, queryFormatted={queryFormatted}]"); - } + return Task.Run>(() => + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); - public static object Invoke(string scope, - string clazz, - string propertyName, - string propertyValue, - string methodName, - object[] parameters = null) - { - var path = $"{scope}:{clazz}.{propertyName}='{propertyValue.ToString(WMIPropertyValueFormatter.Instance)}'"; + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Reading... [scope={scope}, queryFormatted={queryFormatted}]"); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Invoking... [path={path}]"); + var result = new List(); - using var managementObject = new ManagementObject(path); - var result = managementObject.InvokeMethod(methodName, parameters); + using var searcher = new ManagementObjectSearcher(scope, queryFormatted); + foreach (var queryObj in searcher.Get()) + result.Add(converter(queryObj.Properties)); - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Invoked [path={path}, result={result}]"); + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Read [scope={scope}, queryFormatted={queryFormatted}]"); - return result; + return result; + }); } private class WMIPropertyValueFormatter : IFormatProvider, ICustomFormatter @@ -71,18 +58,19 @@ private class WMIPropertyValueFormatter : IFormatProvider, ICustomFormatter private WMIPropertyValueFormatter() { } - public object GetFormat(Type formatType) + public object GetFormat(Type? formatType) { if (formatType == typeof(ICustomFormatter)) return this; - return null; + throw new InvalidOperationException("Invalid type of formatted"); } - public string Format(string format, object arg, IFormatProvider formatProvider) + + public string Format(string? format, object? arg, IFormatProvider? formatProvider) { - var stringArg = arg.ToString(); - stringArg = stringArg.Replace("\\", "\\\\"); - return stringArg; + var stringArg = arg?.ToString(); + stringArg = stringArg?.Replace("\\", "\\\\"); + return stringArg ?? ""; } } } diff --git a/LenovoLegionToolkit.WPF/AboutWindow.xaml b/LenovoLegionToolkit.WPF/AboutWindow.xaml deleted file mode 100644 index c326b3417c..0000000000 --- a/LenovoLegionToolkit.WPF/AboutWindow.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - GitHub - - - © 2021 Bartosz Cichecki - - - diff --git a/LenovoLegionToolkit.WPF/AboutWindow.xaml.cs b/LenovoLegionToolkit.WPF/AboutWindow.xaml.cs deleted file mode 100644 index 6bb0a6525d..0000000000 --- a/LenovoLegionToolkit.WPF/AboutWindow.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using System.Windows; -using System.Windows.Navigation; - -namespace LenovoLegionToolkit -{ - ///

- /// Interaction logic for AboutWindow.xaml - /// - public partial class AboutWindow : Window - { - public AboutWindow() - { - InitializeComponent(); - - versionLbl.Content += Assembly.GetEntryAssembly().GetName().Version.ToString(3); - } - - private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true }); - e.Handled = true; - } - } -} diff --git a/LenovoLegionToolkit.WPF/App.xaml b/LenovoLegionToolkit.WPF/App.xaml index c686fc56eb..8b7743212b 100644 --- a/LenovoLegionToolkit.WPF/App.xaml +++ b/LenovoLegionToolkit.WPF/App.xaml @@ -1,9 +1,15 @@ - + - + + + + + + \ No newline at end of file diff --git a/LenovoLegionToolkit.WPF/App.xaml.cs b/LenovoLegionToolkit.WPF/App.xaml.cs index 9a40e75f09..0a7e6c6e30 100644 --- a/LenovoLegionToolkit.WPF/App.xaml.cs +++ b/LenovoLegionToolkit.WPF/App.xaml.cs @@ -1,62 +1,124 @@ using System; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; using System.Windows.Threading; +using LenovoLegionToolkit.Lib.Features; +using LenovoLegionToolkit.Lib.Listeners; using LenovoLegionToolkit.Lib.Utils; +using LenovoLegionToolkit.WPF.Utils; +using LenovoLegionToolkit.WPF.Windows; namespace LenovoLegionToolkit { - /// - /// Interaction logic for App.xaml - /// public partial class App : Application { private const string MutexName = "LenovoLegionToolkit_Mutex_6efcc882-924c-4cbc-8fec-f45c25696f98"; private const string EventName = "LenovoLegionToolkit_Event_6efcc882-924c-4cbc-8fec-f45c25696f98"; #pragma warning disable IDE0052 // Remove unread private members - private Mutex _mutex; - private EventWaitHandle _eventWaitHandle; + private Mutex? _mutex; + private EventWaitHandle? _eventWaitHandle; #pragma warning restore IDE0052 // Remove unread private members - private void Application_Startup(object sender, StartupEventArgs e) + private async void Application_Startup(object sender, StartupEventArgs e) { if (IsTraceEnabled(e.Args)) Log.Instance.IsTraceEnabled = true; if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Starting... [version={Assembly.GetEntryAssembly().GetName().Version}]"); + Log.Instance.Trace($"Starting... [version={Assembly.GetEntryAssembly()?.GetName().Version}]"); + + RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly; EnsureSingleInstance(); if (!ShouldByPassCompatibilityCheck(e.Args)) - CheckCompatibility(); + await CheckCompatibilityAsync(); + + Container.Initialize(); - var mainWindow = new MainWindow(); - if (ShouldStartMinimized(e.Args)) + try { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Sending MainWindow to tray..."); - mainWindow.SendToTray(); + Log.Instance.Trace($"Ensuring correct power plan is set..."); + + await Container.Resolve().EnsureCorrectPowerPlanIsSetAsync(); } - else + catch (Exception ex) { if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Showing MainWindow..."); - mainWindow.Show(); + Log.Instance.Trace($"Couldn't ensure correct power plan. Exception: {ex.Demystify()}"); } - if (Log.Instance.IsTraceEnabled) - Log.Instance.Trace($"Start up complete"); + try + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Starting power mode listener..."); + + Container.Resolve().Start(); + } + catch (Exception ex) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Couldn't start power model listener. Exception: {ex.Demystify()}"); + } + + try + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Starting display configuration listener..."); + + Container.Resolve().Start(); + } + catch (Exception ex) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Couldn't start display configuration listener. Exception: {ex.Demystify()}"); + } + + Autorun.Validate(); + + Container.Resolve().Apply(); + + using (await ThemePreloader.PreloadAsync()) + { + var mainWindow = new MainWindow + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + MainWindow = mainWindow; + + if (ShouldStartMinimized(e.Args)) + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Sending MainWindow to tray..."); + mainWindow.WindowState = WindowState.Minimized; + mainWindow.Show(); + mainWindow.SendToTray(); + } + else + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Showing MainWindow..."); + mainWindow.Show(); + } + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Start up complete"); + } } private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { - Log.Instance.ErrorReport(e.Exception); + Log.Instance.ErrorReport(e.Exception.Demystify()); - var errorText = e.Exception.ToString(); + var errorText = e.Exception.Demystify().ToString(); if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"***** *** Unhandled exception ***** ***\n{errorText}"); @@ -65,9 +127,10 @@ private void Application_DispatcherUnhandledException(object sender, DispatcherU Shutdown(-1); } - private void CheckCompatibility() + private async Task CheckCompatibilityAsync() { - if (Compatibility.IsCompatible(out var mi)) + var (isCompatible, mi) = await Compatibility.IsCompatibleAsync(); + if (isCompatible) { if (Log.Instance.IsTraceEnabled) Log.Instance.Trace($"Compatibility check passed"); diff --git a/LenovoLegionToolkit.WPF/Assets/paypal_button.png b/LenovoLegionToolkit.WPF/Assets/paypal_button.png new file mode 100644 index 0000000000..9cd08f459a Binary files /dev/null and b/LenovoLegionToolkit.WPF/Assets/paypal_button.png differ diff --git a/LenovoLegionToolkit.WPF/Assets/paypal_qr.png b/LenovoLegionToolkit.WPF/Assets/paypal_qr.png new file mode 100644 index 0000000000..cc6e5d11b6 Binary files /dev/null and b/LenovoLegionToolkit.WPF/Assets/paypal_qr.png differ diff --git a/LenovoLegionToolkit.WPF/Controls/AbstractComboBoxCardControl.cs b/LenovoLegionToolkit.WPF/Controls/AbstractComboBoxCardControl.cs new file mode 100644 index 0000000000..5dcb6ea17a --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/AbstractComboBoxCardControl.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using LenovoLegionToolkit.Lib; +using LenovoLegionToolkit.Lib.Extensions; +using LenovoLegionToolkit.Lib.Features; +using LenovoLegionToolkit.WPF.Utils; +using WPFUI.Common; +using WPFUI.Controls; + +namespace LenovoLegionToolkit.WPF.Controls +{ + public abstract class AbstractComboBoxCardControl : AbstractRefreshingControl where T : struct + { + private readonly IFeature _feature = Container.Resolve>(); + + private readonly CardControl _cardControl = new(); + private readonly ComboBox _comboBox = new(); + + public SymbolRegular Icon + { + get => _cardControl.Icon; + set => _cardControl.Icon = value; + } + + public string Title + { + get => _cardControl.Title; + set => _cardControl.Title = value; + } + + public string Subtitle + { + get => _cardControl.Subtitle; + set => _cardControl.Subtitle = value; + } + + public AbstractComboBoxCardControl() => InitializeComponent(); + + private void InitializeComponent() + { + _comboBox.SelectionChanged += ComboBox_SelectionChanged; + _comboBox.Width = 150; + _comboBox.Visibility = Visibility.Hidden; + + _cardControl.Margin = new Thickness(0, 0, 0, 8); + _cardControl.Content = _comboBox; + + Content = _cardControl; + } + + private async void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) => await OnStateChange(_comboBox, _feature); + + protected override async Task OnRefreshAsync() + { + var items = await _feature.GetAllStatesAsync(); + var selectedItem = await _feature.GetStateAsync(); + + static string displayName(T value) + { + if (value is IDisplayName dn) + return dn.DisplayName; + if (value is Enum e) + return e.GetDisplayName(); + return value.ToString() ?? throw new InvalidOperationException("Unsupported type"); + } + + _comboBox.SetItems(items, selectedItem, displayName); + _comboBox.IsEnabled = items.Any(); + } + + protected override void OnFinishedLoading() => _comboBox.Visibility = Visibility.Visible; + + protected virtual async Task OnStateChange(ComboBox comboBox, IFeature feature) + { + if (IsRefreshing) + return; + + if (!comboBox.TryGetSelectedItem(out T selectedState)) + return; + + T currentState = await feature.GetStateAsync(); + + if (selectedState.Equals(currentState)) + return; + + await feature.SetStateAsync(selectedState); + } + } +} diff --git a/LenovoLegionToolkit.WPF/Controls/AbstractRefreshingControl.cs b/LenovoLegionToolkit.WPF/Controls/AbstractRefreshingControl.cs new file mode 100644 index 0000000000..587085dae3 --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/AbstractRefreshingControl.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using LenovoLegionToolkit.Lib.Utils; + +namespace LenovoLegionToolkit.WPF.Controls +{ + public abstract class AbstractRefreshingControl : UserControl + { + protected bool IsRefreshing => _refreshTask != null; + + private Task? _refreshTask; + + public AbstractRefreshingControl() + { + IsEnabled = false; + + Loaded += RefreshingControl_Loaded; + IsVisibleChanged += RefreshingControl_IsVisibleChanged; + } + + private async void RefreshingControl_Loaded(object sender, RoutedEventArgs e) + { + var loadingTask = Task.Delay(250); + await RefreshAsync(); + await loadingTask; + OnFinishedLoading(); + } + + protected abstract void OnFinishedLoading(); + + private async void RefreshingControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (IsLoaded && IsVisible) + await RefreshAsync(); + } + + protected async Task RefreshAsync() + { + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Refreshing control... [feature={GetType().Name}]"); + + var exceptions = false; + + try + { + IsEnabled = false; + + if (_refreshTask == null) + _refreshTask = OnRefreshAsync(); + await _refreshTask; + } + catch (Exception ex) + { + exceptions = true; + + if (Log.Instance.IsTraceEnabled) + Log.Instance.Trace($"Exception when refreshing control. [feature={GetType().Name}, ex={ex.Demystify()}]"); + } + finally + { + _refreshTask = null; + + if (exceptions) + Visibility = Visibility.Collapsed; + else + IsEnabled = true; + } + } + + protected abstract Task OnRefreshAsync(); + } +} diff --git a/LenovoLegionToolkit.WPF/Controls/AbstractToggleCardControl.cs b/LenovoLegionToolkit.WPF/Controls/AbstractToggleCardControl.cs new file mode 100644 index 0000000000..a5758385fd --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/AbstractToggleCardControl.cs @@ -0,0 +1,70 @@ +using System.Threading.Tasks; +using System.Windows; +using LenovoLegionToolkit.Lib.Features; +using LenovoLegionToolkit.WPF.Utils; +using WPFUI.Common; +using WPFUI.Controls; + +namespace LenovoLegionToolkit.WPF.Controls +{ + public abstract class AbstractToggleCardControl : AbstractRefreshingControl where T : struct + { + private readonly IFeature _feature = Container.Resolve>(); + + private readonly CardControl _cardControl = new(); + private readonly ToggleSwitch _toggle = new(); + + public SymbolRegular Icon + { + get => _cardControl.Icon; + set => _cardControl.Icon = value; + } + + public string Title + { + get => _cardControl.Title; + set => _cardControl.Title = value; + } + + public string Subtitle + { + get => _cardControl.Subtitle; + set => _cardControl.Subtitle = value; + } + + protected abstract T OnState { get; } + + protected abstract T OffState { get; } + + public AbstractToggleCardControl() => InitializeComponent(); + + private void InitializeComponent() + { + _toggle.Click += Toggle_Click; + _toggle.Visibility = Visibility.Hidden; + + _cardControl.Margin = new Thickness(0, 0, 0, 8); + _cardControl.Content = _toggle; + + Content = _cardControl; + } + + private async void Toggle_Click(object sender, RoutedEventArgs e) => await OnStateChange(_toggle, _feature); + + protected override async Task OnRefreshAsync() => _toggle.IsChecked = OnState.Equals(await _feature.GetStateAsync()); + + protected override void OnFinishedLoading() => _toggle.Visibility = Visibility.Visible; + + protected virtual async Task OnStateChange(ToggleSwitch toggle, IFeature feature) + { + if (IsRefreshing || toggle.IsChecked == null) + return; + + var state = toggle.IsChecked.Value ? OnState : OffState; + if (state.Equals(await feature.GetStateAsync())) + return; + + await feature.SetStateAsync(state); + } + } +} diff --git a/LenovoLegionToolkit.WPF/Controls/Dashboard/AlwaysOnUSBControl.cs b/LenovoLegionToolkit.WPF/Controls/Dashboard/AlwaysOnUSBControl.cs new file mode 100644 index 0000000000..12b9749d11 --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/Dashboard/AlwaysOnUSBControl.cs @@ -0,0 +1,15 @@ +using LenovoLegionToolkit.Lib; +using WPFUI.Common; + +namespace LenovoLegionToolkit.WPF.Controls.Dashboard +{ + public class AlwaysOnUSBControl : AbstractComboBoxCardControl + { + public AlwaysOnUSBControl() + { + Icon = SymbolRegular.UsbStick24; + Title = "Always on USB"; + Subtitle = "Charge USB devices, when the computer is\noff or in sleep or hibernation mode."; + } + } +} diff --git a/LenovoLegionToolkit.WPF/Controls/Dashboard/BatteryModeControl.cs b/LenovoLegionToolkit.WPF/Controls/Dashboard/BatteryModeControl.cs new file mode 100644 index 0000000000..787cf38f2c --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/Dashboard/BatteryModeControl.cs @@ -0,0 +1,15 @@ +using LenovoLegionToolkit.Lib; +using WPFUI.Common; + +namespace LenovoLegionToolkit.WPF.Controls.Dashboard +{ + public class BatteryModeControl : AbstractComboBoxCardControl + { + public BatteryModeControl() + { + Icon = SymbolRegular.BatteryCharge24; + Title = "Battery Mode"; + Subtitle = "Choose how the battery is charged."; + } + } +} diff --git a/LenovoLegionToolkit.WPF/Controls/Dashboard/DiscreteGPUControl.xaml b/LenovoLegionToolkit.WPF/Controls/Dashboard/DiscreteGPUControl.xaml new file mode 100644 index 0000000000..ec25cffd11 --- /dev/null +++ b/LenovoLegionToolkit.WPF/Controls/Dashboard/DiscreteGPUControl.xaml @@ -0,0 +1,51 @@ + + + + + + + + +