[Windows翻译]使用WinUI构建Windows终端

644 阅读9分钟

原文地址:blogs.windows.com/windowsdeve…

原文作者:blogs.windows.com/windowsdeve…

发布时间:2020年9月8日

WinUI和Windows Terminal的关系可追溯到Windows Terminal的起源。这篇博文将介绍这两种技术如何结合在一起的历史和架构。

历史沿革

WinUI和Windows Terminal的历史可以追溯到2017年12月。这大约是将成为Windows Terminal的应用程序的第一个原型设计开始的时候。我们作为Windows Terminal团队,刚刚开始让conpty进入一个足够好的地方,作为控制台和终端应用之间的翻译层,是有用的。我们知道,我们希望conpty能够支持一个新的终端应用程序,让我们能够在Windows上建立一个新的命令行用户体验。

当我们还在为Terminal做最初的规划时,我们就知道我们希望能够建立一个现代化的应用程序,使用Windows平台所提供的最佳功能。我们希望确保它在视觉上与其他收件箱应用程序保持一致,并遵循 Fluent Design 原则。

我们选择将Windows Terminal作为一个UWP应用程序来构建。这将使我们能够立即访问UWP XAML和整个WinUI 2库,其中有专门为Windows应用程序构建的本地控件、样式和功能。在当时,"UWP应用程序 "被操作系统施加了特殊的限制。这些安全特性确保了微软商店交付的UWP应用无法访问操作系统内的受限位置,或者在磁盘上留下人工制品,以简化卸载过程。这些UWP应用都是在一个 "低完整性应用容器 "内启动的,这基本上意味着它们在操作系统上实际上没有权限。它们甚至不允许改变工作目录。它们被允许启动外部进程,但这些外部进程额外地受到与应用程序相同的限制的约束。

因此,如果我们想在2018年将Terminal构建为一个UWP应用,那么我们将生成的任何shell(如cmd.exe、powerhell.exe或bash)都无法对系统做任何事情。你能想象在使用你的shell时,无法更改目录、读取文件内容或启动任何其他可以与之交互的进程吗?显然,这对我们来说是不可能的。

那一年,我们花了很大一部分时间与Windows组织中的许多团队讨论我们的选择。我们考虑了一些选项,比如为从UWP启动全信任进程提供特殊的限制能力。我们尝试推动允许UWP应用在应用容器外运行。然而,我们所追求的大多数路线最终不是作为一个安全漏洞,就是作为需要整个组织多年努力的东西。(事实上,许多这样的讨论促成了现在被称为 "Project Reunion "的努力,以及将现代UI框架扩展到桌面应用的WinUI 3努力)。

XAML岛

幸运的是,解决方案实际上比我们想象的更接近。就在我们楼下的大厅里是WinUI团队,我们几乎不知道,他们在前一年的大部分时间里一直在构建XAML Islands。XAML Islands可以让传统的Win32风格的应用程序在其窗口内托管UWP XAML内容,而不受传统UWP应用程序的限制。事实证明,这正是我们所需要的。

我们最终构建的是一个由两部分组成的应用程序。

  • 一个薄薄的Win32窗口,主要是负责在屏幕上画一个窗口,并启动XAML托管基础架构。
  • 该应用程序的大部分是作为一个使用WinUI 2的UWP XAML应用程序构建的,这层甚至不知道它是由一个Win32窗口托管的。就它所知,它只是一个普通的C++/WinRT UWP应用程序。

这种模式使得Terminal能够得到两全其美的效果。作为一个Win32应用程序,我们能够使用所有传统的Win32 API,一个终端应用程序可能需要启动进程和查询文件系统。我们也能够使用UWP XAML和WinUI 2的强大功能来帮助快速而漂亮地构建终端的用户界面。

架构

要想更详细地了解Windows Terminal的架构,我们可以参考下图。

在这个图中,最上面的橙色方框是我们的Win32层。该层构建了我们的最终可执行文件,并包含了所有操作Win32窗口的逻辑,以及为我们的XAML内容托管一个XAML岛。

下面的两个蓝色框框都是Windows Terminal应用程序中的组件。这两个组件本质上都只是UWP XAML组件,可以托管在传统的UWP应用程序中。它们只是碰巧被托管在Windows Terminal的Win32应用程序中,但没有什么能阻止它们在纯UWP上下文中被重用。事实上,Terminal repo甚至构建了一个托管TerminalApp的纯UWP应用程序,以证明可以这样做。

TerminalApp项目代表了应用本身的所有逻辑--比如标签、面板、设置、大部分的UI元素;它们都是TerminalApp项目的一部分。作为终端应用的一部分,它可以同时托管多个终端实例,可以在其他标签页中,甚至并排(在面板中)。为了帮助我们将解决方案进行逻辑上的分解,TerminalControl是它自己的项目:TerminalControl项目。有一天,我们希望能够为TerminalControl衍生出一个nuget包,这样其他应用开发者也可以在自己的应用中使用它。

我们知道,我们希望在Windows上构建一个绝对最快的终端。XAML文本元素是快速和方便的,但WinUI团队指出,我们可以通过使用自定义的DirectX文本渲染器来渲染我们的文本缓冲区来实现更快的性能,所以我们选择了这一路线。

幸运的是,我们已经开始为复古控制台开发DirectX渲染器,以取代GDI渲染器。然后,我们就可以把已经有的渲染器挂到XAML的SwapChainPanel类上,这就比较简单了。这让我们不仅可以在GPU上不可思议地快速渲染文本,还可以在新的Terminal中重用很大一部分控制台代码库。这意味着,对Terminal的改进也改善了收件箱控制台。

注意事项

这并不是说构建一个混合应用程序的旅程是完全没有痛苦的。自从我们开始开发Terminal以来,我们遇到了一些主要的障碍,这是我们进入这个过程时没有想到的。

  • 事实证明,要把我们的UI放到标题栏区域是相当困难的。虽然Terminal现在确实在标题栏中设置了图标标签条,但在Win32应用程序的部分,这实际上是一个相当大的工作量。幸运的是,我们得到了社区成员@greg904的帮助。如果没有他,Terminal的标题栏也不会有今天这么精致。

  • 包装一个混合应用程序也有自己的困难。事实证明,应用打包项目很难把我们的依赖关系准确地放到项目中的正确文件夹中,这意味着我们不得不手动编写大量的msbuild目标。这很可能是因为wapproj项目类型并不是真正为这些而设计的。

  • 通过构建我们自己的TerminalControl,而不是使用现有的XAML元素,我们确实需要自己为该控件构建UIA和可访问性支持。这最终是一个不完全琐碎的过程,而且我们花费的开发时间肯定比我们预期的要多。然而,确保所有的开发者都能有效地使用终端是绝对必要的。

标签视图

终端所需要的关键功能之一是标签--用户希望打开多个终端,任务并行运行。不仅如此,标签控件需要看起来和感觉良好,就像浏览器中的标签体验一样。当我们第一次开始研究构建标签页体验时,WinUI 2中并没有标签页控件,尽管Windows社区工具包中有一个最初的实现。虽然这个最初的C#实现帮助我们确定了控件的API方向,但我们需要一个高性能、高抛光的控件,而这种控件只能在WinUI中找到。我们与WinUI团队紧密合作(他们又与微软内部的各个团队紧密合作,如WCT人员和Edge团队),以定义WinUI 2中内置的TabView控件的外观和感觉。

从我们的角度来看,这种合作是值得的,因为TabView的很多功能需求,如重新排序和关闭标签页,都是由WinUI团队构建的--而从WinUI团队的角度来看,这种合作也是值得的,因为Terminal帮助塑造了一个通用TabView的需求和设计,可以在其他应用程序中使用。另外,WinUI的TabView非常灵活--例如,Terminal包含了一个自定义的下拉标签按钮,这个按钮在 "库存 "TabView中没有,但在控件中得到了很好的支持。

设计过程

我们与WinUI团队以及许多设计师合作,定义TabView的外观。我们从基本的草图到高保真的模拟图,这些模拟图反过来又被开发成一个工作控件。下面是我们在定义Terminal的外观时创建的一个模拟图。

经过与多位设计师的合作,并反复修改了许多模型,TabView才有了今天的成就,它是Windows Terminal中的一个主要功能。

展望未来

随着Windows Terminal获得更多的功能,我们能够不断地与WinUI合作,在这些控件可用时添加更多的控件。下一个大项目是设置用户界面,它将使用许多目前可用的WinUI 2控件。这种合作关系非常棒,我们迫不及待地想通过创建新的功能和控件来继续改进Windows Terminal和WinUI。