Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ExHotKey/ExHotKeyPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public override Task ExecuteAsync(CancellationTokenSource cts)
var hk = new HotKeyItem(new HotKey(KeyCode.LeftControl, KeyCode.E));
hotKeyService
.ObserveHotKeySequence(hk)
// .Where(e=>e.Type == HotKeyEventType.Combo
// .Where(e=>e.Type == HotKeyEventType.Combo)
.Subscribe(e => { logger.LogInformation("测试:{@e}", e.Type); });
return Task.CompletedTask;
}
Expand Down
3 changes: 3 additions & 0 deletions ITool.Contracts.Keyboard/IKeyboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public interface IKeyboardService : IService
/// <summary>
/// 键盘输入事件
/// </summary>
/// <remarks>
/// 将<see cref="KeyPressed"/>拦截后,此源不输出流
/// </remarks>
public IObservable<KeyboardHookEventArgs> KeyTyped { get; }

/// <summary>
Expand Down
1 change: 0 additions & 1 deletion ITool.Controls/ITool.Controls.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
<ItemGroup>
<AvaloniaResource Include="Styles\Styles.axaml"/>
<AvaloniaXaml Remove="Styles\Styles.axaml"/>
<AvaloniaResource Include="Styles\Styles.axaml"/>
</ItemGroup>

<ItemGroup>
Expand Down
19 changes: 9 additions & 10 deletions ITool.Controls/IconControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@ namespace ITool.Controls;
/// <remarks>
/// 目前使用反射实现,如果之后 Iconica 提供直接的 DrawingData 访问,
/// 可以考虑直接使用其提供的DrawingData,而不是通过反射。
/// https://github.com/irihitech/Irihi.Iconica.IconPark/issues/9
/// </remarks>
public class IconControl : IconParkBase
{
private static readonly Dictionary<string, Type> IconMap;
private static readonly Dictionary<string, Type> iconMap;

private static readonly Dictionary<string, DrawingElement[]?> DrawingDataCache;
private static readonly Dictionary<string, DrawingElement[]?> drawingDataCache;

private DrawingElement[]? _currentDrawingData;

static IconControl()
{
IconMap = new Dictionary<string, Type>();
DrawingDataCache = new Dictionary<string, DrawingElement[]?>();
iconMap = new Dictionary<string, Type>();
drawingDataCache = new Dictionary<string, DrawingElement[]?>();

var iconAssembly = typeof(IconParkBase).Assembly;
var iconTypes = iconAssembly.GetTypes()
.Where(t => t is { IsPublic: true, IsAbstract: false } && typeof(IconParkBase).IsAssignableFrom(t));

foreach (var type in iconTypes) IconMap[type.Name.ToLowerInvariant()] = type;
foreach (var type in iconTypes) iconMap[type.Name.ToLowerInvariant()] = type;

ValueProperty.Changed.AddClassHandler<IconControl>((s, e) => s.OnValueChanged(e));
}
Expand All @@ -42,25 +41,25 @@ private void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
{
var key = iconName.ToLowerInvariant();

if (DrawingDataCache.TryGetValue(key, out var cachedData))
if (drawingDataCache.TryGetValue(key, out var cachedData))
{
_currentDrawingData = cachedData;
}
else
{
if (IconMap.TryGetValue(key, out var iconType))
if (iconMap.TryGetValue(key, out var iconType))
{
var tempInstance = (IconParkBase)Activator.CreateInstance(iconType)!;
var drawingDataProperty =
iconType.GetProperty("DrawingData", BindingFlags.NonPublic | BindingFlags.Instance);
var extractedData = drawingDataProperty?.GetValue(tempInstance) as DrawingElement[];

DrawingDataCache[key] = extractedData;
drawingDataCache[key] = extractedData;
_currentDrawingData = extractedData;
}
else
{
DrawingDataCache[key] = null;
drawingDataCache[key] = null;
_currentDrawingData = null;
}
}
Expand Down
2 changes: 1 addition & 1 deletion ITool.Platform.Windows/WindowsForegroundWindowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace ITool.Platform.Windows;

public sealed class WindowsForegroundWindowService : ServiceBase, IForegroundWindowService
public class WindowsForegroundWindowService : ServiceBase, IForegroundWindowService
{
private readonly ReplaySubject<WindowInfo> _foregroundWindowChanged = new(1);

Expand Down
5 changes: 5 additions & 0 deletions ITool.Script.JavaScript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
13 changes: 13 additions & 0 deletions ITool.Script.JavaScript/ITool.Script.JavaScript.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Jint" Version="4.4.1"/>
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions ITool.Script.Python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
13 changes: 13 additions & 0 deletions ITool.Script.Python/ITool.Script.Python.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IronPython.StdLib" Version="3.4.2"/>
</ItemGroup>

</Project>
1 change: 0 additions & 1 deletion ITool.Services.Gesture/Data/Gesture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public Gesture()
AddPoint(0, 0);
}

// TODO: 线程不安全,_points和_segments可能在拷贝时被修改
protected Gesture(Gesture original)
{
_points = original._points.ToList();
Expand Down
76 changes: 33 additions & 43 deletions ITool.Services.Keyboard/HotKeyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@

namespace ITool.Services.Keyboard;

public sealed class HotKeyService(
public class HotKeyService(
IEventSimulatorService eventSimulator,
IKeyboardService keyboard,
ILogger<HotKeyService> logger) : ServiceBase(logger), IHotKeyService
{
private readonly ConcurrentDictionary<HotKeyItem, IObservable<ExHotKeyEventArgs>> _registeredExHotKeys = new();

public TimeSpan ComboTimeout { get; set; } = TimeSpan.FromMilliseconds(300);
public TimeSpan ComboTimeout { get; set; } = TimeSpan.FromMilliseconds(400);

// TODO: 实现修饰键的判断(目前只有引导键和动作键)
// TEST: 测试是否存在事件泄露(触发其他未注册的按键时误发事件)
public IObservable<ExHotKeyEventArgs> ObserveHotKeySequence(HotKeyItem hotKeyItem)
{
return _registeredExHotKeys.GetOrAdd(hotKeyItem, keyItem =>
{
var key = keyItem.HotKey;
logger.LogTrace("--- Hotkey sequence starts with the lead key: '{LeadKey}' ---", key);

var leadKeyPressStream = keyboard.KeyPressed
.Where(e => !e.IsEventSimulated && e.Data.KeyCode == key.LeadKeyCode);
Expand All @@ -51,7 +49,6 @@ public IObservable<ExHotKeyEventArgs> ObserveHotKeySequence(HotKeyItem hotKeyIte
}).DisposeWith(mainSuppressor);

leadKeyEvent.SuppressEvent = true;
recordedEvents.Enqueue(leadKeyEvent);

var actionSuppressor = keyboard.KeyTyping
.Where(group => group.Key == key.ActionKeyCode)
Expand All @@ -72,51 +69,42 @@ public IObservable<ExHotKeyEventArgs> ObserveHotKeySequence(HotKeyItem hotKeyIte
.Take(1)
.TakeUntil(actionPressedSignal)
.Select(_ => recordedEvents.All(e => e.Data.KeyCode == key.LeadKeyCode)
? InternalResultType.ShortPress
: InternalResultType.LeadWithOtherKeys
? ResultType.ShortPress
: ResultType.LeadWithOtherKeys
);

var comboWinStream = keyboard.KeyTyped
var comboWinStream = keyboard.KeyReleased
.Where(e => !e.IsEventSimulated &&
e.Data.KeyCode == key.ActionKeyCode &&
keyboard.PressedKeys.ContainsKeys(key.LeadKeyCode, key.ModifiersKeyCode))
.Select(_ => InternalResultType.Combo);
.Select(_ => ResultType.Combo);

var resultStream = shortPressWinStream.Merge(comboWinStream)
.Take(1)
.Timeout(ComboTimeout, Observable.Return(InternalResultType.Timeout));
.Timeout(ComboTimeout, Observable.Return(ResultType.Timeout));

return resultStream
.Do(resultType =>
{
logger.LogTrace("--- The hotkey sequence is complete, internal results.: {Type} ---",
resultType);
logger.LogDebug("Hotkey was completed, internal results: {type}", resultType);

switch (resultType)
{
case InternalResultType.Combo:
logger.LogTrace("The combination key was successful.");
case ResultType.Combo:
break;

case InternalResultType.ShortPress:
if (keyItem.KeepFunc)
{
logger.LogTrace(
"""Key press compensation for "True short press" is in progress.""");
eventSimulator.SimulateKeyType(key.LeadKeyCode);
}

case ResultType.ShortPress:
if (keyItem.KeepFunc) eventSimulator.SimulateKeyType(key.LeadKeyCode);
break;
case InternalResultType.LeadWithOtherKeys:
case InternalResultType.Timeout:
logger.LogTrace(
"The hotkey sequence is complete, internal results: {Type}. Replay user operations...",
resultType);
case ResultType.LeadWithOtherKeys:
case ResultType.Timeout:
ReplayEvents(recordedEvents, eventSimulator);
break;
}
})
.Where(resultType => resultType != InternalResultType.LeadWithOtherKeys)
.Where(resultType => resultType
is ResultType.Combo
or ResultType.ShortPress
or ResultType.Timeout)
.Select(resultType => new ExHotKeyEventArgs(leadKeyEvent.RawEvent, MapToPublicType(resultType)))
.Finally(() => mainSuppressor.Dispose());
})
Expand All @@ -132,36 +120,38 @@ public override void Dispose()
base.Dispose();
}

private static HotKeyEventType MapToPublicType(InternalResultType internalType)
private static HotKeyEventType MapToPublicType(ResultType internalType)
{
return internalType switch
{
InternalResultType.ShortPress => HotKeyEventType.ShortPress,
InternalResultType.Combo => HotKeyEventType.Combo,
InternalResultType.Timeout => HotKeyEventType.Timeout,
ResultType.ShortPress => HotKeyEventType.ShortPress,
ResultType.Combo => HotKeyEventType.Combo,
ResultType.Timeout => HotKeyEventType.Timeout,
_ => throw new ArgumentOutOfRangeException(nameof(internalType), internalType,
$"Unprocessed internal result type: {internalType}")
};
}

private void ReplayEvents(ConcurrentQueue<KeyboardHookEventArgs> events, IEventSimulatorService simulator)
private static void ReplayEvents(ConcurrentQueue<KeyboardHookEventArgs> events, IEventSimulatorService simulator)
{
while (events.TryDequeue(out var e))
{
if (e.RawEvent.Type == EventType.KeyPressed)
{
logger.LogTrace("Replay KeyPress: {Key}", e.Data.KeyCode);
simulator.SimulateKeyPress(e.Data.KeyCode);
}
else if (e.RawEvent.Type == EventType.KeyReleased)
switch (e.RawEvent.Type)
{
logger.LogTrace("Replay KeyRelease: {Key}", e.Data.KeyCode);
simulator.SimulateKeyRelease(e.Data.KeyCode);
case EventType.KeyPressed:
simulator.SimulateKeyPress(e.Data.KeyCode);
break;
case EventType.KeyReleased:
simulator.SimulateKeyRelease(e.Data.KeyCode);
break;
default:
throw new ArgumentOutOfRangeException(nameof(e), e,
$"Unprocessed event type: {e.RawEvent.Type}");
}
}
}

private enum InternalResultType
private enum ResultType
{
ShortPress,
LeadWithOtherKeys,
Expand Down
4 changes: 2 additions & 2 deletions ITool.Services.Keyboard/KeyboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace ITool.Services.Keyboard;

[ServiceActivation]
public sealed class KeyboardService : ServiceBase, IKeyboardService
public class KeyboardService : ServiceBase, IKeyboardService
{
private readonly IDisposable _keyPressed;

Expand All @@ -26,7 +26,6 @@ public KeyboardService(IGlobalHookService hook, ILogger<KeyboardService> logger)
{
var pressStream = hook.KeyPressed;

// TEST: 试一下去除判断是否会有问题(从EventType的注释来看应该是_keyTyped才会触发多次)
var releaseStream = hook.KeyReleased
.Do(e =>
{
Expand All @@ -50,6 +49,7 @@ public KeyboardService(IGlobalHookService hook, ILogger<KeyboardService> logger)

_keyTyped = hook.KeyTyped.Subscribe(_keyTypedSubject);


_keyTyping = pressStream.Merge(releaseStream)
.GroupByUntil(
eventArgs => eventArgs.Data.KeyCode,
Expand Down
Loading
Loading