How would I integrate SharpShook into an AvaloniaUI App using Microsoft DI? #57
-
|
Hey, I'm trying to integrate SharpShook into an Avalonia UI App using Microsoft Dependency Injection, but I'm not sure, where to start RunAsync. I tried to register this globally, but it seems that no events are triggered / no keysPresses are logged. OS is macOS, but I tried others... Best regards. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 4 replies
-
|
Hi! Thanks for asking! Firstly, regarding Microsoft DI - you can just register the global hook like that: services.AddSingleton<IGlobalHook, TaskPoolGlobalHook>();
// or
services.AddSingleton<IGlobalHook>(s => new TaskPoolGlobalHook());Secondly, regarding Avalonia - I think the best way would be to start the global hook in the Thirdly, do you mean that you've called |
Beta Was this translation helpful? Give feedback.
-
Thanks for the quick response and providing such a useful library.
That's exactly what I tried... did not work.
Well, I think it would be best, if I show you some (not working) code: Detailsusing System;
using AudiobookshelfApi;
using AudiobookshelfApi.Api;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.SimpleRouter;
using LibVLCSharp.Shared;
using Microsoft.Extensions.DependencyInjection;
using SharpHook;
using ToneAudioPlayer.Services;
using ToneAudioPlayer.ViewModels;
using ToneAudioPlayer.Views;
namespace ToneAudioPlayer;
public class App : Application
{
private static TaskPoolGlobalHook _sharpHook = new();
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
IServiceProvider services = ConfigureServices();
var mainViewModel = services.GetRequiredService<MainViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = mainViewModel
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = mainViewModel
};
}
base.OnFrameworkInitializationCompleted();
}
private static ServiceProvider ConfigureServices()
{
_sharpHook.KeyTyped += OnKeyTyped;
_sharpHook.KeyPressed += OnKeyPressed;
_sharpHook.KeyReleased += OnKeyReleased;
_sharpHook.RunAsync();
var services = new ServiceCollection();
services.AddSingleton(_ => _sharpHook);
services.AddSingleton<HistoryRouter<ViewModelBase>>(s => new HistoryRouter<ViewModelBase>(t => (ViewModelBase)s.GetRequiredService(t)));
services.AddSingleton<Credentials>(_ => new Credentials
{
BaseAddress = new Uri(Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_URL") ?? ""),
Username = Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_USERNAME") ?? "root",
Password = Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_PASSWORD") ?? ""
});
services.AddSingleton<LibVLC>(s => new LibVLC(enableDebugLogs:false));
services.AddSingleton<MediaPlayer>();
services.AddSingleton<MediaPlayerService>();
services.AddSingleton<MainViewModel>();
services.AddTransient<HomeViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<SearchViewModel>();
// services.AddSingleton<Audiobookshelf>();
services.AddHttpClient<Audiobookshelf>()
.ConfigureHttpClient((sp, httpClient) =>
{
httpClient.Timeout = TimeSpan.FromSeconds(10);
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
return services.BuildServiceProvider();
}
private static void OnKeyReleased(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyReleased", e);
}
private static void AppendKeyLog(string keyreleased, KeyboardHookEventArgs e)
{
// This is never reached / called
KeyLogContent += $"{keyreleased}: {e.Data.KeyChar}\n";
}
public static string KeyLogContent { get; set; } = "";
private static void OnKeyTyped(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyTyped", e);
}
private static void OnKeyPressed(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyPressed", e);
}
}This does never call any of the key events, regardless of what Keyboard-Key I use. Another approach was to register the events in a specific ViewModel, but this also did not work. I tried to debug this but I did not find a solution: public partial class SettingsViewModel: ViewModelBase
{
private readonly HistoryRouter<ViewModelBase> _router;
private readonly TaskPoolGlobalHook _sharpHook;
[ObservableProperty]
private string _keyLogContent = "";
public SettingsViewModel(HistoryRouter<ViewModelBase> router, TaskPoolGlobalHook sharpHook)
{
_router = router;
_sharpHook = sharpHook;
_sharpHook.KeyTyped += OnKeyTyped;
_sharpHook.KeyPressed += OnKeyPressed;
_sharpHook.KeyReleased += OnKeyReleased;
}
private void OnKeyReleased(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyReleased", e);
}
private void AppendKeyLog(string keyreleased, KeyboardHookEventArgs e)
{
KeyLogContent += $"{keyreleased}: {e.Data.KeyChar}\n";
}
private void OnKeyTyped(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyTyped", e);
}
private void OnKeyPressed(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyPressed", e);
}
[RelayCommand]
private void Navigate(string parameter = "")
{
switch (parameter)
{
case "back":
// _router.Back();
// PlaySampleMedia();
break;
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Mh I think I got it working, with the following code it at least enters the using System;
using AudiobookshelfApi;
using AudiobookshelfApi.Api;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.SimpleRouter;
using LibVLCSharp.Shared;
using Microsoft.Extensions.DependencyInjection;
using SharpHook;
using ToneAudioPlayer.Services;
using ToneAudioPlayer.ViewModels;
using ToneAudioPlayer.Views;
namespace ToneAudioPlayer;
public class App : Application
{
private TaskPoolGlobalHook _sharpHook = new();
private MediaPlayerService _mediaPlayerService;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
IServiceProvider services = ConfigureServices();
_sharpHook = new TaskPoolGlobalHook();
_sharpHook.KeyTyped += OnKeyTyped;
_sharpHook.KeyPressed += OnKeyPressed;
_sharpHook.KeyReleased += OnKeyReleased;
_sharpHook.RunAsync();
_mediaPlayerService = services.GetRequiredService<MediaPlayerService>();
Core.Initialize();
var mainViewModel = services.GetRequiredService<MainViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = mainViewModel,
};
desktop.MainWindow.Closing += OnClosing;
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = mainViewModel
};
singleViewPlatform.MainView.Unloaded += OnUnloaded;
}
base.OnFrameworkInitializationCompleted();
}
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
CleanUpBeforeExit();
}
private void OnClosing(object? sender, WindowClosingEventArgs e)
{
CleanUpBeforeExit();
}
private void CleanUpBeforeExit()
{
_mediaPlayerService.Dispose();
_sharpHook.Dispose();
}
private static ServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<HistoryRouter<ViewModelBase>>(s => new HistoryRouter<ViewModelBase>(t => (ViewModelBase)s.GetRequiredService(t)));
services.AddSingleton<Credentials>(_ => new Credentials
{
BaseAddress = new Uri(Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_URL") ?? ""),
Username = Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_USERNAME") ?? "root",
Password = Environment.GetEnvironmentVariable("AUDIOBOOKSHELF_PASSWORD") ?? ""
});
services.AddSingleton<LibVLC>(s => new LibVLC(enableDebugLogs:false));
services.AddSingleton<MediaPlayer>();
services.AddSingleton<MediaPlayerService>();
services.AddSingleton<MainViewModel>();
services.AddTransient<HomeViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<SearchViewModel>();
// services.AddSingleton<Audiobookshelf>();
services.AddHttpClient<Audiobookshelf>()
.ConfigureHttpClient((sp, httpClient) =>
{
httpClient.Timeout = TimeSpan.FromSeconds(10);
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
return services.BuildServiceProvider();
}
private static void OnKeyReleased(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyReleased", e);
}
private static void AppendKeyLog(string keyreleased, KeyboardHookEventArgs e)
{
KeyLogContent += $"{keyreleased}: {e.Data.KeyChar}\n";
}
public static string KeyLogContent { get; set; } = "";
private static void OnKeyTyped(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyTyped", e);
}
private static void OnKeyPressed(object? sender, KeyboardHookEventArgs e)
{
AppendKeyLog("KeyPressed", e);
}
} |
Beta Was this translation helpful? Give feedback.
-
Oh, this is a nice addition. I thought I would be able to react on Media-Keys emitted by Headphone remotes... Do you plan to extend this to android / iOS anytime? Arm64 made me think it would work, so I would at least add this to the docs. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the feedback. I'm planning to implement Headphone-Commands that include double and triple clicks, double click followed by hold. Maybe extremely hard to implement if not impossible with C# atm. |
Beta Was this translation helpful? Give feedback.
Hi! Thanks for asking!
Firstly, regarding Microsoft DI - you can just register the global hook like that:
Secondly, regarding Avalonia - I think the best way would be to start the global hook in the
Appclass (e.g. in theOnFrameworkInitializationCompletedmethod). You can callRunAsyncand just ignore its result since you don't need to wait for the hook to stop. Also, you can configure the global hook to run on a background thread - this way it won't impede the application from stopping when you close the main window (or the last window, depending on your set…