添加注册uri的功能

This commit is contained in:
PANDA-JSR
2026-02-04 11:09:49 +08:00
parent a4868215f2
commit 8f6383cbe6
11 changed files with 363 additions and 3 deletions
+31
View File
@@ -929,6 +929,22 @@ namespace Ink_Canvas
LogHelper.WriteLogToFile("通过IPC发送展开浮动栏命令失败", LogHelper.LogType.Warning);
}
}
// 检查是否有URI参数
else if (e.Args.Any(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase)))
{
string uriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
LogHelper.WriteLogToFile($"检测到已运行实例且有URI参数: {uriArg}", LogHelper.LogType.Event);
// 尝试通过IPC发送URI命令给已运行实例
if (FileAssociationManager.TrySendUriCommandToExistingInstance(uriArg))
{
LogHelper.WriteLogToFile("URI命令已通过IPC发送给已运行实例", LogHelper.LogType.Event);
}
else
{
LogHelper.WriteLogToFile("通过IPC发送URI命令失败", LogHelper.LogType.Warning);
}
}
else
{
LogHelper.WriteLogToFile("检测到已运行实例,但无文件参数", LogHelper.LogType.Event);
@@ -1021,6 +1037,21 @@ namespace Ink_Canvas
mainWindow.Show();
// 处理启动时的URI参数
string startupUriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(startupUriArg))
{
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
// 延迟一点执行,确保窗口初始化完成
Task.Delay(1000).ContinueWith(_ =>
{
mainWindow.Dispatcher.Invoke(() =>
{
mainWindow.HandleUriCommand(startupUriArg);
});
});
}
// 注册.icstk文件关联
try
{
@@ -26,6 +26,7 @@ namespace Ink_Canvas.Helpers
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
private const string IpcShowModePrefix = "InkCanvasShowMode_";
private const string IpcUriCommandPrefix = "InkCanvasUriCommand_";
private const int IpcTimeout = 5000; // 5秒超时
/// <summary>
@@ -361,6 +362,57 @@ namespace Ink_Canvas.Helpers
}
}
/// <summary>
/// 尝试通过IPC将URI命令发送给已运行的实例
/// </summary>
/// <param name="uri">URI命令</param>
/// <returns>是否成功发送</returns>
public static bool TrySendUriCommandToExistingInstance(string uri)
{
try
{
LogHelper.WriteLogToFile($"尝试通过IPC发送URI命令给已运行实例: {uri}", LogHelper.LogType.Event);
// 创建IPC文件
string tempDir = Path.GetTempPath();
string ipcFileName = IpcUriCommandPrefix + Guid.NewGuid().ToString("N") + ".tmp";
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
// 写入URI命令到IPC文件
File.WriteAllText(ipcFilePath, uri, Encoding.UTF8);
// 创建事件通知已运行实例
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
{
ipcEvent.Set();
}
// 等待一段时间让已运行实例处理命令
Thread.Sleep(1000);
// 清理IPC文件
try
{
if (File.Exists(ipcFilePath))
{
File.Delete(ipcFilePath);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
}
LogHelper.WriteLogToFile("IPC URI命令发送完成", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"通过IPC发送URI命令失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
/// <summary>
/// 启动IPC监听器,等待其他实例发送文件路径
/// </summary>
@@ -576,6 +628,56 @@ namespace Ink_Canvas.Helpers
catch { }
}
}
// 处理URI命令IPC文件
string[] uriCommandFiles = Directory.GetFiles(tempDir, IpcUriCommandPrefix + "*.tmp");
foreach (string ipcFile in uriCommandFiles)
{
try
{
// 读取命令内容
string uri = File.ReadAllText(ipcFile, Encoding.UTF8);
if (!string.IsNullOrEmpty(uri))
{
LogHelper.WriteLogToFile($"IPC接收到URI命令: {uri}", LogHelper.LogType.Event);
// 在UI线程中处理URI命令
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// 获取主窗口并处理URI命令
if (Application.Current.MainWindow is MainWindow mainWindow)
{
mainWindow.HandleUriCommand(uri);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"IPC处理URI命令失败: {ex.Message}", LogHelper.LogType.Error);
}
}));
}
// 删除IPC文件
File.Delete(ipcFile);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理URI命令IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
// 尝试删除损坏的IPC文件
try
{
if (File.Exists(ipcFile))
{
File.Delete(ipcFile);
}
}
catch { }
}
}
}
catch (Exception ex)
{
+89
View File
@@ -0,0 +1,89 @@
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Security;
namespace Ink_Canvas.Helpers
{
public static class UriSchemeHelper
{
private const string SchemeName = "icc";
private const string FriendlyName = "URL:Ink Canvas Protocol";
public static bool RegisterUriScheme()
{
try
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
// 使用 CurrentUser\Software\Classes 代替 ClassesRoot,无需管理员权限
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\Classes\" + SchemeName))
{
key.SetValue("", FriendlyName);
key.SetValue("URL Protocol", "");
using (RegistryKey defaultIconKey = key.CreateSubKey("DefaultIcon"))
{
// 修正引号转义
defaultIconKey.SetValue("", "\"" + exePath + "\",1");
}
using (RegistryKey shellKey = key.CreateSubKey("shell"))
using (RegistryKey openKey = shellKey.CreateSubKey("open"))
using (RegistryKey commandKey = openKey.CreateSubKey("command"))
{
// 修正引号转义
commandKey.SetValue("", "\"" + exePath + "\" \"%1\"");
}
}
LogHelper.WriteLogToFile($"成功注册URI Scheme: {SchemeName}://", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注册URI Scheme失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
public static bool UnregisterUriScheme()
{
try
{
// 使用 CurrentUser\Software\Classes
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\" + SchemeName, false);
LogHelper.WriteLogToFile($"成功注销URI Scheme: {SchemeName}://", LogHelper.LogType.Event);
return true;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"注销URI Scheme失败: {ex.Message}", LogHelper.LogType.Error);
return false;
}
}
public static bool IsUriSchemeRegistered()
{
try
{
// 使用 CurrentUser\Software\Classes
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Classes\" + SchemeName))
{
if (key == null) return false;
// 修正反斜杠路径
using (RegistryKey shellKey = key.OpenSubKey(@"shell\open\command"))
{
if (shellKey == null) return false;
string command = shellKey.GetValue("") as string;
string exePath = Process.GetCurrentProcess().MainModule.FileName;
return !string.IsNullOrEmpty(command) && command.Contains(exePath);
}
}
}
catch
{
return false;
}
}
}
}
+3
View File
@@ -2417,6 +2417,9 @@
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchIsSpecialScreen"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchIsSpecialScreen_OnToggled" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchIsEnableUriScheme"
Visibility="Collapsed" IsOn="False"
Toggled="ToggleSwitchIsEnableUriScheme_Toggled" />
</ui:SimpleStackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Foreground="#fafafa" Text="触摸倍数" VerticalAlignment="Center"
+23
View File
@@ -2853,6 +2853,29 @@ namespace Ink_Canvas
SaveSettingsToFile();
}
private void ToggleSwitchIsEnableUriScheme_Toggled(object sender, RoutedEventArgs e)
{
if (!isLoaded) return;
Settings.Advanced.IsEnableUriScheme = ToggleSwitchIsEnableUriScheme.IsOn;
if (Settings.Advanced.IsEnableUriScheme)
{
if (!UriSchemeHelper.IsUriSchemeRegistered())
{
UriSchemeHelper.RegisterUriScheme();
}
}
else
{
if (UriSchemeHelper.IsUriSchemeRegistered())
{
UriSchemeHelper.UnregisterUriScheme();
}
}
SaveSettingsToFile();
}
private void TouchMultiplierSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!isLoaded) return;
@@ -857,6 +857,7 @@ namespace Ink_Canvas
ToggleSwitchIsSecondConfimeWhenShutdownApp.IsOn = Settings.Advanced.IsSecondConfirmWhenShutdownApp;
ToggleSwitchWindowMode.IsOn = Settings.Advanced.WindowMode;
ToggleSwitchIsSpecialScreen.IsOn = Settings.Advanced.IsSpecialScreen;
ToggleSwitchIsEnableUriScheme.IsOn = Settings.Advanced.IsEnableUriScheme;
ToggleSwitchIsQuadIR.IsOn = Settings.Advanced.IsQuadIR;
ToggleSwitchEraserBindTouchMultiplier.IsOn = Settings.Advanced.EraserBindTouchMultiplier;
ToggleSwitchIsEnableFullScreenHelper.IsOn = Settings.Advanced.IsEnableFullScreenHelper;
+77
View File
@@ -0,0 +1,77 @@
using Ink_Canvas.Helpers;
using System;
using System.Windows;
namespace Ink_Canvas
{
public partial class MainWindow
{
public void HandleUriCommand(string uri)
{
try
{
if (string.IsNullOrEmpty(uri)) return;
LogHelper.WriteLogToFile($"正在处理URI命令: {uri}", LogHelper.LogType.Event);
// 解析URI
// 格式: icc://command?param=value
// 如果URI以icc:开头但不是标准URI格式,尝试手动解析
string command = "";
if (Uri.TryCreate(uri, UriKind.Absolute, out Uri uriObj))
{
command = uriObj.Host.ToLower();
}
else if (uri.StartsWith("icc:", StringComparison.OrdinalIgnoreCase))
{
// 简单的手动解析: icc:fold
string path = uri.Substring(4);
// 移除可能的斜杠
command = path.Trim('/').ToLower();
}
switch (command)
{
case "fold":
if (!isFloatingBarFolded)
{
FoldFloatingBar_MouseUp(new object(), null);
ShowNotification("已进入收纳模式");
}
break;
case "unfold":
case "show": // 兼容旧习惯
if (isFloatingBarFolded)
{
UnFoldFloatingBar_MouseUp(new object(), null);
ShowNotification("已退出收纳模式");
}
break;
case "toggle":
if (isFloatingBarFolded)
{
UnFoldFloatingBar_MouseUp(new object(), null);
ShowNotification("已退出收纳模式");
}
else
{
FoldFloatingBar_MouseUp(new object(), null);
ShowNotification("已进入收纳模式");
}
break;
default:
LogHelper.WriteLogToFile($"未知的URI命令: {command}", LogHelper.LogType.Warning);
break;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"处理URI命令时出错: {ex.Message}", LogHelper.LogType.Error);
}
}
}
}
+3
View File
@@ -629,6 +629,9 @@ namespace Ink_Canvas
[JsonProperty("enableUIAccessTopMost")]
public bool EnableUIAccessTopMost { get; set; } = false;
[JsonProperty("isEnableUriScheme")]
public bool IsEnableUriScheme { get; set; } = false;
[JsonProperty("windowMode")]
public bool WindowMode { get; set; } = true;
}
@@ -430,12 +430,34 @@
<TextBlock Text="管理.icstk文件的关联设置,双击.icstk文件可直接在Ink Canvas中打开"
TextWrapping="Wrap" Foreground="#9a9996" FontSize="11" Margin="0,0,0,12"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0">
<Button x:Name="BtnUnregisterFileAssociation" Content="取消文件关联" Padding="15,5" Margin="0,0,8,0" Background="#2563eb" Foreground="White"/>
<Button x:Name="BtnCheckFileAssociation" Content="检查关联状态" Padding="15,5" Margin="0,0,8,0" Background="#2563eb" Foreground="White"/>
<Button x:Name="BtnRegisterFileAssociation" Content="重新注册关联" Padding="15,5" Background="#2563eb" Foreground="White"/>
<Button x:Name="BtnUnregisterFileAssociation" Content="取消文件关联" Padding="15,5" Margin="0,0,8,0" Background="#2563eb" Foreground="White" Click="Button_Click"/>
<Button x:Name="BtnCheckFileAssociation" Content="检查关联状态" Padding="15,5" Margin="0,0,8,0" Background="#2563eb" Foreground="White" Click="Button_Click"/>
<Button x:Name="BtnRegisterFileAssociation" Content="重新注册关联" Padding="15,5" Background="#2563eb" Foreground="White" Click="Button_Click"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 外部协议调用 -->
<Border Margin="0,25,0,0" BorderBrush="#e6e6e6" BorderThickness="1.25,1.25,1.25,4" CornerRadius="8">
<StackPanel Orientation="Vertical" Margin="18,18,18,18">
<TextBlock Text="外部协议调用" FontWeight="Bold" Foreground="#2e3436" FontSize="18" Margin="0,0,0,12"/>
<TextBlock Text="允许通过 icc:// 协议调用软件,支持从浏览器或其他应用快速控制软件"
TextWrapping="Wrap" Foreground="#9a9996" FontSize="11" Margin="0,0,0,12"/>
<Grid Height="54">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Left">
<TextBlock Foreground="#2e3436" FontSize="14.5" Text="启用外部协议 (icc://)" HorizontalAlignment="Left"/>
<TextBlock Foreground="#9a9996" FontSize="11" Margin="0,3.5,0,0" Text="开启后可通过 icc://fold (收纳) 或 icc://unfold (展开) 等命令控制" HorizontalAlignment="Left"/>
</StackPanel>
<Border x:Name="ToggleSwitchIsEnableUriScheme" Style="{StaticResource ToggleSwitchStyle}" Background="#3584e4" Tag="IsEnableUriScheme" MouseLeftButtonDown="ToggleSwitch_Click">
<Border Width="19" Height="19" Background="White" CornerRadius="10" HorizontalAlignment="Right" VerticalAlignment="Center">
<Border.Effect>
<DropShadowEffect BlurRadius="4" Direction="-45" Color="Black" Opacity="0.3" ShadowDepth="0"/>
</Border.Effect>
</Border>
</Border>
</Grid>
</StackPanel>
</Border>
<!-- 悬浮窗拦截 -->
<Border Margin="0,25,0,0" BorderBrush="#e6e6e6" BorderThickness="1.25,1.25,1.25,4" CornerRadius="8">
@@ -141,6 +141,9 @@ namespace Ink_Canvas.Windows.SettingsViews
SetToggleSwitchState(FindToggleSwitch("ToggleSwitchIsAutoBackupEnabled"), advanced.IsAutoBackupEnabled);
SetOptionButtonState("AutoBackupInterval", advanced.AutoBackupIntervalDays);
// 外部协议
SetToggleSwitchState(FindToggleSwitch("ToggleSwitchIsEnableUriScheme"), advanced.IsEnableUriScheme);
// 悬浮窗拦截
// 注意:IsEnableFloatingWindowInterception 可能不在 Advanced 类中,需要确认
// 这里先假设它在 Advanced 类中,如果不在,需要调整
@@ -319,6 +322,11 @@ namespace Ink_Canvas.Windows.SettingsViews
// 调用 MainWindow 中的方法
MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchIsAutoBackupEnabled", newState);
break;
case "IsEnableUriScheme":
// 调用 MainWindow 中的方法
MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchIsEnableUriScheme", newState);
break;
}
}
@@ -446,6 +446,7 @@ namespace Ink_Canvas.Windows.SettingsViews
{ "ToggleSwitchIsEnableResolutionChangeDetection", "AdvancedPanel" },
{ "ToggleSwitchIsAutoBackupBeforeUpdate", "AdvancedPanel" },
{ "ToggleSwitchIsAutoBackupEnabled", "AdvancedPanel" },
{ "ToggleSwitchIsEnableUriScheme", "AdvancedPanel" },
// LuckyRandomPanel
{ "ToggleSwitchDisplayRandWindowNamesInputBtn", "LuckyRandomPanel" },