├── resources ├── app.png └── app_en.png ├── src ├── ComputerLock │ ├── icon.ico │ ├── wwwroot │ │ ├── icon.ico │ │ ├── images │ │ │ ├── icon.png │ │ │ └── wxPay.jpg │ │ ├── initialize-screen.css │ │ ├── index.html │ │ └── app.css │ ├── Enums │ │ ├── LangEnum.cs │ │ ├── HotkeyType.cs │ │ ├── PasswordBoxActiveMethodEnum.cs │ │ ├── CloseWindowAction.cs │ │ ├── ScreenLocationEnum.cs │ │ ├── HotkeyModifiers.cs │ │ ├── ScreenUnlockMethods.cs │ │ ├── ThemeEnum.cs │ │ ├── LockStatusDisplay.cs │ │ └── PowerActionType.cs │ ├── Interfaces │ │ ├── IWindowsMessageBox.cs │ │ ├── IScreenLockService.cs │ │ └── IGlobalLockService.cs │ ├── Models │ │ ├── Hotkey.cs │ │ └── HotkeyTools.cs │ ├── WebApp.razor │ ├── Services │ │ ├── ThemeSwitchService.cs │ │ ├── PopupService.cs │ │ ├── ScreenLockBaseService.cs │ │ ├── HotkeyScreenLockService.cs │ │ ├── PasswordScreenLockService.cs │ │ └── GlobalLockService.cs │ ├── Platforms │ │ ├── WindowsMessageBox.cs │ │ ├── BaseHook.cs │ │ ├── VersionLogChecker.cs │ │ ├── PowerManager.cs │ │ ├── HotkeyHook.cs │ │ ├── TaskManagerHook.cs │ │ ├── MouseHook.cs │ │ ├── AutostartHook.cs │ │ ├── UserActivityMonitor.cs │ │ ├── SystemKeyHook.cs │ │ └── WinApi.cs │ ├── _Imports.razor │ ├── 首次运行请看这里_Readme.txt │ ├── GlobalUsings.cs │ ├── App.xaml │ ├── Components │ │ ├── TitleBar.razor │ │ ├── HotkeySetting.razor │ │ ├── HotkeyInput.razor │ │ ├── VersionHistoryDialog.razor │ │ ├── SetPassword.razor.cs │ │ ├── SetPassword.razor │ │ ├── Settings │ │ │ ├── UnlockSettings.razor.css │ │ │ ├── UnlockSettings.razor.cs │ │ │ ├── LockSettings.razor.cs │ │ │ ├── GeneralSettings.razor │ │ │ ├── GeneralSettings.razor.cs │ │ │ ├── UnlockSettings.razor │ │ │ └── LockSettings.razor │ │ ├── Donation.razor.cs │ │ ├── TitleBar.razor.cs │ │ ├── HotkeyInput.razor.cs │ │ ├── About.razor.cs │ │ ├── ResetPassword.razor │ │ ├── ResetPassword.razor.cs │ │ ├── About.razor │ │ ├── HotkeySetting.razor.cs │ │ └── Donation.razor │ ├── AssemblyInfo.cs │ ├── BreathingLightHelper.cs │ ├── WindowPopup.xaml │ ├── Configuration │ │ ├── AppBase.cs │ │ ├── AppSettingsProvider.cs │ │ └── AppSettings.cs │ ├── Update │ │ └── UpdateHelper.cs │ ├── WindowPopup.xaml.cs │ ├── WindowMain.xaml │ ├── WindowBlankScreen.xaml │ ├── Shared │ │ ├── MainLayout.razor │ │ └── MainLayout.razor.cs │ ├── ComputerLock.csproj │ ├── WindowBlankScreen.xaml.cs │ ├── app.manifest │ ├── WindowLockScreen.xaml │ ├── Resources │ │ ├── Resource.Designer.cs │ │ └── Resource.resx │ ├── App.xaml.cs │ ├── WindowMain.xaml.cs │ └── WindowLockScreen.xaml.cs └── ComputerLock.slnx ├── CHANGELOG.md ├── .github ├── workflows │ ├── build.yml │ ├── release.yml │ └── autoformat.yml └── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug-report.yml ├── LICENSE ├── README.md ├── VERSION_HISTORY.md ├── README_en.md └── .gitignore /resources/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/resources/app.png -------------------------------------------------------------------------------- /resources/app_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/resources/app_en.png -------------------------------------------------------------------------------- /src/ComputerLock/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/src/ComputerLock/icon.ico -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/src/ComputerLock/wwwroot/icon.ico -------------------------------------------------------------------------------- /src/ComputerLock/Enums/LangEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | public enum LangEnum 3 | { 4 | zh = 0, 5 | en = 1, 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | > [2.3.7] - 2025-10-15 2 | * ✨ 修改:单独配置解锁快捷键时,在非锁定状态下,该快捷键会被程序拦截,无法使用 3 | 4 | 5 | 6 | * 🍭 [查看完整版本记录](VERSION_HISTORY.md) -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/src/ComputerLock/wwwroot/images/icon.png -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/images/wxPay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiuLing-zhang/ComputerLock/HEAD/src/ComputerLock/wwwroot/images/wxPay.jpg -------------------------------------------------------------------------------- /src/ComputerLock/Enums/HotkeyType.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | 3 | public enum HotkeyType 4 | { 5 | Lock = 90, 6 | Unlock = 91, 7 | } -------------------------------------------------------------------------------- /src/ComputerLock/Interfaces/IWindowsMessageBox.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Interfaces; 2 | 3 | public interface IWindowsMessageBox 4 | { 5 | void Show(string message); 6 | } -------------------------------------------------------------------------------- /src/ComputerLock/Enums/PasswordBoxActiveMethodEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | [Flags] 3 | public enum PasswordBoxActiveMethodEnum 4 | { 5 | KeyboardDown = 1, 6 | MouseDown = 2 7 | } -------------------------------------------------------------------------------- /src/ComputerLock/Enums/CloseWindowAction.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | 3 | /// 4 | /// 窗口关闭时的操作 5 | /// 6 | public enum CloseWindowAction 7 | { 8 | MinimizeToTray, 9 | Exit, 10 | } -------------------------------------------------------------------------------- /src/ComputerLock/Enums/ScreenLocationEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | public enum ScreenLocationEnum 3 | { 4 | Center = 0, 5 | TopLeft = 1, 6 | TopRight = 2, 7 | BottomLeft = 3, 8 | BottomRight = 4 9 | } -------------------------------------------------------------------------------- /src/ComputerLock/Models/Hotkey.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Models; 2 | 3 | public class Hotkey(HotkeyModifiers modifiers, Keys key) 4 | { 5 | public HotkeyModifiers Modifiers { get; set; } = modifiers; 6 | public Keys Key { get; set; } = key; 7 | } -------------------------------------------------------------------------------- /src/ComputerLock/Enums/HotkeyModifiers.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | 3 | /// 4 | /// 热键修饰键 5 | /// 6 | [Flags] 7 | public enum HotkeyModifiers : uint 8 | { 9 | Alt = 1, 10 | Control = 2, 11 | Shift = 4, 12 | Win = 8 13 | } 14 | -------------------------------------------------------------------------------- /src/ComputerLock/Interfaces/IScreenLockService.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Interfaces; 2 | 3 | /// 4 | /// 屏幕锁定接口,负责屏幕窗口的锁定和解锁 5 | /// 6 | internal interface IScreenLockService 7 | { 8 | event EventHandler? OnUnlock; 9 | bool Lock(bool showAnimation); 10 | void Unlock(); 11 | } -------------------------------------------------------------------------------- /src/ComputerLock.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ComputerLock/Enums/ScreenUnlockMethods.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace ComputerLock.Enums; 4 | 5 | /// 6 | /// 屏幕解锁方法 7 | /// 8 | public enum ScreenUnlockMethods 9 | { 10 | [Description("快捷键解锁")] 11 | Hotkey = 1, 12 | [Description("密码解锁")] 13 | Password = 2 14 | } -------------------------------------------------------------------------------- /src/ComputerLock/WebApp.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager Navigation 2 | 3 | 4 | 5 | 6 | 7 | @{ 8 | Navigation.NavigateTo("/lock-settings"); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/ComputerLock/Enums/ThemeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | 3 | /// 4 | /// 颜色主题 5 | /// 6 | public enum ThemeEnum 7 | { 8 | /// 9 | /// 跟随系统 10 | /// 11 | System = 0, 12 | /// 13 | /// 浅色主题 14 | /// 15 | Light = 1, 16 | /// 17 | /// 深色主题 18 | /// 19 | Dark = 2 20 | } -------------------------------------------------------------------------------- /src/ComputerLock/Interfaces/IGlobalLockService.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Interfaces; 2 | 3 | /// 4 | /// 锁定器接口,全局锁定:包括鼠标、键盘、任务管理器、系统快捷键等 5 | /// 6 | public interface IGlobalLockService : IDisposable 7 | { 8 | bool IsLocked { get; } 9 | void Lock(); 10 | void Unlock(); 11 | 12 | /// 13 | /// 更新自动锁定设置 14 | /// 15 | void UpdateAutoLockSettings(); 16 | } -------------------------------------------------------------------------------- /src/ComputerLock/Services/ThemeSwitchService.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Services; 2 | 3 | /// 4 | /// 主题切换服务 5 | /// 6 | public class ThemeSwitchService 7 | { 8 | public ThemeEnum Theme { get; private set; } 9 | 10 | public event Action? OnThemeChanged; 11 | 12 | public void SetDarkMode(ThemeEnum theme) 13 | { 14 | Theme = theme; 15 | OnThemeChanged?.Invoke(theme); 16 | } 17 | } -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/WindowsMessageBox.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | 3 | namespace ComputerLock.Platforms; 4 | internal class WindowsMessageBox(IStringLocalizer lang) : IWindowsMessageBox 5 | { 6 | public void Show(string text) 7 | { 8 | MessageBox.Show(text, lang["Title"], MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, System.Windows.Forms.MessageBoxOptions.DefaultDesktopOnly); 9 | } 10 | } -------------------------------------------------------------------------------- /src/ComputerLock/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.JSInterop 8 | @using MudBlazor 9 | @using ComputerLock 10 | @using ComputerLock.Shared 11 | @using ComputerLock.Components 12 | @using Microsoft.Extensions.Localization 13 | @using MudExtensions -------------------------------------------------------------------------------- /src/ComputerLock/首次运行请看这里_Readme.txt: -------------------------------------------------------------------------------- 1 | 首次运行时需要安装 .Net8 运行时,下载地址: 2 | https://aka.ms/dotnet/8.0/windowsdesktop-runtime-win-x64.exe 3 | 4 | Windows 7、8 部分用户需要安装依赖项才能运行,参考: 5 | https://learn.microsoft.com/zh-cn/dotnet/core/install/windows?tabs=net70#additional-deps 6 | 7 | 如果程序无法启动,请检查是否已安装 WebView2 Runtime,(推荐使用【常青引导程序】进行安装,因为独立安装包已经不支持 Windows 7)下载地址: 8 | https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/#download 9 | 10 | !!!忘记密码!!! 11 | 如果忘记密码,可以通过删除配置文件来重置设置。 12 | C:\Users\Username\AppData\Local\ComputerLock\config.json 13 | -------------------------------------------------------------------------------- /src/ComputerLock/Enums/LockStatusDisplay.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Enums; 2 | 3 | /// 4 | /// 锁定标识模式 5 | /// 6 | [Flags] 7 | public enum LockStatusDisplay 8 | { 9 | /// 10 | /// 不显示锁定标识 11 | /// 12 | None = 1, 13 | /// 14 | /// 顶部呼吸灯 15 | /// 16 | BreathingTop = 2, 17 | /// 18 | /// 左上角圆点 19 | /// 20 | DotTopLeft = 4, 21 | /// 22 | /// 右上角圆点 23 | /// 24 | DotTopRight = 8, 25 | } -------------------------------------------------------------------------------- /src/ComputerLock/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using ComputerLock.Components; 2 | global using ComputerLock.Configuration; 3 | global using ComputerLock.Enums; 4 | global using ComputerLock.Models; 5 | global using ComputerLock.Platforms; 6 | global using ComputerLock.Resources; 7 | global using ComputerLock.Services; 8 | global using JiuLing.CommonLibs.ExtensionMethods; 9 | global using JiuLing.CommonLibs.Log; 10 | global using JiuLing.CommonLibs.Text; 11 | global using Microsoft.AspNetCore.Components; 12 | global using Microsoft.Extensions.Localization; 13 | global using MudBlazor; -------------------------------------------------------------------------------- /src/ComputerLock/Enums/PowerActionType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ComputerLock.Enums; 9 | 10 | /// 11 | /// 电源操作类型 12 | /// 13 | public enum PowerActionType 14 | { 15 | /// 16 | /// 关机 17 | /// 18 | [Description("关机")] 19 | Shutdown = 1, 20 | 21 | /// 22 | /// 休眠 23 | /// 24 | [Description("休眠")] 25 | Hibernate = 2 26 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - '**.md' 8 | - '.github/**' 9 | - 'resources/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: windows-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: 9.0.x 23 | 24 | - name: Restore dependencies 25 | run: dotnet restore .\src 26 | 27 | - name: Build 28 | run: dotnet build .\src 29 | -------------------------------------------------------------------------------- /src/ComputerLock/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/TitleBar.razor: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 | @* *@ 11 | 12 | 16 |
-------------------------------------------------------------------------------- /src/ComputerLock/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/HotkeySetting.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @(_text) 5 | 6 | 7 | 8 | @(Lang["Cancel"]) 9 | 12 | @(Lang["Save"]) 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议 2 | description: 提议一个好的想法,帮我们完善程序 3 | labels: [功能建议] 4 | assignees: [JiuLing-zhang] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: 功能描述 10 | description: 用简短的话描述下功能建议。 11 | placeholder: 功能描述 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: solution 16 | attributes: 17 | label: 解决方案 18 | description: 请描述下你理想的解决方案是什么。 19 | placeholder: 解决方案 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: remark 24 | attributes: 25 | label: 补充说明 26 | description: 如果还有其它需要补充的,请说明。 27 | placeholder: 补充说明 28 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/HotkeyInput.razor: -------------------------------------------------------------------------------- 1 | 2 | @(Title) 3 |
4 | 8 | @(_lockHotkeyDisplay) 9 | 10 | @if (Hotkey.IsNotEmpty()) 11 | { 12 | 16 | @(Lang["Delete"]) 17 | 18 | } 19 |
20 |
21 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/VersionHistoryDialog.razor: -------------------------------------------------------------------------------- 1 | @using System.IO 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | @code 12 | { 13 | private string _value = null!; 14 | 15 | [CascadingParameter] 16 | private IMudDialogInstance MudDialog { get; set; } = null!; 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | await base.OnInitializedAsync(); 21 | const string markdownFileName = "VERSION_HISTORY.md"; 22 | if (!File.Exists(markdownFileName)) 23 | { 24 | _value = "什么也没找到\ud83d\ude01\ud83d\ude01"; 25 | return; 26 | } 27 | _value = await File.ReadAllTextAsync(markdownFileName); 28 | } 29 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/SetPassword.razor.cs: -------------------------------------------------------------------------------- 1 | using DialogResult = MudBlazor.DialogResult; 2 | 3 | namespace ComputerLock.Components; 4 | public partial class SetPassword 5 | { 6 | [CascadingParameter] 7 | private IMudDialogInstance MudDialog { get; set; } = default!; 8 | 9 | [Inject] 10 | private IStringLocalizer Lang { get; set; } = default!; 11 | 12 | [Inject] 13 | private ISnackbar Snackbar { get; set; } = default!; 14 | 15 | private string _password = ""; 16 | 17 | protected override async Task OnInitializedAsync() 18 | { 19 | await base.OnInitializedAsync(); 20 | } 21 | 22 | private void Submit() 23 | { 24 | if (_password.IsEmpty()) 25 | { 26 | Snackbar.Add(Lang["PasswordEmpty"], Severity.Error); 27 | return; 28 | } 29 | 30 | var password = JiuLing.CommonLibs.Security.MD5Utils.GetStringValueToLower(_password); 31 | MudDialog.Close(DialogResult.Ok(password)); 32 | Snackbar.Add(Lang["SaveOk"], Severity.Success); 33 | } 34 | 35 | private void Cancel() => MudDialog.Cancel(); 36 | } -------------------------------------------------------------------------------- /src/ComputerLock/BreathingLightHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media.Animation; 4 | 5 | namespace ComputerLock; 6 | public static class BreathingLightHelper 7 | { 8 | public static void InitializeBreathingLight(UIElement element) 9 | { 10 | element.Visibility = Visibility.Visible; 11 | StartBreathingAnimation(element); 12 | } 13 | 14 | private static void StartBreathingAnimation(UIElement element) 15 | { 16 | var animation = new Storyboard(); 17 | 18 | var opacityAnimation = new DoubleAnimation 19 | { 20 | From = 0.1, 21 | To = 0.5, 22 | Duration = TimeSpan.FromSeconds(1), 23 | AutoReverse = true, 24 | RepeatBehavior = RepeatBehavior.Forever 25 | }; 26 | 27 | Storyboard.SetTarget(opacityAnimation, element); 28 | Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity")); 29 | 30 | animation.Children.Add(opacityAnimation); 31 | animation.Begin(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 九零 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 报告错误 2 | description: 报告一个错误 3 | labels: [错误] 4 | assignees: [JiuLing-zhang] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: 问题描述 10 | description: 请简短描述下你所遇到的错误。 11 | placeholder: 问题描述 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: repro-steps 16 | attributes: 17 | label: 重现步骤 18 | description: 请描述问题出现的操作步骤。 19 | placeholder: | 20 | 1. 点击"按钮A" 21 | 2. 点击"按钮B" 22 | 3. 出现错误 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: remark 27 | attributes: 28 | label: 问题补充 29 | description: 如果可以,请补充提供下错误截图等。 30 | placeholder: 问题补充 31 | - type: input 32 | id: os 33 | attributes: 34 | label: 操作系统 35 | description: 请提供错误出现的操作系统。 36 | placeholder: 例如 Windows 10、Windows 11 37 | validations: 38 | required: true 39 | - type: input 40 | id: version 41 | attributes: 42 | label: 程序版本 43 | description: 错误出现在哪个版本中。 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/SetPassword.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @(Lang["SetPassword"]) 7 | 11 | 12 | 13 | 16 | 17 | 22 | @(Lang["SetPasswordFinished"]) 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/BaseHook.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ComputerLock.Platforms; 4 | 5 | /// 6 | /// Windows 系统输入钩子基类 7 | /// 8 | internal abstract class WindowsInputHook : IDisposable 9 | { 10 | protected int _hookId; 11 | protected readonly WinApi.HookDelegate _hookCallback; 12 | 13 | protected WindowsInputHook() 14 | { 15 | _hookCallback = HookCallback; 16 | } 17 | 18 | protected abstract int HookType { get; } 19 | 20 | protected abstract int HookCallback(int nCode, int wParam, IntPtr lParam); 21 | 22 | /// 23 | /// 安装系统钩子 24 | /// 25 | public void InstallHook() 26 | { 27 | using Process curProcess = Process.GetCurrentProcess(); 28 | string? moduleName = curProcess.MainModule?.ModuleName; 29 | if (moduleName == null) 30 | { 31 | return; 32 | } 33 | _hookId = WinApi.SetWindowsHookEx(HookType, _hookCallback, WinApi.GetModuleHandle(moduleName), 0); 34 | } 35 | 36 | public void Dispose() 37 | { 38 | WinApi.UnhookWindowsHookEx(_hookId); 39 | } 40 | } -------------------------------------------------------------------------------- /src/ComputerLock/WindowPopup.xaml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/UnlockSettings.razor.css: -------------------------------------------------------------------------------- 1 | .unlock-card { 2 | position: relative; 3 | cursor: pointer; 4 | transition: box-shadow 0.2s; 5 | } 6 | 7 | .unlock-card.selected { 8 | box-shadow: 0 0 0 2px #ff8a65; 9 | border-radius: 8px; 10 | } 11 | 12 | .unlock-card:not(.selected):hover { 13 | cursor: pointer; 14 | } 15 | 16 | .unlock-card-wrapper:not(.selected):hover { 17 | cursor: pointer; 18 | } 19 | 20 | .card-header { 21 | display: flex; 22 | align-items: center; 23 | justify-content: space-between; 24 | margin-bottom: 0.8rem; 25 | min-height: 32px; 26 | } 27 | 28 | .card-title { 29 | font-weight: 500; 30 | font-size: 1.1rem; 31 | display: flex; 32 | align-items: center; 33 | } 34 | 35 | .card-content { 36 | position: relative; 37 | } 38 | 39 | .card-mask { 40 | position: absolute; 41 | left: -8px; 42 | top: -8px; 43 | right: -8px; 44 | bottom: -8px; 45 | background: rgba(255,255,255,0.5); 46 | z-index: 10; 47 | border-radius: 8px; 48 | pointer-events: all; 49 | transition: background 0.2s; 50 | cursor: pointer; 51 | } 52 | 53 | .dark-mode .card-mask { 54 | background: rgba(50,50,50,0.5); 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 透明锁屏 2 |
3 | 4 | ![](https://img.shields.io/github/license/JiuLing-zhang/ComputerLock) 5 | ![](https://img.shields.io/github/actions/workflow/status/JiuLing-zhang/ComputerLock/build.yml) 6 | [![](https://img.shields.io/github/v/release/JiuLing-zhang/ComputerLock)](https://github.com/JiuLing-zhang/ComputerLock/releases) 7 | 👉👉[English Version](./README_en.md) 8 | 9 |
10 | 11 | > ✨ 担心展示内容时被误操作打断? 12 | > 🎉 害怕离开后忘记锁屏导致隐私泄露? 13 | > 🎈 厌倦了千篇一律的系统锁屏界面? 14 | 15 | > **透明锁屏** 了解一下。 16 | 17 | 18 | 19 | ## 功能特点 20 | * 💖 **告别误操作**:锁屏状态下,屏幕内容依然可见,视频播放、PPT 演示等不受影响,再也不用担心误触键盘鼠标打断展示进程。 21 | * ⚡ **打破单调**:告别枯燥的系统锁屏界面,打造独一无二的锁屏风格。 22 | * 🌈 **防止休眠**:锁屏状态下,系统可保持唤醒状态,避免因休眠中断下载、传输等重要任务。 23 | * 🌀 **自动锁屏**:离开后无需手动操作,系统将根据设置自动锁定屏幕,有效防止他人窥探您的隐私。 24 | * 🔋 **智能关机**:锁屏状态下,经过指定时间后自动执行关机、休眠操作,避免长时间锁屏浪费资源。 25 | 26 | ## 适用场景 27 | * 🎈 **商务展示**:会议演示、产品展示时,避免误操作,确保展示流畅进行。 28 | * ✨ **娱乐休闲**:观看视频、欣赏音乐时,享受不间断的娱乐体验。 29 | * 🎁 **隐私保护**:使用电脑时,可手动或自动锁屏,保护您的隐私安全。 30 | 31 | ## 权限说明 32 | 🟡 程序在锁屏时,需要通过注册表屏蔽掉任务管理器,因此需要管理员权限运行。 33 | 🟡 程序会调用 `Windows API`,因此部分杀毒软件可能会报病毒。 34 | 35 | ## 忘记密码 36 | ✅ **如果忘记密码,可以通过`重置程序设置`功能恢复所有设置。** 37 | 38 | ## 历史更新 39 | 🍭 [查看完整版本记录](VERSION_HISTORY.md) -------------------------------------------------------------------------------- /src/ComputerLock/Configuration/AppBase.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace ComputerLock.Configuration; 6 | internal class AppBase 7 | { 8 | /// 9 | /// App路径(包含文件名) 10 | /// 11 | public static string ExecutablePath { get; } = Process.GetCurrentProcess().MainModule.FileName; 12 | 13 | public static string FriendlyName { get; } = AppDomain.CurrentDomain.FriendlyName; 14 | 15 | /// 16 | /// App Data文件夹路径 17 | /// 18 | private static readonly string DataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 19 | /// 20 | /// 配置文件路径 21 | /// 22 | public static string ConfigPath { get; } = Path.Combine(DataPath, FriendlyName, "config.json"); 23 | 24 | /// 25 | /// 版本文件路径 26 | /// 27 | public static string VersionFilePath { get; } = Path.Combine(DataPath, FriendlyName, "current_version"); 28 | 29 | /// 30 | /// 版本号 31 | /// 32 | public static Version Version { get; } = Assembly.GetExecutingAssembly().GetName().Version ?? throw new InvalidOperationException("App Version"); 33 | public static string VersionString { get; } = Version.ToString(); 34 | } -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/initialize-screen.css: -------------------------------------------------------------------------------- 1 | .initialize-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | background-color: #FFFFFF; 7 | } 8 | 9 | .initialize-loading { 10 | position: relative; 11 | width: 150px; 12 | height: 150px; 13 | } 14 | 15 | .initialize-icon, 16 | .initialize-loader { 17 | position: absolute; 18 | top: 50%; 19 | left: 50%; 20 | transform: translate(-50%, -50%); 21 | } 22 | 23 | .initialize-icon { 24 | width: 32px; 25 | height: 32px; 26 | background: url('icon.ico') no-repeat center center; 27 | background-size: contain; 28 | z-index: 2; 29 | } 30 | 31 | .initialize-loader { 32 | width: 120px; 33 | height: 120px; 34 | animation: spin 3s linear infinite, border-gradient 3s linear infinite; 35 | z-index: 1; 36 | } 37 | 38 | @keyframes spin { 39 | 0% { 40 | transform: translate(-50%, -50%) rotate(0deg); 41 | } 42 | 43 | 100% { 44 | transform: translate(-50%, -50%) rotate(360deg); 45 | } 46 | } 47 | 48 | @keyframes border-gradient { 49 | 0% { 50 | box-shadow: 0 0 10px 0 #fb8c00, 0 0 20px 0 #fb8c00, 0 0 30px 0 #fb8c00; 51 | } 52 | 53 | 100% { 54 | box-shadow: 0 0 10px 20px transparent, 0 0 20px 30px transparent, 0 0 30px 40px transparent; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Donation.razor.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Net.Http; 3 | using System.Text.Json; 4 | 5 | namespace ComputerLock.Components; 6 | 7 | public partial class Donation 8 | { 9 | [CascadingParameter] 10 | private IMudDialogInstance MudDialog { get; set; } = null!; 11 | private void Close() => MudDialog.Cancel(); 12 | 13 | [Inject] 14 | private ISnackbar Snackbar { get; set; } = null!; 15 | [Inject] 16 | private IStringLocalizer Lang { get; set; } = null!; 17 | 18 | [Inject] 19 | private ILogger Logger { get; set; } = null!; 20 | 21 | private int? _donationCount; 22 | private static readonly HttpClient HttpClient = new HttpClient(); 23 | protected override async Task OnInitializedAsync() 24 | { 25 | await base.OnInitializedAsync(); 26 | try 27 | { 28 | var json = await HttpClient.GetStringAsync(Resource.DonationListPath); 29 | using JsonDocument doc = JsonDocument.Parse(json); 30 | if (doc.RootElement.ValueKind == JsonValueKind.Array) 31 | { 32 | _donationCount = doc.RootElement.GetArrayLength(); 33 | } 34 | } 35 | catch (Exception ex) 36 | { 37 | Snackbar.Add(Lang["NetworkRequestFailed"], Severity.Error); 38 | Logger.Error(Lang["NetworkRequestFailed"], ex); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/ComputerLock/Models/HotkeyTools.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Models; 2 | 3 | public class HotkeyTools(IStringLocalizer lang) 4 | { 5 | public Hotkey? StringKeyToHotkey(string shortcutKey) 6 | { 7 | if (shortcutKey.IsEmpty()) 8 | { 9 | return null; 10 | } 11 | 12 | HotkeyModifiers modifiers = 0; 13 | if (shortcutKey.IndexOf("Ctrl") >= 0) 14 | { 15 | modifiers |= HotkeyModifiers.Control; 16 | } 17 | if (shortcutKey.IndexOf("Shift") >= 0) 18 | { 19 | modifiers |= HotkeyModifiers.Shift; 20 | } 21 | if (shortcutKey.IndexOf("Alt") >= 0) 22 | { 23 | modifiers |= HotkeyModifiers.Alt; 24 | } 25 | 26 | string[] keyParts = shortcutKey.Split([" + "], StringSplitOptions.None); 27 | int keyCode = int.Parse(keyParts[^1]); 28 | Keys key = (Keys)keyCode; 29 | return new Hotkey(modifiers, key); 30 | } 31 | 32 | public string StringKeyToDisplay(string shortcutKey) 33 | { 34 | if (shortcutKey.IsEmpty()) 35 | { 36 | return lang["Invalid"]; 37 | } 38 | 39 | string[] keyParts = shortcutKey.Split([" + "], StringSplitOptions.None); 40 | int keyCode = int.Parse(keyParts[^1]); 41 | keyParts[^1] = ((Keys)keyCode).ToString(); 42 | return string.Join(" + ", keyParts); 43 | } 44 | } -------------------------------------------------------------------------------- /src/ComputerLock/Update/UpdateHelper.cs: -------------------------------------------------------------------------------- 1 | using JiuLing.AutoUpgrade.Shell; 2 | using JiuLing.AutoUpgrade.Shell.Enums; 3 | using System.IO; 4 | using ThemeEnum = JiuLing.AutoUpgrade.Shared.ThemeEnum; 5 | 6 | namespace ComputerLock.Update; 7 | internal class UpdateHelper(AppSettings appSettings) 8 | { 9 | public async Task DoAsync(bool isBackgroundCheck) 10 | { 11 | var autoUpgradePath = Resource.AutoUpgradePath; 12 | if (autoUpgradePath.IsEmpty()) 13 | { 14 | return; 15 | } 16 | var theme = appSettings.AppTheme switch 17 | { 18 | Enums.ThemeEnum.System => ThemeEnum.System, 19 | Enums.ThemeEnum.Light => ThemeEnum.Light, 20 | Enums.ThemeEnum.Dark => ThemeEnum.Dark, 21 | _ => ThemeEnum.System 22 | }; 23 | var iconPath = @"wwwroot\icon.ico"; 24 | if (!File.Exists(iconPath)) 25 | { 26 | iconPath = ""; 27 | } 28 | 29 | await UpgradeFactory.CreateHttpApp(autoUpgradePath) 30 | .SetUpgrade(build => 31 | { 32 | build.WithBackgroundCheck(isBackgroundCheck) 33 | .WithTheme(theme) 34 | .WithSignCheck(true) 35 | .WithLang(appSettings.Lang.ToString()) 36 | .WithIcon(iconPath) 37 | .WithVersionFormat(VersionFormatEnum.MajorMinorBuild); 38 | }).RunAsync(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/ComputerLock/Configuration/AppSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | 4 | namespace ComputerLock.Configuration; 5 | public class AppSettingsProvider(IStringLocalizer lang) 6 | { 7 | public AppSettings LoadSettings() 8 | { 9 | if (!File.Exists(AppBase.ConfigPath)) 10 | { 11 | return new AppSettings(); 12 | } 13 | string json = File.ReadAllText(AppBase.ConfigPath); 14 | try 15 | { 16 | var appSettings = JsonSerializer.Deserialize(json); 17 | return appSettings ?? new AppSettings(); 18 | } 19 | catch (JsonException) 20 | { 21 | return new AppSettings(); 22 | } 23 | } 24 | 25 | public void SaveSettings(AppSettings settings) 26 | { 27 | var directory = Path.GetDirectoryName(AppBase.ConfigPath) ?? throw new ArgumentException(lang["ConfigFilePathError"]); 28 | if (!Directory.Exists(directory)) 29 | { 30 | Directory.CreateDirectory(directory); 31 | } 32 | string appConfigString = System.Text.Json.JsonSerializer.Serialize(settings); 33 | File.WriteAllText(AppBase.ConfigPath, appConfigString); 34 | } 35 | 36 | public void RemoveSettings() 37 | { 38 | if (File.Exists(AppBase.ConfigPath)) 39 | { 40 | File.Delete(AppBase.ConfigPath); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/ComputerLock/Services/PopupService.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Threading; 3 | using Application = System.Windows.Application; 4 | 5 | namespace ComputerLock.Services; 6 | 7 | /// 8 | /// 弹窗服务,用于显示各种提示信息 9 | /// 10 | public class PopupService(ILogger logger) 11 | { 12 | /// 13 | /// 显示提示信息 14 | /// 15 | /// 提示信息 16 | /// 显示时长(毫秒) 17 | public void ShowMessage(string message, int duration = 1100) 18 | { 19 | Application.Current.Dispatcher.Invoke(() => 20 | { 21 | logger.Info($"弹窗服务 -> 显示消息:{message}"); 22 | var popup = new WindowPopup(message); 23 | double primaryScreenWidth = SystemParameters.PrimaryScreenWidth; 24 | double primaryScreenHeight = SystemParameters.PrimaryScreenHeight; 25 | popup.Left = (primaryScreenWidth - popup.Width) / 2; 26 | popup.Top = (primaryScreenHeight - popup.Height) / 2; 27 | popup.Show(); 28 | 29 | var timer = new DispatcherTimer() 30 | { 31 | Interval = TimeSpan.FromMilliseconds(duration), 32 | }; 33 | timer.Tick += (_, _) => 34 | { 35 | timer.Stop(); 36 | popup.CloseWindow(); 37 | }; 38 | timer.Start(); 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/TitleBar.razor.cs: -------------------------------------------------------------------------------- 1 | using JiuLing.TitleBarKit; 2 | 3 | namespace ComputerLock.Components; 4 | public partial class TitleBar 5 | { 6 | [Parameter] 7 | public string Class { get; set; } = ""; 8 | 9 | private string _maximizerIcon = default!; 10 | 11 | [Inject] 12 | private TitleBarService TitleBarService { get; set; } = default!; 13 | 14 | [Inject] 15 | private IStringLocalizer Lang { get; set; } = default!; 16 | 17 | protected override async Task OnInitializedAsync() 18 | { 19 | await base.OnInitializedAsync(); 20 | await SetMaximizerIconAsync(); 21 | } 22 | 23 | private Task MinimizeAsync() 24 | { 25 | TitleBarService.Controller.Minimize(); 26 | return Task.CompletedTask; 27 | } 28 | 29 | private async Task MaximizeAsync() 30 | { 31 | TitleBarService.Controller.Maximize(); 32 | await SetMaximizerIconAsync(); 33 | } 34 | private Task CloseAsync() 35 | { 36 | TitleBarService.Controller.Close(); 37 | return Task.CompletedTask; 38 | } 39 | 40 | private Task SetMaximizerIconAsync() 41 | { 42 | if (TitleBarService.Controller.IsMaximized) 43 | { 44 | _maximizerIcon = Icons.Material.Filled.CloseFullscreen; 45 | } 46 | else 47 | { 48 | _maximizerIcon = Icons.Material.Filled.Fullscreen; 49 | } 50 | return Task.CompletedTask; 51 | } 52 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/HotkeyInput.razor.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Components; 2 | public partial class HotkeyInput 3 | { 4 | private string _lockHotkeyDisplay => HotkeyTools.StringKeyToDisplay(Hotkey) ?? ""; 5 | 6 | [Parameter] 7 | public string Title { get; set; } = null!; 8 | 9 | [Parameter] 10 | public bool Disabled { get; set; } = false; 11 | 12 | [Parameter] 13 | public string Hotkey { get; set; } = null!; 14 | 15 | [Parameter] 16 | public EventCallback OnHotkeySet { get; set; } 17 | 18 | [Parameter] 19 | public EventCallback OnHotkeyClear { get; set; } 20 | 21 | 22 | [Inject] 23 | private IStringLocalizer Lang { get; set; } = null!; 24 | 25 | [Inject] 26 | private IDialogService Dialog { get; set; } = null!; 27 | 28 | [Inject] 29 | private HotkeyTools HotkeyTools { get; set; } = null!; 30 | 31 | 32 | private async Task SetShortcutKey() 33 | { 34 | var options = new DialogOptions { NoHeader = true, CloseOnEscapeKey = false, BackdropClick = false, BackgroundClass = "dialog-backdrop-filter" }; 35 | var dialog = await Dialog.ShowAsync("", options); 36 | var result = await dialog.Result; 37 | if (result!.Canceled) 38 | { 39 | return; 40 | } 41 | 42 | await OnHotkeySet.InvokeAsync(result.Data!.ToString()); 43 | } 44 | 45 | private async Task ClearShortcutKey() 46 | { 47 | await OnHotkeyClear.InvokeAsync(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/About.razor.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Update; 2 | 3 | namespace ComputerLock.Components; 4 | 5 | public partial class About 6 | { 7 | private string _version = ""; 8 | [Inject] 9 | private AppSettings AppSettings { get; set; } = null!; 10 | [Inject] 11 | private AppSettingsProvider AppSettingsProvider { get; set; } = null!; 12 | [Inject] 13 | private IStringLocalizer Lang { get; set; } = null!; 14 | [Inject] 15 | private UpdateHelper UpdateHelper { get; set; } = null!; 16 | [Inject] 17 | private IDialogService DialogService { get; set; } = null!; 18 | 19 | protected override async Task OnInitializedAsync() 20 | { 21 | await base.OnInitializedAsync(); 22 | _version = $"v{AppBase.VersionString[..AppBase.VersionString.LastIndexOf('.')]}"; 23 | } 24 | 25 | private async Task OpenVersionHistory() 26 | { 27 | var options = new DialogOptions 28 | { 29 | CloseButton = true, 30 | CloseOnEscapeKey = false, 31 | BackdropClick = false, 32 | BackgroundClass = "dialog-backdrop-filter", 33 | FullWidth = true 34 | }; 35 | await DialogService.ShowAsync(Lang["VersionHistory"], options); 36 | } 37 | 38 | private async Task CheckUpdateAsync() 39 | { 40 | await UpdateHelper.DoAsync(false); 41 | } 42 | 43 | private void SaveSettings() 44 | { 45 | AppSettingsProvider.SaveSettings(AppSettings); 46 | } 47 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release-job 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: 9.0.x 20 | 21 | - name: Restore dependencies 22 | run: dotnet restore .\src 23 | 24 | - name: Build 25 | run: dotnet build .\src --no-restore -c release 26 | 27 | - name: Publish 28 | run: dotnet publish .\src -c Release -r win-x64 -p:PublishReadyToRun=true --self-contained false 29 | 30 | - name: Get version 31 | uses: olegtarasov/get-tag@v2.1.4 32 | id: tagName 33 | 34 | - name: Create zip 35 | shell: pwsh 36 | # 配置【编译后的文件地址】 37 | run: Compress-Archive -Path ${{github.workspace}}\src\ComputerLock\bin\Release\net8.0-windows\win-x64\publish\* -DestinationPath ${{github.workspace}}\src\ComputerLock\bin\Release\ComputerLock_${{ steps.tagName.outputs.tag }}_win_x64.zip 38 | 39 | - name: Release 40 | uses: softprops/action-gh-release@v2 41 | if: startsWith(github.ref, 'refs/tags/') 42 | with: 43 | # 配置【README地址】 44 | body_path: ${{github.workspace}}\CHANGELOG.md 45 | # 配置【zip文件地址】 46 | files: ${{github.workspace}}\src\ComputerLock\bin\Release\ComputerLock_${{ steps.tagName.outputs.tag }}_win_x64.zip 47 | -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 透明锁屏 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 | An unhandled error has occurred. 29 | Reload 30 | 🗙 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/VersionLogChecker.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace ComputerLock.Platforms; 4 | 5 | internal class VersionLogChecker(Version currentVersion, string versionFilePath) 6 | { 7 | // 检查是否需要展示更新日志 8 | public async Task CheckShowUpdateLogAsync() 9 | { 10 | var previousVersion = await GetPreviousVersionAsync(); 11 | 12 | if (currentVersion <= previousVersion) 13 | { 14 | return false; 15 | } 16 | 17 | // 目前的逻辑是只自动显示一次更新信息,所以这里直接更新版本信息 18 | await SaveCurrentVersionAsync(currentVersion); 19 | return true; 20 | } 21 | 22 | // 读取上次保存的版本号 23 | private async Task GetPreviousVersionAsync() 24 | { 25 | if (File.Exists(versionFilePath)) 26 | { 27 | string versionText = await File.ReadAllTextAsync(versionFilePath); 28 | if (Version.TryParse(versionText, out var savedVersion)) 29 | { 30 | return savedVersion; 31 | } 32 | } 33 | // 如果文件不存在或版本无效,则返回一个默认的低版本号 34 | return new Version("0.0.0.0"); 35 | } 36 | 37 | // 保存当前版本号到本地文件 38 | private async Task SaveCurrentVersionAsync(Version version) 39 | { 40 | string directory = Path.GetDirectoryName(versionFilePath) ?? throw new InvalidOperationException("versionFilePath"); 41 | if (!Directory.Exists(directory)) 42 | { 43 | Directory.CreateDirectory(directory); 44 | } 45 | await File.WriteAllTextAsync(versionFilePath, version.ToString()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/PowerManager.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Platforms; 2 | internal class PowerManager 3 | { 4 | private void AdjustPrivileges() 5 | { 6 | WinApi.OpenProcessToken(System.Diagnostics.Process.GetCurrentProcess().Handle, 7 | WinApi.TOKEN_ADJUST_PRIVILEGES | WinApi.TOKEN_QUERY, out IntPtr htok); 8 | 9 | WinApi.LookupPrivilegeValue(null, WinApi.SE_SHUTDOWN_NAME, out var luid); 10 | 11 | var tp = new WinApi.TOKEN_PRIVILEGES 12 | { 13 | PrivilegeCount = 1, 14 | Privileges = new WinApi.LUID_AND_ATTRIBUTES[1] 15 | }; 16 | tp.Privileges[0].Luid = luid; 17 | tp.Privileges[0].Attributes = WinApi.SE_PRIVILEGE_ENABLED; 18 | 19 | WinApi.AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); 20 | } 21 | 22 | public void Shutdown() 23 | { 24 | AdjustPrivileges(); 25 | WinApi.ExitWindowsEx(WinApi.EWX_SHUTDOWN, 0); 26 | } 27 | 28 | public void Hibernate() 29 | { 30 | AdjustPrivileges(); 31 | WinApi.SetSuspendState(true, true, true); 32 | } 33 | 34 | public bool IsHibernateSupported() 35 | { 36 | // 简单检查系统是否支持休眠 37 | string? sysDrive = Environment.GetEnvironmentVariable("SystemDrive"); 38 | if (sysDrive == null) 39 | { 40 | return false; 41 | } 42 | string hiberFile = System.IO.Path.Combine(sysDrive, "hiberfil.sys"); 43 | if (!System.IO.File.Exists(hiberFile)) 44 | { 45 | return false; 46 | } 47 | return true; 48 | } 49 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/ResetPassword.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @(Lang["ResetPassword"]) 6 | 10 | 11 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 32 | @(Lang["Save"]) 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/ComputerLock/WindowPopup.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Media.Animation; 3 | 4 | namespace ComputerLock; 5 | /// 6 | /// WindowPopup.xaml 的交互逻辑 7 | /// 8 | public partial class WindowPopup : Window 9 | { 10 | public WindowPopup(string message) 11 | { 12 | InitializeComponent(); 13 | TxtMessage.Text = message; 14 | Loaded += WindowPopup_Loaded; 15 | } 16 | private void WindowPopup_Loaded(object sender, RoutedEventArgs e) 17 | { 18 | // 在窗口加载完成后开始渐入动画 19 | var fadeInAnimation = new DoubleAnimation 20 | { 21 | From = 0, 22 | To = 1, 23 | Duration = TimeSpan.FromMilliseconds(300), 24 | }; 25 | 26 | BeginAnimation(UIElement.OpacityProperty, fadeInAnimation); 27 | 28 | UpdateWindowPosition(); 29 | } 30 | 31 | private void UpdateWindowPosition() 32 | { 33 | double primaryScreenWidth = SystemParameters.PrimaryScreenWidth; 34 | double primaryScreenHeight = SystemParameters.PrimaryScreenHeight; 35 | Left = (primaryScreenWidth - ActualWidth) / 2; 36 | Top = (primaryScreenHeight - ActualHeight) / 2; 37 | } 38 | 39 | public void CloseWindow() 40 | { 41 | // 在窗口关闭时开始渐出动画 42 | var fadeOutAnimation = new DoubleAnimation 43 | { 44 | From = 1, 45 | To = 0, 46 | Duration = TimeSpan.FromMilliseconds(300), 47 | }; 48 | 49 | fadeOutAnimation.Completed += (_, _) => Close(); 50 | 51 | BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation); 52 | } 53 | } -------------------------------------------------------------------------------- /src/ComputerLock/WindowMain.xaml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/autoformat.yml: -------------------------------------------------------------------------------- 1 | name: Format check on push 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths-ignore: 6 | - '.github/**' 7 | - 'resources/**' 8 | - '**.md' 9 | jobs: 10 | dotnet-format: 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Install dotnet-format 17 | run: dotnet tool install -g dotnet-format 18 | 19 | - name: Run dotnet format 20 | id: format 21 | uses: jfversluis/dotnet-format@v1.0.5 22 | with: 23 | repo-token: $ 24 | action: "fix" 25 | workspace: ".\\src\\ComputerLock.slnx" 26 | only-changed-files: true 27 | 28 | - name: Commit files 29 | if: steps.format.outputs.has-changes == 'true' 30 | run: | 31 | git config --local user.name "github-actions[bot]" 32 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 33 | git commit -a -m 'Automated dotnet-format update' 34 | 35 | - name: Create Pull Request 36 | if: steps.format.outputs.has-changes == 'true' 37 | uses: peter-evans/create-pull-request@v7 38 | with: 39 | title: '[Bot] Automated PR to fix formatting errors' 40 | body: | 41 | Automated PR to fix formatting errors 42 | committer: GitHub 43 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 44 | assignees: JiuLing-zhang 45 | reviewers: JiuLing-zhang 46 | branch: bot/fix-codeformatting -------------------------------------------------------------------------------- /src/ComputerLock/Components/ResetPassword.razor.cs: -------------------------------------------------------------------------------- 1 | using DialogResult = MudBlazor.DialogResult; 2 | 3 | namespace ComputerLock.Components; 4 | public partial class ResetPassword 5 | { 6 | [CascadingParameter] 7 | private IMudDialogInstance MudDialog { get; set; } = default!; 8 | 9 | private string _currentPassword = ""; 10 | private string _newPassword = ""; 11 | private string _confirmPassword = ""; 12 | 13 | [Inject] 14 | private IStringLocalizer Lang { get; set; } = default!; 15 | 16 | [Inject] 17 | private ISnackbar Snackbar { get; set; } = default!; 18 | 19 | [Inject] 20 | private AppSettings AppSettings { get; set; } = default!; 21 | 22 | protected override async Task OnInitializedAsync() 23 | { 24 | await base.OnInitializedAsync(); 25 | } 26 | 27 | 28 | private void Submit() 29 | { 30 | if (_newPassword.IsEmpty()) 31 | { 32 | Snackbar.Add(Lang["PasswordEmpty"], Severity.Error); 33 | return; 34 | } 35 | 36 | if (_newPassword != _confirmPassword) 37 | { 38 | Snackbar.Add(Lang["PasswordInconsistent"], Severity.Error); 39 | return; 40 | } 41 | 42 | if (AppSettings.Password != JiuLing.CommonLibs.Security.MD5Utils.GetStringValueToLower(_currentPassword)) 43 | { 44 | 45 | Snackbar.Add(Lang["WrongPassword"], Severity.Error); 46 | 47 | return; 48 | } 49 | 50 | var newPassword = JiuLing.CommonLibs.Security.MD5Utils.GetStringValueToLower(_newPassword); 51 | MudDialog.Close(DialogResult.Ok(newPassword)); 52 | Snackbar.Add(Lang["SaveOk"], Severity.Success); 53 | } 54 | 55 | private void Cancel() => MudDialog.Cancel(); 56 | } -------------------------------------------------------------------------------- /src/ComputerLock/Services/ScreenLockBaseService.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | using System.Windows; 3 | 4 | namespace ComputerLock.Services; 5 | 6 | public abstract class ScreenLockBaseService : IScreenLockService 7 | { 8 | public abstract event EventHandler? OnUnlock; 9 | public abstract bool Lock(bool showAnimation); 10 | public abstract void Unlock(); 11 | 12 | protected void ShowWindowOnScreen(Window window, Screen screen) 13 | { 14 | // 获取包括任务栏的完整屏幕区域 15 | var bounds = screen.Bounds; 16 | 17 | // 设置窗口初始位置和大小 18 | window.WindowStartupLocation = WindowStartupLocation.Manual; 19 | window.Left = bounds.Left; 20 | window.Top = bounds.Top; 21 | window.Width = bounds.Width; 22 | window.Height = bounds.Height; 23 | 24 | // 在窗口加载后,根据屏幕的 DPI 重新调整位置和大小 25 | // 必须先显示窗口,然后才能获取 DPI,所以窗口大小和位置需要二次调整 26 | window.Loaded += (_, _) => 27 | { 28 | var dpiFactor = GetDpiFactor(window); 29 | window.Left = bounds.Left / dpiFactor.X; 30 | window.Top = bounds.Top / dpiFactor.Y; 31 | window.Width = bounds.Width / dpiFactor.X; 32 | window.Height = bounds.Height / dpiFactor.Y; 33 | }; 34 | 35 | window.WindowStyle = WindowStyle.None; 36 | window.ResizeMode = System.Windows.ResizeMode.NoResize; 37 | 38 | window.Show(); 39 | window.Activate(); 40 | } 41 | 42 | private (double X, double Y) GetDpiFactor(Window window) 43 | { 44 | var source = PresentationSource.FromVisual(window); 45 | if (source?.CompositionTarget != null) 46 | { 47 | var transform = source.CompositionTarget.TransformToDevice; 48 | return (transform.M11, transform.M22); 49 | } 50 | return (1.0, 1.0); // 默认比例 51 | } 52 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/About.razor: -------------------------------------------------------------------------------- 1 | @page "/about" 2 | 3 | @(Lang["About"]) 4 | 5 |
6 | 7 | 8 | 9 | 10 | @Lang["Title"] 11 | 12 | 13 | 18 | @Lang["HomePage"] 19 | 20 | 21 | 26 | GitHub 27 | 28 | 29 | 30 | 31 | 32 | @($"{Lang["Version"]} {_version}") 33 | 34 | 35 | 36 | @Lang["CheckUpdate"] 37 | 38 | 39 | @Lang["VersionHistory"] 40 | 41 | 42 | 47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/HotkeySetting.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using System.Text; 3 | using DialogResult = MudBlazor.DialogResult; 4 | 5 | namespace ComputerLock.Components; 6 | public partial class HotkeySetting 7 | { 8 | [CascadingParameter] 9 | private IMudDialogInstance MudDialog { get; set; } = default!; 10 | 11 | private string _text = default!; 12 | private string _key = ""; 13 | 14 | [Inject] 15 | private IStringLocalizer Lang { get; set; } = default!; 16 | 17 | [Inject] 18 | private HotkeyTools HotkeyTools { get; set; } = default!; 19 | 20 | protected override async Task OnInitializedAsync() 21 | { 22 | await base.OnInitializedAsync(); 23 | _text = Lang["EnterShortcutKey"]; 24 | } 25 | 26 | private Task OnKeyDown(KeyboardEventArgs value) 27 | { 28 | _key = ""; 29 | 30 | byte[] buffer = Encoding.ASCII.GetBytes(value.Key); 31 | if (buffer.Length != 1) 32 | { 33 | _text = Lang["EnterShortcutKey"]; 34 | _key = ""; 35 | return Task.CompletedTask; 36 | } 37 | 38 | var ascii = buffer[0]; 39 | 40 | if (value.CtrlKey) 41 | { 42 | _key = "Ctrl + "; 43 | } 44 | if (value.ShiftKey) 45 | { 46 | _key += "Shift + "; 47 | } 48 | if (value.AltKey) 49 | { 50 | _key += "Alt + "; 51 | } 52 | 53 | if (ascii >= 97 && ascii <= 122) 54 | { 55 | ascii = (byte)char.ToUpper((char)ascii); 56 | } 57 | 58 | if ((ascii >= 48 && ascii <= 57) || (ascii >= 65 && ascii <= 90)) 59 | { 60 | _key += $"{ascii}"; 61 | _text = HotkeyTools.StringKeyToDisplay(_key); 62 | } 63 | else 64 | { 65 | _text = Lang["EnterShortcutKey"]; 66 | _key = ""; 67 | } 68 | return Task.CompletedTask; 69 | } 70 | 71 | private void Submit() 72 | { 73 | MudDialog.Close(DialogResult.Ok(_key)); 74 | } 75 | private void Cancel() => MudDialog.Cancel(); 76 | } -------------------------------------------------------------------------------- /src/ComputerLock/WindowBlankScreen.xaml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 43 | 44 | 45 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Donation.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 如果你喜欢这款软件,不妨支持一下作者! 8 | 9 | 14 | 15 | 16 | 17 | 18 | 💖 请使用 微信 扫码 💖 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 已有 32 | 33 | @if (_donationCount.HasValue) 34 | { 35 | @_donationCount 36 | } 37 | else 38 | { 39 | @("loading") 40 | } 41 | 42 | 人支持 43 | 44 | 45 | 查看赞赏记录 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/ComputerLock/wwwroot/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app, 4 | .page { 5 | height: 100%; 6 | user-select: none; 7 | } 8 | 9 | ::-webkit-scrollbar { 10 | display: none; 11 | } 12 | 13 | .mud-appbar.mud-appbar-fixed-top { 14 | padding-right: 0 !important; 15 | } 16 | 17 | .app-icon { 18 | width: 20px; 19 | height: 20px; 20 | background-position: center center; 21 | background-size: cover; 22 | background-image: url("icon.ico"); 23 | } 24 | 25 | .dialog-backdrop-filter { 26 | backdrop-filter: blur(10px); 27 | background-color: rgba(0, 0, 0, 0.3); 28 | } 29 | 30 | .mud-nav-link.active:not(.mud-nav-link-disabled), 31 | .mud-nav-link.active:not(.mud-nav-link-disabled) .mud-icon-root { 32 | color: var(--mud-palette-primary) !important; 33 | font-weight: var(--mud-typography-default-weight) !important; 34 | } 35 | 36 | .titlebar-btn.mud-icon-button { 37 | border-radius: 0 !important; 38 | padding: 8px !important; 39 | color: #424242 !important; 40 | } 41 | 42 | .titlebar-btn.mud-icon-button:hover { 43 | background-color: #E0E0E0 !important; 44 | } 45 | 46 | 47 | .dark-mode .titlebar-btn.mud-icon-button { 48 | color: #CCCCCC !important; 49 | } 50 | 51 | .dark-mode .titlebar-btn.mud-icon-button:hover { 52 | background-color: #333333 !important; 53 | } 54 | 55 | .close-btn.mud-icon-button:hover { 56 | background-color: #E57373 !important; 57 | color: #FFFFFF !important; 58 | } 59 | 60 | .dark-mode .close-btn.mud-icon-button:hover { 61 | background-color: #E81123 !important; 62 | color: #FFFFFF !important; 63 | } 64 | 65 | /* 分组标题样式 */ 66 | .group-title { 67 | font-size: 0.95rem !important; 68 | font-weight: 500 !important; 69 | letter-spacing: 0.02em !important; 70 | color: #888888 !important; 71 | margin-bottom: 1.3rem !important; 72 | } 73 | 74 | .dark-mode .group-title { 75 | color: #BBBBBB !important; 76 | } 77 | 78 | 79 | .no-left-padding { 80 | margin-left: -6px !important; 81 | } 82 | 83 | .max-width-fix { 84 | max-width: fit-content !important; 85 | } 86 | 87 | .input-title { 88 | margin-bottom: 2px !important; 89 | opacity: 0.8; 90 | } 91 | 92 | .input-tip { 93 | opacity: 0.6; 94 | } 95 | -------------------------------------------------------------------------------- /src/ComputerLock/Services/HotkeyScreenLockService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Application = System.Windows.Application; 3 | 4 | namespace ComputerLock.Services; 5 | internal class HotkeyScreenLockService( 6 | IServiceProvider serviceProvider, 7 | IStringLocalizer lang, 8 | ILogger logger, PopupService popupService) : ScreenLockBaseService 9 | { 10 | private bool _showAnimation; 11 | private readonly List _blankScreens = []; 12 | 13 | public override event EventHandler? OnUnlock; 14 | public override bool Lock(bool showAnimation) 15 | { 16 | _showAnimation = showAnimation; 17 | logger.Info("快捷键屏幕锁定 -> 准备锁定"); 18 | var primaryScreen = Screen.PrimaryScreen; 19 | if (primaryScreen == null) 20 | { 21 | logger.Info("快捷键屏幕锁定 -> 没有检测到屏幕"); 22 | return false; 23 | } 24 | 25 | if (_showAnimation) 26 | { 27 | logger.Info("快捷键屏幕锁定 -> 锁定动画"); 28 | popupService.ShowMessage(lang["Locked"]); 29 | } 30 | 31 | if (_blankScreens.Count > 0) 32 | { 33 | logger.Info("快捷键屏幕锁定 -> 准备初始化屏幕"); 34 | _blankScreens.Clear(); 35 | } 36 | 37 | Application.Current.Dispatcher.Invoke(() => 38 | { 39 | for (var i = 0; i <= Screen.AllScreens.Length - 1; i++) 40 | { 41 | var screen = Screen.AllScreens[i]; 42 | logger.Info($"快捷键屏幕锁定 -> 准备屏幕{i}"); 43 | var blankScreen = serviceProvider.GetRequiredService(); 44 | ShowWindowOnScreen(blankScreen, screen); 45 | _blankScreens.Add(blankScreen); 46 | } 47 | }); 48 | return true; 49 | } 50 | 51 | public override void Unlock() 52 | { 53 | logger.Info("快捷键屏幕锁定 -> 准备解锁"); 54 | foreach (var screen in _blankScreens) 55 | { 56 | logger.Info("快捷键屏幕锁定 -> 释放空白屏幕资源"); 57 | screen.Unlock(); 58 | screen.Close(); 59 | } 60 | 61 | if (_showAnimation) 62 | { 63 | logger.Info("快捷键屏幕锁定 -> 解锁动画"); 64 | popupService.ShowMessage(lang["UnLocked"]); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /VERSION_HISTORY.md: -------------------------------------------------------------------------------- 1 | > [2.3.7] - 2025-10-15 2 | * ✨ 修改:单独配置解锁快捷键时,在非锁定状态下,该快捷键会被程序拦截,无法使用 3 | 4 | 5 | > [2.3.6] - 2025-10-09 6 | * 💖 新增:使用任务计划实现静默开机启动(不需要管理员权限) 7 | * 🎈 修改:修改密码解锁可能失效的问题 8 | * 🌈 修改:自动锁定时可能导致无法解锁的问题 9 | * ✨ 修改:优化锁屏时鼠标的移动方式 10 | * 🎉 修改:优化启动程序时所需的权限 11 | * 🎁 修改:界面美化 12 | 13 | > [2.3.5] - 2025-09-19 14 | * ✨ 内部版本,未公开发布 15 | 16 | > [2.3.4] - 2025-08-13 17 | * 🌈 新增:任务管理器状态恢复机制 18 | * 🎁 修改:解锁方式页面鼠标指示器不正确的问题 19 | * 🎈 修改:解除锁定时鼠标事件未释放的问题 20 | * 💖 修改:使用密码框锁定时,输入法为中文的问题 21 | * ✨ 修改:服务器异常时打赏页面导致程序崩溃的问题 22 | 23 | > [2.3.3] - 2025-07-23 24 | * ✨ 新增:当程序锁定时操作电脑允许显示解锁提示 25 | * 🎉 新增:锁定自定义时间后允许自动关机、休眠 26 | * 🌀 新增:设置选项进行分组展示 27 | * 🌈 修改:程序逻辑优化 28 | * ⚽ 修改:升级基础组件 29 | 30 | > [2.3.2] - 2025-05-23 31 | * 🔥 新增锁屏状态展示:左上角圆点、右上角圆点 32 | * ✨ 新增:部分下拉选项加入图标 33 | * 🌀 修复空闲锁定时无法禁用键盘按键的问题 34 | * 🎉 修改锁屏窗口的透明度,减少锁屏时的色彩差异 35 | 36 | > [2.3.1] - 2025-05-18 37 | * 🎉 新增设置:按下 ESC 键后最小化到托盘 38 | * 🌀 新增设置:使用软件渲染来减少内存占用 39 | * ✨ 修复在 Windows 锁屏状态下启用自动锁定时,额外记录大量日志的问题 40 | * 🌈 程序逻辑优化 41 | 42 | > [2.3.0] - 2025-04-23 43 | * ✨ 全新的 UI 设计 44 | * 🔥 新增简单的程序主页,方便提交反馈和建议 45 | * ⚽ 新增赞赏记录,感谢支持 46 | 47 | > [2.2.1] - 2025-04-07 48 | * 🎉 修改自动更新地址,老域名随时停用!【!建议更新!】 49 | * 🌀 新增:首次启动显示更新日志 50 | * 🔥 升级基础组件 51 | 52 | > [2.2.0] - 2025-02-13 53 | * ✨ 新增:支持通过快捷键解锁 54 | * 🌈 新增:可设置 Windows 系统锁定时自动停止锁定服务 55 | * 🌀 新增:首次启动显示更新日志 56 | * ⚽ 设置自动锁定和密码框位置后实时生效 57 | * 🔥 升级基础组件 58 | * 🎉 优化程序逻辑 59 | 60 | > [2.1.10] - 2025-01-14 61 | * 🌈 锁屏时支持隐藏鼠标光标 62 | * 🌀 修改缩放比例可能导致窗口位置异常的问题 63 | * 🔥 重构配置管理模块 64 | * 🎉 升级自动更新模块 65 | * ✨ 界面布局微调 66 | 67 | > [2.1.9] - 2024-09-14 68 | * 🔥 修改锁屏快捷键后,历史快捷键依然生效的bug 69 | * 🌈 完善多语言支持 70 | 71 | > [2.1.8] - 2024-09-11 72 | * 🔥 完善多语言支持 73 | * 🌈 优化自动更新模块 74 | 75 | > [2.1.7] - 2024-06-11 76 | * 🌈 完善多语言支持 77 | 78 | > [2.1.6] - 2024-07-04 79 | * 🔥 自动锁屏时间支持自定义 80 | * 🌈 新增打赏功能 81 | 82 | > [2.1.5] - 2024-06-11 83 | * 🌈 支持程序启动时锁定屏幕 84 | 85 | > [2.1.4] - 2024-03-03 86 | * 🌈 在设置界面中新增了重置所有设置的功能 87 | 88 | > [2.1.3] - 2024-02-22 89 | * 🔥 解锁动画未结束时锁屏会导致解锁动画无法关闭 90 | 91 | > [2.1.2] - 2024-02-07 92 | * 🔥 Windows 锁屏时暂停定时锁定功能 93 | * 🌈 优化定时锁定,提高性能 94 | 95 | > [2.1.1] - 2024-02-06 96 | * 🔥 启用定时锁定时,优化系统资源的调用和释放方式 97 | * 🌈 禁用密码框时禁止选择密码框位置 98 | * 🌀 程序启动时,设置任务管理器为可用状态 99 | 100 | > [2.1.0] - 2024-01-30 101 | * 🌈 升级到 .NET 8 102 | -------------------------------------------------------------------------------- /src/ComputerLock/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 |
15 | 18 | @(Lang["Title"]) 19 |
20 | 21 | 22 | 23 |
26 | 32 | @(Lang["Donate"]) 33 | 34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | @(Lang["LockOptions"]) 42 | @(Lang["UnlockOptions"]) 43 | @(Lang["GeneralSettings"]) 44 | @(Lang["About"]) 45 | 46 | 47 | 48 | 49 | 50 | @Body 51 | 52 | 53 |
-------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | ## Transparent Lock Screen 2 |
3 | 4 | ![](https://img.shields.io/github/license/JiuLing-zhang/ComputerLock) 5 | ![](https://img.shields.io/github/actions/workflow/status/JiuLing-zhang/ComputerLock/release.yml) 6 | [![](https://img.shields.io/github/v/release/JiuLing-zhang/ComputerLock)](https://github.com/JiuLing-zhang/ComputerLock/releases) 7 | 👉👉[中文版](./README.md) 8 | 9 |
10 | 11 | > ✨ Worried about accidental interruptions during presentations? 12 | > 🎉 Afraid of forgetting to lock your screen and risking privacy leaks? 13 | > 🎈 Tired of the monotonous system lock screen interface? 14 | 15 | > Transparent Lock Screen is here to help. 16 | 17 | 18 | 19 | ## Features 20 | * 💖 No More Accidental Interruptions: While locked, your screen content remains visible. Video playback, PPT presentations, and more continue uninterrupted, so you don’t have to worry about accidental keyboard or mouse clicks disrupting your workflow. 21 | * ⚡ Break the Monotony: Say goodbye to boring system lock screens and create a unique lock screen style. 22 | * 🌈 Prevent Sleep Mode: Keep your system awake while locked, avoiding interruptions to downloads, transfers, or other important tasks. 23 | * 🌀 Auto Lock: No need to manually lock your screen—the system will automatically lock it based on your settings, effectively preventing others from accessing your private information. 24 | * 🔋 Smart Shutdown: Automatically execute shutdown or hibernation after a specified time while locked, preventing resource waste from prolonged lock screen states. 25 | 26 | ## Use Cases 27 | * 🎈 Business Presentations: Ensure smooth presentations during meetings or product demos by avoiding accidental interruptions. 28 | * ✨ Entertainment: Enjoy uninterrupted video playback or music while your screen is locked. 29 | * 🎁 Privacy Protection: Manually or automatically lock your screen while using your computer to safeguard your privacy. 30 | 31 | ## About Permission 32 | 🟡 The program requires administrator privileges to run, as it needs to disable the Task Manager via the registry during lock screen mode. 33 | 🟡 The program uses a significant number of `Windows API` calls, which may trigger warnings from some antivirus software. 34 | 35 | ## Lost Password 36 | ✅ **If you forget your password, you can restore all settings using the Reset Program Settings feature.** 37 | 38 | ## Version History 39 | 🍭 [View Full Version History](VERSION_HISTORY.md) 40 | -------------------------------------------------------------------------------- /src/ComputerLock/ComputerLock.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 九零 5 | 透明锁屏 6 | 2.3.7 7 | WinExe 8 | net8.0-windows 9 | enable 10 | enable 11 | true 12 | true 13 | ComputerLock 14 | icon.ico 15 | app.manifest 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | True 39 | Lang.resx 40 | 41 | 42 | True 43 | True 44 | Resource.resx 45 | 46 | 47 | 48 | 49 | 50 | PublicResXFileCodeGenerator 51 | Lang.Designer.cs 52 | 53 | 54 | ResXFileCodeGenerator 55 | Resource.Designer.cs 56 | 57 | 58 | 59 | 60 | 61 | Always 62 | 63 | 64 | 65 | 66 | 67 | Always 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/HotkeyHook.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Platforms; 2 | 3 | /// 4 | /// 快捷键钩子 5 | /// 6 | public class HotkeyHook : IDisposable 7 | { 8 | private List ids = new List(); 9 | 10 | public event Action? HotkeyPressed; 11 | 12 | private sealed class HotkeyNativeWindow : NativeWindow 13 | { 14 | public event Action? OnHotkeyPressed; 15 | 16 | public HotkeyNativeWindow() 17 | { 18 | this.CreateHandle(new CreateParams()); 19 | } 20 | 21 | protected override void WndProc(ref Message m) 22 | { 23 | if (m.Msg == 0x0312) // WM_HOTKEY 24 | { 25 | OnHotkeyPressed?.Invoke(m.WParam.ToInt32()); 26 | } 27 | else 28 | { 29 | base.WndProc(ref m); 30 | } 31 | } 32 | } 33 | 34 | private readonly HotkeyNativeWindow _nativeWindow; 35 | 36 | public HotkeyHook() 37 | { 38 | _nativeWindow = new HotkeyNativeWindow(); 39 | _nativeWindow.OnHotkeyPressed += (id) => HotkeyPressed?.Invoke(id); 40 | } 41 | 42 | /// 43 | /// 注册快捷键 44 | /// 45 | public void Register(int id, Hotkey hotKey) 46 | { 47 | if (ids.Contains(id)) 48 | { 49 | Unregister(id); 50 | } 51 | 52 | var success = WinApi.RegisterHotKey(_nativeWindow.Handle, id, (uint)hotKey.Modifiers, (uint)hotKey.Key); 53 | if (!success) 54 | { 55 | throw new Exception($"注册快捷键失败。id({id})"); 56 | } 57 | 58 | ids.Add(id); 59 | } 60 | 61 | /// 62 | /// 注销快捷键 63 | /// 64 | public void Unregister(int id) 65 | { 66 | WinApi.UnregisterHotKey(_nativeWindow.Handle, id); 67 | if (ids.Contains(id)) 68 | { 69 | ids.Remove(id); 70 | } 71 | } 72 | 73 | /// 74 | /// 注销所有快捷键 75 | /// 76 | public void UnregisterAll() 77 | { 78 | foreach (var id in ids) 79 | { 80 | WinApi.UnregisterHotKey(_nativeWindow.Handle, id); 81 | } 82 | ids.Clear(); 83 | } 84 | 85 | public void Dispose() 86 | { 87 | Dispose(true); 88 | GC.SuppressFinalize(this); 89 | } 90 | 91 | protected virtual void Dispose(bool disposing) 92 | { 93 | if (!disposing) 94 | { 95 | return; 96 | } 97 | 98 | UnregisterAll(); 99 | _nativeWindow.DestroyHandle(); 100 | } 101 | 102 | ~HotkeyHook() 103 | { 104 | Dispose(false); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/TaskManagerHook.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System.IO; 3 | 4 | namespace ComputerLock.Platforms; 5 | internal class TaskManagerHook 6 | { 7 | private static readonly string OriginalStateFilePath = Path.Combine(Path.GetTempPath(), $"{AppBase.FriendlyName}_OriginalTaskMgr"); 8 | private const string RegKey = @"Software\Microsoft\Windows\CurrentVersion\Policies\System"; 9 | private const string DisableTaskMgrValueName = "DisableTaskMgr"; 10 | 11 | private readonly ILogger _logger; 12 | 13 | private enum TaskManagerStateEnum 14 | { 15 | Enabled = 0, 16 | Disabled = 1 17 | } 18 | 19 | public TaskManagerHook(ILogger logger) 20 | { 21 | _logger = logger; 22 | } 23 | 24 | public void Lock() 25 | { 26 | var currentState = GetCurrentState(); 27 | SaveOriginalState(currentState); 28 | SetState(TaskManagerStateEnum.Disabled); 29 | } 30 | 31 | public void Unlock() 32 | { 33 | try 34 | { 35 | string content = File.ReadAllText(OriginalStateFilePath); 36 | if (Enum.TryParse(content, out var originalState)) 37 | { 38 | SetState(originalState); 39 | } 40 | 41 | File.Delete(OriginalStateFilePath); 42 | } 43 | catch (Exception ex) 44 | { 45 | _logger.Error("恢复任务管理器状态失败", ex); 46 | } 47 | } 48 | 49 | /// 50 | /// 崩溃恢复 51 | /// 52 | public void RecoverFromCrash() 53 | { 54 | if (File.Exists(OriginalStateFilePath)) 55 | { 56 | Unlock(); 57 | } 58 | } 59 | 60 | private TaskManagerStateEnum GetCurrentState() 61 | { 62 | using var registryKey = Registry.CurrentUser.OpenSubKey(RegKey); 63 | if (registryKey == null) 64 | { 65 | return TaskManagerStateEnum.Enabled; 66 | } 67 | 68 | var value = registryKey.GetValue(DisableTaskMgrValueName, 0); 69 | return (int)value == 1 ? TaskManagerStateEnum.Disabled : TaskManagerStateEnum.Enabled; 70 | } 71 | 72 | private void SaveOriginalState(TaskManagerStateEnum state) 73 | { 74 | try 75 | { 76 | File.WriteAllText(OriginalStateFilePath, ((int)state).ToString()); 77 | } 78 | catch (Exception ex) 79 | { 80 | _logger.Error($"保存任务管理器原始状态失败", ex); 81 | } 82 | } 83 | 84 | private void SetState(TaskManagerStateEnum state) 85 | { 86 | using var registryKey = Registry.CurrentUser.CreateSubKey(RegKey); 87 | if (registryKey == null) 88 | { 89 | _logger.Error($"注册表键不存在或无法创建:{RegKey}"); 90 | return; 91 | } 92 | registryKey.SetValue(DisableTaskMgrValueName, (int)state); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ComputerLock/WindowBlankScreen.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Input; 3 | 4 | namespace ComputerLock 5 | { 6 | /// 7 | /// WindowBlankScreen.xaml 的交互逻辑 8 | /// 9 | public partial class WindowBlankScreen : Window 10 | { 11 | private bool _isUnlock = false; 12 | public event EventHandler? OnDeviceInput; 13 | 14 | private readonly AppSettings _appSettings; 15 | private readonly ILogger _logger; 16 | public WindowBlankScreen(AppSettings appSettings, ILogger logger) 17 | { 18 | InitializeComponent(); 19 | _appSettings = appSettings; 20 | _logger = logger; 21 | } 22 | 23 | private void Window_Loaded(object sender, RoutedEventArgs e) 24 | { 25 | _logger.Info("空白屏幕 -> 准备锁定"); 26 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.BreathingTop)) 27 | { 28 | _logger.Info("空白屏幕 -> 启用顶部呼吸灯"); 29 | BreathingLightHelper.InitializeBreathingLight(TopBreathingLight); 30 | } 31 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.DotTopLeft)) 32 | { 33 | _logger.Info("空白屏幕 -> 启用左上角圆点"); 34 | BreathingLightHelper.InitializeBreathingLight(DotTopLeft); 35 | } 36 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.DotTopRight)) 37 | { 38 | _logger.Info("空白屏幕 -> 启用右上角圆点"); 39 | BreathingLightHelper.InitializeBreathingLight(DotTopRight); 40 | } 41 | } 42 | 43 | private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) 44 | { 45 | _logger.Info($"空白屏幕 -> 检测到按键 {e.Key}"); 46 | if (e.Key != Key.Escape) 47 | { 48 | return; 49 | } 50 | _logger.Info("空白屏幕 -> 按下ESC功能键"); 51 | if (_appSettings.EnablePasswordBox) 52 | { 53 | _logger.Info("空白屏幕 -> 密码框显示已启用"); 54 | if ((_appSettings.PasswordBoxActiveMethod & PasswordBoxActiveMethodEnum.KeyboardDown) != PasswordBoxActiveMethodEnum.KeyboardDown) 55 | { 56 | return; 57 | } 58 | } 59 | _logger.Info("空白屏幕 -> 通知设备输入"); 60 | OnDeviceInput?.Invoke(this, EventArgs.Empty); 61 | } 62 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 63 | { 64 | _logger.Info($"空白屏幕 -> 准备关闭,当前解锁状态:{_isUnlock}"); 65 | if (!_isUnlock) 66 | { 67 | e.Cancel = true; 68 | } 69 | } 70 | public void Unlock() 71 | { 72 | _logger.Info($"空白屏幕 -> 解锁"); 73 | _isUnlock = true; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/MouseHook.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Platforms; 2 | internal class MouseHook : WindowsInputHook 3 | { 4 | private int _cursorCount; 5 | private readonly Random _random = new(); 6 | private bool _isAutoInput; 7 | protected override int HookType => WinApi.WH_MOUSE_LL; 8 | 9 | public event EventHandler? OnUserInput; 10 | protected override int HookCallback(int nCode, int wParam, IntPtr lParam) 11 | { 12 | if (nCode >= 0) 13 | { 14 | // 处理鼠标事件 15 | HandleMouseEvent(wParam); 16 | } 17 | return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam); 18 | } 19 | 20 | /// 21 | /// 隐藏鼠标光标。 22 | /// 23 | public void HideCursor() 24 | { 25 | if (_cursorCount >= 0) // 如果光标可见 26 | { 27 | _cursorCount = WinApi.ShowCursor(false); // 隐藏光标 28 | } 29 | } 30 | 31 | /// 32 | /// 显示鼠标光标。 33 | /// 34 | public void ShowCursor() 35 | { 36 | if (_cursorCount < 0) // 如果光标不可见 37 | { 38 | _cursorCount = WinApi.ShowCursor(true); // 显示光标 39 | } 40 | } 41 | 42 | /// 43 | /// 重置光标显示状态。 44 | /// 45 | public void ResetCursorState() 46 | { 47 | while (_cursorCount < 0) // 如果光标隐藏 48 | { 49 | WinApi.ShowCursor(true); // 显示光标 50 | _cursorCount++; 51 | } 52 | 53 | while (_cursorCount > 0) // 如果光标多次显示 54 | { 55 | WinApi.ShowCursor(false); // 隐藏光标 56 | _cursorCount--; 57 | } 58 | } 59 | 60 | /// 61 | /// 移动鼠标并点击 62 | /// 63 | public void MoveAndClick() 64 | { 65 | _isAutoInput = true; 66 | if (!WinApi.GetCursorPos(out var current)) 67 | { 68 | _isAutoInput = false; 69 | return; 70 | } 71 | 72 | // 生成一个很小的偏移量,避免明显跳动 73 | var dx = _random.Next(-2, 3); 74 | var dy = _random.Next(-2, 3); 75 | if (dx == 0 && dy == 0) 76 | { 77 | dx = 1; 78 | } 79 | 80 | var targetX = current.X + dx; 81 | var targetY = current.Y + dy; 82 | 83 | WinApi.SetCursorPos(targetX, targetY); 84 | WinApi.mouse_event(WinApi.MOUSEEVENTF_RIGHTDOWN | WinApi.MOUSEEVENTF_RIGHTUP, targetX, targetY, 0, 0); 85 | _isAutoInput = false; 86 | } 87 | 88 | /// 89 | /// 处理鼠标事件 90 | /// 91 | private void HandleMouseEvent(int wParam) 92 | { 93 | if (_isAutoInput) 94 | { 95 | return; 96 | } 97 | 98 | // 只处理鼠标按下事件 99 | if (wParam == WinApi.WM_LBUTTONDOWN || wParam == WinApi.WM_RBUTTONDOWN) 100 | { 101 | OnUserInput?.Invoke(this, EventArgs.Empty); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/ComputerLock/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | 62 | 63 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/ComputerLock/Services/PasswordScreenLockService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Application = System.Windows.Application; 3 | 4 | namespace ComputerLock.Services; 5 | 6 | internal class PasswordScreenLockService(IServiceProvider serviceProvider, IStringLocalizer lang, ILogger logger, PopupService popupService) 7 | : ScreenLockBaseService 8 | { 9 | private bool _showAnimation; 10 | private WindowLockScreen? _windowLockScreen; 11 | private readonly List _blankScreens = []; 12 | 13 | public override event EventHandler? OnUnlock; 14 | public override bool Lock(bool showAnimation) 15 | { 16 | _showAnimation = showAnimation; 17 | logger.Info("密码屏幕锁定 -> 准备锁定"); 18 | var primaryScreen = Screen.PrimaryScreen; 19 | if (primaryScreen == null) 20 | { 21 | logger.Info("密码屏幕锁定 -> 没有检测到屏幕"); 22 | return false; 23 | } 24 | 25 | if (_showAnimation) 26 | { 27 | logger.Info("密码屏幕锁定 -> 锁定动画"); 28 | popupService.ShowMessage(lang["Locked"]); 29 | } 30 | 31 | if (_blankScreens.Count > 0) 32 | { 33 | logger.Info("密码屏幕锁定 -> 准备处理副屏"); 34 | _blankScreens.Clear(); 35 | } 36 | 37 | Application.Current.Dispatcher.Invoke(() => 38 | { 39 | _windowLockScreen = serviceProvider.GetRequiredService(); 40 | _windowLockScreen.OnUnlock += FmLockScreen_OnUnlock; 41 | _windowLockScreen.Closing += (_, _) => 42 | { 43 | _windowLockScreen.OnUnlock -= FmLockScreen_OnUnlock; 44 | }; 45 | logger.Info("密码屏幕锁定 -> 激活功能屏幕"); 46 | ShowWindowOnScreen(_windowLockScreen, primaryScreen); 47 | 48 | for (var i = 0; i <= Screen.AllScreens.Length - 1; i++) 49 | { 50 | var screen = Screen.AllScreens[i]; 51 | if (screen.Primary) 52 | { 53 | continue; 54 | } 55 | logger.Info($"密码屏幕锁定 -> 准备空白屏幕{i}"); 56 | var blankScreen = serviceProvider.GetRequiredService(); 57 | blankScreen.OnDeviceInput += BlankScreen_OnDeviceInput; 58 | logger.Info($"密码屏幕锁定 -> 激活空白屏幕{i}"); 59 | ShowWindowOnScreen(blankScreen, screen); 60 | _blankScreens.Add(blankScreen); 61 | } 62 | }); 63 | return true; 64 | } 65 | 66 | public override void Unlock() 67 | { 68 | logger.Info("密码屏幕锁定 -> 功能屏幕准备解锁"); 69 | _windowLockScreen!.Close(); 70 | 71 | foreach (var screen in _blankScreens) 72 | { 73 | logger.Info("密码屏幕锁定 -> 释放空白屏幕资源"); 74 | screen.OnDeviceInput -= BlankScreen_OnDeviceInput; 75 | screen.Unlock(); 76 | screen.Close(); 77 | } 78 | 79 | if (_showAnimation) 80 | { 81 | logger.Info("密码屏幕锁定 -> 解锁动画"); 82 | popupService.ShowMessage(lang["UnLocked"]); 83 | } 84 | } 85 | 86 | private void FmLockScreen_OnUnlock(object? sender, EventArgs e) 87 | { 88 | logger.Info("密码屏幕锁定 -> 通知解锁"); 89 | OnUnlock?.Invoke(this, EventArgs.Empty); 90 | } 91 | private void BlankScreen_OnDeviceInput(object? sender, EventArgs e) 92 | { 93 | logger.Info("密码屏幕锁定 -> 收到副屏解锁通知"); 94 | _windowLockScreen?.ShowPassword(); 95 | } 96 | } -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/AutostartHook.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using Microsoft.Win32.TaskScheduler; 3 | using System.IO; 4 | 5 | namespace ComputerLock.Platforms; 6 | internal class AutostartHook(ILogger logger) 7 | { 8 | private const string TaskName = "ComputerLockAutoStart"; 9 | 10 | 11 | /// 12 | /// 迁移老版本中的注册表自启动项到计划任务 13 | /// 14 | public void MigrateRegistryToTaskIfNeeded() 15 | { 16 | try 17 | { 18 | const string regKey = @"Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"; 19 | var registryKey = Registry.LocalMachine.OpenSubKey(regKey); 20 | if (registryKey?.GetValue(AppBase.FriendlyName) == null) 21 | { 22 | return; 23 | } 24 | CreateOrUpdateTask(); 25 | using var registryKeyWrite = Registry.LocalMachine.CreateSubKey(regKey); 26 | registryKeyWrite.DeleteValue(AppBase.FriendlyName); 27 | } 28 | catch (Exception ex) 29 | { 30 | logger.Error("迁移自启动项失败", ex); 31 | } 32 | } 33 | 34 | public bool IsAutostart() 35 | { 36 | // 判断计划任务是否存在 37 | try 38 | { 39 | using var taskService = new TaskService(); 40 | var task = taskService.GetTask(TaskName); 41 | return task != null; 42 | } 43 | catch (Exception ex) 44 | { 45 | logger.Error("任务计划获取失败", ex); 46 | return false; 47 | } 48 | } 49 | 50 | public void EnabledAutostart() 51 | { 52 | CreateOrUpdateTask(); 53 | } 54 | 55 | public void DisabledAutostart() 56 | { 57 | DeleteTaskIfExists(); 58 | } 59 | 60 | private void CreateOrUpdateTask() 61 | { 62 | try 63 | { 64 | using var ts = new TaskService(); 65 | 66 | // 构建任务定义 67 | var td = ts.NewTask(); 68 | td.RegistrationInfo.Description = "透明锁屏开机自动启动任务"; 69 | td.Principal.LogonType = TaskLogonType.InteractiveToken; 70 | td.Principal.RunLevel = TaskRunLevel.Highest; 71 | td.Settings.MultipleInstances = TaskInstancesPolicy.IgnoreNew; 72 | td.Settings.DisallowStartIfOnBatteries = false; 73 | td.Settings.StopIfGoingOnBatteries = false; 74 | td.Settings.StartWhenAvailable = true; 75 | 76 | // 触发器:用户登录 77 | td.Triggers.Add(new LogonTrigger()); 78 | 79 | // 操作:启动程序 80 | var execPath = AppBase.ExecutablePath; 81 | var workingDir = Path.GetDirectoryName(execPath); 82 | td.Actions.Add(new ExecAction(execPath, null, workingDir)); 83 | 84 | // 注册(创建或更新) 85 | ts.RootFolder.RegisterTaskDefinition(TaskName, td, TaskCreation.CreateOrUpdate, null, null, TaskLogonType.InteractiveToken); 86 | 87 | } 88 | catch (Exception ex) 89 | { 90 | logger.Error("任务计划创建失败", ex); 91 | } 92 | } 93 | 94 | private void DeleteTaskIfExists() 95 | { 96 | try 97 | { 98 | using var taskService = new TaskService(); 99 | var task = taskService.GetTask(TaskName); 100 | if (task != null) 101 | { 102 | taskService.RootFolder.DeleteTask(TaskName); 103 | } 104 | } 105 | catch (Exception ex) 106 | { 107 | logger.Error("任务计划删除失败", ex); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/UserActivityMonitor.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows.Threading; 3 | using Dispatcher = System.Windows.Threading.Dispatcher; 4 | 5 | namespace ComputerLock.Platforms 6 | { 7 | public class UserActivityMonitor : IDisposable 8 | { 9 | private readonly DispatcherTimer _timer; 10 | private int _autoLockMillisecond; 11 | private bool _isMonitoring; 12 | private readonly Dispatcher _dispatcher; 13 | private bool _disposed; 14 | // 记录开始监控的时间,用来延迟解锁后的检测空闲 15 | private long _lastStartTime; 16 | 17 | public event EventHandler? OnIdle; 18 | 19 | public UserActivityMonitor() 20 | { 21 | _dispatcher = Dispatcher.CurrentDispatcher; 22 | 23 | _timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, Timer_Tick, _dispatcher); 24 | _timer.Start(); 25 | } 26 | 27 | public void SetAutoLockSecond(int autoLockSecond) 28 | { 29 | RunOnUIThread(() => 30 | { 31 | _autoLockMillisecond = autoLockSecond * 1000; 32 | }); 33 | } 34 | 35 | public void StartMonitoring() 36 | { 37 | RunOnUIThread(() => 38 | { 39 | _isMonitoring = true; 40 | _lastStartTime = Environment.TickCount64; 41 | }); 42 | } 43 | 44 | public void StopMonitoring() 45 | { 46 | RunOnUIThread(() => 47 | { 48 | _isMonitoring = false; 49 | }); 50 | } 51 | 52 | private void Timer_Tick(object? sender, EventArgs e) 53 | { 54 | if (!_isMonitoring || _disposed) 55 | { 56 | return; 57 | } 58 | 59 | // 检查是否已经监控了足够长的时间(至少2秒) 60 | long currentTime = Environment.TickCount64; 61 | long monitoringDuration = currentTime - _lastStartTime; 62 | if (monitoringDuration < 5000) 63 | { 64 | // 重新启动监控时,延迟 5 秒后再开始检测空闲 65 | return; 66 | } 67 | 68 | var lastInputInfo = new WinApi.LastInputInfo(); 69 | lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); 70 | 71 | if (WinApi.GetLastInputInfo(ref lastInputInfo)) 72 | { 73 | long elapsedMillisecond = currentTime - lastInputInfo.dwTime; 74 | if (elapsedMillisecond > _autoLockMillisecond) 75 | { 76 | OnIdle?.Invoke(this, EventArgs.Empty); 77 | // 触发一次后停止监控 78 | StopMonitoring(); 79 | } 80 | } 81 | } 82 | 83 | private void RunOnUIThread(Action action) 84 | { 85 | if (_disposed) 86 | { 87 | return; 88 | } 89 | 90 | if (_dispatcher.CheckAccess()) 91 | { 92 | action(); 93 | } 94 | else 95 | { 96 | _dispatcher.Invoke(action); 97 | } 98 | } 99 | 100 | public void Dispose() 101 | { 102 | Dispose(true); 103 | GC.SuppressFinalize(this); 104 | } 105 | 106 | protected virtual void Dispose(bool disposing) 107 | { 108 | if (_disposed) 109 | { 110 | return; 111 | } 112 | 113 | if (disposing) 114 | { 115 | RunOnUIThread(() => 116 | { 117 | _timer.Stop(); 118 | _timer.Tick -= Timer_Tick; 119 | }); 120 | 121 | OnIdle = null; 122 | } 123 | _disposed = true; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/SystemKeyHook.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace ComputerLock.Platforms; 4 | /// 5 | /// 系统按键钩子,用于禁用系统按键 6 | /// 7 | internal class SystemKeyHook : WindowsInputHook 8 | { 9 | private Hotkey? _ignoreHotkey; // 使用单个Hotkey变量来存储需要忽略的快捷键 10 | 11 | protected override int HookType => WinApi.WH_KEYBOARD_LL; 12 | 13 | public event EventHandler? OnUserInput; 14 | 15 | public void SetIgnoreHotkey(Hotkey? hotKey) 16 | { 17 | _ignoreHotkey = hotKey; 18 | } 19 | 20 | protected override int HookCallback(int nCode, int wParam, IntPtr lParam) 21 | { 22 | if (nCode >= 0) 23 | { 24 | int vkCode = Marshal.ReadInt32(lParam); 25 | 26 | // 仅在按下阶段进行拦截判断;抬起阶段放行避免影响 WM_HOTKEY 触发 27 | if (!(wParam == WinApi.WM_KEYDOWN || wParam == WinApi.WM_SYSKEYDOWN)) 28 | { 29 | return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam); 30 | } 31 | 32 | if (_ignoreHotkey == null) 33 | { 34 | if (IsSystemKey(vkCode)) 35 | { 36 | OnUserInput?.Invoke(this, EventArgs.Empty); 37 | return 1; // 阻止事件传递 38 | } 39 | return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam); // 其他按键放行 40 | } 41 | 42 | // 属于需要放行的热键组成部分(修饰键或主键)则放行 43 | if (IsPartOfIgnoreHotkey(vkCode)) 44 | { 45 | return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam); 46 | } 47 | 48 | // 拦截非必要修饰键 49 | if (IsModifierKey(vkCode) && !IsModifierRequired(vkCode)) 50 | { 51 | return 1; // 阻止事件传递 52 | } 53 | 54 | // 不是忽略热键 55 | if (vkCode != (int)_ignoreHotkey.Key) 56 | { 57 | return 1; // 阻止事件传递 58 | } 59 | } 60 | return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam); 61 | } 62 | 63 | private bool IsSystemKey(int vkCode) 64 | { 65 | return vkCode == WinApi.VK_LWIN || vkCode == WinApi.VK_RWIN || 66 | vkCode == WinApi.VK_LSHIFT || vkCode == WinApi.VK_RSHIFT || 67 | vkCode == WinApi.VK_LCONTROL || vkCode == WinApi.VK_RCONTROL || 68 | vkCode == WinApi.VK_TAB; 69 | } 70 | 71 | private bool IsPartOfIgnoreHotkey(int vkCode) 72 | { 73 | bool isPartOfIgnoreHotkey = false; 74 | if (_ignoreHotkey!.Modifiers.HasFlag(HotkeyModifiers.Control)) 75 | { 76 | isPartOfIgnoreHotkey |= (vkCode == WinApi.VK_LCONTROL || vkCode == WinApi.VK_RCONTROL); 77 | } 78 | 79 | if (_ignoreHotkey.Modifiers.HasFlag(HotkeyModifiers.Shift)) 80 | { 81 | isPartOfIgnoreHotkey |= (vkCode == WinApi.VK_LSHIFT || vkCode == WinApi.VK_RSHIFT); 82 | } 83 | 84 | if (_ignoreHotkey.Modifiers.HasFlag(HotkeyModifiers.Alt)) 85 | { 86 | isPartOfIgnoreHotkey |= (vkCode == WinApi.VK_LMENU || vkCode == WinApi.VK_RMENU); 87 | } 88 | 89 | isPartOfIgnoreHotkey |= (vkCode == (int)_ignoreHotkey.Key); 90 | return isPartOfIgnoreHotkey; 91 | } 92 | 93 | private bool IsModifierKey(int vkCode) 94 | { 95 | return vkCode == WinApi.VK_LCONTROL || vkCode == WinApi.VK_RCONTROL || 96 | vkCode == WinApi.VK_LSHIFT || vkCode == WinApi.VK_RSHIFT || 97 | vkCode == WinApi.VK_LMENU || vkCode == WinApi.VK_RMENU; 98 | } 99 | 100 | private bool IsModifierRequired(int vkCode) 101 | { 102 | if (vkCode == WinApi.VK_LCONTROL || vkCode == WinApi.VK_RCONTROL) 103 | { 104 | return _ignoreHotkey!.Modifiers.HasFlag(HotkeyModifiers.Control); 105 | } 106 | if (vkCode == WinApi.VK_LSHIFT || vkCode == WinApi.VK_RSHIFT) 107 | { 108 | return _ignoreHotkey!.Modifiers.HasFlag(HotkeyModifiers.Shift); 109 | } 110 | if (vkCode == WinApi.VK_LMENU || vkCode == WinApi.VK_RMENU) 111 | { 112 | return _ignoreHotkey!.Modifiers.HasFlag(HotkeyModifiers.Alt); 113 | } 114 | return false; 115 | } 116 | } -------------------------------------------------------------------------------- /src/ComputerLock/WindowLockScreen.xaml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 50 | 51 | 52 | 60 | 61 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/ComputerLock/Resources/Resource.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ComputerLock.Resources { 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 Resource { 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 Resource() { 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("ComputerLock.Resources.Resource", typeof(Resource).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 | /// 查找类似 https://jiuling.cc/api/app/check-update/cdbd0b29d200c61ace8a1ff74605b775/windows 的本地化字符串。 65 | /// 66 | internal static string AutoUpgradePath { 67 | get { 68 | return ResourceManager.GetString("AutoUpgradePath", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// 查找类似 https://www.jiuling.cc/api/donation/list 的本地化字符串。 74 | /// 75 | internal static string DonationListPath { 76 | get { 77 | return ResourceManager.GetString("DonationListPath", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// 查找类似 https://jiuling.cc/donation 的本地化字符串。 83 | /// 84 | internal static string DonationPath { 85 | get { 86 | return ResourceManager.GetString("DonationPath", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// 查找类似 https://github.com/JiuLing-zhang/ComputerLock 的本地化字符串。 92 | /// 93 | internal static string GitHubUrl { 94 | get { 95 | return ResourceManager.GetString("GitHubUrl", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// 查找类似 https://jiuling.cc/computer-lock 的本地化字符串。 101 | /// 102 | internal static string HomePage { 103 | get { 104 | return ResourceManager.GetString("HomePage", resourceCulture); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/UnlockSettings.razor.cs: -------------------------------------------------------------------------------- 1 | namespace ComputerLock.Components.Settings; 2 | public partial class UnlockSettings 3 | { 4 | [Inject] 5 | private AppSettings AppSettings { get; set; } = null!; 6 | 7 | [Inject] 8 | private AppSettingsProvider AppSettingsProvider { get; set; } = null!; 9 | 10 | [Inject] 11 | private IStringLocalizer Lang { get; set; } = null!; 12 | 13 | [Inject] 14 | private ISnackbar Snackbar { get; set; } = null!; 15 | 16 | [Inject] 17 | private IDialogService Dialog { get; set; } = null!; 18 | 19 | [Inject] 20 | private HotkeyHook HotkeyHook { get; set; } = null!; 21 | 22 | [Inject] 23 | private ILogger Logger { get; set; } = null!; 24 | 25 | 26 | private bool _keyboardDownChecked; 27 | private bool _mouseDownChecked; 28 | 29 | protected override async Task OnInitializedAsync() 30 | { 31 | await base.OnInitializedAsync(); 32 | _keyboardDownChecked = (AppSettings.PasswordBoxActiveMethod & PasswordBoxActiveMethodEnum.KeyboardDown) == PasswordBoxActiveMethodEnum.KeyboardDown; 33 | _mouseDownChecked = (AppSettings.PasswordBoxActiveMethod & PasswordBoxActiveMethodEnum.MouseDown) == PasswordBoxActiveMethodEnum.MouseDown; 34 | } 35 | private void SelectUnlockMethod(ScreenUnlockMethods method) 36 | { 37 | if (AppSettings.ScreenUnlockMethod != method) 38 | { 39 | AppSettings.ScreenUnlockMethod = method; 40 | SaveSettings(); 41 | } 42 | } 43 | 44 | private void SaveSettings() 45 | { 46 | AppSettingsProvider.SaveSettings(AppSettings); 47 | } 48 | 49 | private async Task PasswordEdit() 50 | { 51 | IDialogReference dialog; 52 | var options = new DialogOptions { NoHeader = true, CloseOnEscapeKey = false, BackdropClick = false, BackgroundClass = "dialog-backdrop-filter" }; 53 | 54 | if (AppSettings.Password.IsEmpty()) 55 | { 56 | dialog = await Dialog.ShowAsync("", options); 57 | } 58 | else 59 | { 60 | dialog = await Dialog.ShowAsync("", options); 61 | } 62 | var result = await dialog.Result; 63 | if (result == null || result.Canceled) 64 | { 65 | return; 66 | } 67 | if (result.Data == null) 68 | { 69 | return; 70 | } 71 | 72 | AppSettings.Password = result.Data.ToString()!; 73 | SaveSettings(); 74 | } 75 | 76 | private void KeyboardDownChecked() 77 | { 78 | if (!_keyboardDownChecked) 79 | { 80 | if (!_mouseDownChecked) 81 | { 82 | _keyboardDownChecked = true; 83 | Snackbar.Add(Lang["ActiveMethodEmpty"], Severity.Error); 84 | return; 85 | } 86 | } 87 | SavePasswordBoxActiveMethod(); 88 | } 89 | private void MouseDownChecked() 90 | { 91 | if (!_mouseDownChecked) 92 | { 93 | if (!_keyboardDownChecked) 94 | { 95 | _mouseDownChecked = true; 96 | Snackbar.Add(Lang["ActiveMethodEmpty"], Severity.Error); 97 | return; 98 | } 99 | } 100 | SavePasswordBoxActiveMethod(); 101 | } 102 | 103 | private void SavePasswordBoxActiveMethod() 104 | { 105 | AppSettings.PasswordBoxActiveMethod = 0; 106 | if (_keyboardDownChecked) 107 | { 108 | AppSettings.PasswordBoxActiveMethod |= PasswordBoxActiveMethodEnum.KeyboardDown; 109 | } 110 | if (_mouseDownChecked) 111 | { 112 | AppSettings.PasswordBoxActiveMethod |= PasswordBoxActiveMethodEnum.MouseDown; 113 | } 114 | SaveSettings(); 115 | } 116 | 117 | private void PwdBoxLocationChanged(ScreenLocationEnum location) 118 | { 119 | AppSettings.PasswordInputLocation = location; 120 | SaveSettings(); 121 | } 122 | 123 | // ========= 热键的注册和注销交给锁定服务处理(仅当锁定时热键才生效)这里只保存设置 ============ 124 | private Task SetUnlockHotkey(string hotkey) 125 | { 126 | // 检查快捷键是否与锁屏快捷键相同 127 | if (hotkey.IsNotEmpty() && hotkey == AppSettings.LockHotkeyString) 128 | { 129 | Snackbar.Add(Lang["HotkeyDuplicateError"], Severity.Error); 130 | return Task.CompletedTask; 131 | } 132 | 133 | AppSettings.UnlockHotkeyString = hotkey; 134 | SaveSettings(); 135 | return Task.CompletedTask; 136 | } 137 | private Task ClearUnlockHotkey() 138 | { 139 | AppSettings.UnlockHotkeyString = ""; 140 | SaveSettings(); 141 | return Task.CompletedTask; 142 | } 143 | // ================================================================================ 144 | } 145 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/LockSettings.razor.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using MudExtensions; 4 | 5 | namespace ComputerLock.Components.Settings; 6 | 7 | public partial class LockSettings 8 | { 9 | [Inject] 10 | private AppSettings AppSettings { get; set; } = null!; 11 | 12 | [Inject] 13 | private AppSettingsProvider AppSettingsProvider { get; set; } = null!; 14 | 15 | [Inject] 16 | private IStringLocalizer Lang { get; set; } = null!; 17 | 18 | [Inject] 19 | private ISnackbar Snackbar { get; set; } = null!; 20 | 21 | 22 | [Inject] 23 | private IGlobalLockService GlobalLockService { get; set; } = null!; 24 | 25 | [Inject] 26 | private HotkeyHook HotkeyHook { get; set; } = null!; 27 | 28 | [Inject] 29 | private ILogger Logger { get; set; } = null!; 30 | 31 | [Inject] 32 | private PowerManager PowerManager { get; set; } = null!; 33 | 34 | private MudTextFieldExtended? _textTipsMessageRef; 35 | private bool _lockTipsMessageEdit; 36 | public string LockTipsMessage 37 | { 38 | get => AppSettings.LockTipsMessage.IsEmpty() 39 | ? Lang["LockTipsValue"] // 显示默认文案 40 | : AppSettings.LockTipsMessage; 41 | set => AppSettings.LockTipsMessage = value; 42 | } 43 | private async Task EnableTipsMessageEditing() 44 | { 45 | _lockTipsMessageEdit = true; 46 | // 等待渲染后再聚焦,否则焦点可能丢失 47 | await Task.Delay(1); 48 | _textTipsMessageRef?.FocusAsync(); 49 | } 50 | 51 | private void TipsMessageBlur(FocusEventArgs args) 52 | { 53 | TipsMessageFinishEditing(); 54 | } 55 | private void TipsMessageKeyDown(KeyboardEventArgs args) 56 | { 57 | if (args.Key == "Enter") 58 | { 59 | TipsMessageFinishEditing(); 60 | } 61 | } 62 | private void TipsMessageFinishEditing() 63 | { 64 | if (!_lockTipsMessageEdit) 65 | { 66 | return; 67 | } 68 | _lockTipsMessageEdit = false; 69 | } 70 | 71 | private void SaveSettings() 72 | { 73 | AppSettingsProvider.SaveSettings(AppSettings); 74 | } 75 | 76 | private void LockStatusDisplayChanged(LockStatusDisplay lockStatusDisplay) 77 | { 78 | AppSettings.LockStatusDisplay = lockStatusDisplay; 79 | SaveSettings(); 80 | } 81 | 82 | private void AutoLockChanged(int autoLockMinute) 83 | { 84 | AppSettings.AutoLockSecond = autoLockMinute * 60; 85 | SaveSettings(); 86 | GlobalLockService.UpdateAutoLockSettings(); 87 | } 88 | private Task SetLockHotkey(string hotkey) 89 | { 90 | // 检查快捷键是否与解锁快捷键相同 91 | if (hotkey.IsNotEmpty() && hotkey == AppSettings.UnlockHotkeyString) 92 | { 93 | Snackbar.Add(Lang["HotkeyDuplicateError"], Severity.Error); 94 | return Task.CompletedTask; 95 | } 96 | 97 | AppSettings.LockHotkeyString = hotkey; 98 | SaveSettings(); 99 | RegisterLockHotkey(); 100 | return Task.CompletedTask; 101 | } 102 | private Task ClearLockHotkey() 103 | { 104 | AppSettings.LockHotkeyString = ""; 105 | SaveSettings(); 106 | UnregisterLockHotkey(); 107 | return Task.CompletedTask; 108 | } 109 | 110 | 111 | public void RegisterLockHotkey() 112 | { 113 | try 114 | { 115 | if (AppSettings.LockHotkey != null) 116 | { 117 | Logger.Info("注册锁屏热键"); 118 | HotkeyHook.Register((int)HotkeyType.Lock, AppSettings.LockHotkey); 119 | } 120 | } 121 | catch (Exception ex) 122 | { 123 | Logger.Error($"绑定锁屏热键失败", ex); 124 | Snackbar.Add($"{Lang["ExRegistFailed"]}{ex.Message}", Severity.Error); 125 | } 126 | } 127 | public void UnregisterLockHotkey() 128 | { 129 | try 130 | { 131 | Logger.Info("释放锁屏热键"); 132 | HotkeyHook.Unregister((int)HotkeyType.Lock); 133 | } 134 | catch (Exception ex) 135 | { 136 | Logger.Error($"释放锁屏热键失败", ex); 137 | //MessageBoxUtils.ShowError($"取消快捷键失败:{ex.Message}"); 138 | } 139 | } 140 | 141 | private void AutoPowerChanged(int autoPowerMinute) 142 | { 143 | AppSettings.AutoPowerSecond = autoPowerMinute * 60; 144 | SaveSettings(); 145 | } 146 | 147 | private void PowerActionTypeChanged(PowerActionType powerActionType) 148 | { 149 | if (powerActionType == PowerActionType.Hibernate && !PowerManager.IsHibernateSupported()) 150 | { 151 | Snackbar.Add("系统可能未启用休眠功能,该功能可能无效。", Severity.Error); 152 | return; 153 | } 154 | AppSettings.AutoPowerActionType = powerActionType; 155 | SaveSettings(); 156 | } 157 | } -------------------------------------------------------------------------------- /src/ComputerLock/Configuration/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace ComputerLock.Configuration; 4 | public class AppSettings 5 | { 6 | [JsonIgnore] 7 | private HotkeyTools? _hotkeyTools; 8 | 9 | public void Initialize(HotkeyTools hotkeyTools) 10 | { 11 | _hotkeyTools = hotkeyTools; 12 | } 13 | 14 | /// 15 | /// 主题 16 | /// 17 | [JsonPropertyName("AppThemeInt")] 18 | public ThemeEnum AppTheme { get; set; } = ThemeEnum.System; 19 | 20 | /// 21 | /// 按下 ESC 键后最小化到托盘 22 | /// 23 | public bool IsHideWindowWhenEsc { get; set; } = false; 24 | 25 | /// 26 | /// 关闭窗口时最小化到托盘 27 | /// 28 | public bool IsHideWindowWhenClose { get; set; } = true; 29 | 30 | /// 31 | /// 启动后最小化到托盘 32 | /// 33 | public bool IsHideWindowWhenLaunch { get; set; } = true; 34 | 35 | /// 36 | /// 自动移动鼠标 37 | /// 38 | public bool IsDisableWindowsLock { get; set; } = true; 39 | 40 | /// 41 | /// Windows 锁定时自动解锁程序 42 | /// 43 | public bool IsUnlockWhenWindowsLock { get; set; } = true; 44 | 45 | /// 46 | /// 锁定时显示动画 47 | /// 48 | public bool LockAnimation { get; set; } = true; 49 | 50 | /// 51 | /// 锁定提示 52 | /// 53 | public bool LockTips { get; set; } = false; 54 | 55 | /// 56 | /// 自定义锁定提示文案 57 | /// 58 | public string? LockTipsMessage { get; set; } 59 | 60 | /// 61 | /// 程序启动时锁定 62 | /// 63 | public bool LockOnStartup { get; set; } = false; 64 | 65 | /// 66 | /// 自动锁定的秒数 67 | /// 68 | public int AutoLockSecond { get; set; } = 0; 69 | 70 | /// 71 | /// 自动锁定的分钟数 72 | /// 73 | public int AutoLockMinute => AutoLockSecond / 60; 74 | 75 | /// 76 | /// 密码框的位置 77 | /// 78 | public ScreenLocationEnum PasswordInputLocation { get; set; } = ScreenLocationEnum.Center; 79 | 80 | /// 81 | /// 语言 82 | /// 83 | public LangEnum Lang { get; set; } = LangEnum.zh; 84 | 85 | /// 86 | /// 解锁密码 87 | /// 88 | public string Password { get; set; } = ""; 89 | 90 | /// 91 | /// 锁屏快捷键 92 | /// 93 | [JsonPropertyName("ShortcutKeyForLock")] 94 | public string LockHotkeyString { get; set; } = ""; 95 | 96 | /// 97 | /// 锁屏快捷键(映射到按键名称,用于主界面显示) 98 | /// 99 | [JsonIgnore] 100 | public string LockHotkeyDisplay => _hotkeyTools?.StringKeyToDisplay(LockHotkeyString) ?? ""; 101 | 102 | /// 103 | /// 锁屏快捷键 104 | /// 105 | [JsonIgnore] 106 | public Hotkey? LockHotkey => _hotkeyTools?.StringKeyToHotkey(LockHotkeyString); 107 | 108 | /// 109 | /// 解锁时使用锁屏快捷键 110 | /// 111 | public bool IsUnlockUseLockHotkey { get; set; } = true; 112 | 113 | /// 114 | /// 解锁快捷键 115 | /// 116 | public string UnlockHotkeyString { get; set; } = ""; 117 | 118 | /// 119 | /// 解锁快捷键 120 | /// 121 | [JsonIgnore] 122 | public Hotkey? UnlockHotkey => _hotkeyTools?.StringKeyToHotkey(UnlockHotkeyString); 123 | 124 | /// 125 | /// 自动检查更新 126 | /// 127 | public bool IsAutoCheckUpdate { get; set; } = true; 128 | 129 | /// 130 | /// 启用密码框 131 | /// 132 | public bool EnablePasswordBox { get; set; } = true; 133 | 134 | /// 135 | /// 自动隐藏密码框 136 | /// 137 | public bool IsHidePasswordWindow { get; set; } = true; 138 | 139 | /// 140 | /// 密码框激活方式 141 | /// 142 | public PasswordBoxActiveMethodEnum PasswordBoxActiveMethod { get; set; } = PasswordBoxActiveMethodEnum.KeyboardDown | PasswordBoxActiveMethodEnum.MouseDown; 143 | 144 | /// 145 | /// 自动隐藏鼠标光标 146 | /// 147 | public bool IsHideMouseCursor { get; set; } = false; 148 | 149 | /// 150 | /// 屏幕解锁方式 151 | /// 152 | public ScreenUnlockMethods ScreenUnlockMethod { get; set; } = ScreenUnlockMethods.Hotkey; 153 | 154 | /// 155 | /// 锁屏状态展示 156 | /// 157 | public LockStatusDisplay LockStatusDisplay { get; set; } = LockStatusDisplay.None; 158 | 159 | /// 160 | /// 启用软件渲染 161 | /// 162 | public bool IsEnableSoftwareRendering { get; set; } = true; 163 | 164 | /// 165 | /// 锁定后执行关机/休眠(秒) 166 | /// 167 | public int AutoPowerSecond { get; set; } = 0; 168 | 169 | /// 170 | /// 锁定后执行关机/休眠(分钟) 171 | /// 172 | public int AutoPowerMinute => AutoPowerSecond / 60; 173 | 174 | /// 175 | /// 锁定后自动执行的电源操作类型 176 | /// 177 | public PowerActionType AutoPowerActionType { get; set; } = PowerActionType.Shutdown; 178 | } 179 | -------------------------------------------------------------------------------- /src/ComputerLock/Shared/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | using ComputerLock.Update; 3 | using JiuLing.TitleBarKit; 4 | 5 | namespace ComputerLock.Shared; 6 | 7 | public partial class MainLayout 8 | { 9 | private bool _isDarkMode; 10 | private MudThemeProvider _mudThemeProvider = null!; 11 | private MudTheme _customTheme = null!; 12 | 13 | [Inject] 14 | private TitleBarService TitleBarService { get; set; } = null!; 15 | 16 | [Inject] 17 | private AppSettings AppSettings { get; set; } = null!; 18 | 19 | [Inject] 20 | private IStringLocalizer Lang { get; set; } = null!; 21 | 22 | [Inject] 23 | private IDialogService Dialog { get; set; } = null!; 24 | 25 | [Inject] 26 | private VersionLogChecker VersionLogChecker { get; set; } = null!; 27 | 28 | 29 | [Inject] 30 | private UpdateHelper UpdateHelper { get; set; } = null!; 31 | 32 | [Inject] 33 | private IGlobalLockService GlobalLockService { get; set; } = null!; 34 | 35 | [Inject] 36 | private ILogger Logger { get; set; } = null!; 37 | 38 | [Inject] 39 | private ISnackbar Snackbar { get; set; } = null!; 40 | 41 | [Inject] 42 | private ThemeSwitchService ThemeSwitchService { get; set; } = null!; 43 | 44 | private static bool _isInitialized = false; 45 | protected override async Task OnInitializedAsync() 46 | { 47 | await base.OnInitializedAsync(); 48 | 49 | _customTheme = new MudTheme() 50 | { 51 | LayoutProperties = new LayoutProperties 52 | { 53 | AppbarHeight = "36px", 54 | DrawerWidthLeft = "200px" 55 | }, 56 | 57 | PaletteLight = new PaletteLight() 58 | { 59 | Primary = "#FFA500", 60 | Secondary = "#FF7043", 61 | AppbarBackground = "#F5F5F5", 62 | AppbarText = "#FFA500", 63 | Background = "#FFFFFF", 64 | Surface = "#FFFFFF", 65 | LinesDefault = "#E0E0E0", 66 | 67 | Success = "#43A047", 68 | Error = "#E53935", 69 | Warning = "#FDD835", 70 | Info = "#29B6F6", 71 | TextPrimary = "#212121", 72 | TextSecondary = "#616161", 73 | DrawerBackground = "#FFFFFF", 74 | DrawerText = "#212121", 75 | ActionDefault = "#FFE2B0", 76 | HoverOpacity = 0.2 77 | }, 78 | 79 | PaletteDark = new PaletteDark() 80 | { 81 | Primary = "#FFA500", 82 | Secondary = "#FF7043", 83 | AppbarBackground = "#212121", 84 | AppbarText = "#FFA500", 85 | Background = "#212121", 86 | Surface = "#424242", 87 | LinesDefault = "#555555", 88 | 89 | Success = "#66BB6A", 90 | Error = "#EF5350", 91 | Warning = "#FFEE58", 92 | Info = "#4FC3F7", 93 | TextPrimary = "#FFFFFF", 94 | TextSecondary = "#B0BEC5", 95 | DrawerBackground = "#2C2C2C", 96 | DrawerText = "#FFFFFF", 97 | ActionDefault = "#505050", 98 | HoverOpacity = 0.2 99 | } 100 | }; 101 | 102 | if (AppSettings.IsAutoCheckUpdate) 103 | { 104 | await UpdateHelper.DoAsync(true); 105 | } 106 | 107 | if (await VersionLogChecker.CheckShowUpdateLogAsync()) 108 | { 109 | var options = new DialogOptions 110 | { 111 | CloseButton = true, 112 | CloseOnEscapeKey = false, 113 | BackdropClick = false, 114 | BackgroundClass = "dialog-backdrop-filter", 115 | FullWidth = true 116 | }; 117 | await Dialog.ShowAsync(Lang["VersionHistory"], options); 118 | } 119 | 120 | await SwitchThemeAsync(AppSettings.AppTheme); 121 | 122 | InitializeEventBinding(); 123 | } 124 | 125 | private async Task SwitchThemeAsync(ThemeEnum theme) 126 | { 127 | _isDarkMode = theme switch 128 | { 129 | ThemeEnum.System => await _mudThemeProvider.GetSystemDarkModeAsync(), 130 | ThemeEnum.Light => false, 131 | ThemeEnum.Dark => true, 132 | _ => _isDarkMode 133 | }; 134 | await InvokeAsync(StateHasChanged); 135 | } 136 | 137 | private void InitializeEventBinding() 138 | { 139 | if (_isInitialized) 140 | { 141 | return; 142 | } 143 | _isInitialized = true; 144 | 145 | ThemeSwitchService.OnThemeChanged += async theme => 146 | { 147 | await SwitchThemeAsync(theme); 148 | }; 149 | } 150 | 151 | private void MouseDown() 152 | { 153 | TitleBarService.DragHandler.DragMove(); 154 | } 155 | 156 | private async Task OpenDonationDialog() 157 | { 158 | var options = new DialogOptions { NoHeader = true, CloseOnEscapeKey = false, BackdropClick = false, BackgroundClass = "dialog-backdrop-filter" }; 159 | await Dialog.ShowAsync("", options); 160 | } 161 | 162 | 163 | } -------------------------------------------------------------------------------- /src/ComputerLock/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | using ComputerLock.Update; 3 | using JiuLing.TitleBarKit; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using MudBlazor.Services; 6 | using MudExtensions.Services; 7 | using System.Globalization; 8 | using System.IO; 9 | using System.Windows; 10 | using Application = System.Windows.Application; 11 | 12 | namespace ComputerLock; 13 | /// 14 | /// Interaction logic for App.xaml 15 | /// 16 | public partial class App : Application 17 | { 18 | private static Mutex _mutex = default!; 19 | private WindowMain? _mainWindow; 20 | private ILogger _logger; 21 | protected override void OnStartup(StartupEventArgs e) 22 | { 23 | base.OnStartup(e); 24 | 25 | _mutex = new Mutex(true, AppBase.FriendlyName); 26 | if (!_mutex.WaitOne(0, false)) 27 | { 28 | System.Windows.MessageBox.Show("程序已经运行 The program has been run"); 29 | Application.Current.Shutdown(); 30 | return; 31 | } 32 | 33 | Environment.CurrentDirectory = Path.GetDirectoryName(AppBase.ExecutablePath); 34 | 35 | Init(); 36 | } 37 | 38 | private void Init() 39 | { 40 | IServiceCollection services = new ServiceCollection(); 41 | services.AddSingleton(); 42 | 43 | services.AddSingleton((sp) => 44 | { 45 | var appSettings = sp.GetRequiredService().LoadSettings(); 46 | var hotkeyTools = sp.GetRequiredService(); 47 | appSettings.Initialize(hotkeyTools); 48 | return appSettings; 49 | }); 50 | services.AddSingleton((_) => new VersionLogChecker(AppBase.Version, AppBase.VersionFilePath)); 51 | services.AddSingleton(LogManager.GetLogger()); 52 | services.AddSingleton(); 53 | services.AddSingleton(); 54 | services.AddSingleton(); 55 | services.AddSingleton(); 56 | services.AddSingleton(); 57 | services.AddSingleton(); 58 | services.AddSingleton(); 59 | services.AddSingleton(); 60 | services.AddTransient(); 61 | services.AddTransient(); 62 | services.AddSingleton(); 63 | services.AddSingleton(); 64 | services.AddSingleton(); 65 | services.AddSingleton(); 66 | services.AddSingleton(); 67 | services.AddSingleton(); 68 | services.AddKeyedSingleton(ScreenUnlockMethods.Password); 69 | services.AddKeyedSingleton(ScreenUnlockMethods.Hotkey); 70 | 71 | services.AddWpfTitleBarKit(); 72 | services.AddLocalization(); 73 | services.AddWpfBlazorWebView(); 74 | services.AddMudServices(config => 75 | { 76 | config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.TopLeft; 77 | config.SnackbarConfiguration.ShowCloseIcon = false; 78 | config.SnackbarConfiguration.VisibleStateDuration = 1500; 79 | config.SnackbarConfiguration.ShowTransitionDuration = 200; 80 | config.SnackbarConfiguration.HideTransitionDuration = 400; 81 | }); 82 | services.AddMudExtensions(); 83 | services.AddMudMarkdownServices(); 84 | 85 | var sp = services.BuildServiceProvider(); 86 | Resources.Add("services", sp); 87 | 88 | // 自动启动迁移:在应用启动时执行一次注册表 -> 计划任务的迁移 89 | sp.GetRequiredService().MigrateRegistryToTaskIfNeeded(); 90 | 91 | var cultureInfo = new CultureInfo(sp.GetRequiredService().Lang.ToString()); 92 | CultureInfo.CurrentCulture = cultureInfo; 93 | Thread.CurrentThread.CurrentCulture = cultureInfo; 94 | Thread.CurrentThread.CurrentUICulture = cultureInfo; 95 | 96 | if (sp.GetRequiredService().IsEnableSoftwareRendering) 97 | { 98 | System.Windows.Media.RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly; 99 | } 100 | 101 | _logger = sp.GetRequiredService(); 102 | // 确保 HotkeyHook 已被创建 103 | sp.GetRequiredService(); 104 | KeepMessageLive(); 105 | 106 | _mainWindow = sp.GetRequiredService(); 107 | Application.Current.MainWindow = _mainWindow; 108 | _mainWindow.Show(); 109 | } 110 | 111 | private void KeepMessageLive() 112 | { 113 | // 启动后台消息循环,防止主线程被挂起 114 | Task.Run(() => 115 | { 116 | try 117 | { 118 | while (WinApi.GetMessage(out var msg, IntPtr.Zero, 0, 0)) 119 | { 120 | WinApi.TranslateMessage(ref msg); 121 | WinApi.DispatchMessage(ref msg); 122 | } 123 | } 124 | catch (Exception ex) 125 | { 126 | _logger.Error("热键消息保活服务异常", ex); 127 | } 128 | }); 129 | } 130 | 131 | protected override void OnExit(ExitEventArgs e) 132 | { 133 | _mainWindow?.Dispose(); 134 | base.OnExit(e); 135 | } 136 | } -------------------------------------------------------------------------------- /src/ComputerLock/Platforms/WinApi.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace ComputerLock.Platforms; 4 | 5 | /// 6 | /// Windows API 封装 7 | /// 8 | internal static class WinApi 9 | { 10 | #region 热键相关 11 | 12 | [DllImport("user32.dll")] 13 | public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); 14 | 15 | [DllImport("user32.dll")] 16 | public static extern bool UnregisterHotKey(IntPtr hWnd, int id); 17 | 18 | #endregion 19 | 20 | #region 鼠标相关 21 | 22 | public const int MOUSEEVENTF_LEFTDOWN = 0x0002; 23 | public const int MOUSEEVENTF_LEFTUP = 0x0004; 24 | public const int MOUSEEVENTF_RIGHTDOWN = 0x0008; 25 | public const int MOUSEEVENTF_RIGHTUP = 0x0010; 26 | 27 | public const int WH_MOUSE_LL = 14; 28 | public const int WM_MOUSEMOVE = 0x0200; 29 | public const int WM_LBUTTONDOWN = 0x0201; 30 | public const int WM_LBUTTONUP = 0x0202; 31 | public const int WM_RBUTTONDOWN = 0x0204; 32 | public const int WM_RBUTTONUP = 0x0205; 33 | 34 | [DllImport("user32.dll")] 35 | public static extern int ShowCursor(bool bShow); 36 | 37 | [DllImport("user32.dll")] 38 | public static extern int SetCursorPos(int x, int y); 39 | 40 | [DllImport("user32.dll")] 41 | public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); 42 | 43 | [StructLayout(LayoutKind.Sequential)] 44 | public struct POINT 45 | { 46 | public int X; 47 | public int Y; 48 | } 49 | 50 | [DllImport("user32.dll")] 51 | [return: MarshalAs(UnmanagedType.Bool)] 52 | public static extern bool GetCursorPos(out POINT lpPoint); 53 | 54 | #endregion 55 | 56 | #region 键盘相关 57 | 58 | public const int WH_KEYBOARD_LL = 13; 59 | public const int WM_KEYDOWN = 0x0100; 60 | public const int WM_SYSKEYDOWN = 0x0104; 61 | 62 | public const int VK_LWIN = 0x5B; 63 | public const int VK_RWIN = 0x5C; 64 | public const int VK_LSHIFT = 0xA0; 65 | public const int VK_RSHIFT = 0xA1; 66 | public const int VK_LCONTROL = 0xA2; 67 | public const int VK_RCONTROL = 0xA3; 68 | //ALT 69 | public const int VK_LMENU = 0xA4; 70 | public const int VK_RMENU = 0xA5; 71 | public const int VK_TAB = 0x09; 72 | 73 | public delegate int HookDelegate(int nCode, int wParam, IntPtr lParam); 74 | 75 | [DllImport("user32.dll")] 76 | public static extern int SetWindowsHookEx(int idHook, HookDelegate callback, IntPtr hInstance, uint threadId); 77 | 78 | [DllImport("user32.dll")] 79 | public static extern int UnhookWindowsHookEx(int idHook); 80 | 81 | [DllImport("user32.dll")] 82 | public static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam); 83 | 84 | [DllImport("kernel32.dll")] 85 | public static extern IntPtr GetModuleHandle(string lpModuleName); 86 | 87 | #endregion 88 | 89 | #region 系统空闲相关 90 | 91 | public struct LastInputInfo 92 | { 93 | public uint cbSize; 94 | public uint dwTime; 95 | } 96 | 97 | [DllImport("user32.dll")] 98 | public static extern bool GetLastInputInfo(ref LastInputInfo plii); 99 | 100 | #endregion 101 | 102 | #region 电源相关 103 | 104 | [DllImport("user32.dll", SetLastError = true)] 105 | public static extern bool ExitWindowsEx(uint uFlags, uint dwReason); 106 | 107 | [DllImport("powrprof.dll", SetLastError = true)] 108 | [return: MarshalAs(UnmanagedType.Bool)] 109 | public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent); 110 | 111 | [DllImport("advapi32.dll", SetLastError = true)] 112 | public static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); 113 | 114 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] 115 | public static extern bool LookupPrivilegeValue(string? lpSystemName, string lpName, out LUID lpLuid); 116 | 117 | [DllImport("advapi32.dll", SetLastError = true)] 118 | public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength); 119 | 120 | [StructLayout(LayoutKind.Sequential)] 121 | public struct LUID 122 | { 123 | public uint LowPart; 124 | public int HighPart; 125 | } 126 | 127 | [StructLayout(LayoutKind.Sequential)] 128 | public struct TOKEN_PRIVILEGES 129 | { 130 | public uint PrivilegeCount; 131 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] 132 | public LUID_AND_ATTRIBUTES[] Privileges; 133 | } 134 | 135 | [StructLayout(LayoutKind.Sequential)] 136 | public struct LUID_AND_ATTRIBUTES 137 | { 138 | public LUID Luid; 139 | public uint Attributes; 140 | } 141 | 142 | public const uint EWX_SHUTDOWN = 0x00000001; 143 | public const uint TOKEN_QUERY = 0x0008; 144 | public const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; 145 | public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; 146 | public const uint SE_PRIVILEGE_ENABLED = 0x00000002; 147 | 148 | #endregion 149 | 150 | #region 消息相关 151 | [StructLayout(LayoutKind.Sequential)] 152 | public struct ApiMessage 153 | { 154 | public IntPtr hwnd; 155 | public uint message; 156 | public IntPtr wParam; 157 | public IntPtr lParam; 158 | public uint time; 159 | public Point pt; 160 | } 161 | 162 | [DllImport("user32.dll")] 163 | public static extern bool GetMessage(out ApiMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); 164 | 165 | [DllImport("user32.dll")] 166 | public static extern bool TranslateMessage([In] ref ApiMessage lpMsg); 167 | 168 | [DllImport("user32.dll")] 169 | public static extern IntPtr DispatchMessage([In] ref ApiMessage lpMsg); 170 | #endregion 171 | } -------------------------------------------------------------------------------- /src/ComputerLock/WindowMain.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Windows; 3 | using ComputerLock.Interfaces; 4 | 5 | namespace ComputerLock; 6 | 7 | public partial class WindowMain : Window, IDisposable 8 | { 9 | private readonly AppSettings _appSettings; 10 | private readonly IGlobalLockService _globalLockService; 11 | private readonly ILogger _logger; 12 | private readonly HotkeyHook _hotkeyHook; 13 | private readonly IStringLocalizer _lang; 14 | private readonly IWindowsMessageBox _windowsMessageBox; 15 | 16 | private readonly NotifyIcon _notifyIcon = new(); 17 | private readonly ContextMenuStrip _contextMenuStrip = new(); 18 | 19 | public WindowMain(AppSettings appSettings, IGlobalLockService globalLockService, ILogger logger, HotkeyHook hotkeyHook, IStringLocalizer lang, IWindowsMessageBox windowsMessageBox) 20 | { 21 | InitializeComponent(); 22 | 23 | _appSettings = appSettings; 24 | _globalLockService = globalLockService; 25 | _logger = logger; 26 | _hotkeyHook = hotkeyHook; 27 | _lang = lang; 28 | _windowsMessageBox = windowsMessageBox; 29 | 30 | InitializeNotifyIcon(); 31 | 32 | _logger.Info("系统启动"); 33 | 34 | if (_appSettings.LockHotkeyString.IsNotEmpty()) 35 | { 36 | RegisterLockHotkey(); 37 | } 38 | _hotkeyHook.HotkeyPressed += (id) => 39 | { 40 | if (id == (int)HotkeyType.Lock) 41 | { 42 | if (!_globalLockService.IsLocked) 43 | { 44 | _logger.Info("快捷键锁定"); 45 | _globalLockService.Lock(); 46 | } 47 | else 48 | { 49 | if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Hotkey && _appSettings.IsUnlockUseLockHotkey) 50 | { 51 | _logger.Info("快捷键解锁"); 52 | _globalLockService.Unlock(); 53 | } 54 | } 55 | } 56 | else if (id == (int)HotkeyType.Unlock) 57 | { 58 | if (_globalLockService.IsLocked) 59 | { 60 | _logger.Info("快捷键解锁(独立解锁)"); 61 | _globalLockService.Unlock(); 62 | } 63 | } 64 | }; 65 | 66 | if (_appSettings.LockOnStartup) 67 | { 68 | _logger.Info("程序启动时锁定屏幕"); 69 | _globalLockService.Lock(); 70 | } 71 | 72 | } 73 | 74 | private void Window_Loaded(object sender, RoutedEventArgs e) 75 | { 76 | this.Title = Lang.Title; 77 | this.WindowState = _appSettings.IsHideWindowWhenLaunch ? WindowState.Minimized : WindowState.Normal; 78 | } 79 | 80 | private void InitializeNotifyIcon() 81 | { 82 | 83 | var btnShowWindow = new ToolStripMenuItem(Lang.ShowMainWindow); 84 | btnShowWindow.Click += (_, _) => ShowMainWindow(); 85 | _contextMenuStrip.Items.Add(btnShowWindow); 86 | 87 | var btnLock = new ToolStripMenuItem(Lang.DoLock); 88 | btnLock.Click += (_, _) => 89 | { 90 | _logger.Info("托盘锁定"); 91 | _globalLockService.Lock(); 92 | }; 93 | _contextMenuStrip.Items.Add(btnLock); 94 | 95 | var btnClose = new ToolStripMenuItem(Lang.Exit); 96 | btnClose.Click += (_, _) => 97 | { 98 | _logger.Info("托盘关闭"); 99 | System.Windows.Application.Current.Shutdown(); 100 | }; 101 | _contextMenuStrip.Items.Add(btnClose); 102 | 103 | _notifyIcon.ContextMenuStrip = _contextMenuStrip; 104 | Stream iconStream = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/ComputerLock;component/icon.ico")).Stream; 105 | _notifyIcon.Icon = new Icon(iconStream); 106 | _notifyIcon.Text = Lang.Title; 107 | _notifyIcon.Click += (_, e) => 108 | { 109 | var args = e as MouseEventArgs; 110 | if (args is not { Button: MouseButtons.Left }) 111 | { 112 | return; 113 | } 114 | ShowMainWindow(); 115 | }; 116 | _notifyIcon.Visible = true; 117 | } 118 | 119 | private void ShowMainWindow() 120 | { 121 | this.ShowInTaskbar = true; 122 | this.WindowState = WindowState.Normal; 123 | this.Activate(); 124 | } 125 | 126 | private void Window_StateChanged(object sender, EventArgs e) 127 | { 128 | if (this.WindowState == WindowState.Minimized) 129 | { 130 | this.ShowInTaskbar = false; 131 | } 132 | } 133 | 134 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 135 | { 136 | if (_appSettings.IsHideWindowWhenClose) 137 | { 138 | this.WindowState = WindowState.Minimized; 139 | e.Cancel = true; 140 | } 141 | } 142 | private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) 143 | { 144 | if (_appSettings.IsHideWindowWhenEsc) 145 | { 146 | if (e.Key == System.Windows.Input.Key.Escape) 147 | { 148 | this.WindowState = WindowState.Minimized; 149 | this.ShowInTaskbar = false; 150 | } 151 | } 152 | } 153 | 154 | private void RegisterLockHotkey() 155 | { 156 | try 157 | { 158 | if (_appSettings.LockHotkey != null) 159 | { 160 | _logger.Info("注册锁屏热键"); 161 | _hotkeyHook.Register((int)HotkeyType.Lock, _appSettings.LockHotkey); 162 | } 163 | } 164 | catch (Exception ex) 165 | { 166 | _logger.Error($"绑定锁屏热键失败", ex); 167 | _windowsMessageBox.Show($"{_lang["ExRegistFailed"]}{ex.Message}"); 168 | } 169 | } 170 | public void Dispose() 171 | { 172 | _logger.Info("系统资源释放,系统关闭"); 173 | _notifyIcon.Dispose(); 174 | _globalLockService.Dispose(); 175 | } 176 | } -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/GeneralSettings.razor: -------------------------------------------------------------------------------- 1 | @page "/general-settings" 2 | 3 | @(Lang["GeneralSettings"]) 4 | 5 | 6 | 7 | @(Lang["General_Group_Startup"]) 8 | 9 | 16 | 22 | 28 |
29 | @(Lang["CloseMainWindow"]) 30 | 38 | @(Lang["MinimizeToSystemTray"]) 39 | @(Lang["ExitProgram"]) 40 | 41 |
42 |
43 |
44 | 45 | 46 | 47 | @(Lang["General_Group_Appearance"]) 48 | 49 |
50 | 58 | 59 | @(Lang["EnableSoftwareRenderingRemark"]) 60 | 61 |
62 | 63 |
64 | @(Lang["Appearance"]) 65 | 73 | @($"🌓 {Lang["SystemTheme"]}") 74 | @($"☀️ {Lang["LightTheme"]}") 75 | @($"🌙 {Lang["DarkTheme"]}") 76 | 77 |
78 |
79 | @("语言 / Language") 80 | 88 | 中文 89 | English 90 | 91 |
92 |
93 |
94 | 95 | 96 | 97 | @($"{Lang["General_Group_Logs"]} {_logFilesSize / 1024 / 1024:N2} MB") 98 | 99 | @if (!_logLoadingOk) 100 | { 101 | 102 | } 103 | else 104 | { 105 | 106 | 111 | @(Lang["OpenLogs"]) 112 | 113 | 118 | @(Lang["Delete"]) 119 | 120 | 121 | } 122 | 123 | 124 | 125 | 126 | 127 | @(Lang["General_Group_Advanced"]) 128 | 133 | @(Lang["ResetSettings"]) 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/GeneralSettings.razor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using Application = System.Windows.Application; 4 | namespace ComputerLock.Components.Settings; 5 | 6 | public partial class GeneralSettings 7 | { 8 | private bool _isAutostart; 9 | private readonly string _logPath = Path.Combine(Environment.CurrentDirectory, "log"); 10 | private bool _logLoadingOk; 11 | private double _logFilesSize; 12 | 13 | [Inject] 14 | private AppSettings AppSettings { get; set; } = null!; 15 | [Inject] 16 | private AppSettingsProvider AppSettingsProvider { get; set; } = null!; 17 | [Inject] 18 | private IStringLocalizer Lang { get; set; } = null!; 19 | [Inject] 20 | private AutostartHook AutostartHook { get; set; } = null!; 21 | [Inject] 22 | private ISnackbar Snackbar { get; set; } = null!; 23 | [Inject] 24 | private IDialogService DialogService { get; set; } = null!; 25 | [Inject] 26 | private ThemeSwitchService ThemeSwitchService { get; set; } = null!; 27 | 28 | protected override async Task OnInitializedAsync() 29 | { 30 | await base.OnInitializedAsync(); 31 | _isAutostart = AutostartHook.IsAutostart(); 32 | await CalcLogSizeAsync(); 33 | } 34 | 35 | private void AutostartChange(bool isChecked) 36 | { 37 | if (isChecked) 38 | { 39 | AutostartHook.EnabledAutostart(); 40 | } 41 | else 42 | { 43 | AutostartHook.DisabledAutostart(); 44 | } 45 | _isAutostart = AutostartHook.IsAutostart(); 46 | } 47 | 48 | private void SoftwareRenderingChange(bool isChecked) 49 | { 50 | AppSettings.IsEnableSoftwareRendering = isChecked; 51 | SaveSettings(); 52 | RestartTips(); 53 | } 54 | 55 | private void SaveSettings() 56 | { 57 | AppSettingsProvider.SaveSettings(AppSettings); 58 | } 59 | 60 | private CloseWindowAction CloseWindowActionValue => AppSettings.IsHideWindowWhenClose ? CloseWindowAction.MinimizeToTray : CloseWindowAction.Exit; 61 | 62 | private void CloseWindowActionChanged(CloseWindowAction action) 63 | { 64 | AppSettings.IsHideWindowWhenClose = action == CloseWindowAction.MinimizeToTray; 65 | SaveSettings(); 66 | } 67 | 68 | private void ThemeChanged(ThemeEnum theme) 69 | { 70 | AppSettings.AppTheme = theme; 71 | SaveSettings(); 72 | ThemeSwitchService.SetDarkMode(theme); 73 | } 74 | 75 | private Task LangValueChanged(string lang) 76 | { 77 | LangEnum langEnum = (LangEnum)Enum.Parse(typeof(LangEnum), lang); 78 | AppSettings.Lang = langEnum; 79 | SaveSettings(); 80 | RestartTips(); 81 | return Task.CompletedTask; 82 | } 83 | 84 | private void RestartTips() 85 | { 86 | Snackbar.Configuration.NewestOnTop = true; 87 | Snackbar.Configuration.VisibleStateDuration = int.MaxValue; 88 | Snackbar.Configuration.ShowCloseIcon = true; 89 | Snackbar.Configuration.SnackbarVariant = Variant.Text; 90 | Snackbar.Add("重启后生效 Take effect after restart", Severity.Normal, config => 91 | { 92 | config.Action = "重启 Restart"; 93 | config.HideIcon = true; 94 | config.ActionColor = MudBlazor.Color.Warning; 95 | config.ActionVariant = Variant.Outlined; 96 | config.OnClick = _ => 97 | { 98 | Restart(); 99 | return Task.CompletedTask; 100 | }; 101 | }); 102 | } 103 | 104 | private void Restart() 105 | { 106 | if (Application.Current.MainWindow == null) 107 | { 108 | return; 109 | } 110 | Application.Current.Shutdown(); 111 | System.Windows.Forms.Application.Restart(); 112 | } 113 | 114 | private void OpenLogPath() 115 | { 116 | Process.Start("explorer.exe", _logPath); 117 | } 118 | 119 | private async Task ClearLogAsync() 120 | { 121 | var options = new DialogOptions { NoHeader = true, CloseOnEscapeKey = false, BackdropClick = false, BackgroundClass = "dialog-backdrop-filter" }; 122 | bool? result = await DialogService.ShowMessageBox( 123 | "", 124 | Lang["RemoveLogsConfirm"], 125 | options: options, 126 | yesText: Lang["Delete"], 127 | cancelText: Lang["Cancel"]); 128 | if (result != true) 129 | { 130 | return; 131 | } 132 | 133 | try 134 | { 135 | if (!Directory.Exists(_logPath)) 136 | { 137 | return; 138 | } 139 | var files = Directory.GetFiles(_logPath); 140 | foreach (var file in files) 141 | { 142 | File.Delete(file); 143 | } 144 | 145 | Snackbar.Add(Lang["RemoveLogsOk"], Severity.Success); 146 | await CalcLogSizeAsync(); 147 | } 148 | catch (Exception ex) 149 | { 150 | Snackbar.Add($"日志文件删除失败:{ex.Message}", Severity.Error); 151 | } 152 | } 153 | 154 | private async Task CalcLogSizeAsync() 155 | { 156 | try 157 | { 158 | _logFilesSize = 0; 159 | _logLoadingOk = false; 160 | await InvokeAsync(StateHasChanged); 161 | 162 | if (!Directory.Exists(_logPath)) 163 | { 164 | return; 165 | } 166 | var files = Directory.GetFiles(_logPath); 167 | foreach (var file in files) 168 | { 169 | var fi = new FileInfo(file); 170 | _logFilesSize += fi.Length; 171 | } 172 | } 173 | catch (Exception ex) 174 | { 175 | Snackbar.Add($"日志文件计算失败:{ex.Message}", Severity.Error); 176 | } 177 | finally 178 | { 179 | _logLoadingOk = true; 180 | await InvokeAsync(StateHasChanged); 181 | } 182 | } 183 | 184 | private async Task ResetSettingsAsync() 185 | { 186 | var options = new DialogOptions { NoHeader = true, CloseOnEscapeKey = false, BackdropClick = false, BackgroundClass = "dialog-backdrop-filter" }; 187 | bool? result = await DialogService.ShowMessageBox( 188 | "", 189 | Lang["ResetSettingsMessage"], 190 | options: options, 191 | yesText: Lang["Save"], 192 | cancelText: Lang["Cancel"]); 193 | if (result != true) 194 | { 195 | return; 196 | } 197 | 198 | AppSettingsProvider.RemoveSettings(); 199 | Restart(); 200 | } 201 | } -------------------------------------------------------------------------------- /src/ComputerLock/Resources/Resource.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | https://jiuling.cc/api/app/check-update/cdbd0b29d200c61ace8a1ff74605b775/windows 122 | 123 | 124 | https://www.jiuling.cc/api/donation/list 125 | 126 | 127 | https://jiuling.cc/donation 128 | 129 | 130 | https://github.com/JiuLing-zhang/ComputerLock 131 | 132 | 133 | https://jiuling.cc/computer-lock 134 | 135 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/UnlockSettings.razor: -------------------------------------------------------------------------------- 1 | @page "/unlock-settings" 2 | 3 | @{ 4 | var passwordCardClass = "pa-4 unlock-card" + (AppSettings.ScreenUnlockMethod == ScreenUnlockMethods.Password ? " selected" : ""); 5 | var hotkeyCardClass = "pa-4 mb-4 unlock-card" + (AppSettings.ScreenUnlockMethod == ScreenUnlockMethods.Hotkey ? " selected" : ""); 6 | bool isPasswordSelected = AppSettings.ScreenUnlockMethod == ScreenUnlockMethods.Password; 7 | bool isHotkeySelected = AppSettings.ScreenUnlockMethod == ScreenUnlockMethods.Hotkey; 8 | } 9 | 10 | @(Lang["UnlockOptions"]) 11 | 12 |
13 | 14 |
15 | @($"{Lang["HotkeyUnlock"]} ⚡") 16 | 18 |
19 |
20 | 27 | 28 | @(Lang["UnlockHotkey"]) 29 | 34 | @if (!isHotkeySelected) 35 | { 36 |
37 | } 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 | @($"{Lang["PasswordUnlock"]} 🧩") 46 | 48 |
49 |
50 | 51 | 52 | @if (AppSettings.EnablePasswordBox) 53 | { 54 | @(Lang["DisablePasswordBoxTips"]) 55 | } 56 | else 57 | { 58 | @(Lang["EnablePasswordBoxTips"]) 59 | } 60 | 61 | 62 | 69 | @(Lang["SetPassword"]) 70 | 71 | 78 | 79 | 80 |
81 | @(Lang["PwdLocation"]) 82 | 92 | @(Lang["Center"]) 93 | @(Lang["TopLeft"]) 94 | @(Lang["TopRight"]) 95 | @(Lang["BottomLeft"]) 96 | @(Lang["BottomRight"]) 97 | 98 |
99 | 106 | 113 | 120 | 121 |
122 | @if (!isPasswordSelected) 123 | { 124 |
125 | } 126 |
127 |
128 |
129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /src/ComputerLock/WindowLockScreen.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Input; 3 | using System.Windows.Threading; 4 | 5 | namespace ComputerLock; 6 | /// 7 | /// WindowLockScreen.xaml 的交互逻辑 8 | /// 9 | public partial class WindowLockScreen : Window 10 | { 11 | private DateTime _hideSelfTime; 12 | 13 | private readonly int _hideSelfSecond = 3; 14 | private readonly DispatcherTimer _timer = new(); 15 | private readonly AppSettings _appSettings; 16 | private readonly IStringLocalizer _lang; 17 | private readonly ILogger _logger; 18 | 19 | public event EventHandler? OnUnlock; 20 | 21 | public WindowLockScreen(AppSettings appSettings, IStringLocalizer lang, ILogger logger) 22 | { 23 | InitializeComponent(); 24 | _appSettings = appSettings; 25 | _lang = lang; 26 | _logger = logger; 27 | 28 | _timer.Interval = TimeSpan.FromSeconds(1); 29 | _timer.Tick += Timer_Tick; 30 | _timer.Start(); 31 | } 32 | 33 | private void Window_Loaded(object sender, RoutedEventArgs e) 34 | { 35 | _logger.Info("功能屏幕 -> 准备锁定"); 36 | LblPassword.Content = _lang["Password"]; 37 | 38 | _logger.Info($"功能屏幕 -> 启用密码框:{_appSettings.EnablePasswordBox}"); 39 | if (_appSettings.EnablePasswordBox) 40 | { 41 | _logger.Info($"功能屏幕 -> 自动隐藏密码框:{_appSettings.IsHidePasswordWindow}"); 42 | if (_appSettings.IsHidePasswordWindow) 43 | { 44 | LblMessage.Visibility = Visibility.Visible; 45 | LblMessage.Content = string.Format(_lang["HideAfterXSecond"], _hideSelfSecond); 46 | } 47 | RefreshHideSelfTime(); 48 | } 49 | HidePassword(); 50 | 51 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.BreathingTop)) 52 | { 53 | _logger.Info("功能屏幕 -> 启用顶部呼吸灯"); 54 | BreathingLightHelper.InitializeBreathingLight(TopBreathingLight); 55 | } 56 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.DotTopLeft)) 57 | { 58 | _logger.Info("功能屏幕 -> 启用左上角圆点"); 59 | BreathingLightHelper.InitializeBreathingLight(DotTopLeft); 60 | } 61 | if (_appSettings.LockStatusDisplay.HasFlag(LockStatusDisplay.DotTopRight)) 62 | { 63 | _logger.Info("功能屏幕 -> 启用右上角圆点"); 64 | BreathingLightHelper.InitializeBreathingLight(DotTopRight); 65 | } 66 | } 67 | 68 | private void Window_SizeChanged(object sender, SizeChangedEventArgs e) 69 | { 70 | switch (_appSettings.PasswordInputLocation) 71 | { 72 | case ScreenLocationEnum.Center: 73 | PasswordBlock.VerticalAlignment = VerticalAlignment.Center; 74 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; 75 | break; 76 | case ScreenLocationEnum.TopLeft: 77 | PasswordBlock.VerticalAlignment = VerticalAlignment.Top; 78 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 79 | break; 80 | case ScreenLocationEnum.TopRight: 81 | PasswordBlock.VerticalAlignment = VerticalAlignment.Top; 82 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Right; 83 | break; 84 | case ScreenLocationEnum.BottomLeft: 85 | PasswordBlock.VerticalAlignment = VerticalAlignment.Bottom; 86 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; 87 | break; 88 | case ScreenLocationEnum.BottomRight: 89 | PasswordBlock.VerticalAlignment = VerticalAlignment.Bottom; 90 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Right; 91 | break; 92 | default: 93 | PasswordBlock.VerticalAlignment = VerticalAlignment.Center; 94 | PasswordBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; 95 | break; 96 | } 97 | } 98 | 99 | public void ShowPassword() 100 | { 101 | if (_appSettings.EnablePasswordBox) 102 | { 103 | _logger.Info("功能屏幕 -> 准备显示密码框"); 104 | RefreshHideSelfTime(); 105 | TxtPassword.Visibility = Visibility.Visible; 106 | PasswordBlock.Opacity = 1; 107 | } 108 | else 109 | { 110 | _logger.Info("功能屏幕 -> 准备无感解锁"); 111 | TxtPassword.Visibility = Visibility.Visible; 112 | PasswordBlock.Width = 1; 113 | PasswordBlock.Opacity = 0.01; 114 | } 115 | TxtPassword.Password = ""; 116 | TxtPassword.Focus(); 117 | } 118 | 119 | private void Timer_Tick(object? sender, EventArgs e) 120 | { 121 | try 122 | { 123 | var time = DateTime.Now; 124 | if (_appSettings.EnablePasswordBox) 125 | { 126 | if (_appSettings.IsHidePasswordWindow) 127 | { 128 | var hideCountdown = Convert.ToInt32(_hideSelfTime.Subtract(time).TotalSeconds); 129 | LblMessage.Content = string.Format(_lang["HideAfterXSecond"], hideCountdown); 130 | if (hideCountdown == 0) 131 | { 132 | _logger.Info("功能屏幕 -> 准备自动隐藏密码框"); 133 | HidePassword(); 134 | } 135 | } 136 | } 137 | } 138 | catch (Exception ex) 139 | { 140 | _logger.Error($"功能屏幕 -> 定时组件异常", ex); 141 | } 142 | } 143 | 144 | private void TxtPassword_PasswordChanged(object sender, RoutedEventArgs e) 145 | { 146 | _logger.Info("功能屏幕 -> 检测到密码输入"); 147 | if (_appSettings.EnablePasswordBox) 148 | { 149 | RefreshHideSelfTime(); 150 | } 151 | var txt = TxtPassword.Password; 152 | if (txt.IsEmpty()) 153 | { 154 | _logger.Info("功能屏幕 -> 输入为空,跳过"); 155 | return; 156 | } 157 | if (_appSettings.Password != JiuLing.CommonLibs.Security.MD5Utils.GetStringValueToLower(txt)) 158 | { 159 | return; 160 | } 161 | _logger.Info("功能屏幕 -> 密码正确,通知解锁"); 162 | OnUnlock?.Invoke(this, EventArgs.Empty); 163 | } 164 | 165 | private void TxtPassword_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) 166 | { 167 | if (e.Key == Key.Enter || e.Key == Key.Escape) 168 | { 169 | _logger.Info("功能屏幕 -> 清空密码框"); 170 | TxtPassword.Password = ""; 171 | } 172 | } 173 | 174 | private void HidePassword() 175 | { 176 | _logger.Info("功能屏幕 -> 准备隐藏密码框"); 177 | if (PasswordBlock.Opacity == 1) 178 | { 179 | TxtPassword.Visibility = Visibility.Collapsed; 180 | PasswordBlock.Opacity = 0; 181 | } 182 | } 183 | private void RefreshHideSelfTime() 184 | { 185 | _hideSelfTime = DateTime.Now.AddSeconds(_hideSelfSecond); 186 | this.Dispatcher.BeginInvoke(new Action(() => 187 | { 188 | LblMessage.Content = string.Format(_lang["HideAfterXSecond"], _hideSelfSecond); 189 | })); 190 | } 191 | 192 | private void PasswordBlock_MouseDown(object sender, MouseButtonEventArgs e) 193 | { 194 | _logger.Info("功能屏幕 -> 密码框位置检测到点击"); 195 | if (_appSettings.EnablePasswordBox) 196 | { 197 | if ((_appSettings.PasswordBoxActiveMethod & PasswordBoxActiveMethodEnum.MouseDown) != PasswordBoxActiveMethodEnum.MouseDown) 198 | { 199 | return; 200 | } 201 | _logger.Info("功能屏幕 -> 准备鼠标解锁密码框"); 202 | ShowPassword(); 203 | } 204 | } 205 | 206 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 207 | { 208 | _logger.Info($"功能屏幕 -> 准备关闭"); 209 | _timer.Stop(); 210 | } 211 | 212 | private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) 213 | { 214 | _logger.Info("功能屏幕 -> 检测到按键"); 215 | if (e.Key != Key.Escape) 216 | { 217 | return; 218 | } 219 | _logger.Info("功能屏幕 -> 按下ESC功能键"); 220 | if (_appSettings.EnablePasswordBox) 221 | { 222 | if ((_appSettings.PasswordBoxActiveMethod & PasswordBoxActiveMethodEnum.KeyboardDown) != PasswordBoxActiveMethodEnum.KeyboardDown) 223 | { 224 | return; 225 | } 226 | } 227 | _logger.Info("功能屏幕 -> 准备执行解锁"); 228 | ShowPassword(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/ComputerLock/Components/Settings/LockSettings.razor: -------------------------------------------------------------------------------- 1 | @page "/lock-settings" 2 | 3 | @(Lang["LockOptions"]) 4 | 5 | 6 | 7 | @(Lang["Lock_Group_Behavior"]) 8 | 9 | 15 | 16 |
17 | @($"{Lang["AutoLock"]}{Lang["MinSetTip"]}") 18 | 29 |
30 | 31 | 35 |
36 |
37 | 38 | 39 | 40 | @(Lang["Lock_Group_WindowsIntegration"]) 41 | 42 |
43 | 49 | 50 | @(Lang["DisableWindowsRemark"]) 51 | 52 |
53 | 54 |
55 | 61 | @(Lang["SuggestedToEnable"]) 67 |
68 | 69 |
70 | @($"{Lang["AutoPower"]}{Lang["MinSetTip"]}") 71 | 72 | 83 | 84 | 91 | @(Lang["Shutdown"]) 92 | @(Lang["Hibernate"]) 93 | 94 | 95 |
96 |
97 |
98 | 99 | 100 | 101 | @(Lang["Lock_Group_Display"]) 102 | 103 | 109 | 110 | 116 |
117 | 123 | 124 | 125 | 136 | 137 |
138 | @(Lang["LockHintText"]) 139 |
140 |
141 |
142 | 148 |
149 |
150 |
151 |
152 | @(Lang["LockStatusDisplay"]) 153 | 161 | @($"🚫 {Lang["LockStatusDisplayNone"]}") 162 | @($"💡 {Lang["LockStatusDisplayBreathingTop"]}") 163 | @($"🔵 {Lang["LockStatusDisplayDotTopLeft"]}") 164 | @($"🔴 {Lang["LockStatusDisplayDotTopRight"]}") 165 | 166 |
167 |
168 |
-------------------------------------------------------------------------------- /src/ComputerLock/Services/GlobalLockService.cs: -------------------------------------------------------------------------------- 1 | using ComputerLock.Interfaces; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Win32; 4 | 5 | namespace ComputerLock.Services; 6 | 7 | /// 8 | /// 锁定器,全局锁定:包括鼠标、键盘、任务管理器、系统快捷键等 9 | /// 10 | internal class GlobalLockService : IGlobalLockService 11 | { 12 | private readonly ILogger _logger; 13 | private readonly AppSettings _appSettings; 14 | private IScreenLockService? _screenLockService; 15 | private readonly UserActivityMonitor _activityMonitor; 16 | private readonly HotkeyHook _hotkeyHook; 17 | private readonly TaskManagerHook _taskManagerHook; 18 | private readonly MouseHook _mouseHook; 19 | private readonly SystemKeyHook _systemKeyHook; 20 | private readonly IServiceProvider _serviceProvider; 21 | private readonly IWindowsMessageBox _messageBox; 22 | private readonly IStringLocalizer _lang; 23 | private readonly PopupService _popupService; 24 | public bool IsLocked { get; private set; } 25 | private bool _isWindowsLocked; 26 | private CancellationTokenSource? _cts; 27 | private CancellationTokenSource? _powerActionCts; 28 | private PowerManager _powerManager; 29 | public GlobalLockService(ILogger logger, AppSettings appSettings, UserActivityMonitor activityMonitor, HotkeyHook hotkeyHook, TaskManagerHook taskManagerHook, MouseHook mouseHook, SystemKeyHook systemKeyHook, IServiceProvider serviceProvider, IWindowsMessageBox messageBox, IStringLocalizer lang, PopupService popupService, PowerManager powerManager) 30 | { 31 | _logger = logger; 32 | _appSettings = appSettings; 33 | _activityMonitor = activityMonitor; 34 | _hotkeyHook = hotkeyHook; 35 | _taskManagerHook = taskManagerHook; 36 | 37 | _taskManagerHook.RecoverFromCrash(); 38 | _mouseHook = mouseHook; 39 | _systemKeyHook = systemKeyHook; 40 | _serviceProvider = serviceProvider; 41 | _messageBox = messageBox; 42 | _lang = lang; 43 | _popupService = popupService; 44 | _powerManager = powerManager; 45 | 46 | InitActivityMonitor(); 47 | InitUserInputHandling(); 48 | 49 | _logger.Info("空闲自动锁定 -> 准备监控系统会话状态"); 50 | SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; 51 | } 52 | 53 | /// 54 | /// 初始化空闲检测 55 | /// 56 | private void InitActivityMonitor() 57 | { 58 | _activityMonitor.OnIdle += (_, _) => 59 | { 60 | _logger.Info("空闲自动锁定 -> 执行空闲锁定"); 61 | Lock(); 62 | }; 63 | 64 | AutoLockStart(); 65 | } 66 | 67 | private void InitUserInputHandling() 68 | { 69 | _logger.Info("系统 -> 准备监听用户输入状态"); 70 | _systemKeyHook.OnUserInput += (_, _) => 71 | { 72 | if (_appSettings.LockTips && IsLocked) 73 | { 74 | _logger.Info("用户输入 -> 检测到键盘输入"); 75 | _popupService.ShowMessage(GetLockTipsMessage()); 76 | } 77 | }; 78 | 79 | _mouseHook.OnUserInput += (_, _) => 80 | { 81 | if (_appSettings.LockTips && IsLocked) 82 | { 83 | _logger.Info("用户输入 -> 检测到鼠标输入"); 84 | _popupService.ShowMessage(GetLockTipsMessage()); 85 | } 86 | }; 87 | } 88 | 89 | /// 90 | /// 获取锁定提示文案 91 | /// 92 | /// 93 | private string GetLockTipsMessage() 94 | { 95 | return _appSettings.LockTipsMessage.IsEmpty() 96 | ? _lang["LockTipsValue"] 97 | : _appSettings.LockTipsMessage; 98 | } 99 | 100 | /// 101 | /// Windows 事件监控 102 | /// 103 | private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) 104 | { 105 | if (e.Reason == SessionSwitchReason.SessionLock) 106 | { 107 | _logger.Info("系统 -> Windows 系统已锁定"); 108 | _isWindowsLocked = true; 109 | WindowsLock(); 110 | } 111 | else if (e.Reason == SessionSwitchReason.SessionUnlock) 112 | { 113 | _logger.Info("系统 -> Windows 系统已解除锁定"); 114 | _isWindowsLocked = false; 115 | WindowsUnlock(); 116 | } 117 | } 118 | 119 | /// 120 | /// Windows 操作系统锁定 121 | /// 122 | private void WindowsLock() 123 | { 124 | if (!_appSettings.IsUnlockWhenWindowsLock) 125 | { 126 | if (!IsLocked) 127 | { 128 | _logger.Info("系统 -> Windows 系统锁定,暂停空闲检测"); 129 | _activityMonitor.StopMonitoring(); 130 | } 131 | } 132 | else 133 | { 134 | if (IsLocked) 135 | { 136 | _logger.Info("系统 -> Windows 系统锁定,程序解锁"); 137 | Unlock(); 138 | } 139 | } 140 | } 141 | 142 | /// 143 | /// Windows 操作系统解锁 144 | /// 145 | private void WindowsUnlock() 146 | { 147 | if (!_appSettings.IsUnlockWhenWindowsLock) 148 | { 149 | if (!IsLocked) 150 | { 151 | _logger.Info($"系统 -> Windows 系统解锁"); 152 | AutoLockStart(); 153 | } 154 | } 155 | } 156 | 157 | private void AutoLockStart() 158 | { 159 | if (_isWindowsLocked && _appSettings.IsUnlockWhenWindowsLock) 160 | { 161 | _logger.Info($"系统 -> Windows 锁定状态,不启用空闲检测"); 162 | } 163 | 164 | if (_appSettings.AutoLockSecond > 0) 165 | { 166 | _logger.Info($"系统 -> 启动空闲检测,{_appSettings.AutoLockSecond} 秒"); 167 | _activityMonitor.SetAutoLockSecond(_appSettings.AutoLockSecond); 168 | _activityMonitor.StartMonitoring(); 169 | } 170 | } 171 | 172 | public void Lock() 173 | { 174 | if (_isWindowsLocked && _appSettings.IsUnlockWhenWindowsLock) 175 | { 176 | _logger.Info($"系统 -> Windows 锁定状态禁止程序锁定"); 177 | return; 178 | } 179 | 180 | if (!CheckLockConfig(out var message)) 181 | { 182 | _messageBox.Show(message); 183 | return; 184 | } 185 | 186 | _screenLockService = _serviceProvider.GetRequiredKeyedService(_appSettings.ScreenUnlockMethod); 187 | 188 | if (!_screenLockService.Lock(_appSettings.LockAnimation)) 189 | { 190 | _logger.Info("全局锁定 -> 屏幕锁定失败"); 191 | return; 192 | } 193 | 194 | if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Password) 195 | { 196 | _screenLockService.OnUnlock += _screenLockService_OnUnlock; 197 | } 198 | 199 | _logger.Info("全局锁定 -> 暂停空闲检测"); 200 | _activityMonitor.StopMonitoring(); 201 | 202 | _logger.Info("全局锁定 -> 禁用任务管理器和系统键"); 203 | _taskManagerHook.Lock(); 204 | 205 | if (_appSettings.IsHideMouseCursor) 206 | { 207 | _logger.Info("全局锁定 -> 隐藏鼠标光标"); 208 | _mouseHook.HideCursor(); 209 | } 210 | 211 | _logger.Info("全局锁定 -> 启用鼠标钩子"); 212 | _mouseHook.InstallHook(); 213 | 214 | if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Hotkey) 215 | { 216 | _logger.Info("全局锁定 -> 允许快捷键解锁,准备放行快捷键"); 217 | if (_appSettings.IsUnlockUseLockHotkey) 218 | { 219 | if (_appSettings.LockHotkey != null) 220 | { 221 | _systemKeyHook.SetIgnoreHotkey(_appSettings.LockHotkey); 222 | } 223 | } 224 | else 225 | { 226 | if (_appSettings.UnlockHotkey != null) 227 | { 228 | _systemKeyHook.SetIgnoreHotkey(_appSettings.UnlockHotkey); 229 | 230 | try 231 | { 232 | _logger.Info("全局锁定 -> 锁定时注册解锁快捷键"); 233 | _hotkeyHook.Register((int)HotkeyType.Unlock, _appSettings.UnlockHotkey); 234 | } 235 | catch (Exception ex) 236 | { 237 | _logger.Error("注册解锁快捷键失败", ex); 238 | } 239 | } 240 | } 241 | } 242 | else if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Password) 243 | { 244 | _logger.Info("全局锁定 -> 密码解锁,准备放行非系统按键"); 245 | _systemKeyHook.SetIgnoreHotkey(null); 246 | } 247 | _systemKeyHook.InstallHook(); 248 | 249 | if (_appSettings.IsDisableWindowsLock) 250 | { 251 | _cts = new CancellationTokenSource(); 252 | _logger.Info("全局锁定 -> 禁用 Windows 锁屏"); 253 | Task.Run(async () => 254 | { 255 | while (true) 256 | { 257 | _logger.Info("全局锁定 -> 移动鼠标,防止 Windows 锁屏"); 258 | _mouseHook.MoveAndClick(); 259 | try 260 | { 261 | await Task.Delay(TimeSpan.FromSeconds(55), _cts.Token); 262 | } 263 | catch (TaskCanceledException) 264 | { 265 | _logger.Info("全局锁定 -> 鼠标任务释放"); 266 | break; 267 | } 268 | } 269 | }, _cts.Token); 270 | } 271 | if (_appSettings.AutoPowerSecond > 0) 272 | { 273 | _powerActionCts = new CancellationTokenSource(); 274 | _logger.Info($"全局锁定 -> {_appSettings.AutoPowerSecond}秒后自动执行{_appSettings.AutoPowerActionType.GetDescription()}"); 275 | Task.Run(async () => 276 | { 277 | try 278 | { 279 | await Task.Delay(TimeSpan.FromSeconds(_appSettings.AutoPowerSecond), _powerActionCts.Token); 280 | if (_appSettings.AutoPowerActionType == PowerActionType.Shutdown) 281 | { 282 | _logger.Info("准备执行关机"); 283 | _powerManager.Shutdown(); 284 | } 285 | else if (_appSettings.AutoPowerActionType == PowerActionType.Hibernate) 286 | { 287 | _logger.Info("准备执行休眠"); 288 | _powerManager.Hibernate(); 289 | } 290 | } 291 | catch (TaskCanceledException) 292 | { 293 | _logger.Info("自动关机/休眠任务已取消"); 294 | } 295 | catch (Exception ex) 296 | { 297 | _logger.Error($"自动关机/休眠任务异常", ex); 298 | } 299 | }, _powerActionCts.Token); 300 | } 301 | IsLocked = true; 302 | } 303 | 304 | private bool CheckLockConfig(out string error) 305 | { 306 | if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Hotkey) 307 | { 308 | if (_appSettings.IsUnlockUseLockHotkey) 309 | { 310 | if (_appSettings.LockHotkey == null) 311 | { 312 | error = _lang["LockHotkeyUndefined"]; 313 | return false; 314 | } 315 | } 316 | else 317 | { 318 | if (_appSettings.UnlockHotkey == null) 319 | { 320 | error = _lang["UnlockHotkeyUndefined"]; 321 | return false; 322 | } 323 | } 324 | } 325 | else if (_appSettings.ScreenUnlockMethod == ScreenUnlockMethods.Password) 326 | { 327 | if (_appSettings.Password.IsEmpty()) 328 | { 329 | error = _lang["UnlockPasswordUndefined"]; 330 | return false; 331 | } 332 | } 333 | error = ""; 334 | return true; 335 | } 336 | 337 | /// 338 | /// 解锁(目前只有快捷键锁屏才会显式调用) 339 | /// 340 | public void Unlock() 341 | { 342 | _screenLockService!.Unlock(); 343 | SystemUnlock(); 344 | 345 | if (_powerActionCts != null) 346 | { 347 | _powerActionCts.Cancel(); 348 | _powerActionCts.Dispose(); 349 | _powerActionCts = null; 350 | } 351 | IsLocked = false; 352 | 353 | } 354 | 355 | public void UpdateAutoLockSettings() 356 | { 357 | _logger.Info("系统 -> 更新自动锁定设置"); 358 | _activityMonitor.StopMonitoring(); 359 | AutoLockStart(); 360 | } 361 | 362 | private void _screenLockService_OnUnlock(object? sender, EventArgs e) 363 | { 364 | _screenLockService!.OnUnlock -= _screenLockService_OnUnlock; 365 | Unlock(); 366 | } 367 | 368 | /// 369 | /// 系统层面解锁(不包括本程序窗口) 370 | /// 371 | private void SystemUnlock() 372 | { 373 | AutoLockStart(); 374 | 375 | _logger.Info("系统解锁 -> 恢复任务管理器和系统键"); 376 | _taskManagerHook.Unlock(); 377 | 378 | if (_appSettings.IsHideMouseCursor) 379 | { 380 | _logger.Info("系统解锁 -> 恢复鼠标光标"); 381 | _mouseHook.ResetCursorState(); 382 | } 383 | _mouseHook.Dispose(); 384 | _systemKeyHook.Dispose(); 385 | 386 | // 释放解锁快捷键 387 | if (_appSettings.UnlockHotkey != null) 388 | { 389 | try 390 | { 391 | _hotkeyHook.Unregister((int)HotkeyType.Unlock); 392 | } 393 | catch (Exception ex) 394 | { 395 | _logger.Error($"释放解锁热键失败", ex); 396 | } 397 | } 398 | 399 | if (_cts != null) 400 | { 401 | _cts.Cancel(); 402 | _cts.Dispose(); 403 | _cts = null; 404 | } 405 | } 406 | 407 | public void Dispose() 408 | { 409 | _activityMonitor.Dispose(); 410 | _hotkeyHook.Dispose(); 411 | SystemEvents.SessionSwitch -= SystemEvents_SessionSwitch; 412 | } 413 | } --------------------------------------------------------------------------------