diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 0448a247..b1be7072 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -2,12 +2,14 @@ using System;
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;
@@ -46,6 +48,8 @@ namespace SourceGit
Environment.Exit(exitTodo);
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
Environment.Exit(exitMessage);
+ else if (TrySendArgsToExistingInstance(args))
+ Environment.Exit(0);
else
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
@@ -77,6 +81,44 @@ namespace SourceGit
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)
@@ -328,7 +370,13 @@ namespace SourceGit
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);
@@ -488,13 +536,104 @@ namespace SourceGit
_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 () =>
@@ -584,5 +723,7 @@ namespace SourceGit
private ResourceDictionary _activeLocale = null;
private ResourceDictionary _themeOverrides = null;
private ResourceDictionary _fontsOverrides = null;
+ private Task _ipcServerTask = null;
+ private CancellationTokenSource _ipcServerCts = null;
}
}
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 7e931998..123b6381 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -752,4 +752,5 @@
Lock
Remove
Unlock
+ Open repositories in new tab instead of new window
diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs
index cad6c224..d1e78d6a 100644
--- a/src/ViewModels/Preferences.cs
+++ b/src/ViewModels/Preferences.cs
@@ -90,6 +90,12 @@ namespace SourceGit.ViewModels
}
}
+ public bool OpenReposInNewTab
+ {
+ get => _openReposInNewTab;
+ set => SetProperty(ref _openReposInNewTab, value);
+ }
+
public bool UseSystemWindowFrame
{
get => _useSystemWindowFrame;
@@ -632,6 +638,7 @@ namespace SourceGit.ViewModels
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;
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 702ec20f..704757df 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -46,7 +46,7 @@
-
+
+
+