Skip to content

Commit 7fd9557

Browse files
authored
Cleanup. (#90)
1 parent 489f889 commit 7fd9557

File tree

8 files changed

+60
-309
lines changed

8 files changed

+60
-309
lines changed

ControlR.DesktopClient.Linux/Services/DisplayManagerWayland.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal interface IDisplayManagerWayland : IDisplayManager
1616
{
1717
bool HasAnyCaptureSizes { get; }
1818

19-
Task<IReadOnlyDictionary<string, PipeWireStream>> CreatePipeWireStreams(bool forcePortalReinitialize = false, CancellationToken cancellationToken = default);
19+
Task<IReadOnlyDictionary<string, PipeWireStream>> CreatePipeWireStreams(CancellationToken cancellationToken = default);
2020
bool TryGetCaptureSize(string deviceName, out Size size);
2121
void UpdateCaptureSize(string deviceName, int physicalWidth, int physicalHeight);
2222
}
@@ -49,13 +49,11 @@ internal class DisplayManagerWayland(
4949

5050
public bool HasAnyCaptureSizes => !_captureSizes.IsEmpty;
5151

52-
public async Task<IReadOnlyDictionary<string, PipeWireStream>> CreatePipeWireStreams(bool forcePortalReinitialize = false, CancellationToken cancellationToken = default)
52+
public async Task<IReadOnlyDictionary<string, PipeWireStream>> CreatePipeWireStreams(CancellationToken cancellationToken = default)
5353
{
5454
var result = new Dictionary<string, PipeWireStream>();
5555
try
5656
{
57-
await _portalService.Initialize(forcePortalReinitialize);
58-
5957
var connection = await _portalService.GetPipeWireConnection();
6058
if (connection is null || _streamFactory is null)
6159
{

ControlR.DesktopClient.Linux/Services/InputSimulatorWayland.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,16 @@ public async Task MovePointer(PointerCoordinates coordinates, MovePointerType mo
171171
// pixel space (0 to stream width/height), not global virtual screen coordinates.
172172
var clampedX = Math.Clamp(coordinates.NormalizedX, 0, 1);
173173
var clampedY = Math.Clamp(coordinates.NormalizedY, 0, 1);
174-
var maxX = Math.Max(0, coordinates.Display.CapturePixelSize.Width - 1);
175-
var maxY = Math.Max(0, coordinates.Display.CapturePixelSize.Height - 1);
176-
var physicalX = maxX * clampedX;
177-
var physicalY = maxY * clampedY;
174+
var maxX = Math.Max(0, coordinates.Display.LayoutBounds.Width - 1);
175+
var maxY = Math.Max(0, coordinates.Display.LayoutBounds .Height - 1);
176+
var logicalX = maxX * clampedX;
177+
var logicalY = maxY * clampedY;
178178

179179
await _desktopPortal.NotifyPointerMotionAbsoluteAsync(
180180
_sessionHandle,
181181
streamInfo.NodeId,
182-
physicalX,
183-
physicalY);
182+
logicalX,
183+
logicalY);
184184
break;
185185
}
186186

ControlR.DesktopClient.Linux/Services/ScreenGrabberWayland.cs

Lines changed: 9 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using ControlR.DesktopClient.Common.Models;
22
using ControlR.DesktopClient.Common.ServiceInterfaces;
33
using ControlR.Libraries.NativeInterop.Linux;
4+
using ControlR.Libraries.Shared.Extensions;
45
using Microsoft.Extensions.Logging;
56
using SkiaSharp;
67
using System.Collections.Concurrent;
@@ -34,22 +35,18 @@ internal class ScreenGrabberWayland(
3435
ILogger<ScreenGrabberWayland> logger) : IScreenGrabber
3536
{
3637
private const string CaptureModePipeWire = "WaylandPipeWire";
37-
private const int StreamStartPollingIntervalMs = 100;
38-
private const int StreamStartTimeoutMs = 3_000;
39-
private static readonly TimeSpan _recoveryCooldown = TimeSpan.FromSeconds(2);
40-
private static readonly TimeSpan _streamStaleThreshold = TimeSpan.FromSeconds(2);
4138

4239
private readonly IDisplayManagerWayland _displayManager = displayManager;
4340
private readonly SemaphoreSlim _initLock = new(1, 1);
4441
private readonly ILogger<ScreenGrabberWayland> _logger = logger;
42+
private readonly TimeSpan _streamStartPollingInterval = TimeSpan.FromMilliseconds(100);
43+
private readonly TimeSpan _streamStartTimeout = TimeSpan.FromSeconds(3);
4544
private readonly TimeProvider _timeProvider = timeProvider;
4645

4746
private bool _disposed;
4847
private bool _isInitialized;
49-
private long _lastRecoveryAttemptUtcTicks;
5048
private ConcurrentDictionary<string, PipeWireStream> _streams = new();
5149

52-
5350
public async Task<CaptureResult> CaptureAllDisplays(bool captureCursor = true)
5451
{
5552
try
@@ -217,37 +214,13 @@ public async Task Initialize(CancellationToken cancellationToken)
217214
}
218215
}
219216

220-
internal static bool ShouldRecoverStream(
221-
DateTime nowUtc,
222-
DateTime createdUtc,
223-
DateTime? lastFrameReceivedUtc,
224-
TimeSpan staleThreshold,
225-
DateTime? lastRecoveryAttemptUtc,
226-
TimeSpan recoveryCooldown)
227-
{
228-
var mostRecentActivityUtc = lastFrameReceivedUtc ?? createdUtc;
229-
if (nowUtc - mostRecentActivityUtc < staleThreshold)
230-
{
231-
return false;
232-
}
233-
234-
if (lastRecoveryAttemptUtc is not null && nowUtc - lastRecoveryAttemptUtc < recoveryCooldown)
235-
{
236-
return false;
237-
}
238-
239-
return true;
240-
}
241-
242217
private async Task<CaptureResult> CaptureStream(
243218
string deviceName,
244219
PipeWireStream stream,
245220
CancellationToken cancellationToken)
246221
{
247222
try
248223
{
249-
stream = await RefreshStreamIfStale(deviceName, stream, cancellationToken);
250-
251224
if (!stream.TryGetLatestFrame(out var frameData))
252225
{
253226
return CaptureResult.NoChanges(CaptureModePipeWire);
@@ -306,47 +279,6 @@ private void DisposeStreams()
306279
_streams.Clear();
307280
}
308281

309-
private DateTime? GetLastRecoveryAttemptUtc()
310-
{
311-
var ticks = Interlocked.Read(ref _lastRecoveryAttemptUtcTicks);
312-
return ticks > 0
313-
? new DateTime(ticks, DateTimeKind.Utc)
314-
: null;
315-
}
316-
317-
private async Task<PipeWireStream> RefreshStreamIfStale(
318-
string deviceName,
319-
PipeWireStream stream,
320-
CancellationToken cancellationToken)
321-
{
322-
var nowUtc = _timeProvider.GetUtcNow().UtcDateTime;
323-
if (!ShouldRecoverStream(
324-
nowUtc,
325-
stream.CreatedUtc,
326-
stream.LastFrameReceivedUtc,
327-
_streamStaleThreshold,
328-
GetLastRecoveryAttemptUtc(),
329-
_recoveryCooldown))
330-
{
331-
return stream;
332-
}
333-
334-
if (!await TryRefreshStreams(
335-
deviceName,
336-
$"stream has not produced a new frame since {(stream.LastFrameReceivedUtc ?? stream.CreatedUtc):O}",
337-
cancellationToken))
338-
{
339-
return stream;
340-
}
341-
342-
if (_streams.TryGetValue(deviceName, out var refreshedStream))
343-
{
344-
return refreshedStream;
345-
}
346-
347-
return _streams.Values.FirstOrDefault() ?? stream;
348-
}
349-
350282
private void ReplaceStreams(IReadOnlyDictionary<string, PipeWireStream> replacementStreams)
351283
{
352284
var newStreams = new ConcurrentDictionary<string, PipeWireStream>(replacementStreams);
@@ -371,77 +303,6 @@ private void ReplaceStreams(IReadOnlyDictionary<string, PipeWireStream> replacem
371303
originalStreams.Clear();
372304
}
373305

374-
private async Task<bool> TryRefreshStreams(
375-
string deviceName,
376-
string reason,
377-
CancellationToken cancellationToken)
378-
{
379-
if (_disposed)
380-
{
381-
return false;
382-
}
383-
384-
await _initLock.WaitAsync(cancellationToken);
385-
try
386-
{
387-
var nowUtc = _timeProvider.GetUtcNow().UtcDateTime;
388-
if (GetLastRecoveryAttemptUtc() is { } lastRecoveryAttemptUtc &&
389-
nowUtc - lastRecoveryAttemptUtc < _recoveryCooldown)
390-
{
391-
return false;
392-
}
393-
394-
Interlocked.Exchange(ref _lastRecoveryAttemptUtcTicks, nowUtc.Ticks);
395-
396-
_logger.LogWarning(
397-
"Refreshing Wayland PipeWire streams for display {DeviceName} because {Reason}.",
398-
deviceName,
399-
reason);
400-
401-
var replacementStreams = await _displayManager.CreatePipeWireStreams(
402-
forcePortalReinitialize: true,
403-
cancellationToken: cancellationToken);
404-
405-
if (replacementStreams.Count == 0)
406-
{
407-
_logger.LogWarning("Failed to refresh Wayland PipeWire streams. No replacement streams were created.");
408-
return false;
409-
}
410-
411-
try
412-
{
413-
await WaitForStreamsReady(replacementStreams, cancellationToken);
414-
ReplaceStreams(replacementStreams);
415-
_isInitialized = !_streams.IsEmpty;
416-
417-
_logger.LogInformation("Wayland PipeWire streams refreshed successfully.");
418-
return true;
419-
}
420-
catch
421-
{
422-
foreach (var stream in replacementStreams.Values)
423-
{
424-
stream.Dispose();
425-
}
426-
427-
throw;
428-
}
429-
}
430-
catch (OperationCanceledException)
431-
{
432-
throw;
433-
}
434-
catch (Exception ex)
435-
{
436-
_logger.LogWarning(ex, "Error while refreshing Wayland PipeWire streams.");
437-
return false;
438-
}
439-
finally
440-
{
441-
_initLock.Release();
442-
}
443-
}
444-
445306
private async Task WaitForStreamsReady(
446307
IReadOnlyDictionary<string, PipeWireStream> streams,
447308
CancellationToken cancellationToken)
@@ -451,15 +312,19 @@ private async Task WaitForStreamsReady(
451312
var deviceName = kv.Key;
452313
var stream = kv.Value;
453314

454-
for (var j = 0; j < StreamStartTimeoutMs / StreamStartPollingIntervalMs; j++)
315+
using var timer = new PeriodicTimer(_streamStartPollingInterval, _timeProvider);
316+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
317+
cts.CancelAfter(_streamStartTimeout);
318+
319+
while (await timer.WaitForNextTick(throwOnCancellation: false, cts.Token))
455320
{
456321
if (stream.IsStreaming && stream.TryGetLatestFrame(out var frameData))
457322
{
458323
frameData.Dispose();
459324
break;
460325
}
461326

462-
await Task.Delay(StreamStartPollingIntervalMs, cancellationToken);
327+
await Task.Delay(_streamStartPollingInterval, cancellationToken);
463328
}
464329

465330
if (stream.Width > 0 && stream.Height > 0)

ControlR.DesktopClient.Linux/XdgPortal/XdgDesktopPortal.cs

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public interface IXdgDesktopPortal : IDisposable
1313
Task<(SafeFileHandle Fd, string SessionHandle)?> GetPipeWireConnection();
1414
Task<string?> GetRemoteDesktopSessionHandle();
1515
Task<List<PipeWireStreamInfo>> GetScreenCastStreams();
16-
Task Initialize(bool forceReinitialization = false, bool bypassRestoreToken = false);
16+
Task Initialize(bool bypassRestoreToken = false);
1717
Task NotifyKeyboardKeycodeAsync(string sessionHandle, int keycode, bool pressed);
1818
Task NotifyKeyboardKeysymAsync(string sessionHandle, int keysym, bool pressed);
1919
Task NotifyPointerAxisAsync(string sessionHandle, double dx, double dy, bool finish = true);
@@ -79,9 +79,9 @@ public async Task<List<PipeWireStreamInfo>> GetScreenCastStreams()
7979
return _streams ?? throw new InvalidOperationException("ScreenCast streams are not initialized.");
8080
}
8181

82-
public async Task Initialize(bool forceReinitialization = false, bool bypassRestoreToken = false)
82+
public async Task Initialize(bool bypassRestoreToken = false)
8383
{
84-
await EnsureInitializedAsync(forceReinitialization, bypassRestoreToken);
84+
await EnsureInitializedAsync(bypassRestoreToken);
8585
}
8686

8787
public async Task NotifyKeyboardKeycodeAsync(string sessionHandle, int keycode, bool pressed)
@@ -250,26 +250,21 @@ private async Task<Result<string>> CreateRemoteDesktopSessionAsync(CancellationT
250250
}
251251
}
252252

253-
private async Task EnsureInitializedAsync(bool forceReinitialization = false, bool bypassRestoreToken = false)
253+
private async Task EnsureInitializedAsync(bool bypassRestoreToken = false)
254254
{
255-
if (_initialized && !forceReinitialization)
255+
if (_initialized)
256256
{
257257
return;
258258
}
259259

260260
await _initLock.WaitAsync();
261261
try
262262
{
263-
if (_initialized && !forceReinitialization)
263+
if (_initialized)
264264
{
265265
return;
266266
}
267267

268-
if (forceReinitialization)
269-
{
270-
ResetState();
271-
}
272-
273268
await ConnectAsync();
274269

275270
var sessionResult = await CreateRemoteDesktopSessionAsync();
@@ -382,17 +377,6 @@ private async Task<Result<SafeFileHandle>> OpenPipeWireRemoteAsync(string sessio
382377
}
383378
}
384379

385-
private void ResetState()
386-
{
387-
_pipewireFd?.Dispose();
388-
_connection?.Dispose();
389-
_pipewireFd = null;
390-
_connection = null;
391-
_sessionHandle = null;
392-
_streams = null;
393-
_initialized = false;
394-
}
395-
396380
private void SaveRestoreToken(string token)
397381
{
398382
try

ControlR.Web.Server/Services/Users/UserSettingsProviderServer.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ public Task<KeyboardInputMode> GetKeyboardInputMode()
2727
return GetPref(UserPreferenceNames.KeyboardInputMode, KeyboardInputMode.Auto);
2828
}
2929

30-
public Task<bool> GetOpenDeviceInNewTab()
30+
public Task<bool> GetNotifyUserOnSessionStart()
3131
{
32-
return GetPref(UserPreferenceNames.OpenDeviceInNewTab, true);
32+
return GetPref(UserPreferenceNames.NotifyUserOnSessionStart, true);
3333
}
3434

35-
public Task<bool> GetNotifyUserOnSessionStart()
35+
public Task<bool> GetOpenDeviceInNewTab()
3636
{
37-
return GetPref(UserPreferenceNames.NotifyUserOnSessionStart, true);
37+
return GetPref(UserPreferenceNames.OpenDeviceInNewTab, true);
3838
}
3939

4040
public Task<ThemeMode> GetThemeMode()
@@ -62,14 +62,14 @@ public Task SetKeyboardInputMode(KeyboardInputMode value)
6262
return SetPref(UserPreferenceNames.KeyboardInputMode, value);
6363
}
6464

65-
public Task SetOpenDeviceInNewTab(bool value)
65+
public Task SetNotifyUserOnSessionStart(bool value)
6666
{
67-
return SetPref(UserPreferenceNames.OpenDeviceInNewTab, value);
67+
return SetPref(UserPreferenceNames.NotifyUserOnSessionStart, value);
6868
}
6969

70-
public Task SetNotifyUserOnSessionStart(bool value)
70+
public Task SetOpenDeviceInNewTab(bool value)
7171
{
72-
return SetPref(UserPreferenceNames.NotifyUserOnSessionStart, value);
72+
return SetPref(UserPreferenceNames.OpenDeviceInNewTab, value);
7373
}
7474

7575
public Task SetThemeMode(ThemeMode value)

0 commit comments

Comments
 (0)