From e1d5a0c6def8ef768806722db5530252bc36d40e Mon Sep 17 00:00:00 2001 From: lincube Date: Tue, 7 Apr 2026 12:18:15 +0800 Subject: [PATCH] =?UTF-8?q?fead.=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=94=B5?= =?UTF-8?q?=E6=BA=90=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LanMountainDesktop.PluginSdk/LICENSE | 165 +++++++++++ .../LanMountainDesktop.PluginSdk.csproj | 2 + LanMountainDesktop.Shared.Contracts/LICENSE | 165 +++++++++++ ...LanMountainDesktop.Shared.Contracts.csproj | 2 + LanMountainDesktop/Localization/en-US.json | 20 +- LanMountainDesktop/Localization/zh-CN.json | 20 +- .../Services/PowerManagementService.cs | 253 +++++++++++++++++ .../Views/MainWindow.ComponentSystem.cs | 211 ++++++++++++++ LanMountainDesktop/Views/MainWindow.axaml | 260 ++++++++++++++---- 9 files changed, 1039 insertions(+), 59 deletions(-) create mode 100644 LanMountainDesktop.PluginSdk/LICENSE create mode 100644 LanMountainDesktop.Shared.Contracts/LICENSE create mode 100644 LanMountainDesktop/Services/PowerManagementService.cs diff --git a/LanMountainDesktop.PluginSdk/LICENSE b/LanMountainDesktop.PluginSdk/LICENSE new file mode 100644 index 0000000..044e1bf --- /dev/null +++ b/LanMountainDesktop.PluginSdk/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version of + the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj index 292fb62..4024a34 100644 --- a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj +++ b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj @@ -13,6 +13,8 @@ README.md https://github.com/wwiinnddyy/LanMountainDesktop git + LGPL-3.0-or-later + Copyright (c) LanMountainDesktop Contributors diff --git a/LanMountainDesktop.Shared.Contracts/LICENSE b/LanMountainDesktop.Shared.Contracts/LICENSE new file mode 100644 index 0000000..044e1bf --- /dev/null +++ b/LanMountainDesktop.Shared.Contracts/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version of + the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj index 81914dc..03c0829 100644 --- a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj +++ b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj @@ -13,6 +13,8 @@ https://github.com/wwiinnddyy/LanMountainDesktop git true + LGPL-3.0-or-later + Copyright (c) LanMountainDesktop Contributors diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json index 1918d1b..041d488 100644 --- a/LanMountainDesktop/Localization/en-US.json +++ b/LanMountainDesktop/Localization/en-US.json @@ -1087,5 +1087,23 @@ "zhijiaohub.settings.auto_refresh_desc": "Automatically refresh the image list periodically.", "zhijiaohub.settings.interval": "Refresh Interval (minutes)", "zhijiaohub.settings.about": "About", - "zhijiaohub.settings.about_desc": "ZhiJiaoHub displays interesting images from the educational technology community. Images are fetched from GitHub repositories and cached locally." + "zhijiaohub.settings.about_desc": "ZhiJiaoHub displays interesting images from the educational technology community. Images are fetched from GitHub repositories and cached locally.", + "power.menu": "Power", + "power.title": "Power", + "power.back": "Back", + "power.shutdown": "Shutdown", + "power.restart": "Restart", + "power.logout": "Log Out", + "power.sleep": "Sleep", + "power.lock_screen": "Lock Screen", + "power.shutdown_confirm_title": "Shutdown Confirmation", + "power.shutdown_confirm_message": "Are you sure you want to shut down this computer? Unsaved data may be lost.", + "power.restart_confirm_title": "Restart Confirmation", + "power.restart_confirm_message": "Are you sure you want to restart this computer? Unsaved data may be lost.", + "power.logout_confirm_title": "Log Out Confirmation", + "power.logout_confirm_message": "Are you sure you want to log out?", + "power.sleep_confirm_title": "Sleep Confirmation", + "power.sleep_confirm_message": "Are you sure you want to put the computer to sleep?", + "power.confirm_yes": "Yes", + "power.confirm_cancel": "Cancel" } diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index 813d20b..281a316 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -1081,5 +1081,23 @@ "zhijiaohub.settings.auto_refresh_desc": "定期自动刷新图片列表。", "zhijiaohub.settings.interval": "刷新间隔(分钟)", "zhijiaohub.settings.about": "关于", - "zhijiaohub.settings.about_desc": "智教Hub 展示来自教育技术社区的有趣图片。图片从 GitHub 仓库获取并缓存在本地。" + "zhijiaohub.settings.about_desc": "智教Hub 展示来自教育技术社区的有趣图片。图片从 GitHub 仓库获取并缓存在本地。", + "power.menu": "电源", + "power.title": "电源", + "power.back": "返回", + "power.shutdown": "关机", + "power.restart": "重启", + "power.logout": "注销", + "power.sleep": "睡眠", + "power.lock_screen": "锁定屏幕", + "power.shutdown_confirm_title": "关机确认", + "power.shutdown_confirm_message": "确定要关闭计算机吗?未保存的数据可能会丢失。", + "power.restart_confirm_title": "重启确认", + "power.restart_confirm_message": "确定要重启计算机吗?未保存的数据可能会丢失。", + "power.logout_confirm_title": "注销确认", + "power.logout_confirm_message": "确定要注销当前用户吗?", + "power.sleep_confirm_title": "睡眠确认", + "power.sleep_confirm_message": "确定要让计算机进入睡眠状态吗?", + "power.confirm_yes": "确定", + "power.confirm_cancel": "取消" } diff --git a/LanMountainDesktop/Services/PowerManagementService.cs b/LanMountainDesktop/Services/PowerManagementService.cs new file mode 100644 index 0000000..c6b4187 --- /dev/null +++ b/LanMountainDesktop/Services/PowerManagementService.cs @@ -0,0 +1,253 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace LanMountainDesktop.Services; + +public interface IPowerManagementService +{ + bool IsShutdownSupported { get; } + bool IsRestartSupported { get; } + bool IsLogoutSupported { get; } + bool IsLockSupported { get; } + bool IsSleepSupported { get; } + + Task ShutdownAsync(); + Task RestartAsync(); + Task LogoutAsync(); + Task LockAsync(); + Task SleepAsync(); + + void ShowNativePowerUI(PowerAction action); +} + +public enum PowerAction +{ + Shutdown, + Restart +} + +public static class PowerManagementServiceFactory +{ + private static IPowerManagementService? _instance; + private static readonly object _lock = new(); + + public static IPowerManagementService GetOrCreate() + { + lock (_lock) + { + return _instance ??= CreatePlatformService(); + } + } + + private static IPowerManagementService CreatePlatformService() + { + if (OperatingSystem.IsWindows()) + return new WindowsPowerManagementService(); + if (OperatingSystem.IsLinux()) + return new LinuxPowerManagementService(); + return new NullPowerManagementService(); + } +} + +internal sealed class WindowsPowerManagementService : IPowerManagementService +{ + public bool IsShutdownSupported => true; + public bool IsRestartSupported => true; + public bool IsLogoutSupported => true; + public bool IsLockSupported => true; + public bool IsSleepSupported => true; + + public async Task ShutdownAsync() + { + await Task.Run(() => + { + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/s /t 0", + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Hidden + }); + }); + } + + public async Task RestartAsync() + { + await Task.Run(() => + { + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/r /t 0", + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Hidden + }); + }); + } + + public async Task LogoutAsync() + { + await Task.Run(() => + { + ExitWindowsEx(0, 0); + }); + } + + public async Task LockAsync() + { + await Task.Run(() => + { + LockWorkStation(); + }); + } + + public async Task SleepAsync() + { + await Task.Run(() => + { + SetSuspendState(false, false, false); + }); + } + + public void ShowNativePowerUI(PowerAction action) + { + var slideToShutDownPath = Environment.ExpandEnvironmentVariables(@"%windir%\System32\SlideToShutDown.exe"); + if (System.IO.File.Exists(slideToShutDownPath)) + { + Process.Start(new ProcessStartInfo + { + FileName = slideToShutDownPath, + UseShellExecute = true + }); + return; + } + + switch (action) + { + case PowerAction.Shutdown: + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/s /t 5 /c \"LanMountainDesktop: Shutting down...\"", + UseShellExecute = true + }); + break; + + case PowerAction.Restart: + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/r /t 5 /c \"LanMountainDesktop: Restarting...\"", + UseShellExecute = true + }); + break; + } + } + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool ExitWindowsEx(uint uFlags, uint dwReason); + + [DllImport("user32.dll")] + private static extern void LockWorkStation(); + + [DllImport("powrprof.dll", SetLastError = true)] + private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent); +} + +internal sealed class LinuxPowerManagementService : IPowerManagementService +{ + public bool IsShutdownSupported => true; + public bool IsRestartSupported => true; + public bool IsLogoutSupported => true; + public bool IsLockSupported => true; + public bool IsSleepSupported => true; + + public async Task ShutdownAsync() + { + await RunSystemctlCommand("poweroff -i"); + } + + public async Task RestartAsync() + { + await RunSystemctlCommand("reboot -i"); + } + + public async Task LogoutAsync() + { + await RunLoginctlCommand("terminate-session $XDG_SESSION_ID"); + } + + public async Task LockAsync() + { + await RunLoginctlCommand("lock-session"); + } + + public async Task SleepAsync() + { + await RunSystemctlCommand("suspend -i"); + } + + public void ShowNativePowerUI(PowerAction action) + { + switch (action) + { + case PowerAction.Shutdown: + RunProcess("systemctl", "poweroff -i"); + break; + case PowerAction.Restart: + RunProcess("systemctl", "reboot -i"); + break; + } + } + + private static async Task RunSystemctlCommand(string args) + { + await RunProcess("systemctl", args); + } + + private static async Task RunLoginctlCommand(string args) + { + await RunProcess("loginctl", args); + } + + private static async Task RunProcess(string command, string args) + { + await Task.Run(() => + { + try + { + Process.Start(new ProcessStartInfo + { + FileName = command, + Arguments = args, + UseShellExecute = false, + RedirectStandardError = true, + CreateNoWindow = true + })?.WaitForExit(5000); + } + catch (Exception ex) + { + AppLogger.Error("LinuxPowerManagement", $"Failed to execute {command} {args}: {ex.Message}"); + } + }); + } +} + +internal sealed class NullPowerManagementService : IPowerManagementService +{ + public bool IsShutdownSupported => false; + public bool IsRestartSupported => false; + public bool IsLogoutSupported => false; + public bool IsLockSupported => false; + public bool IsSleepSupported => false; + + public Task ShutdownAsync() => Task.CompletedTask; + public Task RestartAsync() => Task.CompletedTask; + public Task LogoutAsync() => Task.CompletedTask; + public Task LockAsync() => Task.CompletedTask; + public Task SleepAsync() => Task.CompletedTask; + + public void ShowNativePowerUI(PowerAction action) { } +} diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs index 06250d2..6751f94 100644 --- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs +++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs @@ -74,6 +74,10 @@ public partial class MainWindow Color PressedColor, Color DividerColor); + private readonly IPowerManagementService _powerService = PowerManagementServiceFactory.GetOrCreate(); + private bool _isPowerMenuOpen; + private bool _isPowerMenuAnimating; + private void InitializeTaskbarProfileFlyout() { if (TaskbarProfileButton is null || TaskbarProfilePopup is null) @@ -98,6 +102,16 @@ public partial class MainWindow TaskbarProfileDisplayNameTextBlock.Text = profile.DisplayName; TaskbarProfileSettingsActionTextBlock.Text = L("tooltip.open_settings", "Settings"); TaskbarProfileDesktopEditActionTextBlock.Text = L("button.component_library", "Edit Desktop"); + TaskbarProfilePowerActionTextBlock.Text = L("power.menu", "Power"); + TaskbarPowerTitleTextBlock.Text = L("power.title", "Power"); + TaskbarPowerBackTextBlock.Text = L("power.back", "Back"); + PowerShutdownTextBlock.Text = L("power.shutdown", "Shutdown"); + PowerRestartTextBlock.Text = L("power.restart", "Restart"); + PowerLogoutTextBlock.Text = L("power.logout", "Log Out"); + PowerSleepTextBlock.Text = L("power.sleep", "Sleep"); + PowerLockTextBlock.Text = L("power.lock_screen", "Lock Screen"); + + UpdatePowerMenuVisibility(); ApplyTaskbarProfilePopupTheme(_appearanceThemeService.GetCurrent()); ToolTip.SetTip(TaskbarProfileButton, profile.DisplayName); @@ -216,6 +230,7 @@ public partial class MainWindow return; } + ResetPowerMenuState(); RefreshTaskbarProfilePresentation(); TaskbarProfilePopup.IsOpen = true; } @@ -279,6 +294,202 @@ public partial class MainWindow app?.OpenIndependentSettingsModule("MainWindowTaskbar"); } + private void OnPowerMenuEnterClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + EnterPowerMenu(); + } + + private void OnPowerMenuBackClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ExitPowerMenu(); + } + + private void ResetPowerMenuState() + { + _isPowerMenuOpen = false; + _isPowerMenuAnimating = false; + + if (TaskbarProfileMainPanel is not null) + { + TaskbarProfileMainPanel.IsVisible = true; + TaskbarProfileMainPanel.Opacity = 1d; + } + + if (TaskbarProfilePowerPanel is not null) + { + TaskbarProfilePowerPanel.IsVisible = false; + TaskbarProfilePowerPanel.Opacity = 0d; + var transform = TaskbarProfilePowerPanel.RenderTransform as TranslateTransform; + if (transform is not null) transform.X = 340d; + } + } + + private void UpdatePowerMenuVisibility() + { + var supported = _powerService.IsShutdownSupported || + _powerService.IsRestartSupported || + _powerService.IsLogoutSupported || + _powerService.IsSleepSupported || + _powerService.IsLockSupported; + + if (TaskbarProfilePowerActionButton is not null) + { + TaskbarProfilePowerActionButton.IsVisible = supported; + } + } + + private async void EnterPowerMenu() + { + if (_isPowerMenuAnimating || _isPowerMenuOpen || TaskbarProfileMainPanel is null || TaskbarProfilePowerPanel is null) + return; + + _isPowerMenuAnimating = true; + + TaskbarProfilePowerPanel.IsVisible = true; + TaskbarProfilePowerPanel.Opacity = 0d; + var powerTransform = TaskbarProfilePowerPanel.RenderTransform as TranslateTransform; + if (powerTransform is not null) powerTransform.X = 340d; + + await Task.Delay(16); + + TaskbarProfileMainPanel.Opacity = 0d; + TaskbarProfilePowerPanel.Opacity = 1d; + if (powerTransform is not null) powerTransform.X = 0d; + + await Task.Delay(280); + + TaskbarProfileMainPanel.IsVisible = false; + _isPowerMenuOpen = true; + _isPowerMenuAnimating = false; + } + + private async void ExitPowerMenu() + { + if (_isPowerMenuAnimating || !_isPowerMenuOpen || TaskbarProfileMainPanel is null || TaskbarProfilePowerPanel is null) + return; + + _isPowerMenuAnimating = true; + + TaskbarProfileMainPanel.IsVisible = true; + TaskbarProfileMainPanel.Opacity = 0d; + var powerTransform = TaskbarProfilePowerPanel.RenderTransform as TranslateTransform; + if (powerTransform is not null) powerTransform.X = 0d; + + await Task.Delay(16); + + TaskbarProfileMainPanel.Opacity = 1d; + TaskbarProfilePowerPanel.Opacity = 0d; + if (powerTransform is not null) powerTransform.X = 340d; + + await Task.Delay(280); + + TaskbarProfilePowerPanel.IsVisible = false; + _isPowerMenuOpen = false; + _isPowerMenuAnimating = false; + } + + private async void OnPowerShutdownClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ClosePopupIfOpen(); + + if (OperatingSystem.IsWindows()) + { + _powerService.ShowNativePowerUI(PowerAction.Shutdown); + } + else + { + await ShowPowerConfirmDialogAsync(L("power.shutdown_confirm_title", "Shutdown"), + L("power.shutdown_confirm_message", "Are you sure you want to shut down this computer?"), + () => _powerService.ShutdownAsync()); + } + } + + private async void OnPowerRestartClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ClosePopupIfOpen(); + + if (OperatingSystem.IsWindows()) + { + _powerService.ShowNativePowerUI(PowerAction.Restart); + } + else + { + await ShowPowerConfirmDialogAsync(L("power.restart_confirm_title", "Restart"), + L("power.restart_confirm_message", "Are you sure you want to restart this computer?"), + () => _powerService.RestartAsync()); + } + } + + private async void OnPowerLogoutClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ClosePopupIfOpen(); + + await ShowPowerConfirmDialogAsync(L("power.logout_confirm_title", "Log Out"), + L("power.logout_confirm_message", "Are you sure you want to log out?"), + () => _powerService.LogoutAsync()); + } + + private async void OnPowerSleepClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ClosePopupIfOpen(); + + await ShowPowerConfirmDialogAsync(L("power.sleep_confirm_title", "Sleep"), + L("power.sleep_confirm_message", "Are you sure you want to put the computer to sleep?"), + () => _powerService.SleepAsync()); + } + + private async void OnPowerLockClick(object? sender, RoutedEventArgs e) + { + _ = sender; + _ = e; + ClosePopupIfOpen(); + await _powerService.LockAsync(); + } + + private async Task ShowPowerConfirmDialogAsync(string title, string message, Func action) + { + try + { + var dialog = new ContentDialog + { + Title = title, + Content = message, + PrimaryButtonText = L("power.confirm_yes", "Yes"), + SecondaryButtonText = L("power.confirm_cancel", "Cancel") + }; + + var result = await dialog.ShowAsync(this); + if (result == ContentDialogResult.Primary) + { + await action(); + } + } + catch (Exception ex) + { + AppLogger.Error("PowerMenu", $"Dialog error: {ex.Message}"); + } + } + + private void ClosePopupIfOpen() + { + if (TaskbarProfilePopup is not null && TaskbarProfilePopup.IsOpen) + { + TaskbarProfilePopup.IsOpen = false; + } + } + private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e) { _componentLibraryWindowService.Close(this); diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml index 7790b32..52c8c6f 100644 --- a/LanMountainDesktop/Views/MainWindow.axaml +++ b/LanMountainDesktop/Views/MainWindow.axaml @@ -398,69 +398,215 @@ - - - - - - - - + + + + + + + + + + + + + + + - - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + +