尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Windows Terminal 多窗口会话管理:`windowingBehavior` 与 `--window` 参数背后的 Emperor/Vassal 进程架构

Windows Terminal 多窗口会话管理:`windowingBehavior` 与 `--window` 参数背后的 Emperor/Vassal 进程架构 Windows Terminal 多窗口会话管理windowingBehavior与--window参数背后的 Emperor/Vassal 进程架构【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal本文基于 Windows Terminal 仓库的规格文档 Windows Terminal Session Management 展开系统讲解“单实例模式Single Instance Mode”与“在已有窗口中执行wt命令”两大能力的设计与实现包括windowingBehavior设置的各取值语义、--window,-w参数如何寻址窗口、当前工作目录CWD如何跨进程传递以及最近使用窗口MRU栈的分布式跟踪方案。读完本文你将理解该特性从规格到源码WindowEmperor.cpp、GlobalAppSettings.idl的完整落地链路并能在实际使用中正确配置与组合这些参数。背景Process Model 2.0 与 Monarch/Peasant 架构本特性是 Process Model 2.0 规格文档 的一个补充。Process Model 2.0 解决的是 Windows Terminal 整体进程架构的改造——把“窗口进程”与“内容进程”分离并引入 Monarch君主/Peasant臣民进程模型。而本文档聚焦其中一子集对应 issue #5000、#4472、#2227如何管理多个 Windows Terminal 窗口具体包括在当前窗口中运行wt命令issue #4472单实例模式Single Instance Modeissue #2227。Monarch/Peasant 架构的核心约定如下每一个 Windows Terminal 窗口都是一个 “Peasant” 进程其中恰好有一个窗口进程同时是 “Monarch” 进程。Monarch 从现有窗口中随机挑选产生且同一时刻有且只有一个 MonarchPeasant 在特定状态变化时例如窗口被激活向 Monarch 通信Monarch 可以向任意 Peasant 下发命令。当前仓库中的对应实现WindowEmperor与AppHost在仓库的实际实现中这一 Monarch/Peasant 模型落地为WindowEmperor帝王与AppHost每个窗口的主机即臣民两个核心类型。WindowEmperor.h 的类注释明确描述了其职责WindowEmperor 是负责管理“拥有全部窗口的单一 Terminal 进程”的类。它负责处理命令行参数并首先尝试寻找另一个可通信的 terminal 进程若找到则把命令行“交接”hand off给已有进程若判定应当创建窗口则新设窗口线程并在主线程上维护消息循环以处理全局状态如全局热键、托盘通知图标。启动入口在 main.cpp每个wt.exe进程启动后都会先make_shared::WindowEmperor()并调用emperor-HandleCommandlineArgs(nCmdShow)由 Emperor 统一决定“我是否要把这条命令行转交给别的窗口进程”。窗口查找则通过GetWindowById/GetWindowByName完成见 WindowEmperor.h#L44-L45这与规格中“每个窗口进程拥有由 Monarch 分配的唯一正整数 ID也可拥有用户指定的字符串名称”的设定一一对应。场景一新标签页打开到“最近使用”的窗口Glomming浏览器有一个常见行为在任意位置点击一个 URL该页面会以新标签页形式打开在“最近使用的窗口”中。这种新标签“吸附”glom到既有窗口的能力规格中将其引入 Terminal此前 Terminal 不支持该特性——每次执行wt都会创建一个新窗口在 Emperor/Vassal 架构下每个窗口被激活时会调用 Emperor 的一个方法报告“我是窗口 N我刚被聚焦”Emperor 据此维护一个最近使用窗口MRU的内部栈每当一个新的wt.exe进程启动它会首先询问 Emperor这条命令行应当放到已有窗口中执行还是自己开窗口。若 glomming 启用Emperor 会把命令行分派给目标窗口处理用户观感上就像标签页“自然”地开在了最近使用的窗口里。windowingBehavior设置项规格提出用windowingBehavior属性控制 glomming 行为初始设计中列出三个取值useExisting无论虚拟桌面总是 glom 到最近使用的窗口useExistingOnSameDesktop仅当当前虚拟桌面上已有窗口时才 glom否则新建窗口规格拟将其作为默认值useNew永不 glom总是新建窗口——这也是 Terminal 当时的实际行为。规格还计划把默认行为从useNew改为useExistingOnSameDesktop以与带标签页的其他应用保持一致详见下文“兼容性”讨论。源码中的枚举取值在当前仓库中该设置由WindowingMode枚举建模定义于 GlobalAppSettings.idl#L33-L38enum WindowingMode { UseNew, UseAnyExisting, UseExisting, }其默认值在 MTSMSettings.h 中声明windowingBehavior缺省为Model::WindowingMode::UseNew即“每次wt都开新窗口”——与规格中“技术上这是 Terminal 当前行为”的描述一致也说明规格中计划切换默认值的动作在本仓库中尚未落地仍可通过设置显式改为其他取值。Emperor 的分发逻辑Emperor 收到命令行后的完整判定流程见 WindowEmperor.cpp#L805-L871。其逻辑可概括为const auto parsedTarget args.TargetWindow(); WindowingMode windowingBehavior WindowingMode::UseNew; uint64_t windowId 0; winrt::hstring windowName; if (parsedTarget.empty()) { // 未指定目标回落到全局 windowingBehavior 设置 windowingBehavior _app.Logic().Settings().GlobalSettings().WindowingBehavior(); } else if (const auto opt til::parse_signedint64_t(parsedTarget, 10)) { // 负数 ID 视为 UseNew0 视为 UseExisting if (*opt 0) { windowId gsl::narrow_castuint64_t(*opt); } else if (*opt 0) { windowingBehavior WindowingMode::UseExisting; } } else if (parsedTarget Llast) { // 保留名 lastglom 到最近使用的窗口 windowingBehavior WindowingMode::UseExisting; } else if (parsedTarget ! Lnew) // 保留名 new总是新窗口 { windowName parsedTarget; // 其余字符串一律当作窗口名 } // 之后按 windowId / windowName 精确查找 // 否则按 UseAnyExisting 取 _mostRecentWindow() // 按 UseExisting 走 _dispatchCommandlineCurrentDesktop()当前虚拟桌面 MRU。这段实现印证了规格的几条设计决策省略目标时遵循windowingBehavior设置-w 0被解释为UseExisting在当前虚拟桌面的最近窗口执行而非“最近窗口的别名”——这正对应规格中“wt -w 0在 WT 外部执行时应当 glom 到最近的 WT 窗口”的结论规格脚注 [2] 中提议的last/current特殊值实现中落地为保留名last负数或new强制新建窗口与windowingBehavior无关last作为保留名与0同义用于显式表达“最近使用的窗口”。当前工作目录CWD的跨进程处理规格给出了一个典型场景用户在explorer.exe地址栏中执行wt -d .Emperor 判定该标签页应当创建在既有窗口中。为叙述方便把既有窗口记作 WT[1]新启动的wt.exe进程记作 WT[2]。此时希望新标签页在WT[2] 的当前工作目录中启动而不是 WT[1] 的目录。因此当 WT[1] 准备执行“原本传给 WT[2] 的命令”时它需要先暂存stash自己当前的 CWD切换到 WT[2] 的 CWD执行 WT[2] 传来的命令恢复到自己原来的 CWD。据此规格要求在“Peasant 向 Monarch 传递启动命令行”的接口中同时携带当前工作目录。当前实现中这一要求体现在命令行分发接口CommandlineArgs上_dispatchCommandlineCommonWindowEmperor.cpp#L874-L882会设置c.CurrentDirectory(...)与c.CurrentEnvironment(...)随后统一经_dispatchCommandline交给目标窗口的DispatchCommandline保证目标窗口拿到的是“发起方”的 CWD 与环境。场景二--window,-w参数与窗口寻址另一个高频需求是让wt.exe命令行在当前窗口执行而不是永远新建窗口。规格将其推广为“在任意指定的 WT 窗口中执行wt命令行”。窗口身份由两部分构成Monarch 分配的唯一正整数 ID窗口总是有 ID以及用户可指定的字符串名称不一定有。于是有如下用法wt.exe --window N new-tab ; split-pane或简写wt -w N new-tab ; split-pane。规格为该参数定义了如下语义当前实现与之一致见上文源码片段window-id为0在当前窗口执行在 WT 外部运行时glom 到最近的 WT 窗口window-id为负数或保留名new在新Terminal 窗口执行window-id为某个已有窗口的 ID 或名称在该窗口执行window-id不是任何已有窗口的 ID 或名称创建一个新窗口并把该 ID/名称赋给它随后在新窗口中运行子命令window-id省略按windowingBehavior取值决定。一个关键设计约束是每次wt.exe启动都必须首先把命令行交给 Emperor 进程处理——这是 glomming 场景成立的前提。Emperor 解析命令行、确定目标窗口再调用该窗口Peasant的ExecuteCommandline实现中即AppHost::DispatchCommandline执行命令。--window只作用于wt.exe本身不作用于子命令--window是wt.exe自身的参数而非new-tab、split-pane等子命令的参数。因此一条wt命令行中的所有子命令都由同一个会话处理。例如想“在窗口 2 打开新标签、同时在窗口 3 分割窗格”不能这样写wt -w 2 new-tab ; -w 3 split-pane而必须拆成两条独立的wt.exe调用wt -w 2 new-tab wt -w 3 split-pane规格对此给出的理由很具体若--window属于每个子命令则每个子命令的解析器都必须理解该参数且命令行的任意一部分都可能触发跨进程调用。源进程需要某种机制来“等待目标进程回报子命令完成”后才能继续分派命令行的下一段——这在进程间协调上得不偿失。因此采用“整批命令一起分派”这一更简单可靠的方案。为什么wt -w 0不能简单依赖 MRU规格特别辨析了一个看似简单、实则错误的“当前窗口”实现如果 Peasant 总是在窗口聚焦时上报事件、Emperor 维护 MRU 顺序那么wt -w 0似乎总能返回“用户正在输入的那个窗口”。但考虑sleep 10 ; wt -w 0 commands在sleep的 10 秒内用户可能切换到另一个 WT 窗口导致 MRU 窗口与正在执行该命令的窗口不一致。为此规格考虑过通过WT_SESSION环境变量定位会话由 Emperor 反查该 GUID 对应的 Peasant并评估了引入WT_WINDOW_ID环境变量的方案它可以省去按会话 ID 反查的步骤但存在两个顾虑——一是把窗口 ID 暴露成环境变量后用户会倾向于绕过wt -w 0这类“替用户把活干完”的别名直接用它二是当标签页被“撕出”tear out成新窗口时子进程中的WT_WINDOW_ID不会更新。两个方案还共同面临“用户把变量改成乱值”的风险而使用 GUID 形式的会话 ID 比 int 形式的窗口 ID 更不容易被猜中。窗口命名Naming Windows仅靠自动生成的不可见数字来识别窗口并不友好。为此规格允许用户为窗口指定字符串名称名称与 ID 一样可用于寻址窗口名称可在原始命令行中直接给出例如wt -w foo nt会把新窗口命名为foo也可通过新的动作NameWindow设置规格脚注 [3] 指出这与已有renameTab(name)/openTabRenamer()两个“重命名标签页”动作对偶未来可能还需要nameWindow(name)/openWindowNamer()。作为子命令使用例如wt -w 4 name-window bar会把 4 号窗口命名为bar禁止整数名称正负均可防止把窗口改名为2后wt -w 2产生歧义名称必须唯一尝试重命名为已存在的名称时忽略该次改名并可弹出一个低干扰的提示toast文案类似 “Unable to rename window. Another window with that name already exists”保留名Terminal 保留名称new并保留所有以_开头的名称——这样未来可以添加新的关键词而不引入破坏性变更。当前实现中窗口名与 ID 均通过 Emperor 的GetWindowByName/GetWindowById精确查找且WindowEmperor内部维护WindowListEntry{ uint64_t Id; std::wstring Name; }列表WindowEmperor.h#L37-L41供WM_GET_WINDOW_LIST消息同步返回——这正对应规格“未来考虑”一节中“必须提供一个列出窗口及其 ID、名称的命令行工具”的诉求。UI/UX 设计windowingBehavior各取值的完整行为规格对windowingBehavior各取值的行为给出了明确矩阵取值行为描述useExisting、useExistingOnSameDesktop浏览器式 glomming新实例默认打开在当前最近窗口newWindow动作开新窗口标签可撕出为新窗口wt -w -1开新窗口useNew不自动 glom——这是 Terminal 当前行为新实例默认开新窗口newWindow开新窗口标签可撕出为新窗口wt -w -1开新窗口从源码看UseAnyExisting对应规格中的useExisting在分发时调用_mostRecentWindow()直接取全局最近窗口UseExisting对应useExistingOnSameDesktop则走_dispatchCommandlineCurrentDesktop()WindowEmperor.cpp#L885-L907先经VirtualDesktopUtils::GetCurrentVirtualDesktopId取得当前虚拟桌面 ID再遍历所有窗口比较每个窗口的虚拟桌面与GetLastActivatedTime()选出当前桌面上最近激活的那个——“仅在当前桌面有窗口才 glom否则新建”的语义由此实现。关注点分析安全、可靠性与兼容性安全COM 按提级级别隔离在实例化 Emperor规格中的 Monarch对象时COM 只会返回同一提级级别elevation level的服务进程对象。因此不必担心未提级的 Peasant 连到提级的 Emperor或反之。这也简化了规格后续确认的“混合提级mixed-elevation”决策自 2020 年 12 月起不再追求混合提级场景提级与未提级的wt实例始终彼此独立各自维护自己的窗口 ID 列表若用户同时运行提级与未提级窗口则存在两个 Emperor——一个提级、一个未提级。可靠性跨进程对象调用必须容错与“由另一个进程托管的对象”打交道时必须格外小心任何此类操作都必须包在 try/catch 中因为对端进程随时可能被杀。Emperor 与 Peasant 两侧代码都必须对该场景冗余处理在对端被杀时给出适当错误提示并优雅恢复或退出。同时在这类情况下一切日志应尽量详尽便于事后定位错误发生在哪个进程。兼容性默认行为变更的回滚路径把默认行为改为“自动 glom 到同桌面最近窗口”是一项破坏性的 UX 变更但可以通过windowingBehavior: useNew设置回退。规格承认这是对 Terminal 默认体验的一次较大改动计划通过 Preview 版分阶段投放、发布说明中特别提示、并收集用户反馈后再决定是否全面铺开还可能选择在“标签撕出tab tear out”功能可用之后再切换默认值以便对 glomming 不满的用户至少可以撕出标签自救。潜在问题与边界情形--window与其他命令行选项的组合有些wt.exe命令行选项在“转交给另一个会话执行”时没有意义包括但不限于--initialSize r,c--initialPosition x,y--fullscreen、--maximized等当命令行被转交给已有实例处理时这些参数会被忽略——它们只适用于窗口的初始创建对一个已有大小的窗口说--initialSize 32, 120并不成立。另外新建窗口启动时当前假设第一条命令总是new-tab而在向已有窗口传递命令行时无需再作此假设因为那里已经存在既有标签页。提级组合的命令行陷阱一个需要特别处理的边界用户从未提级的explorer.exe中执行wt -w 0 new-tab -d . --elevated希望“在当前提级窗口里开新标签”。常规流程是先确定命令行目标窗口再分派。这里-w 0会把命令行交给当前的未提级窗口随后该窗口尝试打开提级标签页、失败并创建新的wt.exe进程——而第二个wt.exe进程丢失了-w 0的上下文不会告知提级 Emperor“这条命令行应在活动会话中执行”。因此创建提级实例时必须保留传入的--window参数。Emperor 侧 MRU 栈的丢失问题Emperor 负责跟踪 MRU 窗口栈但当 Emperor 进程被关闭时这份状态会丢失新选出的 Emperor 无法向旧 Emperor 询问旧 MRU 顺序。规格为此列出三个层次的方案可接受的降级 UX随机化顺序新 Emperor 自己成为 MRU 窗口若用户抱怨可让每个 Peasant 在激活时不仅通知 Emperor、还通知所有其他 Peasant使所有 Peasant 同步跟踪 MRU 栈从而随时有资格成为 Emperor更简单的分布式方案Emperor 根本不维护 MRU 栈而是每个 Peasant 内部记录自己的LastActivated时间戳Emperor 需要时遍历所有 Peasant找出时间戳最新者进一步优化Emperor 也缓存一份栈以便快速查询LastActivated时间戳仅用于新 Emperor 当选时重建栈。从源码结构看当前实现采用了方案 2Emperor 不持有自己的 MRU 栈而是在需要时遍历_windows并读取每个窗口的GetLastActivatedTime()同桌面场景见 WindowEmperor.cpp#L890-L902状态分布式保存在各 Peasant 中Emperor 更换不会造成 MRU 状态丢失。实施计划规格最后给出了可执行任务清单原文为待办列表支持wt.exe进程充当 Monarch 与 Peasant并在它们之间通信该状态纯架构更新不引入用户可见特性以布尔形式支持windowingBehavior设置新 WT 窗口条件性地 glom 到已有窗口通过引入useExisting、useExistingOnSameDesktop、useNew枚举值支持按虚拟桌面的windowingBehavior通过--window,-w window-id命令行参数支持wt.exe把“意在另一窗口的命令行”经 Monarch 传给目标窗口支持通过-w参数按 ID 与名称寻址、命名窗口增加NameWindow动作/子命令允许用户设置窗口名称增加一个让所有窗口短暂显示“当前窗口 ID 与名称”覆盖层的动作类似 Windows “显示”设置中的“标识”identify功能。对照仓库现状上述多数条目已有对应落地Emperor/Vassal 通信WindowEmperor 与 AppHost、三值枚举的windowingBehaviorGlobalAppSettings.idl、-w寻址与窗口命名TargetWindow解析与GetWindowById/GetWindowByName以及“标识”功能对应的WM_IDENTIFY_ALL_WINDOWS消息WindowEmperor.h#L25-L32。未来考虑规格还留了几个开放方向管道输入到既有窗口的窗格man ping wt -w 0 split-pane cat这类用法需要 WT 把 stdin/out 句柄传给目标进程规格认为其归属更靠近 issue #492默认终端或独立规格更朴素的替代写法wt -w 0 split-pane -- man ping cat无需额外工作即可实现同等效果真正的单实例模式只允许存在一个 WT 窗口。早期版本曾提议glomToLastWindow的always取值后更名为windowingBehavior它会禁用标签撕出、禁用newWindow动作并阻止wt -w new开新窗口但评审中结论是“该设置改变撕出行为”并不自洽真正的单实例模式被留给 Quake Modeissue #653见 Quake Mode 规格处理自动生成窗口名参考 gfycat.com/what3words.com 的随机词组或 docker 的“形容词名字”风格自动生成名称——因本地化代价大而搁置窗口列表工具需要一个列出窗口 ID、名称、PID 与标题的命令行工具。规格指出wt.exe是 GUI 应用、无法向控制台打印因此需要额外发布一个命令行辅助 exe其设计留待未来。参考资源本文主体文档Windows Terminal Session Management作者 Mike Griese关联 issue #4472是 Process Model 2.0 Spec 的补充核心实现WindowEmperor.h、WindowEmperor.cpp、main.cpp、AppHost.h设置模型GlobalAppSettings.idl、MTSMSettings.hwindowingBehavior的默认值与序列化键相关规格Tab tearoff#1256、Quake Mode#653、Process Model 2.0#5000。【免费下载链接】terminalThe new Windows Terminal and the original Windows console host, all in the same place!项目地址: https://gitcode.com/GitHub_Trending/term/terminal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表