Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04a50e8152 | ||
|
|
e89853b6b5 | ||
|
|
c6bb9c9e28 | ||
|
|
d027ab9a02 | ||
|
|
13321cbc1a | ||
|
|
9a5af6437f | ||
|
|
a1967edb1d | ||
|
|
5900c51e70 | ||
|
|
6209727037 | ||
|
|
df15cb2231 | ||
|
|
eb2a44d2de | ||
|
|
e4fff8f638 | ||
|
|
80ec667f8b | ||
|
|
112c8c7c6c | ||
|
|
45338d00b8 | ||
|
|
04ed5d85e5 | ||
|
|
96ac5cd49f | ||
|
|
c70061995f | ||
|
|
501c5e20d8 | ||
|
|
5cc7d813a8 | ||
|
|
a0462ef502 | ||
|
|
1b1e29cbfe | ||
|
|
94855b7487 | ||
|
|
8ec0d58065 | ||
|
|
4476fe3061 | ||
|
|
4dbc75f540 | ||
|
|
a82c2a59f5 | ||
|
|
00b250c1b2 | ||
|
|
4ec9f0a5a6 | ||
|
|
5f82724039 | ||
|
|
6c65258439 | ||
|
|
f23493e3c8 | ||
|
|
de07e277f1 | ||
|
|
5c52810149 | ||
|
|
05a5ad56ea | ||
|
|
c51ffaa6e9 | ||
|
|
288b661611 | ||
|
|
32ab18f5f9 | ||
|
|
fddbd12820 | ||
|
|
563a7071ea | ||
|
|
d2c89ccb08 | ||
|
|
b5e8ce7639 | ||
|
|
6829c4c7f3 | ||
|
|
09bd970673 | ||
|
|
af61c83949 | ||
|
|
5ed24f85cf | ||
|
|
763a071488 | ||
|
|
bdb4d57d62 | ||
|
|
1be5fecf41 | ||
|
|
14dfb8b26a | ||
|
|
7284ce7f52 | ||
|
|
ac9f3c2a0d | ||
|
|
9dfab2223f | ||
|
|
c15da646fc | ||
|
|
2a47555987 | ||
|
|
ccf75bd45f | ||
|
|
d9b0b86cd5 | ||
|
|
d157b68384 | ||
|
|
0733381cd0 | ||
|
|
1870209878 | ||
|
|
565b3c3f69 | ||
|
|
7cdda79d67 | ||
|
|
a0f57ab4a3 | ||
|
|
edee7628d0 | ||
|
|
d06ad45423 | ||
|
|
9cf6a61e0f | ||
|
|
0ea4722dc8 | ||
|
|
39960dd755 | ||
|
|
8e5d3dea0a | ||
|
|
973b1a4963 | ||
|
|
dbd8632076 | ||
|
|
fffa7ebcb9 | ||
|
|
ded043db1d | ||
|
|
61ad64519b | ||
|
|
ef7f909d26 | ||
|
|
f2618fa940 | ||
|
|
363f885aea | ||
|
|
ed4a5adb16 | ||
|
|
82a3532e90 | ||
|
|
15d713191c | ||
|
|
575022139a | ||
|
|
b9686ac4ec | ||
|
|
43e4f8290e | ||
|
|
93387ebc57 | ||
|
|
54a9d7ce0d | ||
|
|
44b306ac20 | ||
|
|
14e1e0e3d3 | ||
|
|
136ba3c54f | ||
|
|
1a0c51c6d4 | ||
|
|
363018a43f | ||
|
|
3dcfaa94e9 | ||
|
|
05104e450e | ||
|
|
37f20e70bb | ||
|
|
e5a39fbe0d | ||
|
|
5c2f3e6f95 | ||
|
|
b3e831e3e6 | ||
|
|
31ff544379 | ||
|
|
7c4f62d16b | ||
|
|
f4b4623dab | ||
|
|
a32a86e15f | ||
|
|
6b64d3985d | ||
|
|
ed9c9ad6a6 | ||
|
|
bece957ea9 | ||
|
|
4e83e66a85 | ||
|
|
57d86a9723 | ||
|
|
8b6aae7bda | ||
|
|
2d204e48ab | ||
|
|
38a7b71fa8 | ||
|
|
95c94e3820 | ||
|
|
1920fdf35c | ||
|
|
9f3c46c366 | ||
|
|
221a2c2c6f | ||
|
|
bde65fa865 | ||
|
|
d52860e90a |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
# Git will handle the files in whatever way it thinks is best.
|
||||
# Overwrites global core.autocrlf git setting
|
||||
* text=auto
|
||||
@@ -292,3 +292,5 @@ __pycache__/
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
!FluentTerminal.App/MoshExecutables/*
|
||||
@@ -1,3 +1,3 @@
|
||||
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%1" /f
|
||||
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
|
||||
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
|
||||
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%1\"" /f
|
||||
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%V\"" /f
|
||||
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new \"%%V\"" /f
|
||||
@@ -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>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
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>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
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>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
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>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
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>);
|
||||
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||
|
||||
@@ -86,11 +86,24 @@ 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>);
|
||||
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>, Mock.Of<IInputDialog>, Mock.Of<ISshConnectionInfoDialog>, Mock.Of<ISshProfileSelectionDialog>);
|
||||
|
||||
dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowInputDialogAsync_UsesIInputDialogSetTitle()
|
||||
{
|
||||
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>);
|
||||
|
||||
dialogService.ShowInputDialogAsync(title);
|
||||
|
||||
inputDialog.Verify(x => x.SetTitle(title), Times.Once);
|
||||
inputDialog.Verify(x => x.GetInput(), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
@@ -11,13 +11,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.4.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="5.4.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
|
||||
<PackageReference Include="Moq" Version="4.8.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<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="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -31,4 +34,11 @@
|
||||
<ProjectReference Include="..\FluentTerminal.App.Services\FluentTerminal.App.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Windows.Foundation.FoundationContract">
|
||||
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -41,6 +41,41 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
invoke.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeregisterCommandHandler_CommandIsNull_ThrowsArgumentNullException()
|
||||
{
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
Action invoke = () => keyboardCommandService.DeregisterCommandHandler(null);
|
||||
|
||||
invoke.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeregisterCommandHandler_CommandRegistered_HandlerGetsRemoved()
|
||||
{
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
var handler = _fixture.Create<Action>();
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
keyboardCommandService.RegisterCommandHandler(command, handler);
|
||||
|
||||
Action invoke = () => keyboardCommandService.DeregisterCommandHandler(command);
|
||||
|
||||
invoke.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeregisterCommandHandler_CommandNotRegistered_ShouldNotThrow()
|
||||
{
|
||||
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
|
||||
var keyboardCommandService = new KeyboardCommandService();
|
||||
|
||||
Action invoke = () => keyboardCommandService.DeregisterCommandHandler(command);
|
||||
|
||||
invoke.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCommand_CommandIsRegistered_HandlerGetsInvoked()
|
||||
|
||||
@@ -471,10 +471,6 @@ namespace FluentTerminal.App.Services.Test
|
||||
var commands = _fixture.CreateMany<Command>(5);
|
||||
var defaultValueProvider = new Mock<IDefaultValueProvider>();
|
||||
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
|
||||
foreach (var command in commands)
|
||||
{
|
||||
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(EnumHelper.GetEnumDescription(command), null)).Returns(new Collection<KeyBinding> { });
|
||||
}
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
KeyBindings = keyBindingsContainer.Object,
|
||||
|
||||
@@ -10,6 +10,7 @@ using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation.Collections;
|
||||
using Xunit;
|
||||
|
||||
namespace FluentTerminal.App.Services.Test
|
||||
@@ -34,19 +35,20 @@ namespace FluentTerminal.App.Services.Test
|
||||
});
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
var response = _fixture.Create<CreateTerminalResponse>();
|
||||
var responseMessage = new Dictionary<string, string>
|
||||
var responseMessage = new ValueSet
|
||||
{
|
||||
[MessageKeys.Type] = _fixture.Create<string>(),
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(response)
|
||||
};
|
||||
appServiceConnection.Setup(x => x.SendMessageAsync(It.IsAny<IDictionary<string, string>>())).Returns(Task.FromResult((IDictionary<string, string>)responseMessage));
|
||||
appServiceConnection.Setup(x => x.SendMessageAsync(It.IsAny<ValueSet>())).Returns(Task.FromResult(responseMessage));
|
||||
var terminalSize = _fixture.Create<TerminalSize>();
|
||||
var shellProfile = _fixture.Create<ShellProfile>();
|
||||
var sessionType = _fixture.Create<SessionType>();
|
||||
var id = _fixture.Create<byte>();
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile, sessionType);
|
||||
var result = await trayProcessCommunicationService.CreateTerminal(id, terminalSize, shellProfile, sessionType);
|
||||
|
||||
result.Should().BeEquivalentTo(response);
|
||||
}
|
||||
@@ -54,7 +56,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
[Fact]
|
||||
public async Task ResizeTerminal_Default_SendsResizeTerminalRequest()
|
||||
{
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var terminalId = _fixture.Create<byte>();
|
||||
var terminalSize = _fixture.Create<TerminalSize>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
@@ -68,14 +70,14 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
await trayProcessCommunicationService.ResizeTerminal(terminalId, terminalSize);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(ResizeTerminalRequest))), Times.Once);
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == ResizeTerminalRequest.Identifier)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteText_Default_SendsWriteTextRequest()
|
||||
public async Task Write_Default_SendsWriteDataRequest()
|
||||
{
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var text = _fixture.Create<string>();
|
||||
var terminalId = _fixture.Create<byte>();
|
||||
var data = _fixture.Create<byte[]>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
@@ -86,15 +88,15 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
|
||||
await trayProcessCommunicationService.WriteText(terminalId, text);
|
||||
await trayProcessCommunicationService.Write(terminalId, data);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(WriteTextRequest))), Times.Once);
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == Constants.TerminalBufferRequestIdentifier)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CloseTerminal_Default_SendsTerminalExitedRequest()
|
||||
{
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var terminalId = _fixture.Create<byte>();
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
settingsService.Setup(x => x.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
|
||||
@@ -107,22 +109,19 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
await trayProcessCommunicationService.CloseTerminal(terminalId);
|
||||
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(TerminalExitedRequest))), Times.Once);
|
||||
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<ValueSet>(d => (byte)d[MessageKeys.Type] == TerminalExitedRequest.Identifier)), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnMessageReceived_TerminalExitedRequest_InvokesTerminalExitedEvent()
|
||||
{
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var receivedTerminalId = 0;
|
||||
var terminalExitedEventCalled = false;
|
||||
var request = new TerminalExitedRequest
|
||||
var request = new TerminalExitedRequest(_fixture.Create<byte>(), _fixture.Create<int>());
|
||||
|
||||
var receivedExitStatus = (TerminalExitStatus) null;
|
||||
|
||||
var message = new ValueSet
|
||||
{
|
||||
TerminalId = terminalId
|
||||
};
|
||||
var message = new Dictionary<string, string>
|
||||
{
|
||||
[MessageKeys.Type] = nameof(TerminalExitedRequest),
|
||||
[MessageKeys.Type] = TerminalExitedRequest.Identifier,
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(request)
|
||||
};
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
@@ -134,33 +133,29 @@ namespace FluentTerminal.App.Services.Test
|
||||
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
|
||||
var appServiceConnection = new Mock<IAppServiceConnection>();
|
||||
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
|
||||
trayProcessCommunicationService.TerminalExited += (s, e) =>
|
||||
trayProcessCommunicationService.TerminalExited += (s, status) =>
|
||||
{
|
||||
terminalExitedEventCalled = true;
|
||||
receivedTerminalId = e;
|
||||
receivedExitStatus = status;
|
||||
};
|
||||
|
||||
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
|
||||
|
||||
terminalExitedEventCalled.Should().BeTrue();
|
||||
receivedTerminalId.Should().Be(terminalId);
|
||||
receivedExitStatus.Should().NotBeNull();
|
||||
receivedExitStatus.TerminalId.Should().Be(request.TerminalId);
|
||||
receivedExitStatus.ExitCode.Should().Be(request.ExitCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnMessageReceived_DisplayTerminalOutputRequest_InvokesCorrectOutputHandler()
|
||||
{
|
||||
var terminalId = _fixture.Create<int>();
|
||||
var output = _fixture.Create<string>();
|
||||
var receivedOutput = string.Empty;
|
||||
var request = new DisplayTerminalOutputRequest
|
||||
var terminalId = _fixture.Create<byte>();
|
||||
var output = _fixture.Create<byte[]>();
|
||||
var receivedOutput = default(byte[]);
|
||||
var message = new ValueSet
|
||||
{
|
||||
TerminalId = terminalId,
|
||||
Output = output
|
||||
};
|
||||
var message = new Dictionary<string, string>
|
||||
{
|
||||
[MessageKeys.Type] = nameof(DisplayTerminalOutputRequest),
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(request)
|
||||
[MessageKeys.Type] = Constants.TerminalBufferRequestIdentifier,
|
||||
[MessageKeys.TerminalId] = terminalId,
|
||||
[MessageKeys.Content] = output
|
||||
};
|
||||
var settingsService = new Mock<ISettingsService>();
|
||||
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
|
||||
@@ -178,7 +173,7 @@ namespace FluentTerminal.App.Services.Test
|
||||
|
||||
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
|
||||
|
||||
receivedOutput.Should().Be(output);
|
||||
receivedOutput.Should().BeEquivalentTo(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,6 @@
|
||||
public IApplicationDataContainer Themes { get; set; }
|
||||
public IApplicationDataContainer KeyBindings { get; set; }
|
||||
public IApplicationDataContainer ShellProfiles { get; set; }
|
||||
public IApplicationDataContainer SshProfiles { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,7 @@
|
||||
public const string ThemesContainerName = "Themes";
|
||||
public const string KeyBindingsContainerName = "KeyBindings";
|
||||
public const string ShellProfilesContainerName = "ShellProfiles";
|
||||
public const string SshProfilesContainerName = "SshProfiles";
|
||||
public const byte TerminalBufferRequestIdentifier = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface ISshConnectionInfoDialog
|
||||
{
|
||||
Task<ISshConnectionInfo> GetSshConnectionInfoAsync(ISshConnectionInfo input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentTerminal.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services.Dialogs
|
||||
{
|
||||
public interface ISshProfileSelectionDialog
|
||||
{
|
||||
SshProfile SelectedProfile { get; }
|
||||
|
||||
Task<SshProfile> SelectProfile();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
namespace FluentTerminal.App.Services.EventArgs
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.Services.EventArgs
|
||||
{
|
||||
public class NewWindowRequestedEventArgs : System.EventArgs
|
||||
{
|
||||
public bool ShowProfileSelection { get; set; }
|
||||
public Models.Enums.NewWindowAction Action { get; set; }
|
||||
public Guid ProfileId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="PListNet" Version="2.0.3" />
|
||||
<PackageReference Include="RestSharp" Version="106.5.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="PListNet" Version="3.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.6.9" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IAppServiceConnection
|
||||
{
|
||||
event EventHandler<IDictionary<string, string>> MessageReceived;
|
||||
event EventHandler<ValueSet> MessageReceived;
|
||||
|
||||
Task<IDictionary<string, string>> SendMessageAsync(IDictionary<string, string> message);
|
||||
Task<ValueSet> SendMessageAsync(ValueSet message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface IApplicationLanguageService
|
||||
{
|
||||
IEnumerable<string> Languages { get; }
|
||||
|
||||
void SetLanguage(string language);
|
||||
|
||||
string GetCurrentLanguage();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@ namespace FluentTerminal.App.Services
|
||||
public interface IApplicationView
|
||||
{
|
||||
event CloseRequestedHandler CloseRequested;
|
||||
event EventHandler Closed;
|
||||
|
||||
int Id { get; }
|
||||
string Title { get; set; }
|
||||
|
||||
Task RunOnDispatcherThread(Action action);
|
||||
|
||||
@@ -17,5 +17,9 @@ namespace FluentTerminal.App.Services
|
||||
|
||||
Task<KeyBinding> ShowCreateKeyBindingDialog();
|
||||
Task<string> ShowInputDialogAsync(string title);
|
||||
|
||||
Task<ISshConnectionInfo> ShowSshConnectionInfoDialogAsync(ISshConnectionInfo input);
|
||||
|
||||
Task<SshProfile> ShowSshProfileSelectionDialogAsync();
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,25 @@ namespace FluentTerminal.App.Services
|
||||
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);
|
||||
ApplicationSettings GetApplicationSettings();
|
||||
TerminalTheme GetCurrentTheme();
|
||||
Guid GetCurrentThemeId();
|
||||
ShellProfile GetDefaultShellProfile();
|
||||
SshProfile GetDefaultSshProfile();
|
||||
Guid GetDefaultShellProfileId();
|
||||
Guid GetDefaultSshProfileId();
|
||||
IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings();
|
||||
IEnumerable<ShellProfile> GetShellProfiles();
|
||||
IEnumerable<SshProfile> GetSshProfiles();
|
||||
IEnumerable<TabTheme> GetTabThemes();
|
||||
TerminalOptions GetTerminalOptions();
|
||||
TerminalTheme GetTheme(Guid id);
|
||||
@@ -32,10 +38,15 @@ namespace FluentTerminal.App.Services
|
||||
void SaveApplicationSettings(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 SaveTerminalOptions(TerminalOptions terminalOptions);
|
||||
void SaveTheme(TerminalTheme theme, bool newTheme = false);
|
||||
ShellProfile GetShellProfile(Guid id);
|
||||
SshProfile GetSshProfile(Guid id);
|
||||
string ExportSettings();
|
||||
void ImportSettings(string serializedSettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using FluentTerminal.Models;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ISshHelperService
|
||||
{
|
||||
bool IsSsh(Uri uri);
|
||||
|
||||
ISshConnectionInfo ParseSsh(Uri uri);
|
||||
|
||||
Task<SshProfile> GetSshProfileAsync(SshProfile profile);
|
||||
|
||||
Task<SshProfile> GetSavedSshProfileAsync();
|
||||
|
||||
Task<string> ConvertToUriAsync(ISshConnectionInfo sshConnectionInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentTerminal.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ISystemFontService
|
||||
{
|
||||
IEnumerable<string> GetSystemFontFamilies();
|
||||
IEnumerable<FontInfo> GetSystemFontFamilies();
|
||||
}
|
||||
}
|
||||
@@ -8,22 +8,32 @@ namespace FluentTerminal.App.Services
|
||||
{
|
||||
public interface ITrayProcessCommunicationService
|
||||
{
|
||||
event EventHandler<int> TerminalExited;
|
||||
event EventHandler<TerminalExitStatus> TerminalExited;
|
||||
|
||||
void Initialize(IAppServiceConnection appServiceConnection);
|
||||
|
||||
Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
|
||||
Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
|
||||
|
||||
Task ResizeTerminal(int id, TerminalSize size);
|
||||
Task ResizeTerminal(byte id, TerminalSize size);
|
||||
|
||||
Task UpdateToggleWindowKeyBindings();
|
||||
|
||||
Task WriteText(int terminalId, string text);
|
||||
Task Write(byte terminalId, byte[] data);
|
||||
|
||||
void SubscribeForTerminalOutput(int terminalId, Action<string> callback);
|
||||
void SubscribeForTerminalOutput(byte terminalId, Action<byte[]> callback);
|
||||
|
||||
Task CloseTerminal(int terminalId);
|
||||
Task CloseTerminal(byte terminalId);
|
||||
Task<GetAvailablePortResponse> GetAvailablePort();
|
||||
int GetNextTerminalId();
|
||||
byte GetNextTerminalId();
|
||||
|
||||
Task<string> GetUserName();
|
||||
|
||||
Task SaveTextFileAsync(string path, string content);
|
||||
|
||||
Task<string> GetSshConfigDirAsync();
|
||||
|
||||
Task<string[]> GetFilesFromSshConfigDirAsync();
|
||||
|
||||
Task<bool> CheckFileExistsAsync(string path);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
@@ -21,9 +23,12 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
MouseMiddleClickAction = MouseAction.None,
|
||||
MouseRightClickAction = MouseAction.ContextMenu,
|
||||
AlwaysShowTabs = false,
|
||||
AlwaysUseWinPty = true,
|
||||
ShowNewOutputIndicator = false,
|
||||
EnableTrayIcon = true
|
||||
EnableTrayIcon = true,
|
||||
ShowCustomTitleInTitlebar = true,
|
||||
UseMoshByDefault = true,
|
||||
AutoFallbackToWindowsUsernameInLinks = true,
|
||||
RTrimCopiedLines = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,9 +42,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ToggleWindow),
|
||||
Ctrl = false,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Key = (int)ExtendedVirtualKey.Scroll
|
||||
}
|
||||
};
|
||||
@@ -51,8 +53,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.NextTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Key = (int)ExtendedVirtualKey.Tab
|
||||
}
|
||||
};
|
||||
@@ -64,7 +64,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.PreviousTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Tab
|
||||
}
|
||||
@@ -84,9 +83,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
new KeyBinding
|
||||
{
|
||||
Command = command.ToString(),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.Number1 + (command - Command.SwitchToTerm1)
|
||||
}
|
||||
};
|
||||
@@ -98,8 +95,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.NewTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.T
|
||||
}
|
||||
};
|
||||
@@ -111,12 +107,60 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.ConfigurableNewTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.T
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewSshTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.NewSshTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Y
|
||||
}
|
||||
};
|
||||
|
||||
case Command.NewSshWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.NewSshWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.Y
|
||||
}
|
||||
};
|
||||
|
||||
case Command.SavedSshNewTab:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.SavedSshNewTab),
|
||||
Ctrl = true,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.U
|
||||
}
|
||||
};
|
||||
|
||||
case Command.SavedSshNewWindow:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.SavedSshNewWindow),
|
||||
Ctrl = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.U
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
case Command.ChangeTabTitle:
|
||||
return new List<KeyBinding>
|
||||
{
|
||||
@@ -124,7 +168,6 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.ChangeTabTitle),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.R
|
||||
}
|
||||
@@ -137,8 +180,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.CloseTab),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.W
|
||||
}
|
||||
};
|
||||
@@ -150,8 +192,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.NewWindow),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.N
|
||||
}
|
||||
};
|
||||
@@ -163,8 +204,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.ConfigurableNewWindow),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.N
|
||||
}
|
||||
};
|
||||
@@ -176,8 +216,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.ShowSettings),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.Comma
|
||||
}
|
||||
};
|
||||
@@ -189,8 +228,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.Copy),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.C
|
||||
}
|
||||
};
|
||||
@@ -202,8 +240,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.Paste),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.V
|
||||
}
|
||||
};
|
||||
@@ -216,8 +253,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.PasteWithoutNewlines),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = true,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.V
|
||||
}
|
||||
};
|
||||
@@ -229,8 +265,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.Search),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.F
|
||||
}
|
||||
};
|
||||
@@ -241,10 +276,13 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ToggleFullScreen),
|
||||
Ctrl = false,
|
||||
Alt = true,
|
||||
Shift = false,
|
||||
Key = (int)ExtendedVirtualKey.Enter
|
||||
},
|
||||
new KeyBinding
|
||||
{
|
||||
Command = nameof(Command.ToggleFullScreen),
|
||||
Key = (int)ExtendedVirtualKey.F11
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,8 +293,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.SelectAll),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Shift = true,
|
||||
Key = (int)ExtendedVirtualKey.A
|
||||
}
|
||||
};
|
||||
@@ -268,11 +305,11 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
Command = nameof(Command.Clear),
|
||||
Ctrl = true,
|
||||
Alt = false,
|
||||
Shift = false,
|
||||
Alt = true,
|
||||
Key = (int)ExtendedVirtualKey.L
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -290,7 +327,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
new TabTheme
|
||||
{
|
||||
Id = 0,
|
||||
Name = "None",
|
||||
Name = I18N.Translate("TabTheme.None"),
|
||||
BackgroundOpacity = double.NaN,
|
||||
BackgroundPointerOverOpacity = double.NaN,
|
||||
BackgroundPressedOpacity = double.NaN,
|
||||
@@ -301,41 +338,42 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
new TabTheme
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Red",
|
||||
Name = I18N.Translate("TabTheme.Red"),
|
||||
Color = "#E81123"
|
||||
},
|
||||
new TabTheme
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Green",
|
||||
Name = I18N.Translate("TabTheme.Green"),
|
||||
Color = "#10893E"
|
||||
},
|
||||
new TabTheme
|
||||
{
|
||||
Id = 3,
|
||||
Name = "Blue",
|
||||
Name = I18N.Translate("TabTheme.Blue"),
|
||||
Color = "#0078D7"
|
||||
},
|
||||
new TabTheme
|
||||
{
|
||||
Id = 4,
|
||||
Name = "Purple",
|
||||
Name = I18N.Translate("TabTheme.Purple"),
|
||||
Color = "#881798"
|
||||
},
|
||||
new TabTheme
|
||||
{
|
||||
Id = 5,
|
||||
Name = "Orange",
|
||||
Name = I18N.Translate("TabTheme.Orange"),
|
||||
Color = "#FF8C00"
|
||||
},
|
||||
new TabTheme
|
||||
{
|
||||
Id = 6,
|
||||
Name = "Teal",
|
||||
Name = I18N.Translate("TabTheme.Teal"),
|
||||
Color = "#00B7C3"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public TerminalOptions GetDefaultTerminalOptions()
|
||||
{
|
||||
return new TerminalOptions
|
||||
@@ -371,6 +409,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8), // Windows 10 1903+
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
@@ -393,6 +432,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
@@ -414,7 +454,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Location = @"C:\windows\system32\wsl.exe",
|
||||
PreInstalled = true,
|
||||
WorkingDirectory = string.Empty,
|
||||
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||
LineEndingTranslation = LineEndingStyle.ToLF,
|
||||
UseConPty = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7), // Windows 10 1809+
|
||||
KeyBindings = new []
|
||||
{
|
||||
new KeyBinding
|
||||
@@ -577,18 +618,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
Black = "#300A24",
|
||||
Red = "#CC0000",
|
||||
Green = "#4E9A06",
|
||||
Yellow = "#06989A",
|
||||
Yellow = "#C4A000",
|
||||
Blue = "#3465A4",
|
||||
Magenta = "#75507B",
|
||||
Cyan = "#C4A000",
|
||||
Cyan = "#06989A",
|
||||
White = "#D3D7CF",
|
||||
BrightBlack = "#554E53",
|
||||
BrightRed = "#EF2929",
|
||||
BrightGreen = "#8AE234",
|
||||
BrightYellow = "#34E2E2",
|
||||
BrightYellow = "#FCE94F",
|
||||
BrightBlue = "#729FCF",
|
||||
BrightMagenta = "#AD7FA8",
|
||||
BrightCyan = "#FCE94F",
|
||||
BrightCyan = "#34E2E2",
|
||||
BrightWhite = "#EEEEEE"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,13 +11,20 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
private readonly Func<IMessageDialog> _messageDialogFactory;
|
||||
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
||||
private readonly Func<IInputDialog> _inputDialogFactory;
|
||||
private readonly Func<ISshConnectionInfoDialog> _sshConnectionInfoDialogFactory;
|
||||
private readonly Func<ISshProfileSelectionDialog> _sshProfileSelectionDialogFactory;
|
||||
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory, Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory, Func<IInputDialog> inputDialogFactory)
|
||||
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory,
|
||||
Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory,
|
||||
Func<IInputDialog> inputDialogFactory, Func<ISshConnectionInfoDialog> sshConnectionInfoDialogFactory,
|
||||
Func<ISshProfileSelectionDialog> sshProfileSelectionDialogFactory)
|
||||
{
|
||||
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
||||
_messageDialogFactory = messageDialogFactory;
|
||||
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
||||
_inputDialogFactory = inputDialogFactory;
|
||||
_sshConnectionInfoDialogFactory = sshConnectionInfoDialogFactory;
|
||||
_sshProfileSelectionDialogFactory = sshProfileSelectionDialogFactory;
|
||||
}
|
||||
|
||||
public Task<KeyBinding> ShowCreateKeyBindingDialog()
|
||||
@@ -70,5 +77,15 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
|
||||
return dialog.SelectProfile();
|
||||
}
|
||||
|
||||
public Task<ISshConnectionInfo> ShowSshConnectionInfoDialogAsync(ISshConnectionInfo input) =>
|
||||
_sshConnectionInfoDialogFactory().GetSshConnectionInfoAsync(input);
|
||||
|
||||
public Task<SshProfile> ShowSshProfileSelectionDialogAsync()
|
||||
{
|
||||
var dialog = _sshProfileSelectionDialogFactory();
|
||||
|
||||
return dialog.SelectProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
public const string CurrentThemeKey = "CurrentTheme";
|
||||
public const string DefaultShellProfileKey = "DefaultShellProfile";
|
||||
public const string DefaultSshProfileKey = "DefaultSshProfile";
|
||||
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private readonly IApplicationDataContainer _keyBindings;
|
||||
private readonly IApplicationDataContainer _localSettings;
|
||||
private readonly IApplicationDataContainer _roamingSettings;
|
||||
private readonly IApplicationDataContainer _shellProfiles;
|
||||
private readonly IApplicationDataContainer _sshProfiles;
|
||||
private readonly IApplicationDataContainer _themes;
|
||||
|
||||
public SettingsService(IDefaultValueProvider defaultValueProvider, ApplicationDataContainers containers)
|
||||
@@ -29,6 +31,8 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_themes = containers.Themes;
|
||||
_keyBindings = containers.KeyBindings;
|
||||
_shellProfiles = containers.ShellProfiles;
|
||||
_sshProfiles = containers.SshProfiles;
|
||||
|
||||
|
||||
foreach (var theme in _defaultValueProvider.GetPreInstalledThemes())
|
||||
{
|
||||
@@ -47,6 +51,101 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
public string ExportSettings()
|
||||
{
|
||||
var config = new
|
||||
{
|
||||
App = GetApplicationSettings(),
|
||||
KeyBindings = GetCommandKeyBindings(),
|
||||
TerminalOptions = GetTerminalOptions(),
|
||||
Themes = new List<TerminalTheme>(),
|
||||
Profiles = new List<ShellProfile>(),
|
||||
SshProfiles = new List<SshProfile>()
|
||||
};
|
||||
|
||||
foreach (var theme in GetThemes().Where(x => !x.PreInstalled))
|
||||
{
|
||||
config.Themes.Add(theme);
|
||||
}
|
||||
|
||||
foreach (var profile in GetShellProfiles().Where(x => !x.PreInstalled))
|
||||
{
|
||||
config.Profiles.Add(profile);
|
||||
}
|
||||
|
||||
foreach (var profile in GetSshProfiles())
|
||||
{
|
||||
config.SshProfiles.Add(profile);
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(config);
|
||||
}
|
||||
|
||||
public void ImportSettings(string serializedSettings)
|
||||
{
|
||||
var config = new
|
||||
{
|
||||
App = GetApplicationSettings(),
|
||||
KeyBindings = new Dictionary<string, ICollection<KeyBinding>>(),
|
||||
Themes = new List<TerminalTheme>(),
|
||||
Profiles = new List<ShellProfile>(),
|
||||
SshProfiles = new List<SshProfile>(),
|
||||
TerminalOptions = GetTerminalOptions()
|
||||
};
|
||||
|
||||
JsonConvert.PopulateObject(serializedSettings, config);
|
||||
|
||||
SaveApplicationSettings(config.App);
|
||||
|
||||
// Since we set each command sepaartely, we don't need all existing settings
|
||||
foreach (var pair in config.KeyBindings)
|
||||
{
|
||||
SaveKeyBindings(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
// Can't create/modify pre-installed themes
|
||||
foreach (var theme in config.Themes.Where(x => !x.PreInstalled))
|
||||
{
|
||||
var existingTheme = GetTheme(theme.Id);
|
||||
if (existingTheme?.PreInstalled == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SaveTheme(theme, existingTheme != null);
|
||||
}
|
||||
|
||||
// Can't create pre-installed profiles
|
||||
foreach (var profile in config.Profiles.Where(x => !x.PreInstalled))
|
||||
{
|
||||
var existingProfile = GetShellProfile(profile.Id);
|
||||
var isNew = existingProfile.EqualTo(default);
|
||||
|
||||
// You can only edit certain parts of preinstalled profiles
|
||||
if (!isNew && existingProfile.PreInstalled)
|
||||
{
|
||||
existingProfile.WorkingDirectory = profile.WorkingDirectory;
|
||||
existingProfile.Arguments = profile.Arguments;
|
||||
existingProfile.TabThemeId = profile.TabThemeId;
|
||||
existingProfile.TerminalThemeId = profile.TerminalThemeId;
|
||||
existingProfile.LineEndingTranslation = profile.LineEndingTranslation;
|
||||
existingProfile.KeyBindings = profile.KeyBindings;
|
||||
SaveShellProfile(profile, isNew);
|
||||
continue;
|
||||
}
|
||||
SaveShellProfile(profile, isNew);
|
||||
}
|
||||
|
||||
foreach (var profile in config.SshProfiles)
|
||||
{
|
||||
var existingProfile = GetSshProfile(profile.Id);
|
||||
var isNew = existingProfile.EqualTo(default(SshProfile));
|
||||
|
||||
SaveSshProfile(profile, isNew);
|
||||
}
|
||||
|
||||
SaveTerminalOptions(config.TerminalOptions);
|
||||
}
|
||||
|
||||
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
|
||||
|
||||
public event EventHandler<TerminalTheme> ThemeAdded;
|
||||
@@ -58,6 +157,9 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
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)
|
||||
@@ -66,6 +168,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
ShellProfileDeleted?.Invoke(this, id);
|
||||
}
|
||||
|
||||
public void DeleteSshProfile(Guid id)
|
||||
{
|
||||
_sshProfiles.Delete(id.ToString());
|
||||
SshProfileDeleted?.Invoke(this, id);
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
|
||||
public void DeleteTheme(Guid id)
|
||||
{
|
||||
_themes.Delete(id.ToString());
|
||||
@@ -121,11 +231,22 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
public SshProfile GetDefaultSshProfile()
|
||||
{
|
||||
var id = GetDefaultSshProfileId();
|
||||
var profile = _sshProfiles.ReadValueFromJson(id.ToString(), default(SshProfile));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public ShellProfile GetShellProfile(Guid id)
|
||||
{
|
||||
return _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
|
||||
}
|
||||
public SshProfile GetSshProfile(Guid id)
|
||||
{
|
||||
return _sshProfiles.ReadValueFromJson(id.ToString(), default(SshProfile));
|
||||
}
|
||||
|
||||
public Guid GetDefaultShellProfileId()
|
||||
{
|
||||
@@ -135,6 +256,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
}
|
||||
return _defaultValueProvider.GetDefaultShellProfileId();
|
||||
}
|
||||
public Guid GetDefaultSshProfileId()
|
||||
{
|
||||
if (_localSettings.TryGetValue(DefaultSshProfileKey, out object value))
|
||||
{
|
||||
return (Guid)value;
|
||||
}
|
||||
return System.Guid.Empty;
|
||||
}
|
||||
|
||||
public IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings()
|
||||
{
|
||||
@@ -151,6 +280,14 @@ 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();
|
||||
}
|
||||
|
||||
public IEnumerable<TabTheme> GetTabThemes()
|
||||
{
|
||||
@@ -199,15 +336,19 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
_localSettings.SetValue(DefaultShellProfileKey, id);
|
||||
}
|
||||
public void SaveDefaultSshProfileId(Guid id)
|
||||
{
|
||||
_localSettings.SetValue(DefaultSshProfileKey, id);
|
||||
}
|
||||
|
||||
public void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Command), command))
|
||||
if (!Enum.TryParse<Command>(command, true, out var enumValue))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_keyBindings.WriteValueAsJson(command.ToString(), keyBindings);
|
||||
_keyBindings.WriteValueAsJson(enumValue.ToString(), keyBindings);
|
||||
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
@@ -223,6 +364,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
ShellProfileAdded?.Invoke(this, shellProfile);
|
||||
}
|
||||
}
|
||||
public void SaveSshProfile(SshProfile sshProfile, bool newShell = false)
|
||||
{
|
||||
_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);
|
||||
|
||||
if (newShell)
|
||||
{
|
||||
SshProfileAdded?.Invoke(this, sshProfile);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveTerminalOptions(TerminalOptions terminalOptions)
|
||||
{
|
||||
|
||||
@@ -5,8 +5,8 @@ using FluentTerminal.Models.Responses;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
@@ -14,18 +14,18 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private IAppServiceConnection _appServiceConnection;
|
||||
private readonly Dictionary<int, Action<string>> _terminalOutputHandlers;
|
||||
private int _nextTerminalId = 0;
|
||||
private readonly Dictionary<byte, Action<byte[]>> _terminalOutputHandlers;
|
||||
private byte _nextTerminalId = 0;
|
||||
|
||||
public event EventHandler<int> TerminalExited;
|
||||
public event EventHandler<TerminalExitStatus> TerminalExited;
|
||||
|
||||
public TrayProcessCommunicationService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_terminalOutputHandlers = new Dictionary<int, Action<string>>();
|
||||
_terminalOutputHandlers = new Dictionary<byte, Action<byte[]>>();
|
||||
}
|
||||
|
||||
public int GetNextTerminalId()
|
||||
public byte GetNextTerminalId()
|
||||
{
|
||||
return _nextTerminalId++;
|
||||
}
|
||||
@@ -35,11 +35,110 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
var request = new GetAvailablePortRequest();
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response = JsonConvert.DeserializeObject<GetAvailablePortResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
|
||||
return JsonConvert.DeserializeObject<GetAvailablePortResponse>(responseMessage[MessageKeys.Content]);
|
||||
Logger.Instance.Debug("Received GetAvailablePortResponse: {@response}", response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
|
||||
private string _userName;
|
||||
|
||||
public async Task<string> GetUserName()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userName))
|
||||
{
|
||||
// Returning the username from cache
|
||||
return _userName;
|
||||
}
|
||||
|
||||
GetUserNameResponse 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]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Instance.Error(e, "Error while trying to get username.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.Instance.Debug("Received GetUserNameResponse: {@response}", response);
|
||||
|
||||
_userName = response.UserName;
|
||||
|
||||
return _userName;
|
||||
}
|
||||
|
||||
public async Task SaveTextFileAsync(string path, string content)
|
||||
{
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(new SaveTextFileRequest {Path = path, Content = content}));
|
||||
|
||||
var response = JsonConvert.DeserializeObject<CommonResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
throw new Exception(string.IsNullOrEmpty(response.Error) ? "Failed to save the file." : response.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private static string _sshConfigDir;
|
||||
|
||||
public async Task<string> GetSshConfigDirAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_sshConfigDir))
|
||||
{
|
||||
var response = await GetSshConfigFolderAsync(false);
|
||||
|
||||
if (response?.Success ?? false)
|
||||
{
|
||||
_sshConfigDir = response.Path;
|
||||
}
|
||||
}
|
||||
|
||||
return _sshConfigDir;
|
||||
}
|
||||
|
||||
public async Task<string[]> GetFilesFromSshConfigDirAsync()
|
||||
{
|
||||
var response = await GetSshConfigFolderAsync(false);
|
||||
|
||||
if (response == null || !response.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_sshConfigDir))
|
||||
{
|
||||
_sshConfigDir = response.Path;
|
||||
}
|
||||
|
||||
return response.Files;
|
||||
}
|
||||
|
||||
private async Task<GetSshConfigFolderResponse> GetSshConfigFolderAsync(bool includeContent)
|
||||
{
|
||||
var responseMessage =
|
||||
await _appServiceConnection.SendMessageAsync(CreateMessage(new GetSshConfigFolderRequest
|
||||
{IncludeContent = includeContent}));
|
||||
|
||||
return JsonConvert.DeserializeObject<GetSshConfigFolderResponse>(
|
||||
(string) responseMessage[MessageKeys.Content]);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckFileExistsAsync(string path)
|
||||
{
|
||||
var responseMessage =
|
||||
await _appServiceConnection.SendMessageAsync(CreateMessage(new CheckFileExistsRequest {Path = path}));
|
||||
|
||||
return JsonConvert.DeserializeObject<CommonResponse>((string) responseMessage[MessageKeys.Content]).Success;
|
||||
}
|
||||
|
||||
public async Task<CreateTerminalResponse> CreateTerminal(byte id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
|
||||
{
|
||||
var request = new CreateTerminalRequest
|
||||
{
|
||||
@@ -49,9 +148,14 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
SessionType = sessionType
|
||||
};
|
||||
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
Logger.Instance.Debug("Sending CreateTerminalRequest: {@request}", request);
|
||||
|
||||
return JsonConvert.DeserializeObject<CreateTerminalResponse>(responseMessage[MessageKeys.Content]);
|
||||
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
var response = JsonConvert.DeserializeObject<CreateTerminalResponse>((string)responseMessage[MessageKeys.Content]);
|
||||
|
||||
Logger.Instance.Debug("Received CreateTerminalResponse: {@response}", response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public void Initialize(IAppServiceConnection appServiceConnection)
|
||||
@@ -60,33 +164,38 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
_appServiceConnection.MessageReceived += OnMessageReceived;
|
||||
}
|
||||
|
||||
private void OnMessageReceived(object sender, IDictionary<string, string> e)
|
||||
private void OnMessageReceived(object sender, IDictionary<string, object> e)
|
||||
{
|
||||
var messageType = e[MessageKeys.Type];
|
||||
var messageType = (byte)e[MessageKeys.Type];
|
||||
var messageContent = e[MessageKeys.Content];
|
||||
|
||||
if (messageType == nameof(DisplayTerminalOutputRequest))
|
||||
switch (messageType)
|
||||
{
|
||||
var request = JsonConvert.DeserializeObject<DisplayTerminalOutputRequest>(messageContent);
|
||||
case Constants.TerminalBufferRequestIdentifier:
|
||||
var terminalId = (byte)e[MessageKeys.TerminalId];
|
||||
|
||||
if (_terminalOutputHandlers.ContainsKey(request.TerminalId))
|
||||
{
|
||||
_terminalOutputHandlers[request.TerminalId].Invoke(request.Output);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("output was not handled: " + request.Output);
|
||||
}
|
||||
}
|
||||
else if (messageType == nameof(TerminalExitedRequest))
|
||||
{
|
||||
var request = JsonConvert.DeserializeObject<TerminalExitedRequest>(messageContent);
|
||||
if (_terminalOutputHandlers.ContainsKey(terminalId))
|
||||
{
|
||||
_terminalOutputHandlers[terminalId].Invoke((byte[])messageContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Instance.Error("Received output for unknown terminal Id {id}", terminalId);
|
||||
}
|
||||
break;
|
||||
case TerminalExitedRequest.Identifier:
|
||||
var request = JsonConvert.DeserializeObject<TerminalExitedRequest>((string)messageContent);
|
||||
Logger.Instance.Debug("Received TerminalExitedRequest: {@request}", request);
|
||||
|
||||
TerminalExited?.Invoke(this, request.TerminalId);
|
||||
TerminalExited?.Invoke(this, request.ToStatus());
|
||||
break;
|
||||
default:
|
||||
Logger.Instance.Error("Received unknown message type: {messageType}", messageType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Task ResizeTerminal(int id, TerminalSize size)
|
||||
public Task ResizeTerminal(byte id, TerminalSize size)
|
||||
{
|
||||
var request = new ResizeTerminalRequest
|
||||
{
|
||||
@@ -97,7 +206,7 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
public void SubscribeForTerminalOutput(int terminalId, Action<string> callback)
|
||||
public void SubscribeForTerminalOutput(byte terminalId, Action<byte[]> callback)
|
||||
{
|
||||
_terminalOutputHandlers[terminalId] = callback;
|
||||
}
|
||||
@@ -114,32 +223,32 @@ namespace FluentTerminal.App.Services.Implementation
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
public Task WriteText(int id, string text)
|
||||
public Task Write(byte id, byte[] data)
|
||||
{
|
||||
var request = new WriteTextRequest
|
||||
var message = new ValueSet
|
||||
{
|
||||
TerminalId = id,
|
||||
Text = text
|
||||
[MessageKeys.Type] = Constants.TerminalBufferRequestIdentifier,
|
||||
[MessageKeys.TerminalId] = id,
|
||||
[MessageKeys.Content] = data
|
||||
};
|
||||
|
||||
return _appServiceConnection.SendMessageAsync(message);
|
||||
}
|
||||
|
||||
public Task CloseTerminal(byte terminalId)
|
||||
{
|
||||
var request = new TerminalExitedRequest(terminalId, -1);
|
||||
|
||||
Logger.Instance.Debug("Sending TerminalExitedRequest: {@request}", request);
|
||||
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
public Task CloseTerminal(int terminalId)
|
||||
private ValueSet CreateMessage(IMessage content)
|
||||
{
|
||||
var request = new TerminalExitedRequest
|
||||
return new ValueSet
|
||||
{
|
||||
TerminalId = terminalId
|
||||
};
|
||||
|
||||
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
|
||||
}
|
||||
|
||||
private IDictionary<string, string> CreateMessage(object content)
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
[MessageKeys.Type] = content.GetType().Name,
|
||||
[MessageKeys.Type] = content.Identifier,
|
||||
[MessageKeys.Content] = JsonConvert.SerializeObject(content)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public sealed class Logger
|
||||
{
|
||||
public enum LogLevel
|
||||
{
|
||||
Verbose = 0,
|
||||
Debug = 1,
|
||||
Information = 2,
|
||||
Warning = 3,
|
||||
Error = 4,
|
||||
Fatal = 5
|
||||
}
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
public LogLevel LogLevel { get; set; } = LogLevel.Error;
|
||||
}
|
||||
|
||||
public static Logger Instance { get; } = new Logger();
|
||||
|
||||
private Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Initialize(string filePath, Configuration configuration)
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(filePath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7))
|
||||
.MinimumLevel.Is((LogEventLevel)configuration.LogLevel)
|
||||
.CreateLogger();
|
||||
|
||||
// Log file is not created during unhandled exception processing.
|
||||
// Force log file creation with the highest severity initialization record.
|
||||
Log.Fatal("Initialized");
|
||||
}
|
||||
|
||||
public void Debug(string text, params object[] propertyValues)
|
||||
{
|
||||
Log.Debug(text, propertyValues, propertyValues);
|
||||
}
|
||||
|
||||
public void Information(string text, params object[] propertyValues)
|
||||
{
|
||||
Log.Information(text, propertyValues);
|
||||
}
|
||||
|
||||
public void Warning(string text, params object[] propertyValues)
|
||||
{
|
||||
Log.Warning(text, propertyValues);
|
||||
}
|
||||
|
||||
public void Error(string text, params object[] propertyValues)
|
||||
{
|
||||
Log.Error(text, propertyValues);
|
||||
}
|
||||
|
||||
public void Error(Exception exception, string text, params object[] propertyValues)
|
||||
{
|
||||
Log.Error(exception, text, propertyValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +1,162 @@
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public class Terminal
|
||||
{
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private Func<Task<string>> _selectedTextCallback;
|
||||
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
Id = _trayProcessCommunicationService.GetNextTerminalId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be observed by both view and viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler Closed;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> KeyboardCommandReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by view
|
||||
/// </summary>
|
||||
public event EventHandler<string> OutputReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<TerminalSize> SizeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> TitleChanged;
|
||||
|
||||
public string FallbackTitle { get; private set; }
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To be called by either view or viewmodel
|
||||
/// </summary>
|
||||
public async Task Close()
|
||||
{
|
||||
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by viewmodel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<string> GetSelectedText()
|
||||
{
|
||||
return _selectedTextCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void ProcessKeyboardCommand(string command)
|
||||
{
|
||||
KeyboardCommandReceived?.Invoke(this, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be set by view
|
||||
/// </summary>
|
||||
public void RegisterSelectedTextCallback(Func<Task<string>> selectedTextCallback)
|
||||
{
|
||||
_selectedTextCallback = selectedTextCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be called by view
|
||||
/// </summary>
|
||||
public async Task SetSize(TerminalSize size)
|
||||
{
|
||||
await _trayProcessCommunicationService.ResizeTerminal(Id, size);
|
||||
SizeChanged?.Invoke(this, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = FallbackTitle;
|
||||
}
|
||||
TitleChanged?.Invoke(this, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view when ready
|
||||
/// </summary>
|
||||
/// <param name="shellProfile"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="sessionType"></param>
|
||||
public async Task StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
||||
{
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public Task Write(string text)
|
||||
{
|
||||
return _trayProcessCommunicationService.WriteText(Id, text);
|
||||
}
|
||||
}
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using FluentTerminal.Models.Responses;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.Services
|
||||
{
|
||||
public class Terminal
|
||||
{
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private Func<Task<string>> _selectedTextCallback;
|
||||
private bool _closingFromUI = false;
|
||||
private bool _exited = false;
|
||||
|
||||
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
|
||||
Id = _trayProcessCommunicationService.GetNextTerminalId();
|
||||
}
|
||||
|
||||
private void OnTerminalExited(object sender, TerminalExitStatus status)
|
||||
{
|
||||
if (status.TerminalId != Id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_exited = true;
|
||||
Exited?.Invoke(this, status.ExitCode);
|
||||
|
||||
if (_closingFromUI == true || status.ExitCode <= 0)
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<int> Exited;
|
||||
|
||||
/// <summary>
|
||||
/// To be observed by both view and viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler Closed;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> KeyboardCommandReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by view
|
||||
/// </summary>
|
||||
public event EventHandler<byte[]> OutputReceived;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<TerminalSize> SizeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// to be observed by viewmodel
|
||||
/// </summary>
|
||||
public event EventHandler<string> TitleChanged;
|
||||
|
||||
public string FallbackTitle { get; private set; }
|
||||
|
||||
public byte Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To be called by either view or viewmodel
|
||||
/// </summary>
|
||||
public async Task Close()
|
||||
{
|
||||
if (_exited)
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
_closingFromUI = true;
|
||||
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by viewmodel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<string> GetSelectedText()
|
||||
{
|
||||
return _selectedTextCallback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void ProcessKeyboardCommand(string command)
|
||||
{
|
||||
KeyboardCommandReceived?.Invoke(this, command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be set by view
|
||||
/// </summary>
|
||||
public void RegisterSelectedTextCallback(Func<Task<string>> selectedTextCallback)
|
||||
{
|
||||
_selectedTextCallback = selectedTextCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be called by view
|
||||
/// </summary>
|
||||
public async Task SetSize(TerminalSize size)
|
||||
{
|
||||
await _trayProcessCommunicationService.ResizeTerminal(Id, size);
|
||||
SizeChanged?.Invoke(this, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view
|
||||
/// </summary>
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = FallbackTitle;
|
||||
}
|
||||
TitleChanged?.Invoke(this, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// to be called by view when ready
|
||||
/// </summary>
|
||||
/// <param name="shellProfile"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="sessionType"></param>
|
||||
public async Task<CreateTerminalResponse> StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
||||
{
|
||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||
|
||||
if (response.Success)
|
||||
{
|
||||
FallbackTitle = response.ShellExecutableName;
|
||||
SetTitle(FallbackTitle);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public Task Write(byte[] data)
|
||||
{
|
||||
return _trayProcessCommunicationService.Write(Id, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the ViewModel that this view failed to launch and can be closed.
|
||||
/// </summary>
|
||||
public void ReportLauchFailed()
|
||||
{
|
||||
Closed?.Invoke(this, System.EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace FluentTerminal.App.Services.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Get strings in different languages.
|
||||
/// Author: Zuozishi
|
||||
/// </summary>
|
||||
public static class I18N
|
||||
{
|
||||
public static string Translate(string resource)
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForCurrentView();
|
||||
return resourceLoader.GetString(resource.Replace('.', '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fleck" Version="1.0.3" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
|
||||
<PackageReference Include="ReactiveUI" Version="9.4.1" />
|
||||
<PackageReference Include="Fleck" Version="1.1.0" />
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,342 +1,503 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IDispatcherTimer _dispatcherTimer;
|
||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private ApplicationSettings _applicationSettings;
|
||||
private string _background;
|
||||
private double _backgroundOpacity;
|
||||
private TerminalViewModel _selectedTerminal;
|
||||
private TabsPosition _tabsPosition;
|
||||
|
||||
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
|
||||
_settingsService.ShellProfileAdded += OnShellProfileAdded;
|
||||
_settingsService.ShellProfileDeleted += OnShellProfileDeleted;
|
||||
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
_dialogService = dialogService;
|
||||
_applicationView = applicationView;
|
||||
_dispatcherTimer = dispatcherTimer;
|
||||
_clipboardService = clipboardService;
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal(null, false, Guid.Empty));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddTerminal(null, true, Guid.Empty));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
|
||||
|
||||
// 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++)
|
||||
{
|
||||
var switchCmd = Command.SwitchToTerm1 + i;
|
||||
int tabNumber = i;
|
||||
void handler() => SelectTabNumber(tabNumber);
|
||||
_keyboardCommandService.RegisterCommandHandler(switchCmd.ToString(), handler);
|
||||
}
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NextTab), SelectNextTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.PreviousTab), SelectPreviousTab);
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), () => NewWindow(false));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewWindow), () => NewWindow(true));
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ShowSettings), ShowSettings);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ToggleFullScreen), ToggleFullScreen);
|
||||
|
||||
foreach (ShellProfile profile in _settingsService.GetShellProfiles())
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(profile.Id.ToString(), () => AddTerminal(profile.WorkingDirectory, false, profile.Id));
|
||||
}
|
||||
|
||||
var currentTheme = _settingsService.GetCurrentTheme();
|
||||
var options = _settingsService.GetTerminalOptions();
|
||||
Background = currentTheme.Colors.Background;
|
||||
BackgroundOpacity = options.BackgroundOpacity;
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
TabsPosition = _applicationSettings.TabsPosition;
|
||||
|
||||
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false, Guid.Empty));
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
|
||||
_applicationView.CloseRequested += OnCloseRequest;
|
||||
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnShellProfileDeleted(object sender, Guid e)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
}
|
||||
|
||||
private void OnShellProfileAdded(object sender, ShellProfile e)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddTerminal(e.WorkingDirectory, false, e.Id));
|
||||
}
|
||||
|
||||
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
RaisePropertyChanged(nameof(ShowTabsOnBottom));
|
||||
}
|
||||
|
||||
public event EventHandler Closed;
|
||||
|
||||
public event EventHandler<NewWindowRequestedEventArgs> NewWindowRequested;
|
||||
|
||||
public event EventHandler ShowSettingsRequested;
|
||||
|
||||
public event EventHandler ShowAboutRequested;
|
||||
|
||||
public RelayCommand AddTerminalCommand { get; }
|
||||
|
||||
public bool ShowTabsOnTop
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Top && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
}
|
||||
|
||||
public bool ShowTabsOnBottom
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Bottom && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
}
|
||||
|
||||
public string Background
|
||||
{
|
||||
get => _background;
|
||||
set => Set(ref _background, value);
|
||||
}
|
||||
|
||||
public double BackgroundOpacity
|
||||
{
|
||||
get => _backgroundOpacity;
|
||||
set => Set(ref _backgroundOpacity, value);
|
||||
}
|
||||
|
||||
public TerminalViewModel SelectedTerminal
|
||||
{
|
||||
get => _selectedTerminal;
|
||||
set
|
||||
{
|
||||
if (SelectedTerminal != null)
|
||||
{
|
||||
_selectedTerminal.IsSelected = false;
|
||||
}
|
||||
if (Set(ref _selectedTerminal, value))
|
||||
{
|
||||
SelectedTerminal?.FocusTerminal();
|
||||
if (SelectedTerminal != null)
|
||||
{
|
||||
SelectedTerminal.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
{
|
||||
get => _tabsPosition;
|
||||
set => Set(ref _tabsPosition, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a new terminal window, either with a specified terminal profile, with the default, or by showing the profile selection dialog.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task AddTerminal(string startupDirectory, bool showProfileSelection, Guid profileId)
|
||||
{
|
||||
return _applicationView.RunOnDispatcherThread(async () =>
|
||||
{
|
||||
ShellProfile profile = null;
|
||||
|
||||
if (showProfileSelection)
|
||||
{
|
||||
profile = await _dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
_applicationView.TryClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (profileId == Guid.Empty)
|
||||
{
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
else
|
||||
{
|
||||
profile = _settingsService.GetShellProfile(profileId);
|
||||
}
|
||||
|
||||
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
||||
_applicationSettings, startupDirectory, profile, _applicationView, _dispatcherTimer, _clipboardService);
|
||||
terminal.Closed += OnTerminalCloseRequested;
|
||||
Terminals.Add(terminal);
|
||||
|
||||
SelectedTerminal = terminal;
|
||||
});
|
||||
}
|
||||
|
||||
public void CloseAllTerminals()
|
||||
{
|
||||
foreach (var terminal in Terminals)
|
||||
{
|
||||
terminal.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseCurrentTab()
|
||||
{
|
||||
SelectedTerminal?.CloseCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void NewWindow(bool showProfileSelection)
|
||||
{
|
||||
var args = new NewWindowRequestedEventArgs
|
||||
{
|
||||
ShowProfileSelection = showProfileSelection
|
||||
};
|
||||
|
||||
NewWindowRequested?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
_applicationSettings = e;
|
||||
TabsPosition = e.TabsPosition;
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
RaisePropertyChanged(nameof(ShowTabsOnBottom));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task OnCloseRequest(object sender, CancelableEventArgs e)
|
||||
{
|
||||
if (_applicationSettings.ConfirmClosingWindows)
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this window?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
CloseAllTerminals();
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Cancelled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseAllTerminals();
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnCurrentThemeChanged(object sender, Guid e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var currentTheme = _settingsService.GetTheme(e);
|
||||
Background = currentTheme.Colors.Background;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnTerminalCloseRequested(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
terminal.Close();
|
||||
if (SelectedTerminal == terminal)
|
||||
{
|
||||
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
|
||||
}
|
||||
Terminals.Remove(terminal);
|
||||
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
_applicationView.TryClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
await _applicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
BackgroundOpacity = e.BackgroundOpacity;
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectTabNumber(int tabNumber)
|
||||
{
|
||||
if (tabNumber < Terminals.Count)
|
||||
{
|
||||
SelectedTerminal = Terminals[tabNumber];
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectNextTab()
|
||||
{
|
||||
var currentIndex = Terminals.IndexOf(SelectedTerminal);
|
||||
var nextIndex = (currentIndex + 1) % Terminals.Count;
|
||||
SelectedTerminal = Terminals[nextIndex];
|
||||
}
|
||||
|
||||
private void SelectPreviousTab()
|
||||
{
|
||||
var currentIndex = Terminals.IndexOf(SelectedTerminal);
|
||||
var previousIndex = (currentIndex - 1 + Terminals.Count) % Terminals.Count;
|
||||
SelectedTerminal = Terminals[previousIndex];
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
{
|
||||
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
{
|
||||
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ToggleFullScreen()
|
||||
{
|
||||
_applicationView.ToggleFullScreen();
|
||||
}
|
||||
}
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IDispatcherTimer _dispatcherTimer;
|
||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private readonly ISshHelperService _sshHelperService;
|
||||
private ApplicationSettings _applicationSettings;
|
||||
private string _background;
|
||||
private double _backgroundOpacity;
|
||||
private TerminalViewModel _selectedTerminal;
|
||||
private TabsPosition _tabsPosition;
|
||||
private string _windowTitle;
|
||||
|
||||
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, ISshHelperService sshHelperService)
|
||||
{
|
||||
_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;
|
||||
ApplicationView = applicationView;
|
||||
_dispatcherTimer = dispatcherTimer;
|
||||
_clipboardService = clipboardService;
|
||||
_sshHelperService = sshHelperService;
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), async () => await AddLocalTabAsync());
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewSshTab), async () => await AddSshTabAsync());
|
||||
_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));
|
||||
|
||||
// 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++)
|
||||
{
|
||||
var switchCmd = Command.SwitchToTerm1 + i;
|
||||
int tabNumber = i;
|
||||
// ReSharper disable once InconsistentNaming
|
||||
void handler() => SelectTabNumber(tabNumber);
|
||||
_keyboardCommandService.RegisterCommandHandler(switchCmd.ToString(), handler);
|
||||
}
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NextTab), SelectNextTab);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.PreviousTab), SelectPreviousTab);
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), () => NewWindow(NewWindowAction.StartDefaultLocalTerminal));
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewWindow), () => NewWindow(NewWindowAction.ShowProfileSelection));
|
||||
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ShowSettings), ShowSettings);
|
||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ToggleFullScreen), ToggleFullScreen);
|
||||
|
||||
foreach (ShellProfile profile in _settingsService.GetShellProfiles())
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(profile.Id.ToString(), async () => await AddLocalTabOrWindowAsync(profile.Id));
|
||||
}
|
||||
|
||||
foreach (SshProfile profile in _settingsService.GetSshProfiles())
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(profile.Id.ToString(), async () => await AddSshTabOrWindowAsync(profile.Id));
|
||||
}
|
||||
|
||||
var currentTheme = _settingsService.GetCurrentTheme();
|
||||
var options = _settingsService.GetTerminalOptions();
|
||||
Background = currentTheme.Colors.Background;
|
||||
BackgroundOpacity = options.BackgroundOpacity;
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
TabsPosition = _applicationSettings.TabsPosition;
|
||||
|
||||
AddLocalShellCommand = new RelayCommand(async () => await AddLocalTabAsync());
|
||||
AddRemoteShellCommand = new RelayCommand(async () => await AddSshTabAsync());
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
|
||||
ApplicationView.CloseRequested += OnCloseRequest;
|
||||
ApplicationView.Closed += OnClosed;
|
||||
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnClosed(object sender, EventArgs e)
|
||||
{
|
||||
Closed?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnShellProfileDeleted(object sender, Guid e)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
}
|
||||
|
||||
private void OnShellProfileAdded(object sender, ShellProfile e)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddLocalTabOrWindowAsync(e.Id));
|
||||
}
|
||||
|
||||
private void OnSshProfileAdded(object sender, ShellProfile e)
|
||||
{
|
||||
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddSshTabOrWindowAsync(e.Id));
|
||||
}
|
||||
private void OnSshProfileDeleted(object sender, Guid e)
|
||||
{
|
||||
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
|
||||
}
|
||||
|
||||
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
RaisePropertyChanged(nameof(ShowTabsOnBottom));
|
||||
}
|
||||
|
||||
public event EventHandler Closed;
|
||||
|
||||
public event EventHandler<NewWindowRequestedEventArgs> NewWindowRequested;
|
||||
|
||||
public event EventHandler ShowSettingsRequested;
|
||||
|
||||
public event EventHandler ShowAboutRequested;
|
||||
|
||||
public event EventHandler ActivatedMv;
|
||||
|
||||
public void FocusWindow()
|
||||
{
|
||||
ActivatedMv?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public RelayCommand AddLocalShellCommand { get; }
|
||||
public RelayCommand AddRemoteShellCommand { get; }
|
||||
|
||||
public string WindowTitle
|
||||
{
|
||||
get => _windowTitle;
|
||||
set
|
||||
{
|
||||
if (Set(ref _windowTitle, value))
|
||||
{
|
||||
ApplicationView.Title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowTabsOnTop
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Top && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
}
|
||||
|
||||
public bool ShowTabsOnBottom
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Bottom && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
|
||||
}
|
||||
|
||||
public string Background
|
||||
{
|
||||
get => _background;
|
||||
set => Set(ref _background, value);
|
||||
}
|
||||
|
||||
public double BackgroundOpacity
|
||||
{
|
||||
get => _backgroundOpacity;
|
||||
set => Set(ref _backgroundOpacity, value);
|
||||
}
|
||||
|
||||
public TerminalViewModel SelectedTerminal
|
||||
{
|
||||
get => _selectedTerminal;
|
||||
set
|
||||
{
|
||||
if (SelectedTerminal != null)
|
||||
{
|
||||
_selectedTerminal.IsSelected = false;
|
||||
}
|
||||
if (Set(ref _selectedTerminal, value))
|
||||
{
|
||||
SelectedTerminal?.FocusTerminal();
|
||||
if (SelectedTerminal != null)
|
||||
{
|
||||
SelectedTerminal.IsSelected = true;
|
||||
if (_applicationSettings.ShowCustomTitleInTitlebar)
|
||||
{
|
||||
WindowTitle = SelectedTerminal.TabTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowTitle = SelectedTerminal.ShellTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
{
|
||||
get => _tabsPosition;
|
||||
set => Set(ref _tabsPosition, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
|
||||
|
||||
public IApplicationView ApplicationView { get; }
|
||||
|
||||
public async Task AddConfigurableTerminal()
|
||||
{
|
||||
var profile = await _dialogService.ShowProfileSelectionDialogAsync();
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
await ApplicationView.TryClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await AddTerminalAsync(profile);
|
||||
}
|
||||
|
||||
public async Task AddSshTabAsync()
|
||||
{
|
||||
SshProfile profile = await _sshHelperService.GetSshProfileAsync(new SshProfile());
|
||||
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)
|
||||
{
|
||||
case NewTerminalLocation.Tab:
|
||||
var profile = _settingsService.GetSshProfile(shellProfileId);
|
||||
return AddTerminalAsync(profile);
|
||||
case NewTerminalLocation.Window:
|
||||
NewWindow(NewWindowAction.StartSshTerminal, shellProfileId);
|
||||
return Task.FromResult(0);
|
||||
default:
|
||||
throw new ArgumentException("unknown NewTerminalLocation");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddSavedSshTerminalAsync()
|
||||
{
|
||||
ShellProfile profile = await _sshHelperService.GetSavedSshProfileAsync();
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
// User selected "Cancel"
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
await ApplicationView.TryClose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddTerminalAsync(profile);
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddLocalTabAsync()
|
||||
{
|
||||
var profile = _settingsService.GetDefaultShellProfile();
|
||||
return AddTerminalAsync(profile);
|
||||
}
|
||||
|
||||
public Task AddLocalTabOrWindowAsync(Guid shellProfileId)
|
||||
{
|
||||
switch (_applicationSettings.NewTerminalLocation)
|
||||
{
|
||||
case NewTerminalLocation.Tab:
|
||||
var profile = _settingsService.GetShellProfile(shellProfileId);
|
||||
return AddTerminalAsync(profile);
|
||||
case NewTerminalLocation.Window:
|
||||
NewWindow(NewWindowAction.StartLocalTerminal, shellProfileId);
|
||||
return Task.FromResult(0);
|
||||
default:
|
||||
throw new ArgumentException("unknown NewTerminalLocation");
|
||||
}
|
||||
}
|
||||
|
||||
public Task AddTerminalAsync(ShellProfile profile)
|
||||
{
|
||||
return ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
||||
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService);
|
||||
|
||||
terminal.Closed += OnTerminalClosed;
|
||||
terminal.ShellTitleChanged += Terminal_ShellTitleChanged;
|
||||
terminal.CustomTitleChanged += Terminal_CustomTitleChanged;
|
||||
Terminals.Add(terminal);
|
||||
|
||||
SelectedTerminal = terminal;
|
||||
});
|
||||
}
|
||||
|
||||
private void Terminal_CustomTitleChanged(object sender, string e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
if (terminal.IsSelected && _applicationSettings.ShowCustomTitleInTitlebar)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e))
|
||||
{
|
||||
WindowTitle = terminal.ShellTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowTitle = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_ShellTitleChanged(object sender, string e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal && terminal.IsSelected && !_applicationSettings.ShowCustomTitleInTitlebar)
|
||||
{
|
||||
WindowTitle = e;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CloseAllTerminals()
|
||||
{
|
||||
foreach (var terminal in Terminals)
|
||||
{
|
||||
terminal.Closed -= OnTerminalClosed;
|
||||
terminal.ShellTitleChanged -= Terminal_ShellTitleChanged;
|
||||
terminal.CustomTitleChanged -= Terminal_CustomTitleChanged;
|
||||
await terminal.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseCurrentTab()
|
||||
{
|
||||
SelectedTerminal?.CloseCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void NewWindow(NewWindowAction showSelection)
|
||||
{
|
||||
NewWindow(showSelection, Guid.Empty);
|
||||
}
|
||||
|
||||
private void NewWindow(NewWindowAction showSelection, Guid profileId)
|
||||
{
|
||||
var args = new NewWindowRequestedEventArgs
|
||||
{
|
||||
Action = showSelection,
|
||||
ProfileId = profileId
|
||||
};
|
||||
NewWindowRequested?.Invoke(this, args);
|
||||
}
|
||||
|
||||
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
_applicationSettings = e;
|
||||
TabsPosition = e.TabsPosition;
|
||||
RaisePropertyChanged(nameof(ShowTabsOnTop));
|
||||
RaisePropertyChanged(nameof(ShowTabsOnBottom));
|
||||
|
||||
if (e.ShowCustomTitleInTitlebar)
|
||||
{
|
||||
WindowTitle = SelectedTerminal?.TabTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowTitle = SelectedTerminal?.ShellTitle;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task OnCloseRequest(object sender, CancelableEventArgs e)
|
||||
{
|
||||
if (_applicationSettings.ConfirmClosingWindows)
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmCloseWindow"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
await CloseAllTerminals();
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Cancelled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await CloseAllTerminals();
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnCurrentThemeChanged(object sender, Guid e)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
var currentTheme = _settingsService.GetTheme(e);
|
||||
Background = currentTheme.Colors.Background;
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnTerminalClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is TerminalViewModel terminal)
|
||||
{
|
||||
if (SelectedTerminal == terminal)
|
||||
{
|
||||
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
|
||||
}
|
||||
Logger.Instance.Debug("Terminal with Id: {@id} closed.", terminal.Terminal.Id);
|
||||
Terminals.Remove(terminal);
|
||||
|
||||
if (Terminals.Count == 0)
|
||||
{
|
||||
await ApplicationView.TryClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
await ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
BackgroundOpacity = e.BackgroundOpacity;
|
||||
});
|
||||
}
|
||||
|
||||
private void SelectTabNumber(int tabNumber)
|
||||
{
|
||||
if (tabNumber < Terminals.Count)
|
||||
{
|
||||
SelectedTerminal = Terminals[tabNumber];
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectNextTab()
|
||||
{
|
||||
var currentIndex = Terminals.IndexOf(SelectedTerminal);
|
||||
var nextIndex = (currentIndex + 1) % Terminals.Count;
|
||||
SelectedTerminal = Terminals[nextIndex];
|
||||
}
|
||||
|
||||
private void SelectPreviousTab()
|
||||
{
|
||||
var currentIndex = Terminals.IndexOf(SelectedTerminal);
|
||||
var previousIndex = (currentIndex - 1 + Terminals.Count) % Terminals.Count;
|
||||
SelectedTerminal = Terminals[previousIndex];
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
{
|
||||
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
{
|
||||
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void ToggleFullScreen()
|
||||
{
|
||||
ApplicationView.ToggleFullScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using GalaSoft.MvvmLight;
|
||||
using System;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class OverlayViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDispatcherTimer _overlayTimer;
|
||||
private bool _showOverlay;
|
||||
private string _overlayContent;
|
||||
|
||||
public OverlayViewModel(IDispatcherTimer dispatcherTimer)
|
||||
{
|
||||
_overlayTimer = dispatcherTimer;
|
||||
_overlayTimer.Interval = new TimeSpan(0, 0, 2);
|
||||
_overlayTimer.Tick += OnResizeOverlayTimerFinished;
|
||||
}
|
||||
|
||||
public bool ShowOverlay
|
||||
{
|
||||
get => _showOverlay;
|
||||
set => Set(ref _showOverlay, value);
|
||||
}
|
||||
|
||||
public string OverlayContent
|
||||
{
|
||||
get => _overlayContent;
|
||||
set => Set(ref _overlayContent, value);
|
||||
}
|
||||
|
||||
public void Show(string message)
|
||||
{
|
||||
OverlayContent = message;
|
||||
ShowOverlay = true;
|
||||
|
||||
if (_overlayTimer.IsEnabled)
|
||||
{
|
||||
_overlayTimer.Stop();
|
||||
}
|
||||
_overlayTimer.Start();
|
||||
}
|
||||
|
||||
private void OnResizeOverlayTimerFinished(object sender, object e)
|
||||
{
|
||||
_overlayTimer.Stop();
|
||||
ShowOverlay = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
@@ -15,25 +17,44 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IStartupTaskService _startupTaskService;
|
||||
private bool _canEnableStartupTask;
|
||||
private bool _editingNewTerminalLocation;
|
||||
private bool _editingTabsPosition;
|
||||
private bool _editingInactiveTabColorMode;
|
||||
private bool _startupTaskEnabled;
|
||||
private bool _shouldRestartForTrayMessage;
|
||||
private string _startupTaskErrorMessage;
|
||||
private bool _needsToRestart;
|
||||
private readonly IApplicationLanguageService _applicationLanguageService;
|
||||
|
||||
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IStartupTaskService startupTaskService)
|
||||
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider,
|
||||
IStartupTaskService startupTaskService, IApplicationLanguageService applicationLanguageService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_startupTaskService = startupTaskService;
|
||||
_applicationLanguageService = applicationLanguageService;
|
||||
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
public IEnumerable<string> Languages => _applicationLanguageService.Languages;
|
||||
|
||||
public bool NeedsToRestart
|
||||
{
|
||||
get => _needsToRestart;
|
||||
set => Set(ref _needsToRestart, value);
|
||||
}
|
||||
|
||||
public string SelectedLanguage
|
||||
{
|
||||
get => _applicationLanguageService.GetCurrentLanguage();
|
||||
set
|
||||
{
|
||||
_applicationLanguageService.SetLanguage(value);
|
||||
NeedsToRestart = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnNavigatedTo()
|
||||
{
|
||||
var startupTaskStatus = await _startupTaskService.GetStatus();
|
||||
@@ -54,14 +75,14 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool AlwaysUseWinPty
|
||||
public bool ShowCustomTitleInTitlebar
|
||||
{
|
||||
get => _applicationSettings.AlwaysUseWinPty;
|
||||
get => _applicationSettings.ShowCustomTitleInTitlebar;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.AlwaysUseWinPty != value)
|
||||
if (_applicationSettings.ShowCustomTitleInTitlebar != value)
|
||||
{
|
||||
_applicationSettings.AlwaysUseWinPty = value;
|
||||
_applicationSettings.ShowCustomTitleInTitlebar = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
@@ -105,10 +126,52 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseMoshByDefault
|
||||
{
|
||||
get => _applicationSettings.UseMoshByDefault;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.UseMoshByDefault != value)
|
||||
{
|
||||
_applicationSettings.UseMoshByDefault = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoFallbackToWindowsUsernameInLinks
|
||||
{
|
||||
get => _applicationSettings.AutoFallbackToWindowsUsernameInLinks;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.AutoFallbackToWindowsUsernameInLinks != value)
|
||||
{
|
||||
_applicationSettings.AutoFallbackToWindowsUsernameInLinks = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RTrimCopiedLines
|
||||
{
|
||||
get => _applicationSettings.RTrimCopiedLines;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.RTrimCopiedLines != value)
|
||||
{
|
||||
_applicationSettings.RTrimCopiedLines = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool BottomIsSelected
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Bottom;
|
||||
set => TabsPosition = TabsPosition.Bottom;
|
||||
set { if (value) TabsPosition = TabsPosition.Bottom; }
|
||||
}
|
||||
|
||||
public bool CanEnableStartupTask
|
||||
@@ -150,15 +213,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _applicationSettings.NewTerminalLocation;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.NewTerminalLocation != value && !_editingNewTerminalLocation)
|
||||
if (_applicationSettings.NewTerminalLocation != value)
|
||||
{
|
||||
_editingNewTerminalLocation = true;
|
||||
_applicationSettings.NewTerminalLocation = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(WindowIsSelected));
|
||||
RaisePropertyChanged(nameof(TabIsSelected));
|
||||
_editingNewTerminalLocation = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +247,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool TabIsSelected
|
||||
{
|
||||
get => NewTerminalLocation == NewTerminalLocation.Tab;
|
||||
set => NewTerminalLocation = NewTerminalLocation.Tab;
|
||||
set { if (value) NewTerminalLocation = NewTerminalLocation.Tab; }
|
||||
}
|
||||
|
||||
public TabsPosition TabsPosition
|
||||
@@ -196,15 +255,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _applicationSettings.TabsPosition;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.TabsPosition != value && !_editingTabsPosition)
|
||||
if (_applicationSettings.TabsPosition != value)
|
||||
{
|
||||
_editingTabsPosition = true;
|
||||
_applicationSettings.TabsPosition = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(TopIsSelected));
|
||||
RaisePropertyChanged(nameof(BottomIsSelected));
|
||||
_editingTabsPosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +267,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool TopIsSelected
|
||||
{
|
||||
get => TabsPosition == TabsPosition.Top;
|
||||
set => TabsPosition = TabsPosition.Top;
|
||||
set { if (value) TabsPosition = TabsPosition.Top; }
|
||||
}
|
||||
|
||||
public bool UnderlineSelectedTab
|
||||
@@ -232,13 +287,13 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool BackgroundIsSelected
|
||||
{
|
||||
get => InactiveTabColorMode == InactiveTabColorMode.Background;
|
||||
set => InactiveTabColorMode = InactiveTabColorMode.Background;
|
||||
set { if (value) InactiveTabColorMode = InactiveTabColorMode.Background; }
|
||||
}
|
||||
|
||||
public bool UnderlinedIsSelected
|
||||
{
|
||||
get => InactiveTabColorMode == InactiveTabColorMode.Underlined;
|
||||
set => InactiveTabColorMode = InactiveTabColorMode.Underlined;
|
||||
set { if (value) InactiveTabColorMode = InactiveTabColorMode.Underlined; }
|
||||
}
|
||||
|
||||
public InactiveTabColorMode InactiveTabColorMode
|
||||
@@ -246,15 +301,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _applicationSettings.InactiveTabColorMode;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.InactiveTabColorMode != value && !_editingInactiveTabColorMode)
|
||||
if (_applicationSettings.InactiveTabColorMode != value)
|
||||
{
|
||||
_editingInactiveTabColorMode = true;
|
||||
_applicationSettings.InactiveTabColorMode = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(BackgroundIsSelected));
|
||||
RaisePropertyChanged(nameof(UnderlinedIsSelected));
|
||||
_editingInactiveTabColorMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,12 +313,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool WindowIsSelected
|
||||
{
|
||||
get => NewTerminalLocation == NewTerminalLocation.Window;
|
||||
set => NewTerminalLocation = NewTerminalLocation.Window;
|
||||
set { if (value) NewTerminalLocation = NewTerminalLocation.Window; }
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the general settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreGeneralSettings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -278,9 +329,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
InactiveTabColorMode = defaults.InactiveTabColorMode;
|
||||
NewTerminalLocation = defaults.NewTerminalLocation;
|
||||
AlwaysShowTabs = defaults.AlwaysShowTabs;
|
||||
AlwaysUseWinPty = defaults.AlwaysUseWinPty;
|
||||
ShowNewOutputIndicator = defaults.ShowNewOutputIndicator;
|
||||
EnableTrayIcon = defaults.EnableTrayIcon;
|
||||
ShowCustomTitleInTitlebar = defaults.ShowCustomTitleInTitlebar;
|
||||
UseMoshByDefault = defaults.UseMoshByDefault;
|
||||
AutoFallbackToWindowsUsernameInLinks = defaults.AutoFallbackToWindowsUsernameInLinks;
|
||||
RTrimCopiedLines = defaults.RTrimCopiedLines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,13 +356,13 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
case StartupTaskStatus.DisabledByUser:
|
||||
StartupTaskEnabled = false;
|
||||
StartupTaskErrorMessage = "Disabled by user. Please reactivate it in the Startup tab of the Task Manager.";
|
||||
StartupTaskErrorMessage = I18N.Translate("DisabledByUser");
|
||||
CanEnableStartupTask = false;
|
||||
break;
|
||||
|
||||
case StartupTaskStatus.DisabledByPolicy:
|
||||
StartupTaskEnabled = false;
|
||||
StartupTaskErrorMessage = "Disabled by policy.";
|
||||
StartupTaskErrorMessage = I18N.Translate("DisabledByPolicy");
|
||||
CanEnableStartupTask = false;
|
||||
break;
|
||||
}
|
||||
@@ -323,7 +377,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
else
|
||||
{
|
||||
_startupTaskService.DisableStartupTask();
|
||||
await _startupTaskService.DisableStartupTask();
|
||||
status = await _startupTaskService.GetStatus();
|
||||
}
|
||||
SetStartupTaskPropertiesForStatus(status);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
@@ -118,7 +119,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this keybinding?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
foreach (var value in Enum.GetValues(typeof(Command)))
|
||||
{
|
||||
var command = (Command)value;
|
||||
var viewModel = new KeyBindingsViewModel(command.ToString(), _dialogService, EnumHelper.GetEnumDescription(command), true);
|
||||
var viewModel = new KeyBindingsViewModel(command.ToString(), _dialogService, I18N.Translate($"{nameof(Command)}.{command}"), true);
|
||||
foreach (var keyBinding in _keyBindings[command.ToString()])
|
||||
{
|
||||
viewModel.Add(keyBinding);
|
||||
@@ -79,7 +79,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the default keybindings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreKeybindings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
@@ -16,8 +14,6 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private readonly ApplicationSettings _applicationSettings;
|
||||
private bool _editingMouseRightClickAction;
|
||||
private bool _editingMouseMiddleClickAction;
|
||||
|
||||
public MousePageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
|
||||
{
|
||||
@@ -48,16 +44,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _applicationSettings.MouseRightClickAction;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.MouseRightClickAction != value && !_editingMouseRightClickAction)
|
||||
if (_applicationSettings.MouseRightClickAction != value)
|
||||
{
|
||||
_editingMouseRightClickAction = true;
|
||||
_applicationSettings.MouseRightClickAction = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(MouseRightClickNoneIsSelected));
|
||||
RaisePropertyChanged(nameof(MouseRightClickContextMenuIsSelected));
|
||||
RaisePropertyChanged(nameof(MouseRightClickPasteIsSelected));
|
||||
_editingMouseRightClickAction = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,19 +56,19 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool MouseRightClickNoneIsSelected
|
||||
{
|
||||
get => MouseRightClickAction == MouseAction.None;
|
||||
set => MouseRightClickAction = MouseAction.None;
|
||||
set { if (value) MouseRightClickAction = MouseAction.None; }
|
||||
}
|
||||
|
||||
public bool MouseRightClickContextMenuIsSelected
|
||||
{
|
||||
get => MouseRightClickAction == MouseAction.ContextMenu;
|
||||
set => MouseRightClickAction = MouseAction.ContextMenu;
|
||||
set { if (value) MouseRightClickAction = MouseAction.ContextMenu; }
|
||||
}
|
||||
|
||||
public bool MouseRightClickPasteIsSelected
|
||||
{
|
||||
get => MouseRightClickAction == MouseAction.Paste;
|
||||
set => MouseRightClickAction = MouseAction.Paste;
|
||||
set { if (value) MouseRightClickAction = MouseAction.Paste; }
|
||||
}
|
||||
|
||||
public MouseAction MouseMiddleClickAction
|
||||
@@ -85,16 +76,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _applicationSettings.MouseMiddleClickAction;
|
||||
set
|
||||
{
|
||||
if (_applicationSettings.MouseMiddleClickAction != value && !_editingMouseMiddleClickAction)
|
||||
if (_applicationSettings.MouseMiddleClickAction != value)
|
||||
{
|
||||
_editingMouseMiddleClickAction = true;
|
||||
_applicationSettings.MouseMiddleClickAction = value;
|
||||
_settingsService.SaveApplicationSettings(_applicationSettings);
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(MouseMiddleClickNoneIsSelected));
|
||||
RaisePropertyChanged(nameof(MouseMiddleClickContextMenuIsSelected));
|
||||
RaisePropertyChanged(nameof(MouseMiddleClickPasteIsSelected));
|
||||
_editingMouseMiddleClickAction = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,26 +88,26 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
public bool MouseMiddleClickNoneIsSelected
|
||||
{
|
||||
get => MouseMiddleClickAction == MouseAction.None;
|
||||
set => MouseMiddleClickAction = MouseAction.None;
|
||||
set { if (value) MouseMiddleClickAction = MouseAction.None; }
|
||||
}
|
||||
|
||||
public bool MouseMiddleClickContextMenuIsSelected
|
||||
{
|
||||
get => MouseMiddleClickAction == MouseAction.ContextMenu;
|
||||
set => MouseMiddleClickAction = MouseAction.ContextMenu;
|
||||
set { if (value) MouseMiddleClickAction = MouseAction.ContextMenu; }
|
||||
}
|
||||
|
||||
public bool MouseMiddleClickPasteIsSelected
|
||||
{
|
||||
get => MouseMiddleClickAction == MouseAction.Paste;
|
||||
set => MouseMiddleClickAction = MouseAction.Paste;
|
||||
set { if (value) MouseMiddleClickAction = MouseAction.Paste; }
|
||||
}
|
||||
|
||||
public RelayCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the mouse settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreMouseSettings"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -98,12 +98,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
private void Clone(ShellProfileViewModel shellProfile)
|
||||
{
|
||||
var cloned = new ShellProfile(shellProfile.Model)
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = $"Copy of {shellProfile.Name}"
|
||||
};
|
||||
var cloned = shellProfile.Model.Clone();
|
||||
|
||||
cloned.Id = Guid.NewGuid();
|
||||
cloned.PreInstalled = false;
|
||||
cloned.Name = $"Copy of {shellProfile.Name}";
|
||||
|
||||
AddShellProfile(cloned);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
public class SshProfilesPageViewModel: ViewModelBase
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private SshProfileViewModel _selectedShellProfile;
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
|
||||
public SshProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService,
|
||||
IFileSystemService fileSystemService, IApplicationView applicationView,
|
||||
IDefaultValueProvider defaultValueProvider,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_applicationView = applicationView;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
|
||||
CreateSshProfileCommand = new RelayCommand(CreateSshProfile);
|
||||
CloneCommand = new RelayCommand<SshProfileViewModel>(Clone);
|
||||
|
||||
foreach (var sshProfile in _settingsService.GetSshProfiles())
|
||||
{
|
||||
var viewModel = new SshProfileViewModel(sshProfile, settingsService, dialogService,
|
||||
fileSystemService, applicationView, defaultValueProvider, _trayProcessCommunicationService, false);
|
||||
viewModel.Deleted += OnSshProfileDeleted;
|
||||
SshProfiles.Add(viewModel);
|
||||
}
|
||||
|
||||
if (SshProfiles.Count == 0)
|
||||
{
|
||||
CreateSshProfile();
|
||||
}
|
||||
|
||||
SelectedSshProfile = SshProfiles.First();
|
||||
}
|
||||
|
||||
private void OnSshProfileDeleted(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is SshProfileViewModel shellProfile)
|
||||
{
|
||||
if (SelectedSshProfile == shellProfile)
|
||||
{
|
||||
SelectedSshProfile = SshProfiles.First();
|
||||
}
|
||||
SshProfiles.Remove(shellProfile);
|
||||
_settingsService.DeleteSshProfile(shellProfile.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand CreateSshProfileCommand { get; }
|
||||
|
||||
public RelayCommand<SshProfileViewModel> CloneCommand { get; }
|
||||
|
||||
public void CreateSshProfile()
|
||||
{
|
||||
var shellProfile = new SshProfile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PreInstalled = false,
|
||||
Name = "New SSH profile",
|
||||
KeyBindings = new List<KeyBinding>(),
|
||||
UseMosh = _settingsService.GetApplicationSettings().UseMoshByDefault
|
||||
};
|
||||
|
||||
AddSshProfile(shellProfile);
|
||||
}
|
||||
|
||||
private void Clone(SshProfileViewModel shellProfile)
|
||||
{
|
||||
var cloned = (SshProfile) shellProfile.Model.Clone();
|
||||
|
||||
cloned.Id = Guid.NewGuid();
|
||||
cloned.PreInstalled = false;
|
||||
cloned.Name = $"Copy of {shellProfile.Name}";
|
||||
|
||||
foreach (KeyBinding keyBinding in cloned.KeyBindings)
|
||||
{
|
||||
keyBinding.Command = cloned.Id.ToString();
|
||||
}
|
||||
AddSshProfile(cloned);
|
||||
}
|
||||
|
||||
private void AddSshProfile(SshProfile sshProfile)
|
||||
{
|
||||
var viewModel = new SshProfileViewModel(sshProfile, _settingsService, _dialogService, _fileSystemService,
|
||||
_applicationView, _defaultValueProvider, _trayProcessCommunicationService, true);
|
||||
|
||||
viewModel.EditCommand.Execute(null);
|
||||
viewModel.Deleted += OnSshProfileDeleted;
|
||||
SshProfiles.Add(viewModel);
|
||||
SelectedSshProfile = viewModel;
|
||||
}
|
||||
|
||||
public SshProfileViewModel SelectedSshProfile
|
||||
{
|
||||
get => _selectedShellProfile;
|
||||
set => Set(ref _selectedShellProfile, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<SshProfileViewModel> SshProfiles { get; } = new ObservableCollection<SshProfileViewModel>();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,56 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
public class TerminalPageViewModel : ReactiveObject
|
||||
public class TerminalPageViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TerminalOptions _terminalOptions;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private bool _isEditingCursorStyle;
|
||||
private bool _isEditingScrollBarStyle;
|
||||
|
||||
public bool BarIsSelected
|
||||
{
|
||||
get => CursorStyle == CursorStyle.Bar;
|
||||
set => CursorStyle = CursorStyle.Bar;
|
||||
set { if (value) CursorStyle = CursorStyle.Bar; }
|
||||
}
|
||||
|
||||
public bool BlockIsSelected
|
||||
{
|
||||
get => CursorStyle == CursorStyle.Block;
|
||||
set => CursorStyle = CursorStyle.Block;
|
||||
set { if (value) CursorStyle = CursorStyle.Block; }
|
||||
}
|
||||
|
||||
public bool UnderlineIsSelected
|
||||
{
|
||||
get => CursorStyle == CursorStyle.Underline;
|
||||
set => CursorStyle = CursorStyle.Underline;
|
||||
set { if (value) CursorStyle = CursorStyle.Underline; }
|
||||
}
|
||||
|
||||
public bool HiddenIsSelected
|
||||
{
|
||||
get => ScrollBarStyle == ScrollBarStyle.Hidden;
|
||||
set => ScrollBarStyle = ScrollBarStyle.Hidden;
|
||||
set { if (value) ScrollBarStyle = ScrollBarStyle.Hidden; }
|
||||
}
|
||||
|
||||
public bool AutoHidingIsSelected
|
||||
{
|
||||
get => ScrollBarStyle == ScrollBarStyle.AutoHiding;
|
||||
set => ScrollBarStyle = ScrollBarStyle.AutoHiding;
|
||||
set { if (value) ScrollBarStyle = ScrollBarStyle.AutoHiding; }
|
||||
}
|
||||
|
||||
public bool VisibleIsSelected
|
||||
{
|
||||
get => ScrollBarStyle == ScrollBarStyle.Visible;
|
||||
set => ScrollBarStyle = ScrollBarStyle.Visible;
|
||||
set { if (value) ScrollBarStyle = ScrollBarStyle.Visible; }
|
||||
}
|
||||
|
||||
public bool CursorBlink
|
||||
@@ -65,7 +62,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.CursorBlink = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +76,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.FontFamily = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +89,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
if (_terminalOptions.BackgroundOpacity != value)
|
||||
{
|
||||
_terminalOptions.BackgroundOpacity = value;
|
||||
this.RaisePropertyChanged();
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +103,8 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
if (_terminalOptions.Padding != value)
|
||||
{
|
||||
_terminalOptions.Padding = value;
|
||||
this.RaisePropertyChanged();
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,12 +125,12 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.ScrollBackLimit = intValue;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,12 +144,26 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.BoldText = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Fonts { get; }
|
||||
public bool ShowTextCopied
|
||||
{
|
||||
get => _terminalOptions.ShowTextCopied;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.ShowTextCopied != value)
|
||||
{
|
||||
_terminalOptions.ShowTextCopied = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<FontInfo> Fonts { get; }
|
||||
|
||||
public int FontSize
|
||||
{
|
||||
@@ -161,7 +174,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
{
|
||||
_terminalOptions.FontSize = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,16 +188,11 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _terminalOptions.CursorStyle;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.CursorStyle != value && !_isEditingCursorStyle)
|
||||
if (_terminalOptions.CursorStyle != value)
|
||||
{
|
||||
_isEditingCursorStyle = true;
|
||||
_terminalOptions.CursorStyle = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(BlockIsSelected));
|
||||
this.RaisePropertyChanged(nameof(BarIsSelected));
|
||||
this.RaisePropertyChanged(nameof(UnderlineIsSelected));
|
||||
_isEditingCursorStyle = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,23 +202,18 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
get => _terminalOptions.ScrollBarStyle;
|
||||
set
|
||||
{
|
||||
if (_terminalOptions.ScrollBarStyle != value && !_isEditingScrollBarStyle)
|
||||
if (_terminalOptions.ScrollBarStyle != value)
|
||||
{
|
||||
_isEditingScrollBarStyle = true;
|
||||
_terminalOptions.ScrollBarStyle = value;
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
this.RaisePropertyChanged();
|
||||
this.RaisePropertyChanged(nameof(HiddenIsSelected));
|
||||
this.RaisePropertyChanged(nameof(AutoHidingIsSelected));
|
||||
this.RaisePropertyChanged(nameof(VisibleIsSelected));
|
||||
_isEditingScrollBarStyle = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the default terminal options?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmRestoreTerminalOptions"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -223,6 +226,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
BoldText = defaults.BoldText;
|
||||
BackgroundOpacity = defaults.BackgroundOpacity;
|
||||
ScrollBackLimit = defaults.ScrollBackLimit.ToString();
|
||||
ShowTextCopied = defaults.ShowTextCopied;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,19 +238,10 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
|
||||
|
||||
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s);
|
||||
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s.Name);
|
||||
Sizes = Enumerable.Range(1, 72);
|
||||
|
||||
_terminalOptions = _settingsService.GetTerminalOptions();
|
||||
|
||||
this.ObservableForProperty(x => x.BackgroundOpacity).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
|
||||
{
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
});
|
||||
this.ObservableForProperty(x => x.Padding).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
|
||||
{
|
||||
_settingsService.SaveTerminalOptions(_terminalOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.Models;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
@@ -118,7 +119,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
|
||||
if (parser == null)
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc("Import theme failed", "No suitable parser found", DialogButton.OK).ConfigureAwait(false);
|
||||
await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("ImportThemeFailed"), I18N.Translate("NoSuitableParserFound"), DialogButton.OK).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ namespace FluentTerminal.App.ViewModels.Settings
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await _dialogService.ShowMessageDialogAsnyc("Import theme failed", exception.Message, DialogButton.OK).ConfigureAwait(false);
|
||||
await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("ImportThemeFailed"), exception.Message, DialogButton.OK).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ 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, IUpdateService updateService, IApplicationView applicationView, IApplicationLanguageService applicationLanguageService)
|
||||
{
|
||||
About = new AboutPageViewModel(settingsService, updateService, applicationView);
|
||||
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, defaultValueProvider, trayProcessCommunicationService);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService);
|
||||
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
|
||||
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService, applicationLanguageService);
|
||||
Profiles = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
|
||||
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider, systemFontService);
|
||||
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService);
|
||||
Mouse = new MousePageViewModel(settingsService, dialogService, defaultValueProvider);
|
||||
SshProfiles = new SshProfilesPageViewModel(settingsService, dialogService, fileSystemService, applicationView, defaultValueProvider, trayProcessCommunicationService);
|
||||
}
|
||||
|
||||
public event EventHandler Closed;
|
||||
@@ -25,11 +26,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public GeneralPageViewModel General { get; }
|
||||
public KeyBindingsPageViewModel KeyBindings { get; }
|
||||
public ProfilesPageViewModel Shell { get; }
|
||||
public ProfilesPageViewModel Profiles { get; }
|
||||
public TerminalPageViewModel Terminal { get; }
|
||||
public ThemesPageViewModel Themes { get; }
|
||||
public MousePageViewModel Mouse { get; }
|
||||
public AboutPageViewModel About { get; }
|
||||
public SshProfilesPageViewModel SshProfiles { get; }
|
||||
|
||||
public void NavigateToAboutPage()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.App.ViewModels.Settings;
|
||||
using FluentTerminal.Models;
|
||||
@@ -14,76 +15,261 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class ShellProfileViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private string _arguments;
|
||||
private ShellProfile _fallbackProfile;
|
||||
private bool _inEditMode;
|
||||
private bool _isDefault;
|
||||
private string _location;
|
||||
private LineEndingStyle _lineEndingStyle;
|
||||
private string _name;
|
||||
private bool _isNew;
|
||||
private TabTheme _selectedTabTheme;
|
||||
private TerminalTheme _selectedTerminalTheme;
|
||||
private string _workingDirectory;
|
||||
private readonly IApplicationView _applicationView;
|
||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||
private bool _editingLineEndingStyle;
|
||||
#region Static
|
||||
|
||||
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView, IDefaultValueProvider defaultValueProvider, Boolean isNew)
|
||||
private static readonly LineEndingStyle[] LineEndingStylesArray =
|
||||
Enum.GetValues(typeof(LineEndingStyle)).Cast<LineEndingStyle>().ToArray();
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Fields
|
||||
|
||||
private ShellProfile _fallbackProfile;
|
||||
|
||||
protected readonly IDialogService DialogService;
|
||||
protected readonly IFileSystemService FileSystemService;
|
||||
protected readonly ISettingsService SettingsService;
|
||||
protected readonly IApplicationView ApplicationView;
|
||||
protected readonly IDefaultValueProvider DefaultValueProvider;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
protected bool IsNew { get; set; }
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
public ShellProfile Model { get; private set; }
|
||||
|
||||
public bool PreInstalled { get; private set; }
|
||||
|
||||
private bool _useConPty;
|
||||
|
||||
public bool UseConPty
|
||||
{
|
||||
get => _useConPty;
|
||||
set => Set(ref _useConPty, value);
|
||||
}
|
||||
|
||||
private string _arguments;
|
||||
|
||||
public string Arguments
|
||||
{
|
||||
get => _arguments;
|
||||
set => Set(ref _arguments, value);
|
||||
}
|
||||
|
||||
private bool _inEditMode;
|
||||
|
||||
public bool InEditMode
|
||||
{
|
||||
get => _inEditMode;
|
||||
set => Set(ref _inEditMode, value);
|
||||
}
|
||||
|
||||
private bool _isDefault;
|
||||
|
||||
public bool IsDefault
|
||||
{
|
||||
get => _isDefault;
|
||||
set => Set(ref _isDefault, value);
|
||||
}
|
||||
|
||||
private string _location;
|
||||
|
||||
public string Location
|
||||
{
|
||||
get => _location;
|
||||
set => Set(ref _location, value);
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => Set(ref _name, value);
|
||||
}
|
||||
|
||||
private TabTheme _selectedTabTheme;
|
||||
|
||||
public TabTheme SelectedTabTheme
|
||||
{
|
||||
get => _selectedTabTheme ?? (_selectedTabTheme = TabThemes.FirstOrDefault(t => t.Id.Equals(_tabThemeId)));
|
||||
set
|
||||
{
|
||||
if (value != null && Set(ref _selectedTabTheme, value))
|
||||
{
|
||||
_tabThemeId = value.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _tabThemeId;
|
||||
|
||||
public int TabThemeId
|
||||
{
|
||||
get => _tabThemeId;
|
||||
set
|
||||
{
|
||||
if (Set(ref _tabThemeId, value))
|
||||
{
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id.Equals(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TerminalTheme _selectedTerminalTheme;
|
||||
|
||||
public TerminalTheme SelectedTerminalTheme
|
||||
{
|
||||
get => _selectedTerminalTheme ??
|
||||
(_selectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id.Equals(_terminalThemeId)));
|
||||
set
|
||||
{
|
||||
if (value != null && Set(ref _selectedTerminalTheme, value))
|
||||
{
|
||||
_terminalThemeId = value.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Guid _terminalThemeId;
|
||||
|
||||
public Guid TerminalThemeId
|
||||
{
|
||||
get => _terminalThemeId;
|
||||
set
|
||||
{
|
||||
if (Set(ref _terminalThemeId, value))
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id.Equals(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _workingDirectory;
|
||||
|
||||
public string WorkingDirectory
|
||||
{
|
||||
get => _workingDirectory;
|
||||
set => Set(ref _workingDirectory, value);
|
||||
}
|
||||
|
||||
private LineEndingStyle _lineEndingTranslation;
|
||||
|
||||
public LineEndingStyle LineEndingTranslation
|
||||
{
|
||||
get => _lineEndingTranslation;
|
||||
set => Set(ref _lineEndingTranslation, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
|
||||
public KeyBindingsViewModel KeyBindings { get; }
|
||||
|
||||
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
|
||||
|
||||
public ObservableCollection<LineEndingStyle> LineEndingStyles { get; } =
|
||||
new ObservableCollection<LineEndingStyle>(LineEndingStylesArray);
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler Deleted;
|
||||
|
||||
public event EventHandler SetAsDefault;
|
||||
|
||||
#endregion Events
|
||||
|
||||
#region Commands
|
||||
|
||||
public IAsyncCommand BrowseForCustomShellCommand { get; }
|
||||
|
||||
public IAsyncCommand BrowseForWorkingDirectoryCommand { get; }
|
||||
|
||||
public IAsyncCommand CancelEditCommand { get; }
|
||||
|
||||
public IAsyncCommand DeleteCommand { get; }
|
||||
|
||||
public RelayCommand EditCommand { get; }
|
||||
|
||||
public IAsyncCommand SaveChangesCommand { get; }
|
||||
|
||||
public RelayCommand SetDefaultCommand { get; }
|
||||
|
||||
public IAsyncCommand RestoreDefaultsCommand { get; }
|
||||
|
||||
public IAsyncCommand AddKeyboardShortcutCommand { get; }
|
||||
|
||||
#endregion Commands
|
||||
|
||||
#region Constrcutor
|
||||
|
||||
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService,
|
||||
IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView,
|
||||
IDefaultValueProvider defaultValueProvider, bool isNew)
|
||||
{
|
||||
Model = shellProfile;
|
||||
_settingsService = settingsService;
|
||||
_dialogService = dialogService;
|
||||
_fileSystemService = fileSystemService;
|
||||
_applicationView = applicationView;
|
||||
_defaultValueProvider = defaultValueProvider;
|
||||
_isNew = isNew;
|
||||
SettingsService = settingsService;
|
||||
DialogService = dialogService;
|
||||
FileSystemService = fileSystemService;
|
||||
ApplicationView = applicationView;
|
||||
DefaultValueProvider = defaultValueProvider;
|
||||
IsNew = isNew;
|
||||
|
||||
_settingsService.ThemeAdded += OnThemeAdded;
|
||||
_settingsService.ThemeDeleted += OnThemeDeleted;
|
||||
SettingsService.ThemeAdded += OnThemeAdded;
|
||||
SettingsService.ThemeDeleted += OnThemeDeleted;
|
||||
|
||||
TabThemes = new ObservableCollection<TabTheme>(settingsService.GetTabThemes());
|
||||
|
||||
TerminalThemes = new ObservableCollection<TerminalTheme>();
|
||||
TerminalThemes.Add(new TerminalTheme
|
||||
TerminalThemes = new ObservableCollection<TerminalTheme>
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = "Default"
|
||||
});
|
||||
foreach (var theme in _settingsService.GetThemes())
|
||||
new TerminalTheme
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Name = "Default"
|
||||
}
|
||||
};
|
||||
foreach (var theme in SettingsService.GetThemes())
|
||||
{
|
||||
TerminalThemes.Add(theme);
|
||||
}
|
||||
|
||||
KeyBindings = new KeyBindingsViewModel(shellProfile.Id.ToString(), _dialogService, string.Empty, false);
|
||||
KeyBindings = new KeyBindingsViewModel(shellProfile.Id.ToString(), DialogService, string.Empty, false);
|
||||
|
||||
InitializeViewModelProperties(shellProfile);
|
||||
InitializeViewModelPropertiesPrivate(shellProfile);
|
||||
|
||||
SetDefaultCommand = new RelayCommand(SetDefault);
|
||||
DeleteCommand = new AsyncCommand(Delete, CanDelete);
|
||||
EditCommand = new RelayCommand(Edit);
|
||||
CancelEditCommand = new AsyncCommand(CancelEdit);
|
||||
SaveChangesCommand = new RelayCommand(SaveChanges);
|
||||
SaveChangesCommand = new AsyncCommand(SaveChangesAsync);
|
||||
AddKeyboardShortcutCommand = new AsyncCommand(AddKeyboardShortcut);
|
||||
BrowseForCustomShellCommand = new AsyncCommand(BrowseForCustomShell);
|
||||
BrowseForWorkingDirectoryCommand = new AsyncCommand(BrowseForWorkingDirectory);
|
||||
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
|
||||
}
|
||||
|
||||
private void InitializeViewModelProperties(ShellProfile shellProfile)
|
||||
#endregion Constrcutor
|
||||
|
||||
#region Methods
|
||||
|
||||
// Loads view model properties from the input shellProfile
|
||||
private void InitializeViewModelPropertiesPrivate(ShellProfile shellProfile)
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == shellProfile.TerminalThemeId);
|
||||
Id = shellProfile.Id;
|
||||
Name = shellProfile.Name;
|
||||
Arguments = shellProfile.Arguments;
|
||||
Location = shellProfile.Location;
|
||||
WorkingDirectory = shellProfile.WorkingDirectory;
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == shellProfile.TabThemeId);
|
||||
TerminalThemeId = shellProfile.TerminalThemeId;
|
||||
TabThemeId = shellProfile.TabThemeId;
|
||||
PreInstalled = shellProfile.PreInstalled;
|
||||
LineEndingStyle = shellProfile.LineEndingTranslation;
|
||||
LineEndingTranslation = shellProfile.LineEndingTranslation;
|
||||
UseConPty = shellProfile.UseConPty;
|
||||
|
||||
KeyBindings.Clear();
|
||||
foreach (var keyBinding in shellProfile.KeyBindings.Select(x => new KeyBinding(x)).ToList())
|
||||
@@ -92,9 +278,41 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
// Loads view model properties from the input shellProfile
|
||||
protected virtual void InitializeViewModelProperties(ShellProfile shellProfile) =>
|
||||
InitializeViewModelPropertiesPrivate(shellProfile);
|
||||
|
||||
// Fills the input profile properties the view model properties. (Inverse of InitializeViewModelProperties()).
|
||||
protected virtual Task FillProfileAsync(ShellProfile profile)
|
||||
{
|
||||
profile.Id = Id;
|
||||
profile.Name = Name;
|
||||
profile.Arguments = Arguments;
|
||||
profile.Location = Location;
|
||||
profile.WorkingDirectory = WorkingDirectory;
|
||||
profile.TerminalThemeId = TerminalThemeId;
|
||||
profile.TabThemeId = TabThemeId;
|
||||
profile.PreInstalled = PreInstalled;
|
||||
profile.LineEndingTranslation = LineEndingTranslation;
|
||||
profile.UseConPty = UseConPty;
|
||||
profile.KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Used to "remember" the current state.
|
||||
protected virtual async Task<ShellProfile> CreateProfileAsync()
|
||||
{
|
||||
var profile = new ShellProfile();
|
||||
|
||||
await FillProfileAsync(profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private void OnThemeAdded(object sender, TerminalTheme e)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() =>
|
||||
ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
TerminalThemes.Add(e);
|
||||
});
|
||||
@@ -104,7 +322,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (SelectedTerminalTheme.Id == e)
|
||||
{
|
||||
_applicationView.RunOnDispatcherThread(() =>
|
||||
ApplicationView.RunOnDispatcherThread(() =>
|
||||
{
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(x => x.Id == Guid.Empty);
|
||||
Model.TerminalThemeId = Guid.Empty;
|
||||
@@ -116,138 +334,15 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Deleted;
|
||||
public event EventHandler SetAsDefault;
|
||||
|
||||
public IAsyncCommand BrowseForCustomShellCommand { get; }
|
||||
public IAsyncCommand BrowseForWorkingDirectoryCommand { get; }
|
||||
public IAsyncCommand CancelEditCommand { get; }
|
||||
public IAsyncCommand DeleteCommand { get; }
|
||||
public RelayCommand EditCommand { get; }
|
||||
public RelayCommand SaveChangesCommand { get; }
|
||||
public RelayCommand SetDefaultCommand { get; }
|
||||
public IAsyncCommand RestoreDefaultsCommand { get; }
|
||||
public IAsyncCommand AddKeyboardShortcutCommand { get; }
|
||||
public ObservableCollection<TabTheme> TabThemes { get; }
|
||||
public KeyBindingsViewModel KeyBindings { get; }
|
||||
|
||||
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
|
||||
public ShellProfile Model { get; private set; }
|
||||
public bool PreInstalled { get; private set; }
|
||||
|
||||
public string Arguments
|
||||
public virtual async Task SaveChangesAsync()
|
||||
{
|
||||
get => _arguments;
|
||||
set => Set(ref _arguments, value);
|
||||
}
|
||||
await FillProfileAsync(Model);
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
public bool InEditMode
|
||||
{
|
||||
get => _inEditMode;
|
||||
set => Set(ref _inEditMode, value);
|
||||
}
|
||||
|
||||
public bool IsDefault
|
||||
{
|
||||
get => _isDefault;
|
||||
set => Set(ref _isDefault, value);
|
||||
}
|
||||
|
||||
public string Location
|
||||
{
|
||||
get => _location;
|
||||
set => Set(ref _location, value);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => Set(ref _name, value);
|
||||
}
|
||||
|
||||
public TabTheme SelectedTabTheme
|
||||
{
|
||||
get => _selectedTabTheme;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Set(ref _selectedTabTheme, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalTheme SelectedTerminalTheme
|
||||
{
|
||||
get => _selectedTerminalTheme;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
Set(ref _selectedTerminalTheme, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string WorkingDirectory
|
||||
{
|
||||
get => _workingDirectory;
|
||||
set => Set(ref _workingDirectory, value);
|
||||
}
|
||||
|
||||
public LineEndingStyle LineEndingStyle
|
||||
{
|
||||
get => _lineEndingStyle;
|
||||
set
|
||||
{
|
||||
if (_lineEndingStyle != value && !_editingLineEndingStyle)
|
||||
{
|
||||
_editingLineEndingStyle = true;
|
||||
_lineEndingStyle = value;
|
||||
RaisePropertyChanged();
|
||||
RaisePropertyChanged(nameof(DoNotModifyIsSelected));
|
||||
RaisePropertyChanged(nameof(ToCRLFIsSelected));
|
||||
RaisePropertyChanged(nameof(ToLFIsSelected));
|
||||
_editingLineEndingStyle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoNotModifyIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.DoNotModify;
|
||||
set => LineEndingStyle = LineEndingStyle.DoNotModify;
|
||||
}
|
||||
|
||||
public bool ToCRLFIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.ToCRLF;
|
||||
set => LineEndingStyle = LineEndingStyle.ToCRLF;
|
||||
}
|
||||
|
||||
public bool ToLFIsSelected
|
||||
{
|
||||
get => LineEndingStyle == LineEndingStyle.ToLF;
|
||||
set => LineEndingStyle = LineEndingStyle.ToLF;
|
||||
}
|
||||
|
||||
public void SaveChanges()
|
||||
{
|
||||
Model.Arguments = Arguments;
|
||||
Model.Location = Location;
|
||||
Model.Name = Name;
|
||||
Model.WorkingDirectory = WorkingDirectory;
|
||||
Model.TabThemeId = SelectedTabTheme.Id;
|
||||
Model.TerminalThemeId = SelectedTerminalTheme.Id;
|
||||
Model.LineEndingTranslation = _lineEndingStyle;
|
||||
Model.KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
|
||||
_settingsService.SaveShellProfile(Model);
|
||||
SettingsService.SaveShellProfile(Model);
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
_isNew = false;
|
||||
IsNew = false;
|
||||
}
|
||||
|
||||
private async Task RestoreDefaults()
|
||||
@@ -257,13 +352,14 @@ namespace FluentTerminal.App.ViewModels
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore this profile?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"),
|
||||
I18N.Translate("ConfirmRestoreProfile"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Model = _defaultValueProvider.GetPreinstalledShellProfiles().FirstOrDefault(x => x.Id == Model.Id);
|
||||
Model = DefaultValueProvider.GetPreinstalledShellProfiles().FirstOrDefault(x => x.Id == Model.Id);
|
||||
InitializeViewModelProperties(Model);
|
||||
_settingsService.SaveShellProfile(Model);
|
||||
SettingsService.SaveShellProfile(Model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +370,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task BrowseForCustomShell()
|
||||
{
|
||||
var file = await _fileSystemService.OpenFile(new[] { ".exe" }).ConfigureAwait(true);
|
||||
var file = await FileSystemService.OpenFile(new[] { ".exe" }).ConfigureAwait(true);
|
||||
if (file != null)
|
||||
{
|
||||
Location = file.Path;
|
||||
@@ -283,7 +379,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task BrowseForWorkingDirectory()
|
||||
{
|
||||
var directory = await _fileSystemService.BrowseForDirectory().ConfigureAwait(true);
|
||||
var directory = await FileSystemService.BrowseForDirectory().ConfigureAwait(true);
|
||||
if (directory != null)
|
||||
{
|
||||
WorkingDirectory = directory;
|
||||
@@ -292,43 +388,22 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task CancelEdit()
|
||||
{
|
||||
if (_isNew)
|
||||
if (IsNew)
|
||||
{
|
||||
await Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellProfile changedProfile = new ShellProfile
|
||||
{
|
||||
Arguments = Arguments,
|
||||
Location = Location,
|
||||
Name = Name,
|
||||
WorkingDirectory = WorkingDirectory,
|
||||
TabThemeId = SelectedTabTheme.Id,
|
||||
TerminalThemeId = SelectedTerminalTheme.Id,
|
||||
LineEndingTranslation = _lineEndingStyle,
|
||||
KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList()
|
||||
};
|
||||
ShellProfile changedProfile = await CreateProfileAsync();
|
||||
|
||||
if (!_fallbackProfile.Equals(changedProfile))
|
||||
if (!_fallbackProfile.EqualTo(changedProfile))
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
Arguments = _fallbackProfile.Arguments;
|
||||
Location = _fallbackProfile.Location;
|
||||
Name = _fallbackProfile.Name;
|
||||
WorkingDirectory = _fallbackProfile.WorkingDirectory;
|
||||
LineEndingStyle = _fallbackProfile.LineEndingTranslation;
|
||||
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TerminalThemeId);
|
||||
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TabThemeId);
|
||||
|
||||
KeyBindings.KeyBindings.Clear();
|
||||
foreach (var keyBinding in Model.KeyBindings.Select(x => new KeyBinding(x)).ToList())
|
||||
{
|
||||
KeyBindings.Add(keyBinding);
|
||||
}
|
||||
// Cancelled, so rollback
|
||||
InitializeViewModelProperties(_fallbackProfile);
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
@@ -349,7 +424,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteProfile"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -359,16 +434,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private void Edit()
|
||||
{
|
||||
// todo write copy ctor
|
||||
_fallbackProfile = new ShellProfile
|
||||
{
|
||||
Arguments = Model.Arguments,
|
||||
Location = Model.Location,
|
||||
Name = Model.Name,
|
||||
WorkingDirectory = Model.WorkingDirectory,
|
||||
TabThemeId = Model.TabThemeId,
|
||||
TerminalThemeId = Model.TerminalThemeId
|
||||
};
|
||||
_fallbackProfile = Model.Clone();
|
||||
|
||||
KeyBindings.Editable = true;
|
||||
InEditMode = true;
|
||||
@@ -378,5 +444,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
SetAsDefault?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class SshProfileViewModel : ShellProfileViewModel, ISshConnectionInfo
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
|
||||
// To prevent validating the existence of the same file multiple times because it's kinda expensive
|
||||
private string _validatedIdentityFile;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
private string _host;
|
||||
|
||||
public string Host
|
||||
{
|
||||
get => _host;
|
||||
set => Set(ref _host, value);
|
||||
}
|
||||
|
||||
private ushort _sshPort;
|
||||
|
||||
public ushort SshPort
|
||||
{
|
||||
get => _sshPort;
|
||||
set => Set(ref _sshPort, value);
|
||||
}
|
||||
|
||||
private string _username;
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => _username;
|
||||
set => Set(ref _username, value);
|
||||
}
|
||||
|
||||
private string _identityFile;
|
||||
|
||||
public string IdentityFile
|
||||
{
|
||||
get => _identityFile;
|
||||
set => Set(ref _identityFile, value);
|
||||
}
|
||||
|
||||
private bool _useMosh;
|
||||
|
||||
public bool UseMosh
|
||||
{
|
||||
get => _useMosh;
|
||||
set => Set(ref _useMosh, value);
|
||||
}
|
||||
|
||||
private ushort _moshPortFrom;
|
||||
|
||||
public ushort MoshPortFrom
|
||||
{
|
||||
get => _moshPortFrom;
|
||||
set => Set(ref _moshPortFrom, value);
|
||||
}
|
||||
|
||||
private ushort _moshPortTo;
|
||||
|
||||
public ushort MoshPortTo
|
||||
{
|
||||
get => _moshPortTo;
|
||||
set => Set(ref _moshPortTo, value);
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Commands
|
||||
|
||||
public IAsyncCommand BrowseForIdentityFileCommand { get; }
|
||||
|
||||
#endregion Commands
|
||||
|
||||
#region Constructor
|
||||
|
||||
public SshProfileViewModel(SshProfile sshProfile, ISettingsService settingsService,
|
||||
IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView,
|
||||
IDefaultValueProvider defaultValueProvider,
|
||||
ITrayProcessCommunicationService trayProcessCommunicationService, bool isNew) : base(
|
||||
sshProfile ?? new SshProfile(), settingsService, dialogService, fileSystemService, applicationView,
|
||||
defaultValueProvider, isNew)
|
||||
{
|
||||
_trayProcessCommunicationService = trayProcessCommunicationService;
|
||||
|
||||
InitializeViewModelPropertiesPrivate(sshProfile ?? new SshProfile());
|
||||
|
||||
BrowseForIdentityFileCommand = new AsyncCommand(BrowseForIdentityFile);
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Methods
|
||||
|
||||
// Fills the remaining view model properties (those which aren't filled by the base method) from the input sshProfile
|
||||
private void InitializeViewModelPropertiesPrivate(SshProfile sshProfile)
|
||||
{
|
||||
Host = sshProfile.Host;
|
||||
SshPort = sshProfile.SshPort;
|
||||
|
||||
Username = sshProfile.Username;
|
||||
|
||||
if (string.IsNullOrEmpty(Username))
|
||||
{
|
||||
_trayProcessCommunicationService.GetUserName().ContinueWith(t =>
|
||||
{
|
||||
var username = t.Result;
|
||||
|
||||
if (string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(username))
|
||||
{
|
||||
ApplicationView.RunOnDispatcherThread(() => Username = username);
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
IdentityFile = sshProfile.IdentityFile;
|
||||
UseMosh = sshProfile.UseMosh;
|
||||
MoshPortFrom = sshProfile.MoshPortFrom;
|
||||
MoshPortTo = sshProfile.MoshPortTo;
|
||||
}
|
||||
|
||||
protected override void InitializeViewModelProperties(ShellProfile sshProfile)
|
||||
{
|
||||
base.InitializeViewModelProperties(sshProfile);
|
||||
|
||||
InitializeViewModelPropertiesPrivate((SshProfile)sshProfile);
|
||||
}
|
||||
|
||||
protected override async Task FillProfileAsync(ShellProfile profile)
|
||||
{
|
||||
await base.FillProfileAsync(profile);
|
||||
|
||||
profile.Location = _useMosh ? ShellLocation.Mosh : ShellLocation.SSH;
|
||||
profile.Arguments = GetArgumentsString();
|
||||
profile.WorkingDirectory = null;
|
||||
|
||||
var sshProfile = (SshProfile)profile;
|
||||
|
||||
sshProfile.Host = Host;
|
||||
sshProfile.SshPort = SshPort;
|
||||
sshProfile.Username = Username;
|
||||
sshProfile.IdentityFile = IdentityFile;
|
||||
sshProfile.UseMosh = UseMosh;
|
||||
sshProfile.MoshPortFrom = MoshPortFrom;
|
||||
sshProfile.MoshPortTo = MoshPortTo;
|
||||
}
|
||||
|
||||
public virtual Task AcceptChangesAsync() => FillProfileAsync(Model);
|
||||
|
||||
protected override async Task<ShellProfile> CreateProfileAsync()
|
||||
{
|
||||
var profile = new SshProfile();
|
||||
|
||||
await FillProfileAsync(profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private string GetArgumentsString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
if (_sshPort != SshProfile.DefaultSshPort)
|
||||
sb.Append($"-p {_sshPort:#####} ");
|
||||
|
||||
if (!string.IsNullOrEmpty(_identityFile))
|
||||
sb.Append($"-i \"{_identityFile}\" ");
|
||||
|
||||
sb.Append($"{_username}@{_host}");
|
||||
|
||||
if (_useMosh)
|
||||
sb.Append($" {_moshPortFrom}:{_moshPortTo}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override async Task SaveChangesAsync()
|
||||
{
|
||||
var result = await ValidateAsync();
|
||||
|
||||
if (result != SshConnectionInfoValidationResult.Valid)
|
||||
{
|
||||
await DialogService.ShowMessageDialogAsnyc(I18N.Translate("InvalidInput"),
|
||||
result.GetErrorString(Environment.NewLine), DialogButton.OK);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await AcceptChangesAsync();
|
||||
|
||||
SettingsService.SaveSshProfile((SshProfile) Model, IsNew);
|
||||
|
||||
KeyBindings.Editable = false;
|
||||
InEditMode = false;
|
||||
IsNew = false;
|
||||
}
|
||||
|
||||
private async Task BrowseForIdentityFile()
|
||||
{
|
||||
var file = await FileSystemService.OpenFile(new[] { "*" }).ConfigureAwait(true);
|
||||
if (file != null)
|
||||
{
|
||||
IdentityFile = file.Path;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SshConnectionInfoValidationResult> ValidateAsync()
|
||||
{
|
||||
var result = this.GetValidationResult();
|
||||
|
||||
var identityFile = _identityFile;
|
||||
|
||||
if (!string.IsNullOrEmpty(identityFile) && !string.Equals(identityFile, _validatedIdentityFile,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Here we need to take into account that files from ssh config dir can be provided by name, without full path.
|
||||
string fullPath;
|
||||
|
||||
if (identityFile.Contains(Path.PathSeparator))
|
||||
{
|
||||
fullPath = identityFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sshConfigDir = await _trayProcessCommunicationService.GetSshConfigDirAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(sshConfigDir))
|
||||
{
|
||||
result |= SshConnectionInfoValidationResult.IdentityFileDoesNotExist;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fullPath = Path.Combine(sshConfigDir, identityFile);
|
||||
}
|
||||
|
||||
if (await _trayProcessCommunicationService.CheckFileExistsAsync(fullPath))
|
||||
{
|
||||
_validatedIdentityFile = identityFile;
|
||||
|
||||
IdentityFile = fullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
result |= SshConnectionInfoValidationResult.IdentityFileDoesNotExist;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SetValidatedIdentityFile(string identityFile)
|
||||
{
|
||||
_validatedIdentityFile = identityFile;
|
||||
IdentityFile = identityFile;
|
||||
}
|
||||
|
||||
#endregion Methods
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using FluentTerminal.App.ViewModels.Infrastructure;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
@@ -7,26 +8,35 @@ using GalaSoft.MvvmLight.Command;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
public class TerminalViewModel : ViewModelBase
|
||||
{
|
||||
#region Static
|
||||
|
||||
private static readonly Regex SshTitleRx = new Regex(@"^\w+@\S+:", RegexOptions.Compiled);
|
||||
|
||||
#endregion Static
|
||||
|
||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||
private readonly IDispatcherTimer _resizeOverlayTimer;
|
||||
private bool _isSelected;
|
||||
private bool _newOutput;
|
||||
private string _resizeOverlayContent;
|
||||
private bool _hasNewOutput;
|
||||
private bool _hasExitedWithError;
|
||||
private string _searchText;
|
||||
private bool _showResizeOverlay;
|
||||
private bool _showSearchPanel;
|
||||
private TabTheme _tabTheme;
|
||||
private TerminalTheme _terminalTheme;
|
||||
private string _title;
|
||||
private TerminalOptions _terminalOptions;
|
||||
private string _tabTitle;
|
||||
private string _shellTitle;
|
||||
private bool _hasCustomTitle;
|
||||
|
||||
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
|
||||
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile,
|
||||
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile,
|
||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
||||
{
|
||||
SettingsService = settingsService;
|
||||
@@ -35,12 +45,13 @@ namespace FluentTerminal.App.ViewModels
|
||||
SettingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
SettingsService.KeyBindingsChanged += OnKeyBindingsChanged;
|
||||
|
||||
_terminalOptions = SettingsService.GetTerminalOptions();
|
||||
|
||||
TrayProcessCommunicationService = trayProcessCommunicationService;
|
||||
|
||||
DialogService = dialogService;
|
||||
_keyboardCommandService = keyboardCommandService;
|
||||
ApplicationSettings = applicationSettings;
|
||||
StartupDirectory = startupDirectory;
|
||||
ApplicationView = applicationView;
|
||||
ClipboardService = clipboardService;
|
||||
|
||||
@@ -50,10 +61,6 @@ namespace FluentTerminal.App.ViewModels
|
||||
TabThemes = new ObservableCollection<TabTheme>(SettingsService.GetTabThemes());
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == ShellProfile.TabThemeId);
|
||||
|
||||
_resizeOverlayTimer = dispatcherTimer;
|
||||
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
|
||||
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
|
||||
|
||||
CloseCommand = new RelayCommand(async () => await TryClose().ConfigureAwait(false));
|
||||
FindNextCommand = new RelayCommand(FindNext);
|
||||
FindPreviousCommand = new RelayCommand(FindPrevious);
|
||||
@@ -61,17 +68,16 @@ namespace FluentTerminal.App.ViewModels
|
||||
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme);
|
||||
EditTitleCommand = new AsyncCommand(EditTitle);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(StartupDirectory))
|
||||
{
|
||||
ShellProfile.WorkingDirectory = StartupDirectory;
|
||||
}
|
||||
|
||||
Terminal = new Terminal(TrayProcessCommunicationService);
|
||||
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
|
||||
Terminal.OutputReceived += Terminal_OutputReceived;
|
||||
Terminal.SizeChanged += Terminal_SizeChanged;
|
||||
Terminal.TitleChanged += Terminal_TitleChanged;
|
||||
Terminal.Exited += Terminal_Exited;
|
||||
Terminal.Closed += Terminal_Closed;
|
||||
|
||||
Overlay = new OverlayViewModel(dispatcherTimer);
|
||||
|
||||
}
|
||||
|
||||
public event EventHandler Activated;
|
||||
@@ -82,6 +88,8 @@ namespace FluentTerminal.App.ViewModels
|
||||
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 ApplicationSettings ApplicationSettings { get; private set; }
|
||||
|
||||
@@ -119,8 +127,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
if (IsSelected)
|
||||
{
|
||||
ApplicationView.Title = Title ?? string.Empty;
|
||||
NewOutput = false;
|
||||
HasNewOutput = false;
|
||||
}
|
||||
RaisePropertyChanged(nameof(IsUnderlined));
|
||||
RaisePropertyChanged(nameof(BackgroundTabTheme));
|
||||
@@ -131,16 +138,22 @@ namespace FluentTerminal.App.ViewModels
|
||||
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
|
||||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Color != null);
|
||||
|
||||
public bool NewOutput
|
||||
public bool HasNewOutput
|
||||
{
|
||||
get => _newOutput;
|
||||
set => Set(ref _newOutput, value);
|
||||
get => _hasNewOutput;
|
||||
set => Set(ref _hasNewOutput, value);
|
||||
}
|
||||
|
||||
public string ResizeOverlayContent
|
||||
public bool HasExitedWithError
|
||||
{
|
||||
get => _resizeOverlayContent;
|
||||
set => Set(ref _resizeOverlayContent, value);
|
||||
get => _hasExitedWithError;
|
||||
set
|
||||
{
|
||||
if (Set(ref _hasExitedWithError, value) && value)
|
||||
{
|
||||
HasNewOutput = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SearchText
|
||||
@@ -155,31 +168,12 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public ShellProfile ShellProfile { get; }
|
||||
|
||||
public bool ShowResizeOverlay
|
||||
{
|
||||
get => _showResizeOverlay;
|
||||
set
|
||||
{
|
||||
Set(ref _showResizeOverlay, value);
|
||||
if (value)
|
||||
{
|
||||
if (_resizeOverlayTimer.IsEnabled)
|
||||
{
|
||||
_resizeOverlayTimer.Stop();
|
||||
}
|
||||
_resizeOverlayTimer.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSearchPanel
|
||||
{
|
||||
get => _showSearchPanel;
|
||||
set => Set(ref _showSearchPanel, value);
|
||||
}
|
||||
|
||||
public string StartupDirectory { get; }
|
||||
|
||||
public TabTheme TabTheme
|
||||
{
|
||||
get => _tabTheme;
|
||||
@@ -195,20 +189,55 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public Terminal Terminal { get; private set; }
|
||||
|
||||
public OverlayViewModel Overlay { get; private set; }
|
||||
|
||||
public TerminalTheme TerminalTheme
|
||||
{
|
||||
get => _terminalTheme;
|
||||
set => Set(ref _terminalTheme, value);
|
||||
}
|
||||
|
||||
public string Title
|
||||
public string TabTitle
|
||||
{
|
||||
get => _title;
|
||||
get => _tabTitle;
|
||||
set
|
||||
{
|
||||
if (Set(ref _title, value) && IsSelected)
|
||||
var title = value?.Trim() ?? string.Empty;
|
||||
|
||||
if (ShellProfile is SshProfile)
|
||||
{
|
||||
ApplicationView.Title = Title;
|
||||
// For SshProfile we are adjusting title.
|
||||
if (title.Equals("ssh", StringComparison.OrdinalIgnoreCase) ||
|
||||
title.EndsWith("\\ssh.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
title = $"[ssh] {I18N.Translate("Authenticate")}";
|
||||
}
|
||||
else if (title.Equals("mosh", StringComparison.OrdinalIgnoreCase) ||
|
||||
title.EndsWith("\\mosh.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
title = $"[mosh] {I18N.Translate("Authenticate")}";
|
||||
}
|
||||
else if (SshTitleRx.IsMatch(title))
|
||||
{
|
||||
title = $"[ssh] {title}";
|
||||
}
|
||||
}
|
||||
|
||||
if (Set(ref _tabTitle, title))
|
||||
{
|
||||
CustomTitleChanged?.Invoke(this, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ShellTitle
|
||||
{
|
||||
get => _shellTitle;
|
||||
set
|
||||
{
|
||||
if (Set(ref _shellTitle, value) && !_hasCustomTitle)
|
||||
{
|
||||
TabTitle = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,6 +246,10 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public Task Close()
|
||||
{
|
||||
SettingsService.CurrentThemeChanged -= OnCurrentThemeChanged;
|
||||
SettingsService.TerminalOptionsChanged -= OnTerminalOptionsChanged;
|
||||
SettingsService.ApplicationSettingsChanged -= OnApplicationSettingsChanged;
|
||||
SettingsService.KeyBindingsChanged -= OnKeyBindingsChanged;
|
||||
return Terminal.Close();
|
||||
}
|
||||
|
||||
@@ -227,10 +260,19 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
public async Task EditTitle()
|
||||
{
|
||||
var result = await DialogService.ShowInputDialogAsync("Edit Title");
|
||||
var result = await DialogService.ShowInputDialogAsync(I18N.Translate("EditTitleString"));
|
||||
if (result != null)
|
||||
{
|
||||
Title = result;
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
_hasCustomTitle = false;
|
||||
TabTitle = ShellTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasCustomTitle = true;
|
||||
TabTitle = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,30 +322,30 @@ namespace FluentTerminal.App.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
private async void OnKeyBindingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
KeyBindingsChanged?.Invoke(this, EventArgs.Empty);
|
||||
await ApplicationView.RunOnDispatcherThread(() => KeyBindingsChanged?.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
|
||||
private void OnResizeOverlayTimerFinished(object sender, object e)
|
||||
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
{
|
||||
_resizeOverlayTimer.Stop();
|
||||
ShowResizeOverlay = false;
|
||||
_terminalOptions = e;
|
||||
await ApplicationView.RunOnDispatcherThread(() => OptionsChanged?.Invoke(this, e));
|
||||
}
|
||||
|
||||
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
|
||||
private void SelectTabTheme(string id)
|
||||
{
|
||||
OptionsChanged?.Invoke(this, e);
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Id == int.Parse(id));
|
||||
}
|
||||
|
||||
private void SelectTabTheme(string name)
|
||||
private void Terminal_Exited(object sender, int exitCode)
|
||||
{
|
||||
TabTheme = TabThemes.FirstOrDefault(t => t.Name == name);
|
||||
ApplicationView.RunOnDispatcherThread(() => HasExitedWithError = exitCode > 0);
|
||||
}
|
||||
|
||||
private void Terminal_Closed(object sender, EventArgs e)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
ApplicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
|
||||
private async void Terminal_KeyboardCommandReceived(object sender, string e)
|
||||
@@ -314,6 +356,10 @@ namespace FluentTerminal.App.ViewModels
|
||||
{
|
||||
var selection = await Terminal.GetSelectedText().ConfigureAwait(true);
|
||||
ClipboardService.SetText(selection);
|
||||
if(_terminalOptions.ShowTextCopied)
|
||||
{
|
||||
Overlay.Show(I18N.Translate("TextCopied"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nameof(Command.Paste):
|
||||
@@ -322,7 +368,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.TranslateLineEndings(content);
|
||||
await Terminal.Write(content).ConfigureAwait(true);
|
||||
await Terminal.Write(Encoding.UTF8.GetBytes(content)).ConfigureAwait(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -332,7 +378,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
if (content != null)
|
||||
{
|
||||
content = ShellProfile.NewlinePattern.Replace(content, string.Empty);
|
||||
await Terminal.Write(content).ConfigureAwait(true);
|
||||
await Terminal.Write(Encoding.UTF8.GetBytes(content)).ConfigureAwait(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -353,30 +399,30 @@ namespace FluentTerminal.App.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_OutputReceived(object sender, string e)
|
||||
private void Terminal_OutputReceived(object sender, byte[] e)
|
||||
{
|
||||
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
|
||||
{
|
||||
NewOutput = true;
|
||||
ApplicationView.RunOnDispatcherThread(() => HasNewOutput = true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Terminal_SizeChanged(object sender, TerminalSize e)
|
||||
{
|
||||
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
|
||||
ShowResizeOverlay = true;
|
||||
Overlay.Show($"{e.Columns} x {e.Rows}");
|
||||
}
|
||||
|
||||
private void Terminal_TitleChanged(object sender, string e)
|
||||
{
|
||||
Title = e;
|
||||
ShellTitle = e;
|
||||
ShellTitleChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private async Task TryClose()
|
||||
{
|
||||
if (ApplicationSettings.ConfirmClosingTabs)
|
||||
{
|
||||
var result = await DialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await DialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmCloseTab"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.Cancel)
|
||||
{
|
||||
|
||||
@@ -344,7 +344,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
if (!_fallbackTheme.Equals(changedTheme))
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
@@ -392,7 +392,7 @@ namespace FluentTerminal.App.ViewModels
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
var result = await _dialogService.ShowMessageDialogAsnyc(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteTheme"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
|
||||
|
||||
if (result == DialogButton.OK)
|
||||
{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace FluentTerminal.App.ViewModels.Utilities
|
||||
{
|
||||
public class InputBuffer
|
||||
{
|
||||
private readonly Timer _timer = new Timer();
|
||||
private readonly Func<string, Task> _writeCallback;
|
||||
private readonly List<string> _buffer = new List<string>();
|
||||
private readonly object _lock = new object();
|
||||
|
||||
public InputBuffer(Func<string, Task> writeCallback)
|
||||
{
|
||||
_timer.AutoReset = true;
|
||||
_timer.Elapsed += _timer_Elapsed;
|
||||
_writeCallback = writeCallback;
|
||||
_timer.Interval = 5;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var data = string.Concat(_buffer);
|
||||
_buffer.Clear();
|
||||
_writeCallback.Invoke(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string data)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_buffer.Add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace FluentTerminal.App.Adapters
|
||||
{
|
||||
private readonly AppServiceConnection _appServiceConnection;
|
||||
|
||||
public event EventHandler<IDictionary<string, string>> MessageReceived;
|
||||
public event EventHandler<ValueSet> MessageReceived;
|
||||
|
||||
public AppServiceConnectionAdapter(AppServiceConnection appServiceConnection)
|
||||
{
|
||||
@@ -22,35 +22,16 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
private void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
|
||||
{
|
||||
var messageType = (string)args.Request.Message[MessageKeys.Type];
|
||||
var messageContent = (string)args.Request.Message[MessageKeys.Content];
|
||||
|
||||
MessageReceived?.Invoke(this, new Dictionary<string, string>
|
||||
{
|
||||
[MessageKeys.Type] = messageType,
|
||||
[MessageKeys.Content] = messageContent
|
||||
});
|
||||
MessageReceived?.Invoke(this, args.Request.Message);
|
||||
}
|
||||
|
||||
public async Task<IDictionary<string, string>> SendMessageAsync(IDictionary<string, string> message)
|
||||
public async Task<ValueSet> SendMessageAsync(ValueSet message)
|
||||
{
|
||||
var valueSet = new ValueSet();
|
||||
|
||||
foreach (var pair in message)
|
||||
{
|
||||
valueSet.Add(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
var response = await _appServiceConnection.SendMessageAsync(valueSet).AsTask();
|
||||
var response = await _appServiceConnection.SendMessageAsync(message).AsTask();
|
||||
|
||||
if (response.Status == AppServiceResponseStatus.Success)
|
||||
{
|
||||
var responseDict = new Dictionary<string, string>();
|
||||
foreach (var pair in response.Message)
|
||||
{
|
||||
responseDict.Add(pair.Key, (string)pair.Value);
|
||||
}
|
||||
return responseDict;
|
||||
return response.Message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,20 +14,32 @@ namespace FluentTerminal.App.Adapters
|
||||
{
|
||||
private readonly ApplicationView _applicationView;
|
||||
private readonly CoreDispatcher _dispatcher;
|
||||
private bool _closed;
|
||||
|
||||
public event CloseRequestedHandler CloseRequested;
|
||||
public event EventHandler Closed;
|
||||
|
||||
public ApplicationViewAdapter()
|
||||
{
|
||||
_applicationView = ApplicationView.GetForCurrentView();
|
||||
_applicationView.Consolidated += _applicationView_Consolidated;
|
||||
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
|
||||
|
||||
Logger.Instance.Debug("Created ApplicationViewAdapter for ApplicationView with Id: {Id}", _applicationView.Id);
|
||||
}
|
||||
|
||||
private void _applicationView_Consolidated(ApplicationView sender, ApplicationViewConsolidatedEventArgs args)
|
||||
{
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public int Id => _applicationView.Id;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _applicationView.Title;
|
||||
set => _applicationView.Title = value;
|
||||
set => _applicationView.Title = value ?? string.Empty;
|
||||
}
|
||||
|
||||
public bool IsApiContractPresent(string api, ushort version)
|
||||
@@ -40,9 +52,16 @@ namespace FluentTerminal.App.Adapters
|
||||
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
|
||||
}
|
||||
|
||||
public Task<bool> TryClose()
|
||||
public async Task<bool> TryClose()
|
||||
{
|
||||
return _applicationView.TryConsolidateAsync().AsTask();
|
||||
if (_closed)
|
||||
{
|
||||
Logger.Instance.Debug("ApplicationViewAdapter.TryClose was called, but was already closed. ApplicationView.Id: {Id}", _applicationView.Id);
|
||||
return true;
|
||||
}
|
||||
Logger.Instance.Debug("TryClose ApplicationView with Id: {Id}", _applicationView.Id);
|
||||
_closed = await _applicationView.TryConsolidateAsync().AsTask();
|
||||
return _closed;
|
||||
}
|
||||
|
||||
public bool ToggleFullScreen()
|
||||
@@ -63,6 +82,7 @@ namespace FluentTerminal.App.Adapters
|
||||
var deferral = e.GetDeferral();
|
||||
|
||||
var args = new CancelableEventArgs();
|
||||
|
||||
if (CloseRequested != null)
|
||||
{
|
||||
await CloseRequested.Invoke(this, args);
|
||||
@@ -70,6 +90,11 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
e.Handled = args.Cancelled;
|
||||
|
||||
if (!e.Handled)
|
||||
{
|
||||
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested -= OnCloseRequest;
|
||||
}
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Popups;
|
||||
@@ -29,14 +30,19 @@ namespace FluentTerminal.App.Adapters
|
||||
|
||||
public void AddButton(DialogButton button)
|
||||
{
|
||||
_messageDialog.Commands.Add(new UICommand(button.ToString()));
|
||||
var command = new UICommand
|
||||
{
|
||||
Label = I18N.Translate(button.ToString()),
|
||||
Id = button
|
||||
};
|
||||
_messageDialog.Commands.Add(command);
|
||||
}
|
||||
|
||||
public async Task<DialogButton> ShowAsync()
|
||||
{
|
||||
var result = await _messageDialog.ShowAsync();
|
||||
|
||||
return Enum.Parse<DialogButton>(result.Label);
|
||||
return (DialogButton)result.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,8 @@
|
||||
<converters:EnumValueToVisibiltyConverter x:Key="EnumValueToVisibiltyConverter" />
|
||||
<converters:TabThemeSelectedConverter x:Key="TabThemeSelectedConverter" />
|
||||
<converters:ColorResourceKeyFallbackConverter x:Key="ColorResourceKeyFallbackConverter" />
|
||||
<converters:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
|
||||
<converters:TrueToBoldConverter x:Key="TrueToBoldConverter" />
|
||||
<converters:I18NConverter x:Key="I18NConverter" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,327 +1,667 @@
|
||||
using Autofac;
|
||||
using FluentTerminal.App.Adapters;
|
||||
using FluentTerminal.App.Dialogs;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Adapters;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.App.Services.Implementation;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.App.Views;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using IContainer = Autofac.IContainer;
|
||||
|
||||
namespace FluentTerminal.App
|
||||
{
|
||||
public sealed partial class App : Application
|
||||
{
|
||||
public TaskCompletionSource<int> _trayReady = new TaskCompletionSource<int>();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private bool _alreadyLaunched;
|
||||
private ApplicationSettings _applicationSettings;
|
||||
private readonly IContainer _container;
|
||||
private readonly List<MainViewModel> _mainViewModels;
|
||||
private SettingsViewModel _settingsViewModel;
|
||||
private int? _settingsWindowId;
|
||||
private IAppServiceConnection _appServiceConnection;
|
||||
private BackgroundTaskDeferral _appServiceDeferral;
|
||||
|
||||
public App()
|
||||
{
|
||||
_mainViewModels = new List<MainViewModel>();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
UnhandledException += OnUnhandledException;
|
||||
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
||||
RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
|
||||
KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always))
|
||||
};
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
|
||||
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
||||
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
||||
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
|
||||
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
|
||||
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
|
||||
builder.RegisterType<MainViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
|
||||
builder.RegisterType<ITermThemeParser>().As<IThemeParser>().SingleInstance();
|
||||
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
|
||||
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
|
||||
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
|
||||
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
||||
builder.RegisterInstance(applicationDataContainers);
|
||||
|
||||
_container = builder.Build();
|
||||
|
||||
_settingsService = _container.Resolve<ISettingsService>();
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
|
||||
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
|
||||
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e.Exception.ToString());
|
||||
}
|
||||
|
||||
protected override async void OnActivated(IActivatedEventArgs args)
|
||||
{
|
||||
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var arguments = commandLineActivated.Operation.Arguments.Split(' ', 2);
|
||||
var command = arguments[0];
|
||||
var parameter = arguments.Length > 1 ? arguments[1] : string.Empty;
|
||||
|
||||
if (_alreadyLaunched)
|
||||
{
|
||||
if (command == "settings")
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
}
|
||||
else if (command == "new")
|
||||
{
|
||||
if (_applicationSettings.NewTerminalLocation == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
||||
{
|
||||
await _mainViewModels.Last().AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CreateNewTerminalWindow(parameter, false).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (command == "settings")
|
||||
{
|
||||
var viewModel = _container.Resolve<SettingsViewModel>();
|
||||
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
|
||||
}
|
||||
else if (command == "new")
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminal(null, false, Guid.Empty).ConfigureAwait(true);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
Window.Current.Activate();
|
||||
}
|
||||
else if (_mainViewModels.Count == 0)
|
||||
{
|
||||
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, false, string.Empty).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
base.OnBackgroundActivated(args);
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
|
||||
{
|
||||
if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||
{
|
||||
_appServiceDeferral = args.TaskInstance.GetDeferral();
|
||||
args.TaskInstance.Canceled += OnTaskCanceled;
|
||||
|
||||
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
|
||||
|
||||
_trayReady.SetResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
_appServiceDeferral?.Complete();
|
||||
_appServiceDeferral = null;
|
||||
_appServiceConnection = null;
|
||||
|
||||
Application.Current.Exit();
|
||||
}
|
||||
|
||||
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||
{
|
||||
await StartSystemTray().ConfigureAwait(true);
|
||||
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
if (rootFrame == null)
|
||||
{
|
||||
rootFrame = new Frame();
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||
|
||||
if (viewModel is MainViewModel mainViewModel)
|
||||
{
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
}
|
||||
|
||||
rootFrame.Navigate(pageType, viewModel);
|
||||
}
|
||||
_alreadyLaunched = true;
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
private async Task CreateNewTerminalWindow(string startupDirectory, bool showProfileSelection)
|
||||
{
|
||||
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, showProfileSelection, startupDirectory).ConfigureAwait(true);
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
|
||||
}
|
||||
|
||||
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, bool showProfileSelection, object parameter)
|
||||
{
|
||||
int windowId = 0;
|
||||
TViewModel viewModel = default;
|
||||
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
viewModel = _container.Resolve<TViewModel>();
|
||||
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar;
|
||||
var frame = new Frame();
|
||||
frame.Navigate(pageType, viewModel);
|
||||
Window.Current.Content = frame;
|
||||
Window.Current.Activate();
|
||||
|
||||
windowId = ApplicationView.GetForCurrentView().Id;
|
||||
});
|
||||
|
||||
if (viewModel is MainViewModel mainViewModel && parameter is string directory)
|
||||
{
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
await mainViewModel.AddTerminal(directory, showProfileSelection, Guid.Empty).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
if (viewModel is SettingsViewModel settingsViewModel)
|
||||
{
|
||||
_settingsViewModel = settingsViewModel;
|
||||
_settingsViewModel.Closed += OnSettingsClosed;
|
||||
}
|
||||
|
||||
return windowId;
|
||||
}
|
||||
|
||||
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
{
|
||||
_applicationSettings = e;
|
||||
}
|
||||
|
||||
private void OnMainViewModelClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is MainViewModel viewModel)
|
||||
{
|
||||
viewModel.Closed -= OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested -= OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
|
||||
|
||||
_mainViewModels.Remove(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||
{
|
||||
await CreateNewTerminalWindow(string.Empty, e.ShowProfileSelection).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void OnSettingsClosed(object sender, EventArgs e)
|
||||
{
|
||||
_settingsViewModel.Closed -= OnSettingsClosed;
|
||||
_settingsViewModel = null;
|
||||
_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()
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
_settingsViewModel.NavigateToAboutPage();
|
||||
}
|
||||
|
||||
private async Task ShowSettings()
|
||||
{
|
||||
if (_settingsViewModel == null)
|
||||
{
|
||||
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, false, null).ConfigureAwait(true);
|
||||
}
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
||||
}
|
||||
|
||||
private async Task StartSystemTray()
|
||||
{
|
||||
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
|
||||
var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask();
|
||||
await Task.WhenAll(launch, clearCache, _trayReady.Task).ConfigureAwait(true);
|
||||
_trayProcessCommunicationService.Initialize(_appServiceConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
using Autofac;
|
||||
using CommandLine;
|
||||
using FluentTerminal.App.Adapters;
|
||||
using FluentTerminal.App.CommandLineArguments;
|
||||
using FluentTerminal.App.Dialogs;
|
||||
using FluentTerminal.App.Services;
|
||||
using FluentTerminal.App.Services.Adapters;
|
||||
using FluentTerminal.App.Services.Dialogs;
|
||||
using FluentTerminal.App.Services.EventArgs;
|
||||
using FluentTerminal.App.Services.Implementation;
|
||||
using FluentTerminal.App.ViewModels;
|
||||
using FluentTerminal.App.Views;
|
||||
using FluentTerminal.Models;
|
||||
using FluentTerminal.Models.Enums;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.ApplicationModel.AppService;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using FluentTerminal.App.Utilities;
|
||||
using IContainer = Autofac.IContainer;
|
||||
using FluentTerminal.App.Services.Utilities;
|
||||
|
||||
namespace FluentTerminal.App
|
||||
{
|
||||
// ReSharper disable once RedundantExtendsListEntry
|
||||
public sealed partial class App : Application
|
||||
{
|
||||
private readonly TaskCompletionSource<int> _trayReady = new TaskCompletionSource<int>();
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||
private readonly Lazy<ISshHelperService> _sshHelperService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private bool _alreadyLaunched;
|
||||
private bool _isLaunching;
|
||||
private ApplicationSettings _applicationSettings;
|
||||
private readonly IContainer _container;
|
||||
private readonly List<MainViewModel> _mainViewModels;
|
||||
private SettingsViewModel _settingsViewModel;
|
||||
private int? _settingsWindowId;
|
||||
private IAppServiceConnection _appServiceConnection;
|
||||
private BackgroundTaskDeferral _appServiceDeferral;
|
||||
private Parser _commandLineParser;
|
||||
private int? _activeWindowId;
|
||||
|
||||
public App()
|
||||
{
|
||||
_mainViewModels = new List<MainViewModel>();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
UnhandledException += OnUnhandledException;
|
||||
|
||||
var applicationDataContainers = new ApplicationDataContainers
|
||||
{
|
||||
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
||||
RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
|
||||
KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always)),
|
||||
SshProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.SshProfilesContainerName, ApplicationDataCreateDisposition.Always))
|
||||
};
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
|
||||
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
||||
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
||||
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
|
||||
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
|
||||
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
|
||||
builder.RegisterType<MainViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
|
||||
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
|
||||
builder.RegisterType<ITermThemeParser>().As<IThemeParser>().SingleInstance();
|
||||
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
|
||||
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
|
||||
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
|
||||
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
||||
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<SshProfileSelectionDialog>().As<ISshProfileSelectionDialog>().InstancePerDependency();
|
||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
||||
builder.RegisterType<SshInfoDialog>().As<ISshConnectionInfoDialog>().InstancePerDependency();
|
||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
||||
builder.RegisterType<SshHelperService>().As<ISshHelperService>().SingleInstance();
|
||||
builder.RegisterType<ApplicationLanguageService>().As<IApplicationLanguageService>().SingleInstance();
|
||||
builder.RegisterInstance(applicationDataContainers);
|
||||
|
||||
_container = builder.Build();
|
||||
|
||||
_settingsService = _container.Resolve<ISettingsService>();
|
||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||
|
||||
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
|
||||
|
||||
_sshHelperService = new Lazy<ISshHelperService>(() => _container.Resolve<ISshHelperService>());
|
||||
|
||||
_dialogService = _container.Resolve<IDialogService>();
|
||||
|
||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||
|
||||
JsonConvert.DefaultSettings = () =>
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
};
|
||||
settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
_commandLineParser = new Parser(settings =>
|
||||
{
|
||||
settings.CaseSensitive = false;
|
||||
settings.CaseInsensitiveEnumValues = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.Instance.Error(e.Exception, "Unhandled Exception");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SplitArguments(string arguments)
|
||||
{
|
||||
var chars = arguments.ToCharArray();
|
||||
var inQuote = false;
|
||||
|
||||
for (var i = 0; i < chars.Length; i++)
|
||||
{
|
||||
if (chars[i] == '"')
|
||||
{
|
||||
inQuote = !inQuote;
|
||||
}
|
||||
|
||||
if (!inQuote && chars[i] == ' ')
|
||||
{
|
||||
chars[i] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var value in new string(chars).Split('\n'))
|
||||
{
|
||||
yield return value.Trim('"');
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnActivated(IActivatedEventArgs args)
|
||||
{
|
||||
if (args is ProtocolActivatedEventArgs protocolActivated)
|
||||
{
|
||||
MainViewModel mainViewModel = null;
|
||||
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
// App wasn't launched before double clicking a shortcut, so we have to create a window
|
||||
// in order to be able to communicate with user.
|
||||
mainViewModel = _container.Resolve<MainViewModel>();
|
||||
|
||||
await CreateMainView(typeof(MainPage), mainViewModel, true);
|
||||
}
|
||||
|
||||
bool isSsh;
|
||||
|
||||
try
|
||||
{
|
||||
isSsh = _sshHelperService.Value.IsSsh(protocolActivated.Uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await new MessageDialog($"Invalid link: {ex.Message}", "Invalid Link").ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSsh)
|
||||
{
|
||||
SshProfileViewModel connectionInfo;
|
||||
|
||||
try
|
||||
{
|
||||
connectionInfo = (SshProfileViewModel) _sshHelperService.Value.ParseSsh(protocolActivated.Uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await new MessageDialog($"Invalid link: {ex.Message}", "Invalid Link").ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_applicationSettings.AutoFallbackToWindowsUsernameInLinks &&
|
||||
string.IsNullOrEmpty(connectionInfo.Username))
|
||||
{
|
||||
connectionInfo.Username = await _trayProcessCommunicationService.GetUserName();
|
||||
}
|
||||
|
||||
SshConnectionInfoValidationResult result = await connectionInfo.ValidateAsync();
|
||||
|
||||
if (result == SshConnectionInfoValidationResult.Valid)
|
||||
{
|
||||
await connectionInfo.AcceptChangesAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Link is valid, but incomplete (i.e. username missing), so we need to show dialog.
|
||||
connectionInfo =
|
||||
(SshProfileViewModel) await _dialogService.ShowSshConnectionInfoDialogAsync(connectionInfo);
|
||||
|
||||
if (connectionInfo == null)
|
||||
{
|
||||
// User clicked "Cancel" in the dialog.
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainViewModel == null)
|
||||
await CreateTerminal(connectionInfo.Model, _applicationSettings.NewTerminalLocation);
|
||||
else
|
||||
await mainViewModel.AddTerminalAsync(connectionInfo.Model);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await new MessageDialog($"Invalid link: {protocolActivated.Uri}", "Invalid Link").ShowAsync();
|
||||
|
||||
mainViewModel?.ApplicationView.TryClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
||||
{
|
||||
if (verb is SettingsVerb settingsVerb)
|
||||
{
|
||||
if (!settingsVerb.Import && !settingsVerb.Export)
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
}
|
||||
else if (settingsVerb.Export)
|
||||
{
|
||||
var exportFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||
|
||||
var settings = _settingsService.ExportSettings();
|
||||
await FileIO.WriteTextAsync(exportFile, settings);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsExported")} {exportFile.Path}").ShowAsync();
|
||||
}
|
||||
else if (settingsVerb.Import)
|
||||
{
|
||||
var file = await ApplicationData.Current.LocalFolder.GetFileAsync("config.json");
|
||||
var content = await FileIO.ReadTextAsync(file);
|
||||
_settingsService.ImportSettings(content);
|
||||
await new MessageDialog($"{I18N.Translate("SettingsImported")} {file.Path}").ShowAsync();
|
||||
}
|
||||
}
|
||||
else if (verb is NewVerb newVerb)
|
||||
{
|
||||
var profile = default(ShellProfile);
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
|
||||
{
|
||||
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
profile = _settingsService.GetDefaultShellProfile();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory))
|
||||
{
|
||||
profile.WorkingDirectory = newVerb.Directory;
|
||||
}
|
||||
|
||||
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||
}
|
||||
else if (verb is RunVerb runVerb)
|
||||
{
|
||||
var profile = new ShellProfile
|
||||
{
|
||||
Id = Guid.Empty,
|
||||
Location = null,
|
||||
Arguments = runVerb.Command,
|
||||
WorkingDirectory = runVerb.Directory
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
|
||||
{
|
||||
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (theme != null)
|
||||
{
|
||||
profile.TerminalThemeId = theme.Id;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
|
||||
{
|
||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||
}
|
||||
|
||||
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||
: NewTerminalLocation.Window;
|
||||
|
||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
if (_isLaunching)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isLaunching = true;
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
await InitializeLogger();
|
||||
|
||||
// TODO: Check the reason for such strange using of tasks!
|
||||
Task.Run(async () => await JumpListHelper.Update(_settingsService.GetShellProfiles()));
|
||||
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
|
||||
{
|
||||
await viewModel.AddLocalTabOrWindowAsync(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await viewModel.AddLocalTabAsync();
|
||||
}
|
||||
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);
|
||||
}
|
||||
_isLaunching = false;
|
||||
}
|
||||
|
||||
private static async Task InitializeLogger()
|
||||
{
|
||||
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
|
||||
var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log");
|
||||
var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||
var configContent = await FileIO.ReadTextAsync(configFile);
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configContent))
|
||||
{
|
||||
configContent = JsonConvert.SerializeObject(new Logger.Configuration());
|
||||
await FileIO.WriteTextAsync(configFile, configContent);
|
||||
}
|
||||
|
||||
var config = JsonConvert.DeserializeObject<Logger.Configuration>(configContent) ?? new Logger.Configuration();
|
||||
|
||||
Logger.Instance.Initialize(logFile, config);
|
||||
}
|
||||
|
||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
base.OnBackgroundActivated(args);
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
|
||||
{
|
||||
if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||
{
|
||||
_appServiceDeferral = args.TaskInstance.GetDeferral();
|
||||
args.TaskInstance.Canceled += OnTaskCanceled;
|
||||
|
||||
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
|
||||
|
||||
_trayReady.SetResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
_appServiceDeferral?.Complete();
|
||||
_appServiceDeferral = null;
|
||||
_appServiceConnection = null;
|
||||
|
||||
// ReSharper disable once ArrangeStaticMemberQualifier
|
||||
Application.Current.Exit();
|
||||
}
|
||||
|
||||
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||
{
|
||||
await StartSystemTray().ConfigureAwait(true);
|
||||
|
||||
if (!(Window.Current.Content is Frame rootFrame))
|
||||
{
|
||||
rootFrame = new Frame();
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||
|
||||
if (viewModel is MainViewModel mainViewModel)
|
||||
{
|
||||
mainViewModel.Closed += OnMainViewModelClosed;
|
||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
mainViewModel.ActivatedMv += OnMainViewActivated;
|
||||
_mainViewModels.Add(mainViewModel);
|
||||
}
|
||||
|
||||
rootFrame.Navigate(pageType, viewModel);
|
||||
}
|
||||
_alreadyLaunched = true;
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
private async Task<MainViewModel> CreateNewTerminalWindow()
|
||||
{
|
||||
var viewModel = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
||||
viewModel.Closed += OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested += OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||
viewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||
viewModel.ActivatedMv += OnMainViewActivated;
|
||||
_mainViewModels.Add(viewModel);
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private async Task<TViewModel> CreateSecondaryView<TViewModel>(Type pageType, bool extendViewIntoTitleBar)
|
||||
{
|
||||
int windowId = 0;
|
||||
TViewModel viewModel = default;
|
||||
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
viewModel = _container.Resolve<TViewModel>();
|
||||
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||
var frame = new Frame();
|
||||
frame.Navigate(pageType, viewModel);
|
||||
Window.Current.Content = frame;
|
||||
Window.Current.Activate();
|
||||
|
||||
windowId = ApplicationView.GetForCurrentView().Id;
|
||||
});
|
||||
|
||||
if (viewModel is SettingsViewModel settingsViewModel)
|
||||
{
|
||||
_settingsViewModel = settingsViewModel;
|
||||
_settingsViewModel.Closed += OnSettingsClosed;
|
||||
_settingsWindowId = windowId;
|
||||
}
|
||||
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId);
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||
{
|
||||
_applicationSettings = e;
|
||||
}
|
||||
|
||||
private void OnMainViewModelClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is MainViewModel viewModel)
|
||||
{
|
||||
Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id);
|
||||
|
||||
viewModel.Closed -= OnMainViewModelClosed;
|
||||
viewModel.NewWindowRequested -= OnNewWindowRequested;
|
||||
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
|
||||
viewModel.ShowAboutRequested -= OnShowAboutRequested;
|
||||
viewModel.ActivatedMv -= OnMainViewActivated;
|
||||
if (_activeWindowId == viewModel.ApplicationView.Id)
|
||||
{
|
||||
_activeWindowId = 0;
|
||||
}
|
||||
|
||||
_mainViewModels.Remove(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainViewActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is MainViewModel viewModel)
|
||||
{
|
||||
Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} activated.", viewModel.ApplicationView.Id);
|
||||
_activeWindowId = viewModel.ApplicationView.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||
{
|
||||
ShellProfile profile = null;
|
||||
switch (e.Action)
|
||||
{
|
||||
case NewWindowAction.StartDefaultLocalTerminal:
|
||||
break;
|
||||
case NewWindowAction.ShowProfileSelection:
|
||||
profile = await _dialogService.ShowProfileSelectionDialogAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowSshProfileSelection:
|
||||
profile = await _sshHelperService.Value.GetSavedSshProfileAsync();
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.ShowSshInfoDialog:
|
||||
profile = await _sshHelperService.Value.GetSshProfileAsync(new SshProfile());
|
||||
if (profile == null)
|
||||
{
|
||||
// Nothing to do if user cancels.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NewWindowAction.StartLocalTerminal:
|
||||
profile = _settingsService.GetShellProfile(e.ProfileId);
|
||||
break;
|
||||
case NewWindowAction.StartSshTerminal:
|
||||
profile = _settingsService.GetSshProfile(e.ProfileId);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("this ProfileSelection not implemented yet");
|
||||
}
|
||||
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
await viewModel.AddLocalTabAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsClosed(object sender, EventArgs e)
|
||||
{
|
||||
Task.Run(async () => await JumpListHelper.Update(_settingsService.GetShellProfiles()));
|
||||
_settingsViewModel.Closed -= OnSettingsClosed;
|
||||
_settingsViewModel = null;
|
||||
_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()
|
||||
{
|
||||
await ShowSettings().ConfigureAwait(true);
|
||||
_settingsViewModel.NavigateToAboutPage();
|
||||
}
|
||||
|
||||
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location)
|
||||
{
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<MainViewModel>();
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||
}
|
||||
else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
||||
{
|
||||
|
||||
MainViewModel item = _mainViewModels.FirstOrDefault(o => o.ApplicationView.Id == _activeWindowId);
|
||||
if (item != null)
|
||||
{
|
||||
await item.AddTerminalAsync(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _mainViewModels.Last().AddTerminalAsync(profile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||
await viewModel.AddTerminalAsync(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowSettings()
|
||||
{
|
||||
if (!_alreadyLaunched)
|
||||
{
|
||||
var viewModel = _container.Resolve<SettingsViewModel>();
|
||||
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
|
||||
}
|
||||
else if (_settingsViewModel == null)
|
||||
{
|
||||
await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true).ConfigureAwait(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartSystemTray()
|
||||
{
|
||||
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
|
||||
await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true);
|
||||
_trayProcessCommunicationService.Initialize(_appServiceConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 962 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 789 B After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 926 B |
|
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 510 B |
|
After Width: | Height: | Size: 369 B |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 730 B |
|
After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 973 B |
|
After Width: | Height: | Size: 689 B |