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
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static void AddCommonServices(this IServiceCollection collection)
collection.AddSingleton<IComboColourProjectStore, ComboColourProjectStore>();
collection.AddScoped<IOsuMemoryReaderService, OsuMemoryReaderService>();
collection.AddScoped<ILazerLookupService, LazerLookupService>();
collection.AddSingleton<OsuNowPlayingMonitor>();
collection.AddSingleton<ISongLibraryService, SongLibraryService>();
collection.AddSingleton<ISettingsService, SettingsService>();
collection.AddSingleton<IThemeService, ThemeService>();
Expand Down
5 changes: 2 additions & 3 deletions MapWizard.Desktop/MapWizard.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
<PackageReference Include="Lucide.Avalonia" Version="0.2.22" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.12" />
<PackageReference Include="OsuMemoryDataProvider" Version="0.12.2" />
<PackageReference Include="OsuWineMemReader" Version="1.0.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="4.152.1" />
<PackageReference Include="Velopack" Version="1.2.0" />
<PackageReference Include="Xaml.Behaviors" Version="12.0.7" />
Expand Down Expand Up @@ -62,8 +61,8 @@
-->
<ItemGroup>
<TrimmerRootAssembly Include="BeatmapParser" />
<TrimmerRootAssembly Include="OsuMemoryDataProvider" Condition="'$(RuntimeIdentifier)' == 'win-x64'" />
<TrimmerRootAssembly Include="ProcessMemoryDataFinder" Condition="'$(RuntimeIdentifier)' == 'win-x64'" />
<TrimmerRootAssembly Include="OsuMemoryDataProvider" />
<TrimmerRootAssembly Include="ProcessMemoryDataFinder" />
</ItemGroup>

<ItemGroup>
Expand Down
55 changes: 55 additions & 0 deletions MapWizard.Desktop/Models/OsuNowPlaying.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;

namespace MapWizard.Desktop.Models;

public enum OsuClient
{
Stable,
Lazer
}

/// <summary>
/// Snapshot of the beatmap currently open in an osu! client. Instances are immutable; a new one is
/// published whenever the open beatmap changes. The owner disposes <see cref="Background"/> once the
/// snapshot is replaced.
/// </summary>
public sealed class OsuNowPlaying(
OsuClient client,
string mapsetDirectory,
IReadOnlyList<string> beatmapPaths,
string artist,
string title,
string? difficulty,
string creator,
Bitmap? background) : IDisposable
{
public OsuClient Client { get; } = client;
public string MapsetDirectory { get; } = mapsetDirectory;

/// <summary>
/// The open difficulty for stable; every difficulty of the mounted set for lazer, which does not
/// expose which one is being edited.
/// </summary>
public IReadOnlyList<string> BeatmapPaths { get; } = beatmapPaths;

public string Artist { get; } = artist;
public string Title { get; } = title;
public string? Difficulty { get; } = difficulty;
public string Creator { get; } = creator;
public Bitmap? Background { get; } = background;

public bool HasBackground => Background is not null;
public string ClientLabel => Client == OsuClient.Lazer ? "osu!lazer" : "osu!stable";

public string Headline => Difficulty is null ? $"{Artist} - {Title}" : $"{Artist} - {Title} [{Difficulty}]";

public string Byline => Client == OsuClient.Lazer && BeatmapPaths.Count > 1
? $"mapped by {Creator} · {BeatmapPaths.Count} difficulties"
: $"mapped by {Creator}";

public string ToolTip => $"{Headline}\n{Byline}\n{ClientLabel}";

public void Dispose() => Background?.Dispose();
}
39 changes: 38 additions & 1 deletion MapWizard.Desktop/Services/MemoryService/FallbackClientIpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace MapWizard.Desktop.Services.MemoryService;
internal static class FallbackClientIpc
{
private const string PipeName = "mtipc";
private const string LinuxSocketPath = $"/tmp/{PipeName}.sock";
private static readonly Lock Sync = new();
private static NamedPipeClientStream? _pipeClient;
private static Socket? _socketClient;
Expand All @@ -23,6 +24,12 @@ public static bool TryReadBeatmapPath(out string? beatmapPath, out string? error
beatmapPath = null;
error = null;

if (!IsEndpointAvailable())
{
error = "No fallback client is listening.";
return false;
}

try
{
using var reader = SendMessage(MessageType.ReadBeatmap);
Expand Down Expand Up @@ -62,6 +69,12 @@ public static bool TryReadEditorTime(out int timestamp, out string? error)
timestamp = 0;
error = null;

if (!IsEndpointAvailable())
{
error = "No fallback client is listening.";
return false;
}

try
{
using var reader = SendMessage(MessageType.EditorTime);
Expand All @@ -77,6 +90,30 @@ public static bool TryReadEditorTime(out int timestamp, out string? error)
}
}

/// <summary>
/// Checks for the client's endpoint without connecting, so callers (including background polling)
/// skip the connect timeout and exception logging when no fallback client is running.
/// Pipes are enumerated rather than probed because opening one would consume a server instance.
/// </summary>
private static bool IsEndpointAvailable()
{
try
{
if (OperatingSystem.IsWindows())
{
return Directory.EnumerateFiles(@"\\.\pipe\")
.Any(path => string.Equals(Path.GetFileName(path), PipeName, StringComparison.OrdinalIgnoreCase));
}

return File.Exists(LinuxSocketPath);
}
catch (Exception)
{
// Enumeration failures should not hide a working endpoint; let the connect attempt decide.
return true;
}
}

private static BinaryReader SendMessage(MessageType messageType) => OperatingSystem.IsWindows()
? SendMessageWindows(messageType)
: SendMessageLinux(messageType);
Expand Down Expand Up @@ -205,7 +242,7 @@ private static void EnsureConnectionLinux()

_socketClient?.Dispose();
_socketClient = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
_socketClient.Connect(new UnixDomainSocketEndPoint($"/tmp/{PipeName}.sock"));
_socketClient.Connect(new UnixDomainSocketEndPoint(LinuxSocketPath));

using var reader = SendMessage(MessageType.Hello);
if (reader.ReadInt32() != 1337)
Expand Down
Loading
Loading