Custom Integrations Upgrade: Allow launching and closing based on specific games

This commit is contained in:
Cubester 2024-12-18 21:53:30 -05:00
parent 1dc31867bb
commit 9318813bdb
No known key found for this signature in database
GPG Key ID: BCAD0DEA8F81F94A
11 changed files with 218 additions and 23 deletions

View File

@ -37,6 +37,8 @@
<converters:StringFormatConverter x:Key="StringFormatConverter" />
<converters:RangeConverter x:Key="RangeConverter" />
<converters:EnumNameConverter x:Key="EnumNameConverter" />
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -426,6 +426,7 @@ namespace Bloxstrap
// launch custom integrations now
foreach (var integration in App.Settings.Prop.CustomIntegrations)
{
if (!integration.SpecifyGame) {
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
int pid = 0;
@ -451,6 +452,7 @@ namespace Bloxstrap
if (integration.AutoClose && pid != 0)
autoclosePids.Add(pid);
}
}
if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())
{

View File

@ -0,0 +1,100 @@
namespace Bloxstrap.Integrations
{
public class IntegrationWatcher : IDisposable
{
private readonly ActivityWatcher _activityWatcher;
private readonly Dictionary<int, CustomIntegration> _activeIntegrations = new();
public IntegrationWatcher(ActivityWatcher activityWatcher)
{
_activityWatcher = activityWatcher;
_activityWatcher.OnGameJoin += OnGameJoin;
_activityWatcher.OnGameLeave += OnGameLeave;
}
private void OnGameJoin(object? sender, EventArgs e)
{
if (!_activityWatcher.InGame)
return;
long currentGameId = _activityWatcher.Data.PlaceId;
foreach (var integration in App.Settings.Prop.CustomIntegrations)
{
if (!integration.SpecifyGame || integration.GameID != currentGameId.ToString())
continue;
LaunchIntegration(integration);
}
}
private void OnGameLeave(object? sender, EventArgs e)
{
foreach (var pid in _activeIntegrations.Keys.ToList())
{
var integration = _activeIntegrations[pid];
if (integration.AutoCloseOnGame)
{
TerminateProcess(pid);
_activeIntegrations.Remove(pid);
}
}
}
private void LaunchIntegration(CustomIntegration integration)
{
const string LOG_IDENT = "IntegrationWatcher::LaunchIntegration";
try
{
var process = Process.Start(new ProcessStartInfo
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
});
if (process != null)
{
App.Logger.WriteLine(LOG_IDENT, $"Integration '{integration.Name}' launched for game ID '{integration.GameID}' (PID {process.Id}).");
_activeIntegrations[process.Id] = integration;
}
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}': {ex.Message}");
}
}
private void TerminateProcess(int pid)
{
const string LOG_IDENT = "IntegrationWatcher::TerminateProcess";
try
{
var process = Process.GetProcessById(pid);
process.Kill();
App.Logger.WriteLine(LOG_IDENT, $"Terminated integration process (PID {pid}).");
}
catch (Exception)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to terminate process (PID {pid}), likely already exited.");
}
}
public void Dispose()
{
foreach (var pid in _activeIntegrations.Keys)
{
TerminateProcess(pid);
}
_activeIntegrations.Clear();
GC.SuppressFinalize(this);
}
}
}

View File

@ -5,6 +5,9 @@
public string Name { get; set; } = "";
public string Location { get; set; } = "";
public string LaunchArgs { get; set; } = "";
public bool SpecifyGame { get; set; } = false;
public string GameID { get; set; } = "";
public bool AutoCloseOnGame { get; set; } = true;
public bool AutoClose { get; set; } = true;
}
}

View File

@ -2731,6 +2731,33 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Run on specific games.
/// </summary>
public static string Menu_Integrations_Custom_SpecifyGame {
get {
return ResourceManager.GetString("Menu.Integrations.Custom.SpecifyGame", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Game ID.
/// </summary>
public static string Menu_Integrations_Custom_GameID {
get {
return ResourceManager.GetString("Menu.Integrations.Custom.GameID", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Auto close when game closes.
/// </summary>
public static string Menu_Integrations_Custom_AutoCloseOnGame {
get {
return ResourceManager.GetString("Menu.Integrations.Custom.AutoCloseOnGame", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Auto close when Roblox closes.
/// </summary>

View File

@ -689,6 +689,15 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve">
<value>Application Location</value>
</data>
<data name="Menu.Integrations.Custom.SpecifyGame" xml:space="preserve">
<value>Run on a specific game</value>
</data>
<data name="Menu.Integrations.Custom.GameID" xml:space="preserve">
<value>Game ID</value>
</data>
<data name="Menu.Integrations.Custom.AutoCloseOnGame" xml:space="preserve">
<value>Auto close when the game closes</value>
</data>
<data name="Menu.Integrations.Custom.AutoClose" xml:space="preserve">
<value>Auto close when Roblox closes</value>
</data>

View File

@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Data;
namespace Bloxstrap.UI.Converters
{
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return boolValue ? Visibility.Visible : Visibility.Collapsed;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Data;
namespace Bloxstrap.UI.Converters
{
public class InverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return boolValue ? Visibility.Collapsed : Visibility.Visible;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -110,6 +110,7 @@
<controls:MarkdownTextBlock MarkdownText="[Redusofficial](https://github.com/Redusofficial)" />
<controls:MarkdownTextBlock MarkdownText="[srthMD](https://github.com/srthMD)" />
<controls:MarkdownTextBlock MarkdownText="[axellse](https://github.com/axellse)" />
<controls:MarkdownTextBlock MarkdownText="[CubesterYT](https://github.com/CubesterYT)" />
</StackPanel>
</controls:Expander>

View File

@ -68,7 +68,7 @@
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Title}" FontSize="20" FontWeight="Medium" Margin="0,16,0,0" />
<TextBlock Text="{x:Static resources:Strings.Menu_Integrations_Custom_Description}" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Grid Margin="0,8,0,0">
<Grid Margin="0,8,0,0" MinHeight="325">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
@ -109,7 +109,11 @@
</Grid>
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:TextBox Margin="0,4,0,0" PlaceholderText="{Binding Source='/k echo {0}', Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs_Placeholder}}" Text="{Binding SelectedCustomIntegration.LaunchArgs}" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_SpecifyGame}" IsChecked="{Binding SelectedCustomIntegration.SpecifyGame, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_GameID}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ui:TextBox Margin="0,4,0,0" PlaceholderText="1818" Text="{Binding SelectedCustomIntegration.GameID}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoCloseOnGame}" IsChecked="{Binding SelectedCustomIntegration.AutoCloseOnGame, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding SelectedCustomIntegration.SpecifyGame, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
</StackPanel>
<TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Text="{x:Static resources:Strings.Menu_Integrations_Custom_NoneSelected}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Style>

View File

@ -16,6 +16,8 @@ namespace Bloxstrap
public readonly DiscordRichPresence? RichPresence;
public readonly IntegrationWatcher? IntegrationWatcher;
public Watcher()
{
const string LOG_IDENT = "Watcher";
@ -63,6 +65,8 @@ namespace Bloxstrap
if (App.Settings.Prop.UseDiscordRichPresence)
RichPresence = new(ActivityWatcher);
IntegrationWatcher = new IntegrationWatcher(ActivityWatcher);
}
_notifyIcon = new(this);
@ -122,6 +126,7 @@ namespace Bloxstrap
{
App.Logger.WriteLine("Watcher::Dispose", "Disposing Watcher");
IntegrationWatcher?.Dispose();
_notifyIcon?.Dispose();
RichPresence?.Dispose();