Compare commits

...

7 Commits

Author SHA1 Message Date
Cubester
dc637f9156
Merge d7e5912eb9 into 338ebba191 2025-03-15 09:40:27 +00:00
Matt
338ebba191
Localise more custom dialog related strings (#4881)
Some checks are pending
CI (Debug) / build (push) Waiting to run
CI (Release) / build (push) Waiting to run
CI (Release) / release (push) Blocked by required conditions
CI (Release) / release-test (push) Blocked by required conditions
* translate template comments

* localise default custom theme name
2025-03-15 00:17:23 +00:00
Cubester
d7e5912eb9
Merge branch 'main' into integrations-upgrade 2025-03-04 19:07:27 -05:00
Cubester
c78b04dce7
Merge branch 'bloxstraplabs:main' into integrations-upgrade 2025-03-02 03:31:32 -05:00
Cubester
9700c61523
Readd "Dispose" 2025-03-02 02:37:24 -05:00
Cubester
c9dabd6b4f
Merge branch 'main' into integrations-upgrade 2024-12-18 22:10:10 -05:00
Cubester
9318813bdb
Custom Integrations Upgrade: Allow launching and closing based on specific games 2024-12-18 21:53:30 -05:00
15 changed files with 301 additions and 30 deletions

View File

@ -39,6 +39,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

@ -575,30 +575,32 @@ namespace Bloxstrap
// launch custom integrations now
foreach (var integration in App.Settings.Prop.CustomIntegrations)
{
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
if (!integration.SpecifyGame) {
App.Logger.WriteLine(LOG_IDENT, $"Launching custom integration '{integration.Name}' ({integration.Location} {integration.LaunchArgs} - autoclose is {integration.AutoClose})");
int pid = 0;
int pid = 0;
try
{
var process = Process.Start(new ProcessStartInfo
try
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
})!;
var process = Process.Start(new ProcessStartInfo
{
FileName = integration.Location,
Arguments = integration.LaunchArgs.Replace("\r\n", " "),
WorkingDirectory = Path.GetDirectoryName(integration.Location),
UseShellExecute = true
})!;
pid = process.Id;
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
App.Logger.WriteLine(LOG_IDENT, ex.Message);
}
pid = process.Id;
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to launch integration '{integration.Name}'!");
App.Logger.WriteLine(LOG_IDENT, ex.Message);
}
if (integration.AutoClose && pid != 0)
autoclosePids.Add(pid);
if (integration.AutoClose && pid != 0)
autoclosePids.Add(pid);
}
}
if (App.Settings.Prop.EnableActivityTracking || App.LaunchSettings.TestModeFlag.Active || autoclosePids.Any())

View File

@ -1,10 +1,36 @@
namespace Bloxstrap.Extensions
using System.Text;
namespace Bloxstrap.Extensions
{
static class CustomThemeTemplateEx
{
const string EXAMPLES_URL = "https://github.com/bloxstraplabs/custom-bootstrapper-examples";
public static string GetFileName(this CustomThemeTemplate template)
{
return $"CustomBootstrapperTemplate_{template}.xml";
}
public static string GetFileContents(this CustomThemeTemplate template)
{
string contents = Encoding.UTF8.GetString(Resource.Get(template.GetFileName()).Result);
switch (template)
{
case CustomThemeTemplate.Blank:
{
string moreText = string.Format(Strings.CustomTheme_Templates_Blank_MoreExamples, EXAMPLES_URL);
return string.Format(contents, Strings.CustomTheme_Templates_Blank_UIElements, moreText);
}
case CustomThemeTemplate.Simple:
{
string moreText = string.Format(Strings.CustomTheme_Templates_Simple_MoreExamples, EXAMPLES_URL);
return string.Format(contents, moreText);
}
default:
Debug.Assert(false);
return contents;
}
}
}
}

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();
_activityWatcher.Dispose();
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

@ -1,4 +1,4 @@
<BloxstrapCustomBootstrapper Version="1" Height="320" Width="500">
<!-- Put UI elements here -->
<!-- Examples of custom bootstrappers can be found at https://github.com/bloxstraplabs/custom-bootstrapper-examples -->
<!-- {0} -->
<!-- {1} -->
</BloxstrapCustomBootstrapper>

View File

@ -1,5 +1,5 @@
<BloxstrapCustomBootstrapper Version="1" Height="320" Width="520" IgnoreTitleBarInset="True" Theme="Default" Margin="30">
<!-- Find more custom bootstrapper examples at https://github.com/bloxstraplabs/custom-bootstrapper-examples -->
<!-- {0} -->
<TitleBar Title="" ShowMinimize="False" ShowClose="False" />
<Image Source="{Icon}" Height="100" Width="100" HorizontalAlignment="Center" Margin="0,15,0,0" />

View File

@ -973,6 +973,15 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Custom Theme {0}.
/// </summary>
public static string CustomTheme_DefaultName {
get {
return ResourceManager.GetString("CustomTheme.DefaultName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Save changes to {0}?.
/// </summary>
@ -1271,6 +1280,33 @@ namespace Bloxstrap.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Examples of custom bootstrappers can be found at {0}.
/// </summary>
public static string CustomTheme_Templates_Blank_MoreExamples {
get {
return ResourceManager.GetString("CustomTheme.Templates.Blank.MoreExamples", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Put UI elements here.
/// </summary>
public static string CustomTheme_Templates_Blank_UIElements {
get {
return ResourceManager.GetString("CustomTheme.Templates.Blank.UIElements", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Find more custom bootstrapper examples at {0}.
/// </summary>
public static string CustomTheme_Templates_Simple_MoreExamples {
get {
return ResourceManager.GetString("CustomTheme.Templates.Simple.MoreExamples", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add Fast Flag.
/// </summary>
@ -3280,7 +3316,34 @@ namespace Bloxstrap.Resources {
return ResourceManager.GetString("Menu.Integrations.Custom.AppLocation", resourceCulture);
}
}
/// <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>
@ -1474,4 +1483,17 @@ Defaulting to {1}.</value>
<data name="Menu.Behaviour.BackgroundUpdates.Description" xml:space="preserve">
<value>Update Roblox in the background instead of waiting. Not recommended for slow networks. At least 3GB of free storage space is required for this feature to work.</value>
</data>
<data name="CustomTheme.Templates.Blank.UIElements" xml:space="preserve">
<value>Put UI elements here</value>
</data>
<data name="CustomTheme.Templates.Blank.MoreExamples" xml:space="preserve">
<value>Examples of custom bootstrappers can be found at {0}</value>
</data>
<data name="CustomTheme.Templates.Simple.MoreExamples" xml:space="preserve">
<value>Find more custom bootstrapper examples at {0}</value>
</data>
<data name="CustomTheme.DefaultName" xml:space="preserve">
<value>Custom Theme {0}</value>
<comment>{0} is a string (e.g. '1', '1-1234')</comment>
</data>
</root>

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

@ -39,11 +39,12 @@ namespace Bloxstrap.UI.Elements.Dialogs
{
int count = Directory.GetDirectories(Paths.CustomThemes).Count();
string name = $"Custom Theme {count + 1}";
int i = count + 1;
string name = string.Format(Strings.CustomTheme_DefaultName, i);
// TODO: this sucks
if (File.Exists(GetThemePath(name)))
name += " " + Random.Shared.Next(1, 100000).ToString(); // easy
name = string.Format(Strings.CustomTheme_DefaultName, $"{i}-{Random.Shared.Next(1, 100000)}"); // easy
return name;
}
@ -76,7 +77,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
string themeFilePath = Path.Combine(dir, "Theme.xml");
string templateContent = Encoding.UTF8.GetString(Resource.Get(template.GetFileName()).Result);
string templateContent = template.GetFileContents();
File.WriteAllText(themeFilePath, templateContent);
}

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

@ -9,13 +9,15 @@ namespace Bloxstrap
private readonly InterProcessLock _lock = new("Watcher");
private readonly WatcherData? _watcherData;
private readonly NotifyIconWrapper? _notifyIcon;
public readonly ActivityWatcher? ActivityWatcher;
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();