├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ └── release.yml ├── CHANGELOG.md ├── COPYING ├── README.md ├── lipui.sln ├── runtimes ├── win10-arm64 │ └── native │ │ └── Microsoft.WindowsAppRuntime.Bootstrap.dll ├── win10-x64 │ └── native │ │ └── Microsoft.WindowsAppRuntime.Bootstrap.dll └── win10-x86 │ └── native │ └── Microsoft.WindowsAppRuntime.Bootstrap.dll ├── src ├── AutoUpdate │ ├── AutoUpdate.csproj │ └── Program.cs └── LipUI │ ├── App.xaml │ ├── App.xaml.cs │ ├── Assets │ ├── Images.Designer.cs │ ├── Images.resx │ ├── SplashScreen.scale-200.png │ ├── Square150x150Logo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Square44x44Logo.targetsize-24_altform-unplated.png │ ├── StoreLogo.png │ ├── Wide310x150Logo.scale-200.png │ ├── WindowIcon.ico │ ├── applicationIcon-1024.png │ ├── applicationIcon-256.png │ ├── glass.png │ ├── grass_block.png │ └── netherrack.png │ ├── BuiltInPlugins │ ├── DefaultLipProxySetter │ │ └── DefaultLipProxySetter.cs │ ├── LipPanel │ │ ├── LipPanel.cs │ │ ├── LipPanelPage.xaml │ │ └── LipPanelPage.xaml.cs │ └── LipuiTips │ │ └── LipuiTips.cs │ ├── Language │ ├── en-US │ │ └── Resources.resw │ └── zh-CN │ │ └── Resources.resw │ ├── LipUI.csproj │ ├── MainWindow │ ├── MainWIndowWin32Invoke.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── MainWindowInfoBarController.cs │ ├── MainWindowNavigationView.cs │ └── MainWindowPluginsHandler.cs │ ├── Models │ ├── Config.cs │ ├── GlobalIcons.cs │ ├── InternalServices.cs │ ├── Lip │ │ ├── LipCommand.cs │ │ ├── LipCommandContext.cs │ │ ├── LipCommandOption.cs │ │ ├── LipConsole.cs │ │ └── LipConsoleInstance.cs │ ├── Main.cs │ ├── PackageInstaller.cs │ ├── Plugin │ │ ├── ConfigCollection.cs │ │ ├── IHomePageModule.cs │ │ ├── IPlugin.cs │ │ ├── IUIPlugin.cs │ │ ├── LipuiRuntimeInfo.cs │ │ ├── LipuiServices.cs │ │ ├── PluginConfigManager.cs │ │ └── PluginSystem.cs │ ├── ServerIcon.cs │ └── ServerInstance.cs │ ├── Package.appxmanifest │ ├── Pages │ ├── Home │ │ ├── HomePage.xaml │ │ ├── HomePage.xaml.cs │ │ └── Modules │ │ │ ├── AllowListViewerPage.xaml │ │ │ ├── AllowListViewerPage.xaml.cs │ │ │ ├── BdsPropertiesEditorPage.xaml │ │ │ ├── BdsPropertiesEditorPage.xaml.cs │ │ │ ├── ModuleIcon.xaml │ │ │ ├── ModuleIcon.xaml.cs │ │ │ ├── ModulesPage.xaml │ │ │ └── ModulesPage.xaml.cs │ ├── Index │ │ ├── IndexPage.xaml │ │ ├── IndexPage.xaml.cs │ │ ├── LipIndexToothView.xaml │ │ └── LipIndexToothView.xaml.cs │ ├── LipExecutionPanel │ │ ├── ConsoleLineView.xaml │ │ ├── ConsoleLineView.xaml.cs │ │ ├── EulaAcceptView.xaml │ │ ├── EulaAcceptView.xaml.cs │ │ ├── LipExecutionPanelPage.xaml │ │ ├── LipExecutionPanelPage.xaml.cs │ │ ├── LipInstallerView.xaml │ │ └── LipInstallerView.xaml.cs │ ├── LocalPackage │ │ ├── LocalPackagePage.xaml │ │ ├── LocalPackagePage.xaml.cs │ │ ├── LocalToothView.xaml │ │ └── LocalToothView.xaml.cs │ ├── ModuleManager │ │ ├── LipuiPluginsView.xaml │ │ ├── LipuiPluginsView.xaml.cs │ │ ├── ModuleManagerPage.xaml │ │ └── ModuleManagerPage.xaml.cs │ ├── ServerSelection │ │ ├── ServerInstanceEditView.xaml │ │ ├── ServerInstanceEditView.xaml.cs │ │ ├── ServerInstanceView.xaml │ │ ├── ServerInstanceView.xaml.cs │ │ ├── ServerSelectionPage.xaml │ │ └── ServerSelectionPage.xaml.cs │ ├── Settings │ │ ├── GeneralSettingsView.xaml │ │ ├── GeneralSettingsView.xaml.cs │ │ ├── LipSettingsView.xaml │ │ ├── LipSettingsView.xaml.cs │ │ ├── PersonalizationSettingsView.xaml │ │ ├── PersonalizationSettingsView.xaml.cs │ │ ├── SettingsAndAboutView.xaml │ │ ├── SettingsAndAboutView.xaml.cs │ │ ├── SettingsPage.xaml │ │ └── SettingsPage.xaml.cs │ ├── ToothInfoPage.xaml │ └── ToothInfoPage.xaml.cs │ ├── Properties │ ├── PublishProfiles │ │ ├── win10-arm64.pubxml │ │ ├── win10-x64.pubxml │ │ └── win10-x86.pubxml │ └── launchSettings.json │ ├── Protocol │ ├── LipIndex.cs │ ├── LipTooth.cs │ └── LocalToothItem.cs │ └── app.manifest └── tooth.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Describe the bug 9 | description: A clear and concise description of what the bug is. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: To Reproduce 16 | description: Steps to reproduce the behavior. 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Expected behavior 23 | description: A clear and concise description of what you expected to happen. 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | attributes: 29 | label: Screenshots 30 | description: If applicable, add screenshots to help explain your problem. 31 | 32 | - type: input 33 | attributes: 34 | label: Platform 35 | description: The platform you are using. (e.g. Windows 10, macOS 10.15, Ubuntu 20.04) 36 | 37 | - type: input 38 | attributes: 39 | label: Version 40 | description: The version of the application you are using. (e.g. 1.0.0) 41 | 42 | - type: textarea 43 | attributes: 44 | label: Additional context 45 | description: Add any other context about the problem here. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Is your feature request related to a problem? Please describe. 9 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: Describe the solution you'd like 16 | description: A clear and concise description of what you want to happen. 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: Describe alternatives you've considered 23 | description: A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | - type: textarea 26 | attributes: 27 | label: Additional context 28 | description: Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What does this PR do? 2 | 3 | 4 | 5 | ## Which issues does this PR resolve? 6 | 7 | 8 | 9 | ## Checklist before merging 10 | 11 | Thank you for your contribution to the repository. 12 | Before submitting this PR, please make sure: 13 | 14 | - [ ] Your code builds clean without any errors or warnings 15 | - [ ] Your code follows the code style of [Common C# code conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) 16 | - [ ] You have tested all functions 17 | - [ ] You have not used code without license 18 | - [ ] You have added statement for third-party code 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | strategy: 9 | matrix: 10 | include: 11 | - runtime: win-arm64 12 | platform: arm64 13 | - runtime: win-x64 14 | platform: x64 15 | runs-on: windows-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: 8.0.x 22 | 23 | - run: | 24 | dotnet publish src/AutoUpdate/AutoUpdate.csproj -c Release -o bin/lipui -r ${{ matrix.runtime }} 25 | 26 | dotnet build src/LipUI/LipUI.csproj -c Release -o bin/lipui -r ${{ matrix.runtime }} ` 27 | -p:DebugType=none -p:Platform=${{ matrix.platform }} 28 | 29 | copy runtimes/win10-${{ matrix.platform }}/native/Microsoft.WindowsAppRuntime.Bootstrap.dll bin/lipui 30 | 31 | - uses: actions/upload-artifact@v4 32 | with: 33 | name: LipUI-${{ matrix.runtime }}-${{ github.sha }} 34 | path: bin 35 | 36 | check-style: 37 | runs-on: windows-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - uses: actions/setup-dotnet@v4 42 | with: 43 | dotnet-version: 8.0.x 44 | 45 | - run: dotnet format --verify-no-changes src/LipUI 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: 4 | - published 5 | 6 | jobs: 7 | build: 8 | strategy: 9 | matrix: 10 | include: 11 | - runtime: win-arm64 12 | platform: arm64 13 | - runtime: win-x64 14 | platform: x64 15 | runs-on: windows-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: actions/setup-dotnet@v4 20 | with: 21 | dotnet-version: 8.0.x 22 | 23 | - run: | 24 | dotnet publish src/AutoUpdate/AutoUpdate.csproj -c Release -o bin/lipui -r ${{ matrix.runtime }} 25 | 26 | dotnet build src/LipUI/LipUI.csproj -c Release -o bin/lipui -r ${{ matrix.runtime }} ` 27 | -p:DebugType=none -p:Platform=${{ matrix.platform }} 28 | 29 | copy runtimes/win10-${{ matrix.platform }}/native/Microsoft.WindowsAppRuntime.Bootstrap.dll bin/lipui 30 | 31 | - uses: actions/upload-artifact@v4 32 | with: 33 | name: LipUI-${{ matrix.runtime }}-${{ github.sha }} 34 | path: bin 35 | 36 | update-changelog: 37 | permissions: 38 | contents: write 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - id: extract-release-notes 44 | uses: ffurrer2/extract-release-notes@v2 45 | 46 | - uses: softprops/action-gh-release@v1 47 | with: 48 | body: | 49 | ${{ steps.extract-release-notes.outputs.release_notes }} 50 | 51 | upload-to-release: 52 | needs: 53 | - build 54 | permissions: 55 | contents: write 56 | runs-on: ubuntu-latest 57 | strategy: 58 | matrix: 59 | runtime: 60 | - win-arm64 61 | - win-x64 62 | steps: 63 | - uses: actions/checkout@v4 64 | 65 | - uses: actions/download-artifact@v4 66 | with: 67 | name: LipUI-${{ matrix.runtime }}-${{ github.sha }} 68 | path: bin 69 | 70 | - run: | 71 | cp CHANGELOG.md COPYING README.md bin/ 72 | 73 | - name: Create archive 74 | run: | 75 | cd bin 76 | zip -r ../LipUI-${{ matrix.runtime }}.zip * 77 | cd .. 78 | 79 | - uses: softprops/action-gh-release@v1 80 | with: 81 | files: | 82 | LipUI-${{ matrix.runtime }}.zip 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.4.1] - 2024-03-05 9 | 10 | ### Added 11 | 12 | - Introduce support for .NET 8.0. 13 | - Provide a built-in default proxy setter for users in Mainland China. 14 | - Introduce a plugin configuration system. 15 | - Add an auto-update feature. 16 | 17 | ### Changed 18 | 19 | - Refactor the display logic of the info bar based on queue information. 20 | 21 | ### Fixed 22 | 23 | - Fix related bugs. 24 | 25 | ## [0.4.0] - 2024-02-04 26 | 27 | ### Added 28 | 29 | - Install teeth by dragging and dropping the package file into the window. 30 | 31 | ### Changed 32 | 33 | - Adapt to lip-index 0.8.0. 34 | 35 | ### Fixed 36 | 37 | - Crash under some conditions. 38 | 39 | ## [0.3.0] - 2024-01-18 40 | 41 | ### Changed 42 | 43 | - Rewrite all the code. 44 | 45 | ## [0.2.7] - 2023-06-27 46 | 47 | ### Added 48 | 49 | - Cache purge. 50 | 51 | ### Changed 52 | 53 | - Adapt to lip 0.15.0. 54 | 55 | ### Fixed 56 | 57 | - Crash on deleting working directory. 58 | 59 | ## [0.2.6] - 2023-04-24 60 | 61 | ### Fixed 62 | 63 | - Wrong visibility of the `skip dependency` texts. 64 | 65 | ## [0.2.5] - 2023-04-04 66 | 67 | ### Added 68 | 69 | - i18n support. 70 | 71 | ### Changed 72 | 73 | - Optimize the UI. 74 | - Optimize the UI. 75 | 76 | ## [0.2.4] - 2023-03-11 77 | 78 | ### Added 79 | 80 | - Animations. 81 | 82 | ### Changed 83 | 84 | - Optimize user experience. 85 | 86 | ### Removed 87 | 88 | - Icon at status bar. 89 | 90 | ### Fixed 91 | 92 | - Unicode encoding. 93 | 94 | ## [0.2.3] - 2023-03-07 95 | 96 | ### Added 97 | 98 | - Pop up EULA. 99 | - Packages now can be filtered by tags. 100 | - Upgrade support for packages. 101 | 102 | ### Changed 103 | 104 | - Optimize the UI. 105 | 106 | ## [0.2.2] - 2023-02-19 107 | 108 | ### Fixed 109 | 110 | - Some bugs. 111 | 112 | ## [0.2.1] - 2023-02-18 113 | 114 | ### Fixed 115 | 116 | - Some bugs. 117 | 118 | ## [0.2.0] - 2023-02-15 119 | 120 | ### Added 121 | 122 | - More functionalities. 123 | 124 | ## [0.1.0] - 2023-02-13 125 | 126 | - Initial release. 127 | 128 | [0.4.0]: https://github.com/lippkg/LipUI/compare/v0.3.0...v0.4.0 129 | [0.3.0]: https://github.com/lippkg/LipUI/compare/v0.2.7...v0.3.0 130 | [0.2.7]: https://github.com/lippkg/LipUI/compare/v0.2.6...v0.2.7 131 | [0.2.6]: https://github.com/lippkg/LipUI/compare/v0.2.5...v0.2.6 132 | [0.2.5]: https://github.com/lippkg/LipUI/compare/v0.2.4...v0.2.5 133 | [0.2.4]: https://github.com/lippkg/LipUI/compare/v0.2.3...v0.2.4 134 | [0.2.3]: https://github.com/lippkg/LipUI/compare/v0.2.2...v0.2.3 135 | [0.2.2]: https://github.com/lippkg/LipUI/compare/v0.2.1...v0.2.2 136 | [0.2.1]: https://github.com/lippkg/LipUI/compare/v0.2.0...v0.2.1 137 | [0.2.0]: https://github.com/lippkg/LipUI/compare/v0.1.0...v0.2.0 138 | [0.1.0]: https://github.com/lippkg/LipUI/releases/tag/v0.1.0 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > I'm refactoring lip and lipui will be integrated within lip. Go to [lip repo](https://github.com/futrime/lip) to keep track. 3 | 4 | # LipUI 5 | 6 | A GUI client for lip 7 | 8 | ## Install 9 | 10 | 1. Download the latest release from [here](https://github.com/lippkg/LipUI/releases). 11 | 12 | 2. Extract the archive. 13 | 14 | 3. Run `LipUI.exe`. 15 | 16 | ## Usage 17 | 18 | Just follow the instructions on the screen. 19 | 20 | ## Contributing 21 | 22 | If you have any suggestions or improvements, please [submit an issue](https://github.com/lippkg/LipUI/issues) or a pull request. 23 | 24 | LipUI follows the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) Code of Conduct. 25 | 26 | ## License 27 | 28 | GPL-3.0-only © lippkg 29 | -------------------------------------------------------------------------------- /lipui.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34408.163 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LipUI", "src\LipUI\LipUI.csproj", "{E0A3F349-6972-4266-8730-9E983EEFCA0D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdate", "src\AutoUpdate\AutoUpdate.csproj", "{184A05DC-72A5-47D9-AFCB-0228CD8C563F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|ARM64 = Debug|ARM64 14 | Debug|x64 = Debug|x64 15 | Release|Any CPU = Release|Any CPU 16 | Release|ARM64 = Release|ARM64 17 | Release|x64 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|Any CPU.ActiveCfg = Debug|x64 21 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|Any CPU.Build.0 = Debug|x64 22 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|Any CPU.Deploy.0 = Debug|x64 23 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|ARM64.ActiveCfg = Debug|ARM64 24 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|ARM64.Build.0 = Debug|ARM64 25 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|ARM64.Deploy.0 = Debug|ARM64 26 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|x64.ActiveCfg = Debug|x64 27 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|x64.Build.0 = Debug|x64 28 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Debug|x64.Deploy.0 = Debug|x64 29 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|Any CPU.ActiveCfg = Release|x64 30 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|Any CPU.Build.0 = Release|x64 31 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|Any CPU.Deploy.0 = Release|x64 32 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|ARM64.ActiveCfg = Release|ARM64 33 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|ARM64.Build.0 = Release|ARM64 34 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|ARM64.Deploy.0 = Release|ARM64 35 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|x64.ActiveCfg = Release|x64 36 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|x64.Build.0 = Release|x64 37 | {E0A3F349-6972-4266-8730-9E983EEFCA0D}.Release|x64.Deploy.0 = Release|x64 38 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|ARM64.ActiveCfg = Debug|Any CPU 41 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|ARM64.Build.0 = Debug|Any CPU 42 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Debug|x64.Build.0 = Debug|Any CPU 44 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|ARM64.ActiveCfg = Release|Any CPU 47 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|ARM64.Build.0 = Release|Any CPU 48 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|x64.ActiveCfg = Release|Any CPU 49 | {184A05DC-72A5-47D9-AFCB-0228CD8C563F}.Release|x64.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {19C83D64-AA9C-4A55-9A1F-D035A212D14F} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /runtimes/win10-arm64/native/Microsoft.WindowsAppRuntime.Bootstrap.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/runtimes/win10-arm64/native/Microsoft.WindowsAppRuntime.Bootstrap.dll -------------------------------------------------------------------------------- /runtimes/win10-x64/native/Microsoft.WindowsAppRuntime.Bootstrap.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/runtimes/win10-x64/native/Microsoft.WindowsAppRuntime.Bootstrap.dll -------------------------------------------------------------------------------- /runtimes/win10-x86/native/Microsoft.WindowsAppRuntime.Bootstrap.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/runtimes/win10-x86/native/Microsoft.WindowsAppRuntime.Bootstrap.dll -------------------------------------------------------------------------------- /src/AutoUpdate/AutoUpdate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | true 10 | 11 | 12 | 13 | none 14 | 15 | 16 | 17 | none 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AutoUpdate/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System.CommandLine; 3 | using System.Diagnostics; 4 | 5 | using var factory = LoggerFactory.Create(builder => builder.AddConsole()); 6 | var logger = factory.CreateLogger("AutoUpdate"); 7 | 8 | var lipuiDirOption = new Option("--lipui-dir", "LipUI directory") 9 | { 10 | IsRequired = true, 11 | }; 12 | var lipuiPid = new Option("--lipui-pid", "LipUI process ID") 13 | { 14 | IsRequired = true 15 | }; 16 | 17 | RootCommand rootCommand = [lipuiDirOption, lipuiPid]; 18 | rootCommand.SetHandler((lipuiPath, pid) => 19 | { 20 | var lipuiProcess = Process.GetProcessById(pid); 21 | 22 | lipuiProcess.Kill(); 23 | lipuiProcess.WaitForExit(); 24 | logger.LogInformation("Waiting for lipui process {pid} to exit", pid); 25 | 26 | Task.Delay(TimeSpan.FromSeconds(2)).Wait(); 27 | 28 | var currentDir = new FileInfo(Environment.ProcessPath!).Directory ?? throw new InvalidOperationException("Current directory not found"); 29 | 30 | var lipuiDir = new DirectoryInfo(lipuiPath); 31 | 32 | if (lipuiDir.FullName == currentDir.FullName) 33 | { 34 | logger.LogError("LipUI directory cannot be the same as the current directory"); 35 | logger.LogError("lipuiDir: {lipuiDir.FullName} currentDir: {currentDir.FullName}", lipuiDir.FullName, currentDir.FullName); 36 | return; 37 | } 38 | 39 | var lipuiExeFile = new FileInfo(Path.Combine(lipuiDir.FullName, "LipUI.exe")); 40 | if (lipuiExeFile.Exists is false) 41 | { 42 | logger.LogError("LipUI.exe file not found"); 43 | return; 44 | } 45 | 46 | foreach (var dir in currentDir.EnumerateDirectories()) 47 | { 48 | var temp = Path.Combine(lipuiDir.FullName, dir.Name); 49 | if (Directory.Exists(temp)) 50 | Directory.Delete(temp, true); 51 | Directory.Move(dir.FullName, temp); 52 | logger.LogInformation("Moved {dir} to {lipuiDir}", dir.Name, lipuiDir.FullName); 53 | } 54 | 55 | foreach (var file in currentDir.EnumerateFiles()) 56 | { 57 | File.Move(file.FullName, Path.Combine(lipuiDir.FullName, file.Name), true); 58 | logger.LogInformation("Moved {file} to {lipuiWorkingDir}", file.Name, lipuiDir.FullName); 59 | } 60 | 61 | Task.Delay(TimeSpan.FromSeconds(2)).Wait(); 62 | 63 | Process.Start(lipuiExeFile.FullName); 64 | Environment.Exit(0); 65 | 66 | }, lipuiDirOption, lipuiPid); 67 | 68 | rootCommand.Invoke(args); -------------------------------------------------------------------------------- /src/LipUI/App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/LipUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using Microsoft.UI.Xaml; 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI 7 | { 8 | /// 9 | /// Provides application-specific behavior to supplement the default Application class. 10 | /// 11 | public partial class App : Application 12 | { 13 | /// 14 | /// Initializes the singleton application object. This is the first line of authored code 15 | /// executed, and as such is the logical equivalent of main() or WinMain(). 16 | /// 17 | public App() 18 | { 19 | InitializeComponent(); 20 | 21 | Current.RequestedTheme = InternalServices.ApplicationTheme = Main.Config.PersonalizationSettings.ColorTheme switch 22 | { 23 | ElementTheme.Dark => ApplicationTheme.Dark, 24 | ElementTheme.Light => ApplicationTheme.Light, 25 | ElementTheme.Default or _ => Current.RequestedTheme 26 | }; 27 | } 28 | 29 | /// 30 | /// Invoked when the application is launched. 31 | /// 32 | /// Details about the launch request and process. 33 | protected override void OnLaunched(LaunchActivatedEventArgs args) 34 | { 35 | 36 | m_window = new MainWindow(); 37 | m_window.Activate(); 38 | 39 | UnhandledException += App_UnhandledException; 40 | } 41 | 42 | private async void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) 43 | { 44 | static async ValueTask _exp(Exception ex) 45 | { 46 | await InternalServices.ShowInfoBarAsync(ex, containsStacktrace: true); 47 | if (ex.InnerException is not null) 48 | await _exp(ex.InnerException); 49 | } 50 | 51 | if (InternalServices.MainWindow is not null) 52 | { 53 | e.Handled = true; 54 | await _exp(e.Exception); 55 | } 56 | } 57 | 58 | internal Window? m_window; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/LipUI/Assets/Images.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace LipUI.Assets { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Images { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Images() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LipUI.Assets.Images", typeof(Images).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 重写当前线程的 CurrentUICulture 属性,对 51 | /// 使用此强类型资源类的所有资源查找执行重写。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找 System.Byte[] 类型的本地化资源。 65 | /// 66 | internal static byte[] applicationIcon_1024 { 67 | get { 68 | object obj = ResourceManager.GetObject("applicationIcon_1024", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// 查找 System.Byte[] 类型的本地化资源。 75 | /// 76 | internal static byte[] applicationIcon_256 { 77 | get { 78 | object obj = ResourceManager.GetObject("applicationIcon_256", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// 查找 System.Byte[] 类型的本地化资源。 85 | /// 86 | internal static byte[] glass { 87 | get { 88 | object obj = ResourceManager.GetObject("glass", resourceCulture); 89 | return ((byte[])(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// 查找 System.Byte[] 类型的本地化资源。 95 | /// 96 | internal static byte[] grass_block { 97 | get { 98 | object obj = ResourceManager.GetObject("grass_block", resourceCulture); 99 | return ((byte[])(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// 查找 System.Byte[] 类型的本地化资源。 105 | /// 106 | internal static byte[] netherrack { 107 | get { 108 | object obj = ResourceManager.GetObject("netherrack", resourceCulture); 109 | return ((byte[])(obj)); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/LipUI/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /src/LipUI/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/LipUI/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /src/LipUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /src/LipUI/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/StoreLogo.png -------------------------------------------------------------------------------- /src/LipUI/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /src/LipUI/Assets/WindowIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/WindowIcon.ico -------------------------------------------------------------------------------- /src/LipUI/Assets/applicationIcon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/applicationIcon-1024.png -------------------------------------------------------------------------------- /src/LipUI/Assets/applicationIcon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/applicationIcon-256.png -------------------------------------------------------------------------------- /src/LipUI/Assets/glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/glass.png -------------------------------------------------------------------------------- /src/LipUI/Assets/grass_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/grass_block.png -------------------------------------------------------------------------------- /src/LipUI/Assets/netherrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Assets/netherrack.png -------------------------------------------------------------------------------- /src/LipUI/BuiltInPlugins/DefaultLipProxySetter/DefaultLipProxySetter.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models.Lip; 2 | using LipUI.Models.Plugin; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | 6 | namespace LipUI.BuiltInPlugins.DefaultLipProxySetter; 7 | 8 | [LipUIModule] 9 | internal class DefaultLipProxySetter : IPlugin 10 | { 11 | private static class Strings 12 | { 13 | public const string PluginName = "Lip默认代理配置器"; 14 | public const string ConfirmButtonContent = "是"; 15 | public const string CancelButtonContent = "否"; 16 | public const string InfoBarMessage = "检测到系统默认语言为简体中文,是否设置默认代理?"; 17 | public const string NullLipConsoleInstance = "无法获取LipConsole"; 18 | public const string SettingUp = "设置中"; 19 | public const string Complete = "设置完成"; 20 | } 21 | 22 | public string PluginName => Strings.PluginName; 23 | 24 | public bool DefaultEnabled => System.Globalization.CultureInfo.CurrentCulture.Name.ToLower() is "zh-cn"; 25 | 26 | public Guid Guid => new("4A909307-3571-5020-681D-2491B045A8B5"); 27 | 28 | 29 | private static class DefaultProxies 30 | { 31 | public const string Github_LipInstaller = "https://mirror.ghproxy.com"; 32 | public const string Github_Lip = "https://github.bibk.top"; 33 | public const string GoProxy_Lip = "https://goproxy.cn"; 34 | } 35 | 36 | void IPlugin.OnInitlalize(LipuiServices services) 37 | { 38 | var config = services.GetPluginConfig(this); 39 | 40 | var selfDisabled = config["SelfDisabled"]; 41 | if (selfDisabled.IsNull) 42 | config["SelfDisabled"] = false; 43 | } 44 | 45 | void IPlugin.OnEnable(LipuiServices services) 46 | { 47 | var config = services.GetPluginConfig(this); 48 | if (config["SelfDisabled"].As()) 49 | return; 50 | 51 | config["SelfDisabled"] = true; 52 | 53 | if (System.Globalization.CultureInfo.CurrentCulture.Name.ToLower() is "zh-cn") 54 | { 55 | services.DispatcherQueue.TryEnqueue(async () => 56 | { 57 | var cts = new CancellationTokenSource(); 58 | var confirmButton = new Button() 59 | { 60 | Content = Strings.ConfirmButtonContent, 61 | }; 62 | var cancelButton = new Button() 63 | { 64 | Content = Strings.CancelButtonContent, 65 | }; 66 | 67 | confirmButton.Click += async (sender, _) => 68 | { 69 | services.LipuiConfig.GeneralSettings.GithubProxy = DefaultProxies.Github_LipInstaller; 70 | var lip = await services.CreateLipConsoleAsync((sender as Button)!.XamlRoot, string.Empty); 71 | 72 | if (lip is null) 73 | { 74 | cts.Cancel(); 75 | services.ShowInfoBar( 76 | message: Strings.NullLipConsoleInstance, 77 | severity: InfoBarSeverity.Error); 78 | return; 79 | } 80 | 81 | cts.Cancel(); 82 | 83 | services.ShowInfoBar( 84 | message: Strings.SettingUp, 85 | severity: InfoBarSeverity.Informational, 86 | interval: TimeSpan.FromSeconds(2)); 87 | 88 | await lip.Run(LipCommand.CreateCommand() + LipCommand.Config + $"GitHubMirrorURL {DefaultProxies.Github_Lip}"); 89 | 90 | await lip.Run(LipCommand.CreateCommand() + LipCommand.Config + $"GoModuleProxyURL {DefaultProxies.GoProxy_Lip}"); 91 | 92 | services.ShowInfoBar( 93 | message: Strings.Complete, 94 | severity: InfoBarSeverity.Success, 95 | interval: TimeSpan.FromSeconds(3)); 96 | }; 97 | cancelButton.Click += (_, _) => cts.Cancel(); 98 | 99 | var stackPanel = new StackPanel() 100 | { 101 | Orientation = Orientation.Horizontal, 102 | Children = { confirmButton, cancelButton }, 103 | Margin = new(4), 104 | Spacing = 8, 105 | HorizontalAlignment = HorizontalAlignment.Center, 106 | VerticalAlignment = VerticalAlignment.Center 107 | }; 108 | 109 | await services.ShowInfoBarAsync( 110 | severity: InfoBarSeverity.Warning, 111 | message: Strings.InfoBarMessage, 112 | interval: TimeSpan.FromSeconds(30), 113 | barContent: stackPanel, 114 | cancellationToken: cts.Token); 115 | }); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/LipUI/BuiltInPlugins/LipPanel/LipPanel.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models.Plugin; 2 | using Microsoft.UI; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | using Microsoft.UI.Xaml.Media; 6 | 7 | namespace LipUI.BuiltInPlugins.LipPanel; 8 | 9 | //[LipUIModule] 10 | internal class LipPanel : IHomePageModule 11 | { 12 | public Type PageType => typeof(LipPanelPage); 13 | 14 | public string PluginName => "LipPanel"; 15 | 16 | public bool DefaultEnabled => true; 17 | 18 | public Guid Guid => new("21A13C74-BEC2-41BE-782F-1EF4BD7701AB"); 19 | 20 | FrameworkElement IHomePageModule.IconContent => new SymbolIcon(Symbol.Remote) 21 | { 22 | Height = 32, 23 | Width = 32, 24 | }; 25 | 26 | Brush IHomePageModule.IconBackground => 27 | LipuiServices.ApplicationTheme is ApplicationTheme.Light ? 28 | new SolidColorBrush(Colors.LightGray) : 29 | new SolidColorBrush(Colors.DarkGray); 30 | } 31 | -------------------------------------------------------------------------------- /src/LipUI/BuiltInPlugins/LipPanel/LipPanelPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/LipUI/BuiltInPlugins/LipPanel/LipPanelPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI.BuiltInPlugins.LipPanel 7 | { 8 | /// 9 | /// An empty page that can be used on its own or navigated to within a Frame. 10 | /// 11 | public sealed partial class LipPanelPage : Page 12 | { 13 | public LipPanelPage() 14 | { 15 | this.InitializeComponent(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/LipUI/BuiltInPlugins/LipuiTips/LipuiTips.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models.Plugin; 2 | 3 | namespace LipUI.BuiltInPlugins.LipuiTips; 4 | 5 | //[LipUIModule] 6 | internal class LipuiTips : IPlugin 7 | { 8 | public string PluginName => "Tips"; 9 | 10 | public bool DefaultEnabled => true; 11 | 12 | public Guid Guid => new("9116AABC-6448-9933-DE77-D477EB10B857"); 13 | 14 | void IPlugin.OnEnable(LipuiServices services) 15 | { 16 | } 17 | } -------------------------------------------------------------------------------- /src/LipUI/LipUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0-windows10.0.19041.0 5 | 10.0.17763.0 6 | LipUI 7 | Assets/WindowIcon.ico 8 | app.manifest 9 | x64;arm64 10 | win-x64;win-arm64 11 | enable 12 | enable 13 | true 14 | true 15 | None 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWIndowWin32Invoke.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using WinRT; 3 | using static PInvoke.User32; 4 | 5 | namespace LipUI; 6 | 7 | internal partial class MainWindow 8 | { 9 | private readonly int MinWidth = 800; 10 | private readonly int MinHeight = 450; 11 | 12 | private NativeMethods.WinProc? newWndProc = null; 13 | private nint oldWndProc = nint.Zero; 14 | 15 | private void SubClassing() 16 | { 17 | var hwnd = this.As().WindowHandle; 18 | if (hwnd == nint.Zero) 19 | { 20 | int error = Marshal.GetLastWin32Error(); 21 | throw new InvalidOperationException($"Failed to get window handler: error code {error}"); 22 | } 23 | 24 | newWndProc = new(NewWindowProc); 25 | 26 | // Here we use the NativeMethods class 👇 27 | oldWndProc = NativeMethods.SetWindowLong(hwnd, WindowLongIndexFlags.GWL_WNDPROC, newWndProc); 28 | if (oldWndProc == nint.Zero) 29 | { 30 | int error = Marshal.GetLastWin32Error(); 31 | throw new InvalidOperationException($"Failed to set GWL_WNDPROC: error code {error}"); 32 | } 33 | } 34 | 35 | private nint NewWindowProc(nint hWnd, WindowMessage Msg, nint wParam, nint lParam) 36 | { 37 | [DllImport("user32.dll")] 38 | static extern nint CallWindowProc(nint lpPrevWndFunc, nint hWnd, WindowMessage Msg, nint wParam, nint lParam); 39 | 40 | switch (Msg) 41 | { 42 | case WindowMessage.WM_GETMINMAXINFO: 43 | var dpi = GetDpiForWindow(hWnd); 44 | float scalingFactor = (float)dpi / 96; 45 | 46 | MINMAXINFO minMaxInfo = Marshal.PtrToStructure(lParam); 47 | minMaxInfo.ptMinTrackSize.x = (int)(MinWidth * scalingFactor); 48 | minMaxInfo.ptMinTrackSize.y = (int)(MinHeight * scalingFactor); 49 | Marshal.StructureToPtr(minMaxInfo, lParam, true); 50 | break; 51 | 52 | } 53 | return CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam); 54 | } 55 | 56 | private static class NativeMethods 57 | { 58 | [StructLayout(LayoutKind.Sequential)] 59 | struct MINMAXINFO 60 | { 61 | public PInvoke.POINT ptReserved; 62 | public PInvoke.POINT ptMaxSize; 63 | public PInvoke.POINT ptMaxPosition; 64 | public PInvoke.POINT ptMinTrackSize; 65 | public PInvoke.POINT ptMaxTrackSize; 66 | } 67 | 68 | 69 | public delegate nint WinProc(nint hWnd, WindowMessage Msg, nint wParam, nint lParam); 70 | 71 | // We have to handle the 32-bit and 64-bit functions separately. 72 | // 'SetWindowLongPtr' is the 64-bit version of 'SetWindowLong', and isn't available in user32.dll for 32-bit processes. 73 | [DllImport("user32.dll", EntryPoint = "SetWindowLong")] 74 | private static extern nint SetWindowLong32(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc); 75 | 76 | [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] 77 | private static extern nint SetWindowLong64(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc); 78 | 79 | public static nint SetWindowLong(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc) 80 | => nint.Size is 4 ? SetWindowLong32(hWnd, nIndex, newProc) : SetWindowLong64(hWnd, nIndex, newProc); 81 | 82 | [ComImport] 83 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 84 | [Guid("EECDBF0E-BAE9-4CB6-A68E-9598E1CB57BB")] 85 | internal interface IWindowNative 86 | { 87 | nint WindowHandle { get; } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 42 | 43 | 48 | 49 | 56 | 57 | 58 | 59 | 68 | 69 | 70 | 71 | 75 | 76 | 80 | 81 | 85 | 86 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 102 | 103 | 104 | 105 | 106 | 107 | 112 | 113 | 114 | 115 | 121 | 122 | 123 | 124 | 126 | 128 | 129 | 130 | 132 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Lip; 3 | using LipUI.Models.Plugin; 4 | using LipUI.Pages.LipExecutionPanel; 5 | using LipUI.Pages.Settings; 6 | using Microsoft.UI.Dispatching; 7 | using Microsoft.UI.Xaml; 8 | using Microsoft.UI.Xaml.Controls; 9 | using Windows.ApplicationModel.DataTransfer; 10 | using Windows.Storage; 11 | using Windows.System; 12 | 13 | // To learn more about WinUI, the WinUI project structure, 14 | // and more about our project templates, see: http://aka.ms/winui-project-info. 15 | 16 | namespace LipUI; 17 | 18 | /// 19 | /// An empty window that can be used on its own or navigated to within a Frame. 20 | /// 21 | internal sealed partial class MainWindow : Window 22 | { 23 | public MainWindow() 24 | { 25 | InitializeComponent(); 26 | 27 | AppWindow.Closing += (_, _) => InternalServices.OnWindowClosed(); 28 | 29 | ExtendsContentIntoTitleBar = true; 30 | SetTitleBar(AppTitleBar); 31 | 32 | SubClassing(); 33 | 34 | AppWindow.Resize(new(1600, 900)); 35 | 36 | InternalServices.MainWindow = this; 37 | 38 | PersonalizationSettingsView.Initialize(Main.Config.PersonalizationSettings); 39 | 40 | enabled = PluginEnabled; 41 | disabled = PluginDisabled; 42 | } 43 | 44 | private async void RootBorder_Drop(object sender, DragEventArgs e) 45 | { 46 | if (e.DataView.Contains(StandardDataFormats.StorageItems) is false) 47 | return; 48 | 49 | foreach (var item in await e.DataView.GetStorageItemsAsync()) 50 | { 51 | if (item is StorageFile file && Path.GetExtension(file.Path) is ".tth") 52 | { 53 | var lip = await Main.CreateLipConsole(RootBorder.XamlRoot); 54 | if (lip is null) 55 | { 56 | InternalServices.ShowInfoBar( 57 | "infobar$error".GetLocalized(), 58 | Main.Config.SelectedServer is null ? 59 | "lipExecution$nullServerPath".GetLocalized() : 60 | "lipExecution$nullLipPath".GetLocalized(), 61 | InfoBarSeverity.Error); 62 | 63 | return; 64 | } 65 | var cmd = LipCommand.CreateCommand() + LipCommand.Install + file.Path; 66 | var info = new List(); 67 | 68 | ContentFrame.Navigate( 69 | typeof(LipExecutionPanelPage), 70 | new LipExecutionPanelPage.NavigationArgs(file.Path, info, lip, cmd)); 71 | } 72 | else 73 | { 74 | await InternalServices.ShowInfoBarAsync( 75 | "infobar$error".GetLocalized(), 76 | @$"{item.Name} is not a '.tth' file.", 77 | InfoBarSeverity.Error); 78 | } 79 | } 80 | } 81 | 82 | private void RootBorder_DragOver(object sender, DragEventArgs e) 83 | { 84 | e.AcceptedOperation = DataPackageOperation.Move; 85 | e.DragUIOverride.IsCaptionVisible = false; 86 | e.DragUIOverride.IsGlyphVisible = false; 87 | } 88 | 89 | private void RootBorder_Loaded(object sender, RoutedEventArgs e) 90 | { 91 | Task.Run(PluginSystem.LoadAsync); 92 | 93 | var timer = DispatcherQueue.CreateTimer(); 94 | timer.Interval = TimeSpan.FromSeconds(60); 95 | timer.IsRepeating = true; 96 | timer.Tick += (_sender, e) => Task.Run(Main.SaveConfig); 97 | timer.Start(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWindowInfoBarController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | 4 | namespace LipUI; 5 | 6 | internal partial class MainWindow 7 | { 8 | private record struct InfoBarTask( 9 | ManualResetEvent Mre, 10 | string? Title, 11 | string? Message, 12 | InfoBarSeverity Severity, 13 | TimeSpan Interval, 14 | UIElement? Content, 15 | Action? Completed, 16 | CancellationToken CancellationToken); 17 | 18 | private readonly Queue InfoBarTaskQueue = new(); 19 | private bool IsRunning = false; 20 | 21 | private void ShowInfoBar(InfoBarTask task) 22 | { 23 | DispatcherQueue.TryEnqueue(() => 24 | { 25 | GlobalInfoBar.Title = task.Title; 26 | GlobalInfoBar.Message = task.Message; 27 | GlobalInfoBar.Severity = task.Severity; 28 | GlobalInfoBar.IsClosable = false; 29 | GlobalInfoBar.Content = task.Content; 30 | GlobalInfoBar.IsOpen = true; 31 | 32 | void set(object? sender, object e) 33 | { 34 | InfoBarPopInStoryboard.Completed -= set; 35 | task.Mre.Set(); 36 | } 37 | InfoBarPopInStoryboard.Completed += set; 38 | 39 | InfoBarPopInStoryboard.Begin(); 40 | }); 41 | } 42 | 43 | private void CloseInfoBar(ManualResetEvent mre) 44 | { 45 | InfoBarPopOutStoryboard.Begin(); 46 | 47 | void task(object? sender, object e) 48 | { 49 | InfoBarPopOutStoryboard.Completed -= task; 50 | GlobalInfoBar.IsOpen = false; 51 | mre.Set(); 52 | } 53 | InfoBarPopOutStoryboard.Completed += task; 54 | } 55 | 56 | private void StartShowInfoBarLoop() 57 | => Task.Run(async () => 58 | { 59 | InfoBarTask task; 60 | bool dequeued; 61 | 62 | IsRunning = true; 63 | LOOP: 64 | lock (InfoBarTaskQueue) 65 | { 66 | dequeued = InfoBarTaskQueue.TryDequeue(out task); 67 | } 68 | 69 | if (dequeued) 70 | { 71 | DispatcherQueue.TryEnqueue(() => ShowInfoBar(task)); 72 | 73 | try 74 | { 75 | await Task.Delay(task.Interval, task.CancellationToken); 76 | } 77 | catch (TaskCanceledException) { } 78 | 79 | task.Mre.Reset(); 80 | DispatcherQueue.TryEnqueue(() => CloseInfoBar(task.Mre)); 81 | task.Mre.WaitOne(); 82 | task.Mre.Dispose(); 83 | task.Completed?.Invoke(); 84 | 85 | goto LOOP; 86 | } 87 | IsRunning = false; 88 | }); 89 | 90 | internal void ShowInfoBar( 91 | string? title, 92 | string? message, 93 | InfoBarSeverity severity, 94 | TimeSpan interval = default, 95 | UIElement? barContent = null, 96 | Action? completed = null, 97 | CancellationToken cancellationToken = default) 98 | { 99 | Task.Run(() => 100 | { 101 | var mre = new ManualResetEvent(false); 102 | InfoBarTaskQueue.Enqueue(new(mre, title, message, severity, 103 | interval, barContent, completed, cancellationToken)); 104 | if (IsRunning is false) 105 | StartShowInfoBarLoop(); 106 | mre.WaitOne(); 107 | }, CancellationToken.None); 108 | } 109 | 110 | internal async ValueTask ShowInfoBarAsync(string? title, 111 | string? message, 112 | InfoBarSeverity severity, 113 | TimeSpan interval = default, 114 | UIElement? barContent = null, 115 | CancellationToken cancellationToken = default) 116 | { 117 | await Task.Run(() => 118 | { 119 | var mre = new ManualResetEvent(false); 120 | InfoBarTaskQueue.Enqueue(new(mre, title, message, severity, 121 | interval, barContent, null, cancellationToken)); 122 | if (IsRunning is false) 123 | StartShowInfoBarLoop(); 124 | mre.WaitOne(); 125 | }, CancellationToken.None); 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWindowNavigationView.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Plugin; 3 | using LipUI.Pages.Home; 4 | using LipUI.Pages.Index; 5 | using LipUI.Pages.LocalPackage; 6 | using LipUI.Pages.Settings; 7 | using LipUI.Pages.ToothPack; 8 | using Microsoft.UI.Xaml; 9 | using Microsoft.UI.Xaml.Controls; 10 | using Microsoft.UI.Xaml.Media.Animation; 11 | using Microsoft.UI.Xaml.Navigation; 12 | using System.ComponentModel; 13 | 14 | namespace LipUI; 15 | 16 | internal partial class MainWindow 17 | { 18 | private record NavigationPages(string HomePage, string IndexPage, string ModuleManagerPage, string LocalPackagePage, string SettingsPage); 19 | 20 | 21 | private readonly NavigationPages NavigationPage = new( 22 | typeof(HomePage).FullName!, 23 | typeof(IndexPage).FullName!, 24 | typeof(ModuleManagerPage).FullName!, 25 | typeof(LocalPackagePage).FullName!, 26 | typeof(SettingsPage).FullName!); 27 | 28 | private readonly HashSet NavigationPageTypes = [ 29 | typeof(HomePage), 30 | typeof(IndexPage), 31 | typeof(ModuleManagerPage), 32 | typeof(LocalPackagePage), 33 | typeof(SettingsPage) 34 | ]; 35 | 36 | 37 | 38 | private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) 39 | { 40 | throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 41 | } 42 | 43 | private void NavView_Loading(FrameworkElement sender, object args) 44 | { 45 | sender.Resources["NavigationViewContentBackground"] 46 | = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBackground; 47 | sender.Resources["NavigationViewContentGridBorderBrush"] 48 | = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBorder; 49 | 50 | PersonalizationSettingsView.MyRes.PropertyChanged += (object? _sender, PropertyChangedEventArgs e) => 51 | { 52 | switch (e.PropertyName) 53 | { 54 | case nameof(GlobalResources.ApplicationNavigationViewContentBackground): 55 | sender.Resources["NavigationViewContentBackground"] 56 | = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBackground; 57 | break; 58 | 59 | case nameof(GlobalResources.ApplicationNavigationViewContentBorder): 60 | sender.Resources["NavigationViewContentGridBorderBrush"] 61 | = PersonalizationSettingsView.MyRes.ApplicationNavigationViewContentBorder; 62 | break; 63 | 64 | case nameof(GlobalResources.ApplicationBackground): 65 | RootBorder.Background = PersonalizationSettingsView.MyRes.ApplicationBackground; 66 | break; 67 | } 68 | }; 69 | } 70 | 71 | private void NavView_Loaded(object sender, RoutedEventArgs e) 72 | { 73 | // You can also add items in code. 74 | 75 | // Add handler for ContentFrame navigation. 76 | ContentFrame.Navigated += On_Navigated; 77 | 78 | // NavView doesn't load any page by default, so load home page. 79 | NavView.SelectedItem = NavView.MenuItems[0]; 80 | // If navigation occurs on SelectionChanged, this isn't needed. 81 | // Because we use ItemInvoked to navigate, we need to call Navigate 82 | // here to load the home page. 83 | NavView_Navigate(typeof(HomePage), null, new EntranceNavigationTransitionInfo()); 84 | } 85 | 86 | private async void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) 87 | { 88 | 89 | if (args.IsSettingsInvoked == true) 90 | { 91 | NavView_Navigate(typeof(SettingsPage), null, args.RecommendedNavigationTransitionInfo); 92 | } 93 | else if (args.InvokedItemContainer != null) 94 | { 95 | if (args.InvokedItemContainer.Tag is null) 96 | return; 97 | 98 | if (args.InvokedItemContainer.Tag is IUIPlugin plugin) 99 | { 100 | try 101 | { 102 | NavView_Navigate( 103 | plugin.PageType, plugin.PageParameterRequsted(), args.RecommendedNavigationTransitionInfo); 104 | } 105 | catch (Exception ex) { await InternalServices.ShowInfoBarAsync(ex); } 106 | } 107 | else 108 | { 109 | NavView_Navigate( 110 | Type.GetType(args.InvokedItemContainer.Tag.ToString()!)!, 111 | null, 112 | args.RecommendedNavigationTransitionInfo); 113 | } 114 | } 115 | } 116 | 117 | private void NavView_Navigate( 118 | Type navPageType, 119 | object? parameter, 120 | NavigationTransitionInfo transitionInfo) 121 | { 122 | Type preNavPageType = ContentFrame.CurrentSourcePageType; 123 | 124 | if (navPageType is not null && !Equals(preNavPageType, navPageType)) 125 | { 126 | ContentFrame.Navigate(navPageType, parameter, transitionInfo); 127 | } 128 | } 129 | 130 | private void NavView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) 131 | { 132 | TryGoBack(); 133 | } 134 | 135 | private bool TryGoBack() 136 | { 137 | if (!ContentFrame.CanGoBack) 138 | return false; 139 | 140 | if (NavView.IsPaneOpen && 141 | (NavView.DisplayMode is NavigationViewDisplayMode.Compact || 142 | NavView.DisplayMode is NavigationViewDisplayMode.Minimal)) 143 | return false; 144 | 145 | ContentFrame.TryGoBack(); 146 | return true; 147 | } 148 | 149 | private void On_Navigated(object sender, NavigationEventArgs e) 150 | { 151 | NavView.IsBackEnabled = ContentFrame.CanGoBack; 152 | 153 | if (ContentFrame.SourcePageType == typeof(SettingsPage)) 154 | { 155 | NavView.SelectedItem = (NavigationViewItem)NavView.SettingsItem; 156 | } 157 | else if (ContentFrame.SourcePageType != null) 158 | { 159 | 160 | if (NavigationPageTypes.Contains(ContentFrame.SourcePageType)) 161 | NavView.SelectedItem = NavView.MenuItems 162 | .OfType() 163 | .First(i => i.Tag.Equals(ContentFrame.SourcePageType.FullName!.ToString())); 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/LipUI/MainWindow/MainWindowPluginsHandler.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Plugin; 3 | using Microsoft.UI.Xaml.Controls; 4 | 5 | namespace LipUI; 6 | 7 | internal partial class MainWindow 8 | { 9 | public static void InitEventHandlers() 10 | { 11 | PluginSystem.PluginEnabled += PluginSystem_PluginEnabled; 12 | PluginSystem.PluginDisabled += PluginSystem_PluginDisabled; 13 | } 14 | 15 | private static readonly HashSet enabledModules = []; 16 | private static void PluginSystem_PluginEnabled(IPlugin obj) 17 | { 18 | lock (enabledModules) 19 | { 20 | if (obj is IUIPlugin uiPlugin) 21 | { 22 | enabledModules.Add(uiPlugin); 23 | enabled?.Invoke(uiPlugin); 24 | } 25 | } 26 | } 27 | 28 | private static void PluginSystem_PluginDisabled(IPlugin obj) 29 | { 30 | lock (enabledModules) 31 | { 32 | if (obj is IUIPlugin uiPlugin) 33 | { 34 | enabledModules.Remove(uiPlugin); 35 | disabled?.Invoke(uiPlugin); 36 | } 37 | } 38 | } 39 | 40 | private static Action? enabled; 41 | private static Action? disabled; 42 | 43 | private readonly object _lock = new(); 44 | private uint navViewBarEnabledPluginsCount; 45 | 46 | private readonly Dictionary views = []; 47 | 48 | private void PluginEnabled(IUIPlugin plugin) 49 | { 50 | DispatcherQueue.TryEnqueue(async () => 51 | { 52 | try 53 | { 54 | lock (_lock) 55 | { 56 | IList items; 57 | 58 | //bool addIntoNavViewBar = false; 59 | //if (navViewBarEnabledPluginsCount < 4) 60 | //{ 61 | items = NavView.MenuItems; 62 | //addIntoNavViewBar = true; 63 | //} 64 | //else 65 | // items = NavigationViewItem_More.MenuItems; 66 | 67 | var view = new NavigationViewItem() 68 | { 69 | Icon = plugin.NavigatonBarIcon, 70 | Content = plugin.NavigationBarContent, 71 | Tag = plugin 72 | }; 73 | views.Add(plugin, view); 74 | 75 | items.Add(view); 76 | 77 | //if (addIntoNavViewBar) 78 | navViewBarEnabledPluginsCount++; 79 | 80 | } 81 | } 82 | catch (Exception ex) 83 | { 84 | await InternalServices.ShowInfoBarAsync(ex); 85 | } 86 | }); 87 | } 88 | 89 | private void PluginDisabled(IUIPlugin plugin) 90 | { 91 | DispatcherQueue.TryEnqueue(async () => 92 | { 93 | try 94 | { 95 | lock (_lock) 96 | { 97 | if (NavView.MenuItems.Remove(views[plugin])) 98 | { 99 | navViewBarEnabledPluginsCount--; 100 | } 101 | //else 102 | //{ 103 | // NavigationViewItem_More.MenuItems.Remove(views[plugin]); 104 | //} 105 | 106 | views.Remove(plugin); 107 | } 108 | } 109 | catch (Exception ex) 110 | { 111 | await InternalServices.ShowInfoBarAsync(ex); 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/LipUI/Models/GlobalIcons.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Assets; 2 | using Microsoft.UI.Xaml.Media.Imaging; 3 | 4 | 5 | namespace LipUI.Models; 6 | 7 | internal static class GlobalIcons 8 | { 9 | public static readonly BitmapImage GrassBlock = InternalServices.CreateImageFromBytes(Images.grass_block); 10 | public static readonly BitmapImage Netherrack = InternalServices.CreateImageFromBytes(Images.netherrack); 11 | public static readonly BitmapImage Glass = InternalServices.CreateImageFromBytes(Images.glass); 12 | } 13 | -------------------------------------------------------------------------------- /src/LipUI/Models/InternalServices.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Media.Imaging; 4 | using Microsoft.Windows.ApplicationModel.Resources; 5 | using Windows.UI; 6 | 7 | namespace LipUI.Models; 8 | 9 | internal static class ResourceExtensions 10 | { 11 | private static readonly ResourceLoader _resourceLoader = new(); 12 | 13 | public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey); 14 | } 15 | 16 | internal static class FrameExtensions 17 | { 18 | public static bool TryGoBack(this Frame frame) 19 | { 20 | if (frame.CanGoBack) 21 | { 22 | frame.GoBack(); 23 | return true; 24 | } 25 | return false; 26 | } 27 | } 28 | 29 | internal static class ColorExtensions 30 | { 31 | public static Color Invert(this in Color color) 32 | => Color.FromArgb(color.A, (byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B)); 33 | } 34 | 35 | internal static class InternalServices 36 | { 37 | public static BitmapImage CreateImageFromBytes(byte[] bytes, Action? onInit = null) 38 | { 39 | var image = new BitmapImage(); 40 | onInit?.Invoke(image); 41 | using var stream = new MemoryStream(bytes); 42 | image.SetSource(stream.AsRandomAccessStream()); 43 | return image; 44 | } 45 | 46 | internal static MainWindow? MainWindow { get; set; } 47 | 48 | public static async ValueTask ShowInfoBarAsync( 49 | string? title = null, 50 | string? message = null, 51 | InfoBarSeverity severity = InfoBarSeverity.Informational, 52 | TimeSpan interval = default, 53 | UIElement? barContent = null, 54 | CancellationToken cancellationToken = default) 55 | { 56 | if (interval == default) 57 | interval = TimeSpan.FromSeconds(3); 58 | 59 | if (MainWindow is not null) 60 | await MainWindow.ShowInfoBarAsync(title, message, severity, interval, barContent, cancellationToken); 61 | } 62 | 63 | public static async ValueTask ShowInfoBarAsync( 64 | Exception ex, 65 | bool containsStacktrace = false, 66 | InfoBarSeverity severity = InfoBarSeverity.Error, 67 | TimeSpan interval = default, 68 | UIElement? barContent = null, 69 | CancellationToken cancellationToken = default) 70 | { 71 | if (interval == default) 72 | interval = TimeSpan.FromSeconds(5); 73 | 74 | await ShowInfoBarAsync( 75 | ex.GetType().Name, 76 | containsStacktrace ? ex.ToString() : ex.Message, 77 | severity, 78 | interval, 79 | barContent, 80 | cancellationToken); 81 | } 82 | 83 | public static void ShowInfoBar( 84 | string? title = null, 85 | string? message = null, 86 | InfoBarSeverity severity = InfoBarSeverity.Informational, 87 | TimeSpan interval = default, 88 | UIElement? barContent = null, 89 | Action? completed = null, 90 | CancellationToken cancellationToken = default) 91 | { 92 | if (interval == default) 93 | interval = TimeSpan.FromSeconds(3); 94 | 95 | MainWindow?.ShowInfoBar(title, message, severity, interval, barContent, completed, cancellationToken); 96 | } 97 | 98 | public static void ShowInfoBar( 99 | Exception ex, 100 | bool containsStacktrace = false, 101 | InfoBarSeverity severity = InfoBarSeverity.Error, 102 | TimeSpan interval = default, 103 | UIElement? barContent = null, 104 | Action? completed = null, 105 | CancellationToken cancellationToken = default) 106 | { 107 | if (interval == default) 108 | interval = TimeSpan.FromSeconds(5); 109 | 110 | ShowInfoBar( 111 | ex.GetType().Name, 112 | containsStacktrace ? ex.ToString() : ex.Message, 113 | severity, 114 | interval, 115 | barContent, 116 | completed, 117 | cancellationToken); 118 | } 119 | 120 | public static event Action? WindowClosed; 121 | 122 | internal static void OnWindowClosed() => WindowClosed?.Invoke(); 123 | 124 | public static ApplicationTheme ApplicationTheme { get; internal set; } 125 | 126 | public static HttpClient HttpClient { get; } = new HttpClient( 127 | new HttpClientHandler() { ClientCertificateOptions = ClientCertificateOption.Automatic }) 128 | { 129 | Timeout = TimeSpan.FromSeconds(5), 130 | DefaultRequestHeaders = { ExpectContinue = false } 131 | }; 132 | } 133 | -------------------------------------------------------------------------------- /src/LipUI/Models/Lip/LipCommand.cs: -------------------------------------------------------------------------------- 1 | namespace LipUI.Models.Lip; 2 | 3 | public class LipCommand 4 | { 5 | public readonly string Command; 6 | public readonly LipCommand? Parent; 7 | 8 | private LipCommand() => throw new NotSupportedException(); 9 | 10 | private LipCommand(string command, LipCommand? parent = null) 11 | { 12 | Command = command; 13 | Parent = parent; 14 | } 15 | 16 | 17 | 18 | public static readonly LipCommand Lip = new("lip"); 19 | 20 | public static readonly LipCommand AutoRemove = new("autoremove", Lip); 21 | 22 | public static readonly LipCommand Cache = new("cache", Lip); 23 | 24 | public static readonly LipCommand Purge = new("purge", Cache); 25 | 26 | public static readonly LipCommand Install = new("install", Lip); 27 | 28 | public static readonly LipCommand List = new("list", List); 29 | 30 | public static readonly LipCommand Show = new("show", Lip); 31 | 32 | public static readonly LipCommand Tooth = new("tooth", Lip); 33 | 34 | public static readonly LipCommand Init = new("init", Tooth); 35 | 36 | public static readonly LipCommand Pack = new("pack", Tooth); 37 | 38 | public static readonly LipCommand Uninstall = new("uninstall", Lip); 39 | 40 | public static readonly LipCommand Config = new("config", Lip); 41 | 42 | public override string ToString() => Command; 43 | 44 | public static explicit operator string(LipCommand command) => command.Command; 45 | 46 | public static bool operator ==(LipCommand left, LipCommand right) 47 | => left.Equals(right); 48 | 49 | public static bool operator !=(LipCommand left, LipCommand right) 50 | => !left.Equals(right); 51 | 52 | public static LipCommandContext CreateCommand() => new(); 53 | 54 | public override bool Equals(object? obj) 55 | { 56 | if (ReferenceEquals(this, obj)) 57 | { 58 | return true; 59 | } 60 | 61 | if (obj is null) 62 | { 63 | return false; 64 | } 65 | 66 | if (obj is LipCommand command) 67 | return Command == command.Command; 68 | 69 | return false; 70 | } 71 | 72 | public override int GetHashCode() => Command.GetHashCode(); 73 | } 74 | -------------------------------------------------------------------------------- /src/LipUI/Models/Lip/LipCommandContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace LipUI.Models.Lip; 4 | 5 | public class LipCommandSyntaxException : Exception 6 | { 7 | public LipCommandSyntaxException(string? message) : base(message) 8 | { 9 | } 10 | } 11 | 12 | public class LipCommandContext 13 | { 14 | private readonly List commands = []; 15 | private readonly List options = []; 16 | 17 | private LipCommand? current; 18 | private string? parameter; 19 | 20 | public LipCommandContext() { } 21 | 22 | public LipCommandContext Append(LipCommand command) 23 | { 24 | if (current is null || (command.Parent is not null && command.Parent == current)) 25 | { 26 | commands.Add(command); 27 | current = command; 28 | return this; 29 | } 30 | 31 | throw new LipCommandSyntaxException(this); 32 | } 33 | 34 | public LipCommandContext Append(LipCommandOption option) 35 | { 36 | if (current is not null && option.AvailableCommands.First(c => c == current) is not null) 37 | { 38 | options.Add(option); 39 | return this; 40 | } 41 | 42 | throw new LipCommandSyntaxException(this); 43 | } 44 | 45 | public LipCommandContext Commands(params LipCommand[] commands) 46 | { 47 | foreach (var option in commands) Append(option); 48 | return this; 49 | } 50 | 51 | public LipCommandContext Options(params LipCommandOption[] options) 52 | { 53 | foreach (var option in options) Append(option); 54 | return this; 55 | } 56 | 57 | public LipCommandContext Parameter(string param) 58 | { 59 | parameter = param; 60 | return this; 61 | } 62 | 63 | public static LipCommandContext operator +(LipCommandContext context, LipCommand command) 64 | => context.Append(command); 65 | 66 | public static LipCommandContext operator +(LipCommandContext context, LipCommandOption option) 67 | => context.Append(option); 68 | 69 | public static LipCommandContext operator +(LipCommandContext context, string param) 70 | => context.Parameter(param); 71 | 72 | public override string ToString() 73 | { 74 | var bulider = new StringBuilder(); 75 | foreach (var cmd in commands) 76 | bulider.Append($"{cmd} "); 77 | foreach (var option in options) 78 | bulider.Append($"{option} "); 79 | if (parameter is not null) 80 | bulider.Append(parameter); 81 | return bulider.ToString().Trim(); 82 | } 83 | 84 | public static implicit operator string(LipCommandContext command) 85 | => command.ToString(); 86 | } 87 | -------------------------------------------------------------------------------- /src/LipUI/Models/Lip/LipCommandOption.cs: -------------------------------------------------------------------------------- 1 | using static LipUI.Models.Lip.LipCommand; 2 | 3 | namespace LipUI.Models.Lip; 4 | 5 | public class LipCommandOption 6 | { 7 | public readonly string? Abbreviation; 8 | public readonly string Option; 9 | public readonly LipCommand[] AvailableCommands; 10 | 11 | private LipCommandOption() => throw new NotSupportedException(); 12 | 13 | public LipCommandOption(string? abbreviation, string option, params LipCommand[] availableCommands) 14 | { 15 | Abbreviation = abbreviation; 16 | Option = option; 17 | AvailableCommands = availableCommands; 18 | } 19 | 20 | public static readonly LipCommandOption Help 21 | = new("h", "help", LipCommand.Lip, AutoRemove, Cache, Purge, Install, List, Show, Tooth, Init, Pack, Uninstall, LipCommand.Config); 22 | 23 | public static readonly LipCommandOption Version 24 | = new("V", "version", LipCommand.Lip); 25 | 26 | public static readonly LipCommandOption Verbose 27 | = new("v", "verbose", LipCommand.Lip); 28 | 29 | public static readonly LipCommandOption Quiet 30 | = new("q", "quiet", LipCommand.Lip); 31 | 32 | public static readonly LipCommandOption Yes 33 | = new("y", "yes", AutoRemove, Install, Uninstall); 34 | 35 | public static readonly LipCommandOption Upgrade 36 | = new(null, "upgrade", Install); 37 | 38 | public static readonly LipCommandOption ForceReinstall 39 | = new(null, "force-reinstall", Install); 40 | 41 | public static readonly LipCommandOption NoDependencies 42 | = new(null, "no-dependencies", Install); 43 | 44 | public static readonly LipCommandOption Upgradable 45 | = new(null, "upgradable", List); 46 | 47 | public static readonly LipCommandOption Json 48 | = new(null, "json", List, Show); 49 | 50 | public static readonly LipCommandOption Available 51 | = new(null, "available", Show); 52 | 53 | public static readonly LipCommandOption KeepPossession 54 | = new(null, "keep-possession", Uninstall); 55 | 56 | public override string ToString() 57 | { 58 | if (Abbreviation is not null) 59 | return $"-{Abbreviation}"; 60 | else 61 | return $"--{Option}"; 62 | } 63 | 64 | public static explicit operator string(LipCommandOption option) => option.ToString(); 65 | } 66 | -------------------------------------------------------------------------------- /src/LipUI/Models/Lip/LipConsole.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | 4 | namespace LipUI.Models.Lip; 5 | 6 | public class LipConsole 7 | { 8 | public LipConsole(string executablePath, string workingDir) 9 | { 10 | ExecutablePath = executablePath; 11 | WorkingPath = workingDir; 12 | } 13 | public string ExecutablePath { get; } 14 | public string WorkingPath { get; } 15 | 16 | public event Action? OutputError; 17 | public event Action? Output; 18 | 19 | public async ValueTask Run(string command, Action? getInstance = null, CancellationToken cancellationToken = default) 20 | { 21 | var ins = new LipConsoleInstance( 22 | ExecutablePath, 23 | WorkingPath, 24 | command, 25 | cancellationToken, 26 | output => 27 | { 28 | Output?.Invoke(output); 29 | }, 30 | error => 31 | { 32 | OutputError?.Invoke(error); 33 | }, 34 | out Process? process); 35 | getInstance?.Invoke(ins); 36 | 37 | while (ins.HasExited is false) await Task.Delay(100, cancellationToken); 38 | cancellationToken.ThrowIfCancellationRequested(); 39 | 40 | return ins.ExitCode; 41 | } 42 | 43 | public async ValueTask Run(LipCommandContext command, Action? getInstance = null, CancellationToken cancellationToken = default) 44 | => await Run(command.ToString(), getInstance, cancellationToken); 45 | 46 | public async ValueTask RunAndGetString(string command, Action? getInstance = null, CancellationToken cancellationToken = default) 47 | { 48 | var builder = new StringBuilder(); 49 | var ins = new LipConsoleInstance( 50 | ExecutablePath, 51 | WorkingPath, 52 | command, 53 | cancellationToken, 54 | output => 55 | { 56 | Output?.Invoke(output); 57 | builder.AppendLine(output); 58 | }, 59 | error => 60 | { 61 | OutputError?.Invoke(error); 62 | builder.AppendLine(error); 63 | }, 64 | out Process? process); 65 | getInstance?.Invoke(ins); 66 | 67 | while (ins.HasExited is false) 68 | { 69 | try { await Task.Delay(100, cancellationToken); } catch { } 70 | ins.KillIfCanceled(); 71 | } 72 | cancellationToken.ThrowIfCancellationRequested(); 73 | 74 | return builder.ToString(); 75 | } 76 | 77 | public async ValueTask RunAndGetString(LipCommandContext command, Action? getInstance = null, CancellationToken cancellationToken = default) 78 | => await RunAndGetString(command.ToString(), getInstance, cancellationToken); 79 | } 80 | -------------------------------------------------------------------------------- /src/LipUI/Models/Main.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models.Lip; 2 | using LipUI.Pages.LipExecutionPanel; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | using System.Diagnostics; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Text.Json; 8 | 9 | namespace LipUI.Models; 10 | 11 | internal static class Main 12 | { 13 | static Main() => Initialize(); 14 | 15 | 16 | private static Config? config; 17 | 18 | public static Config Config 19 | { 20 | get => config ?? throw new NullReferenceException(); 21 | private set 22 | { 23 | if (config is not null) 24 | config.PropertyChanged -= ConfigPropertyChanged; 25 | 26 | config = value; 27 | config!.PropertyChanged += ConfigPropertyChanged; 28 | } 29 | } 30 | 31 | public static string WorkingDirectory { get; private set; } 32 | 33 | public static string ProgramDirectory { get; private set; } 34 | 35 | public static bool ColorsFirstInitSign = false; 36 | 37 | 38 | [MemberNotNull(nameof(Config), nameof(WorkingDirectory), nameof(ProgramDirectory))] 39 | private static void Initialize() 40 | { 41 | InitializeWorkingDir(); 42 | InitializeConfig(); 43 | InternalServices.WindowClosed += SaveConfig; 44 | AutoUpdate(); 45 | } 46 | 47 | private static void AutoUpdate() 48 | { 49 | var autoupdateDir = new DirectoryInfo(Path.Combine(ProgramDirectory, ".autoupdate")); 50 | if (autoupdateDir.Exists is false) 51 | return; 52 | 53 | if (autoupdateDir.EnumerateFiles().Count() is 0) 54 | { 55 | autoupdateDir.Delete(); 56 | return; 57 | } 58 | 59 | SaveConfig(); 60 | Process.Start("CMD.exe", $"/c {Path.Combine(autoupdateDir.FullName, "AutoUpdate.exe")} --lipui-dir {ProgramDirectory} --lipui-pid {Environment.ProcessId}"); 61 | } 62 | 63 | [MemberNotNull(nameof(WorkingDirectory), nameof(ProgramDirectory))] 64 | private static void InitializeWorkingDir() 65 | { 66 | var currentDir = new FileInfo(Environment.ProcessPath!).Directory!.FullName; 67 | 68 | ProgramDirectory = currentDir; 69 | 70 | var path = Path.Combine(currentDir, DefaultSettings.DataDirectory); 71 | if (Directory.Exists(path) is false) 72 | { 73 | Directory.CreateDirectory(path); 74 | } 75 | WorkingDirectory = path; 76 | } 77 | 78 | [MemberNotNull(nameof(Config))] 79 | private static void InitializeConfig() 80 | { 81 | var path = Path.Combine(WorkingDirectory, DefaultSettings.ConfigFileName); 82 | if (File.Exists(path)) 83 | { 84 | var str = File.ReadAllText(path); 85 | Config = JsonSerializer.Deserialize(str) ?? throw new NullReferenceException(); 86 | } 87 | else 88 | { 89 | Config = new Config(); 90 | Config.PersonalizationSettings.ResetColors = true; 91 | } 92 | } 93 | 94 | public static async ValueTask CreateLipConsole(XamlRoot xamlRoot, string? workingDir = null) 95 | { 96 | var (success, path) = await TryGetLipConsolePathAsync(xamlRoot); 97 | if (success is false) 98 | return null; 99 | 100 | var server = Config.SelectedServer; 101 | if (server is null) 102 | return null; 103 | 104 | return new LipConsole(path!, workingDir is null ? server.WorkingDirectory : workingDir); 105 | } 106 | 107 | public static async ValueTask<(bool, string)> TryGetLipConsolePathAsync(XamlRoot xamlRoot) 108 | { 109 | if (File.Exists(Config.GeneralSettings.LipPath)) 110 | return (true, Config.GeneralSettings.LipPath); 111 | else 112 | { 113 | if (Path.GetDirectoryName(Config.GeneralSettings.LipPath) != WorkingDirectory) 114 | return (false, string.Empty); 115 | 116 | var dialog = new ContentDialog() 117 | { 118 | XamlRoot = xamlRoot, 119 | Content = new LipInstallerView() 120 | }; 121 | 122 | await dialog.ShowAsync(); 123 | 124 | 125 | return File.Exists(Config.GeneralSettings.LipPath) ? 126 | (true, Config.GeneralSettings.LipPath) : 127 | (false, string.Empty); 128 | } 129 | } 130 | 131 | private static bool configChanged = false; 132 | private static uint configEditCount = 0; 133 | private static readonly object _lock = new(); 134 | private static bool saving = false; 135 | private static bool saveRequesting = false; 136 | 137 | private static void ConfigPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) 138 | => ConfigChanged(); 139 | 140 | internal static void ConfigChanged() 141 | { 142 | configChanged = true; 143 | configEditCount++; 144 | 145 | if (configEditCount >= 0xf) 146 | Task.Run(SaveConfig); 147 | } 148 | 149 | internal static void SaveConfig() 150 | { 151 | Task.Run(Plugin.PluginConfigManager.Save); 152 | 153 | if (saving) 154 | { 155 | saveRequesting = true; 156 | return; 157 | } 158 | 159 | lock (_lock) 160 | { 161 | saving = true; 162 | if (configChanged) 163 | { 164 | var path = Path.Combine(WorkingDirectory, DefaultSettings.ConfigFileName); 165 | if (File.Exists(path)) File.Delete(path); 166 | 167 | using var file = File.Create(path); 168 | using var writer = new StreamWriter(file); 169 | 170 | writer.Write(Config.Serialize()); 171 | 172 | configChanged = false; 173 | configEditCount = 0; 174 | } 175 | 176 | if (saveRequesting) 177 | { 178 | saveRequesting = false; 179 | SaveConfig(); 180 | } 181 | 182 | saving = false; 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /src/LipUI/Models/PackageInstaller.cs: -------------------------------------------------------------------------------- 1 | namespace LipUI.Models; 2 | 3 | internal static class PackageInstaller 4 | { 5 | //public static ValueTask InstallPackage(LipIndex.LipIndexData.LipToothItem item) 6 | //{ 7 | 8 | //} 9 | } 10 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/ConfigCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Specialized; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace LipUI.Models.Plugin; 8 | 9 | public struct ConfigItem 10 | { 11 | [JsonPropertyName("value")] 12 | public string Value { get; set; } 13 | 14 | [JsonIgnore] 15 | public readonly bool IsNull => Value is "null"; 16 | 17 | public readonly T As(IFormatProvider? formatProvider = null) 18 | where T : IParsable 19 | => T.Parse(Value, formatProvider); 20 | 21 | public static implicit operator ConfigItem(string value) => new() { Value = value }; 22 | 23 | public static implicit operator ConfigItem(int value) => new() { Value = value.ToString() }; 24 | 25 | public static implicit operator ConfigItem(double value) => new() { Value = value.ToString() }; 26 | 27 | public static implicit operator ConfigItem(bool value) => new() { Value = value.ToString() }; 28 | 29 | public static implicit operator ConfigItem(Guid value) => new() { Value = value.ToString() }; 30 | 31 | public static implicit operator ConfigItem(DateTime value) => new() { Value = value.ToString() }; 32 | 33 | public static implicit operator ConfigItem(TimeSpan value) => new() { Value = value.ToString() }; 34 | 35 | public static implicit operator ConfigItem(Version value) => new() { Value = value.ToString() }; 36 | 37 | public static implicit operator ConfigItem(Uri value) => new() { Value = value.ToString() }; 38 | 39 | public static implicit operator string(ConfigItem item) => item.Value; 40 | 41 | public static ConfigItem Create(T value) 42 | where T : IParsable 43 | => new() { Value = value.ToString() ?? "null" }; 44 | 45 | public override readonly string ToString() => Value; 46 | } 47 | 48 | public class ConfigCollection 49 | : IDictionary 50 | , INotifyCollectionChanged 51 | { 52 | private readonly Dictionary items; 53 | internal bool Changed { get; set; } = false; 54 | 55 | internal ConfigCollection() 56 | { 57 | items = []; 58 | CollectionChanged += ConfigCollection_CollectionChanged; 59 | } 60 | 61 | internal ConfigCollection(string json) 62 | { 63 | items = JsonSerializer.Deserialize>(json) ?? throw new ArgumentNullException(nameof(json)); 64 | CollectionChanged += ConfigCollection_CollectionChanged; 65 | } 66 | 67 | public ConfigItem this[string key] 68 | { 69 | get 70 | { 71 | if (items.TryGetValue(key, out var value)) 72 | return value; 73 | 74 | var item = new ConfigItem() { Value = "null" }; 75 | items.Add(key, item); 76 | return item; 77 | } 78 | set 79 | { 80 | items[key] = value; 81 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); 82 | } 83 | } 84 | 85 | public ICollection Keys => items.Keys; 86 | 87 | public ICollection Values => items.Values; 88 | 89 | public int Count => items.Count; 90 | 91 | public bool IsReadOnly => ((ICollection>)items).IsReadOnly; 92 | 93 | public event NotifyCollectionChangedEventHandler? CollectionChanged; 94 | 95 | private void ConfigCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) 96 | { 97 | Changed = true; 98 | Main.ConfigChanged(); 99 | } 100 | 101 | public void Add(string key, ConfigItem value) 102 | { 103 | items.Add(key, value); 104 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); 105 | } 106 | 107 | public void Add(KeyValuePair item) 108 | { 109 | ((ICollection>)items).Add(item); 110 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); 111 | } 112 | 113 | public void Clear() 114 | { 115 | ((ICollection>)items).Clear(); 116 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); 117 | } 118 | 119 | public bool Contains(KeyValuePair item) 120 | => ((ICollection>)items).Contains(item); 121 | 122 | public bool ContainsKey(string key) 123 | => items.ContainsKey(key); 124 | 125 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 126 | => ((ICollection>)items).CopyTo(array, arrayIndex); 127 | 128 | public IEnumerator> GetEnumerator() 129 | => items.GetEnumerator(); 130 | 131 | public bool Remove(string key) 132 | { 133 | var ret = ((IDictionary)items).Remove(key); 134 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Remove)); 135 | return ret; 136 | } 137 | 138 | public bool Remove(KeyValuePair item) 139 | { 140 | var ret = ((ICollection>)items).Remove(item); 141 | CollectionChanged?.Invoke(this, new(NotifyCollectionChangedAction.Remove)); 142 | return ret; 143 | } 144 | 145 | public bool TryGetValue(string key, [MaybeNullWhen(false)] out ConfigItem value) 146 | => items.TryGetValue(key, out value); 147 | 148 | IEnumerator IEnumerable.GetEnumerator() 149 | => items.GetEnumerator(); 150 | 151 | internal string Serialize() => JsonSerializer.Serialize(items, Config.options); 152 | 153 | internal static ConfigCollection Deserialize([StringSyntax("Json")] string json) => new(json); 154 | } 155 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/IHomePageModule.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Pages.Home.Modules; 2 | using Microsoft.UI.Xaml; 3 | using Microsoft.UI.Xaml.Media; 4 | 5 | namespace LipUI.Models.Plugin; 6 | 7 | public interface IHomePageModule : IPlugin 8 | { 9 | public Type PageType { get; } 10 | 11 | public FrameworkElement? IconContent { get => null; } 12 | 13 | public Brush? IconBackground { get => null; } 14 | 15 | public void OnIconInitialze(ModuleIcon icon) { } 16 | 17 | public void OnExit() { } 18 | } 19 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/IPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace LipUI.Models.Plugin; 2 | 3 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] 4 | public sealed class LipUIModuleAttribute : Attribute 5 | { 6 | } 7 | 8 | public interface IPlugin 9 | { 10 | public string PluginName { get; } 11 | 12 | public bool DefaultEnabled { get; } 13 | 14 | public Guid Guid { get; } 15 | 16 | public void OnInitlalize(LipuiServices services) { } 17 | 18 | public void OnEnable(LipuiServices services) { } 19 | 20 | public void OnDisable(LipuiServices services) { } 21 | 22 | public void OnApplicationExit() { } 23 | } 24 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/IUIPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | namespace LipUI.Models.Plugin; 4 | 5 | public interface IUIPlugin : IPlugin 6 | { 7 | public IconElement NavigatonBarIcon { get; } 8 | 9 | public object NavigationBarContent { get; } 10 | 11 | public Type PageType { get; } 12 | 13 | public object? PageParameterRequsted() { return null; } 14 | } 15 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/LipuiRuntimeInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | namespace LipUI.Models.Plugin; 4 | 5 | #nullable disable 6 | public class LipuiRuntimeInfo 7 | { 8 | public ElementTheme Theme { get; init; } 9 | 10 | public Window ApplicationWindow { get; init; } 11 | } 12 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/LipuiServices.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CA1822 2 | 3 | using LipUI.Models.Lip; 4 | using Microsoft.UI.Dispatching; 5 | using Microsoft.UI.Xaml; 6 | using Microsoft.UI.Xaml.Controls; 7 | 8 | namespace LipUI.Models.Plugin; 9 | 10 | public class LipuiServices 11 | { 12 | internal static LipuiServices Default { get; } = new(); 13 | 14 | public async ValueTask CreateLipConsoleAsync(XamlRoot xamlRoot, string? workingDir = null) 15 | { 16 | var (success, path) = await Main.TryGetLipConsolePathAsync(xamlRoot); 17 | if (success is false) 18 | return null; 19 | 20 | workingDir ??= Main.Config.SelectedServer?.WorkingDirectory; 21 | 22 | if (workingDir is null) 23 | return null; 24 | 25 | return new(path!, workingDir); 26 | } 27 | 28 | public async ValueTask ShowInfoBarAsync( 29 | string? title = null, 30 | string? message = null, 31 | InfoBarSeverity severity = InfoBarSeverity.Informational, 32 | TimeSpan interval = default, 33 | UIElement? barContent = null, 34 | CancellationToken cancellationToken = default) 35 | => await InternalServices.ShowInfoBarAsync(title, message, severity, interval, barContent, cancellationToken); 36 | 37 | public async ValueTask ShowInfoBarAsync( 38 | Exception ex, 39 | bool containsStacktrace = false, 40 | InfoBarSeverity severity = InfoBarSeverity.Error, 41 | TimeSpan interval = default, 42 | UIElement? barContent = null, 43 | CancellationToken cancellationToken = default) 44 | => await InternalServices.ShowInfoBarAsync(ex, containsStacktrace, severity, interval, barContent, cancellationToken); 45 | 46 | public void ShowInfoBar( 47 | string? title = null, 48 | string? message = null, 49 | InfoBarSeverity severity = InfoBarSeverity.Informational, 50 | TimeSpan interval = default, 51 | UIElement? barContent = null, 52 | Action? completed = null, 53 | CancellationToken cancellationToken = default) 54 | => InternalServices.ShowInfoBar(title, message, severity, interval, barContent, completed, cancellationToken); 55 | 56 | public void ShowInfoBar( 57 | Exception ex, 58 | bool containsStacktrace = false, 59 | InfoBarSeverity severity = InfoBarSeverity.Error, 60 | TimeSpan interval = default, 61 | UIElement? barContent = null, 62 | Action? completed = null, 63 | CancellationToken cancellationToken = default) 64 | => InternalServices.ShowInfoBar(ex, containsStacktrace, severity, interval, barContent, completed, cancellationToken); 65 | 66 | public string GetPluginKey(IPlugin plugin) => PluginSystem.GetPluginKey(plugin); 67 | 68 | public ConfigCollection GetPluginConfig(IPlugin plugin) 69 | => PluginConfigManager.GetOrCreatePluginConfig(plugin); 70 | 71 | public string? CurrentServerDirectory => Main.Config.SelectedServer?.WorkingDirectory; 72 | 73 | public Config LipuiConfig => Main.Config; 74 | 75 | public DispatcherQueue DispatcherQueue => InternalServices.MainWindow!.DispatcherQueue; 76 | 77 | public LipuiRuntimeInfo LipuiInfo => new() 78 | { 79 | Theme = Main.Config.PersonalizationSettings.ColorTheme, 80 | ApplicationWindow = InternalServices.MainWindow ?? throw new NullReferenceException() 81 | }; 82 | 83 | public static ApplicationTheme ApplicationTheme => InternalServices.ApplicationTheme; 84 | } 85 | -------------------------------------------------------------------------------- /src/LipUI/Models/Plugin/PluginConfigManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text.RegularExpressions; 3 | using PluginKey = System.String; 4 | 5 | namespace LipUI.Models.Plugin; 6 | 7 | internal record struct PluginConfigFileInfo(string AssemblyName, string Name, Guid Guid); 8 | internal record struct PluginKeyInfo(string AssemblyName, Guid Guid); 9 | 10 | internal static partial class PluginConfigManager 11 | { 12 | private static readonly Dictionary pluginConfigs = []; 13 | 14 | [GeneratedRegex(@"(?.+?)\+(?.+)\+(?[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})$")] 15 | private static partial Regex PluginConfigFileNameRegex(); 16 | 17 | [GeneratedRegex(@"(?.+?)\+(?[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})$")] 18 | private static partial Regex PluginKeyRegex(); 19 | 20 | private static string GetPluginKey(string assemblyName, in Guid guid) => $"{assemblyName}+{guid}"; 21 | 22 | private static bool TryParseFileInfo(string str, [NotNullWhen(true)] out PluginConfigFileInfo info) 23 | { 24 | info = default; 25 | 26 | var match = PluginConfigFileNameRegex().Match(str); 27 | if (match.Success is false) 28 | return false; 29 | 30 | info = new(match.Groups["assembly_name"].Value, match.Groups["name"].Value, Guid.Parse(match.Groups["guid"].Value)); 31 | return true; 32 | } 33 | 34 | private static bool TryParseKeyInfo(string str, [NotNullWhen(true)] out PluginKeyInfo info) 35 | { 36 | info = default; 37 | 38 | var match = PluginKeyRegex().Match(str); 39 | if (match.Success is false) 40 | return false; 41 | 42 | info = new(match.Groups["assembly_name"].Value, Guid.Parse(match.Groups["guid"].Value)); 43 | return true; 44 | } 45 | 46 | public static void Load() 47 | { 48 | lock (pluginConfigs) 49 | { 50 | var dir = new DirectoryInfo(Path.Combine(Main.WorkingDirectory, DefaultSettings.ConfigsDirectory)); 51 | if (dir.Exists is false) 52 | dir.Create(); 53 | 54 | foreach (var file in dir.EnumerateFiles()) 55 | { 56 | var key = Path.GetFileNameWithoutExtension(file.Name); 57 | 58 | if (TryParseFileInfo(key, out var info) is false) 59 | continue; 60 | 61 | pluginConfigs.Add($"{info.AssemblyName}+{info.Guid}", (info, ConfigCollection.Deserialize(File.ReadAllText(file.FullName)))); 62 | } 63 | } 64 | } 65 | 66 | public static void Save(in PluginConfigFileInfo info, ConfigCollection config) 67 | { 68 | lock (config) 69 | { 70 | if (config.Changed is false) 71 | return; 72 | 73 | var path = Path.Combine(Main.WorkingDirectory, DefaultSettings.ConfigsDirectory, $"{info.AssemblyName}+{info.Name}+{info.Guid}.json"); 74 | if (File.Exists(path)) 75 | File.Delete(path); 76 | File.WriteAllText(path, config.Serialize()); 77 | 78 | config.Changed = false; 79 | } 80 | } 81 | 82 | public static void Save() 83 | { 84 | lock (pluginConfigs) 85 | { 86 | foreach (var (_, (info, config)) in pluginConfigs) 87 | Save(info, config); 88 | } 89 | } 90 | 91 | public static ConfigCollection? InitPluginConfig(PluginKey key, string pluginDisplayName) 92 | { 93 | if (TryParseKeyInfo(key, out var info) is false) 94 | return null; 95 | 96 | lock (pluginConfigs) 97 | { 98 | if (pluginConfigs.ContainsKey(key)) 99 | return null; 100 | 101 | var ret = new ConfigCollection(); 102 | 103 | pluginConfigs.Add(key, (new(info.AssemblyName, pluginDisplayName, info.Guid), ret)); 104 | 105 | return ret; 106 | } 107 | } 108 | 109 | public static ConfigCollection? InitPluginConfig(IPlugin plugin) 110 | => InitPluginConfig(PluginSystem.GetPluginKey(plugin), plugin.PluginName); 111 | 112 | public static bool TryGetPluginConfig(PluginKey key, [NotNullWhen(true)] out ConfigCollection? config) 113 | { 114 | config = null; 115 | 116 | if (TryParseKeyInfo(key, out var _) && pluginConfigs.TryGetValue(key, out var temp) is true) 117 | { 118 | config = temp.Config; 119 | return true; 120 | } 121 | 122 | return false; 123 | } 124 | 125 | public static bool TryGetPluginConfig(IPlugin plugin, [NotNullWhen(true)] out ConfigCollection? config) 126 | => TryGetPluginConfig(PluginSystem.GetPluginKey(plugin), out config); 127 | 128 | public static ConfigCollection GetOrCreatePluginConfig(PluginKey key, string pluginDisplayName) 129 | { 130 | if (TryGetPluginConfig(key, out var config)) 131 | return config; 132 | 133 | return InitPluginConfig(key, pluginDisplayName) ?? throw new NullReferenceException(); 134 | } 135 | 136 | public static ConfigCollection GetOrCreatePluginConfig(IPlugin plugin) 137 | => GetOrCreatePluginConfig(PluginSystem.GetPluginKey(plugin), plugin.PluginName); 138 | 139 | public static bool Contains(IPlugin plugin) => pluginConfigs.ContainsKey(PluginSystem.GetPluginKey(plugin)); 140 | } 141 | -------------------------------------------------------------------------------- /src/LipUI/Models/ServerIcon.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Media.Imaging; 2 | 3 | namespace LipUI.Models; 4 | 5 | internal static class ServerIcon 6 | { 7 | [Flags] public enum IconType { None, Empty, Preview, Default, Custom } 8 | 9 | public static async ValueTask GetIcon(string? version, string? iconPath) 10 | { 11 | IconType type; 12 | 13 | if (iconPath is null) 14 | { 15 | if (string.IsNullOrEmpty(version)) 16 | type = IconType.Empty; 17 | else if (version.Contains("preview")) 18 | type = IconType.Preview; 19 | else 20 | type = IconType.Default; 21 | } 22 | else type = IconType.Custom; 23 | 24 | switch (type) 25 | { 26 | case IconType.Empty: return GlobalIcons.Glass; 27 | case IconType.Preview: return GlobalIcons.Netherrack; 28 | case IconType.Default: return GlobalIcons.GrassBlock; 29 | case IconType.Custom: 30 | { 31 | if (File.Exists(iconPath)) 32 | { 33 | try 34 | { 35 | using var file = File.OpenRead(iconPath); 36 | var bitmap = new BitmapImage(); 37 | await bitmap.SetSourceAsync(file.AsRandomAccessStream()); 38 | return bitmap; 39 | } 40 | catch (Exception) 41 | { 42 | return await GetIcon(version, null); 43 | } 44 | } 45 | else return await GetIcon(version, null); 46 | 47 | } 48 | 49 | case IconType.None: 50 | default: throw new InvalidProgramException("???"); 51 | 52 | } 53 | } 54 | 55 | public static async ValueTask GetIcon(ServerInstance? server) 56 | => server is null ? await GetIcon(null, null) : await GetIcon(server.Version, server.Icon); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/LipUI/Models/ServerInstance.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace LipUI.Models; 4 | 5 | public class ServerInstance 6 | { 7 | [JsonPropertyName("name")] 8 | public string Name { get; set; } = "undefined"; 9 | 10 | [JsonPropertyName("description")] 11 | public string Description { get; set; } = string.Empty; 12 | 13 | [JsonPropertyName("version")] 14 | public string Version { get; set; } = string.Empty; 15 | 16 | [JsonPropertyName("working_directory")] 17 | public string WorkingDirectory { get; set; } = string.Empty; 18 | 19 | [JsonPropertyName("icon")] 20 | public string? Icon { get; set; } 21 | 22 | public static bool operator ==(ServerInstance? left, ServerInstance? right) 23 | => left is not null && left.Equals(right); 24 | 25 | public static bool operator !=(ServerInstance? left, ServerInstance? right) 26 | => !(left == right); 27 | 28 | public override bool Equals(object? obj) 29 | { 30 | if (ReferenceEquals(this, obj)) 31 | { 32 | return true; 33 | } 34 | 35 | if (obj is null) 36 | { 37 | return false; 38 | } 39 | 40 | if (obj is ServerInstance instance) 41 | return WorkingDirectory == instance.WorkingDirectory; 42 | else 43 | return false; 44 | } 45 | 46 | public override int GetHashCode() 47 | => WorkingDirectory.GetHashCode(); 48 | } 49 | -------------------------------------------------------------------------------- /src/LipUI/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | LipUI 19 | minec 20 | Assets\StoreLogo.png 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/HomePage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 52 | 53 | 58 | 59 | 63 | 64 | 65 | 66 | 70 | 71 | 78 | 79 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 132 | 133 | 141 | 142 | 143 | 144 | 145 | 146 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/HomePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Pages.Home.Modules; 3 | using LipUI.Pages.ServerSelection; 4 | using Microsoft.UI.Xaml; 5 | using Microsoft.UI.Xaml.Controls; 6 | using Microsoft.UI.Xaml.Input; 7 | using Microsoft.UI.Xaml.Navigation; 8 | using System.Diagnostics; 9 | 10 | // To learn more about WinUI, the WinUI project structure, 11 | // and more about our project templates, see: http://aka.ms/winui-project-info. 12 | 13 | namespace LipUI.Pages.Home; 14 | 15 | /// 16 | /// An empty page that can be used on its own or navigated to within a Frame. 17 | /// 18 | public sealed partial class HomePage : Page 19 | { 20 | public HomePage() 21 | { 22 | InitializeComponent(); 23 | } 24 | 25 | private void ServerIconImage_Loading(FrameworkElement sender, object args) 26 | => RefreshIcon(); 27 | 28 | private async void RefreshIcon() 29 | { 30 | ServerInstance? instance = Main.Config.SelectedServer; 31 | var image = await ServerIcon.GetIcon(instance); 32 | image.DecodePixelType = Microsoft.UI.Xaml.Media.Imaging.DecodePixelType.Logical; 33 | image.DecodePixelHeight = (int)ServerIconImage.Height; 34 | image.DecodePixelWidth = (int)ServerIconImage.Width; 35 | 36 | ServerIconImage.Source = image; 37 | ServerDesc.Text = 38 | instance is null ? 39 | "home$nullServerPath".GetLocalized() : 40 | string.IsNullOrWhiteSpace(instance.Description) ? 41 | "home$emptyDesc".GetLocalized() : 42 | instance.Description; 43 | } 44 | 45 | private void SelectServerButton_Click(object sender, RoutedEventArgs e) 46 | => Frame.Navigate(typeof(ServerSelectionPage), () => { DispatcherQueue.TryEnqueue(RefreshIcon); }); 47 | 48 | private void StartServerButton_Click(object sender, RoutedEventArgs e) 49 | { 50 | if (Main.Config.SelectedServer is null) 51 | return; 52 | 53 | var dir = Main.Config.SelectedServer.WorkingDirectory; 54 | var path = Path.Combine(dir, "bedrock_server_mod.exe"); 55 | 56 | if (File.Exists(path)) 57 | { 58 | Process.Start(path); 59 | return; 60 | } 61 | 62 | path = Path.Combine(dir, "bedrock_server.exe"); 63 | if (File.Exists(path)) 64 | { 65 | Process.Start(path); 66 | } 67 | } 68 | 69 | private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) 70 | => throw new Exception("Failed to load Page " + e.SourcePageType.FullName); 71 | 72 | private void ContentFrame_Loading(FrameworkElement sender, object args) 73 | => ContentFrame.Navigate(typeof(ModulesPage)); 74 | 75 | private void BackButton_PointerEntered(object sender, PointerRoutedEventArgs e) 76 | => AnimatedIcon.SetState(BackAnimatedIcon, "PointerOver"); 77 | 78 | private void BackButton_PointerExited(object sender, PointerRoutedEventArgs e) 79 | => AnimatedIcon.SetState(BackAnimatedIcon, "Normal"); 80 | 81 | private void BackButton_Click(object sender, RoutedEventArgs e) 82 | { 83 | ModuleIcon.OnExit(ContentFrame.Content.GetType()); 84 | ContentFrame.TryGoBack(); 85 | } 86 | 87 | private void ContentFrame_Navigated(object sender, NavigationEventArgs e) 88 | { 89 | BackButton.IsEnabled = ContentFrame.CanGoBack; 90 | var type = e.Content.GetType(); 91 | 92 | DispatcherQueue.TryEnqueue(async () => 93 | { 94 | try 95 | { 96 | InternalFrameTitle.Text = type != typeof(ModulesPage) ? 97 | ModuleIcon.Modules[type].PluginName : "modules$title$modulePage".GetLocalized(); 98 | } 99 | catch (Exception ex) 100 | { 101 | await InternalServices.ShowInfoBarAsync(ex); 102 | } 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/AllowListViewerPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI.Pages.Home.Modules; 7 | 8 | //[LipUIModule] 9 | //internal class AllowListViewerPage_Module : ILipUIModules 10 | //{ 11 | // public string ModuleName => "modules$title$allowListViewer".GetLocalized(); 12 | 13 | // public Type PageType => typeof(AllowListViewerPage); 14 | 15 | // public FrameworkElement? IconContent 16 | // => new SymbolIcon(Symbol.ContactInfo) { Height = 32, Width = 32 }; 17 | 18 | // public Brush? IconBackground 19 | // { 20 | // get 21 | // { 22 | // var color = Colors.AliceBlue; 23 | // color.A = 200; 24 | // return new SolidColorBrush(color); 25 | // } 26 | // } 27 | //} 28 | 29 | /// 30 | /// An empty page that can be used on its own or navigated to within a Frame. 31 | /// 32 | internal sealed partial class AllowListViewerPage : Page 33 | { 34 | public AllowListViewerPage() 35 | { 36 | InitializeComponent(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/BdsPropertiesEditorPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/ModuleIcon.xaml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/ModuleIcon.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Plugin; 3 | using Microsoft.UI; 4 | using Microsoft.UI.Xaml.Controls; 5 | using Microsoft.UI.Xaml.Media; 6 | 7 | // To learn more about WinUI, the WinUI project structure, 8 | // and more about our project templates, see: http://aka.ms/winui-project-info. 9 | 10 | namespace LipUI.Pages.Home.Modules; 11 | 12 | public sealed partial class ModuleIcon : UserControl 13 | { 14 | 15 | internal static Dictionary Modules { get; private set; } = new(); 16 | 17 | public Type? PageType { get; private set; } 18 | 19 | public ModuleIcon(IHomePageModule module) 20 | { 21 | InitializeComponent(); 22 | 23 | try 24 | { 25 | 26 | if (module is not null) 27 | { 28 | PageType = module.PageType; 29 | 30 | ModuleName.Text = module.PluginName; 31 | 32 | var icon = module.IconContent; 33 | IconView.Child = icon; 34 | 35 | ElementBorder.Background = module.IconBackground; 36 | 37 | IconView.Child ??= (icon = new SymbolIcon(Symbol.More)); 38 | IconView.Height = icon!.Height; 39 | IconView.Width = icon!.Width; 40 | 41 | ElementBorder.Background ??= new SolidColorBrush(Colors.Transparent); 42 | 43 | Modules.TryAdd(PageType, module); 44 | 45 | module.OnIconInitialze(this); 46 | } 47 | 48 | } 49 | catch (Exception ex) 50 | { 51 | Task.Run(() => InternalServices.ShowInfoBarAsync(ex)); 52 | return; 53 | } 54 | } 55 | 56 | internal static void OnExit(Type type) 57 | { 58 | if (Modules.TryGetValue(type, out var module)) 59 | { 60 | module.OnExit(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/ModulesPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Home/Modules/ModulesPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Plugin; 3 | using Microsoft.UI; 4 | using Microsoft.UI.Xaml; 5 | using Microsoft.UI.Xaml.Controls; 6 | using Microsoft.UI.Xaml.Media; 7 | 8 | // To learn more about WinUI, the WinUI project structure, 9 | // and more about our project templates, see: http://aka.ms/winui-project-info. 10 | 11 | namespace LipUI.Pages.Home.Modules; 12 | 13 | internal class ModulesPage_Module : IHomePageModule 14 | { 15 | 16 | public FrameworkElement IconContent => new Viewbox(); 17 | 18 | public Brush IconBackground => new SolidColorBrush(Colors.Transparent); 19 | 20 | public Type PageType => typeof(ModulesPage); 21 | 22 | public string PluginName => "modules$title$modulePage".GetLocalized(); 23 | 24 | public bool DefaultEnabled => false; 25 | 26 | public Guid Guid => default; 27 | } 28 | 29 | /// 30 | /// An empty page that can be used on its own or navigated to within a Frame. 31 | /// 32 | public sealed partial class ModulesPage : Page 33 | { 34 | public static void InitEventHandlers() 35 | { 36 | PluginSystem.PluginEnabled += PluginSystem_PluginEnabled; 37 | PluginSystem.PluginDisabled += PluginSystem_PluginDisabled; 38 | } 39 | 40 | private static readonly HashSet enabledModules = new(); 41 | private static void PluginSystem_PluginEnabled(IPlugin obj) 42 | { 43 | lock (enabledModules) 44 | { 45 | if (obj is IHomePageModule module) 46 | enabledModules.Add(module); 47 | } 48 | } 49 | 50 | private static void PluginSystem_PluginDisabled(IPlugin obj) 51 | { 52 | lock (enabledModules) 53 | { 54 | if (obj is IHomePageModule module) 55 | enabledModules.Remove(module); 56 | } 57 | } 58 | 59 | public ModulesPage() 60 | { 61 | InitializeComponent(); 62 | 63 | DispatcherQueue.TryEnqueue(async () => 64 | { 65 | await Task.Delay(500); 66 | 67 | foreach (var module in enabledModules) 68 | { 69 | ModulesView.Items.Add(new ModuleIcon(module)); 70 | } 71 | }); 72 | } 73 | 74 | private void ModulesView_ItemClick(object sender, ItemClickEventArgs e) 75 | { 76 | var item = e.ClickedItem as ModuleIcon; 77 | Frame.Navigate(item!.PageType); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Index/IndexPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Index/IndexPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Protocol; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | using static LipUI.Protocol.LipIndex.LipIndexData; 6 | 7 | // To learn more about WinUI, the WinUI project structure, 8 | // and more about our project templates, see: http://aka.ms/winui-project-info. 9 | 10 | namespace LipUI.Pages.Index; 11 | 12 | /// 13 | /// An empty page that can be used on its own or navigated to within a Frame. 14 | /// 15 | public sealed partial class IndexPage : Page 16 | { 17 | 18 | private LipIndex? lipIndex; 19 | 20 | public IndexPage() 21 | { 22 | InitializeComponent(); 23 | } 24 | 25 | private void ReloadLipIndex(IEnumerable? items = null) 26 | { 27 | DispatcherQueue.TryEnqueue(async () => 28 | { 29 | try 30 | { 31 | TeethScrollView.Content = new ProgressRing(); 32 | ToothListView.Items.Clear(); 33 | 34 | if (items is null) 35 | { 36 | lipIndex = await RequestLipIndexAsync(Main.Config.GeneralSettings.LipIndexApiKey); 37 | items = lipIndex.Data.Items; 38 | } 39 | 40 | var handler = AuthorButton_Click; 41 | foreach (var item in items) 42 | { 43 | ToothListView.Items.Add(new LipIndexToothView(item, handler)); 44 | } 45 | TeethScrollView.Content = ToothListView; 46 | } 47 | catch (Exception ex) 48 | { 49 | TeethScrollView.Content = ToothListView; 50 | await InternalServices.ShowInfoBarAsync(ex); 51 | } 52 | }); 53 | } 54 | 55 | private void AuthorButton_Click(string author) 56 | { 57 | DispatcherQueue.TryEnqueue(() => SuggestBox.Text = author); 58 | QuerySubmitted(author); 59 | } 60 | 61 | private void IndexPage_Loaded(object sender, RoutedEventArgs e) 62 | => ReloadLipIndex(); 63 | 64 | private static async ValueTask RequestLipIndexAsync(string lipApiUrl) 65 | { 66 | static void ThrowException(string api) => throw new NullReferenceException($"Failed to get index : {api}"); 67 | 68 | var client = InternalServices.HttpClient; 69 | 70 | //foreach all lip index pages 71 | 72 | var text = await client.GetStringAsync($"https://{lipApiUrl}/search/teeth"); 73 | if (string.IsNullOrWhiteSpace(text)) 74 | ThrowException(lipApiUrl); 75 | 76 | var index = LipIndex.Deserialize(text)!; 77 | var pageCount = index.Data.TotalPages; 78 | for (int i = 2; i <= pageCount; ++i) 79 | { 80 | text = await client.GetStringAsync($"https://{lipApiUrl}/search/teeth?&page={i}"); 81 | if (string.IsNullOrWhiteSpace(text)) 82 | ThrowException(lipApiUrl); 83 | 84 | var temp = LipIndex.Deserialize(text)!; 85 | 86 | index.Data.Items.AddRange(temp.Data.Items); 87 | } 88 | 89 | return index; 90 | } 91 | 92 | private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) 93 | { 94 | if (args.Reason is not AutoSuggestionBoxTextChangeReason.UserInput) 95 | return; 96 | 97 | var str = sender.Text.ToLower(); 98 | 99 | Task.Run(async () => 100 | { 101 | try 102 | { 103 | lock (lipIndex!.Data.Items) 104 | { 105 | var dataset = from tooth in lipIndex.Data.Items 106 | where tooth.Name.ToLower().Contains(str) 107 | || tooth.Author.ToLower().Contains(str) 108 | select tooth.Name; 109 | 110 | DispatcherQueue.TryEnqueue(() => sender.ItemsSource = dataset); 111 | } 112 | } 113 | catch (Exception ex) 114 | { 115 | await InternalServices.ShowInfoBarAsync(ex); 116 | } 117 | }); 118 | } 119 | 120 | private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) 121 | => QuerySubmitted(args.QueryText); 122 | 123 | private void QuerySubmitted(string queryText) 124 | => Task.Run(async () => 125 | { 126 | try 127 | { 128 | var query = queryText.ToLower(); 129 | 130 | if (string.IsNullOrWhiteSpace(query)) 131 | { 132 | ReloadLipIndex(); 133 | return; 134 | } 135 | lock (lipIndex!.Data.Items) 136 | { 137 | var selectedTeeth = from tooth in lipIndex.Data.Items 138 | where tooth.Name.ToLower().Contains(query) 139 | || tooth.Author.ToLower().Contains(query) 140 | select tooth; 141 | 142 | ReloadLipIndex(selectedTeeth); 143 | } 144 | } 145 | catch (Exception ex) 146 | { 147 | await InternalServices.ShowInfoBarAsync(ex); 148 | } 149 | }); 150 | 151 | private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) 152 | { 153 | 154 | } 155 | 156 | private void FeaturedFilterButton_Checked(object sender, RoutedEventArgs e) 157 | { 158 | 159 | } 160 | 161 | private void FeaturedFilterButton_Unchecked(object sender, RoutedEventArgs e) 162 | { 163 | 164 | } 165 | 166 | private void ToothListView_ItemClick(object sender, ItemClickEventArgs e) 167 | { 168 | var view = e.ClickedItem as LipIndexToothView; 169 | Frame.Navigate(typeof(ToothInfoPage), view!.Tooth); 170 | } 171 | 172 | private void ReloadPackageButton_Click(object sender, RoutedEventArgs e) 173 | { 174 | SuggestBox.Text = null; 175 | ReloadLipIndex(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Index/LipIndexToothView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 37 | 38 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 63 | 64 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Index/LipIndexToothView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Media; 4 | using Windows.UI; 5 | using static LipUI.Protocol.LipIndex.LipIndexData; 6 | 7 | // To learn more about WinUI, the WinUI project structure, 8 | // and more about our project templates, see: http://aka.ms/winui-project-info. 9 | 10 | namespace LipUI.Pages.Index; 11 | 12 | public sealed partial class LipIndexToothView : UserControl 13 | { 14 | 15 | public LipToothItem Tooth { get; private set; } 16 | 17 | private readonly Action onClick; 18 | 19 | public LipIndexToothView(LipToothItem tooth, Action authorButtonClickHandler) 20 | { 21 | Tooth = tooth; 22 | 23 | InitializeComponent(); 24 | 25 | ToothName.Text = tooth.Name; 26 | ToothDescription.Text = tooth.Description; 27 | AuthorButtonText.Text = tooth.Author; 28 | onClick = authorButtonClickHandler; 29 | 30 | var style = Application.Current.Resources["CaptionTextBlockStyle"] as Style; 31 | var foreground = new SolidColorBrush((Color)Application.Current.Resources["TextFillColorTertiary"]); 32 | 33 | foreach (var tag in tooth.Tags) 34 | ToothTags.Children.Add(new TextBlock() 35 | { 36 | Text = tag, 37 | Style = style, 38 | Foreground = foreground 39 | }); 40 | } 41 | 42 | private void AuthorButton_Click(object sender, RoutedEventArgs e) 43 | => onClick(Tooth.Author); 44 | } 45 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/ConsoleLineView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI.Pages.LipExecutionPanel; 7 | 8 | public sealed partial class ConsoleLineView : UserControl 9 | { 10 | public ConsoleLineView(string line) 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 13 | 14 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/EulaAcceptView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI.VIews 7 | { 8 | public sealed partial class EulaAcceptView : UserControl 9 | { 10 | public EulaAcceptView() 11 | { 12 | this.InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 73 | 74 | 76 | 77 | 78 | 79 | 88 | 89 | 95 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 114 | 115 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futrime/lipui/f5d2e51f434dcdc0424a3ef6284d5feb490bc99d/src/LipUI/Pages/LipExecutionPanel/LipExecutionPanelPage.xaml.cs -------------------------------------------------------------------------------- /src/LipUI/Pages/LipExecutionPanel/LipInstallerView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LocalPackage/LocalPackagePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Lip; 3 | using LipUI.Protocol; 4 | using Microsoft.UI.Xaml; 5 | using Microsoft.UI.Xaml.Controls; 6 | using System.Text.Json; 7 | 8 | // To learn more about WinUI, the WinUI project structure, 9 | // and more about our project templates, see: http://aka.ms/winui-project-info. 10 | 11 | namespace LipUI.Pages.LocalPackage; 12 | 13 | /// 14 | /// An empty page that can be used on its own or navigated to within a Frame. 15 | /// 16 | public sealed partial class LocalPackagePage : Page 17 | { 18 | public LocalPackagePage() 19 | { 20 | InitializeComponent(); 21 | } 22 | 23 | private ToothPackage[]? teeth; 24 | private readonly object _lock = new(); 25 | 26 | private void ReloadPackage(IEnumerable? items = null) 27 | { 28 | DispatcherQueue.TryEnqueue(async () => 29 | { 30 | TeethScrollView.Content = new ProgressRing(); 31 | ToothListView.Items.Clear(); 32 | 33 | var lip = await Main.CreateLipConsole(XamlRoot); 34 | 35 | if (lip is null) 36 | { 37 | ((ProgressRing)TeethScrollView.Content).IsActive = false; 38 | return; 39 | } 40 | 41 | var cmd = LipCommand.CreateCommand(); 42 | 43 | 44 | 45 | ToothPackage[]? arr = null; 46 | 47 | if (items is not null) 48 | arr = items.ToArray(); 49 | else 50 | try 51 | { 52 | var json = await lip.RunAndGetString(cmd + LipCommand.List + LipCommandOption.Json); 53 | teeth = arr = JsonSerializer.Deserialize(json); 54 | } 55 | catch (Exception ex) 56 | { 57 | await InternalServices.ShowInfoBarAsync(ex); 58 | } 59 | 60 | if (arr is not null) 61 | { 62 | foreach (var item in arr!) 63 | { 64 | ToothListView.Items.Add(new LocalToothView(this, item)); 65 | } 66 | RefreshUpgradableTeeth(); 67 | } 68 | TeethScrollView.Content = ToothListView; 69 | }); 70 | } 71 | 72 | private void RefreshUpgradableTeeth() 73 | { 74 | DispatcherQueue.TryEnqueue(async () => 75 | { 76 | var lip = await Main.CreateLipConsole(XamlRoot); 77 | if (lip is null) return; 78 | 79 | var cmd = LipCommand.CreateCommand(); 80 | 81 | ToothPackage[] arr; 82 | try 83 | { 84 | var json = await lip.RunAndGetString(cmd + LipCommand.List + LipCommandOption.Upgradable + LipCommandOption.Json); 85 | arr = JsonSerializer.Deserialize(json)!; 86 | } 87 | catch (Exception ex) 88 | { 89 | await InternalServices.ShowInfoBarAsync(ex); 90 | return; 91 | } 92 | 93 | HashSet strings = []; 94 | 95 | foreach (var tooth in arr) 96 | strings.Add(tooth.Info.Name + tooth.Info.Author + tooth.Version); 97 | 98 | foreach (var i in ToothListView.Items) 99 | { 100 | var item = (LocalToothView)i; 101 | 102 | var tooth = item.Tooth; 103 | 104 | item.RefreshUpdateButton(strings.Contains(tooth.Info.Name + tooth.Info.Author + tooth.Version)); 105 | } 106 | }); 107 | } 108 | 109 | private void Page_Loaded(object sender, RoutedEventArgs e) 110 | => ReloadPackage(); 111 | 112 | private void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) 113 | { 114 | if (args.Reason is not AutoSuggestionBoxTextChangeReason.UserInput) 115 | return; 116 | 117 | var str = sender.Text.ToLower(); 118 | 119 | Task.Run(async () => 120 | { 121 | try 122 | { 123 | lock (_lock) 124 | { 125 | var dataset = from tooth in teeth 126 | where tooth.Info.Name.ToLower().Contains(str) 127 | || tooth.Info.Author.ToLower().Contains(str) 128 | select tooth.Info.Name; 129 | 130 | DispatcherQueue.TryEnqueue(() => sender.ItemsSource = dataset); 131 | } 132 | } 133 | catch (Exception ex) 134 | { 135 | await InternalServices.ShowInfoBarAsync(ex); 136 | } 137 | }); 138 | } 139 | 140 | private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) 141 | { 142 | var query = args.QueryText.ToLower(); 143 | Task.Run(async () => 144 | { 145 | try 146 | { 147 | if (string.IsNullOrWhiteSpace(query)) 148 | { 149 | ReloadPackage(); 150 | return; 151 | } 152 | lock (_lock) 153 | { 154 | var selectedTeeth = from tooth in teeth 155 | where tooth.Info.Name.ToLower().Contains(query) 156 | || tooth.Info.Author.ToLower().Contains(query) 157 | select tooth; 158 | 159 | ReloadPackage(selectedTeeth); 160 | } 161 | } 162 | catch (Exception ex) 163 | { 164 | await InternalServices.ShowInfoBarAsync(ex); 165 | } 166 | }); 167 | } 168 | 169 | private void AutoSuggestBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) 170 | { 171 | 172 | } 173 | 174 | private void FeaturedFilterButton_Checked(object sender, RoutedEventArgs e) 175 | { 176 | 177 | } 178 | 179 | private void FeaturedFilterButton_Unchecked(object sender, RoutedEventArgs e) 180 | { 181 | 182 | } 183 | 184 | private void ReloadPackageButton_Click(object sender, RoutedEventArgs e) 185 | => ReloadPackage(); 186 | } 187 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LocalPackage/LocalToothView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 35 | 36 | 40 | 41 | 42 | 43 | 51 | 52 | 60 | 61 | 72 | 73 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/LipUI/Pages/LocalPackage/LocalToothView.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using LipUI.Models.Lip; 3 | using LipUI.Pages.LipExecutionPanel; 4 | using LipUI.Protocol; 5 | using Microsoft.UI.Xaml; 6 | using Microsoft.UI.Xaml.Controls; 7 | 8 | // To learn more about WinUI, the WinUI project structure, 9 | // and more about our project templates, see: http://aka.ms/winui-project-info. 10 | 11 | namespace LipUI.Pages.LocalPackage; 12 | 13 | internal sealed partial class LocalToothView : UserControl 14 | { 15 | public ToothPackage Tooth { get; private set; } 16 | 17 | private LocalPackagePage page; 18 | 19 | public LocalToothView(LocalPackagePage page, ToothPackage tooth) 20 | { 21 | this.page = page; 22 | Tooth = tooth; 23 | 24 | InitializeComponent(); 25 | 26 | ToothName.Text = tooth.Info.Name; 27 | ToothDescription.Text = tooth.Info.Description; 28 | UpdateButtonText.Text = "localTooth$update".GetLocalized(); 29 | DeleteButtonText.Text = "localTooth$uninstall".GetLocalized(); 30 | 31 | UpdateButton.Content = new ProgressRing() 32 | { 33 | Height = 16, 34 | Width = 16 35 | }; 36 | } 37 | 38 | public void RefreshUpdateButton(bool enable) => 39 | DispatcherQueue.TryEnqueue(() => 40 | { 41 | UpdateButton.IsEnabled = enable; 42 | UpdateButton.Content = UpdateButtonText; 43 | }); 44 | 45 | private async void UpdateButton_Click(object sender, RoutedEventArgs e) 46 | { 47 | var lip = await Main.CreateLipConsole(XamlRoot); 48 | if (lip is null) 49 | { 50 | InternalServices.ShowInfoBar( 51 | "infobar$error".GetLocalized(), 52 | Main.Config.SelectedServer is null ? 53 | "lipExecution$nullServerPath".GetLocalized() : 54 | "lipExecution$nullLipPath".GetLocalized(), 55 | InfoBarSeverity.Error); 56 | 57 | return; 58 | } 59 | var cmd = LipCommand.CreateCommand() + LipCommand.Install + LipCommandOption.Upgrade + Tooth.Tooth; 60 | var info = new List(); 61 | 62 | page.Frame.Navigate( 63 | typeof(LipExecutionPanelPage), 64 | new LipExecutionPanelPage.NavigationArgs(Tooth.Tooth, info, lip, cmd)); 65 | } 66 | 67 | private async void DeleteButton_Click(object sender, RoutedEventArgs e) 68 | { 69 | var lip = await Main.CreateLipConsole(XamlRoot); 70 | if (lip is null) 71 | { 72 | InternalServices.ShowInfoBar( 73 | "infobar$error".GetLocalized(), 74 | Main.Config.SelectedServer is null ? 75 | "lipExecution$nullServerPath".GetLocalized() : 76 | "lipExecution$nullLipPath".GetLocalized(), 77 | InfoBarSeverity.Error); 78 | 79 | return; 80 | } 81 | var cmd = LipCommand.CreateCommand() + LipCommand.Uninstall + Tooth.Tooth; 82 | var info = new List(); 83 | 84 | page.Frame.Navigate( 85 | typeof(LipExecutionPanelPage), 86 | new LipExecutionPanelPage.NavigationArgs(Tooth.Tooth, info, lip, cmd)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ModuleManager/LipuiPluginsView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 41 | 42 | 58 | 59 | 75 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ModuleManager/ModuleManagerPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models.Plugin; 2 | using LipUI.Pages.ModuleManager; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | 6 | // To learn more about WinUI, the WinUI project structure, 7 | // and more about our project templates, see: http://aka.ms/winui-project-info. 8 | 9 | namespace LipUI.Pages.ToothPack; 10 | 11 | /// 12 | /// An empty page that can be used on its own or navigated to within a Frame. 13 | /// 14 | public sealed partial class ModuleManagerPage : Page 15 | { 16 | [Flags] 17 | private enum Selection { Plugins, UIPlugins, Modules } 18 | 19 | private static Selection currentSelection = Selection.Plugins; 20 | 21 | public ModuleManagerPage() 22 | { 23 | InitializeComponent(); 24 | } 25 | 26 | private void PluginsButton_Click(object sender, RoutedEventArgs e) 27 | => Plugins_Selection(); 28 | 29 | private void Plugins_Selection() 30 | { 31 | currentSelection = Selection.Plugins; 32 | 33 | if (PluginSystem.Plugins is null) return; 34 | 35 | ModulesTreeView.Content = new ProgressRing(); 36 | DispatcherQueue.TryEnqueue(async () => 37 | { 38 | var view = new LipuiPluginsView(PluginSystem.Plugins); 39 | await view.InitializeUIAsync(); 40 | ModulesTreeView.Content = view; 41 | }); 42 | } 43 | 44 | private void UIPluginsButton_Click(object sender, RoutedEventArgs e) 45 | => UIPlugins_Selection(); 46 | 47 | private void UIPlugins_Selection() 48 | { 49 | currentSelection = Selection.UIPlugins; 50 | 51 | if (PluginSystem.UIPlugins is null) return; 52 | 53 | ModulesTreeView.Content = new ProgressRing(); 54 | DispatcherQueue.TryEnqueue(async () => 55 | { 56 | var view = new LipuiPluginsView(PluginSystem.UIPlugins); 57 | await view.InitializeUIAsync(); 58 | ModulesTreeView.Content = view; 59 | }); 60 | } 61 | 62 | private void ModulesButton_Click(object sender, RoutedEventArgs e) 63 | => Modules_Selection(); 64 | 65 | private void Modules_Selection() 66 | { 67 | currentSelection = Selection.Modules; 68 | 69 | if (PluginSystem.Modules is null) return; 70 | 71 | ModulesTreeView.Content = new ProgressRing(); 72 | DispatcherQueue.TryEnqueue(async () => 73 | { 74 | var view = new LipuiPluginsView(PluginSystem.Modules); 75 | await view.InitializeUIAsync(); 76 | ModulesTreeView.Content = view; 77 | }); 78 | } 79 | 80 | private void Page_Loading(FrameworkElement sender, object args) 81 | { 82 | switch (currentSelection) 83 | { 84 | case Selection.Plugins: 85 | Plugins_Selection(); 86 | break; 87 | case Selection.UIPlugins: 88 | UIPlugins_Selection(); 89 | break; 90 | case Selection.Modules: 91 | Modules_Selection(); 92 | break; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 24 | 25 | 37 | 38 | 39 | 42 | 43 | 46 | 47 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ServerSelection/ServerInstanceEditView.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using Microsoft.UI.Xaml; 3 | using Microsoft.UI.Xaml.Controls; 4 | using Microsoft.UI.Xaml.Media.Imaging; 5 | using Windows.Storage.Pickers; 6 | using WinRT.Interop; 7 | 8 | // To learn more about WinUI, the WinUI project structure, 9 | // and more about our project templates, see: http://aka.ms/winui-project-info. 10 | 11 | namespace LipUI.Pages.ServerSelection; 12 | 13 | internal sealed partial class ServerInstanceEditView : UserControl 14 | { 15 | 16 | 17 | public ServerInstance Server { get; private set; } 18 | 19 | private string? iconPath = null; 20 | 21 | public BitmapImage? CustomIcon { get; private set; } 22 | 23 | public ServerInstanceEditView(ServerInstance server) 24 | { 25 | this.Server = server; 26 | InitializeComponent(); 27 | } 28 | 29 | private async ValueTask RefreshIcon(string? version, string? iconPath) 30 | { 31 | var image = await ServerIcon.GetIcon(version, iconPath); 32 | image.DecodePixelType = DecodePixelType.Logical; 33 | image.DecodePixelHeight = (int)IconImage.Height; 34 | image.DecodePixelWidth = (int)IconImage.Width; 35 | IconImage.Source = image; 36 | CustomIcon = image; 37 | } 38 | 39 | private void UserControl_Loaded(object sender, RoutedEventArgs e) 40 | { 41 | NameInput.Text = Server.Name; 42 | DescriptionInput.Text = Server.Description; 43 | VersionInput.Text = Server.Version; 44 | WorkingDirectoryInput.Text = Server.WorkingDirectory; 45 | 46 | DispatcherQueue.TryEnqueue(async () => await RefreshIcon(Server.Version, Server.Icon)); 47 | } 48 | 49 | private async void VersionInput_TextChanged(object sender, TextChangedEventArgs e) 50 | => await RefreshIcon(VersionInput.Text, iconPath); 51 | 52 | private async void EditIconButton_Click(object sender, RoutedEventArgs e) 53 | { 54 | var picker = new FileOpenPicker() 55 | { 56 | SuggestedStartLocation = PickerLocationId.PicturesLibrary, 57 | FileTypeFilter = { ".jpeg", ".jpg", ".png", ".bmp", ".tiff", ".ico" } 58 | }; 59 | InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle((Application.Current as App)!.m_window)); 60 | 61 | var file = await picker.PickSingleFileAsync(); 62 | if (file is not null) 63 | iconPath = file.Path; 64 | 65 | await RefreshIcon(VersionInput.Text, iconPath); 66 | } 67 | 68 | public void CommitServerProperies() 69 | { 70 | Server.Name = NameInput.Text; 71 | Server.Description = DescriptionInput.Text; 72 | Server.Version = VersionInput.Text; 73 | Server.WorkingDirectory = WorkingDirectoryInput.Text; 74 | 75 | if (iconPath is not null) 76 | { 77 | var dir = Path.Combine(Server.WorkingDirectory, DefaultSettings.DataDirectory); 78 | 79 | if (Directory.Exists(dir) is false) 80 | Directory.CreateDirectory(dir); 81 | 82 | var dest = Path.Combine(dir, $"icon{new FileInfo(iconPath).Extension}"); 83 | 84 | File.Copy(iconPath, dest, true); 85 | 86 | Server.Icon = dest; 87 | return; 88 | } 89 | 90 | Server.Icon = iconPath; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ServerSelection/ServerInstanceView.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Media; 4 | using Microsoft.UI.Xaml.Media.Animation; 5 | 6 | // To learn more about WinUI, the WinUI project structure, 7 | // and more about our project templates, see: http://aka.ms/winui-project-info. 8 | 9 | namespace LipUI.Pages.ServerSelection; 10 | 11 | internal sealed partial class ServerInstanceView : UserControl 12 | { 13 | public ServerInstance ServerInstance { get; private set; } 14 | 15 | public void SetIconImageSource(ImageSource source) 16 | => Image.Source = source; 17 | 18 | public ServerInstanceView(ServerInstance instance) 19 | { 20 | InitializeComponent(); 21 | ServerInstance = instance; 22 | 23 | RefreshUI(); 24 | 25 | DispatcherQueue.TryEnqueue(async () => 26 | { 27 | Image.Source = await ServerIcon.GetIcon(instance); 28 | }); 29 | } 30 | 31 | public void RefreshUI() 32 | { 33 | Text.Text = $"{ServerInstance.Name}{Environment.NewLine}{ServerInstance.Description}{Environment.NewLine}{ServerInstance.Version}"; 34 | } 35 | 36 | public Storyboard SelectStoryboard => _selectStoryboard; 37 | public Storyboard UnselectStoryboard => _unselectStoryboard; 38 | } 39 | -------------------------------------------------------------------------------- /src/LipUI/Pages/ServerSelection/ServerSelectionPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 45 | 46 | 47 | 54 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 84 | 85 | 89 | 90 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Settings/GeneralSettingsView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 18 | 19 | 23 | 24 | 30 | 31 | 32 | 33 | 36 | 37 | 41 | 42 | 48 | 49 | 50 | 51 | 54 | 55 | 59 | 60 | 66 | 67 | 68 | 69 | 72 | 73 | 77 | 78 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Settings/GeneralSettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using LipUI.Models; 2 | using Microsoft.UI.Xaml; 3 | using Microsoft.UI.Xaml.Controls; 4 | 5 | // To learn more about WinUI, the WinUI project structure, 6 | // and more about our project templates, see: http://aka.ms/winui-project-info. 7 | 8 | namespace LipUI.Pages.Settings; 9 | 10 | internal sealed partial class GeneralSettingsView : UserControl 11 | { 12 | public GeneralSettingsView() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | private void GithubProxyInput_TextChanged(object sender, TextChangedEventArgs e) 18 | { 19 | Main.Config.GeneralSettings.GithubProxy = GithubProxyInput.Text; 20 | } 21 | 22 | private void LipIndexApiInput_TextChanged(object sender, TextChangedEventArgs e) 23 | { 24 | Main.Config.GeneralSettings.LipIndexApiKey = LipIndexApiInput.Text; 25 | } 26 | 27 | private void LipPathInput_TextChanged(object sender, TextChangedEventArgs e) 28 | { 29 | Main.Config.GeneralSettings.LipPath = LipPathInput.Text; 30 | } 31 | 32 | private void GithubApiInput_TextChanged(object sender, TextChangedEventArgs e) 33 | { 34 | Main.Config.GeneralSettings.GithubApiKey = GithubApiInput.Text; 35 | } 36 | 37 | private void GithubProxyInput_Loading(FrameworkElement sender, object args) 38 | { 39 | GithubProxyInput.Text = Main.Config.GeneralSettings.GithubProxy; 40 | } 41 | 42 | private void LipIndexApiInput_Loading(FrameworkElement sender, object args) 43 | { 44 | LipIndexApiInput.Text = Main.Config.GeneralSettings.LipIndexApiKey; 45 | } 46 | 47 | private void GithubApiInput_Loading(FrameworkElement sender, object args) 48 | { 49 | GithubApiInput.Text = Main.Config.GeneralSettings.GithubApiKey; 50 | } 51 | 52 | private void LipPathInput_Loading(FrameworkElement sender, object args) 53 | { 54 | LipPathInput.Text = Main.Config.GeneralSettings.LipPath; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Settings/LipSettingsView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Settings/LipSettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace LipUI.Pages.Settings 7 | { 8 | public sealed partial class LipSettingsView : UserControl 9 | { 10 | public LipSettingsView() 11 | { 12 | this.InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/LipUI/Pages/Settings/SettingsAndAboutView.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 55 | 56 | 57 | 58 | 63 | 64 | 69 | 70 | 74 | 75 | 79 | 80 | 84 | 85 | 89 | 90 | 94 | 95 | 96 | 97 | 101 | 102 | 106 | 107 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/LipUI/Properties/PublishProfiles/win10-arm64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | ARM64 9 | win10-arm64 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | False 14 | True 15 | 19 | 20 | -------------------------------------------------------------------------------- /src/LipUI/Properties/PublishProfiles/win10-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | x64 9 | win10-x64 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | False 14 | True 15 | 19 | 20 | -------------------------------------------------------------------------------- /src/LipUI/Properties/PublishProfiles/win10-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | FileSystem 8 | x86 9 | win10-x86 10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ 11 | true 12 | False 13 | False 14 | True 15 | 19 | 20 | -------------------------------------------------------------------------------- /src/LipUI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LipUI (Package)": { 4 | "commandName": "MsixPackage" 5 | }, 6 | "LipUI (Unpackaged)": { 7 | "commandName": "Project" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/LipUI/Protocol/LipIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace LipUI.Protocol; 5 | 6 | public class LipIndex 7 | { 8 | [JsonPropertyName("apiVersion")] 9 | public string ApiVersion { get; set; } = string.Empty; 10 | 11 | [JsonPropertyName("data")] 12 | public LipIndexData Data { get; set; } = new(); 13 | 14 | public class LipIndexData 15 | { 16 | [JsonPropertyName("pageIndex")] 17 | public int PageIndex { get; set; } 18 | 19 | [JsonPropertyName("totalPages")] 20 | public int TotalPages { get; set; } 21 | 22 | [JsonPropertyName("items")] 23 | public List Items { get; set; } = []; 24 | 25 | public class LipToothItem 26 | { 27 | [JsonPropertyName("repoPath")] 28 | public string RepoPath { get; set; } = string.Empty; 29 | 30 | [JsonPropertyName("repoOwner")] 31 | public string RepoOwner { get; set; } = string.Empty; 32 | 33 | [JsonPropertyName("repoName")] 34 | public string RepoName { get; set; } = string.Empty; 35 | 36 | [JsonPropertyName("latestVersion")] 37 | public string LatestVersion { get; set; } = string.Empty; 38 | 39 | [JsonPropertyName("latestVersionReleasedAt")] 40 | public string LatestVersionReleasedAt { get; set; } = string.Empty; 41 | 42 | [JsonPropertyName("name")] 43 | public string Name { get; set; } = string.Empty; 44 | 45 | [JsonPropertyName("description")] 46 | public string Description { get; set; } = string.Empty; 47 | 48 | [JsonPropertyName("author")] 49 | public string Author { get; set; } = string.Empty; 50 | 51 | [JsonPropertyName("tags")] 52 | public IReadOnlyList Tags { get; set; } = []; 53 | 54 | [JsonPropertyName("avatarUrl")] 55 | public string AvatarUrl { get; set; } = string.Empty; 56 | 57 | [JsonPropertyName("repoCreatedAt")] 58 | public string RepoCreatedAt { get; set; } = string.Empty; 59 | 60 | [JsonPropertyName("starCount")] 61 | public int StarCount { get; set; } 62 | } 63 | } 64 | 65 | public static LipIndex Deserialize(string json) 66 | => JsonSerializer.Deserialize(json) ?? throw new NullReferenceException(); 67 | } 68 | -------------------------------------------------------------------------------- /src/LipUI/Protocol/LipTooth.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace LipUI.Protocol; 5 | 6 | public struct LipToothVersion() 7 | { 8 | [JsonPropertyName("version")] 9 | public string Version { get; set; } = string.Empty; 10 | 11 | [JsonPropertyName("releasedAt")] 12 | public string ReleasedAt { get; set; } = string.Empty; 13 | } 14 | 15 | public class LipTooth 16 | { 17 | [JsonPropertyName("apiVersion")] 18 | public string ApiVersion { get; set; } = string.Empty; 19 | 20 | [JsonPropertyName("data")] 21 | public LipToothData Data { get; set; } = new(); 22 | 23 | public class LipToothData 24 | { 25 | [JsonPropertyName("repoPath")] 26 | public string RepoPath { get; set; } = string.Empty; 27 | 28 | [JsonPropertyName("repoOwner")] 29 | public string RepoOwner { get; set; } = string.Empty; 30 | 31 | [JsonPropertyName("repoName")] 32 | public string RepoName { get; set; } = string.Empty; 33 | 34 | [JsonPropertyName("version")] 35 | public string Version { get; set; } = string.Empty; 36 | 37 | [JsonPropertyName("releasedAt")] 38 | public string ReleasedAt { get; set; } = string.Empty; 39 | 40 | [JsonPropertyName("name")] 41 | public string Name { get; set; } = string.Empty; 42 | 43 | [JsonPropertyName("description")] 44 | public string Description { get; set; } = string.Empty; 45 | 46 | [JsonPropertyName("author")] 47 | public string Author { get; set; } = string.Empty; 48 | 49 | [JsonPropertyName("tags")] 50 | public IReadOnlyList Tags { get; set; } = new List(); 51 | 52 | [JsonPropertyName("avatarUrl")] 53 | public string AvatarUrl { get; set; } = string.Empty; 54 | 55 | [JsonPropertyName("repoCreatedAt")] 56 | public string RepoCreatedAt { get; set; } = string.Empty; 57 | 58 | [JsonPropertyName("starCount")] 59 | public int StarCount { get; set; } 60 | 61 | [JsonPropertyName("versions")] 62 | public IReadOnlyList Versions { get; set; } = new List(); 63 | 64 | [JsonPropertyName("downloadCount")] 65 | public int DownloadCount { get; set; } 66 | } 67 | 68 | public static LipTooth Deserialize(string json) 69 | => JsonSerializer.Deserialize(json) ?? throw new NullReferenceException(); 70 | } 71 | -------------------------------------------------------------------------------- /src/LipUI/Protocol/LocalToothItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace LipUI.Protocol; 4 | 5 | internal class ToothPackage 6 | { 7 | public class ToothPackageInfo 8 | { 9 | [JsonPropertyName("name")] 10 | public string Name { get; set; } = string.Empty; 11 | 12 | [JsonPropertyName("description")] 13 | public string Description { get; set; } = string.Empty; 14 | 15 | [JsonPropertyName("author")] 16 | public string Author { get; set; } = string.Empty; 17 | 18 | [JsonPropertyName("tags")] 19 | public List Tags { get; set; } = []; 20 | } 21 | 22 | public class ToothPackageFiles 23 | { 24 | [JsonPropertyName("place")] 25 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 26 | public List? Place { get; set; } 27 | 28 | [JsonPropertyName("preserve")] 29 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 30 | public List? Preserve { get; set; } 31 | 32 | [JsonPropertyName("remove")] 33 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 34 | public List? Remove { get; set; } 35 | } 36 | 37 | public class Place 38 | { 39 | [JsonPropertyName("src")] 40 | public string Src { get; set; } = string.Empty; 41 | 42 | [JsonPropertyName("dest")] 43 | public string Dest { get; set; } = string.Empty; 44 | } 45 | 46 | public class ToothPackageCommands 47 | { 48 | [JsonPropertyName("pre_install")] 49 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 50 | public List? PreInstall { get; set; } 51 | 52 | [JsonPropertyName("post_install")] 53 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 54 | public List? PostInstall { get; set; } 55 | 56 | [JsonPropertyName("pre_uninstall")] 57 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 58 | public List? PreUninstall { get; set; } 59 | 60 | [JsonPropertyName("post_uninstall")] 61 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 62 | public List? PostUninstall { get; set; } 63 | } 64 | 65 | 66 | [JsonPropertyName("format_version")] 67 | public int FormatVersion { get; set; } 68 | 69 | [JsonPropertyName("tooth")] 70 | public string Tooth { get; set; } = string.Empty; 71 | 72 | [JsonPropertyName("version")] 73 | public string Version { get; set; } = string.Empty; 74 | 75 | [JsonPropertyName("info")] 76 | public ToothPackageInfo Info { get; set; } = new(); 77 | 78 | [JsonPropertyName("commands")] 79 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 80 | public ToothPackageCommands? Commands { get; set; } 81 | 82 | [JsonPropertyName("dependencies")] 83 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 84 | public Dictionary? Dependencies { get; set; } 85 | 86 | [JsonPropertyName("prerequisites")] 87 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 88 | public Dictionary? Prerequisites { get; set; } 89 | 90 | [JsonPropertyName("files")] 91 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 92 | public ToothPackageFiles? Files { get; set; } 93 | } 94 | -------------------------------------------------------------------------------- /src/LipUI/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tooth.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_version": 2, 3 | "tooth": "github.com/lippkg/LipUI", 4 | "version": "0.5.1", 5 | "info": { 6 | "name": "LipUI", 7 | "description": "A GUI client for lip", 8 | "author": "lippkg", 9 | "tags": [ 10 | "lipui" 11 | ] 12 | }, 13 | "platforms": [ 14 | { 15 | "goos": "windows", 16 | "goarch": "amd64", 17 | "asset_url": "https://github.com/lippkg/LipUI/releases/download/v0.5.1/LipUI-win-x64.zip", 18 | "files": { 19 | "place": [ 20 | { 21 | "src": "lipui/*", 22 | "dest": ".autoupdate" 23 | } 24 | ] 25 | }, 26 | "commands": { 27 | "post-install": [ 28 | "cmd /c ./LipUI.exe" 29 | ] 30 | } 31 | }, 32 | { 33 | "goos": "windows", 34 | "goarch": "arm64", 35 | "asset_url": "https://github.com/lippkg/LipUI/releases/download/v0.5.1/LipUI-win-arm64.zip", 36 | "files": { 37 | "place": [ 38 | { 39 | "src": "lipui/*", 40 | "dest": ".autoupdate" 41 | } 42 | ] 43 | }, 44 | "commands": { 45 | "post-install": [ 46 | "cmd /c ./LipUI.exe" 47 | ] 48 | } 49 | } 50 | ] 51 | } --------------------------------------------------------------------------------