本文介绍如何在按下、按住和释放 Microsoft Copilot 硬件按键或 Windows 键 + C 时进行注册,以激活应用并接收通知。 此功能使应用程序能够根据检测到的键状态变化执行不同的动作。 例如,当键处于单按下状态时,应用可能会执行正常激活,但在按下并按住该键时拍摄屏幕截图。 或者,应用可以开始录制音频,并显示一个状态指示器,指示在按下并按住键时正在录制音频,然后在释放键时停止录制音频。 键必须按下并按住至少 300 毫秒才能进入保留状态。
此功能扩展了基本Microsoft Copilot 硬件密钥提供程序的功能,只需在按下硬件密钥时注册即可启动。 有关详细信息,请参阅 Microsoft Copilot 硬件密钥提供程序。
本文的其余部分将详细讲解如何创建一个简单的 C# WinUI 3 应用,以响应通过单次按下或按下并按住再释放 Microsoft Copilot 按钮启动的激活。
创建新项目
在 Visual Studio 中,创建新项目。 对于此示例,在“ 创建新项目 ”对话框中,将语言筛选器设置为 C# 并将项目类型设置为 WinUI 3,然后选择“空白应用,打包(桌面版 WinUI 3)。
添加属性以跟踪Microsoft Copilot 键按下状态
在此示例中,我们将创建一个名为 State 的属性,用于在 UI 中显示当前激活状态。 在 MainWindow.xaml.cs
MainWindow 的定义中,添加以下代码以创建可在 XAML 文件中绑定到的字符串属性。
// MainWindow.xaml.cs
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "State")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void SetState(string state)
{
State = state;
}
private string _state;
public string State
{
get => _state;
set
{
if (_state != value)
{
_state = value;
OnPropertyChanged();
}
}
}
将 TextBox 控件添加到 UI 以显示应用的当前激活状态。 将 MainPage.xaml 中的默认 StackPanel 元素替换为以下代码。
<!-- MainWindow.xaml -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Name="KeyStateText" Text="{x:Bind State, Mode=OneWay}" />
</StackPanel>
最后,更新 MainWindow 构造函数以采用一个参数,该参数将在创建窗口时设置 State 属性。
// MainWindow.xaml.cs
public MainWindow(string state)
{
this.InitializeComponent();
_state = state;
}
注册 URI 激活
系统使用 URI 激活启动 Microsoft Copilot 硬件密钥提供程序。 通过将 uap:Protocol 元素添加到应用清单来注册启动协议。 有关如何注册为 URI 方案的默认处理程序的详细信息,请参阅 “处理 URI 激活”。
以下示例演示了注册 URI 方案“myapp-copilothotkey”的 uap:Extension。
<!-- Package.appxmanifest -->
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
...
<Extensions>
...
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="myapp-copilothotkey"> <!-- app-defined protocol name -->
<uap:DisplayName>SDK Sample URI Scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
...
Microsoft Copilot 硬件密钥应用扩展
必须打包应用才能注册为 Microsoft Copilot 硬件密钥提供程序。 有关应用打包的信息,请参阅 Windows 应用中的包标识概述。 Microsoft Copilot 硬件密钥提供程序在 uap3:AppExtension 中声明其注册信息。 扩展的 Name 属性必须设置为“com.microsoft.windows.copilotkeyprovider”。 若要支持密钥状态更改,应用必须向其 uap3:AppExtension 声明提供一些其他条目。
在 uap3:AppExtension 元素内部,添加包含子元素 PressAndHoldStart 和 PressAndHoldStop 的 uap3:Properties 元素。 这些元素的内容应为上一步骤中清单中注册的协议方案的 URI。 查询字符串参数指定是启动 URI 是因为用户按下并按住了热键,还是因为用户释放了热键。 应用在应用激活期间使用这些查询字符串值来确定要采取的正确作。
<!-- Package.appxmanifest -->
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.copilotkeyprovider"
Id="MyAppId"
DisplayName="App display name"
Description="App description"
PublicFolder="Public">
<uap3:Properties>
<PressAndHoldStart>myapp-copilothotkey:?state=Down</PressAndHoldStart>
<PressAndHoldStop>myapp-copilothotkey:?state=Up</PressAndHoldStop>
</uap3:Properties>
</ uap3:AppExtension>
</uap3:Extension>
...
处理 URI 激活
若要检测应用是否已通过 URI 激活激活,请调用 AppInstance.GetActivatedEventArgs 并检查 AppActivationArguments.Kind 属性的值是否为 Protocol。 如果应用是通过协议激活启动的,请检查 URI 方案是否与应用清单中指定的协议名称相同。 如果所有这些测试都通过,则你知道你的应用已由用户按 Copilot 硬件密钥激活。 此时,可以分析 URI 查询字符串并获取 状态 参数,该参数将在应用清单的 PressAndHoldStart 和 PressAndHoldStop 元素中指定值。
// App.xaml.cs
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();
string state = "";
if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.Protocol))
{
var protocolArgs = (Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs)eventargs.Data;
WwwFormUrlDecoder decoderEntries = new WwwFormUrlDecoder(protocolArgs.Uri.Query);
state = Uri.UnescapeDataString(decoderEntries.GetFirstValueByName("state"));
}
state = (state == "") ? "Launched" : state;
m_window = new MainWindow(state);
m_window.Activate();
}
重要
请注意,默认情况下,WinUI 3 应用是多实例的,这意味着每当按下或释放 Microsoft Copilot 热键时,都会启动一个新实例。 这可能是许多提供程序的所需行为,但如果愿意,可以更新应用以使用单个实例。 有关详细信息,请参阅 使用 C# 创建单实例 WinUI 应用。
处理快速路径调用
除了 URI 激活之外,应用还可以注册以支持快速路径调用,其中正在运行的应用通过窗口消息接收有关 Copilot 硬件应用的消息。 对于当前运行的应用,此调用方法比 URI 激活更快,并且会提供更好的用户体验,因为应用可以在按下并按住键后更快地开始侦听语音。
更新应用清单文件以支持快速路径调用
若要添加对快速路径调用的支持,请更新“com.microsoft.windows.copilotkeyprovider”扩展,将 MessageWParam 属性添加到 SingleTap、PressAndHoldStart 和 PressAndHoldStop 元素。 每个 MessageWParam 值必须是唯一的 32 位整数,但应用会选择所使用的值。 此示例分别使用值 0、1 和 2。 这些值将在示例中稍后在 Windows 消息的 wParam 参数中传递时使用,以确定 Windows Copilot 硬件键的当前按下状态。
<!-- Package.appxmanifest -->
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.copilotkeyprovider"
Id="MyAppId"
DisplayName="App display name"
Description="App description"
PublicFolder="Public">
<uap3:Properties>
<SingleTap MessageWParam="0"/>
<PressAndHoldStart MessageWParam="1">myapp-copilothotkey://?state=Down</PressAndHoldStart>
<PressAndHoldStop MessageWParam="2">myapp-copilothotkey://?state=Up</PressAndHoldStop>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
访问 win32 API 进行窗口注册
通过在与应用窗口之一关联的 IPropertyStore 上设置属性来启用快速路径激活。 为此,需要访问一些原生 Win32 API。 本演练将使用 CsWin32 库,该库可自动生成 C# 绑定,并可用作 NuGet 包。
在 Visual Studio 的解决方案资源管理器中,右键单击项目文件,然后选择“ 管理 NuGet 包...”。在 NuGet 包管理器的“ 浏览 ”选项卡上,搜索“cswin32”,然后选择“Microsoft.Windows.CsWin32”包,然后单击“安装”。
安装包后,在项目目录中添加新的文本文件并将其命名为“NativeMethods.txt”。 CsWin32 工具将在此文件中查找它将为其生成绑定的 Win32 API 列表。 将以下 API 名称放入“NativeMethods.txt”。
SUBCLASSPROC
SHGetPropertyStoreForWindow
IPropertyStore
SetWindowSubclass
DefSubclassProc
注册 Microsoft Copilot FastPath 调用的窗口
接下来,我们将更新 MainWindow 类以注册窗口,以接收来自 Copilot 硬件密钥的快速路径调用。
首先,调用 GetWindowHandle 以获取 MainWindow 的 HWND 句柄。 调用 SHGetPropertyStoreForWindow 以获取窗口的 IPropertyStore 。 创建一个新的 PROPERTYKEY,并将 fmtid 成员设置为用于 Windows Copilot FastPath 激活的 GUID。 将属性的值设置为应用定义的值,当硬件密钥状态发生更改时,该值将从系统传回应用。 应用定义值是窗口消息 ID,该 ID 必须位于WM_APP范围内。 有关详细信息,请参阅 WM_APP。 调用 SetValue ,然后调用 Commit 将更改提交到属性存储区。
最后,创建一个 SUBCLASSPROC 回调,该回调将在硬件密钥状态更改时调用。 WindowSubClass 是下一步中显示的回调实现。 调用 SetWindowSubclass 注册回调。
private HWND hWndMain;
private Windows.Win32.UI.Shell.SUBCLASSPROC SubClassDelegate;
public const int WM_COPILOT = 0x8000 + 0x0001;
public MainWindow(string state)
{
this.InitializeComponent();
hWndMain = (HWND)WinRT.Interop.WindowNative.GetWindowHandle(this);
Microsoft.UI.Windowing.AppWindow appWindow = AppWindow;
var propertyStoreGUID = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
var hr = PInvoke.SHGetPropertyStoreForWindow((HWND)this.AppWindow.Id.Value, in propertyStoreGUID, out var propertyStore);
var key = new PROPERTYKEY();
var copilotFastpathGUID = new Guid("38652BCA-4329-4E74-86F9-39CF29345EEA");
key.fmtid = copilotFastpathGUID;
key.pid = 0x00000002;
var value = new PROPVARIANT();
value.Anonymous.Anonymous.vt = VARENUM.VT_UINT;
value.Anonymous.decVal = WM_COPILOT;
((IPropertyStore)propertyStore).SetValue(in key, in value);
((IPropertyStore)propertyStore).Commit();
SubClassDelegate = new Windows.Win32.UI.Shell.SUBCLASSPROC(WindowSubClass);
bool bRet = PInvoke.SetWindowSubclass((HWND)appWindow.Id.Value, SubClassDelegate, 0, 0);
_state = state;
}
实现窗口子类回调
此示例的最后一步是实现窗口子类回调,每当应用运行并且 Windows Copilot 硬件密钥的状态发生变化时,都会调用该回调。 在此示例中,我们检查窗口消息是否是我们在上一步中设置属性存储值时指定的 WM_COPILOT 值。 然后,我们检查 wParam 参数的值,以查看在应用清单中使用 MessageWParam 属性指定的值中的哪一个值已传入。 调用 SetState 来更新 UI,使其反映当前状态。
private LRESULT WindowSubClass(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
{
switch (uMsg)
{
case WM_COPILOT:
{
switch (wParam.Value)
{
case 0:
SetState("SingleTap");
break;
case 1:
SetState("PressAndHold START");
break;
case 2:
SetState("PressAndHold END");
break;
}
}
break;
}
return PInvoke.DefSubclassProc((HWND)hWnd, uMsg, wParam, lParam);
}
对 Windows Copilot 硬件密钥提供程序进行签名
必须对提供程序应用进行签名才能作为 Microsoft Copilot 硬件密钥的目标来启用。 有关打包和签名应用的信息,请参阅 在 Visual Studio 中打包桌面或 UWP 应用。