在 Avalonia 的窗口 OnClosing 事件里面,将 WindowClosingEventArgs 的 Cancel 属性赋值为 true 用来取消窗口关闭,此时 Linux 麒麟系统的 ukui 组件在进行关机时,将会在调用 会话管理器 时,收到会话管理器取消了本次操作,进而无法进行关机或注销或重启
最简复现代码如下
public partial class MainWindow : Window
{
protected override void OnClosing(WindowClosingEventArgs e)
{
e.Cancel = true;
base.OnClosing(e);
}
... // 忽略其他代码
}
使用以上代码之后,能够在麒麟带 ukui 桌面环境的设备上,复现出从开始菜单点关机或注销或重启失败问题,且此时通知栏收到会话管理器取消了本次操作的提示。此时依然可以从命令行用 shutdown 命令进行关闭
解决方法是判断当前的关闭原因,如果是应用关闭或系统关闭的话,就不阻止窗口关闭即可,代码如下
public partial class MainWindow : Window
{
protected override void OnClosing(WindowClosingEventArgs e)
{
if (e.CloseReason is WindowCloseReason.ApplicationShutdown or WindowCloseReason.OSShutdown)
{
// 应用关闭或操作系统关闭时,允许关闭窗口
// 为什么需要判断 ApplicationShutdown 而不是只判断 OSShutdown 呢?这是因为在 11.3.1 及以前版本,在 Avalonia 底层挖了一个坑,无法区分原因。我将此问题报告给官方,详细请看
// [Incorrect WindowCloseReason Passed During System Shutdown in Avalonia · Issue #19027 · AvaloniaUI/Avalonia](https://github.com/AvaloniaUI/Avalonia/issues/19027 )
}
else
{
e.Cancel = true;
}
base.OnClosing(e);
}
... // 忽略其他代码
}
详细机制原因如下
在 Linux 里,有一个机制叫 xsm (session manager) 会话管理器机制。详细请参阅 https://linux.die.net/man/1/xsm
在 Avalonia 里面,通过 libSM.so.6
库和 xsm 建立联系。通过 SmcOpenConnection
机制建立联系。当系统进行关机或注销或重启时,会进入 SaveYourself 机制回调。在 SaveYourself 里面,通过 libSM 的 SmcInteractRequest 方法传入了 Avalonia 的处理逻辑
在 Avalonia 的处理逻辑里面,先判断了 AVALONIA_X11_USE_SESSION_MANAGEMENT
环境变量是否被设置为 0 的值,如果被设置了,则直接退出,不触发业务上的逻辑。这就意味着,如果想要调查是否 Avalonia 应用导致 Linux 麒麟系统无法关机或注销或重启时,可以通过 export AVALONIA_X11_USE_SESSION_MANAGEMENT=0
设置环境变量再启动应用来进行测试
再接着触发 ShutdownRequestedEventArgs 参数的 ShutdownRequested 事件。这将在 ClassicDesktopStyleApplicationLifetime 里面进行处理。处理方法是遍历当前所有窗口,尝试将其关闭,如果依然还有存活的窗口,则设置取消应用退出。核心代码如下
private bool DoShutdown(
ShutdownRequestedEventArgs e,
bool isProgrammatic,
bool force = false,
int exitCode = 0)
{
if (!force)
{
ShutdownRequested?.Invoke(this, e);
if (e.Cancel)
return false;
if (_isShuttingDown)
throw new InvalidOperationException("Application is already shutting down.");
}
_exitCode = exitCode;
_isShuttingDown = true;
var shutdownCancelled = false;
try
{
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in new List<Window>(_windows))
{
if (w.Owner is null)
{
var ignoreCancel = force || (ShutdownMode == ShutdownMode.OnMainWindowClose && w != MainWindow);
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic, ignoreCancel);
}
}
if (!force && Windows.Count > 0)
{
e.Cancel = true; // 核心阻止关闭的代码
shutdownCancelled = true;
return false;
}
var args = new ControlledApplicationLifetimeExitEventArgs(exitCode);
Exit?.Invoke(this, args);
_exitCode = args.ApplicationExitCode;
}
finally
{
_isShuttingDown = false;
if (!shutdownCancelled)
{
_cts?.Cancel();
_cts = null;
Dispatcher.UIThread.InvokeShutdown();
}
}
return true;
}
当 ShutdownRequestedEventArgs 的 Cancel 被设置为 true 时,将传递到 libSM 的 SmcInteractDone 方法,从而通知会话管理器取消了本次操作。代码如下
var e = new ShutdownRequestedEventArgs();
if (_platform.Options.EnableSessionManagement)
{
ShutdownRequested?.Invoke(this, e);
}
SMLib.SmcInteractDone(smcConn, e.Cancel);
if (e.Cancel)
{
return;
}
_saveYourselfPhase = false;
SMLib.SmcSaveYourselfDone(smcConn, true);
如 https://www.x.org/releases/X11R7.7/doc/libSM/SMlib.html 文档所示,调用的 SmcInteractDone 方法的签名如下
void SmcInteractDone(SmcConn smc_conn, Bool cancel_shutdown);
After interacting with the user (in response to an “Interact” message), you should call SmcInteractDone
smc_conn:The session management connection object.
cancel_shutdown:If True, indicates that the user requests that the entire shutdown be cancelled.
cancel_shutdown:如果是 True 值,表示用户请求取消整个关闭操作
这就意味着只要有窗口没有关闭,则会阻止系统关闭
在麒麟 ukui 底层里面,就依赖了 xsm 机制。当收到了有至少一个应用取消关闭时,将会取消关机等的令牌,从而取消关机等命令
由于 ukui 和 Windows 系统的机制不同,不会再弹出提示窗口说有哪些应用没有退出,是否强制关机。因此会导致用户产生困扰,用户将无法从开始菜单进行关机。我认为更好的解决方法是让 ukui 仿照 Windows 系统的做法,弹出提示窗口,如果用户没有主动取消关机,则等几秒强制关机
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/Avalonia-%E5%8F%96%E6%B6%88%E7%AA%97%E5%8F%A3%E5%85%B3%E9%97%AD%E5%AF%BC%E8%87%B4-Linux-%E9%BA%92%E9%BA%9F%E7%B3%BB%E7%BB%9F%E6%97%A0%E6%B3%95%E5%85%B3%E6%9C%BA%E6%B3%A8%E9%94%80%E9%87%8D%E5%90%AF.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用
知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:
https://blog.lindexi.com
),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请
与我联系
。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利