Skip to content

Commit

Permalink
feat: notification reader
Browse files Browse the repository at this point in the history
  • Loading branch information
timschneeb committed May 17, 2024
1 parent 44146c9 commit 8025d40
Show file tree
Hide file tree
Showing 34 changed files with 361 additions and 59 deletions.
1 change: 1 addition & 0 deletions GalaxyBudsClient.Platform.Linux/BluetoothService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;
using Serilog;
using ThePBone.BlueZNet;
using ThePBone.BlueZNet.Interop;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
<ItemGroup>
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="SharpHook" Version="5.3.2" />
<PackageReference Include="Tmds.DBus" Version="0.16.0" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.16.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public class LinuxPlatformImplCreator : IPlatformImplCreator
public IHotkeyBroadcast CreateHotkeyBroadcast() => new HotkeyBroadcast();
public IHotkeyReceiver CreateHotkeyReceiver() => new HotkeyReceiver();
public IMediaKeyRemote CreateMediaKeyRemote() => new MediaKeyRemote();
public INotificationListener? CreateNotificationListener() => null;
public INotificationListener CreateNotificationListener() => new NotificationListener();
}
111 changes: 111 additions & 0 deletions GalaxyBudsClient.Platform.Linux/NotificationListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;
using Serilog;
using Tmds.DBus.Protocol;

namespace GalaxyBudsClient.Platform.Linux;

public class NotificationListener : INotificationListener
{
public bool IsEnabled
{
get => _isEnabled;
set
{
_isEnabled = value;
if (value)
{
_cancelSource = new CancellationTokenSource();
_loop = Task.Run(WatchBusAsync, _cancelSource.Token);
}
else
_cancelSource.Cancel();
}
}

public event EventHandler<Notification>? NotificationReceived;

private CancellationTokenSource _cancelSource = new();
private Task _loop;

Check warning on line 33 in GalaxyBudsClient.Platform.Linux/NotificationListener.cs

View workflow job for this annotation

GitHub Actions / build-arm

Non-nullable field '_loop' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 33 in GalaxyBudsClient.Platform.Linux/NotificationListener.cs

View workflow job for this annotation

GitHub Actions / build-x64

Non-nullable field '_loop' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 33 in GalaxyBudsClient.Platform.Linux/NotificationListener.cs

View workflow job for this annotation

GitHub Actions / build-x64-musl

Non-nullable field '_loop' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 33 in GalaxyBudsClient.Platform.Linux/NotificationListener.cs

View workflow job for this annotation

GitHub Actions / build-arm64

Non-nullable field '_loop' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
private bool _isEnabled;

private record NotifyMessageBody
{
public required string AppName { get; init; }
public required uint ReplacesId { get; init; }
public required string AppIcon { get; init; }
public required string Summary { get; init; }
public required string Body { get; init; }
public required string[] Actions { get; init; }
public required Dictionary<string, object> Hints { get; init; }
public required int ExpireTimeout { get; init; }
}

private NotifyMessageBody? ReadNotifyMessage(Reader reader)
{
try
{
return new NotifyMessageBody
{
AppName = reader.ReadString(),
ReplacesId = reader.ReadUInt32(),
AppIcon = reader.ReadString(),
Summary = reader.ReadString(),
Body = reader.ReadString(),
Actions = reader.ReadArray<string>(),
Hints = reader.ReadDictionary<string, object>(),
ExpireTimeout = reader.ReadInt32()
};
}
catch (Exception e)
{
Log.Error(e, "Failed to read Notify message body");
return null;
}
}

private async Task WatchBusAsync()
{
Log.Information("Notification listener started");

string address = Address.Session ?? throw new ArgumentNullException(nameof(address));

var rules = new List<MatchRule>
{
new()
{
Type = MessageType.MethodCall,
Path = "/org/freedesktop/Notifications",
Interface = "org.freedesktop.Notifications",
Member = "Notify"
}
};

var token = _cancelSource.Token;

while (token.IsCancellationRequested == false)
{
await foreach (var dmsg in Connection.MonitorBusAsync(address, rules, token))
{
using var _ = dmsg;
var msg = dmsg.Message;

if (msg.MemberAsString != "Notify")
continue;

var body = ReadNotifyMessage(msg.GetBodyReader());
if (body is null)
continue;

NotificationReceived?.Invoke(this,
new Notification(body.Summary, body.Body, body.AppName, body.AppName));
}
}

Log.Information("Notification listener stopped");
}
}
1 change: 1 addition & 0 deletions GalaxyBudsClient.Platform.OSX/BluetoothService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;
using Serilog;

namespace GalaxyBudsClient.Platform.OSX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using GalaxyBudsClient.Platform.Windows.Utils;
using GalaxyBudsClient.Platform;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Bluetooth.Msft;
using InTheHand.Net.Sockets;
Expand Down
3 changes: 2 additions & 1 deletion GalaxyBudsClient.Platform.WindowsRT/BluetoothDeviceRT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using Windows.Devices.Enumeration;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;

namespace GalaxyBudsClient.Platform.WindowsRT
{
Expand All @@ -18,7 +19,7 @@ internal class BluetoothDeviceRt(DeviceInformation info, IEnumerable<Guid>? serv
public override bool IsConnected => (bool) (DeviceInfo.Properties["System.Devices.Aep.IsConnected"] ?? false);
public override Guid[]? ServiceUuids => _services?.ToArray();

public void Update(DeviceInformationUpdate? update, IEnumerable<Guid> services)
public void Update(DeviceInformationUpdate? update, IEnumerable<Guid>? services)
{
if (update == null)
{
Expand Down
2 changes: 1 addition & 1 deletion GalaxyBudsClient.Platform.WindowsRT/BluetoothService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
using Windows.Storage.Streams;
using GalaxyBudsClient.Platform.Interfaces;
using Serilog;
using BluetoothDevice = GalaxyBudsClient.Platform.Interfaces.BluetoothDevice;
using BluetoothDevice = GalaxyBudsClient.Platform.Model.BluetoothDevice;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
#pragma warning disable CS0169 // Field is never used

Expand Down
65 changes: 15 additions & 50 deletions GalaxyBudsClient.Platform/Interfaces/IBluetoothService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,24 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GalaxyBudsClient.Platform.Model;

namespace GalaxyBudsClient.Platform.Interfaces
{
public class BluetoothDevice(string name, string address, bool isConnected, bool isPaired, BluetoothCoD cod, Guid[]? serviceUuids = null)
{
public BluetoothDevice(uint cod) : this(string.Empty, string.Empty, false, false, new BluetoothCoD(cod))
{
}

public override string ToString()
{
return $"{Name} ({Address})"; //$"BluetoothDevice[Name={Name},Address={Address},IsConnected={IsConnected},IsPaired='{IsPaired}',CoD='{Class}']";
}
namespace GalaxyBudsClient.Platform.Interfaces;

public virtual string Name { get; } = name;
public virtual string Address { get; } = address;
public virtual bool IsConnected { get; } = isConnected;
public virtual bool IsPaired { get; } = isPaired;
public BluetoothCoD Class { get; } = cod;
public virtual Guid[]? ServiceUuids { get; } = serviceUuids;

public static IEnumerable<BluetoothDevice> DummyDevices()
{
/* Dummy devices for testing */
var cod = new BluetoothCoD(0);
return new BluetoothDevice[]
{
new("Galaxy Buds (36FD) [Dummy]", "36:AB:38:F5:04:FD", true, true, cod),
new("Galaxy Buds+ (A2D5) [Dummy]", "A2:BF:D4:4A:52:D5", true, true, cod),
new("Galaxy Buds Live (4AC3) [Dummy]", "4A:6B:87:E5:12:C3", true, true, cod),
new("Galaxy Buds Pro (E43F) [Dummy]", "E4:25:FA:6D:B9:3F", true, true, cod),
new("Galaxy Buds2 (D592) [Dummy]", "D5:97:B8:23:AB:92", true, true, cod),
new("Galaxy Buds2 Pro (3292) [Dummy]", "32:97:B8:23:AB:92", true, true, cod),
new("Galaxy Buds FE (A7D4) [Dummy]", "A7:97:B8:23:AB:D4", true, true, cod)
};
}
}

public interface IBluetoothService
{
event EventHandler<BluetoothException>? BluetoothErrorAsync;
event EventHandler? Connecting;
event EventHandler? Connected;
event EventHandler? RfcommConnected;
event EventHandler<string>? Disconnected;
event EventHandler<byte[]>? NewDataAvailable;
public interface IBluetoothService
{
event EventHandler<BluetoothException>? BluetoothErrorAsync;
event EventHandler? Connecting;
event EventHandler? Connected;
event EventHandler? RfcommConnected;
event EventHandler<string>? Disconnected;
event EventHandler<byte[]>? NewDataAvailable;

bool IsStreamConnected { get; }
bool IsStreamConnected { get; }

Task ConnectAsync(string macAddress, string serviceUuid, CancellationToken cancelToken);
Task DisconnectAsync();
Task SendAsync(byte[] data);
Task ConnectAsync(string macAddress, string serviceUuid, CancellationToken cancelToken);
Task DisconnectAsync();
Task SendAsync(byte[] data);

Task<BluetoothDevice[]> GetDevicesAsync();
}
Task<BluetoothDevice[]> GetDevicesAsync();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using GalaxyBudsClient.Platform.Model;

namespace GalaxyBudsClient.Platform.Interfaces;

public interface INotificationListener
{

public bool IsEnabled { set; get; }
public event EventHandler<Notification>? NotificationReceived;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace GalaxyBudsClient.Platform
namespace GalaxyBudsClient.Platform.Model
{
public class BluetoothCoD
{
Expand Down
39 changes: 39 additions & 0 deletions GalaxyBudsClient.Platform/Model/BluetoothDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;

namespace GalaxyBudsClient.Platform.Model;

public class BluetoothDevice(string name, string address, bool isConnected, bool isPaired, BluetoothCoD cod, Guid[]? serviceUuids = null)
{
protected BluetoothDevice(uint cod) : this(string.Empty, string.Empty, false, false, new BluetoothCoD(cod))
{
}

public override string ToString()
{
return $"{Name} ({Address})"; //$"BluetoothDevice[Name={Name},Address={Address},IsConnected={IsConnected},IsPaired='{IsPaired}',CoD='{Class}']";
}

public virtual string Name { get; } = name;
public virtual string Address { get; } = address;
public virtual bool IsConnected { get; } = isConnected;
public virtual bool IsPaired { get; } = isPaired;
public BluetoothCoD Class { get; } = cod;
public virtual Guid[]? ServiceUuids { get; } = serviceUuids;

public static IEnumerable<BluetoothDevice> DummyDevices()
{
/* Dummy devices for testing */
var cod = new BluetoothCoD(0);
return new BluetoothDevice[]
{
new("Galaxy Buds (36FD) [Dummy]", "36:AB:38:F5:04:FD", true, true, cod),
new("Galaxy Buds+ (A2D5) [Dummy]", "A2:BF:D4:4A:52:D5", true, true, cod),
new("Galaxy Buds Live (4AC3) [Dummy]", "4A:6B:87:E5:12:C3", true, true, cod),
new("Galaxy Buds Pro (E43F) [Dummy]", "E4:25:FA:6D:B9:3F", true, true, cod),
new("Galaxy Buds2 (D592) [Dummy]", "D5:97:B8:23:AB:92", true, true, cod),
new("Galaxy Buds2 Pro (3292) [Dummy]", "32:97:B8:23:AB:92", true, true, cod),
new("Galaxy Buds FE (A7D4) [Dummy]", "A7:97:B8:23:AB:D4", true, true, cod)
};
}
}
14 changes: 14 additions & 0 deletions GalaxyBudsClient.Platform/Model/Notification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace GalaxyBudsClient.Platform.Model;

/**
* <param name="Title">The title of the notification</param>
* <param name="Content">The text content of the notification</param>
* <param name="AppName">A user-friendly name of the application that sent the notification</param>
* <param name="AppId">An unique identifier of the sender application</param>
*/
public record Notification(
string Title,
string Content,
string AppName,
string AppId
);
1 change: 1 addition & 0 deletions GalaxyBudsClient.Platform/PlatformUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum Platforms
public static bool SupportsAutoboot => IsWindows || (IsLinux && !IsRunningInFlatpak) || IsOSX;
public static bool SupportsHotkeys => IsWindows || IsLinux || IsOSX;
public static bool SupportsHotkeysBroadcast => IsWindows || IsLinux || IsOSX;
public static bool SupportsNotificationListener => IsLinux;
public static bool SupportsMicaTheme => IsWindows && Environment.OSVersion.Version.Build >= 22000;
public static bool SupportsBlurTheme => IsWindows || IsOSX || (IsLinux && Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP")?.Contains("KDE") == true);

Expand Down
1 change: 1 addition & 0 deletions GalaxyBudsClient.Platform/Stubs/BluetoothService.Dummy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;

#pragma warning disable CS0067

Expand Down
6 changes: 6 additions & 0 deletions GalaxyBudsClient.Platform/Stubs/NotificationListener.Dummy.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System;
using GalaxyBudsClient.Platform.Interfaces;
using GalaxyBudsClient.Platform.Model;

namespace GalaxyBudsClient.Platform.Stubs;

public class DummyNotificationListener : INotificationListener
{
public bool IsEnabled { get; set; }

#pragma warning disable CS0067
public event EventHandler<Notification>? NotificationReceived;
#pragma warning restore CS0067
}
5 changes: 5 additions & 0 deletions GalaxyBudsClient/GalaxyBudsClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
<AvaloniaXaml Remove="bin\**" />
<EmbeddedResource Remove="bin\**" />
<None Remove="bin\**" />
<Compile Update="Interface\Pages\NotificationReaderPage.axaml.cs">
<DependentUpon>NotificationReaderPage.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>

<!-- Dependencies -->
Expand Down Expand Up @@ -107,6 +111,7 @@
<PackageReference Include="Serilog.Sinks.Trace" Version="3.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="Tmds.DBus" Version="0.16.0" />
<PackageReference Include="Voice100" Version="1.3.0" />
</ItemGroup>

<!-- Project references -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
xmlns:i18N="clr-namespace:GalaxyBudsClient.Generated.I18N"
xmlns:config="clr-namespace:GalaxyBudsClient.Model.Config"
xmlns:interfaces="clr-namespace:GalaxyBudsClient.Platform.Interfaces;assembly=GalaxyBudsClient.Platform"
xmlns:model="clr-namespace:GalaxyBudsClient.Platform.Model;assembly=GalaxyBudsClient.Platform"
mc:Ignorable="d"
Padding="0, 5, 0, 0"
x:CompileBindings="True"
Expand All @@ -35,7 +36,7 @@
IsVisible="{Binding !NoDevices}"
Name="DevList">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="interfaces:BluetoothDevice">
<DataTemplate x:DataType="model:BluetoothDevice">
<Border Background="Transparent" PointerPressed="OnListItemPressed">
<Panel>
<StackPanel Spacing="4" Margin="4 10" VerticalAlignment="Center">
Expand Down
Loading

0 comments on commit 8025d40

Please sign in to comment.