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.mdhttps://github.com/wwiinnddyy/LanMountainDesktopgit
+ 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/LanMountainDesktopgittrue
+ 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 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+