Screen position toggle (Closes #1379) (#1380)

This commit is contained in:
2026-02-08 06:19:01 -08:00
parent 824881c108
commit ea0a7a1edb
13 changed files with 514 additions and 283 deletions
+31 -27
View File
@@ -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 '<Version>' Directory.Build.props | sed 's/.*<Version>\(.*\)<\/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 }}
@@ -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<double>(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; }
}
@@ -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<Process> GetGuildwarsProcesses()
public ReadOnlyMemory<Process> GetGuildwarsProcesses()
{
return this.guildWarsProcessFinder.GetGuildWarsProcesses();
}
@@ -722,4 +726,21 @@ internal sealed class ApplicationLauncher(
return [argName, $"\"{argValue}\""];
}
private static Process? FindFirstOrDefault(
ReadOnlyMemory<Process> processes,
Func<Process, bool> selector
)
{
var span = processes.Span;
for (var i = 0; i < span.Length; i++)
{
if (selector(span[i]))
{
return span[i];
}
}
return default;
}
}
+233 -103
View File
@@ -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> focusViewOptions) : ViewModelBase<LaunchViewModel, LaunchView>, INotifyPropertyChanged, IDisposable
IOptionsMonitor<FocusViewOptions> focusViewOptions
) : ViewModelBase<LaunchViewModel, LaunchView>, 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 = focusViewOptions.ThrowIfNull();
private readonly IOptionsMonitor<FocusViewOptions> focusViewOptions =
focusViewOptions.ThrowIfNull();
private CancellationTokenSource? cancellationTokenSource;
private string? autoLaunchConfigurationId;
@@ -43,12 +47,12 @@ public sealed class LaunchViewModel(
public event PropertyChangedEventHandler? PropertyChanged;
public ObservableCollection<LauncherViewContext> 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<LauncherOnboardingView>((nameof(LauncherOnboardingView.Status), this.onboardingService.CheckOnboardingStage().ToString()));
this.viewManager.ShowView<LauncherOnboardingView>(
(
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<FocusView>(
(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<FocusView>(
(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<FocusView>(
(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"
)
)
);
}
}
-38
View File
@@ -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);
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
@@ -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;
/// </summary>
public sealed class GuildWarsProcessFinder(
IWinePrefixManager winePrefixManager,
ILogger<GuildWarsProcessFinder> logger)
: IGuildWarsProcessFinder
ILogger<GuildWarsProcessFinder> logger
) : IGuildWarsProcessFinder
{
private const string GuildWarsExeName = "Gw.exe";
private readonly Memory<Process> guildWarsProcesses = new(new Process[256]);
private readonly IWinePrefixManager winePrefixManager = winePrefixManager;
private readonly ILogger<GuildWarsProcessFinder> logger = logger;
public IReadOnlyList<Process> GetGuildWarsProcesses()
public Memory<Process> GetGuildWarsProcesses()
{
var scopedLogger = this.logger.CreateScopedLogger();
var processes = new List<Process>();
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<GuildWarsApplicationLaunchContext?> FindProcesses(params LaunchConfigurationWithCredentials[] configurations)
public IEnumerable<GuildWarsApplicationLaunchContext?> 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
);
}
}
@@ -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<bool> ReapplyMods(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, IEnumerable<IModService> mods, CancellationToken cancellationToken);
Task<IEnumerable<IModService>> CheckMods(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext, CancellationToken cancellationToken);
IEnumerable<string> GetLoadedModules(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext);
GuildWarsApplicationLaunchContext? GetGuildwarsProcess(LaunchConfigurationWithCredentials launchConfigurationWithCredentials);
IEnumerable<GuildWarsApplicationLaunchContext?> GetGuildwarsProcesses(params LaunchConfigurationWithCredentials[] launchConfigurationWithCredentials);
IReadOnlyList<Process> GetGuildwarsProcesses();
Task<bool> ReapplyMods(
GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext,
IEnumerable<IModService> mods,
CancellationToken cancellationToken
);
Task<IEnumerable<IModService>> CheckMods(
GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext,
CancellationToken cancellationToken
);
IEnumerable<string> GetLoadedModules(
GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext
);
GuildWarsApplicationLaunchContext? GetGuildwarsProcess(
LaunchConfigurationWithCredentials launchConfigurationWithCredentials
);
IEnumerable<GuildWarsApplicationLaunchContext?> GetGuildwarsProcesses(
params LaunchConfigurationWithCredentials[] launchConfigurationWithCredentials
);
ReadOnlyMemory<Process> GetGuildwarsProcesses();
void KillGuildWarsProcess(GuildWarsApplicationLaunchContext guildWarsApplicationLaunchContext);
Task<GuildWarsApplicationLaunchContext?> LaunchGuildwars(LaunchConfigurationWithCredentials launchConfigurationWithCredentials, CancellationToken cancellationToken);
Task<GuildWarsApplicationLaunchContext?> LaunchGuildwars(
LaunchConfigurationWithCredentials launchConfigurationWithCredentials,
CancellationToken cancellationToken
);
void RestartDaybreak();
void RestartDaybreakAsAdmin();
void RestartDaybreakAsNormalUser();
@@ -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
/// <summary>
/// Returns all running Guild Wars processes.
/// </summary>
IReadOnlyList<Process> GetGuildWarsProcesses();
Memory<Process> GetGuildWarsProcesses();
/// <summary>
/// Finds a running Guild Wars process that matches the given launch configuration.
/// Returns null if no matching process is found.
/// </summary>
GuildWarsApplicationLaunchContext? FindProcess(LaunchConfigurationWithCredentials configuration);
GuildWarsApplicationLaunchContext? FindProcess(
LaunchConfigurationWithCredentials configuration
);
/// <summary>
/// Finds all running Guild Wars processes that match any of the given launch configurations.
/// </summary>
IEnumerable<GuildWarsApplicationLaunchContext?> FindProcesses(params LaunchConfigurationWithCredentials[] configurations);
IEnumerable<GuildWarsApplicationLaunchContext?> FindProcesses(
params LaunchConfigurationWithCredentials[] configurations
);
}
@@ -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<Process> GetGuildWarsProcesses()
public Memory<Process> GetGuildWarsProcesses()
{
return Process.GetProcessesByName(ProcessName);
}
public GuildWarsApplicationLaunchContext? FindProcess(LaunchConfigurationWithCredentials configuration)
public GuildWarsApplicationLaunchContext? FindProcess(
LaunchConfigurationWithCredentials configuration
)
{
return this.FindProcesses(configuration).FirstOrDefault();
}
public IEnumerable<GuildWarsApplicationLaunchContext?> FindProcesses(params LaunchConfigurationWithCredentials[] configurations)
public IEnumerable<GuildWarsApplicationLaunchContext?> 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<ProcessEntry32>()
};
var pe32 = new ProcessEntry32 { dwSize = (uint)Marshal.SizeOf<ProcessEntry32>() };
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);
@@ -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<ScreenManagerOptions> liveUpdateableOptions,
ILogger<ScreenManager> logger) : IScreenManager, IHostedService
IOptionsMonitor<LauncherOptions> liveUpdateableLauncherOptions,
ILogger<ScreenManager> logger
) : IScreenManager, IHostedService
{
private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull();
private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull();
private readonly IOptionsMonitor<ScreenManagerOptions> liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull();
private readonly IOptionsMonitor<ScreenManagerOptions> liveUpdateableOptions =
liveUpdateableOptions.ThrowIfNull();
private readonly IOptionsMonitor<LauncherOptions> liveUpdateableLauncherOptions =
liveUpdateableLauncherOptions.ThrowIfNull();
private readonly ILogger<ScreenManager> logger = logger.ThrowIfNull();
public IEnumerable<Screen> 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<Screen>();
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<NativeMethods.MonitorInfoEx>()
};
var monitorInfo = new NativeMethods.MonitorInfoEx
{
CbSize = (uint)Marshal.SizeOf<NativeMethods.MonitorInfoEx>(),
};
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;
}
-5
View File
@@ -1,5 +0,0 @@
Write-Host "Retrieving version"
$filepath = Get-ChildItem -Path .\Daybreak -Filter *.version
$version = $filepath.BaseName
Write-Host "Version: $version"
return $version
-20
View File
@@ -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
+14
View File
@@ -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