Merge pull request #366 from PANDAJSR/feat--允许第三方程序通过系统URI来控制部分功能
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user