Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b08fb11a78 | ||
|
|
f5c91c10e3 | ||
|
|
223fafcf1c | ||
|
|
cbdf629b49 | ||
|
|
614e86f38a | ||
|
|
1519fc1f1a | ||
|
|
9eb31ec872 | ||
|
|
5c568e6c33 | ||
|
|
b07c56247a | ||
|
|
ad82c96d8c | ||
|
|
2bca8b345f | ||
|
|
6c2ee1bfff | ||
|
|
83ead9a1d4 | ||
|
|
3b78de25a4 | ||
|
|
e36704b2b1 | ||
|
|
1fd253ff6b | ||
|
|
3ed69a048b | ||
|
|
43ed08e0e2 | ||
|
|
e89721130b | ||
|
|
7cbcce3404 | ||
|
|
708152ee19 | ||
|
|
26317d9ed2 | ||
|
|
d4de37fd15 | ||
|
|
88fdd2fec5 | ||
|
|
3493bbcf17 | ||
|
|
7ffa71d254 | ||
|
|
aa21efb86b | ||
|
|
54548564dd | ||
|
|
1f3dcc3332 | ||
|
|
dfb2de91f9 | ||
|
|
33c2fa07b8 | ||
|
|
3bf35c4d5d | ||
|
|
912dddaa6c | ||
|
|
bad0f85f6b | ||
|
|
b46dcb193f | ||
|
|
2c0aa31be7 | ||
|
|
01a672130c | ||
|
|
a1a37d33f9 | ||
|
|
48216e342b | ||
|
|
1a442ff002 | ||
|
|
dbe4aad8ea | ||
|
|
8d9571f5d8 | ||
|
|
93939d08bb | ||
|
|
219e342315 | ||
|
|
e11556bd9a | ||
|
|
917cacb18b | ||
|
|
b154c7b5aa | ||
|
|
c8ca8e55df | ||
|
|
39cecde560 | ||
|
|
c9c029f821 | ||
|
|
ecd40f5c43 | ||
|
|
d14236d502 | ||
|
|
5eecbc6718 | ||
|
|
09e4381ea7 | ||
|
|
cc991c6761 | ||
|
|
defec12c04 | ||
|
|
ef1c5fcfc5 | ||
|
|
0d12bf6b4a | ||
|
|
2e432967af | ||
|
|
8e53a6909f | ||
|
|
38b3e4ec94 | ||
|
|
6c00c1f497 | ||
|
|
121b3d6dc1 | ||
|
|
d2eed2b4fc | ||
|
|
5decbe9b86 | ||
|
|
91ba648095 | ||
|
|
919b4930b9 | ||
|
|
fb3c76184c | ||
|
|
d72a64ce67 | ||
|
|
07248bd28d | ||
|
|
37c948c9ac | ||
|
|
98b6e99743 | ||
|
|
0ffdf236a1 | ||
|
|
3f78aff86a | ||
|
|
601cca612c | ||
|
|
901cbda824 | ||
|
|
afb50f1bdd | ||
|
|
08877785aa | ||
|
|
71282eb65d | ||
|
|
edfb890638 | ||
|
|
e88fb78e9d | ||
|
|
93e52f3209 | ||
|
|
3af29314e7 | ||
|
|
8b18773da9 | ||
|
|
8d710fe6d7 | ||
|
|
010f3fdd7e | ||
|
|
0749d9e17d | ||
|
|
8a1ebd89ad | ||
|
|
594ebdd36a | ||
|
|
90c299da83 | ||
|
|
02a695056a |
No files matched your search
@@ -23,7 +23,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
|
||||
{
|
||||
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowCreateKeyBindingDialog();
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = string.Empty;
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var title = _fixture.Create<string>();
|
||||
var content = string.Empty;
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
{
|
||||
var title = _fixture.Create<string>();
|
||||
var content = _fixture.Create<string>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
var content = _fixture.Create<string>();
|
||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||
var messageDialog = new Mock<IMessageDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
|
||||
{
|
||||
var shellProfileSelectionDialog = new Mock<IShellProfileSelectionDialog>();
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
{
|
||||
var title = "title";
|
||||
var inputDialog = new Mock<IInputDialog>();
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, () => inputDialog.Object, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, () => inputDialog.Object, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ICustomCommandDialog>, Mock.Of<ISshProfileSelectionDialog>, Mock.Of<IAboutDialog>);
|
||||
|
||||
dialogService.ShowInputDialogAsync(title);
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.10.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.12.0" />
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.13.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
|
||||
@@ -10,6 +10,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using Xunit;
|
||||
|
||||
namespace FluentTerminal.App.Services.Test
|
||||
@@ -159,7 +161,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
};
|
||||
var currentThemeChangedEventInvoked = false;
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.CurrentThemeChanged += (s, e) => currentThemeChangedEventInvoked = true;
|
||||
Messenger.Default.Register<CurrentThemeChangedMessage>(this,
|
||||
message => currentThemeChangedEventInvoked = true);
|
||||
|
||||
settingsService.SaveCurrentThemeId(currentThemeId);
|
||||
|
||||
@@ -206,7 +209,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.CurrentThemeChanged += (s, e) => currentThemeChangedEventInvoked = true;
|
||||
Messenger.Default.Register<CurrentThemeChangedMessage>(this,
|
||||
message => currentThemeChangedEventInvoked = true);
|
||||
|
||||
settingsService.SaveTheme(theme);
|
||||
|
||||
@@ -363,7 +367,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.TerminalOptionsChanged += (s, e) => terminalOptionsChangedEventInvoked = true;
|
||||
Messenger.Default.Register<TerminalOptionsChangedMessage>(this,
|
||||
message => terminalOptionsChangedEventInvoked = true);
|
||||
|
||||
settingsService.SaveTerminalOptions(terminalOptions);
|
||||
|
||||
@@ -430,7 +435,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.ApplicationSettingsChanged += (s, e) => applicationSettingsChangedEventInvoked = true;
|
||||
Messenger.Default.Register<ApplicationSettingsChangedMessage>(this,
|
||||
a => applicationSettingsChangedEventInvoked = true);
|
||||
|
||||
settingsService.SaveApplicationSettings(applicationSettings);
|
||||
|
||||
@@ -528,7 +534,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.KeyBindingsChanged += (s, e) => keyBindingsChangedEventInvoked = true;
|
||||
Messenger.Default.Register<KeyBindingsChangedMessage>(this,
|
||||
message => keyBindingsChangedEventInvoked = true);
|
||||
|
||||
settingsService.SaveKeyBindings(command.ToString(), keyBindings);
|
||||
|
||||
@@ -574,7 +581,8 @@ namespace FluentTerminal.App.Services.Test
|
||||
ShellProfiles = Mock.Of<IApplicationDataContainer>()
|
||||
};
|
||||
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
|
||||
settingsService.KeyBindingsChanged += (s, e) => keyBindingsChangedEventInvoked = true;
|
||||
Messenger.Default.Register<KeyBindingsChangedMessage>(this,
|
||||
message => keyBindingsChangedEventInvoked = true);
|
||||
|
||||
settingsService.ResetKeyBindings();
|
||||
|
||||
@@ -649,7 +657,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
var result = settingsService.GetShellProfiles();
|
||||
|
||||
result.Should().BeEquivalentTo(shellProfiles);
|
||||
result.Select(p => p.EqualTo(shellProfiles.FirstOrDefault(x => x.Id == p.Id)).Should().BeTrue());
|
||||
shellProfilesContainer.Verify(x => x.GetAll(), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
await trayProcessCommunicationService.ResizeTerminal(terminalId, terminalSize);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == ResizeTerminalRequest.Identifier)), Times.Once);
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == (byte)MessageIdentifiers.ResizeTerminalRequest)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -109,7 +109,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
await trayProcessCommunicationService.CloseTerminal(terminalId);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == TerminalExitedRequest.Identifier)), Times.Once);
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == (byte)MessageIdentifiers.TerminalExitedRequest)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,7 +121,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
var message = new ValueSet
|
||||
{
|
||||
[MessageKeys.Type] = TerminalExitedRequest.Identifier,
|
||||
[MessageKeys.Type] = (byte) MessageIdentifiers.TerminalExitedRequest,
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(request)
|
||||
};
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace FluentTerminal.App.Services.Adapters
|
||||
_applicationDataContainer.Values.Remove(key);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_applicationDataContainer.Values.Clear();
|
||||
}
|
||||
|
||||
public IEnumerable<object> GetAll()
|
||||
{
|
||||
return _applicationDataContainer.Values.Select(x => x.Value);
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
public const string SshCommandName = "ssh";
|
||||
public const string MoshCommandName = "mosh";
|
||||
public const string ExecutedCommandsContainerName = "HistoryContainer";
|
||||
public const string TerminalViewModelStateId = "TerminalViewModelState";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface IAboutDialog
|
||||
{
|
||||
Task ShowAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface ICustomCommandDialog
|
||||
{
|
||||
Task<ShellProfile> GetCustomCommandAsync(ShellProfile input = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace FluentTerminal.App.Services.EventArgs
|
||||
{
|
||||
public class NewTabRequestedEventArgs : System.EventArgs
|
||||
{
|
||||
public DragEventArgs DragEventArgs { get; set; }
|
||||
public int Position { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FluentTerminal.App.Services.Exceptions
|
||||
{
|
||||
public class SaveTextFileException : Exception
|
||||
{
|
||||
public SaveTextFileException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public SaveTextFileException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SaveTextFileException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected SaveTextFileException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.6.0" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="PListNet" Version="3.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.9" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.10" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
void Delete(string key);
|
||||
|
||||
void Clear();
|
||||
|
||||
IEnumerable<object> GetAll();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,10 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
Task<SshProfile> ShowSshConnectionInfoDialogAsync(SshProfile input = null);
|
||||
|
||||
Task<ShellProfile> ShowCustomCommandDialogAsync(ShellProfile input = null);
|
||||
|
||||
Task<SshProfile> ShowSshProfileSelectionDialogAsync();
|
||||
|
||||
Task ShowAboutDialogAsync();
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,6 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
void SendCommand(string command);
|
||||
void DeregisterCommandHandler(string command);
|
||||
void ClearCommandHandlers();
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,6 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ISettingsService
|
||||
{
|
||||
event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
|
||||
event EventHandler<Guid> CurrentThemeChanged;
|
||||
event EventHandler KeyBindingsChanged;
|
||||
event EventHandler<ShellProfile> ShellProfileAdded;
|
||||
event EventHandler<Guid> ShellProfileDeleted;
|
||||
event EventHandler<SshProfile> SshProfileAdded;
|
||||
event EventHandler<Guid> SshProfileDeleted;
|
||||
event EventHandler<TerminalOptions> TerminalOptionsChanged;
|
||||
event EventHandler<Guid> ThemeDeleted;
|
||||
event EventHandler<TerminalTheme> ThemeAdded;
|
||||
|
||||
void DeleteShellProfile(Guid id);
|
||||
void DeleteSshProfile(Guid id);
|
||||
void DeleteTheme(Guid id);
|
||||
@@ -36,12 +25,13 @@ namespace FluentTerminal.App.Services
|
||||
IEnumerable<TerminalTheme> GetThemes();
|
||||
void ResetKeyBindings();
|
||||
void SaveApplicationSettings(ApplicationSettings applicationSettings);
|
||||
void NotifyApplicationSettingsChanged(ApplicationSettings applicationSettings);
|
||||
void SaveCurrentThemeId(Guid id);
|
||||
void SaveDefaultShellProfileId(Guid id);
|
||||
void SaveDefaultSshProfileId(Guid id);
|
||||
void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings);
|
||||
void SaveShellProfile(ShellProfile shellProfile, bool newShell = false);
|
||||
void SaveSshProfile(SshProfile shellProfile, bool newShell = false);
|
||||
void SaveSshProfile(SshProfile sshProfile, bool newShell = false);
|
||||
void SaveTerminalOptions(TerminalOptions terminalOptions);
|
||||
void SaveTheme(TerminalTheme theme, bool newTheme = false);
|
||||
ShellProfile GetShellProfile(Guid id);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IShellProfileMigrationService
|
||||
{
|
||||
void Migrate(ShellProfile profile);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
|
||||
|
||||
Task<PauseTerminalOutputResponse> PauseTerminalOutput(byte id, bool pause);
|
||||
|
||||
Task ResizeTerminal(byte id, TerminalSize size);
|
||||
|
||||
Task UpdateToggleWindowKeyBindings();
|
||||
@@ -22,8 +24,9 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
void SubscribeForTerminalOutput(byte terminalId, Action<byte[]> callback);
|
||||
|
||||
void UnsubscribeFromTerminalOutput(byte terminalId);
|
||||
|
||||
Task CloseTerminal(byte terminalId);
|
||||
Task<GetAvailablePortResponse> GetAvailablePort();
|
||||
byte GetNextTerminalId();
|
||||
|
||||
Task<string> GetUserName();
|
||||
@@ -37,5 +40,9 @@ namespace FluentTerminal.App.Services
|
||||
Task<bool> CheckFileExistsAsync(string path);
|
||||
|
||||
void MuteTerminal(bool mute);
|
||||
|
||||
void UpdateSettings(ApplicationSettings settings);
|
||||
|
||||
Task<string> GetCommandPathAsync(string command);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
@@ -11,6 +12,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public ApplicationSettings GetDefaultApplicationSettings()
|
||||
{
|
||||
var logDirectoryPath = string.Empty;
|
||||
try
|
||||
{
|
||||
// Accessing ApplicationData.Current during run of unit tests causes
|
||||
// "System.InvalidOperationException : The process has no package identity" exception.
|
||||
logDirectoryPath = ApplicationData.Current.LocalCacheFolder.Path;
|
||||
} catch (InvalidOperationException) { }
|
||||
|
||||
return new ApplicationSettings
|
||||
{
|
||||
ConfirmClosingTabs = false,
|
||||
@@ -22,15 +31,16 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
CopyOnSelect = false,
|
||||
MouseMiddleClickAction = MouseAction.None,
|
||||
MouseRightClickAction = MouseAction.ContextMenu,
|
||||
AlwaysShowTabs = false,
|
||||
ShowNewOutputIndicator = false,
|
||||
EnableTrayIcon = true,
|
||||
ShowCustomTitleInTitlebar = true,
|
||||
UseMoshByDefault = true,
|
||||
AutoFallbackToWindowsUsernameInLinks = true,
|
||||
UseQuickSshConnectByDefault = false,
|
||||
RTrimCopiedLines = true,
|
||||
MuteTerminalBeeps = true
|
||||
MuteTerminalBeeps = true,
|
||||
EnableLogging = false,
|
||||
PrintableOutputOnly = true,
|
||||
LogDirectoryPath = logDirectoryPath
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,6 +100,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.DuplicateTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.DuplicateTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.D
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -138,6 +160,30 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewCustomCommandTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.NewCustomCommandTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Q
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewCustomCommandWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.NewCustomCommandWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.Q
|
||||
}
|
||||
};
|
||||
|
||||
case Command.SavedSshNewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -312,9 +358,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Default keybindings for Command '{command}' are missing");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Guid GetDefaultShellProfileId()
|
||||
@@ -389,7 +435,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
BoldText = false,
|
||||
BackgroundOpacity = 0.8,
|
||||
Padding = 12,
|
||||
ScrollBackLimit = 1000
|
||||
ScrollBackLimit = 1000,
|
||||
WordSeparator = " ()[]{}:;|│!&*<>@"&squo;"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -406,12 +453,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Id = GetDefaultShellProfileId(),
|
||||
Name = "Powershell",
|
||||
MigrationVersion = ShellProfile.CurrentMigrationVersion,
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8), // Windows 10 1903+
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
},
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
@@ -429,12 +481,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Id = Guid.Parse("ab942a61-7673-4755-9bd8-765aff91d9a3"),
|
||||
Name = "CMD",
|
||||
MigrationVersion = ShellProfile.CurrentMigrationVersion,
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\Windows\System32\cmd.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
},
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
@@ -452,12 +509,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Id= Guid.Parse("e5785ad6-584f-40cb-bdcd-d5b3b3953e7f"),
|
||||
Name = "WSL",
|
||||
MigrationVersion = ShellProfile.CurrentMigrationVersion,
|
||||
Arguments = string.Empty,
|
||||
Location = @"C:\windows\system32\wsl.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.ToLF,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
EnvironmentVariables = new Dictionary<string, string>
|
||||
{
|
||||
["TERM"] = "xterm-256color"
|
||||
},
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
|
||||
@@ -12,19 +12,25 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
||||
private readonly Func<IInputDialog> _inputDialogFactory;
|
||||
private readonly Func<ISshConnectionInfoDialog> _sshConnectionInfoDialogFactory;
|
||||
private readonly Func<ICustomCommandDialog> _customCommandDialogFactory;
|
||||
private readonly Func<ISshProfileSelectionDialog> _sshProfileSelectionDialogFactory;
|
||||
private readonly Func<IAboutDialog> _aboutDialogFactory;
|
||||
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory,
|
||||
Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory,
|
||||
Func<IInputDialog> inputDialogFactory, Func<ISshConnectionInfoDialog> sshConnectionInfoDialogFactory,
|
||||
Func<ISshProfileSelectionDialog> sshProfileSelectionDialogFactory)
|
||||
Func<ICustomCommandDialog> customCommandDialogFactory,
|
||||
Func<ISshProfileSelectionDialog> sshProfileSelectionDialogFactory,
|
||||
Func<IAboutDialog> aboutDialogFactory)
|
||||
{
|
||||
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
||||
_messageDialogFactory = messageDialogFactory;
|
||||
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
||||
_inputDialogFactory = inputDialogFactory;
|
||||
_sshConnectionInfoDialogFactory = sshConnectionInfoDialogFactory;
|
||||
_customCommandDialogFactory = customCommandDialogFactory;
|
||||
_sshProfileSelectionDialogFactory = sshProfileSelectionDialogFactory;
|
||||
_aboutDialogFactory = aboutDialogFactory;
|
||||
}
|
||||
|
||||
public Task<KeyBinding> ShowCreateKeyBindingDialog()
|
||||
@@ -81,11 +87,21 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public Task<SshProfile> ShowSshConnectionInfoDialogAsync(SshProfile input = null) =>
|
||||
_sshConnectionInfoDialogFactory().GetSshConnectionInfoAsync(input);
|
||||
|
||||
public Task<ShellProfile> ShowCustomCommandDialogAsync(ShellProfile input = null) =>
|
||||
_customCommandDialogFactory().GetCustomCommandAsync(input);
|
||||
|
||||
public Task<SshProfile> ShowSshProfileSelectionDialogAsync()
|
||||
{
|
||||
var dialog = _sshProfileSelectionDialogFactory();
|
||||
|
||||
return dialog.SelectProfile();
|
||||
}
|
||||
|
||||
public Task ShowAboutDialogAsync()
|
||||
{
|
||||
var dialog = _aboutDialogFactory();
|
||||
|
||||
return dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
public const string RedComponent = "Red Component";
|
||||
}
|
||||
|
||||
public async Task<TerminalTheme> Parse(string fileName, Stream fileContent)
|
||||
public Task<TerminalTheme> Parse(string fileName, Stream fileContent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
@@ -65,13 +65,13 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
var node = PList.Load(fileContent) as DictionaryNode ?? throw new ParseThemeException("Root node was not a dictionary.");
|
||||
|
||||
return new TerminalTheme
|
||||
return Task.FromResult(new TerminalTheme
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(fileName),
|
||||
Colors = GetColors(node),
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private TerminalColors GetColors(DictionaryNode themeDictionary)
|
||||
@@ -83,6 +83,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Cursor = GetColorString(themeDictionary[ITermThemeKeys.CursorColor]),
|
||||
CursorAccent = GetColorString(themeDictionary[ITermThemeKeys.CursorTextColor]),
|
||||
Selection = GetColorString(themeDictionary[ITermThemeKeys.SelectionColor], Opacity30Percent),
|
||||
SelectionForeground = GetColorString(themeDictionary[ITermThemeKeys.SelectedTextColor]),
|
||||
SelectionBackground = GetColorString(themeDictionary[ITermThemeKeys.SelectionColor]),
|
||||
Black = GetColorString(themeDictionary[ITermThemeKeys.Ansi0Color]),
|
||||
Red = GetColorString(themeDictionary[ITermThemeKeys.Ansi1Color]),
|
||||
Green = GetColorString(themeDictionary[ITermThemeKeys.Ansi2Color]),
|
||||
@@ -110,8 +112,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
var green = dictionaryNode[ITermThemeColorKeys.GreenComponent] as RealNode ?? throw new ParseThemeException("Green node value was not a real number");
|
||||
var blue = dictionaryNode[ITermThemeColorKeys.BlueComponent] as RealNode ?? throw new ParseThemeException("Blue node value was not a real number");
|
||||
|
||||
var color = $"#{alpha:X2}{GetByteValue(red):X2}{GetByteValue(green):X2}{GetByteValue(blue):X2}";
|
||||
|
||||
if (alpha == byte.MaxValue)
|
||||
{
|
||||
return $"#{GetByteValue(red):X2}{GetByteValue(green):X2}{GetByteValue(blue):X2}";
|
||||
|
||||
@@ -42,5 +42,10 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
throw new KeyNotFoundException($"No handler registered for command: {command}");
|
||||
}
|
||||
|
||||
public void ClearCommandHandlers()
|
||||
{
|
||||
_commandHandlers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
@@ -146,33 +148,17 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
SaveTerminalOptions(config.TerminalOptions);
|
||||
}
|
||||
|
||||
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
|
||||
|
||||
public event EventHandler<TerminalTheme> ThemeAdded;
|
||||
public event EventHandler<Guid> CurrentThemeChanged;
|
||||
public event EventHandler<Guid> ThemeDeleted;
|
||||
|
||||
public event EventHandler KeyBindingsChanged;
|
||||
|
||||
public event EventHandler<ShellProfile> ShellProfileAdded;
|
||||
public event EventHandler<Guid> ShellProfileDeleted;
|
||||
|
||||
public event EventHandler<SshProfile> SshProfileAdded;
|
||||
public event EventHandler<Guid> SshProfileDeleted;
|
||||
|
||||
public event EventHandler<TerminalOptions> TerminalOptionsChanged;
|
||||
|
||||
public void DeleteShellProfile(Guid id)
|
||||
{
|
||||
_shellProfiles.Delete(id.ToString());
|
||||
ShellProfileDeleted?.Invoke(this, id);
|
||||
Messenger.Default.Send(new ShellProfileDeletedMessage(id));
|
||||
}
|
||||
|
||||
public void DeleteSshProfile(Guid id)
|
||||
{
|
||||
_sshProfiles.Delete(id.ToString());
|
||||
SshProfileDeleted?.Invoke(this, id);
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
Messenger.Default.Send(new SshProfileDeletedMessage(id));
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +175,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
ThemeDeleted?.Invoke(this, id);
|
||||
Messenger.Default.Send(new ThemeDeletedMessage(id));
|
||||
}
|
||||
|
||||
public ApplicationSettings GetApplicationSettings()
|
||||
@@ -280,13 +266,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
return _shellProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<ShellProfile>((string)x)).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<SshProfile> GetSshProfiles()
|
||||
{
|
||||
if (_sshProfiles == null)
|
||||
{
|
||||
return new List<SshProfile>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return _sshProfiles.GetAll()
|
||||
.Select(x => JsonConvert.DeserializeObject<SshProfile>((string) x)).ToList();
|
||||
.Select(x => JsonConvert.DeserializeObject<SshProfile>((string)x)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TabTheme> GetTabThemes()
|
||||
@@ -316,20 +307,25 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_keyBindings.WriteValueAsJson(command.ToString(), _defaultValueProvider.GetDefaultKeyBindings(command));
|
||||
}
|
||||
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
}
|
||||
|
||||
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
|
||||
{
|
||||
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
|
||||
ApplicationSettingsChanged?.Invoke(this, applicationSettings);
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings));
|
||||
}
|
||||
|
||||
public void NotifyApplicationSettingsChanged(ApplicationSettings applicationSettings)
|
||||
{
|
||||
Messenger.Default.Send(new ApplicationSettingsChangedMessage(applicationSettings));
|
||||
}
|
||||
|
||||
public void SaveCurrentThemeId(Guid id)
|
||||
{
|
||||
_roamingSettings.SetValue(CurrentThemeKey, id);
|
||||
|
||||
CurrentThemeChanged?.Invoke(this, id);
|
||||
Messenger.Default.Send(new CurrentThemeChangedMessage(id));
|
||||
}
|
||||
|
||||
public void SaveDefaultShellProfileId(Guid id)
|
||||
@@ -349,7 +345,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
|
||||
_keyBindings.WriteValueAsJson(enumValue.ToString(), keyBindings);
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
}
|
||||
|
||||
public void SaveShellProfile(ShellProfile shellProfile, bool newShell = false)
|
||||
@@ -357,11 +353,11 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
|
||||
|
||||
// When saving the shell profile, we also need to update keybindings for everywhere.
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
|
||||
if (newShell)
|
||||
{
|
||||
ShellProfileAdded?.Invoke(this, shellProfile);
|
||||
Messenger.Default.Send(new ShellProfileAddedMessage(shellProfile));
|
||||
}
|
||||
}
|
||||
public void SaveSshProfile(SshProfile sshProfile, bool newShell = false)
|
||||
@@ -369,18 +365,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_sshProfiles.WriteValueAsJson(sshProfile.Id.ToString(), sshProfile);
|
||||
|
||||
// When saving the shell profile, we also need to update keybindings for everywhere.
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
Messenger.Default.Send(new KeyBindingsChangedMessage());
|
||||
|
||||
if (newShell)
|
||||
{
|
||||
SshProfileAdded?.Invoke(this, sshProfile);
|
||||
Messenger.Default.Send(new SshProfileAddedMessage(sshProfile));
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveTerminalOptions(TerminalOptions terminalOptions)
|
||||
{
|
||||
_roamingSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
|
||||
TerminalOptionsChanged?.Invoke(this, terminalOptions);
|
||||
Messenger.Default.Send(new TerminalOptionsChangedMessage(terminalOptions));
|
||||
}
|
||||
|
||||
public void SaveTheme(TerminalTheme theme, bool newTheme = false)
|
||||
@@ -389,12 +385,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
if (theme.Id == GetCurrentThemeId())
|
||||
{
|
||||
CurrentThemeChanged?.Invoke(this, theme.Id);
|
||||
Messenger.Default.Send(new CurrentThemeChangedMessage(theme.Id));
|
||||
}
|
||||
|
||||
if (newTheme)
|
||||
{
|
||||
ThemeAdded?.Invoke(this, theme);
|
||||
Messenger.Default.Send(new ThemeAddedMessage(theme));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using FluentTerminal.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class ShellProfileMigrationService : IShellProfileMigrationService
|
||||
{
|
||||
private readonly Dictionary<int, Action<ShellProfile>> _migrationSteps = new Dictionary<int, Action<ShellProfile>>
|
||||
{
|
||||
[1] = new Action<ShellProfile>(profile =>
|
||||
{
|
||||
profile.EnvironmentVariables["TERM"] = "xterm-256color";
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
public void Migrate(ShellProfile profile)
|
||||
{
|
||||
while(profile.MigrationVersion < ShellProfile.CurrentMigrationVersion)
|
||||
{
|
||||
ApplyMigrationStep(profile, profile.MigrationVersion + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMigrationStep(ShellProfile profile, int targetVersion)
|
||||
{
|
||||
_migrationSteps[targetVersion](profile);
|
||||
profile.MigrationVersion = targetVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public class ThemeParserFactory : IThemeParserFactory
|
||||
{
|
||||
private IEnumerable<IThemeParser> _parsers;
|
||||
private readonly IEnumerable<IThemeParser> _parsers;
|
||||
|
||||
public IEnumerable<string> SupportedFileTypes { get; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.App.Services.Exceptions;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Requests;
|
||||
using FluentTerminal.Models.Responses;
|
||||
@@ -30,18 +31,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _nextTerminalId++;
|
||||
}
|
||||
|
||||
public async Task<GetAvailablePortResponse> GetAvailablePort()
|
||||
{
|
||||
var request = new GetAvailablePortRequest();
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response = JsonConvert.DeserializeObject<GetAvailablePortResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
|
||||
Logger.Instance.Debug("Received GetAvailablePortResponse: {@response}", response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private string _userName;
|
||||
|
||||
public async Task<string> GetUserName()
|
||||
@@ -52,13 +41,13 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _userName;
|
||||
}
|
||||
|
||||
GetUserNameResponse response;
|
||||
StringValueResponse response;
|
||||
|
||||
// No need to crash for username, so try/catch
|
||||
try
|
||||
{
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(new GetUserNameRequest()));
|
||||
response = JsonConvert.DeserializeObject<GetUserNameResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
response = JsonConvert.DeserializeObject<StringValueResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -69,7 +58,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
Logger.Instance.Debug("Received GetUserNameResponse: {@response}", response);
|
||||
|
||||
_userName = response.UserName;
|
||||
_userName = response.Value;
|
||||
|
||||
return _userName;
|
||||
}
|
||||
@@ -82,7 +71,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
throw new Exception(string.IsNullOrEmpty(response.Error) ? "Failed to save the file." : response.Error);
|
||||
throw new SaveTextFileException(string.IsNullOrEmpty(response.Error) ? "Failed to save the file." : response.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +132,11 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_appServiceConnection.SendMessageAsync(CreateMessage(new MuteTerminalRequest { Mute = mute }));
|
||||
}
|
||||
|
||||
public void UpdateSettings(ApplicationSettings settings)
|
||||
{
|
||||
_appServiceConnection.SendMessageAsync(CreateMessage(new UpdateSettingsRequest { Settings = settings }));
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
|
||||
{
|
||||
var request = new CreateTerminalRequest
|
||||
@@ -163,6 +157,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<PauseTerminalOutputResponse> PauseTerminalOutput(byte id, bool pause)
|
||||
{
|
||||
var request = new PauseTerminalOutputRequest
|
||||
{
|
||||
Id = id,
|
||||
Pause = pause
|
||||
};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
return JsonConvert.DeserializeObject<PauseTerminalOutputResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
}
|
||||
|
||||
public void Initialize(IAppServiceConnection appServiceConnection)
|
||||
{
|
||||
_appServiceConnection = appServiceConnection;
|
||||
@@ -188,7 +194,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Logger.Instance.Error("Received output for unknown terminal Id {id}", terminalId);
|
||||
}
|
||||
break;
|
||||
case TerminalExitedRequest.Identifier:
|
||||
case (byte) MessageIdentifiers.TerminalExitedRequest:
|
||||
var request = JsonConvert.DeserializeObject<TerminalExitedRequest>((string)messageContent);
|
||||
Logger.Instance.Debug("Received TerminalExitedRequest: {@request}", request);
|
||||
|
||||
@@ -216,6 +222,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_terminalOutputHandlers[terminalId] = callback;
|
||||
}
|
||||
|
||||
public void UnsubscribeFromTerminalOutput(byte id)
|
||||
{
|
||||
if (_terminalOutputHandlers.ContainsKey(id))
|
||||
{
|
||||
_terminalOutputHandlers.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateToggleWindowKeyBindings()
|
||||
{
|
||||
var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)];
|
||||
@@ -249,6 +263,49 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> CommandPaths = new Dictionary<string, string>();
|
||||
|
||||
private static readonly Dictionary<string, string> CommandErrors = new Dictionary<string, string>();
|
||||
|
||||
public async Task<string> GetCommandPathAsync(string command)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
{
|
||||
throw new ArgumentException("Input value is null or empty.", nameof(command));
|
||||
}
|
||||
|
||||
command = command.Trim();
|
||||
|
||||
var commandLower = command.ToLowerInvariant();
|
||||
|
||||
if (CommandPaths.TryGetValue(commandLower, out var path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if (CommandErrors.TryGetValue(commandLower, out var error))
|
||||
{
|
||||
throw new Exception(error);
|
||||
}
|
||||
|
||||
var request = new GetCommandPathRequest {Command = command};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response =
|
||||
JsonConvert.DeserializeObject<StringValueResponse>((string) responseMessage[MessageKeys.Content]);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
CommandPaths[commandLower] = response.Value;
|
||||
|
||||
return response.Value;
|
||||
}
|
||||
|
||||
CommandErrors[commandLower] = response.Error;
|
||||
|
||||
throw new Exception(response.Error);
|
||||
}
|
||||
|
||||
private ValueSet CreateMessage(IMessage content)
|
||||
{
|
||||
return new ValueSet
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Responses;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
@@ -10,14 +11,16 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private Func<Task<string>> _selectedTextCallback;
|
||||
private bool _closingFromUI = false;
|
||||
private bool _exited = false;
|
||||
private bool _closingFromUI;
|
||||
private bool _exited;
|
||||
private readonly bool _requireShellProcessStart;
|
||||
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService, byte? terminalId = null)
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
|
||||
Id = _trayProcessCommunicationService.GetNextTerminalId();
|
||||
Id = terminalId ?? trayProcessCommunicationService.GetNextTerminalId();
|
||||
_requireShellProcessStart = !terminalId.HasValue;
|
||||
}
|
||||
|
||||
private void OnTerminalExited(object sender, TerminalExitStatus status)
|
||||
@@ -30,10 +33,12 @@ namespace FluentTerminal.App.Services
|
||||
_exited = true;
|
||||
Exited?.Invoke(this, status.ExitCode);
|
||||
|
||||
if (_closingFromUI == true || status.ExitCode <= 0)
|
||||
if (_closingFromUI || status.ExitCode <= 0)
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
_trayProcessCommunicationService.TerminalExited -= OnTerminalExited;
|
||||
}
|
||||
|
||||
public event EventHandler<int> Exited;
|
||||
@@ -78,6 +83,7 @@ namespace FluentTerminal.App.Services
|
||||
return;
|
||||
}
|
||||
_closingFromUI = true;
|
||||
_trayProcessCommunicationService.UnsubscribeFromTerminalOutput(Id);
|
||||
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ namespace FluentTerminal.App.Services
|
||||
/// <returns></returns>
|
||||
public Task<string> GetSelectedText()
|
||||
{
|
||||
return _selectedTextCallback();
|
||||
return _selectedTextCallback?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,17 +139,30 @@ namespace FluentTerminal.App.Services
|
||||
/// <param name="shellProfile"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="sessionType"></param>
|
||||
public async Task<CreateTerminalResponse> StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
||||
public async Task<TerminalResponse> StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState)
|
||||
{
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
if (!_requireShellProcessStart && !string.IsNullOrEmpty(termState))
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
OutputReceived?.Invoke(this, Encoding.UTF8.GetBytes(termState));
|
||||
}
|
||||
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||
|
||||
if (_requireShellProcessStart)
|
||||
{
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
return await _trayProcessCommunicationService.PauseTerminalOutput(Id, false);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public Task Write(byte[] data)
|
||||
|
||||
@@ -13,5 +13,12 @@ namespace FluentTerminal.App.Services.Utilities
|
||||
var resourceLoader = ResourceLoader.GetForCurrentView();
|
||||
return resourceLoader.GetString(resource.Replace('.', '/'));
|
||||
}
|
||||
|
||||
public static string TranslateWithFallback(string resource, string fallback)
|
||||
{
|
||||
var result = Translate(resource);
|
||||
|
||||
return string.IsNullOrEmpty(result) ? fallback : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class EnvironmentVariableViewModel : ViewModelBase
|
||||
{
|
||||
private string _name;
|
||||
private string _value;
|
||||
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => Set(ref _name, value);
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get => _value;
|
||||
set => Set(ref _value, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.1.0" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public interface ISessionSuccessTracker
|
||||
{
|
||||
void SetSuccessfulSessionStart();
|
||||
|
||||
void SetOutputReceived();
|
||||
|
||||
void SetInvalid();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Views
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public interface ITerminalView
|
||||
{
|
||||
@@ -10,8 +9,10 @@ namespace FluentTerminal.App.Views
|
||||
Task ChangeKeyBindings();
|
||||
Task ChangeOptions(TerminalOptions options);
|
||||
Task Initialize(TerminalViewModel viewModel);
|
||||
void DisposalPrepare();
|
||||
Task FindNext(string searchText);
|
||||
Task FindPrevious(string searchText);
|
||||
Task FocusTerminal();
|
||||
Task<string> SerializeXtermState();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace FluentTerminal.App.ViewModels.Infrastructure
|
||||
bool CanExecute();
|
||||
}
|
||||
|
||||
public interface IAsyncCommand<T> : ICommand
|
||||
public interface IAsyncCommand<in T> : ICommand
|
||||
{
|
||||
Task ExecuteAsync(T parameter);
|
||||
bool CanExecute(T parameter);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
@@ -10,6 +11,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.Models.Messages;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
@@ -31,14 +33,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
||||
{
|
||||
MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
|
||||
MessengerInstance.Register<CurrentThemeChangedMessage>(this, OnCurrentThemeChanged);
|
||||
MessengerInstance.Register<ShellProfileAddedMessage>(this, OnShellProfileAdded);
|
||||
MessengerInstance.Register<ShellProfileDeletedMessage>(this, OnShellProfileDeleted);
|
||||
MessengerInstance.Register<SshProfileAddedMessage>(this, OnSshProfileAdded);
|
||||
MessengerInstance.Register<SshProfileDeletedMessage>(this, OnSshProfileDeleted);
|
||||
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
|
||||
|
||||
_settingsService = settingsService;
|
||||
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
_settingsService.ShellProfileAdded += OnShellProfileAdded;
|
||||
_settingsService.ShellProfileDeleted += OnShellProfileDeleted;
|
||||
_settingsService.SshProfileAdded += OnSshProfileAdded;
|
||||
_settingsService.SshProfileDeleted += OnSshProfileDeleted;
|
||||
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_dialogService = dialogService;
|
||||
@@ -48,12 +51,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), async () => await AddLocalTabAsync());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewSshTab), async () => await AddSshTabAsync());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewCustomCommandTab), async () => await AddCustomCommandTabAsync());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), async () => await AddConfigurableTerminal());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), async () => await SelectedTerminal.EditTitle());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.SavedSshNewTab), async () => await AddSavedSshTerminalAsync());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.SavedSshNewWindow), () => NewWindow(NewWindowAction.ShowSshProfileSelection));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewSshWindow), () => NewWindow(NewWindowAction.ShowSshInfoDialog));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewCustomCommandWindow), () => NewWindow(NewWindowAction.ShowCustomCommandDialog));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.DuplicateTab), async () => await AddTerminalAsync(SelectedTerminal.ShellProfile.Clone()));
|
||||
|
||||
// Add all of the commands for switching to a tab of a given ID, if there's one open there
|
||||
for (int i = 0; i < 9; i++)
|
||||
@@ -92,8 +98,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
TabsPosition = _applicationSettings.TabsPosition;
|
||||
|
||||
AddLocalShellCommand = new RelayCommand(async () => await AddLocalTabAsync());
|
||||
AddRemoteShellCommand = new RelayCommand(async () => await AddSshTabAsync());
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
AddSshShellCommand = new RelayCommand(async () => await AddSshTabAsync());
|
||||
AddQuickShellCommand = new RelayCommand(async () => await AddCustomCommandTabAsync());
|
||||
ShowAboutCommand = new AsyncCommand(ShowAbout);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
|
||||
ApplicationView.CloseRequested += OnCloseRequest;
|
||||
@@ -103,26 +110,44 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void OnClosed(object sender, EventArgs e)
|
||||
{
|
||||
MessengerInstance.Unregister(this);
|
||||
|
||||
ApplicationView.CloseRequested -= OnCloseRequest;
|
||||
ApplicationView.Closed -= OnClosed;
|
||||
Terminals.CollectionChanged -= OnTerminalsCollectionChanged;
|
||||
|
||||
_keyboardCommandService.ClearCommandHandlers();
|
||||
|
||||
_applicationSettings = null;
|
||||
|
||||
AddLocalShellCommand = null;
|
||||
AddSshShellCommand = null;
|
||||
AddQuickShellCommand = null;
|
||||
ShowAboutCommand = null;
|
||||
ShowSettingsCommand = null;
|
||||
|
||||
Closed?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnShellProfileDeleted(object sender, Guid e)
|
||||
private void OnShellProfileDeleted(ShellProfileDeletedMessage message)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
_keyboardCommandService.DeregisterCommandHandler(message.ProfileId.ToString());
|
||||
}
|
||||
|
||||
private void OnShellProfileAdded(object sender, ShellProfile e)
|
||||
private void OnShellProfileAdded(ShellProfileAddedMessage message)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddLocalTabOrWindowAsync(e.Id));
|
||||
_keyboardCommandService.RegisterCommandHandler(message.ShellProfile.Id.ToString(),
|
||||
() => AddLocalTabOrWindowAsync(message.ShellProfile.Id));
|
||||
}
|
||||
|
||||
private void OnSshProfileAdded(object sender, ShellProfile e)
|
||||
private void OnSshProfileAdded(SshProfileAddedMessage message)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddSshTabOrWindowAsync(e.Id));
|
||||
_keyboardCommandService.RegisterCommandHandler(message.SshProfile.Id.ToString(),
|
||||
() => AddSshTabOrWindowAsync(message.SshProfile.Id));
|
||||
}
|
||||
private void OnSshProfileDeleted(object sender, Guid e)
|
||||
private void OnSshProfileDeleted(SshProfileDeletedMessage message)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
_keyboardCommandService.DeregisterCommandHandler(message.ProfileId.ToString());
|
||||
}
|
||||
|
||||
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -137,17 +162,23 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public event EventHandler ShowSettingsRequested;
|
||||
|
||||
public event EventHandler ShowAboutRequested;
|
||||
|
||||
public event EventHandler ActivatedMv;
|
||||
|
||||
public event EventHandler<TerminalViewModel> TabTearedOff;
|
||||
|
||||
public void TearOffTab(TerminalViewModel model)
|
||||
{
|
||||
TabTearedOff?.Invoke(this, model);
|
||||
}
|
||||
|
||||
public void FocusWindow()
|
||||
{
|
||||
ActivatedMv?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public RelayCommand AddLocalShellCommand { get; }
|
||||
public RelayCommand AddRemoteShellCommand { get; }
|
||||
public RelayCommand AddLocalShellCommand { get; private set; }
|
||||
public RelayCommand AddSshShellCommand { get; private set; }
|
||||
public RelayCommand AddQuickShellCommand { get; private set; }
|
||||
|
||||
public string WindowTitle
|
||||
{
|
||||
@@ -163,12 +194,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public bool ShowTabsOnTop
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Top && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
get => TabsPosition == TabsPosition.Top;
|
||||
}
|
||||
|
||||
public bool ShowTabsOnBottom
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Bottom && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
get => TabsPosition == TabsPosition.Bottom;
|
||||
}
|
||||
|
||||
public string Background
|
||||
@@ -211,9 +242,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
public IAsyncCommand ShowAboutCommand { get; private set; }
|
||||
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
public RelayCommand ShowSettingsCommand { get; private set; }
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
{
|
||||
@@ -259,6 +290,23 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddCustomCommandTabAsync()
|
||||
{
|
||||
var profile = await _dialogService.ShowCustomCommandDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// User selected "Cancel"
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
await ApplicationView.TryClose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddTerminalAsync(profile);
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddSshTabOrWindowAsync(Guid shellProfileId)
|
||||
{
|
||||
switch (_applicationSettings.NewTerminalLocation)
|
||||
@@ -313,22 +361,85 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddTerminalAsync(ShellProfile profile)
|
||||
public Task AddTerminalAsync(ShellProfile profile, string terminalState, int position)
|
||||
{
|
||||
return ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
||||
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService);
|
||||
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService, terminalState);
|
||||
|
||||
terminal.Closed += OnTerminalClosed;
|
||||
terminal.ShellTitleChanged += Terminal_ShellTitleChanged;
|
||||
terminal.CustomTitleChanged += Terminal_CustomTitleChanged;
|
||||
Terminals.Add(terminal);
|
||||
terminal.CloseLeftTabsRequested += Terminal_CloseLeftTabsRequested;
|
||||
terminal.CloseRightTabsRequested += Terminal_CloseRightTabsRequested;
|
||||
terminal.CloseOtherTabsRequested += Terminal_CloseOtherTabsRequested;
|
||||
terminal.DuplicateTabRequested += Terminal_DuplicateTabRequested;
|
||||
Terminals.Insert(Math.Min(position, Terminals.Count), terminal);
|
||||
|
||||
SelectedTerminal = terminal;
|
||||
});
|
||||
}
|
||||
|
||||
private async void Terminal_DuplicateTabRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
await AddTerminalAsync(terminal.ShellProfile.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_CloseOtherTabsRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
Array.ForEach<TerminalViewModel>(Terminals.ToArray(),
|
||||
async t => {
|
||||
if (terminal != t)
|
||||
{
|
||||
Logger.Instance.Debug("Terminal with Id: {@id} closed.", t.Terminal.Id);
|
||||
await t.CloseCommand.ExecuteAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void Terminal_CloseRightTabsRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
for (int i = Terminals.Count - 1; i > Terminals.IndexOf(terminal); --i)
|
||||
{
|
||||
var terminalToRemove = Terminals[i];
|
||||
Logger.Instance.Debug("Terminal with Id: {@id} closed.", terminalToRemove.Terminal.Id);
|
||||
await terminalToRemove.CloseCommand.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Terminal_CloseLeftTabsRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
for(int i = Terminals.IndexOf(terminal) - 1; i >= 0; --i)
|
||||
{
|
||||
var terminalToRemove = Terminals[i];
|
||||
Logger.Instance.Debug("Terminal with Id: {@id} closed.", terminalToRemove.Terminal.Id);
|
||||
await terminalToRemove.CloseCommand.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddTerminalAsync(ShellProfile profile)
|
||||
{
|
||||
return AddTerminalAsync(profile, "", Terminals.Count);
|
||||
}
|
||||
|
||||
public Task AddTerminalAsync(string terminalState, int position)
|
||||
{
|
||||
return AddTerminalAsync(new ShellProfile(), terminalState, position);
|
||||
}
|
||||
|
||||
private void Terminal_CustomTitleChanged(object sender, string e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
@@ -362,13 +473,17 @@ namespace FluentTerminal.App.ViewModels
|
||||
terminal.Closed -= OnTerminalClosed;
|
||||
terminal.ShellTitleChanged -= Terminal_ShellTitleChanged;
|
||||
terminal.CustomTitleChanged -= Terminal_CustomTitleChanged;
|
||||
terminal.CloseLeftTabsRequested -= Terminal_CloseLeftTabsRequested;
|
||||
terminal.CloseRightTabsRequested -= Terminal_CloseRightTabsRequested;
|
||||
terminal.CloseOtherTabsRequested -= Terminal_CloseOtherTabsRequested;
|
||||
terminal.DuplicateTabRequested -= Terminal_DuplicateTabRequested;
|
||||
await terminal.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseCurrentTab()
|
||||
{
|
||||
SelectedTerminal?.CloseCommand.Execute(null);
|
||||
SelectedTerminal?.CloseCommand.ExecuteAsync();
|
||||
}
|
||||
|
||||
private void NewWindow(NewWindowAction showSelection)
|
||||
@@ -386,16 +501,16 @@ namespace FluentTerminal.App.ViewModels
|
||||
NewWindowRequested?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
private async void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
_applicationSettings = e;
|
||||
TabsPosition = e.TabsPosition;
|
||||
_applicationSettings = message.ApplicationSettings;
|
||||
TabsPosition = message.ApplicationSettings.TabsPosition;
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
RaisePropertyChanged(nameof(ShowTabsOnBottom));
|
||||
|
||||
if (e.ShowCustomTitleInTitlebar)
|
||||
if (message.ApplicationSettings.ShowCustomTitleInTitlebar)
|
||||
{
|
||||
WindowTitle = SelectedTerminal?.TabTitle;
|
||||
}
|
||||
@@ -427,13 +542,13 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnCurrentThemeChanged(object sender, Guid e)
|
||||
private async void OnCurrentThemeChanged(CurrentThemeChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var currentTheme = _settingsService.GetTheme(e);
|
||||
Background = currentTheme.Colors.Background;
|
||||
});
|
||||
{
|
||||
var currentTheme = _settingsService.GetTheme(message.ThemeId);
|
||||
Background = currentTheme.Colors.Background;
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnTerminalClosed(object sender, EventArgs e)
|
||||
@@ -445,6 +560,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
|
||||
}
|
||||
Logger.Instance.Debug("Terminal with Id: {@id} closed.", terminal.Terminal.Id);
|
||||
|
||||
terminal.Closed -= OnTerminalClosed;
|
||||
terminal.ShellTitleChanged -= Terminal_ShellTitleChanged;
|
||||
terminal.CustomTitleChanged -= Terminal_CustomTitleChanged;
|
||||
terminal.CloseLeftTabsRequested -= Terminal_CloseLeftTabsRequested;
|
||||
terminal.CloseRightTabsRequested -= Terminal_CloseRightTabsRequested;
|
||||
terminal.CloseOtherTabsRequested -= Terminal_CloseOtherTabsRequested;
|
||||
terminal.DuplicateTabRequested -= Terminal_DuplicateTabRequested;
|
||||
|
||||
Terminals.Remove(terminal);
|
||||
|
||||
if (Terminals.Count == 0)
|
||||
@@ -454,11 +578,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
private async void OnTerminalOptionsChanged(TerminalOptionsChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
BackgroundOpacity = e.BackgroundOpacity;
|
||||
BackgroundOpacity = message.TerminalOptions.BackgroundOpacity;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -484,9 +608,9 @@ namespace FluentTerminal.App.ViewModels
|
||||
SelectedTerminal = Terminals[previousIndex];
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
private Task ShowAbout()
|
||||
{
|
||||
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
|
||||
return _dialogService.ShowAboutDialogAsync();
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
_overlayTimer = dispatcherTimer;
|
||||
_overlayTimer.Interval = new TimeSpan(0, 0, 2);
|
||||
_overlayTimer.Tick += OnResizeOverlayTimerFinished;
|
||||
}
|
||||
|
||||
public bool ShowOverlay
|
||||
@@ -37,13 +36,16 @@ namespace FluentTerminal.App.ViewModels
|
||||
if (_overlayTimer.IsEnabled)
|
||||
{
|
||||
_overlayTimer.Stop();
|
||||
_overlayTimer.Tick -= OnResizeOverlayTimerFinished;
|
||||
}
|
||||
_overlayTimer.Start();
|
||||
_overlayTimer.Tick += OnResizeOverlayTimerFinished;
|
||||
}
|
||||
|
||||
private void OnResizeOverlayTimerFinished(object sender, object e)
|
||||
{
|
||||
_overlayTimer.Stop();
|
||||
_overlayTimer.Tick -= OnResizeOverlayTimerFinished;
|
||||
ShowOverlay = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
public class ExecutedCommand
|
||||
{
|
||||
public string Command { get; set; }
|
||||
|
||||
public string Args { get; set; }
|
||||
|
||||
public DateTime LastExecution { get; set; }
|
||||
|
||||
public int ExecutionCount { get; set; }
|
||||
}
|
||||
|
||||
public class ExecutedCommandHistory
|
||||
{
|
||||
public List<ExecutedCommand> ExecutedCommands { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
public CommonProfileProviderViewModel(ISettingsService settingsService, IApplicationView applicationView,
|
||||
IFileSystemService fileSystemService, ShellProfile original = null) : base(settingsService, applicationView,
|
||||
original)
|
||||
true, original)
|
||||
{
|
||||
_fileSystemService = fileSystemService;
|
||||
|
||||
@@ -88,9 +88,9 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
Initialize(profile);
|
||||
}
|
||||
|
||||
protected override void CopyToProfile(ShellProfile profile)
|
||||
protected override async Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
base.CopyToProfile(profile);
|
||||
await base.CopyToProfileAsync(profile);
|
||||
|
||||
profile.Location = _location;
|
||||
profile.Arguments = _arguments;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
public class ExecutedCommand
|
||||
{
|
||||
public string Value { get; set; }
|
||||
|
||||
public bool IsProfile { get; set; }
|
||||
|
||||
public DateTime LastExecution { get; set; }
|
||||
|
||||
public int ExecutionCount { get; set; }
|
||||
|
||||
public ShellProfile ShellProfile { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Web;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Profiles
|
||||
@@ -28,6 +29,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
protected readonly ISettingsService SettingsService;
|
||||
protected readonly IApplicationView ApplicationView;
|
||||
private readonly bool _strictProfileType;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
@@ -42,7 +44,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (ReferenceEquals(_model, value))
|
||||
@@ -50,7 +52,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return;
|
||||
}
|
||||
|
||||
if (_model != null && _model.GetType() != value.GetType())
|
||||
if (_strictProfileType && _model != null && _model.GetType() != value.GetType())
|
||||
{
|
||||
throw new ArgumentException($"The value has to be of type {_model.GetType().Name}.");
|
||||
}
|
||||
@@ -60,6 +62,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
LoadFromProfile(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
|
||||
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
|
||||
@@ -174,10 +177,11 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
#region Constructor
|
||||
|
||||
protected ProfileProviderViewModelBase(ISettingsService settingsService, IApplicationView applicationView,
|
||||
ShellProfile original = null)
|
||||
bool strictProfileType, ShellProfile original = null)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
ApplicationView = applicationView;
|
||||
_strictProfileType = strictProfileType;
|
||||
|
||||
_model = original ?? new ShellProfile();
|
||||
|
||||
@@ -187,12 +191,12 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
TerminalThemes = new ObservableCollection<TerminalTheme>(settingsService.GetThemes());
|
||||
|
||||
TerminalThemes.Insert(0, new TerminalTheme { Id = Guid.Empty, Name = "Default" });
|
||||
TerminalThemes.Insert(0, new TerminalTheme {Id = Guid.Empty, Name = "Default"});
|
||||
|
||||
SelectedTerminalTheme = TerminalThemes.First();
|
||||
|
||||
settingsService.ThemeAdded += OnThemeAdded;
|
||||
settingsService.ThemeDeleted += OnThemeDeleted;
|
||||
MessengerInstance.Register<ThemeAddedMessage>(this, OnThemeAdded);
|
||||
MessengerInstance.Register<ThemeDeletedMessage>(this, OnThemeDeleted);
|
||||
|
||||
Initialize(Model);
|
||||
}
|
||||
@@ -201,29 +205,29 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
#region Methods
|
||||
|
||||
private void OnThemeDeleted(object sender, Guid e)
|
||||
private void OnThemeDeleted(ThemeDeletedMessage message)
|
||||
{
|
||||
var theme = TerminalThemes.FirstOrDefault(t => t.Id.Equals(message.ThemeId));
|
||||
|
||||
if (theme == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var theme = TerminalThemes.FirstOrDefault(t => t.Id.Equals(e));
|
||||
|
||||
if (theme == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TerminalThemes.Remove(theme);
|
||||
|
||||
if (_terminalThemeId.Equals(e))
|
||||
if (_terminalThemeId.Equals(message.ThemeId))
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.First();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
private void OnThemeAdded(object sender, TerminalTheme e)
|
||||
private void OnThemeAdded(ThemeAddedMessage message)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => TerminalThemes.Add(e), false);
|
||||
ApplicationView.RunOnDispatcherThread(() => TerminalThemes.Add(message.Theme), false);
|
||||
}
|
||||
|
||||
private void Initialize(ShellProfile profile)
|
||||
@@ -239,12 +243,13 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
Initialize(profile);
|
||||
}
|
||||
|
||||
protected virtual void CopyToProfile(ShellProfile profile)
|
||||
protected virtual Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
profile.LineEndingTranslation = _lineEndingTranslation;
|
||||
profile.UseConPty = _useConPty;
|
||||
profile.TerminalThemeId = _terminalThemeId;
|
||||
profile.TabThemeId = _tabThemeId;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task<string> ValidateAsync()
|
||||
@@ -279,7 +284,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
if (acceptIfInvalid || string.IsNullOrEmpty(error))
|
||||
{
|
||||
CopyToProfile(Model);
|
||||
await CopyToProfileAsync(Model);
|
||||
}
|
||||
|
||||
return error;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -21,11 +20,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
#region Static
|
||||
|
||||
private static readonly Regex CommandValidationRx = new Regex(@"^(?<cmd>[^\s\.]+)(\.exe)?(\s+(?<args>\S.*))?$",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly string[] AcceptableCommands = { Constants.SshCommandName };
|
||||
|
||||
public static string GetErrorString(SshConnectionInfoValidationResult result, string separator = "; ") =>
|
||||
string.Join(separator, GetErrors(result));
|
||||
|
||||
@@ -52,9 +46,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private readonly IApplicationDataContainer _historyContainer;
|
||||
|
||||
private bool _commandInputOriginal;
|
||||
|
||||
// To prevent validating the existence of the same file multiple times because it's kinda expensive
|
||||
private string _validatedIdentityFile;
|
||||
@@ -63,34 +54,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
#region Properties
|
||||
|
||||
private bool _commandInput;
|
||||
|
||||
public bool CommandInput
|
||||
{
|
||||
get => _commandInput;
|
||||
set
|
||||
{
|
||||
if (Set(ref _commandInput, value) && _useMosh)
|
||||
{
|
||||
RaisePropertyChanged(nameof(MoshVisible));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Quick SSH properties
|
||||
|
||||
private string _command;
|
||||
|
||||
public string Command
|
||||
{
|
||||
get => _command;
|
||||
set => Set(ref _command, value);
|
||||
}
|
||||
|
||||
#endregion Quick SSH properties
|
||||
|
||||
#region Full UI properties
|
||||
|
||||
private string _host;
|
||||
|
||||
public string Host
|
||||
@@ -128,13 +91,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
public bool UseMosh
|
||||
{
|
||||
get => _useMosh;
|
||||
set
|
||||
{
|
||||
if (Set(ref _useMosh, value) && !_commandInput)
|
||||
{
|
||||
RaisePropertyChanged(nameof(MoshVisible));
|
||||
}
|
||||
}
|
||||
set => Set(ref _useMosh, value);
|
||||
}
|
||||
|
||||
private ushort _moshPortFrom;
|
||||
@@ -153,12 +110,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
set => Set(ref _moshPortTo, value);
|
||||
}
|
||||
|
||||
#endregion Full UI properties
|
||||
|
||||
public bool MoshVisible => _useMosh && !_commandInput;
|
||||
|
||||
public ObservableCollection<string> CommandHistory { get; private set; }
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
@@ -167,66 +118,28 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
#endregion Commands
|
||||
|
||||
#region Constructors
|
||||
#region Constructor
|
||||
|
||||
public SshConnectViewModel(ISettingsService settingsService, IApplicationView applicationView,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IFileSystemService fileSystemService,
|
||||
IApplicationDataContainer historyContainer, SshProfile original = null) : base(settingsService, applicationView,
|
||||
SshProfile original = null) : base(settingsService, applicationView, true,
|
||||
original ?? new SshProfile { UseMosh = settingsService.GetApplicationSettings().UseMoshByDefault })
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_historyContainer = historyContainer;
|
||||
|
||||
if (string.IsNullOrEmpty(Model.Location))
|
||||
{
|
||||
_commandInput = settingsService.GetApplicationSettings().UseQuickSshConnectByDefault;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(((SshProfile)Model).Host))
|
||||
{
|
||||
_commandInput = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandInput = false;
|
||||
}
|
||||
|
||||
_commandInputOriginal = _commandInput;
|
||||
|
||||
FillCommandHistory();
|
||||
|
||||
Initialize((SshProfile)Model);
|
||||
|
||||
BrowseForIdentityFileCommand = new AsyncCommand(BrowseForIdentityFile);
|
||||
}
|
||||
|
||||
private SshConnectViewModel(ISettingsService settingsService, IApplicationView applicationView,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IFileSystemService fileSystemService,
|
||||
IApplicationDataContainer historyContainer, bool useCommandInput) : base(settingsService, applicationView,
|
||||
new SshProfile { UseMosh = settingsService.GetApplicationSettings().UseMoshByDefault })
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_historyContainer = historyContainer;
|
||||
_commandInput = useCommandInput;
|
||||
_commandInputOriginal = useCommandInput;
|
||||
|
||||
FillCommandHistory();
|
||||
|
||||
Initialize((SshProfile)Model);
|
||||
|
||||
BrowseForIdentityFileCommand = new AsyncCommand(BrowseForIdentityFile);
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
#endregion Constructor
|
||||
|
||||
#region Methods
|
||||
|
||||
// Fills the view model properties from the input sshProfile
|
||||
private void Initialize(SshProfile sshProfile)
|
||||
{
|
||||
Command = string.IsNullOrEmpty(sshProfile.Location) ? string.Empty : $"{sshProfile.Location} {sshProfile.Arguments}";
|
||||
|
||||
Host = sshProfile.Host;
|
||||
SshPort = sshProfile.SshPort;
|
||||
|
||||
@@ -282,29 +195,15 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
{
|
||||
base.LoadFromProfile(profile);
|
||||
|
||||
CommandInput = _commandInputOriginal;
|
||||
|
||||
Initialize((SshProfile)profile);
|
||||
}
|
||||
|
||||
protected override void CopyToProfile(ShellProfile profile)
|
||||
protected override async Task CopyToProfileAsync(ShellProfile profile)
|
||||
{
|
||||
base.CopyToProfile(profile);
|
||||
await base.CopyToProfileAsync(profile);
|
||||
|
||||
_commandInputOriginal = _commandInput;
|
||||
SshProfile sshProfile = (SshProfile) profile;
|
||||
|
||||
if (_commandInput)
|
||||
{
|
||||
CopyToProfileQuick((SshProfile)profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyToProfileFull((SshProfile)profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToProfileFull(SshProfile sshProfile)
|
||||
{
|
||||
sshProfile.Location = _useMosh ? Constants.MoshCommandName : Constants.SshCommandName;
|
||||
sshProfile.Arguments = GetArgumentsString();
|
||||
|
||||
@@ -319,46 +218,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
sshProfile.MoshPortTo = _moshPortTo;
|
||||
}
|
||||
|
||||
private void CopyToProfileQuick(SshProfile sshProfile)
|
||||
{
|
||||
var command = _command?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
sshProfile.Location = null;
|
||||
sshProfile.Arguments = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var match = CommandValidationRx.Match(command);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
// Should not happen ever because this method gets called only if validation succeeds.
|
||||
throw new Exception("Invalid command.");
|
||||
}
|
||||
|
||||
sshProfile.Location = match.Groups["cmd"].Value;
|
||||
sshProfile.Arguments = match.Groups["args"].Success ? match.Groups["args"].Value.Trim() : null;
|
||||
|
||||
sshProfile.WorkingDirectory = null;
|
||||
|
||||
sshProfile.Host = null;
|
||||
sshProfile.SshPort = 0;
|
||||
sshProfile.Username = null;
|
||||
sshProfile.IdentityFile = null;
|
||||
sshProfile.UseMosh = false;
|
||||
sshProfile.MoshPortFrom = 0;
|
||||
sshProfile.MoshPortTo = 0;
|
||||
}
|
||||
|
||||
public override Task<string> ValidateAsync()
|
||||
{
|
||||
return _commandInput ? ValidateQuickAsync() : ValidateFullAsync();
|
||||
}
|
||||
|
||||
private async Task<string> ValidateFullAsync()
|
||||
public override async Task<string> ValidateAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
|
||||
@@ -384,56 +244,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return error;
|
||||
}
|
||||
|
||||
private async Task<string> ValidateQuickAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var match = CommandValidationRx.Match(_command?.Trim() ?? string.Empty);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
error = I18N.Translate("InvalidCommand");
|
||||
|
||||
return string.IsNullOrEmpty(error) ? "Invalid command." : error;
|
||||
}
|
||||
|
||||
if (!AcceptableCommands.Any(c => c.Equals(match.Groups["cmd"].Value, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
error = I18N.Translate("UnsupportedCommand");
|
||||
|
||||
return string.IsNullOrEmpty(error)
|
||||
? $"Unsupported command: {match.Groups["cmd"]}."
|
||||
: $"{error} {match.Groups["cmd"]}";
|
||||
}
|
||||
|
||||
if (!match.Groups["args"].Success)
|
||||
{
|
||||
error = I18N.Translate("CommandArgumentsMandatory");
|
||||
|
||||
return string.IsNullOrEmpty(error) ? "Command arguments are missing." : error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool HasChanges()
|
||||
{
|
||||
// ReSharper disable once ArrangeRedundantParentheses
|
||||
if (base.HasChanges() || (_commandInput != _commandInputOriginal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_commandInput)
|
||||
{
|
||||
return !_command.NullableEqualTo($"{Model.Location} {Model.Arguments}");
|
||||
}
|
||||
|
||||
var original = (SshProfile)Model;
|
||||
|
||||
return base.HasChanges() || !original.Host.NullableEqualTo(_host) || original.SshPort != _sshPort ||
|
||||
@@ -533,63 +345,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
|
||||
#endregion Methods
|
||||
|
||||
#region Command history
|
||||
|
||||
private const string ExecutedCommandsKey = "ExecutedCommands";
|
||||
private const int CommandHistoryLimit = 20;
|
||||
|
||||
private void FillCommandHistory()
|
||||
{
|
||||
var commands = _historyContainer.ReadValueFromJson<ExecutedCommandHistory>(ExecutedCommandsKey, null)
|
||||
?.ExecutedCommands;
|
||||
|
||||
CommandHistory = commands == null
|
||||
? new ObservableCollection<string>()
|
||||
: new ObservableCollection<string>(commands.Select(c => $"{c.Command} {c.Args}"));
|
||||
}
|
||||
|
||||
public void SaveCommand(string cmd, string args)
|
||||
{
|
||||
cmd = cmd?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(cmd))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cmd));
|
||||
}
|
||||
|
||||
var commandHistory =
|
||||
_historyContainer.ReadValueFromJson<ExecutedCommandHistory>(ExecutedCommandsKey, null) ??
|
||||
new ExecutedCommandHistory { ExecutedCommands = new List<ExecutedCommand>() };
|
||||
|
||||
var command = commandHistory.ExecutedCommands.FirstOrDefault(c =>
|
||||
string.Equals(cmd, c.Command, StringComparison.Ordinal) && args.NullableEqualTo(c.Args));
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
command = new ExecutedCommand
|
||||
{ Command = cmd, Args = args, ExecutionCount = 1, LastExecution = DateTime.UtcNow };
|
||||
}
|
||||
else
|
||||
{
|
||||
command.ExecutionCount++;
|
||||
|
||||
command.LastExecution = DateTime.UtcNow;
|
||||
|
||||
commandHistory.ExecutedCommands.Remove(command);
|
||||
}
|
||||
|
||||
commandHistory.ExecutedCommands.Insert(0, command);
|
||||
|
||||
while (commandHistory.ExecutedCommands.Count > CommandHistoryLimit)
|
||||
{
|
||||
commandHistory.ExecutedCommands.RemoveAt(CommandHistoryLimit);
|
||||
}
|
||||
|
||||
_historyContainer.WriteValueAsJson(ExecutedCommandsKey, commandHistory);
|
||||
}
|
||||
|
||||
#endregion Command history
|
||||
|
||||
#region Links/shortcuts related
|
||||
|
||||
// Resources:
|
||||
@@ -599,11 +354,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
private const string SshUriScheme = "ssh";
|
||||
private const string MoshUriScheme = "mosh";
|
||||
|
||||
private const string CustomSshUriScheme = "sshft";
|
||||
private const string CustomUriHost = "fluent.terminal";
|
||||
private const string CustomCommandQueryStringName = "cmd";
|
||||
private const string CustomArgumentsQueryStringName = "args";
|
||||
|
||||
// Constant derived from https://man.openbsd.org/ssh
|
||||
private const string IdentityFileOptionName = "IdentityFile";
|
||||
|
||||
@@ -612,41 +362,16 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
private static readonly Regex MoshRangeRx =
|
||||
new Regex(@"^(?<from>\d{1,5})[:-](?<to>\d{1,5})$", RegexOptions.Compiled);
|
||||
|
||||
public static bool CheckScheme(Uri uri) => CheckSchemeStandard(uri) || CheckSchemeCustom(uri);
|
||||
|
||||
private static bool CheckSchemeStandard(Uri uri) =>
|
||||
public static bool CheckScheme(Uri uri) =>
|
||||
SshUriScheme.Equals(uri?.Scheme, StringComparison.OrdinalIgnoreCase) ||
|
||||
MoshUriScheme.Equals(uri?.Scheme, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool CheckSchemeCustom(Uri uri) =>
|
||||
CustomSshUriScheme.Equals(uri?.Scheme, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static SshConnectViewModel ParseUri(Uri uri, ISettingsService settingsService,
|
||||
IApplicationView applicationView, ITrayProcessCommunicationService trayProcessCommunicationService,
|
||||
IFileSystemService fileSystemService, IApplicationDataContainer historyContainer)
|
||||
{
|
||||
if (CheckSchemeStandard(uri))
|
||||
{
|
||||
return ParseUriStandard(uri, settingsService, applicationView, trayProcessCommunicationService,
|
||||
fileSystemService, historyContainer);
|
||||
}
|
||||
|
||||
if (CheckSchemeCustom(uri))
|
||||
{
|
||||
return ParseUriCustom(uri, settingsService, applicationView, trayProcessCommunicationService,
|
||||
fileSystemService, historyContainer);
|
||||
}
|
||||
|
||||
// Won't happen ever
|
||||
throw new Exception($"Unsupported URI scheme: {uri.Scheme}");
|
||||
}
|
||||
|
||||
private static SshConnectViewModel ParseUriStandard(Uri uri, ISettingsService settingsService,
|
||||
IApplicationView applicationView, ITrayProcessCommunicationService trayProcessCommunicationService,
|
||||
IFileSystemService fileSystemService, IApplicationDataContainer historyContainer)
|
||||
{
|
||||
var vm = new SshConnectViewModel(settingsService, applicationView, trayProcessCommunicationService,
|
||||
fileSystemService, historyContainer, false)
|
||||
fileSystemService)
|
||||
{
|
||||
Host = uri.Host,
|
||||
UseMosh = MoshUriScheme.Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase)
|
||||
@@ -708,49 +433,7 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static SshConnectViewModel ParseUriCustom(Uri uri, ISettingsService settingsService,
|
||||
IApplicationView applicationView, ITrayProcessCommunicationService trayProcessCommunicationService,
|
||||
IFileSystemService fileSystemService, IApplicationDataContainer historyContainer)
|
||||
{
|
||||
var vm = new SshConnectViewModel(settingsService, applicationView, trayProcessCommunicationService,
|
||||
fileSystemService, historyContainer, true);
|
||||
|
||||
// ReSharper disable once ConstantConditionalAccessQualifier
|
||||
string queryString = uri.Query?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(queryString))
|
||||
{
|
||||
return vm;
|
||||
}
|
||||
|
||||
if (queryString.StartsWith("?", StringComparison.Ordinal))
|
||||
{
|
||||
queryString = queryString.Substring(1);
|
||||
}
|
||||
|
||||
var queryStringParams = ParseParams(queryString, '&').ToList();
|
||||
|
||||
var cmdParam = queryStringParams.FirstOrDefault(t =>
|
||||
CustomCommandQueryStringName.Equals(t.Item1, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var argsParam = queryStringParams.FirstOrDefault(t =>
|
||||
CustomArgumentsQueryStringName.Equals(t.Item1, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
vm._command =
|
||||
$"{(string.IsNullOrEmpty(cmdParam?.Item2) ? Constants.SshCommandName : cmdParam.Item2)} {argsParam?.Item2}"
|
||||
.Trim();
|
||||
|
||||
vm.LoadBaseFromQueryString(queryStringParams);
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
public override Task<Tuple<bool, string>> GetUrlAsync()
|
||||
{
|
||||
return _commandInput ? GetUrlQuickAsync() : GetUrlFullAsync();
|
||||
}
|
||||
|
||||
private async Task<Tuple<bool, string>> GetUrlFullAsync()
|
||||
public override async Task<Tuple<bool, string>> GetUrlAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
|
||||
@@ -833,28 +516,6 @@ namespace FluentTerminal.App.ViewModels.Profiles
|
||||
return Tuple.Create(true, sb.ToString());
|
||||
}
|
||||
|
||||
private async Task<Tuple<bool, string>> GetUrlQuickAsync()
|
||||
{
|
||||
var error = await base.ValidateAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
return Tuple.Create(false, error);
|
||||
}
|
||||
|
||||
var match = CommandValidationRx.Match(_command);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
error = I18N.Translate("InvalidCommand");
|
||||
|
||||
return Tuple.Create(false, string.IsNullOrEmpty(error) ? "Invalid command." : error);
|
||||
}
|
||||
|
||||
return Tuple.Create(true,
|
||||
$"{CustomSshUriScheme}://{CustomUriHost}/?{CustomCommandQueryStringName}={Constants.SshCommandName}&{CustomArgumentsQueryStringName}={HttpUtility.UrlEncode(match.Groups["args"].Value.Trim())}&{GetBaseQueryString()}");
|
||||
}
|
||||
|
||||
#endregion Links/shortcuts related
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public class AboutPageViewModel : ViewModelBase
|
||||
{
|
||||
private const string BaseUrl = "https://github.com/felixse/FluentTerminal/releases/tag/";
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IUpdateService _updateService;
|
||||
private string _latestVersion;
|
||||
private readonly IApplicationView _applicationView;
|
||||
|
||||
public AboutPageViewModel(ISettingsService settingsService, IUpdateService updateService, IApplicationView applicationView)
|
||||
public AboutPageViewModel(IUpdateService updateService, IApplicationView applicationView)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_updateService = updateService;
|
||||
_applicationView = applicationView;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
@@ -23,9 +24,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private bool _needsToRestart;
|
||||
private readonly IApplicationLanguageService _applicationLanguageService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
|
||||
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider,
|
||||
IStartupTaskService startupTaskService, IApplicationLanguageService applicationLanguageService, ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
IStartupTaskService startupTaskService, IApplicationLanguageService applicationLanguageService,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IFileSystemService fileSystemService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
@@ -33,10 +36,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_startupTaskService = startupTaskService;
|
||||
_applicationLanguageService = applicationLanguageService;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_fileSystemService = fileSystemService;
|
||||
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
|
||||
BrowseLogDirectoryCommand = new AsyncCommand(BrowseLogDirectory);
|
||||
}
|
||||
|
||||
public IEnumerable<string> Languages => _applicationLanguageService.Languages;
|
||||
@@ -63,20 +68,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
SetStartupTaskPropertiesForStatus(startupTaskStatus);
|
||||
}
|
||||
|
||||
public bool AlwaysShowTabs
|
||||
{
|
||||
get => _applicationSettings.AlwaysShowTabs;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.AlwaysShowTabs != value)
|
||||
{
|
||||
_applicationSettings.AlwaysShowTabs = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowCustomTitleInTitlebar
|
||||
{
|
||||
get => _applicationSettings.ShowCustomTitleInTitlebar;
|
||||
@@ -156,20 +147,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseQuickSshConnectByDefault
|
||||
{
|
||||
get => _applicationSettings.UseQuickSshConnectByDefault;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.UseQuickSshConnectByDefault != value)
|
||||
{
|
||||
_applicationSettings.UseQuickSshConnectByDefault = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RTrimCopiedLines
|
||||
{
|
||||
get => _applicationSettings.RTrimCopiedLines;
|
||||
@@ -199,6 +176,47 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool EnableLogging
|
||||
{
|
||||
get => _applicationSettings.EnableLogging;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.EnableLogging != value)
|
||||
{
|
||||
_applicationSettings.EnableLogging = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool PrintableOutputOnly
|
||||
{
|
||||
get => _applicationSettings.PrintableOutputOnly;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.PrintableOutputOnly != value)
|
||||
{
|
||||
_applicationSettings.PrintableOutputOnly = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string LogDirectoryPath
|
||||
{
|
||||
get => _applicationSettings.LogDirectoryPath;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.LogDirectoryPath != value)
|
||||
{
|
||||
_applicationSettings.LogDirectoryPath = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool BottomIsSelected
|
||||
{
|
||||
@@ -254,7 +272,9 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand RestoreDefaultsCommand { get; }
|
||||
public IAsyncCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
public IAsyncCommand BrowseLogDirectoryCommand { get; }
|
||||
|
||||
public bool StartupTaskEnabled
|
||||
{
|
||||
@@ -360,7 +380,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
UnderlineSelectedTab = defaults.UnderlineSelectedTab;
|
||||
InactiveTabColorMode = defaults.InactiveTabColorMode;
|
||||
NewTerminalLocation = defaults.NewTerminalLocation;
|
||||
AlwaysShowTabs = defaults.AlwaysShowTabs;
|
||||
ShowNewOutputIndicator = defaults.ShowNewOutputIndicator;
|
||||
EnableTrayIcon = defaults.EnableTrayIcon;
|
||||
ShowCustomTitleInTitlebar = defaults.ShowCustomTitleInTitlebar;
|
||||
@@ -368,6 +387,9 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
AutoFallbackToWindowsUsernameInLinks = defaults.AutoFallbackToWindowsUsernameInLinks;
|
||||
RTrimCopiedLines = defaults.RTrimCopiedLines;
|
||||
MuteTerminalBeeps = defaults.MuteTerminalBeeps;
|
||||
EnableLogging = defaults.EnableLogging;
|
||||
PrintableOutputOnly = defaults.PrintableOutputOnly;
|
||||
LogDirectoryPath = defaults.LogDirectoryPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,5 +437,15 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
SetStartupTaskPropertiesForStatus(status);
|
||||
}
|
||||
|
||||
private async Task BrowseLogDirectory()
|
||||
{
|
||||
var folder = await _fileSystemService.BrowseForDirectory();
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
LogDirectoryPath = folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,14 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
public class KeyBindingsPageViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private IDictionary<string, ICollection<KeyBinding>> _keyBindings;
|
||||
|
||||
public KeyBindingsPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
public KeyBindingsPageViewModel(ISettingsService settingsService, IDialogService dialogService, ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
AddCommand = new RelayCommand<string>(async command => await Add(command).ConfigureAwait(false));
|
||||
@@ -54,15 +51,13 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
private void Initialize(IDictionary<string, ICollection<KeyBinding>> keyBindings)
|
||||
{
|
||||
_keyBindings = keyBindings;
|
||||
|
||||
ClearKeyBindings();
|
||||
|
||||
foreach (var value in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
var command = (Command)value;
|
||||
var viewModel = new KeyBindingsViewModel(command.ToString(), _dialogService, I18N.Translate($"{nameof(Command)}.{command}"), true);
|
||||
foreach (var keyBinding in _keyBindings[command.ToString()])
|
||||
foreach (var keyBinding in keyBindings[command.ToString()])
|
||||
{
|
||||
viewModel.Add(keyBinding);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
cloned.Id = Guid.NewGuid();
|
||||
cloned.PreInstalled = false;
|
||||
cloned.Name = $"Copy of {shellProfile.Name}";
|
||||
cloned.KeyBindings = new List<KeyBinding>();
|
||||
|
||||
AddShellProfile(cloned);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
cloned.Id = Guid.NewGuid();
|
||||
cloned.PreInstalled = false;
|
||||
cloned.Name = $"Copy of {shellProfile.Name}";
|
||||
cloned.KeyBindings = new List<KeyBinding>();
|
||||
|
||||
foreach (KeyBinding keyBinding in cloned.KeyBindings)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
@@ -114,7 +115,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _terminalOptions.ScrollBackLimit.ToString();
|
||||
set
|
||||
{
|
||||
var oldValue = _terminalOptions.ScrollBackLimit;
|
||||
if (uint.TryParse(value, out uint intValue))
|
||||
{
|
||||
if (intValue == uint.MinValue)
|
||||
@@ -135,6 +135,21 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public string WordSeparator
|
||||
{
|
||||
get => WebViewSpecialCharEncoder.Decode(_terminalOptions.WordSeparator);
|
||||
set
|
||||
{
|
||||
value = WebViewSpecialCharEncoder.Encode(value);
|
||||
if (_terminalOptions.WordSeparator != value)
|
||||
{
|
||||
_terminalOptions.WordSeparator = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool BoldText
|
||||
{
|
||||
get => _terminalOptions.BoldText;
|
||||
@@ -227,6 +242,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
BackgroundOpacity = defaults.BackgroundOpacity;
|
||||
ScrollBackLimit = defaults.ScrollBackLimit.ToString();
|
||||
ShowTextCopied = defaults.ShowTextCopied;
|
||||
WordSeparator = defaults.WordSeparator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +255,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
|
||||
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s.Name);
|
||||
Sizes = Enumerable.Range(1, 72);
|
||||
Sizes = Enumerable.Range(2, 72);
|
||||
|
||||
_terminalOptions = _settingsService.GetTerminalOptions();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.Models.Messages;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
@@ -31,10 +34,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
_fileSystemService = fileSystemService;
|
||||
|
||||
CreateThemeCommand = new RelayCommand(CreateTheme);
|
||||
ImportThemeCommand = new RelayCommand(ImportTheme);
|
||||
ImportThemeCommand = new AsyncCommand(ImportTheme);
|
||||
CloneCommand = new RelayCommand<ThemeViewModel>(CloneTheme);
|
||||
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
|
||||
|
||||
BackgroundOpacity = _settingsService.GetTerminalOptions().BackgroundOpacity;
|
||||
|
||||
@@ -56,7 +59,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
|
||||
public RelayCommand CreateThemeCommand { get; }
|
||||
public RelayCommand ImportThemeCommand { get; }
|
||||
public IAsyncCommand ImportThemeCommand { get; }
|
||||
public RelayCommand<ThemeViewModel> CloneCommand { get; set; }
|
||||
|
||||
public double BackgroundOpacity
|
||||
@@ -110,7 +113,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
AddTheme(theme);
|
||||
}
|
||||
|
||||
private async void ImportTheme()
|
||||
private async Task ImportTheme()
|
||||
{
|
||||
var file = await _fileSystemService.OpenFile(_themeParserFactory.SupportedFileTypes).ConfigureAwait(true);
|
||||
if (file != null)
|
||||
@@ -180,9 +183,9 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
private void OnTerminalOptionsChanged(TerminalOptionsChangedMessage message)
|
||||
{
|
||||
BackgroundOpacity = e.BackgroundOpacity;
|
||||
BackgroundOpacity = message.TerminalOptions.BackgroundOpacity;
|
||||
}
|
||||
|
||||
private void OnSelectedThemeBackgroundChanged(object sender, string e)
|
||||
|
||||
@@ -9,12 +9,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, IThemeParserFactory themeParserFactory, ISystemFontService systemFontService,
|
||||
IFileSystemService fileSystemService, IStartupTaskService startupTaskService, IUpdateService updateService, IApplicationView applicationView,
|
||||
IFileSystemService fileSystemService, IStartupTaskService startupTaskService, IApplicationView applicationView,
|
||||
IApplicationLanguageService applicationLanguageService, ApplicationDataContainers containers)
|
||||
{
|
||||
About = new AboutPageViewModel(settingsService, updateService, applicationView);
|
||||
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, defaultValueProvider, trayProcessCommunicationService);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService, applicationLanguageService, trayProcessCommunicationService);
|
||||
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, trayProcessCommunicationService);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService, applicationLanguageService, trayProcessCommunicationService, fileSystemService);
|
||||
Profiles = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
|
||||
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider, systemFontService);
|
||||
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService);
|
||||
@@ -24,7 +23,6 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
|
||||
public event EventHandler Closed;
|
||||
public event EventHandler AboutPageRequested;
|
||||
|
||||
public GeneralPageViewModel General { get; }
|
||||
public KeyBindingsPageViewModel KeyBindings { get; }
|
||||
@@ -32,14 +30,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
public TerminalPageViewModel Terminal { get; }
|
||||
public ThemesPageViewModel Themes { get; }
|
||||
public MousePageViewModel Mouse { get; }
|
||||
public AboutPageViewModel About { get; }
|
||||
public SshProfilesPageViewModel SshProfiles { get; }
|
||||
|
||||
public void NavigateToAboutPage()
|
||||
{
|
||||
AboutPageRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
@@ -11,9 +11,9 @@ using FluentTerminal.App.ViewModels.Profiles;
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="ShellProfileViewModelBase{T}"/>, and implements only "Set as default" logic.
|
||||
/// Extends <see cref="ProfileViewModelBase{T}"/>, and implements only "Set as default" logic.
|
||||
/// </summary>
|
||||
public class ShellProfileViewModel : ShellProfileViewModelBase<CommonProfileProviderViewModel>
|
||||
public class ShellProfileViewModel : ProfileViewModelBase<CommonProfileProviderViewModel>
|
||||
{
|
||||
#region Fields
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.App.Services;
|
||||
@@ -14,10 +15,10 @@ using GalaSoft.MvvmLight.Command;
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all shell profile view models. Implements logic for saving, editing, resetting, etc.
|
||||
/// Base class for all profile view models. Implements logic for saving, editing, resetting, etc.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class ShellProfileViewModelBase<T> : ViewModelBase where T : ProfileProviderViewModelBase
|
||||
public abstract class ProfileViewModelBase<T> : ViewModelBase where T : ProfileProviderViewModelBase
|
||||
{
|
||||
#region Fields
|
||||
|
||||
@@ -25,6 +26,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
protected readonly ISettingsService SettingsService;
|
||||
|
||||
private List<KeyBinding> _bindingsBeforeEdit;
|
||||
private Dictionary<string, string> _environmentVariablesBeforeEdit;
|
||||
private string _nameBeforeEdit;
|
||||
|
||||
#endregion Fields
|
||||
@@ -53,6 +55,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public KeyBindingsViewModel KeyBindings { get; }
|
||||
|
||||
public ObservableCollection<EnvironmentVariableViewModel> EnvironmentVariables { get; } = new ObservableCollection<EnvironmentVariableViewModel>();
|
||||
|
||||
public T ProfileVm { get; protected set; }
|
||||
|
||||
#endregion Properties
|
||||
@@ -79,7 +83,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
#region Constructor
|
||||
|
||||
protected ShellProfileViewModelBase(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, bool isNew)
|
||||
protected ProfileViewModelBase(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, bool isNew)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
DialogService = dialogService;
|
||||
@@ -87,6 +91,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
KeyBindings = new KeyBindingsViewModel(shellProfile.Id.ToString(), dialogService, string.Empty, false);
|
||||
|
||||
foreach (var environmentVariable in shellProfile.EnvironmentVariables)
|
||||
{
|
||||
EnvironmentVariables.Add(new EnvironmentVariableViewModel
|
||||
{
|
||||
Name = environmentVariable.Key,
|
||||
Value = environmentVariable.Value
|
||||
});
|
||||
}
|
||||
|
||||
Initialize(shellProfile);
|
||||
|
||||
DeleteCommand = new AsyncCommand(Delete, CanDelete);
|
||||
@@ -115,9 +128,10 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
protected virtual bool HasChanges()
|
||||
{
|
||||
return ProfileVm.HasChanges() || !_nameBeforeEdit.NullableEqualTo(_name) ||
|
||||
!(_bindingsBeforeEdit?.SequenceEqual(KeyBindings.KeyBindings.Select(x => x.Model).ToList()) ??
|
||||
false);
|
||||
return ProfileVm.HasChanges()
|
||||
|| !_nameBeforeEdit.NullableEqualTo(_name)
|
||||
|| !(_bindingsBeforeEdit?.SequenceEqual(KeyBindings.KeyBindings.Select(x => x.Model).ToList()) ?? false)
|
||||
|| !(_environmentVariablesBeforeEdit?.SequenceEqual(EnvironmentVariables.ToDictionary(x => x.Name, x => x.Value)) ?? false);
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
@@ -141,6 +155,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
|
||||
_bindingsBeforeEdit = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
|
||||
_environmentVariablesBeforeEdit = EnvironmentVariables.ToDictionary(x => x.Name, x => x.Value);
|
||||
_nameBeforeEdit = _name;
|
||||
|
||||
KeyBindings.Editable = true;
|
||||
@@ -189,11 +204,26 @@ namespace FluentTerminal.App.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnvironmentVariables.Select(x => x.Name).Any(x => string.IsNullOrWhiteSpace(x)))
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"), I18N.Translate("EmptyEnvironmentVariableName"), DialogButton.OK);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (EnvironmentVariables.Select(x => x.Name).Distinct().Count() != EnvironmentVariables.Count)
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"), I18N.Translate("DuplicateEnvironmentVariable"), DialogButton.OK);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var profile = ProfileVm.Model;
|
||||
|
||||
profile.Id = Id;
|
||||
profile.Name = _name;
|
||||
profile.KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
|
||||
profile.EnvironmentVariables = EnvironmentVariables.ToDictionary(x => x.Name, x => x.Value);
|
||||
|
||||
if (profile is SshProfile sshProfile)
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@ using FluentTerminal.Models;
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <see cref="ShellProfileViewModelBase{T}"/>, and doesn't implement any additional logic because in
|
||||
/// Extends <see cref="ProfileViewModelBase{T}"/>, and doesn't implement any additional logic because in
|
||||
/// case of SSH profiles no additional logic is needed.
|
||||
/// </summary>
|
||||
public class SshProfileViewModel : ShellProfileViewModelBase<SshConnectViewModel>
|
||||
public class SshProfileViewModel : ProfileViewModelBase<SshConnectViewModel>
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
isNew)
|
||||
{
|
||||
ProfileVm = new SshConnectViewModel(settingsService, applicationView, trayProcessCommunicationService,
|
||||
fileSystemService, historyContainer, sshProfile);
|
||||
fileSystemService, sshProfile);
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
@@ -5,12 +5,14 @@ using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.Models.Messages;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
@@ -22,8 +24,63 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Serialize terminal state
|
||||
private class TerminalState
|
||||
{
|
||||
public bool HasCustomTitle { get; set; }
|
||||
public string ShellTitle { get; set; }
|
||||
public string TabTitle { get; set; }
|
||||
public TerminalTheme TerminalTheme { get; set; }
|
||||
public TabTheme TabTheme { get; set; }
|
||||
public bool ShowSearchPanel { get; set; }
|
||||
public string SearchText { get; set; }
|
||||
public string XtermBufferState { get; set; }
|
||||
public byte TerminalId { get; set; }
|
||||
public ShellProfile ShellProfile { get; set; }
|
||||
}
|
||||
|
||||
public async Task<string> Serialize()
|
||||
{
|
||||
TerminalState state = new TerminalState
|
||||
{
|
||||
HasCustomTitle = _hasCustomTitle,
|
||||
ShellTitle = ShellTitle,
|
||||
TabTitle = TabTitle,
|
||||
TerminalTheme = TerminalTheme,
|
||||
TabTheme = TabTheme,
|
||||
ShowSearchPanel = ShowSearchPanel,
|
||||
SearchText = SearchText,
|
||||
XtermBufferState = await SerializeXtermState(),
|
||||
TerminalId = Terminal.Id,
|
||||
ShellProfile = ShellProfile
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(state);
|
||||
}
|
||||
|
||||
public void Restore(string data)
|
||||
{
|
||||
TerminalState state = JsonConvert.DeserializeObject<TerminalState>(data);
|
||||
if (state != null)
|
||||
{
|
||||
_hasCustomTitle = state.HasCustomTitle;
|
||||
ShellTitle = state.ShellTitle;
|
||||
TabTitle = state.TabTitle;
|
||||
TerminalTheme = state.TerminalTheme;
|
||||
TabTheme = state.TabTheme;
|
||||
ShowSearchPanel = state.ShowSearchPanel;
|
||||
SearchText = state.SearchText;
|
||||
XtermBufferState = state.XtermBufferState;
|
||||
_terminalId = state.TerminalId;
|
||||
ShellProfile = state.ShellProfile;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Serialize terminal state
|
||||
|
||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||
private bool _isSelected;
|
||||
private bool _isHovered;
|
||||
private bool _hasNewOutput;
|
||||
private bool _hasExitedWithError;
|
||||
private string _searchText;
|
||||
@@ -34,16 +91,17 @@ namespace FluentTerminal.App.ViewModels
|
||||
private string _tabTitle;
|
||||
private string _shellTitle;
|
||||
private bool _hasCustomTitle;
|
||||
private byte? _terminalId;
|
||||
|
||||
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
|
||||
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, string terminalState = null)
|
||||
{
|
||||
MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
|
||||
MessengerInstance.Register<CurrentThemeChangedMessage>(this, OnCurrentThemeChanged);
|
||||
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
|
||||
|
||||
SettingsService = settingsService;
|
||||
SettingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
SettingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
SettingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
SettingsService.KeyBindingsChanged += OnKeyBindingsChanged;
|
||||
|
||||
_terminalOptions = SettingsService.GetTerminalOptions();
|
||||
|
||||
@@ -61,14 +119,23 @@ namespace FluentTerminal.App.ViewModels
|
||||
TabThemes = new ObservableCollection<TabTheme>(SettingsService.GetTabThemes());
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == ShellProfile.TabThemeId);
|
||||
|
||||
CloseCommand = new RelayCommand(async () => await TryClose().ConfigureAwait(false));
|
||||
FindNextCommand = new RelayCommand(FindNext);
|
||||
FindPreviousCommand = new RelayCommand(FindPrevious);
|
||||
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel);
|
||||
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme);
|
||||
EditTitleCommand = new AsyncCommand(EditTitle);
|
||||
CloseCommand = new AsyncCommand(CloseTab, CanExecuteCommand);
|
||||
CloseLeftTabsCommand = new RelayCommand(CloseLeftTabs, CanExecuteCommand);
|
||||
CloseRightTabsCommand = new RelayCommand(CloseRightTabs, CanExecuteCommand);
|
||||
CloseOtherTabsCommand = new RelayCommand(CloseOtherTabs, CanExecuteCommand);
|
||||
FindNextCommand = new RelayCommand(FindNext, CanExecuteCommand);
|
||||
FindPreviousCommand = new RelayCommand(FindPrevious, CanExecuteCommand);
|
||||
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel, CanExecuteCommand);
|
||||
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme, CanExecuteCommand);
|
||||
EditTitleCommand = new AsyncCommand(EditTitle, CanExecuteCommand);
|
||||
DuplicateTabCommand = new RelayCommand(DuplicateTab, CanExecuteCommand);
|
||||
|
||||
Terminal = new Terminal(TrayProcessCommunicationService);
|
||||
if (!String.IsNullOrEmpty(terminalState))
|
||||
{
|
||||
Restore(terminalState);
|
||||
}
|
||||
|
||||
Terminal = new Terminal(TrayProcessCommunicationService, _terminalId);
|
||||
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
|
||||
Terminal.OutputReceived += Terminal_OutputReceived;
|
||||
Terminal.SizeChanged += Terminal_SizeChanged;
|
||||
@@ -80,16 +147,30 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
}
|
||||
|
||||
private bool _disposalRequested;
|
||||
|
||||
public void DisposalPrepare()
|
||||
{
|
||||
_disposalRequested = true;
|
||||
CloseCommand = null;
|
||||
TerminalView.DisposalPrepare();
|
||||
TerminalView = null;
|
||||
Terminal = null;
|
||||
Overlay = null;
|
||||
}
|
||||
|
||||
public event EventHandler Activated;
|
||||
public event EventHandler Closed;
|
||||
public event EventHandler<string> FindNextRequested;
|
||||
public event EventHandler<string> FindPreviousRequested;
|
||||
public event EventHandler KeyBindingsChanged;
|
||||
public event EventHandler<TerminalOptions> OptionsChanged;
|
||||
public event EventHandler SearchStarted;
|
||||
public event EventHandler<TerminalTheme> ThemeChanged;
|
||||
public event EventHandler<string> ShellTitleChanged;
|
||||
public event EventHandler<string> CustomTitleChanged;
|
||||
public event EventHandler CloseLeftTabsRequested;
|
||||
public event EventHandler CloseRightTabsRequested;
|
||||
public event EventHandler CloseOtherTabsRequested;
|
||||
public event EventHandler DuplicateTabRequested;
|
||||
|
||||
public ApplicationSettings ApplicationSettings { get; private set; }
|
||||
|
||||
@@ -106,7 +187,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public IClipboardService ClipboardService { get; }
|
||||
|
||||
public RelayCommand CloseCommand { get; }
|
||||
public string XtermBufferState { get; private set; }
|
||||
|
||||
public AsyncCommand CloseCommand { get; private set; }
|
||||
|
||||
public RelayCommand CloseRightTabsCommand { get; }
|
||||
|
||||
public RelayCommand CloseLeftTabsCommand { get; }
|
||||
|
||||
public RelayCommand CloseOtherTabsCommand { get; }
|
||||
|
||||
public RelayCommand CloseSearchPanelCommand { get; }
|
||||
|
||||
@@ -118,6 +207,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public RelayCommand FindPreviousCommand { get; }
|
||||
|
||||
public RelayCommand DuplicateTabCommand { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
@@ -131,10 +222,28 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
RaisePropertyChanged(nameof(ShowCloseButton));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHovered
|
||||
{
|
||||
get => _isHovered;
|
||||
set
|
||||
{
|
||||
if (Set(ref _isHovered, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(ShowCloseButton));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => IsHovered || IsSelected;
|
||||
}
|
||||
|
||||
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
|
||||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Color != null);
|
||||
|
||||
@@ -166,7 +275,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public ISettingsService SettingsService { get; }
|
||||
|
||||
public ShellProfile ShellProfile { get; }
|
||||
public ShellProfile ShellProfile { get; private set; }
|
||||
|
||||
public bool ShowSearchPanel
|
||||
{
|
||||
@@ -197,6 +306,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
set => Set(ref _terminalTheme, value);
|
||||
}
|
||||
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
public string TabTitle
|
||||
{
|
||||
get => _tabTitle;
|
||||
@@ -246,16 +357,18 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public Task Close()
|
||||
{
|
||||
SettingsService.CurrentThemeChanged -= OnCurrentThemeChanged;
|
||||
SettingsService.TerminalOptionsChanged -= OnTerminalOptionsChanged;
|
||||
SettingsService.ApplicationSettingsChanged -= OnApplicationSettingsChanged;
|
||||
SettingsService.KeyBindingsChanged -= OnKeyBindingsChanged;
|
||||
MessengerInstance.Unregister(this);
|
||||
|
||||
return Terminal.Close();
|
||||
}
|
||||
|
||||
public void CopyText(string text)
|
||||
{
|
||||
ClipboardService.SetText(text);
|
||||
if (_terminalOptions.ShowTextCopied)
|
||||
{
|
||||
Overlay.Show(I18N.Translate("TextCopied"));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EditTitle()
|
||||
@@ -288,49 +401,85 @@ namespace FluentTerminal.App.ViewModels
|
||||
FocusTerminal();
|
||||
}
|
||||
|
||||
private bool CanExecuteCommand<T>(T a)
|
||||
{
|
||||
return CanExecuteCommand();
|
||||
}
|
||||
|
||||
private bool CanExecuteCommand()
|
||||
{
|
||||
return Initialized && !_disposalRequested;
|
||||
}
|
||||
|
||||
private async Task CloseTab()
|
||||
{
|
||||
await TryClose().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CloseLeftTabs()
|
||||
{
|
||||
CloseLeftTabsRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void CloseRightTabs()
|
||||
{
|
||||
CloseRightTabsRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void CloseOtherTabs()
|
||||
{
|
||||
CloseOtherTabsRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void FindNext()
|
||||
{
|
||||
FindNextRequested?.Invoke(this, SearchText);
|
||||
}
|
||||
|
||||
public ITerminalView TerminalView { get; set; }
|
||||
|
||||
public async Task<string> SerializeXtermState()
|
||||
{
|
||||
return await TerminalView?.SerializeXtermState();
|
||||
}
|
||||
|
||||
private void FindPrevious()
|
||||
{
|
||||
FindPreviousRequested?.Invoke(this, SearchText);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
private void DuplicateTab()
|
||||
{
|
||||
DuplicateTabRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
ApplicationSettings = e;
|
||||
ApplicationSettings = message.ApplicationSettings;
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnCurrentThemeChanged(object sender, Guid e)
|
||||
private async void OnCurrentThemeChanged(CurrentThemeChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
// only change theme if not overwritten by profile
|
||||
if (ShellProfile.TerminalThemeId == Guid.Empty)
|
||||
{
|
||||
var currentTheme = SettingsService.GetTheme(e);
|
||||
var currentTheme = SettingsService.GetTheme(message.ThemeId);
|
||||
TerminalTheme = currentTheme;
|
||||
ThemeChanged?.Invoke(this, currentTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
private void OnTerminalOptionsChanged(TerminalOptionsChangedMessage message)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() => KeyBindingsChanged?.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
_terminalOptions = e;
|
||||
await ApplicationView.RunOnDispatcherThread(() => OptionsChanged?.Invoke(this, e));
|
||||
_terminalOptions = message.TerminalOptions;
|
||||
}
|
||||
|
||||
private void SelectTabTheme(string id)
|
||||
@@ -340,12 +489,24 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void Terminal_Exited(object sender, int exitCode)
|
||||
{
|
||||
if (ShellProfile?.Tag is ISessionSuccessTracker tracker && exitCode != 0)
|
||||
{
|
||||
tracker.SetInvalid();
|
||||
}
|
||||
|
||||
ApplicationView.RunOnDispatcherThread(() => HasExitedWithError = exitCode > 0);
|
||||
}
|
||||
|
||||
private void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
|
||||
|
||||
Terminal.KeyboardCommandReceived -= Terminal_KeyboardCommandReceived;
|
||||
Terminal.OutputReceived -= Terminal_OutputReceived;
|
||||
Terminal.SizeChanged -= Terminal_SizeChanged;
|
||||
Terminal.TitleChanged -= Terminal_TitleChanged;
|
||||
Terminal.Exited -= Terminal_Exited;
|
||||
Terminal.Closed -= Terminal_Closed;
|
||||
}
|
||||
|
||||
private async void Terminal_KeyboardCommandReceived(object sender, string e)
|
||||
@@ -355,11 +516,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
case nameof(Command.Copy):
|
||||
{
|
||||
var selection = await Terminal.GetSelectedText().ConfigureAwait(true);
|
||||
ClipboardService.SetText(selection);
|
||||
if(_terminalOptions.ShowTextCopied)
|
||||
{
|
||||
Overlay.Show(I18N.Translate("TextCopied"));
|
||||
}
|
||||
CopyText(selection);
|
||||
break;
|
||||
}
|
||||
case nameof(Command.Paste):
|
||||
@@ -401,6 +558,11 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void Terminal_OutputReceived(object sender, byte[] e)
|
||||
{
|
||||
if (ShellProfile?.Tag is ISessionSuccessTracker tracker)
|
||||
{
|
||||
tracker.SetOutputReceived();
|
||||
}
|
||||
|
||||
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => HasNewOutput = true);
|
||||
@@ -420,9 +582,14 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task TryClose()
|
||||
{
|
||||
if (ShellProfile?.Tag is ISessionSuccessTracker tracker)
|
||||
{
|
||||
tracker.SetInvalid();
|
||||
}
|
||||
|
||||
if (ApplicationSettings.ConfirmClosingTabs)
|
||||
{
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmCloseTab"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), String.Format(I18N.Translate("ConfirmCloseTab"), ShellTitle), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.Cancel)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace FluentTerminal.App.ViewModels.Utilities
|
||||
{
|
||||
public static class WebViewSpecialCharEncoder
|
||||
{
|
||||
public static string Encode(string value)
|
||||
{
|
||||
return value.Replace("\"", """).Replace("'", "&squo;").Replace("\\", "\");
|
||||
}
|
||||
|
||||
public static string Decode(string value)
|
||||
{
|
||||
return value.Replace(""", "\"").Replace("&squo;", "'").Replace("\", "\\");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
private void _applicationView_Consolidated(ApplicationView sender, ApplicationViewConsolidatedEventArgs args)
|
||||
{
|
||||
_applicationView.Consolidated -= _applicationView_Consolidated;
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,11 @@ namespace FluentTerminal.App.Adapters
|
||||
public DispatcherTimerAdapter()
|
||||
{
|
||||
_dispatcherTimer = new DispatcherTimer();
|
||||
_dispatcherTimer.Tick += OnDispatcherTimerTick;
|
||||
}
|
||||
|
||||
private void OnDispatcherTimerTick(object sender, object e)
|
||||
{
|
||||
Tick?.Invoke(this, null);
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public TimeSpan Interval
|
||||
@@ -31,12 +30,14 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_dispatcherTimer.Tick += OnDispatcherTimerTick;
|
||||
_dispatcherTimer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_dispatcherTimer.Stop();
|
||||
_dispatcherTimer.Tick -= OnDispatcherTimerTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="Styles\Custom.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<LinearGradientBrush x:Key="MissingColorSelectionBrush" StartPoint="0,0" EndPoint="1,1">
|
||||
@@ -26,6 +27,7 @@
|
||||
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
|
||||
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
|
||||
<converters:TabColorFallbackConverter x:Key="TabColorFallbackConverter" />
|
||||
<converters:TextMiddleEllipsisConverter x:Key="TextMiddleEllipsisConverter" />
|
||||
<converters:IntToVisibilityConverter
|
||||
x:Key="ZeroToCollapsedConverter"
|
||||
Else="Visible"
|
||||
|
||||
@@ -36,6 +36,8 @@ using FluentTerminal.App.Utilities;
|
||||
using IContainer = Autofac.IContainer;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Profiles;
|
||||
using FluentTerminal.Models.Messages;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
|
||||
namespace FluentTerminal.App
|
||||
{
|
||||
@@ -96,18 +98,35 @@ namespace FluentTerminal.App
|
||||
builder.RegisterType<SshProfileSelectionDialog>().As<ISshProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||
builder.RegisterType<AboutDialog>().As<IAboutDialog>().InstancePerDependency();
|
||||
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
||||
builder.RegisterType<SshInfoDialog>().As<ISshConnectionInfoDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CustomCommandDialog>().As<ICustomCommandDialog>().InstancePerDependency();
|
||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
||||
builder.RegisterType<ApplicationLanguageService>().As<IApplicationLanguageService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileMigrationService>().As<IShellProfileMigrationService>().SingleInstance();
|
||||
builder.RegisterInstance(applicationDataContainers);
|
||||
|
||||
_container = builder.Build();
|
||||
|
||||
Messenger.Default.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
|
||||
|
||||
_settingsService = _container.Resolve<ISettingsService>();
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
|
||||
var shellProfileMigrationService = _container.Resolve<IShellProfileMigrationService>();
|
||||
foreach (var profile in _settingsService.GetShellProfiles())
|
||||
{
|
||||
shellProfileMigrationService.Migrate(profile);
|
||||
_settingsService.SaveShellProfile(profile);
|
||||
}
|
||||
|
||||
foreach (var profile in _settingsService.GetSshProfiles())
|
||||
{
|
||||
shellProfileMigrationService.Migrate(profile);
|
||||
_settingsService.SaveSshProfile(profile);
|
||||
}
|
||||
|
||||
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
|
||||
|
||||
@@ -166,6 +185,12 @@ namespace FluentTerminal.App
|
||||
{
|
||||
if (args is ProtocolActivatedEventArgs protocolActivated)
|
||||
{
|
||||
if (protocolActivated.Uri == new Uri("ftcmd://fluent.terminal?focus"))
|
||||
{
|
||||
await ShowOrCreateWindow(protocolActivated.ViewSwitcher);
|
||||
return;
|
||||
}
|
||||
|
||||
MainViewModel mainViewModel = null;
|
||||
// IApplicationView to use for creating view models
|
||||
IApplicationView applicationView;
|
||||
@@ -195,7 +220,10 @@ namespace FluentTerminal.App
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await new MessageDialog($"Invalid link: {ex.Message}", "Invalid Link").ShowAsync();
|
||||
await new MessageDialog(
|
||||
$"{I18N.TranslateWithFallback("InvalidLink", "Invalid link.")} {ex.Message}",
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
@@ -214,15 +242,17 @@ namespace FluentTerminal.App
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await new MessageDialog($"Invalid link: {ex.Message}", "Invalid Link").ShowAsync();
|
||||
await new MessageDialog(
|
||||
$"{I18N.TranslateWithFallback("InvalidLink", "Invalid link.")} {ex.Message}",
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vm.CommandInput && _applicationSettings.AutoFallbackToWindowsUsernameInLinks &&
|
||||
string.IsNullOrEmpty(vm.Username))
|
||||
if (_applicationSettings.AutoFallbackToWindowsUsernameInLinks && string.IsNullOrEmpty(vm.Username))
|
||||
{
|
||||
vm.Username = await _trayProcessCommunicationService.GetUserName();
|
||||
}
|
||||
@@ -246,14 +276,68 @@ namespace FluentTerminal.App
|
||||
}
|
||||
|
||||
if (mainViewModel == null)
|
||||
await CreateTerminal(profile, _applicationSettings.NewTerminalLocation);
|
||||
await CreateTerminal(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
else
|
||||
await mainViewModel.AddTerminalAsync(profile);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await new MessageDialog($"Invalid link: {protocolActivated.Uri}", "Invalid Link").ShowAsync();
|
||||
if (CommandProfileProviderViewModel.CheckScheme(protocolActivated.Uri))
|
||||
{
|
||||
CommandProfileProviderViewModel vm;
|
||||
|
||||
try
|
||||
{
|
||||
vm = CommandProfileProviderViewModel.ParseUri(protocolActivated.Uri, _settingsService,
|
||||
applicationView, _trayProcessCommunicationService,
|
||||
_container.Resolve<ApplicationDataContainers>().HistoryContainer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await new MessageDialog(
|
||||
$"{I18N.TranslateWithFallback("InvalidLink", "Invalid link.")} {ex.Message}",
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var error = await vm.AcceptChangesAsync(true);
|
||||
|
||||
var profile = vm.Model;
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
// Link is valid, but incomplete, so we need to show dialog.
|
||||
profile = await _dialogService.ShowCustomCommandDialogAsync(profile);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
// User clicked "Cancel" in the dialog.
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainViewModel == null)
|
||||
{
|
||||
await CreateTerminal(profile, _applicationSettings.NewTerminalLocation, protocolActivated.ViewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
await mainViewModel.AddTerminalAsync(profile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await new MessageDialog(
|
||||
$"{I18N.TranslateWithFallback("InvalidLink", "Invalid link.")} {protocolActivated.Uri}",
|
||||
"Invalid Link")
|
||||
.ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
@@ -262,12 +346,13 @@ namespace FluentTerminal.App
|
||||
|
||||
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments))
|
||||
var arguments = commandLineActivated.Operation.Arguments;
|
||||
if (string.IsNullOrWhiteSpace(arguments))
|
||||
{
|
||||
return;
|
||||
arguments = "new";
|
||||
}
|
||||
|
||||
_commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
||||
_commandLineParser.ParseArguments(SplitArguments(arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
||||
{
|
||||
if (verb is SettingsVerb settingsVerb)
|
||||
{
|
||||
@@ -304,10 +389,14 @@ namespace FluentTerminal.App
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory))
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory) && newVerb.Directory != ".")
|
||||
{
|
||||
profile.WorkingDirectory = newVerb.Directory;
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
@@ -349,6 +438,19 @@ namespace FluentTerminal.App
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowOrCreateWindow(ActivationViewSwitcher viewSwitcher)
|
||||
{
|
||||
if (_mainViewModels.Count == 0)
|
||||
{
|
||||
await CreateTerminal(_settingsService.GetDefaultShellProfile(), NewTerminalLocation.Tab, viewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = _mainViewModels.Find(o => o.ApplicationView.Id == _activeWindowId) ?? _mainViewModels.Last();
|
||||
await ShowAsStandaloneAsync(viewModel, viewSwitcher);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
if (_isLaunching)
|
||||
@@ -376,16 +478,18 @@ namespace FluentTerminal.App
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
Window.Current.Activate();
|
||||
}
|
||||
else if (_mainViewModels.Count == 0)
|
||||
{
|
||||
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
||||
}
|
||||
else if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
|
||||
{
|
||||
var location = _applicationSettings.NewTerminalLocation;
|
||||
var profile = _settingsService.GetShellProfile(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
|
||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||
await CreateTerminal(profile, location, args.ViewSwitcher).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await viewModel.AddLocalTabAsync();
|
||||
}
|
||||
|
||||
_isLaunching = false;
|
||||
}
|
||||
|
||||
@@ -438,6 +542,8 @@ namespace FluentTerminal.App
|
||||
|
||||
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||
{
|
||||
ApplicationViewSwitcher.DisableSystemViewActivationPolicy();
|
||||
|
||||
await StartSystemTray().ConfigureAwait(true);
|
||||
|
||||
if (!(Window.Current.Content is Frame rootFrame))
|
||||
@@ -455,8 +561,8 @@ namespace FluentTerminal.App
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
mainViewModel.ActivatedMv += OnMainViewActivated;
|
||||
mainViewModel.TabTearedOff += OnTabTearOff;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
}
|
||||
|
||||
@@ -472,8 +578,8 @@ namespace FluentTerminal.App
|
||||
viewModel.Closed += OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
viewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
viewModel.ActivatedMv += OnMainViewActivated;
|
||||
viewModel.TabTearedOff += OnTabTearOff;
|
||||
_mainViewModels.Add(viewModel);
|
||||
|
||||
return viewModel;
|
||||
@@ -508,9 +614,10 @@ namespace FluentTerminal.App
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
private void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
|
||||
{
|
||||
_applicationSettings = e;
|
||||
_applicationSettings = message.ApplicationSettings;
|
||||
_trayProcessCommunicationService.UpdateSettings(message.ApplicationSettings);
|
||||
}
|
||||
|
||||
private void OnMainViewModelClosed(object sender, EventArgs e)
|
||||
@@ -522,14 +629,24 @@ namespace FluentTerminal.App
|
||||
viewModel.Closed -= OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested -= OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
|
||||
viewModel.ShowAboutRequested -= OnShowAboutRequested;
|
||||
viewModel.ActivatedMv -= OnMainViewActivated;
|
||||
viewModel.TabTearedOff -= OnTabTearOff;
|
||||
if (_activeWindowId == viewModel.ApplicationView.Id)
|
||||
{
|
||||
_activeWindowId = 0;
|
||||
}
|
||||
|
||||
_mainViewModels.Remove(viewModel);
|
||||
|
||||
try
|
||||
{
|
||||
// try/catch because the method cannot be called on the last window
|
||||
Window.Current.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,6 +659,14 @@ namespace FluentTerminal.App
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTabTearOff(object sender, TerminalViewModel model)
|
||||
{
|
||||
Logger.Instance.Debug("App.xaml.cs on tab tear off");
|
||||
|
||||
var newViewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await newViewModel.AddTerminalAsync(await model.Serialize(), 0);
|
||||
}
|
||||
|
||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||
{
|
||||
ShellProfile profile = null;
|
||||
@@ -565,6 +690,14 @@ namespace FluentTerminal.App
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowCustomCommandDialog:
|
||||
profile = await _dialogService.ShowCustomCommandDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowSshInfoDialog:
|
||||
profile = await _dialogService.ShowSshConnectionInfoDialogAsync();
|
||||
if (profile == null)
|
||||
@@ -603,24 +736,28 @@ namespace FluentTerminal.App
|
||||
_settingsWindowId = null;
|
||||
}
|
||||
|
||||
private void OnShowAboutRequested(object sender, EventArgs e)
|
||||
{
|
||||
ShowAbout().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async void OnShowSettingsRequested(object sender, EventArgs e)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task ShowAbout()
|
||||
private async Task ShowAsStandaloneAsync(MainViewModel viewModel, ActivationViewSwitcher viewSwitcher = null)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
_settingsViewModel.NavigateToAboutPage();
|
||||
int viewId = viewModel.ApplicationView.Id;
|
||||
if (viewSwitcher != null)
|
||||
{
|
||||
await viewModel.ApplicationView.RunOnDispatcherThread(async () => await viewSwitcher.ShowAsStandaloneAsync(viewId));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location)
|
||||
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location, ActivationViewSwitcher viewSwitcher = null)
|
||||
{
|
||||
|
||||
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
@@ -631,19 +768,19 @@ namespace FluentTerminal.App
|
||||
{
|
||||
|
||||
MainViewModel item = _mainViewModels.FirstOrDefault(o => o.ApplicationView.Id == _activeWindowId);
|
||||
if (item != null)
|
||||
if (item == null)
|
||||
{
|
||||
await item.AddTerminalAsync(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _mainViewModels.Last().AddTerminalAsync(profile);
|
||||
item = _mainViewModels.Last();
|
||||
}
|
||||
|
||||
await item.AddTerminalAsync(profile);
|
||||
await ShowAsStandaloneAsync(item, viewSwitcher);
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
await ShowAsStandaloneAsync(viewModel, viewSwitcher);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 926 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 369 B |
|
Before Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 689 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 753 B |
|
Before Width: | Height: | Size: 978 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 428 B |
|
Before Width: | Height: | Size: 4.4 KiB |