Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 143 additions & 2 deletions src/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

using Avalonia;
using Avalonia.Controls;
Expand Down Expand Up @@ -46,6 +48,8 @@ public static void Main(string[] args)
Environment.Exit(exitTodo);
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
Environment.Exit(exitMessage);
else if (TrySendArgsToExistingInstance(args))
Environment.Exit(0);
else
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
Expand Down Expand Up @@ -77,6 +81,44 @@ public static AppBuilder BuildAvaloniaApp()
return builder;
}

private static bool TrySendArgsToExistingInstance(string[] args)
{
if (args == null || args.Length != 1 || !Directory.Exists(args[0]))
return false;

var pref = ViewModels.Preferences.Instance;

if (!pref.OpenReposInNewTab)
return false;

try
{
var processes = Process.GetProcessesByName("SourceGit");

if (processes.Length <= 1)
return false;

using var client = new NamedPipeClientStream(".", "SourceGitIPC", PipeDirection.Out);

client.Connect(1000);

if (client.IsConnected)
{
using var writer = new StreamWriter(client);

writer.WriteLine(args[0]);
writer.Flush();

return true;
}
}
catch (Exception)
{
}

return false;
}

private static void LogException(Exception ex)
{
if (ex == null)
Expand Down Expand Up @@ -328,7 +370,13 @@ public override void Initialize()
AvaloniaXamlLoader.Load(this);

var pref = ViewModels.Preferences.Instance;
pref.PropertyChanged += (_, _) => pref.Save();

pref.PropertyChanged += (s, e) => {
pref.Save();

if (e.PropertyName.Equals(nameof(ViewModels.Preferences.OpenReposInNewTab)))
HandleOpenReposInNewTabChanged();
};

SetLocale(pref.Locale);
SetTheme(pref.Theme, pref.ThemeOverrides);
Expand Down Expand Up @@ -488,13 +536,104 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
_launcher = new ViewModels.Launcher(startupRepo);
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };

#if !DISABLE_UPDATE_DETECTION
var pref = ViewModels.Preferences.Instance;

HandleOpenReposInNewTabChanged();

#if !DISABLE_UPDATE_DETECTION
if (pref.ShouldCheck4UpdateOnStartup())
Check4Update();
#endif
}

private void HandleOpenReposInNewTabChanged()
{
var pref = ViewModels.Preferences.Instance;

if (pref.OpenReposInNewTab)
{
if (_ipcServerTask == null || _ipcServerTask.IsCompleted)
{
// Start IPC server
_ipcServerCts = new CancellationTokenSource();
_ipcServerTask = Task.Run(() => StartIPCServer(_ipcServerCts.Token));
}
}
else
{
// Stop IPC server if running
if (_ipcServerCts != null && !_ipcServerCts.IsCancellationRequested)
{
_ipcServerCts.Cancel();
_ipcServerCts.Dispose();
_ipcServerCts = null;
}
_ipcServerTask = null;
}
}

private void StartIPCServer(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
using var server = new NamedPipeServerStream("SourceGitIPC", PipeDirection.In);

// Use WaitForConnectionAsync with cancellation token
try
{
Task connectionTask = server.WaitForConnectionAsync(cancellationToken);
connectionTask.Wait(cancellationToken);
}
catch (OperationCanceledException)
{
return;
}
catch (AggregateException ae) when (ae.InnerExceptions.Any(e => e is OperationCanceledException))
{
return;
}

// Process the connection
using var reader = new StreamReader(server);
var repoPath = reader.ReadLine();

if (!string.IsNullOrEmpty(repoPath) && Directory.Exists(repoPath))
{
Dispatcher.UIThread.Post(() =>
{
try
{
var test = new Commands.QueryRepositoryRootPath(repoPath).ReadToEnd();

if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
{
var repoRootPath = test.StdOut.Trim();
var pref = ViewModels.Preferences.Instance;
var node = pref.FindOrAddNodeByRepositoryPath(repoRootPath, null, false);

ViewModels.Welcome.Instance.Refresh();

_launcher?.OpenRepositoryInTab(node, null);

if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null)
desktop.MainWindow.Activate();
}
}
catch (Exception)
{
}
});
}
}
}
catch (Exception)
{
// Pipe server failed, we can just exit the thread
}
}

private void Check4Update(bool manually = false)
{
Task.Run(async () =>
Expand Down Expand Up @@ -584,5 +723,7 @@ private string FixFontFamilyName(string input)
private ResourceDictionary _activeLocale = null;
private ResourceDictionary _themeOverrides = null;
private ResourceDictionary _fontsOverrides = null;
private Task _ipcServerTask = null;
private CancellationTokenSource _ipcServerCts = null;
}
}
1 change: 1 addition & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -752,4 +752,5 @@
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Lock</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Remove</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Unlock</x:String>
<x:String x:Key="Text.Preferences.General.OpenReposInNewTab" xml:space="preserve">Open repositories in new tab instead of new window</x:String>
</ResourceDictionary>
7 changes: 7 additions & 0 deletions src/ViewModels/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ public bool OnlyUseMonoFontInEditor
}
}

public bool OpenReposInNewTab
{
get => _openReposInNewTab;
set => SetProperty(ref _openReposInNewTab, value);
}

public bool UseSystemWindowFrame
{
get => _useSystemWindowFrame;
Expand Down Expand Up @@ -632,6 +638,7 @@ private bool RemoveInvalidRepositoriesRecursive(List<RepositoryNode> collection)
private string _defaultFontFamily = string.Empty;
private string _monospaceFontFamily = string.Empty;
private bool _onlyUseMonoFontInEditor = false;
private bool _openReposInNewTab = false;
private bool _useSystemWindowFrame = false;
private double _defaultFontSize = 13;
private double _editorFontSize = 13;
Expand Down
7 changes: 6 additions & 1 deletion src/Views/Preferences.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.General}"/>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preferences.General.Locale}"
HorizontalAlignment="Right"
Expand Down Expand Up @@ -142,6 +142,11 @@
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowChildren, Mode=TwoWay}"/>

<CheckBox Grid.Row="8" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.General.OpenReposInNewTab}"
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=OpenReposInNewTab, Mode=TwoWay}"/>

<CheckBox Grid.Row="9" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.General.Check4UpdatesOnStartup}"
IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"
Expand Down