diff --git a/.github/workflows/version_check.yaml b/.github/workflows/version_check.yaml index 0cccb6e9..a65398ff 100644 --- a/.github/workflows/version_check.yaml +++ b/.github/workflows/version_check.yaml @@ -1,4 +1,4 @@ -name: Daybreak Version Check +name: Daybreak Version Check on: pull_request: @@ -7,16 +7,19 @@ on: paths: - "Daybreak/**" - "Daybreak.Installer/**" + - "Daybreak.Linux/**" + - "Daybreak.Windows/**" + - "Daybreak.API/**" + - "Daybreak.Shared/**" + - "Daybreak.Injector/**" jobs: - check_version: - strategy: matrix: - targetplatform: [x86] + targetplatform: [x64] - runs-on: windows-latest + runs-on: ubuntu-latest env: Configuration: Release @@ -24,30 +27,31 @@ jobs: Actions_Allow_Unsecure_Commands: true steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - - name: Install .NET Core - uses: actions/setup-dotnet@v5 - with: - dotnet-version: '9.x' + - name: Install .NET Core + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.x" - - name: Get Latest Tag - id: getLatestTag - uses: WyriHaximus/github-action-get-previous-tag@v1 + - name: Get Latest Tag + id: getLatestTag + uses: WyriHaximus/github-action-get-previous-tag@v1 - - name: Build Daybreak project - run: dotnet build Daybreak -c $env:Configuration --property:SolutionDir=$env:GITHUB_WORKSPACE + - name: Build Daybreak project + run: dotnet build Daybreak.Linux -c $env:Configuration --property:SolutionDir=$env:GITHUB_WORKSPACE - - name: Set version variable - run: | - $version = .\Scripts\GetBuildVersion.ps1 - echo "::set-env name=Version::$version" + - name: Set version variable + run: | + version=$(grep '' Directory.Build.props | sed 's/.*\(.*\)<\/Version>.*/\1/') + echo "Version=$version" >> $GITHUB_ENV + + - name: Check version difference + run: | + .\Scripts\CompareVersions -currentVersion ${{ env.Version }} -lastVersion ${{ env.LatestReleaseTag }} + env: + LatestReleaseTag: ${{ steps.getLatestTag.outputs.tag }} - - name: Check version difference - run: | - .\Scripts\CompareVersions -currentVersion ${{ env.Version }} -lastVersion ${{ env.LatestReleaseTag }} - env: - LatestReleaseTag: ${{ steps.getLatestTag.outputs.tag }} \ No newline at end of file diff --git a/Daybreak.Core/Configuration/Options/LauncherOptions.cs b/Daybreak.Core/Configuration/Options/LauncherOptions.cs index 8fcef749..63efef06 100644 --- a/Daybreak.Core/Configuration/Options/LauncherOptions.cs +++ b/Daybreak.Core/Configuration/Options/LauncherOptions.cs @@ -1,5 +1,5 @@ -using Daybreak.Shared.Attributes; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; +using Daybreak.Shared.Attributes; namespace Daybreak.Configuration.Options; @@ -7,12 +7,19 @@ namespace Daybreak.Configuration.Options; public sealed class LauncherOptions { [JsonPropertyName(nameof(ShortcutLocation))] - [OptionName(Name = "Shortcut Location", Description = "Location where the shortcut will be placed")] + [OptionName( + Name = "Shortcut Location", + Description = "Location where the shortcut will be placed" + )] [OptionSynchronizationIgnore] - public string? ShortcutLocation { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + public string? ShortcutLocation { get; set; } = + Environment.GetFolderPath(Environment.SpecialFolder.Desktop); [JsonPropertyName(nameof(PlaceShortcut))] - [OptionName(Name = "Shortcut Placed", Description = "If true, the shortcut will be placed. If false, the shortcut will be deleted")] + [OptionName( + Name = "Shortcut Placed", + Description = "If true, the shortcut will be placed. If false, the shortcut will be deleted" + )] public bool PlaceShortcut { get; set; } [JsonPropertyName(nameof(AutoCheckUpdate))] @@ -20,11 +27,24 @@ public sealed class LauncherOptions public bool AutoCheckUpdate { get; set; } = true; [JsonPropertyName(nameof(MultiLaunchSupport))] - [OptionName(Name = "Multi-Launch Support", Description = "If true, the launcher will support multiple executables being launched at the same time")] + [OptionName( + Name = "Multi-Launch Support", + Description = "If true, the launcher will support multiple executables being launched at the same time" + )] public bool MultiLaunchSupport { get; set; } [JsonPropertyName(nameof(ModStartupTimeout))] - [OptionName(Name = "Mod Startup Timeout", Description = "Amount of seconds that Daybreak will wait for each mod to start-up before cancelling the tasks")] + [OptionName( + Name = "Mod Startup Timeout", + Description = "Amount of seconds that Daybreak will wait for each mod to start-up before cancelling the tasks" + )] [OptionRange(MinValue = 30, MaxValue = 300)] public double ModStartupTimeout { get; set; } = 30; + + [JsonPropertyName(nameof(SaveWindowPosition))] + [OptionName( + Name = "Save Window Position", + Description = "If true, the launcher will save its position on the screen between launching" + )] + public bool SaveWindowPosition { get; set; } } diff --git a/Daybreak.Core/Services/ApplicationLauncher/ApplicationLauncher.cs b/Daybreak.Core/Services/ApplicationLauncher/ApplicationLauncher.cs index 93cc007b..6ab3ca33 100644 --- a/Daybreak.Core/Services/ApplicationLauncher/ApplicationLauncher.cs +++ b/Daybreak.Core/Services/ApplicationLauncher/ApplicationLauncher.cs @@ -175,9 +175,13 @@ internal sealed class ApplicationLauncher( var availableExecutables = this.guildWarsExecutableManager.GetExecutableList(); var runningInstances = this.GetGuildwarsProcesses(); var firstAvailableExecutable = availableExecutables.FirstOrDefault(e => - !runningInstances.Any(p => - Path.GetFullPath(p.MainModule?.FileName ?? string.Empty) == Path.GetFullPath(e) + FindFirstOrDefault( + runningInstances, + p => + Path.GetFullPath(p.MainModule?.FileName ?? string.Empty) + == Path.GetFullPath(e) ) + is null ); if (firstAvailableExecutable is null) { @@ -538,7 +542,7 @@ internal sealed class ApplicationLauncher( return this.guildWarsProcessFinder.FindProcesses(launchConfigurationWithCredentials); } - public IReadOnlyList GetGuildwarsProcesses() + public ReadOnlyMemory GetGuildwarsProcesses() { return this.guildWarsProcessFinder.GetGuildWarsProcesses(); } @@ -722,4 +726,21 @@ internal sealed class ApplicationLauncher( return [argName, $"\"{argValue}\""]; } + + private static Process? FindFirstOrDefault( + ReadOnlyMemory processes, + Func selector + ) + { + var span = processes.Span; + for (var i = 0; i < span.Length; i++) + { + if (selector(span[i])) + { + return span[i]; + } + } + + return default; + } } diff --git a/Daybreak.Core/Views/LaunchView.razor.cs b/Daybreak.Core/Views/LaunchView.razor.cs index a1906fef..a40a0d70 100644 --- a/Daybreak.Core/Views/LaunchView.razor.cs +++ b/Daybreak.Core/Views/LaunchView.razor.cs @@ -1,4 +1,9 @@ -using Daybreak.Configuration.Options; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Core.Extensions; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Daybreak.Configuration.Options; using Daybreak.Shared.Models; using Daybreak.Shared.Models.Api; using Daybreak.Shared.Models.LaunchConfigurations; @@ -9,15 +14,11 @@ using Daybreak.Shared.Services.LaunchConfigurations; using Daybreak.Shared.Services.Notifications; using Daybreak.Shared.Services.Onboarding; using Microsoft.Extensions.Options; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Core.Extensions; -using System.Diagnostics; -using System.Runtime.CompilerServices; using TrailBlazr.Services; using TrailBlazr.ViewModels; namespace Daybreak.Views; + public sealed class LaunchViewModel( IViewManager viewManager, INotificationService notificationService, @@ -25,17 +26,20 @@ public sealed class LaunchViewModel( ILaunchConfigurationService launchConfigurationService, IOnboardingService onboardingService, IApplicationLauncher applicationLauncher, - IOptionsMonitor focusViewOptions) : ViewModelBase, INotifyPropertyChanged, IDisposable + IOptionsMonitor focusViewOptions +) : ViewModelBase, INotifyPropertyChanged, IDisposable { private static readonly TimeSpan LaunchTimeout = TimeSpan.FromSeconds(10); private readonly IViewManager viewManager = viewManager.ThrowIfNull(); private readonly INotificationService notificationService = notificationService.ThrowIfNull(); private readonly IDaybreakApiService daybreakApiService = daybreakApiService.ThrowIfNull(); - private readonly ILaunchConfigurationService launchConfigurationService = launchConfigurationService.ThrowIfNull(); + private readonly ILaunchConfigurationService launchConfigurationService = + launchConfigurationService.ThrowIfNull(); private readonly IOnboardingService onboardingService = onboardingService.ThrowIfNull(); private readonly IApplicationLauncher applicationLauncher = applicationLauncher.ThrowIfNull(); - private readonly IOptionsMonitor focusViewOptions = focusViewOptions.ThrowIfNull(); + private readonly IOptionsMonitor focusViewOptions = + focusViewOptions.ThrowIfNull(); private CancellationTokenSource? cancellationTokenSource; private string? autoLaunchConfigurationId; @@ -43,12 +47,12 @@ public sealed class LaunchViewModel( public event PropertyChangedEventHandler? PropertyChanged; public ObservableCollection LaunchConfigurations { get; } = []; - - public LauncherViewContext? SelectedConfiguration - { + + public LauncherViewContext? SelectedConfiguration + { get; - private set - { + private set + { if (field != value) { field = value; @@ -57,12 +61,12 @@ public sealed class LaunchViewModel( } } } - - public bool CanLaunch - { + + public bool CanLaunch + { get; - private set - { + private set + { if (field != value) { field = value; @@ -73,19 +77,19 @@ public sealed class LaunchViewModel( public bool IsLaunching { get; private set; } - public bool IsDropdownOpen - { - get; - set - { + public bool IsDropdownOpen + { + get; + set + { if (field != value) { field = value; this.OnPropertyChanged(); } - } + } } - + public string LaunchButtonText => this.GetLaunchButtonText(); public string LaunchButtonSubtext => this.GetLaunchButtonSubtext(); @@ -94,7 +98,12 @@ public sealed class LaunchViewModel( { if (!this.IsOnboarded()) { - this.viewManager.ShowView((nameof(LauncherOnboardingView.Status), this.onboardingService.CheckOnboardingStage().ToString())); + this.viewManager.ShowView( + ( + nameof(LauncherOnboardingView.Status), + this.onboardingService.CheckOnboardingStage().ToString() + ) + ); return base.ParametersSet(view, cancellationToken); } @@ -128,14 +137,17 @@ public sealed class LaunchViewModel( } else { - await this.LaunchGuildWars(this.cancellationTokenSource?.Token ?? CancellationToken.None); + await this.LaunchGuildWars( + this.cancellationTokenSource?.Token ?? CancellationToken.None + ); } } catch (Exception ex) { this.notificationService.NotifyError( - title: "Launch Failed", - description: $"Failed to launch: {ex.Message}"); + title: "Launch Failed", + description: $"Failed to launch: {ex.Message}" + ); } finally { @@ -153,7 +165,7 @@ public sealed class LaunchViewModel( this.SelectedConfiguration = configuration; this.UpdateCanLaunch(); } - + this.IsDropdownOpen = false; this.RefreshView(); } @@ -179,9 +191,19 @@ public sealed class LaunchViewModel( this.RetrieveLaunchConfigurations(); var ct = this.cancellationTokenSource.Token; - Task.Factory.StartNew(() => this.PeriodicallyCheckSelectedConfigState(ct), ct, TaskCreationOptions.LongRunning, TaskScheduler.Current); - if (this.autoLaunchConfigurationId is not null && - this.LaunchConfigurations.FirstOrDefault(c => c.Configuration?.Identifier == this.autoLaunchConfigurationId) is LauncherViewContext context) + Task.Factory.StartNew( + () => this.PeriodicallyCheckSelectedConfigState(ct), + ct, + TaskCreationOptions.LongRunning, + TaskScheduler.Current + ); + if ( + this.autoLaunchConfigurationId is not null + && this.LaunchConfigurations.FirstOrDefault(c => + c.Configuration?.Identifier == this.autoLaunchConfigurationId + ) + is LauncherViewContext context + ) { this.SelectedConfiguration = context; Task.Run(this.LaunchSelectedConfiguration); @@ -216,24 +238,33 @@ public sealed class LaunchViewModel( throw new InvalidOperationException($"Unexpected onboarding stage {onboardingStage}"); } - return onboardingStage is not (LauncherOnboardingStage.NeedsCredentials or LauncherOnboardingStage.NeedsExecutable or LauncherOnboardingStage.NeedsConfiguration); + return onboardingStage + is not ( + LauncherOnboardingStage.NeedsCredentials + or LauncherOnboardingStage.NeedsExecutable + or LauncherOnboardingStage.NeedsConfiguration + ); } private void RetrieveLaunchConfigurations() { - var latestLaunchConfiguration = this.launchConfigurationService.GetLastLaunchConfigurationWithCredentials(); - var configurations = this.launchConfigurationService.GetLaunchConfigurations() + var latestLaunchConfiguration = + this.launchConfigurationService.GetLastLaunchConfigurationWithCredentials(); + var configurations = this + .launchConfigurationService.GetLaunchConfigurations() .Select(c => new LauncherViewContext { Configuration = c, CanLaunch = false }) .ToList(); - + this.LaunchConfigurations.Clear(); foreach (var config in configurations) { this.LaunchConfigurations.Add(config); } - - this.SelectedConfiguration = this.LaunchConfigurations.FirstOrDefault(c => c.Configuration?.Equals(latestLaunchConfiguration) is true) - ?? this.LaunchConfigurations.FirstOrDefault(); + + this.SelectedConfiguration = + this.LaunchConfigurations.FirstOrDefault(c => + c.Configuration?.Equals(latestLaunchConfiguration) is true + ) ?? this.LaunchConfigurations.FirstOrDefault(); } private async Task PeriodicallyCheckSelectedConfigState(CancellationToken cancellationToken) @@ -249,12 +280,8 @@ public sealed class LaunchViewModel( await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } - catch (OperationCanceledException) - { - } - catch (Exception) - { - } + catch (OperationCanceledException) { } + catch (Exception) { } } } @@ -291,7 +318,12 @@ public sealed class LaunchViewModel( } else { - this.CanLaunch = (this.SelectedConfiguration.CanKill || this.SelectedConfiguration.CanLaunch || this.SelectedConfiguration.CanAttach) && !this.IsLaunching; + this.CanLaunch = + ( + this.SelectedConfiguration.CanKill + || this.SelectedConfiguration.CanLaunch + || this.SelectedConfiguration.CanAttach + ) && !this.IsLaunching; } } @@ -351,8 +383,10 @@ public sealed class LaunchViewModel( return; } - this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(this.SelectedConfiguration.Configuration); - + this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials( + this.SelectedConfiguration.Configuration + ); + try { if (this.SelectedConfiguration.AppContext is not null) @@ -361,7 +395,11 @@ public sealed class LaunchViewModel( } else if (this.SelectedConfiguration.ApiContext is not null) { - await this.AttachContext(this.SelectedConfiguration, this.SelectedConfiguration.ApiContext, cancellationToken); + await this.AttachContext( + this.SelectedConfiguration, + this.SelectedConfiguration.ApiContext, + cancellationToken + ); } else { @@ -375,11 +413,17 @@ public sealed class LaunchViewModel( } } - private async Task CheckGameState(LauncherViewContext launcherViewContext, bool isSelected, CancellationToken cancellationToken) + private async Task CheckGameState( + LauncherViewContext launcherViewContext, + bool isSelected, + CancellationToken cancellationToken + ) { - if (launcherViewContext.Configuration is null || - launcherViewContext.Configuration.Credentials is null || - !isSelected) + if ( + launcherViewContext.Configuration is null + || launcherViewContext.Configuration.Credentials is null + || !isSelected + ) { launcherViewContext.GameRunning = false; launcherViewContext.CanLaunch = false; @@ -390,7 +434,7 @@ public sealed class LaunchViewModel( return; } - if (this.applicationLauncher.GetGuildwarsProcesses().Count == 0) + if (this.applicationLauncher.GetGuildwarsProcesses().Length == 0) { launcherViewContext.GameRunning = false; launcherViewContext.CanLaunch = true; @@ -401,8 +445,11 @@ public sealed class LaunchViewModel( return; } - (var appContext, var apiContext) = await this.GetAppAndApiContext(launcherViewContext, cancellationToken); - + (var appContext, var apiContext) = await this.GetAppAndApiContext( + launcherViewContext, + cancellationToken + ); + // No app context means game is not running for this configuration - allow launch if (appContext is null) { @@ -437,32 +484,49 @@ public sealed class LaunchViewModel( } } - private async Task<(GuildWarsApplicationLaunchContext? AppContext, ScopedApiContext? ApiContext)> GetAppAndApiContext(LauncherViewContext launcherViewContext, CancellationToken cancellationToken) + private async Task<( + GuildWarsApplicationLaunchContext? AppContext, + ScopedApiContext? ApiContext + )> GetAppAndApiContext( + LauncherViewContext launcherViewContext, + CancellationToken cancellationToken + ) { - if (launcherViewContext.Configuration is null || - launcherViewContext.Configuration.Credentials is null) + if ( + launcherViewContext.Configuration is null + || launcherViewContext.Configuration.Credentials is null + ) { return default; } // Return early if the app and api context are already initialized - if (launcherViewContext.AppContext is not null && - !launcherViewContext.AppContext.GuildWarsProcess.HasExited && - launcherViewContext.ApiContext is not null && - await launcherViewContext.ApiContext.IsAvailable(cancellationToken)) + if ( + launcherViewContext.AppContext is not null + && !launcherViewContext.AppContext.GuildWarsProcess.HasExited + && launcherViewContext.ApiContext is not null + && await launcherViewContext.ApiContext.IsAvailable(cancellationToken) + ) { return (launcherViewContext.AppContext, launcherViewContext.ApiContext); } - var processContext = this.applicationLauncher.GetGuildwarsProcess(launcherViewContext.Configuration); + var processContext = this.applicationLauncher.GetGuildwarsProcess( + launcherViewContext.Configuration + ); if (processContext is not null) { - var maybeApiContext = await this.daybreakApiService.GetDaybreakApiContext(processContext.GuildWarsProcess, cancellationToken); + var maybeApiContext = await this.daybreakApiService.GetDaybreakApiContext( + processContext.GuildWarsProcess, + cancellationToken + ); // If the API is available but it does not belong to the desired user, return null - if (maybeApiContext is not null && - await maybeApiContext.GetLoginInfo(cancellationToken) is LoginInfo loginInfo && - loginInfo.Email != launcherViewContext.Configuration.Credentials.Username) + if ( + maybeApiContext is not null + && await maybeApiContext.GetLoginInfo(cancellationToken) is LoginInfo loginInfo + && loginInfo.Email != launcherViewContext.Configuration.Credentials.Username + ) { return (processContext, default); } @@ -470,11 +534,17 @@ public sealed class LaunchViewModel( return (processContext, maybeApiContext); } - var apiContext = await this.daybreakApiService.FindDaybreakApiContextByCredentials(launcherViewContext.Configuration.Credentials, cancellationToken); + var apiContext = await this.daybreakApiService.FindDaybreakApiContextByCredentials( + launcherViewContext.Configuration.Credentials, + cancellationToken + ); return (processContext, apiContext); } - private async Task AttachContext(GuildWarsApplicationLaunchContext context, CancellationToken cancellationToken) + private async Task AttachContext( + GuildWarsApplicationLaunchContext context, + CancellationToken cancellationToken + ) { if (!this.focusViewOptions.CurrentValue.Enabled) { @@ -482,25 +552,41 @@ public sealed class LaunchViewModel( } using var notificationToken = this.notificationService.NotifyInformation( - title: "Attaching to Guild Wars process...", - description: "Attempting to attach to Guild Wars process"); - var apiContext = await this.daybreakApiService.AttachDaybreakApiContext(context, cancellationToken); + title: "Attaching to Guild Wars process...", + description: "Attempting to attach to Guild Wars process" + ); + var apiContext = await this.daybreakApiService.AttachDaybreakApiContext( + context, + cancellationToken + ); if (apiContext is null) { this.notificationService.NotifyError( title: "Could not attach to Guild Wars", - description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details"); + description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details" + ); } else { this.viewManager.ShowView( (nameof(FocusView.ProcessId), context.ProcessId.ToString()), - (nameof(FocusView.ConfigurationId), context.LaunchConfiguration.Identifier ?? throw new InvalidOperationException("LaunchConfig identifier cannot be null"))); + ( + nameof(FocusView.ConfigurationId), + context.LaunchConfiguration.Identifier + ?? throw new InvalidOperationException( + "LaunchConfig identifier cannot be null" + ) + ) + ); } } - private async Task AttachContext(LauncherViewContext launcherViewContext, ScopedApiContext apiContext, CancellationToken cancellationToken) + private async Task AttachContext( + LauncherViewContext launcherViewContext, + ScopedApiContext apiContext, + CancellationToken cancellationToken + ) { if (launcherViewContext.Configuration is null) { @@ -513,17 +599,21 @@ public sealed class LaunchViewModel( } using var notificationToken = this.notificationService.NotifyInformation( - title: "Attaching to Guild Wars process...", - description: "Attempting to attach to Guild Wars process"); + title: "Attaching to Guild Wars process...", + description: "Attempting to attach to Guild Wars process" + ); var processIdResponse = await apiContext.GetProcessId(cancellationToken); - if (processIdResponse is null || - Process.GetProcessById(processIdResponse.ProcessId) is not Process guildWarsProcess) + if ( + processIdResponse is null + || Process.GetProcessById(processIdResponse.ProcessId) is not Process guildWarsProcess + ) { notificationToken.Cancel(); this.notificationService.NotifyError( title: "Could not attach to Guild Wars", - description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details"); + description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details" + ); return; } @@ -531,17 +621,31 @@ public sealed class LaunchViewModel( { ProcessId = (uint)processIdResponse.ProcessId, GuildWarsProcess = guildWarsProcess, - LaunchConfiguration = launcherViewContext.Configuration + LaunchConfiguration = launcherViewContext.Configuration, }; - await this.daybreakApiService.AttachDaybreakApiContext(launchContext, apiContext, cancellationToken); - this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(launcherViewContext.Configuration); + await this.daybreakApiService.AttachDaybreakApiContext( + launchContext, + apiContext, + cancellationToken + ); + this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials( + launcherViewContext.Configuration + ); this.viewManager.ShowView( - (nameof(FocusView.ProcessId), launchContext.ProcessId.ToString()), - (nameof(FocusView.ConfigurationId), launchContext.LaunchConfiguration.Identifier ?? throw new InvalidOperationException("LaunchConfig identifier cannot be null"))); + (nameof(FocusView.ProcessId), launchContext.ProcessId.ToString()), + ( + nameof(FocusView.ConfigurationId), + launchContext.LaunchConfiguration.Identifier + ?? throw new InvalidOperationException("LaunchConfig identifier cannot be null") + ) + ); } - private async Task LaunchContext(LauncherViewContext launcherViewContext, CancellationToken cancellationToken) + private async Task LaunchContext( + LauncherViewContext launcherViewContext, + CancellationToken cancellationToken + ) { if (launcherViewContext.Configuration is null) { @@ -549,15 +653,20 @@ public sealed class LaunchViewModel( } var launchNotificationToken = this.notificationService.NotifyInformation( - title: "Launching Guild Wars...", - description: $"Attempting to launch Guild Wars process at {launcherViewContext.Configuration.ExecutablePath}", - expirationTime: DateTime.MaxValue); - var launchedContext = await this.applicationLauncher.LaunchGuildwars(launcherViewContext.Configuration, cancellationToken); + title: "Launching Guild Wars...", + description: $"Attempting to launch Guild Wars process at {launcherViewContext.Configuration.ExecutablePath}", + expirationTime: DateTime.MaxValue + ); + var launchedContext = await this.applicationLauncher.LaunchGuildwars( + launcherViewContext.Configuration, + cancellationToken + ); if (launchedContext is null) { this.notificationService.NotifyError( title: "Could not launch Guild Wars", - description: $"Could not launch Guild Wars at {launcherViewContext.Configuration.ExecutablePath}. Check the logs for more details"); + description: $"Could not launch Guild Wars at {launcherViewContext.Configuration.ExecutablePath}. Check the logs for more details" + ); launchNotificationToken.Cancel(); return; } @@ -565,39 +674,60 @@ public sealed class LaunchViewModel( launcherViewContext.AppContext = launchedContext; launchNotificationToken.Cancel(); this.daybreakApiService.RequestInstancesAnnouncement(); - this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(launcherViewContext.Configuration); + this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials( + launcherViewContext.Configuration + ); if (!this.focusViewOptions.CurrentValue.Enabled) { return; } var attachNotificationToken = this.notificationService.NotifyInformation( - title: "Attaching to Guild Wars process...", - description: "Attempting to attach to Guild Wars process"); + title: "Attaching to Guild Wars process...", + description: "Attempting to attach to Guild Wars process" + ); //Wait 1 second to allow the launched Guild Wars process to advertise itself var sw = Stopwatch.StartNew(); - var apiContext = await this.daybreakApiService.AttachDaybreakApiContext(launchedContext, cancellationToken); - while (sw.Elapsed < LaunchTimeout && !cancellationToken.IsCancellationRequested && apiContext is null) + var apiContext = await this.daybreakApiService.AttachDaybreakApiContext( + launchedContext, + cancellationToken + ); + while ( + sw.Elapsed < LaunchTimeout + && !cancellationToken.IsCancellationRequested + && apiContext is null + ) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); - apiContext = await this.daybreakApiService.AttachDaybreakApiContext(launchedContext, cancellationToken); + apiContext = await this.daybreakApiService.AttachDaybreakApiContext( + launchedContext, + cancellationToken + ); } - + attachNotificationToken.Cancel(); if (apiContext is null) { this.notificationService.NotifyError( title: "Could not attach to Guild Wars", - description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details"); + description: "Could not find the Api context to attach to Guild Wars. Check the logs for more details" + ); } else { launcherViewContext.ApiContext = apiContext; this.viewManager.ShowView( (nameof(FocusView.ProcessId), launchedContext.ProcessId.ToString()), - (nameof(FocusView.ConfigurationId), launchedContext.LaunchConfiguration.Identifier ?? throw new InvalidOperationException("LaunchConfig identifier cannot be null"))); + ( + nameof(FocusView.ConfigurationId), + launchedContext.LaunchConfiguration.Identifier + ?? throw new InvalidOperationException( + "LaunchConfig identifier cannot be null" + ) + ) + ); } } diff --git a/Daybreak.Linux/Launcher.cs b/Daybreak.Linux/Launcher.cs index e328a585..1f064cf7 100644 --- a/Daybreak.Linux/Launcher.cs +++ b/Daybreak.Linux/Launcher.cs @@ -1,5 +1,4 @@ using Daybreak.Linux.Configuration; -using System.Diagnostics; namespace Daybreak.Linux; @@ -7,45 +6,8 @@ public static partial class Launcher { public static void Main(string[] args) { - ConfigureEnvironment(); - var bootstrap = Launch.Launcher.SetupBootstrap(); var platformConfiguration = new LinuxPlatformConfiguration(); Launch.Launcher.LaunchSequence(args, bootstrap, platformConfiguration); } - - /// - /// Configures environment variables needed for WebKitGTK/Photino to work - /// reliably across different Linux display servers and GPU drivers. - /// Variables are only set when not already defined by the user, so they - /// can always be overridden externally. - /// - private static void ConfigureEnvironment() - { - // These environment variables must ideally be set BEFORE the process - // starts (e.g. via the launch wrapper script) so that native libraries - // like Mesa/EGL and libwayland see them at load time. Setting them here - // serves as a fallback for cases where the wrapper isn't used, but may - // not prevent all Wayland-related crashes. - - // WebKitGTK's DMA-BUF renderer can crash or produce artifacts on - // certain GPU driver combinations (especially Nvidia proprietary). - SetIfUnset("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); - - // GPU-accelerated compositing in WebKitGTK can cause blank windows - // or crashes with some Mesa/Nvidia drivers. - SetIfUnset("WEBKIT_DISABLE_COMPOSITING_MODE", "1"); - - // Photino (via WebKitGTK + GTK3) does not fully support native Wayland. - // Force X11 (via XWayland on Wayland sessions). - SetIfUnset("GDK_BACKEND", "x11"); - } - - private static void SetIfUnset(string variable, string value) - { - if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(variable))) - { - Environment.SetEnvironmentVariable(variable, value); - } - } } diff --git a/Daybreak.Linux/Services/ApplicationLauncher/GuildWarsProcessFinder.cs b/Daybreak.Linux/Services/ApplicationLauncher/GuildWarsProcessFinder.cs index 9d45912b..507b71f6 100644 --- a/Daybreak.Linux/Services/ApplicationLauncher/GuildWarsProcessFinder.cs +++ b/Daybreak.Linux/Services/ApplicationLauncher/GuildWarsProcessFinder.cs @@ -1,9 +1,9 @@ +using System.Diagnostics; +using System.Extensions.Core; using Daybreak.Linux.Services.Wine; using Daybreak.Shared.Models.LaunchConfigurations; using Daybreak.Shared.Services.ApplicationLauncher; using Microsoft.Extensions.Logging; -using System.Diagnostics; -using System.Extensions.Core; namespace Daybreak.Linux.Services.ApplicationLauncher; @@ -15,24 +15,25 @@ namespace Daybreak.Linux.Services.ApplicationLauncher; /// public sealed class GuildWarsProcessFinder( IWinePrefixManager winePrefixManager, - ILogger logger) - : IGuildWarsProcessFinder + ILogger logger +) : IGuildWarsProcessFinder { private const string GuildWarsExeName = "Gw.exe"; + private readonly Memory guildWarsProcesses = new(new Process[256]); private readonly IWinePrefixManager winePrefixManager = winePrefixManager; private readonly ILogger logger = logger; - public IReadOnlyList GetGuildWarsProcesses() + public Memory GetGuildWarsProcesses() { var scopedLogger = this.logger.CreateScopedLogger(); - var processes = new List(); - + var index = 0; foreach (var (pid, _) in this.ScanForGuildWarsProcesses()) { try { - processes.Add(Process.GetProcessById(pid)); + this.guildWarsProcesses.Span[index] = Process.GetProcessById(pid); + index++; } catch (Exception ex) { @@ -40,16 +41,20 @@ public sealed class GuildWarsProcessFinder( } } - scopedLogger.LogDebug("Found {Count} Guild Wars process(es) under Wine", processes.Count); - return processes; + scopedLogger.LogDebug("Found {Count} Guild Wars process(es) under Wine", index); + return this.guildWarsProcesses[..index]; } - public GuildWarsApplicationLaunchContext? FindProcess(LaunchConfigurationWithCredentials configuration) + public GuildWarsApplicationLaunchContext? FindProcess( + LaunchConfigurationWithCredentials configuration + ) { return this.FindProcesses(configuration).FirstOrDefault(); } - public IEnumerable FindProcesses(params LaunchConfigurationWithCredentials[] configurations) + public IEnumerable FindProcesses( + params LaunchConfigurationWithCredentials[] configurations + ) { var scopedLogger = this.logger.CreateScopedLogger(); @@ -57,7 +62,8 @@ public sealed class GuildWarsProcessFinder( { // Match executable path from Wine cmdline to configuration var matchedConfig = configurations.FirstOrDefault(c => - ConfigurationMatchesPath(c, executablePath)); + ConfigurationMatchesPath(c, executablePath) + ); if (matchedConfig is null) { @@ -79,7 +85,7 @@ public sealed class GuildWarsProcessFinder( { GuildWarsProcess = process, LaunchConfiguration = matchedConfig, - ProcessId = (uint)pid + ProcessId = (uint)pid, }; } } @@ -132,7 +138,8 @@ public sealed class GuildWarsProcessFinder( // cmdline is null-separated; find the segment containing Gw.exe var segments = cmdline.Split('\0', StringSplitOptions.RemoveEmptyEntries); var exeSegment = segments.FirstOrDefault(s => - s.EndsWith(GuildWarsExeName, StringComparison.OrdinalIgnoreCase)); + s.EndsWith(GuildWarsExeName, StringComparison.OrdinalIgnoreCase) + ); if (exeSegment is not null) { @@ -140,8 +147,14 @@ public sealed class GuildWarsProcessFinder( executablePath = WinePathToLinuxPath(exeSegment); } } - catch (UnauthorizedAccessException) { continue; } - catch (IOException) { continue; } + catch (UnauthorizedAccessException) + { + continue; + } + catch (IOException) + { + continue; + } if (executablePath is not null) { @@ -157,8 +170,10 @@ public sealed class GuildWarsProcessFinder( private static string WinePathToLinuxPath(string winePath) { // Strip Z: prefix and convert backslashes - if (winePath.StartsWith("Z:", StringComparison.OrdinalIgnoreCase) || - winePath.StartsWith("z:", StringComparison.OrdinalIgnoreCase)) + if ( + winePath.StartsWith("Z:", StringComparison.OrdinalIgnoreCase) + || winePath.StartsWith("z:", StringComparison.OrdinalIgnoreCase) + ) { winePath = winePath[2..]; } @@ -168,7 +183,8 @@ public sealed class GuildWarsProcessFinder( private static bool ConfigurationMatchesPath( LaunchConfigurationWithCredentials configuration, - string executablePath) + string executablePath + ) { if (configuration.ExecutablePath is null) { @@ -178,6 +194,7 @@ public sealed class GuildWarsProcessFinder( return string.Equals( Path.GetFullPath(configuration.ExecutablePath), Path.GetFullPath(executablePath), - StringComparison.OrdinalIgnoreCase); + StringComparison.OrdinalIgnoreCase + ); } } diff --git a/Daybreak.Shared/Services/ApplicationLauncher/IApplicationLauncher.cs b/Daybreak.Shared/Services/ApplicationLauncher/IApplicationLauncher.cs index 00921e31..2c321e3a 100644 --- a/Daybreak.Shared/Services/ApplicationLauncher/IApplicationLauncher.cs +++ b/Daybreak.Shared/Services/ApplicationLauncher/IApplicationLauncher.cs @@ -1,19 +1,35 @@ -using Daybreak.Shared.Models.LaunchConfigurations; +using System.Diagnostics; +using Daybreak.Shared.Models.LaunchConfigurations; using Daybreak.Shared.Services.Mods; -using System.Diagnostics; namespace Daybreak.Shared.Services.ApplicationLauncher; public interface IApplicationLauncher { - Task ReapplyMods(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, IEnumerable mods, CancellationToken cancellationToken); - Task> CheckMods(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, CancellationToken cancellationToken); - IEnumerable GetLoadedModules(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext); - GuildWarsApplicationLaunchContext? GetGuildwarsProcess(LaunchConfigurationWithCredentials launchConfigurationWithCredentials); - IEnumerable GetGuildwarsProcesses(params LaunchConfigurationWithCredentials[] launchConfigurationWithCredentials); - IReadOnlyList GetGuildwarsProcesses(); + Task ReapplyMods( + GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, + IEnumerable mods, + CancellationToken cancellationToken + ); + Task> CheckMods( + GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, + CancellationToken cancellationToken + ); + IEnumerable GetLoadedModules( + GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext + ); + GuildWarsApplicationLaunchContext? GetGuildwarsProcess( + LaunchConfigurationWithCredentials launchConfigurationWithCredentials + ); + IEnumerable GetGuildwarsProcesses( + params LaunchConfigurationWithCredentials[] launchConfigurationWithCredentials + ); + ReadOnlyMemory GetGuildwarsProcesses(); void KillGuildWarsProcess(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext); - Task LaunchGuildwars(LaunchConfigurationWithCredentials launchConfigurationWithCredentials, CancellationToken cancellationToken); + Task LaunchGuildwars( + LaunchConfigurationWithCredentials launchConfigurationWithCredentials, + CancellationToken cancellationToken + ); void RestartDaybreak(); void RestartDaybreakAsAdmin(); void RestartDaybreakAsNormalUser(); diff --git a/Daybreak.Shared/Services/ApplicationLauncher/IGuildWarsProcessFinder.cs b/Daybreak.Shared/Services/ApplicationLauncher/IGuildWarsProcessFinder.cs index 8fd03f8d..a32b1edd 100644 --- a/Daybreak.Shared/Services/ApplicationLauncher/IGuildWarsProcessFinder.cs +++ b/Daybreak.Shared/Services/ApplicationLauncher/IGuildWarsProcessFinder.cs @@ -1,5 +1,6 @@ -using Daybreak.Shared.Models.LaunchConfigurations; +using System.Collections.Immutable; using System.Diagnostics; +using Daybreak.Shared.Models.LaunchConfigurations; namespace Daybreak.Shared.Services.ApplicationLauncher; @@ -13,16 +14,20 @@ public interface IGuildWarsProcessFinder /// /// Returns all running Guild Wars processes. /// - IReadOnlyList GetGuildWarsProcesses(); + Memory GetGuildWarsProcesses(); /// /// Finds a running Guild Wars process that matches the given launch configuration. /// Returns null if no matching process is found. /// - GuildWarsApplicationLaunchContext? FindProcess(LaunchConfigurationWithCredentials configuration); + GuildWarsApplicationLaunchContext? FindProcess( + LaunchConfigurationWithCredentials configuration + ); /// /// Finds all running Guild Wars processes that match any of the given launch configurations. /// - IEnumerable FindProcesses(params LaunchConfigurationWithCredentials[] configurations); + IEnumerable FindProcesses( + params LaunchConfigurationWithCredentials[] configurations + ); } diff --git a/Daybreak.Windows/Services/ApplicationLauncher/GuildWarsProcessFinder.cs b/Daybreak.Windows/Services/ApplicationLauncher/GuildWarsProcessFinder.cs index fac40e9a..8636ceaa 100644 --- a/Daybreak.Windows/Services/ApplicationLauncher/GuildWarsProcessFinder.cs +++ b/Daybreak.Windows/Services/ApplicationLauncher/GuildWarsProcessFinder.cs @@ -1,9 +1,9 @@ -using Daybreak.Shared.Models.LaunchConfigurations; -using Daybreak.Shared.Services.ApplicationLauncher; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; +using Daybreak.Shared.Models.LaunchConfigurations; +using Daybreak.Shared.Services.ApplicationLauncher; using static Daybreak.Windows.Utils.NativeMethods; namespace Daybreak.Windows.Services.ApplicationLauncher; @@ -16,23 +16,30 @@ public sealed class GuildWarsProcessFinder : IGuildWarsProcessFinder { private const string ProcessName = "gw"; - public IReadOnlyList GetGuildWarsProcesses() + public Memory GetGuildWarsProcesses() { return Process.GetProcessesByName(ProcessName); } - public GuildWarsApplicationLaunchContext? FindProcess(LaunchConfigurationWithCredentials configuration) + public GuildWarsApplicationLaunchContext? FindProcess( + LaunchConfigurationWithCredentials configuration + ) { return this.FindProcesses(configuration).FirstOrDefault(); } - public IEnumerable FindProcesses(params LaunchConfigurationWithCredentials[] configurations) + public IEnumerable FindProcesses( + params LaunchConfigurationWithCredentials[] configurations + ) { - return Process.GetProcessesByName(ProcessName) + return Process + .GetProcessesByName(ProcessName) .Select(process => { (var AssociatedConfiguration, _, var ProcessId) = configurations - .Select(c => (c, ConfigurationMatchesProcess(c, process, out var processId), processId)) + .Select(c => + (c, ConfigurationMatchesProcess(c, process, out var processId), processId) + ) .FirstOrDefault(c => c.Item2); return (Process: process, AssociatedConfiguration, ProcessId); }) @@ -40,19 +47,29 @@ public sealed class GuildWarsProcessFinder : IGuildWarsProcessFinder .Select(tuple => new GuildWarsApplicationLaunchContext { GuildWarsProcess = tuple.Process, - LaunchConfiguration = tuple.AssociatedConfiguration!, - ProcessId = tuple.ProcessId + LaunchConfiguration = tuple.AssociatedConfiguration, + ProcessId = tuple.ProcessId, }); } - private static bool ConfigurationMatchesProcess(LaunchConfigurationWithCredentials launchConfigurationWithCredentials, Process process, out uint processId) + private static bool ConfigurationMatchesProcess( + LaunchConfigurationWithCredentials launchConfigurationWithCredentials, + Process process, + out uint processId + ) { try { processId = (uint)process.Id; - return launchConfigurationWithCredentials.ExecutablePath == process.MainModule?.FileName; + return launchConfigurationWithCredentials.ExecutablePath + == process.MainModule?.FileName; } - catch (Win32Exception ex) when (ex.Message.Contains("Access is denied") || ex.Message.Contains("Only part of a ReadProcessMemory or WriteProcessMemory request was completed.")) + catch (Win32Exception ex) + when (ex.Message.Contains("Access is denied") + || ex.Message.Contains( + "Only part of a ReadProcessMemory or WriteProcessMemory request was completed." + ) + ) { processId = 0; /* @@ -62,10 +79,7 @@ public sealed class GuildWarsProcessFinder : IGuildWarsProcessFinder * We create a process snapshot and compare the paths */ var hSnapshot = CreateToolhelp32Snapshot(0x00000002, 0); // TH32CS_SNAPPROCESS - var pe32 = new ProcessEntry32 - { - dwSize = (uint)Marshal.SizeOf() - }; + var pe32 = new ProcessEntry32 { dwSize = (uint)Marshal.SizeOf() }; var nameBuffer = new StringBuilder(1024); if (Process32First(hSnapshot, ref pe32)) @@ -75,9 +89,21 @@ public sealed class GuildWarsProcessFinder : IGuildWarsProcessFinder var size = 1024U; if (pe32.szExeFile == "Gw.exe") { - var maybeDesiredProcessHandle = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, pe32.th32ProcessID); - if (QueryFullProcessImageName(maybeDesiredProcessHandle, 0, nameBuffer, ref size) && - nameBuffer.ToString() == launchConfigurationWithCredentials.ExecutablePath) + var maybeDesiredProcessHandle = OpenProcess( + ProcessAccessFlags.QueryLimitedInformation, + false, + pe32.th32ProcessID + ); + if ( + QueryFullProcessImageName( + maybeDesiredProcessHandle, + 0, + nameBuffer, + ref size + ) + && nameBuffer.ToString() + == launchConfigurationWithCredentials.ExecutablePath + ) { processId = pe32.th32ProcessID; CloseHandle(maybeDesiredProcessHandle); diff --git a/Daybreak.Windows/Services/Screens/ScreenManager.cs b/Daybreak.Windows/Services/Screens/ScreenManager.cs index 854cd352..4d6ba292 100644 --- a/Daybreak.Windows/Services/Screens/ScreenManager.cs +++ b/Daybreak.Windows/Services/Screens/ScreenManager.cs @@ -1,3 +1,9 @@ +using System.Core.Extensions; +using System.Diagnostics; +using System.Drawing; +using System.Extensions; +using System.Extensions.Core; +using System.Runtime.InteropServices; using Daybreak.Configuration.Options; using Daybreak.Shared.Models; using Daybreak.Shared.Services.Options; @@ -7,12 +13,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Photino.NET; -using System.Core.Extensions; -using System.Diagnostics; -using System.Drawing; -using System.Extensions; -using System.Extensions.Core; -using System.Runtime.InteropServices; namespace Daybreak.Windows.Services.Screens; @@ -20,11 +20,16 @@ internal sealed class ScreenManager( PhotinoWindow photinoWindow, IOptionsProvider optionsProvider, IOptionsMonitor liveUpdateableOptions, - ILogger logger) : IScreenManager, IHostedService + IOptionsMonitor liveUpdateableLauncherOptions, + ILogger logger +) : IScreenManager, IHostedService { private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull(); private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull(); - private readonly IOptionsMonitor liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(); + private readonly IOptionsMonitor liveUpdateableOptions = + liveUpdateableOptions.ThrowIfNull(); + private readonly IOptionsMonitor liveUpdateableLauncherOptions = + liveUpdateableLauncherOptions.ThrowIfNull(); private readonly ILogger logger = logger.ThrowIfNull(); public IEnumerable Screens => GetAllScreens(); @@ -52,11 +57,18 @@ internal sealed class ScreenManager( public void SaveWindowPositionAndSize() { + if (!this.liveUpdateableLauncherOptions.CurrentValue.SaveWindowPosition) + { + this.ResetSavedPosition(); + return; + } + var position = new Rectangle( this.photinoWindow.Left, this.photinoWindow.Top, this.photinoWindow.Width, - this.photinoWindow.Height); + this.photinoWindow.Height + ); var options = this.liveUpdateableOptions.CurrentValue; options.X = position.Left; @@ -86,7 +98,15 @@ internal sealed class ScreenManager( return false; } - NativeMethods.SetWindowPos(hwnd.Value, NativeMethods.HWND_TOP, screen.Size.Left, screen.Size.Top, screen.Size.Width, screen.Size.Height, NativeMethods.SWP_SHOWWINDOW); + NativeMethods.SetWindowPos( + hwnd.Value, + NativeMethods.HWND_TOP, + screen.Size.Left, + screen.Size.Top, + screen.Size.Width, + screen.Size.Height, + NativeMethods.SWP_SHOWWINDOW + ); return true; } @@ -96,9 +116,14 @@ internal sealed class ScreenManager( (int)this.liveUpdateableOptions.CurrentValue.X, (int)this.liveUpdateableOptions.CurrentValue.Y, (int)this.liveUpdateableOptions.CurrentValue.Width, - (int)this.liveUpdateableOptions.CurrentValue.Height); + (int)this.liveUpdateableOptions.CurrentValue.Height + ); - if (savedPosition.Width is 0 || savedPosition.Height is 0) + if ( + savedPosition.Width is 0 + || savedPosition.Height is 0 + || !this.liveUpdateableLauncherOptions.CurrentValue.SaveWindowPosition + ) { var firstScreen = this.Screens.FirstOrDefault(); if (firstScreen.Size.IsEmpty) @@ -107,10 +132,11 @@ internal sealed class ScreenManager( } return new Rectangle( - firstScreen.Size.X + (firstScreen.Size.Width / 4), - firstScreen.Size.Y + (firstScreen.Size.Height / 4), - firstScreen.Size.Width / 2, - firstScreen.Size.Height / 2); + firstScreen.Size.X + (firstScreen.Size.Width / 4), + firstScreen.Size.Y + (firstScreen.Size.Height / 4), + firstScreen.Size.Width / 2, + firstScreen.Size.Height / 2 + ); } return savedPosition; @@ -127,20 +153,35 @@ internal sealed class ScreenManager( var screens = new List(); int index = 0; - NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, ref lprcMonitor, dwData) => - { - var monitorInfo = new NativeMethods.MonitorInfoEx + NativeMethods.EnumDisplayMonitors( + IntPtr.Zero, + IntPtr.Zero, + (hMonitor, hdcMonitor, ref lprcMonitor, dwData) => { - CbSize = (uint)Marshal.SizeOf() - }; + var monitorInfo = new NativeMethods.MonitorInfoEx + { + CbSize = (uint)Marshal.SizeOf(), + }; - if (NativeMethods.GetMonitorInfo(hMonitor, ref monitorInfo)) - { - screens.Add(new Screen(index++, new Rectangle(monitorInfo.RcMonitor.Left, monitorInfo.RcMonitor.Top, monitorInfo.RcMonitor.Width, monitorInfo.RcMonitor.Height))); - } + if (NativeMethods.GetMonitorInfo(hMonitor, ref monitorInfo)) + { + screens.Add( + new Screen( + index++, + new Rectangle( + monitorInfo.RcMonitor.Left, + monitorInfo.RcMonitor.Top, + monitorInfo.RcMonitor.Width, + monitorInfo.RcMonitor.Height + ) + ) + ); + } - return true; - }, IntPtr.Zero); + return true; + }, + IntPtr.Zero + ); return screens; } diff --git a/Scripts/GetBuildVersion.ps1 b/Scripts/GetBuildVersion.ps1 deleted file mode 100644 index b777ffd6..00000000 --- a/Scripts/GetBuildVersion.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Write-Host "Retrieving version" -$filepath = Get-ChildItem -Path .\Daybreak -Filter *.version -$version = $filepath.BaseName -Write-Host "Version: $version" -return $version \ No newline at end of file diff --git a/Scripts/PushFilesToBlobStorage.ps1 b/Scripts/PushFilesToBlobStorage.ps1 deleted file mode 100644 index 0254189d..00000000 --- a/Scripts/PushFilesToBlobStorage.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$version, - [Parameter(Mandatory=$true)] - [string]$connectionString, - [Parameter(Mandatory=$true)] - [string]$sourcePath -) - -# Create a new blob container -$containerName = "v$($version.ToLower().Replace('.', '-'))" # Container names must be lowercase -Write-Host "Creating container $($containerName)" -az storage container create --name $containerName --connection-string $connectionString - -Write-Host "Uploading files" -# Upload all files from the source path to the blob container -az storage blob upload-batch --destination $containerName --source $sourcePath --connection-string $connectionString - -Write-Host "Setting public access to container" -az storage container set-permission --name $containerName --public-access blob --connection-string $connectionString \ No newline at end of file diff --git a/Scripts/RunLinux.ps1 b/Scripts/RunLinux.ps1 new file mode 100644 index 00000000..ba2a13b9 --- /dev/null +++ b/Scripts/RunLinux.ps1 @@ -0,0 +1,14 @@ +$ErrorActionPreference = "Stop" +$RepoRoot = Resolve-Path "$PSScriptRoot/.." + +# Force X11 backend — Photino/WebKitGTK/GTK3 doesn't support Wayland natively +if (-not $env:GDK_BACKEND) { $env:GDK_BACKEND = "x11" } + +# Prevent WebKitGTK from using DMA-BUF (crashes on some Nvidia/Mesa combos) +if (-not $env:WEBKIT_DISABLE_DMABUF_RENDERER) { $env:WEBKIT_DISABLE_DMABUF_RENDERER = "1" } + +# Disable GPU compositing (blank windows on some drivers) +if (-not $env:WEBKIT_DISABLE_COMPOSITING_MODE) { $env:WEBKIT_DISABLE_COMPOSITING_MODE = "1" } + +dotnet run --project Daybreak.Linux +