Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bbed15874 | ||
|
|
39960dd755 | ||
|
|
8e5d3dea0a | ||
|
|
973b1a4963 | ||
|
|
997cb9814e | ||
|
|
dbd8632076 | ||
|
|
fffa7ebcb9 | ||
|
|
6524d14687 | ||
|
|
7876984327 | ||
|
|
ded043db1d | ||
|
|
61ad64519b | ||
|
|
ef7f909d26 | ||
|
|
f2618fa940 | ||
|
|
363f885aea | ||
|
|
ed4a5adb16 | ||
|
|
82a3532e90 | ||
|
|
15d713191c | ||
|
|
575022139a | ||
|
|
b9686ac4ec |
No files matched your search
@@ -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\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\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\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()
|
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
|
||||||
{
|
{
|
||||||
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
|
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>);
|
||||||
|
|
||||||
dialogService.ShowCreateKeyBindingDialog();
|
dialogService.ShowCreateKeyBindingDialog();
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ namespace FluentTerminal.App.Services.Test
|
|||||||
var title = string.Empty;
|
var title = string.Empty;
|
||||||
var content = _fixture.Create<string>();
|
var content = _fixture.Create<string>();
|
||||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
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>);
|
||||||
|
|
||||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
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 title = _fixture.Create<string>();
|
||||||
var content = string.Empty;
|
var content = string.Empty;
|
||||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
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>);
|
||||||
|
|
||||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
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 title = _fixture.Create<string>();
|
||||||
var content = _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>);
|
||||||
|
|
||||||
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
|
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ namespace FluentTerminal.App.Services.Test
|
|||||||
var content = _fixture.Create<string>();
|
var content = _fixture.Create<string>();
|
||||||
var buttons = _fixture.CreateMany<DialogButton>(2);
|
var buttons = _fixture.CreateMany<DialogButton>(2);
|
||||||
var messageDialog = new Mock<IMessageDialog>();
|
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>);
|
||||||
|
|
||||||
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
|
||||||
|
|
||||||
@@ -86,11 +86,24 @@ namespace FluentTerminal.App.Services.Test
|
|||||||
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
|
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
|
||||||
{
|
{
|
||||||
var shellProfileSelectionDialog = new Mock<IShellProfileSelectionDialog>();
|
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>);
|
||||||
|
|
||||||
dialogService.ShowProfileSelectionDialogAsync();
|
dialogService.ShowProfileSelectionDialogAsync();
|
||||||
|
|
||||||
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
|
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>);
|
||||||
|
|
||||||
|
dialogService.ShowInputDialogAsync(title);
|
||||||
|
|
||||||
|
inputDialog.Verify(x => x.SetTitle(title), Times.Once);
|
||||||
|
inputDialog.Verify(x => x.GetInput(), Times.Once);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace FluentTerminal.App.Services.Dialogs
|
||||||
|
{
|
||||||
|
public interface ISshConnectionInfo
|
||||||
|
{
|
||||||
|
string Host { get; set; }
|
||||||
|
|
||||||
|
ushort Port { get; set; }
|
||||||
|
|
||||||
|
string Username { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Services.Dialogs
|
||||||
|
{
|
||||||
|
public interface ISshConnectionInfoDialog
|
||||||
|
{
|
||||||
|
Task<ISshConnectionInfo> GetSshConnectionInfoAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentTerminal.Models;
|
using FluentTerminal.Models;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FluentTerminal.App.Services.Dialogs;
|
||||||
|
|
||||||
namespace FluentTerminal.App.Services
|
namespace FluentTerminal.App.Services
|
||||||
{
|
{
|
||||||
@@ -17,5 +18,7 @@ namespace FluentTerminal.App.Services
|
|||||||
|
|
||||||
Task<KeyBinding> ShowCreateKeyBindingDialog();
|
Task<KeyBinding> ShowCreateKeyBindingDialog();
|
||||||
Task<string> ShowInputDialogAsync(string title);
|
Task<string> ShowInputDialogAsync(string title);
|
||||||
|
|
||||||
|
Task<ISshConnectionInfo> ShowSshConnectionInfoDialogAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,19 @@ namespace FluentTerminal.App.Services.Implementation
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case Command.NewRemoteTab:
|
||||||
|
return new List<KeyBinding>
|
||||||
|
{
|
||||||
|
new KeyBinding
|
||||||
|
{
|
||||||
|
Command = nameof(Command.NewRemoteTab),
|
||||||
|
Ctrl = false,
|
||||||
|
Alt = true,
|
||||||
|
Shift = false,
|
||||||
|
Key = (int)ExtendedVirtualKey.T
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
case Command.ChangeTabTitle:
|
case Command.ChangeTabTitle:
|
||||||
return new List<KeyBinding>
|
return new List<KeyBinding>
|
||||||
{
|
{
|
||||||
@@ -578,18 +591,18 @@ namespace FluentTerminal.App.Services.Implementation
|
|||||||
Black = "#300A24",
|
Black = "#300A24",
|
||||||
Red = "#CC0000",
|
Red = "#CC0000",
|
||||||
Green = "#4E9A06",
|
Green = "#4E9A06",
|
||||||
Yellow = "#06989A",
|
Yellow = "#C4A000",
|
||||||
Blue = "#3465A4",
|
Blue = "#3465A4",
|
||||||
Magenta = "#75507B",
|
Magenta = "#75507B",
|
||||||
Cyan = "#C4A000",
|
Cyan = "#06989A",
|
||||||
White = "#D3D7CF",
|
White = "#D3D7CF",
|
||||||
BrightBlack = "#554E53",
|
BrightBlack = "#554E53",
|
||||||
BrightRed = "#EF2929",
|
BrightRed = "#EF2929",
|
||||||
BrightGreen = "#8AE234",
|
BrightGreen = "#8AE234",
|
||||||
BrightYellow = "#34E2E2",
|
BrightYellow = "#FCE94F",
|
||||||
BrightBlue = "#729FCF",
|
BrightBlue = "#729FCF",
|
||||||
BrightMagenta = "#AD7FA8",
|
BrightMagenta = "#AD7FA8",
|
||||||
BrightCyan = "#FCE94F",
|
BrightCyan = "#34E2E2",
|
||||||
BrightWhite = "#EEEEEE"
|
BrightWhite = "#EEEEEE"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ namespace FluentTerminal.App.Services.Implementation
|
|||||||
private readonly Func<IMessageDialog> _messageDialogFactory;
|
private readonly Func<IMessageDialog> _messageDialogFactory;
|
||||||
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
|
||||||
private readonly Func<IInputDialog> _inputDialogFactory;
|
private readonly Func<IInputDialog> _inputDialogFactory;
|
||||||
|
private readonly Func<ISshConnectionInfoDialog> _sshConnectionInfoDialogFactory;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
|
||||||
_messageDialogFactory = messageDialogFactory;
|
_messageDialogFactory = messageDialogFactory;
|
||||||
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
|
||||||
_inputDialogFactory = inputDialogFactory;
|
_inputDialogFactory = inputDialogFactory;
|
||||||
|
_sshConnectionInfoDialogFactory = sshConnectionInfoDialogFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<KeyBinding> ShowCreateKeyBindingDialog()
|
public Task<KeyBinding> ShowCreateKeyBindingDialog()
|
||||||
@@ -70,5 +74,8 @@ namespace FluentTerminal.App.Services.Implementation
|
|||||||
|
|
||||||
return dialog.SelectProfile();
|
return dialog.SelectProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ISshConnectionInfo> ShowSshConnectionInfoDialogAsync() =>
|
||||||
|
_sshConnectionInfoDialogFactory().GetSshConnectionInfoAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentTerminal.Models;
|
using FluentTerminal.Models;
|
||||||
using FluentTerminal.Models.Enums;
|
using FluentTerminal.Models.Enums;
|
||||||
|
using FluentTerminal.Models.Responses;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ namespace FluentTerminal.App.Services
|
|||||||
/// <param name="shellProfile"></param>
|
/// <param name="shellProfile"></param>
|
||||||
/// <param name="size"></param>
|
/// <param name="size"></param>
|
||||||
/// <param name="sessionType"></param>
|
/// <param name="sessionType"></param>
|
||||||
public async Task StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
public async Task<CreateTerminalResponse> StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
|
||||||
{
|
{
|
||||||
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
|
||||||
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
|
||||||
@@ -124,11 +125,20 @@ namespace FluentTerminal.App.Services
|
|||||||
FallbackTitle = response.ShellExecutableName;
|
FallbackTitle = response.ShellExecutableName;
|
||||||
SetTitle(FallbackTitle);
|
SetTitle(FallbackTitle);
|
||||||
}
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Write(byte[] data)
|
public Task Write(byte[] data)
|
||||||
{
|
{
|
||||||
return _trayProcessCommunicationService.Write(Id, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FluentTerminal.App.Services.Implementation;
|
||||||
|
|
||||||
namespace FluentTerminal.App.ViewModels
|
namespace FluentTerminal.App.ViewModels
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,7 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
private readonly IKeyboardCommandService _keyboardCommandService;
|
private readonly IKeyboardCommandService _keyboardCommandService;
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||||
|
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||||
private ApplicationSettings _applicationSettings;
|
private ApplicationSettings _applicationSettings;
|
||||||
private string _background;
|
private string _background;
|
||||||
private double _backgroundOpacity;
|
private double _backgroundOpacity;
|
||||||
@@ -28,7 +30,7 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
private string _windowTitle;
|
private string _windowTitle;
|
||||||
|
|
||||||
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
|
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
|
||||||
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
|
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, IDefaultValueProvider defaultValueProvider)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
|
||||||
@@ -42,8 +44,10 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
ApplicationView = applicationView;
|
ApplicationView = applicationView;
|
||||||
_dispatcherTimer = dispatcherTimer;
|
_dispatcherTimer = dispatcherTimer;
|
||||||
_clipboardService = clipboardService;
|
_clipboardService = clipboardService;
|
||||||
|
_defaultValueProvider = defaultValueProvider;
|
||||||
_keyboardCommandService = keyboardCommandService;
|
_keyboardCommandService = keyboardCommandService;
|
||||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal());
|
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal());
|
||||||
|
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewRemoteTab), () => AddRemoteTerminal());
|
||||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddConfigurableTerminal());
|
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddConfigurableTerminal());
|
||||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle());
|
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle());
|
||||||
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
|
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
|
||||||
@@ -214,6 +218,34 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task AddRemoteTerminal()
|
||||||
|
{
|
||||||
|
return ApplicationView.RunOnDispatcherThread(async () =>
|
||||||
|
{
|
||||||
|
var connectionInfo = await _dialogService.ShowSshConnectionInfoDialogAsync();
|
||||||
|
|
||||||
|
if (connectionInfo == null)
|
||||||
|
{
|
||||||
|
if (Terminals.Count == 0)
|
||||||
|
{
|
||||||
|
await ApplicationView.TryClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = new ShellProfile
|
||||||
|
{
|
||||||
|
Arguments = $"-p {connectionInfo.Port:#####} {connectionInfo.Username}@{connectionInfo.Host}",
|
||||||
|
Location = @"C:\Windows\System32\OpenSSH\ssh.exe",
|
||||||
|
WorkingDirectory = string.Empty,
|
||||||
|
LineEndingTranslation = LineEndingStyle.DoNotModify,
|
||||||
|
};
|
||||||
|
|
||||||
|
AddTerminal(profile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void AddTerminal()
|
public void AddTerminal()
|
||||||
{
|
{
|
||||||
var profile = _settingsService.GetDefaultShellProfile();
|
var profile = _settingsService.GetDefaultShellProfile();
|
||||||
@@ -232,6 +264,7 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
{
|
{
|
||||||
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
|
||||||
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService);
|
_applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService);
|
||||||
|
|
||||||
terminal.Closed += OnTerminalClosed;
|
terminal.Closed += OnTerminalClosed;
|
||||||
terminal.ShellTitleChanged += Terminal_ShellTitleChanged;
|
terminal.ShellTitleChanged += Terminal_ShellTitleChanged;
|
||||||
terminal.CustomTitleChanged += Terminal_CustomTitleChanged;
|
terminal.CustomTitleChanged += Terminal_CustomTitleChanged;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
private string _workingDirectory;
|
private string _workingDirectory;
|
||||||
private readonly IApplicationView _applicationView;
|
private readonly IApplicationView _applicationView;
|
||||||
private readonly IDefaultValueProvider _defaultValueProvider;
|
private readonly IDefaultValueProvider _defaultValueProvider;
|
||||||
private bool _editingLineEndingStyle;
|
|
||||||
|
|
||||||
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView, IDefaultValueProvider defaultValueProvider, Boolean isNew)
|
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView, IDefaultValueProvider defaultValueProvider, Boolean isNew)
|
||||||
{
|
{
|
||||||
@@ -200,37 +199,31 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
public LineEndingStyle LineEndingStyle
|
public LineEndingStyle LineEndingStyle
|
||||||
{
|
{
|
||||||
get => _lineEndingStyle;
|
get => _lineEndingStyle;
|
||||||
set
|
set => Set(ref _lineEndingStyle, value);
|
||||||
{
|
|
||||||
if (_lineEndingStyle != value && !_editingLineEndingStyle)
|
|
||||||
{
|
|
||||||
_editingLineEndingStyle = true;
|
|
||||||
_lineEndingStyle = value;
|
|
||||||
RaisePropertyChanged();
|
|
||||||
RaisePropertyChanged(nameof(DoNotModifyIsSelected));
|
|
||||||
RaisePropertyChanged(nameof(ToCRLFIsSelected));
|
|
||||||
RaisePropertyChanged(nameof(ToLFIsSelected));
|
|
||||||
_editingLineEndingStyle = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoNotModifyIsSelected
|
public bool DoNotModifyIsSelected
|
||||||
{
|
{
|
||||||
get => LineEndingStyle == LineEndingStyle.DoNotModify;
|
get => LineEndingStyle == LineEndingStyle.DoNotModify;
|
||||||
set => LineEndingStyle = LineEndingStyle.DoNotModify;
|
set { if (value) LineEndingStyle = LineEndingStyle.DoNotModify; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ToCRLFIsSelected
|
public bool ToCRLFIsSelected
|
||||||
{
|
{
|
||||||
get => LineEndingStyle == LineEndingStyle.ToCRLF;
|
get => LineEndingStyle == LineEndingStyle.ToCRLF;
|
||||||
set => LineEndingStyle = LineEndingStyle.ToCRLF;
|
set { if (value) LineEndingStyle = LineEndingStyle.ToCRLF; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ToLFIsSelected
|
public bool ToLFIsSelected
|
||||||
{
|
{
|
||||||
get => LineEndingStyle == LineEndingStyle.ToLF;
|
get => LineEndingStyle == LineEndingStyle.ToLF;
|
||||||
set => LineEndingStyle = LineEndingStyle.ToLF;
|
set { if (value) LineEndingStyle = LineEndingStyle.ToLF; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ToCRIsSelected
|
||||||
|
{
|
||||||
|
get => LineEndingStyle == LineEndingStyle.ToCR;
|
||||||
|
set { if (value) LineEndingStyle = LineEndingStyle.ToCR; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveChanges()
|
public void SaveChanges()
|
||||||
@@ -363,6 +356,7 @@ namespace FluentTerminal.App.ViewModels
|
|||||||
_fallbackProfile = new ShellProfile
|
_fallbackProfile = new ShellProfile
|
||||||
{
|
{
|
||||||
Arguments = Model.Arguments,
|
Arguments = Model.Arguments,
|
||||||
|
LineEndingTranslation = Model.LineEndingTranslation,
|
||||||
Location = Model.Location,
|
Location = Model.Location,
|
||||||
Name = Model.Name,
|
Name = Model.Name,
|
||||||
WorkingDirectory = Model.WorkingDirectory,
|
WorkingDirectory = Model.WorkingDirectory,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FluentTerminal.App.Services.Dialogs;
|
||||||
|
using GalaSoft.MvvmLight;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.ViewModels
|
||||||
|
{
|
||||||
|
public class SshConnectionInfoViewModel : ViewModelBase, ISshConnectionInfo
|
||||||
|
{
|
||||||
|
private string _host = string.Empty;
|
||||||
|
|
||||||
|
public string Host
|
||||||
|
{
|
||||||
|
get => _host;
|
||||||
|
set => Set(ref _host, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort _port = 22;
|
||||||
|
|
||||||
|
public ushort Port
|
||||||
|
{
|
||||||
|
get => _port;
|
||||||
|
set => Set(ref _port, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _username = string.Empty;
|
||||||
|
|
||||||
|
public string Username
|
||||||
|
{
|
||||||
|
get => _username;
|
||||||
|
set => Set(ref _username, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+452
-451
@@ -1,452 +1,453 @@
|
|||||||
using Autofac;
|
using Autofac;
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
using FluentTerminal.App.Adapters;
|
using FluentTerminal.App.Adapters;
|
||||||
using FluentTerminal.App.CommandLineArguments;
|
using FluentTerminal.App.CommandLineArguments;
|
||||||
using FluentTerminal.App.Dialogs;
|
using FluentTerminal.App.Dialogs;
|
||||||
using FluentTerminal.App.Services;
|
using FluentTerminal.App.Services;
|
||||||
using FluentTerminal.App.Services.Adapters;
|
using FluentTerminal.App.Services.Adapters;
|
||||||
using FluentTerminal.App.Services.Dialogs;
|
using FluentTerminal.App.Services.Dialogs;
|
||||||
using FluentTerminal.App.Services.EventArgs;
|
using FluentTerminal.App.Services.EventArgs;
|
||||||
using FluentTerminal.App.Services.Implementation;
|
using FluentTerminal.App.Services.Implementation;
|
||||||
using FluentTerminal.App.ViewModels;
|
using FluentTerminal.App.ViewModels;
|
||||||
using FluentTerminal.App.Views;
|
using FluentTerminal.App.Views;
|
||||||
using FluentTerminal.Models;
|
using FluentTerminal.Models;
|
||||||
using FluentTerminal.Models.Enums;
|
using FluentTerminal.Models.Enums;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Windows.ApplicationModel;
|
using Windows.ApplicationModel;
|
||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
using Windows.ApplicationModel.AppService;
|
using Windows.ApplicationModel.AppService;
|
||||||
using Windows.ApplicationModel.Background;
|
using Windows.ApplicationModel.Background;
|
||||||
using Windows.ApplicationModel.Core;
|
using Windows.ApplicationModel.Core;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Windows.UI.Core;
|
using Windows.UI.Core;
|
||||||
using Windows.UI.ViewManagement;
|
using Windows.UI.ViewManagement;
|
||||||
using Windows.UI.Xaml;
|
using Windows.UI.Xaml;
|
||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using IContainer = Autofac.IContainer;
|
using IContainer = Autofac.IContainer;
|
||||||
|
|
||||||
namespace FluentTerminal.App
|
namespace FluentTerminal.App
|
||||||
{
|
{
|
||||||
public sealed partial class App : Application
|
public sealed partial class App : Application
|
||||||
{
|
{
|
||||||
public TaskCompletionSource<int> _trayReady = new TaskCompletionSource<int>();
|
public TaskCompletionSource<int> _trayReady = new TaskCompletionSource<int>();
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
|
||||||
private bool _alreadyLaunched;
|
private bool _alreadyLaunched;
|
||||||
private ApplicationSettings _applicationSettings;
|
private ApplicationSettings _applicationSettings;
|
||||||
private readonly IContainer _container;
|
private readonly IContainer _container;
|
||||||
private readonly List<MainViewModel> _mainViewModels;
|
private readonly List<MainViewModel> _mainViewModels;
|
||||||
private SettingsViewModel _settingsViewModel;
|
private SettingsViewModel _settingsViewModel;
|
||||||
private int? _settingsWindowId;
|
private int? _settingsWindowId;
|
||||||
private IAppServiceConnection _appServiceConnection;
|
private IAppServiceConnection _appServiceConnection;
|
||||||
private BackgroundTaskDeferral _appServiceDeferral;
|
private BackgroundTaskDeferral _appServiceDeferral;
|
||||||
private Parser _commandLineParser;
|
private Parser _commandLineParser;
|
||||||
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
_mainViewModels = new List<MainViewModel>();
|
_mainViewModels = new List<MainViewModel>();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
UnhandledException += OnUnhandledException;
|
UnhandledException += OnUnhandledException;
|
||||||
|
|
||||||
var applicationDataContainers = new ApplicationDataContainers
|
var applicationDataContainers = new ApplicationDataContainers
|
||||||
{
|
{
|
||||||
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
|
||||||
RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
|
RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
|
||||||
KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
|
KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
|
||||||
ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
|
ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
|
||||||
Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always))
|
Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always))
|
||||||
};
|
};
|
||||||
|
|
||||||
var builder = new ContainerBuilder();
|
var builder = new ContainerBuilder();
|
||||||
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
|
||||||
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
|
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
|
||||||
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
|
||||||
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
|
||||||
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
|
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
|
||||||
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
|
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
|
||||||
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
|
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
|
||||||
builder.RegisterType<MainViewModel>().InstancePerDependency();
|
builder.RegisterType<MainViewModel>().InstancePerDependency();
|
||||||
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
|
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
|
||||||
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
|
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
|
||||||
builder.RegisterType<ITermThemeParser>().As<IThemeParser>().SingleInstance();
|
builder.RegisterType<ITermThemeParser>().As<IThemeParser>().SingleInstance();
|
||||||
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
|
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
|
||||||
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
|
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
|
||||||
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
|
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
|
||||||
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
|
||||||
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
|
||||||
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
|
||||||
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
|
||||||
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
|
||||||
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
builder.RegisterType<SshInfoDialog>().As<ISshConnectionInfoDialog>().InstancePerDependency();
|
||||||
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
|
||||||
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
|
||||||
builder.RegisterInstance(applicationDataContainers);
|
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
|
||||||
|
builder.RegisterInstance(applicationDataContainers);
|
||||||
_container = builder.Build();
|
|
||||||
|
_container = builder.Build();
|
||||||
_settingsService = _container.Resolve<ISettingsService>();
|
|
||||||
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
_settingsService = _container.Resolve<ISettingsService>();
|
||||||
|
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
|
||||||
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
|
|
||||||
|
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
|
||||||
_applicationSettings = _settingsService.GetApplicationSettings();
|
|
||||||
|
_applicationSettings = _settingsService.GetApplicationSettings();
|
||||||
JsonConvert.DefaultSettings = () =>
|
|
||||||
{
|
JsonConvert.DefaultSettings = () =>
|
||||||
var settings = new JsonSerializerSettings
|
{
|
||||||
{
|
var settings = new JsonSerializerSettings
|
||||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
{
|
||||||
};
|
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||||
settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));
|
};
|
||||||
|
settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy)));
|
||||||
return settings;
|
|
||||||
};
|
return settings;
|
||||||
|
};
|
||||||
_commandLineParser = new Parser(settings =>
|
|
||||||
{
|
_commandLineParser = new Parser(settings =>
|
||||||
settings.CaseSensitive = false;
|
{
|
||||||
settings.CaseInsensitiveEnumValues = true;
|
settings.CaseSensitive = false;
|
||||||
});
|
settings.CaseInsensitiveEnumValues = true;
|
||||||
}
|
});
|
||||||
|
}
|
||||||
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
|
||||||
{
|
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||||
Logger.Instance.Error(e.Exception, "Unhandled Exception");
|
{
|
||||||
}
|
Logger.Instance.Error(e.Exception, "Unhandled Exception");
|
||||||
|
}
|
||||||
private static IEnumerable<string> SplitArguments(string arguments)
|
|
||||||
{
|
private static IEnumerable<string> SplitArguments(string arguments)
|
||||||
var chars = arguments.ToCharArray();
|
{
|
||||||
var inQuote = false;
|
var chars = arguments.ToCharArray();
|
||||||
|
var inQuote = false;
|
||||||
for (var i = 0; i < chars.Length; i++)
|
|
||||||
{
|
for (var i = 0; i < chars.Length; i++)
|
||||||
if (chars[i] == '"')
|
{
|
||||||
{
|
if (chars[i] == '"')
|
||||||
inQuote = !inQuote;
|
{
|
||||||
}
|
inQuote = !inQuote;
|
||||||
|
}
|
||||||
if (!inQuote && chars[i] == ' ')
|
|
||||||
{
|
if (!inQuote && chars[i] == ' ')
|
||||||
chars[i] = '\n';
|
{
|
||||||
}
|
chars[i] = '\n';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
foreach (var value in new string(chars).Split('\n'))
|
|
||||||
{
|
foreach (var value in new string(chars).Split('\n'))
|
||||||
yield return value.Trim('"');
|
{
|
||||||
}
|
yield return value.Trim('"');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected override void OnActivated(IActivatedEventArgs args)
|
|
||||||
{
|
protected override void OnActivated(IActivatedEventArgs args)
|
||||||
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
{
|
||||||
{
|
if (args is CommandLineActivatedEventArgs commandLineActivated)
|
||||||
if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments))
|
{
|
||||||
{
|
if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments))
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
_commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
|
||||||
{
|
_commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb =>
|
||||||
if (verb is SettingsVerb)
|
{
|
||||||
{
|
if (verb is SettingsVerb)
|
||||||
await ShowSettings().ConfigureAwait(true);
|
{
|
||||||
}
|
await ShowSettings().ConfigureAwait(true);
|
||||||
else if (verb is NewVerb newVerb)
|
}
|
||||||
{
|
else if (verb is NewVerb newVerb)
|
||||||
var profile = default(ShellProfile);
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
|
var profile = default(ShellProfile);
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
|
||||||
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
|
{
|
||||||
}
|
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
}
|
||||||
if (profile == null)
|
|
||||||
{
|
if (profile == null)
|
||||||
profile = _settingsService.GetDefaultShellProfile();
|
{
|
||||||
}
|
profile = _settingsService.GetDefaultShellProfile();
|
||||||
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(newVerb.Directory))
|
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(newVerb.Directory))
|
||||||
profile.WorkingDirectory = newVerb.Directory;
|
{
|
||||||
}
|
profile.WorkingDirectory = newVerb.Directory;
|
||||||
|
}
|
||||||
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
|
||||||
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||||
: NewTerminalLocation.Window;
|
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||||
|
: NewTerminalLocation.Window;
|
||||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
|
||||||
}
|
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||||
else if (verb is RunVerb runVerb)
|
}
|
||||||
{
|
else if (verb is RunVerb runVerb)
|
||||||
var profile = new ShellProfile
|
{
|
||||||
{
|
var profile = new ShellProfile
|
||||||
Id = Guid.Empty,
|
{
|
||||||
Location = null,
|
Id = Guid.Empty,
|
||||||
Arguments = runVerb.Command,
|
Location = null,
|
||||||
WorkingDirectory = runVerb.Directory
|
Arguments = runVerb.Command,
|
||||||
};
|
WorkingDirectory = runVerb.Directory
|
||||||
|
};
|
||||||
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
|
|
||||||
{
|
if (!string.IsNullOrWhiteSpace(runVerb.Theme))
|
||||||
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
|
{
|
||||||
if (theme != null)
|
var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase));
|
||||||
{
|
if (theme != null)
|
||||||
profile.TerminalThemeId = theme.Id;
|
{
|
||||||
}
|
profile.TerminalThemeId = theme.Id;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
|
|
||||||
{
|
if (string.IsNullOrWhiteSpace(profile.WorkingDirectory))
|
||||||
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
{
|
||||||
}
|
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
|
||||||
|
}
|
||||||
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
|
||||||
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
|
||||||
: NewTerminalLocation.Window;
|
: runVerb.Target == Target.Tab ? NewTerminalLocation.Tab
|
||||||
|
: NewTerminalLocation.Window;
|
||||||
await CreateTerminal(profile, location).ConfigureAwait(true);
|
|
||||||
}
|
await CreateTerminal(profile, location).ConfigureAwait(true);
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
|
||||||
{
|
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
if (!_alreadyLaunched)
|
{
|
||||||
{
|
if (!_alreadyLaunched)
|
||||||
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
|
{
|
||||||
var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log");
|
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
|
||||||
var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log");
|
||||||
var configContent = await FileIO.ReadTextAsync(configFile);
|
var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
|
||||||
|
var configContent = await FileIO.ReadTextAsync(configFile);
|
||||||
if (string.IsNullOrWhiteSpace(configContent))
|
|
||||||
{
|
if (string.IsNullOrWhiteSpace(configContent))
|
||||||
configContent = JsonConvert.SerializeObject(new Logger.Configuration());
|
{
|
||||||
await FileIO.WriteTextAsync(configFile, configContent);
|
configContent = JsonConvert.SerializeObject(new Logger.Configuration());
|
||||||
}
|
await FileIO.WriteTextAsync(configFile, configContent);
|
||||||
|
}
|
||||||
var config = JsonConvert.DeserializeObject<Logger.Configuration>(configContent) ?? new Logger.Configuration();
|
|
||||||
|
var config = JsonConvert.DeserializeObject<Logger.Configuration>(configContent) ?? new Logger.Configuration();
|
||||||
Logger.Instance.Initialize(logFile, config);
|
|
||||||
|
Logger.Instance.Initialize(logFile, config);
|
||||||
var viewModel = _container.Resolve<MainViewModel>();
|
|
||||||
viewModel.AddTerminal();
|
var viewModel = _container.Resolve<MainViewModel>();
|
||||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
viewModel.AddTerminal();
|
||||||
Window.Current.Activate();
|
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||||
}
|
Window.Current.Activate();
|
||||||
else if (_mainViewModels.Count == 0)
|
}
|
||||||
{
|
else if (_mainViewModels.Count == 0)
|
||||||
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
{
|
||||||
}
|
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
|
||||||
{
|
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||||
base.OnBackgroundActivated(args);
|
{
|
||||||
|
base.OnBackgroundActivated(args);
|
||||||
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
|
|
||||||
{
|
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
|
||||||
if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
{
|
||||||
{
|
if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
|
||||||
_appServiceDeferral = args.TaskInstance.GetDeferral();
|
{
|
||||||
args.TaskInstance.Canceled += OnTaskCanceled;
|
_appServiceDeferral = args.TaskInstance.GetDeferral();
|
||||||
|
args.TaskInstance.Canceled += OnTaskCanceled;
|
||||||
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
|
|
||||||
|
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
|
||||||
_trayReady.SetResult(0);
|
|
||||||
}
|
_trayReady.SetResult(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
|
||||||
{
|
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||||
_appServiceDeferral?.Complete();
|
{
|
||||||
_appServiceDeferral = null;
|
_appServiceDeferral?.Complete();
|
||||||
_appServiceConnection = null;
|
_appServiceDeferral = null;
|
||||||
|
_appServiceConnection = null;
|
||||||
Application.Current.Exit();
|
|
||||||
}
|
Application.Current.Exit();
|
||||||
|
}
|
||||||
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
|
||||||
{
|
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
|
||||||
await StartSystemTray().ConfigureAwait(true);
|
{
|
||||||
|
await StartSystemTray().ConfigureAwait(true);
|
||||||
Frame rootFrame = Window.Current.Content as Frame;
|
|
||||||
if (rootFrame == null)
|
Frame rootFrame = Window.Current.Content as Frame;
|
||||||
{
|
if (rootFrame == null)
|
||||||
rootFrame = new Frame();
|
{
|
||||||
Window.Current.Content = rootFrame;
|
rootFrame = new Frame();
|
||||||
}
|
Window.Current.Content = rootFrame;
|
||||||
|
}
|
||||||
if (rootFrame.Content == null)
|
|
||||||
{
|
if (rootFrame.Content == null)
|
||||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
{
|
||||||
|
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
|
||||||
if (viewModel is MainViewModel mainViewModel)
|
|
||||||
{
|
if (viewModel is MainViewModel mainViewModel)
|
||||||
mainViewModel.Closed += OnMainViewModelClosed;
|
{
|
||||||
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
mainViewModel.Closed += OnMainViewModelClosed;
|
||||||
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
mainViewModel.NewWindowRequested += OnNewWindowRequested;
|
||||||
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||||
_mainViewModels.Add(mainViewModel);
|
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||||
}
|
_mainViewModels.Add(mainViewModel);
|
||||||
|
}
|
||||||
rootFrame.Navigate(pageType, viewModel);
|
|
||||||
}
|
rootFrame.Navigate(pageType, viewModel);
|
||||||
_alreadyLaunched = true;
|
}
|
||||||
Window.Current.Activate();
|
_alreadyLaunched = true;
|
||||||
}
|
Window.Current.Activate();
|
||||||
|
}
|
||||||
private async Task<MainViewModel> CreateNewTerminalWindow()
|
|
||||||
{
|
private async Task<MainViewModel> CreateNewTerminalWindow()
|
||||||
var viewModel = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
{
|
||||||
viewModel.Closed += OnMainViewModelClosed;
|
var viewModel = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true).ConfigureAwait(true);
|
||||||
viewModel.NewWindowRequested += OnNewWindowRequested;
|
viewModel.Closed += OnMainViewModelClosed;
|
||||||
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
viewModel.NewWindowRequested += OnNewWindowRequested;
|
||||||
viewModel.ShowAboutRequested += OnShowAboutRequested;
|
viewModel.ShowSettingsRequested += OnShowSettingsRequested;
|
||||||
_mainViewModels.Add(viewModel);
|
viewModel.ShowAboutRequested += OnShowAboutRequested;
|
||||||
|
_mainViewModels.Add(viewModel);
|
||||||
return viewModel;
|
|
||||||
}
|
return viewModel;
|
||||||
|
}
|
||||||
private async Task<TViewModel> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar)
|
|
||||||
{
|
private async Task<TViewModel> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar)
|
||||||
int windowId = 0;
|
{
|
||||||
TViewModel viewModel = default;
|
int windowId = 0;
|
||||||
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
TViewModel viewModel = default;
|
||||||
{
|
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||||
viewModel = _container.Resolve<TViewModel>();
|
{
|
||||||
|
viewModel = _container.Resolve<TViewModel>();
|
||||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar;
|
|
||||||
var frame = new Frame();
|
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar;
|
||||||
frame.Navigate(pageType, viewModel);
|
var frame = new Frame();
|
||||||
Window.Current.Content = frame;
|
frame.Navigate(pageType, viewModel);
|
||||||
Window.Current.Activate();
|
Window.Current.Content = frame;
|
||||||
|
Window.Current.Activate();
|
||||||
windowId = ApplicationView.GetForCurrentView().Id;
|
|
||||||
});
|
windowId = ApplicationView.GetForCurrentView().Id;
|
||||||
|
});
|
||||||
if (viewModel is SettingsViewModel settingsViewModel)
|
|
||||||
{
|
if (viewModel is SettingsViewModel settingsViewModel)
|
||||||
_settingsViewModel = settingsViewModel;
|
{
|
||||||
_settingsViewModel.Closed += OnSettingsClosed;
|
_settingsViewModel = settingsViewModel;
|
||||||
_settingsWindowId = windowId;
|
_settingsViewModel.Closed += OnSettingsClosed;
|
||||||
}
|
_settingsWindowId = windowId;
|
||||||
|
}
|
||||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId);
|
|
||||||
|
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId);
|
||||||
return viewModel;
|
|
||||||
}
|
return viewModel;
|
||||||
|
}
|
||||||
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
|
||||||
{
|
private void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
|
||||||
_applicationSettings = e;
|
{
|
||||||
}
|
_applicationSettings = e;
|
||||||
|
}
|
||||||
private void OnMainViewModelClosed(object sender, EventArgs e)
|
|
||||||
{
|
private void OnMainViewModelClosed(object sender, EventArgs e)
|
||||||
if (sender is MainViewModel viewModel)
|
{
|
||||||
{
|
if (sender is MainViewModel viewModel)
|
||||||
Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id);
|
{
|
||||||
|
Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id);
|
||||||
viewModel.Closed -= OnMainViewModelClosed;
|
|
||||||
viewModel.NewWindowRequested -= OnNewWindowRequested;
|
viewModel.Closed -= OnMainViewModelClosed;
|
||||||
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
|
viewModel.NewWindowRequested -= OnNewWindowRequested;
|
||||||
viewModel.ShowAboutRequested -= OnShowAboutRequested;
|
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
|
||||||
|
viewModel.ShowAboutRequested -= OnShowAboutRequested;
|
||||||
_mainViewModels.Remove(viewModel);
|
|
||||||
}
|
_mainViewModels.Remove(viewModel);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
|
||||||
{
|
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
|
||||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
{
|
||||||
|
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||||
if (e.ShowProfileSelection)
|
|
||||||
{
|
if (e.ShowProfileSelection)
|
||||||
await viewModel.AddConfigurableTerminal().ConfigureAwait(true);
|
{
|
||||||
}
|
await viewModel.AddConfigurableTerminal().ConfigureAwait(true);
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
viewModel.AddTerminal();
|
{
|
||||||
}
|
viewModel.AddTerminal();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void OnSettingsClosed(object sender, EventArgs e)
|
|
||||||
{
|
private void OnSettingsClosed(object sender, EventArgs e)
|
||||||
_settingsViewModel.Closed -= OnSettingsClosed;
|
{
|
||||||
_settingsViewModel = null;
|
_settingsViewModel.Closed -= OnSettingsClosed;
|
||||||
_settingsWindowId = null;
|
_settingsViewModel = null;
|
||||||
}
|
_settingsWindowId = null;
|
||||||
|
}
|
||||||
private void OnShowAboutRequested(object sender, EventArgs e)
|
|
||||||
{
|
private void OnShowAboutRequested(object sender, EventArgs e)
|
||||||
ShowAbout().ConfigureAwait(true);
|
{
|
||||||
}
|
ShowAbout().ConfigureAwait(true);
|
||||||
|
}
|
||||||
private async void OnShowSettingsRequested(object sender, EventArgs e)
|
|
||||||
{
|
private async void OnShowSettingsRequested(object sender, EventArgs e)
|
||||||
await ShowSettings().ConfigureAwait(true);
|
{
|
||||||
}
|
await ShowSettings().ConfigureAwait(true);
|
||||||
|
}
|
||||||
private async Task ShowAbout()
|
|
||||||
{
|
private async Task ShowAbout()
|
||||||
await ShowSettings().ConfigureAwait(true);
|
{
|
||||||
_settingsViewModel.NavigateToAboutPage();
|
await ShowSettings().ConfigureAwait(true);
|
||||||
}
|
_settingsViewModel.NavigateToAboutPage();
|
||||||
|
}
|
||||||
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location)
|
|
||||||
{
|
private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location)
|
||||||
if (!_alreadyLaunched)
|
{
|
||||||
{
|
if (!_alreadyLaunched)
|
||||||
var viewModel = _container.Resolve<MainViewModel>();
|
{
|
||||||
viewModel.AddTerminal(profile);
|
var viewModel = _container.Resolve<MainViewModel>();
|
||||||
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
viewModel.AddTerminal(profile);
|
||||||
}
|
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
|
||||||
else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
}
|
||||||
{
|
else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
|
||||||
_mainViewModels.Last().AddTerminal(profile);
|
{
|
||||||
}
|
_mainViewModels.Last().AddTerminal(profile);
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
{
|
||||||
viewModel.AddTerminal(profile);
|
var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true);
|
||||||
}
|
viewModel.AddTerminal(profile);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private async Task ShowSettings()
|
|
||||||
{
|
private async Task ShowSettings()
|
||||||
if (!_alreadyLaunched)
|
{
|
||||||
{
|
if (!_alreadyLaunched)
|
||||||
var viewModel = _container.Resolve<SettingsViewModel>();
|
{
|
||||||
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
|
var viewModel = _container.Resolve<SettingsViewModel>();
|
||||||
}
|
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
|
||||||
else if (_settingsViewModel == null)
|
}
|
||||||
{
|
else if (_settingsViewModel == null)
|
||||||
await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true).ConfigureAwait(true);
|
{
|
||||||
}
|
await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true).ConfigureAwait(true);
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
{
|
||||||
}
|
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private async Task StartSystemTray()
|
|
||||||
{
|
private async Task StartSystemTray()
|
||||||
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
|
{
|
||||||
await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true);
|
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
|
||||||
_trayProcessCommunicationService.Initialize(_appServiceConnection);
|
await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true);
|
||||||
}
|
_trayProcessCommunicationService.Initialize(_appServiceConnection);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<ContentDialog
|
||||||
|
x:Class="FluentTerminal.App.Dialogs.SshInfoDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="using:FluentTerminal.App.ViewModels"
|
||||||
|
Title="SSH Info"
|
||||||
|
PrimaryButtonText="OK"
|
||||||
|
SecondaryButtonText="Cancel"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<ContentDialog.DataContext>
|
||||||
|
<vm:SshConnectionInfoViewModel />
|
||||||
|
</ContentDialog.DataContext>
|
||||||
|
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||||
|
<TextBox
|
||||||
|
Width="50"
|
||||||
|
PlaceholderText="user"
|
||||||
|
Text="{Binding Username, Mode=TwoWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Width="20"
|
||||||
|
FontSize="22"
|
||||||
|
Text="@" />
|
||||||
|
<TextBox
|
||||||
|
Width="120"
|
||||||
|
PlaceholderText="host"
|
||||||
|
Text="{Binding Host, Mode=TwoWay}" />
|
||||||
|
<TextBlock
|
||||||
|
Width="20"
|
||||||
|
FontSize="22"
|
||||||
|
Text=":" />
|
||||||
|
<TextBox
|
||||||
|
Width="50"
|
||||||
|
PlaceholderText="port"
|
||||||
|
Text="{Binding Port, Mode=TwoWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ContentDialog>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using FluentTerminal.App.Services.Dialogs;
|
||||||
|
using FluentTerminal.App.Utilities;
|
||||||
|
using FluentTerminal.App.Services;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Dialogs
|
||||||
|
{
|
||||||
|
public sealed partial class SshInfoDialog : ContentDialog, ISshConnectionInfoDialog
|
||||||
|
{
|
||||||
|
public SshInfoDialog(ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
var currentTheme = settingsService.GetCurrentTheme();
|
||||||
|
RequestedTheme = ContrastHelper.GetIdealThemeForBackgroundColor(currentTheme.Colors.Background);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ISshConnectionInfo> GetSshConnectionInfoAsync()
|
||||||
|
{
|
||||||
|
ContentDialogResult result = await ShowAsync();
|
||||||
|
|
||||||
|
return result == ContentDialogResult.Primary ? (ISshConnectionInfo)DataContext : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,6 +154,26 @@
|
|||||||
<Compile Include="Dialogs\CreateKeyBindingDialog.xaml.cs">
|
<Compile Include="Dialogs\CreateKeyBindingDialog.xaml.cs">
|
||||||
<DependentUpon>CreateKeyBindingDialog.xaml</DependentUpon>
|
<DependentUpon>CreateKeyBindingDialog.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Views\NativeFrontend\Ansi\AnsiParser.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Ansi\C0.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Ansi\CharAttributes.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Ansi\TerminalCode.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Ansi\TerminalCodeType.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\TerminalColorHelper.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\TerminalControl.xaml.cs">
|
||||||
|
<DependentUpon>TerminalControl.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\SelectionMode.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\SpecialColorIds.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalBuffer.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalBufferChar.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalBufferLine.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalPoint.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalSelection.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalSize.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalTag.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Terminal\TerminalTagArray.cs" />
|
||||||
|
<Compile Include="Views\NativeFrontend\Utility\ArrayReader.cs" />
|
||||||
<Compile Include="Views\SettingsPage.xaml.cs">
|
<Compile Include="Views\SettingsPage.xaml.cs">
|
||||||
<DependentUpon>SettingsPage.xaml</DependentUpon>
|
<DependentUpon>SettingsPage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -178,6 +198,9 @@
|
|||||||
<Compile Include="Views\SettingsPages\ShellProfileSettings.xaml.cs">
|
<Compile Include="Views\SettingsPages\ShellProfileSettings.xaml.cs">
|
||||||
<DependentUpon>ShellProfileSettings.xaml</DependentUpon>
|
<DependentUpon>ShellProfileSettings.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Dialogs\SshInfoDialog.xaml.cs">
|
||||||
|
<DependentUpon>SshInfoDialog.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Views\TabBar.xaml.cs">
|
<Compile Include="Views\TabBar.xaml.cs">
|
||||||
<DependentUpon>TabBar.xaml</DependentUpon>
|
<DependentUpon>TabBar.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -193,6 +216,9 @@
|
|||||||
<Compile Include="Views\TerminalView.xaml.cs">
|
<Compile Include="Views\TerminalView.xaml.cs">
|
||||||
<DependentUpon>TerminalView.xaml</DependentUpon>
|
<DependentUpon>TerminalView.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Views\VtNetCoreFrontend\VtNetCoreTerminal.xaml.cs">
|
||||||
|
<DependentUpon>VtNetCoreTerminal.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Views\XtermTerminalView.xaml.cs">
|
<Compile Include="Views\XtermTerminalView.xaml.cs">
|
||||||
<DependentUpon>XtermTerminalView.xaml</DependentUpon>
|
<DependentUpon>XtermTerminalView.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -234,7 +260,6 @@
|
|||||||
<Content Include="Win32\winpty.dll" />
|
<Content Include="Win32\winpty.dll" />
|
||||||
<Content Include="Win32\winpty.NET.dll" />
|
<Content Include="Win32\winpty.NET.dll" />
|
||||||
<None Include="FluentTerminal.App_StoreKey.pfx" />
|
<None Include="FluentTerminal.App_StoreKey.pfx" />
|
||||||
<None Include="FluentTerminal.App_TemporaryKey.pfx" />
|
|
||||||
<Content Include="Client\bundle.js" />
|
<Content Include="Client\bundle.js" />
|
||||||
<Content Include="Client\index.html" />
|
<Content Include="Client\index.html" />
|
||||||
<Content Include="Client\style.css" />
|
<Content Include="Client\style.css" />
|
||||||
@@ -287,7 +312,6 @@
|
|||||||
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-150.png" />
|
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-150.png" />
|
||||||
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-200.png" />
|
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-200.png" />
|
||||||
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-400.png" />
|
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-400.png" />
|
||||||
<None Include="Package.StoreAssociation.xml" />
|
|
||||||
<Content Include="Properties\Default.rd.xml" />
|
<Content Include="Properties\Default.rd.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -320,6 +344,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\NativeFrontend\TerminalControl.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\SettingsPage.xaml">
|
<Page Include="Views\SettingsPage.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -352,6 +380,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Dialogs\SshInfoDialog.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\TabBar.xaml">
|
<Page Include="Views\TabBar.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -364,6 +396,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\VtNetCoreFrontend\VtNetCoreTerminal.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\XtermTerminalView.xaml">
|
<Page Include="Views\XtermTerminalView.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -391,6 +427,9 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json">
|
<PackageReference Include="Newtonsoft.Json">
|
||||||
<Version>12.0.1</Version>
|
<Version>12.0.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="VtNetCore">
|
||||||
|
<Version>1.0.15</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Win2D.uwp">
|
<PackageReference Include="Win2D.uwp">
|
||||||
<Version>1.21.0</Version>
|
<Version>1.21.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap mp desktop rescap">
|
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap mp desktop rescap">
|
||||||
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.4.0.0" />
|
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.4.1.0" />
|
||||||
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Fluent Terminal</DisplayName>
|
<DisplayName>Fluent Terminal</DisplayName>
|
||||||
|
|||||||
@@ -0,0 +1,679 @@
|
|||||||
|
using FluentTerminal.App.Views.NativeFrontend.Utility;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Ansi
|
||||||
|
{
|
||||||
|
internal class AnsiParser
|
||||||
|
{
|
||||||
|
private ParserState _state;
|
||||||
|
private int _position;
|
||||||
|
private object _currentParam;
|
||||||
|
private List<object> _params = new List<object>();
|
||||||
|
private List<TerminalCode> _terminalCodes = new List<TerminalCode>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSI codes can have prefixes of '?', '>'
|
||||||
|
/// </summary>
|
||||||
|
private string _prefix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSI codes can have postfixes of '$',
|
||||||
|
/// </summary>
|
||||||
|
private string _postfix;
|
||||||
|
|
||||||
|
private readonly StringBuilder _currentTextEmit = new StringBuilder(capacity: 128);
|
||||||
|
|
||||||
|
public TerminalCode[] Parse(ArrayReader<char> reader)
|
||||||
|
{
|
||||||
|
_terminalCodes.Clear();
|
||||||
|
while (reader.TryRead(out char ch))
|
||||||
|
{
|
||||||
|
int code = ch;
|
||||||
|
// if (0xD800 <= code && code <= 0xDBFF)
|
||||||
|
// {
|
||||||
|
// // we got a surrogate high
|
||||||
|
// // get surrogate low (next 2 bytes)
|
||||||
|
// if (!reader.TryPeek(out char low))
|
||||||
|
// {
|
||||||
|
// // end of data stream, save surrogate high
|
||||||
|
// // this._terminal.surrogate_high = ch;
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
|
||||||
|
// // ch += low;
|
||||||
|
// }
|
||||||
|
// // surrogate low - already handled above
|
||||||
|
// if (0xDC00 <= code && code <= 0xDFFF)
|
||||||
|
// {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
switch (_state)
|
||||||
|
{
|
||||||
|
case ParserState.Normal:
|
||||||
|
if (!NormalStateHandler(ch))
|
||||||
|
{
|
||||||
|
Emit(ch);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParserState.Escaped:
|
||||||
|
if (!EscapedStateHandler(ch))
|
||||||
|
{
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
// ESC (,),*,+,-,. Designate G0-G2 Character Set.
|
||||||
|
case '(': // <-- this seems to get all the attention
|
||||||
|
// this._terminal.gcharset = 0;
|
||||||
|
_state = ParserState.Charset;
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
case '-':
|
||||||
|
// this._terminal.gcharset = 1;
|
||||||
|
_state = ParserState.Charset;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
case '.':
|
||||||
|
// this._terminal.gcharset = 2;
|
||||||
|
_state = ParserState.Charset;
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
// this._terminal.gcharset = 3;
|
||||||
|
_state = ParserState.Charset;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Designate G3 Character Set (VT300).
|
||||||
|
// A = ISO Latin-1 Supplemental.
|
||||||
|
// Not implemented.
|
||||||
|
case '/':
|
||||||
|
// _terminal.gcharset = 3;
|
||||||
|
_state = ParserState.Charset;
|
||||||
|
// _position--
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC N
|
||||||
|
// Single Shift Select of G2 Character Set
|
||||||
|
// ( SS2 is 0x8e). This affects next character only.
|
||||||
|
case 'N':
|
||||||
|
break;
|
||||||
|
// ESC O
|
||||||
|
// Single Shift Select of G3 Character Set
|
||||||
|
// ( SS3 is 0x8f). This affects next character only.
|
||||||
|
case 'O':
|
||||||
|
break;
|
||||||
|
// ESC n
|
||||||
|
// Invoke the G2 Character Set as GL (LS2).
|
||||||
|
case 'n':
|
||||||
|
// this._terminal.setgLevel(2);
|
||||||
|
break;
|
||||||
|
// ESC o
|
||||||
|
// Invoke the G3 Character Set as GL (LS3).
|
||||||
|
case 'o':
|
||||||
|
// this._terminal.setgLevel(3);
|
||||||
|
break;
|
||||||
|
// ESC |
|
||||||
|
// Invoke the G3 Character Set as GR (LS3R).
|
||||||
|
case '|':
|
||||||
|
// this._terminal.setgLevel(3);
|
||||||
|
break;
|
||||||
|
// ESC }
|
||||||
|
// Invoke the G2 Character Set as GR (LS2R).
|
||||||
|
case '}':
|
||||||
|
// this._terminal.setgLevel(2);
|
||||||
|
break;
|
||||||
|
// ESC ~
|
||||||
|
// Invoke the G1 Character Set as GR (LS1R).
|
||||||
|
case '~':
|
||||||
|
// this._terminal.setgLevel(1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC 7 Save Cursor (DECSC).
|
||||||
|
case '7':
|
||||||
|
Emit(TerminalCodeType.SaveCursor);
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC 8 Restore Cursor (DECRC).
|
||||||
|
case '8':
|
||||||
|
Emit(TerminalCodeType.RestoreCursor);
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC # 3 DEC line height/width
|
||||||
|
case '#':
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
// _position++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC H Tab Set (HTS is 0x88).
|
||||||
|
case 'H':
|
||||||
|
Emit(TerminalCodeType.TabSet);
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC = Application Keypad (DECKPAM).
|
||||||
|
case '=':
|
||||||
|
// this._terminal.log('Serial port requested application keypad.');
|
||||||
|
// this._terminal.applicationKeypad = true;
|
||||||
|
// this._terminal.viewport.syncScrollArea();
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ESC > Normal Keypad (DECKPNM).
|
||||||
|
case '>':
|
||||||
|
// this._terminal.log('Switching back to normal keypad.');
|
||||||
|
// this._terminal.applicationKeypad = false;
|
||||||
|
// this._terminal.viewport.syncScrollArea();
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
// _terminal.error('Unknown ESC control: %s.', ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParserState.Charset:
|
||||||
|
// if (ch in CHARSETS)
|
||||||
|
// {
|
||||||
|
// cs = CHARSETS[ch];
|
||||||
|
// if (ch === '/')
|
||||||
|
// { // ISOLatin is actually /A
|
||||||
|
// this.skipNextChar();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// cs = DEFAULT_CHARSET;
|
||||||
|
// }
|
||||||
|
// this._terminal.setgCharset(this._terminal.gcharset, cs);
|
||||||
|
// this._terminal.gcharset = null;
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
break;
|
||||||
|
case ParserState.Osc:
|
||||||
|
// OSC Ps ; Pt ST
|
||||||
|
// OSC Ps ; Pt BEL
|
||||||
|
// Set Text Parameters.
|
||||||
|
if (ch == C0.ESC || ch == C0.BEL)
|
||||||
|
{
|
||||||
|
if (ch == C0.ESC)
|
||||||
|
{
|
||||||
|
_position++;
|
||||||
|
}
|
||||||
|
_params.Add(_currentParam);
|
||||||
|
switch ((int)_params[0])
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
if (_params.Count >= 1 && _params[1] != null)
|
||||||
|
{
|
||||||
|
Emit(new TerminalCode(TerminalCodeType.SetTitle, (string)_params[1]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// set X property
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
// change dynamic colors
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
case 15:
|
||||||
|
case 16:
|
||||||
|
case 17:
|
||||||
|
case 18:
|
||||||
|
case 19:
|
||||||
|
// change dynamic ui colors
|
||||||
|
break;
|
||||||
|
case 46:
|
||||||
|
// change log file
|
||||||
|
break;
|
||||||
|
case 50:
|
||||||
|
// dynamic font
|
||||||
|
break;
|
||||||
|
case 51:
|
||||||
|
// emacs shell
|
||||||
|
break;
|
||||||
|
case 52:
|
||||||
|
// manipulate selection data
|
||||||
|
break;
|
||||||
|
case 104:
|
||||||
|
case 105:
|
||||||
|
case 110:
|
||||||
|
case 111:
|
||||||
|
case 112:
|
||||||
|
case 113:
|
||||||
|
case 114:
|
||||||
|
case 115:
|
||||||
|
case 116:
|
||||||
|
case 117:
|
||||||
|
case 118:
|
||||||
|
// reset colors
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_params.Clear();
|
||||||
|
_currentParam = 0;
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_params.Count == 0)
|
||||||
|
{
|
||||||
|
if (ch >= '0' && ch <= '9')
|
||||||
|
{
|
||||||
|
_currentParam = ((int)_currentParam * 10) + ch - 48;
|
||||||
|
}
|
||||||
|
else if (ch == ';')
|
||||||
|
{
|
||||||
|
_params.Add(_currentParam);
|
||||||
|
_currentParam = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentParam = (string)_currentParam + ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParserState.CsiParam:
|
||||||
|
if (!CsiParamStateHandler(ch))
|
||||||
|
{
|
||||||
|
FinaliseParam();
|
||||||
|
_state = ParserState.Csi;
|
||||||
|
goto case ParserState.Csi;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParserState.Csi:
|
||||||
|
if (!CsiStateHandler(ch))
|
||||||
|
{
|
||||||
|
//throw new Exception($"Unknown CSI code: {ch}"); // conpty is sending 'X' ?!
|
||||||
|
}
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
_prefix = "";
|
||||||
|
_postfix = "";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserState.Dcs:
|
||||||
|
if (ch == C0.ESC || ch == C0.BEL)
|
||||||
|
{
|
||||||
|
if (ch == C0.ESC)
|
||||||
|
{
|
||||||
|
// _position++;
|
||||||
|
}
|
||||||
|
switch (_prefix)
|
||||||
|
{
|
||||||
|
// User-Defined Keys (DECUDK)
|
||||||
|
case "":
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Request Status String (DECRQSS).
|
||||||
|
// test: echo -e '\eP$q"p\e\\'
|
||||||
|
case "$q":
|
||||||
|
{
|
||||||
|
string pt = (string)_currentParam;
|
||||||
|
bool valid = false;
|
||||||
|
switch (pt)
|
||||||
|
{
|
||||||
|
// DECSCA
|
||||||
|
case "\"q":
|
||||||
|
pt = "0\"q";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// DECSCL
|
||||||
|
case "\"p":
|
||||||
|
pt = "61\"p";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// DECSTBM
|
||||||
|
case "r":
|
||||||
|
// pt = ""
|
||||||
|
// + (this._terminal.scrollTop + 1)
|
||||||
|
// + ";"
|
||||||
|
// + (this._terminal.scrollBottom + 1)
|
||||||
|
// + "r";
|
||||||
|
break;
|
||||||
|
|
||||||
|
// SGR
|
||||||
|
case "m":
|
||||||
|
pt = "0m";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unknown DCS Pt: {pt}.");
|
||||||
|
pt = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Termcap/Terminfo Data (xterm, experimental).
|
||||||
|
case "+p":
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Request Termcap/Terminfo String (xterm, experimental)
|
||||||
|
// Regular xterm does not even respond to this sequence.
|
||||||
|
// This can cause a small glitch in vim.
|
||||||
|
// test: echo -ne '\eP+q6b64\e\\'
|
||||||
|
case "+q":
|
||||||
|
{
|
||||||
|
string pt = (string)_currentParam;
|
||||||
|
bool valid = false;
|
||||||
|
// this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unknown DCS prefix: {_prefix}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentParam = 0;
|
||||||
|
_prefix = "";
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
}
|
||||||
|
else if (_currentParam == null)
|
||||||
|
{
|
||||||
|
if (_prefix.Length == 0 && ch != '$' && ch != '+')
|
||||||
|
{
|
||||||
|
_currentParam = ch;
|
||||||
|
}
|
||||||
|
else if (_prefix.Length == 2)
|
||||||
|
{
|
||||||
|
_currentParam = ch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_prefix += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentParam = (string)_currentParam + ch;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ParserState.Ignore:
|
||||||
|
// For PM and APC.
|
||||||
|
if (ch == C0.ESC || ch == C0.BEL)
|
||||||
|
{
|
||||||
|
if (ch == C0.ESC)
|
||||||
|
{
|
||||||
|
_position++;
|
||||||
|
}
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FinialiseEmit();
|
||||||
|
return _terminalCodes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NormalStateHandler(char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case C0.BEL:
|
||||||
|
Emit(TerminalCodeType.Bell);
|
||||||
|
return true;
|
||||||
|
case C0.LF:
|
||||||
|
case C0.VT:
|
||||||
|
case C0.FF:
|
||||||
|
Emit(TerminalCodeType.LineFeed);
|
||||||
|
return true;
|
||||||
|
case C0.CR:
|
||||||
|
Emit(TerminalCodeType.CarriageReturn);
|
||||||
|
return true;
|
||||||
|
case C0.BS:
|
||||||
|
Emit(TerminalCodeType.Backspace);
|
||||||
|
return true;
|
||||||
|
case C0.HT:
|
||||||
|
Emit(TerminalCodeType.Tab);
|
||||||
|
return true;
|
||||||
|
case C0.SO:
|
||||||
|
Emit(TerminalCodeType.ShiftOut);
|
||||||
|
return true;
|
||||||
|
case C0.SI:
|
||||||
|
Emit(TerminalCodeType.ShiftIn);
|
||||||
|
return true;
|
||||||
|
case C0.ESC:
|
||||||
|
_state = ParserState.Escaped;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool EscapedStateHandler(char ch)
|
||||||
|
{
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '[':
|
||||||
|
// ESC [ Control Sequence Introducer (CSI is 0x9b)
|
||||||
|
_params.Clear();
|
||||||
|
_currentParam = 0;
|
||||||
|
_state = ParserState.CsiParam;
|
||||||
|
return true;
|
||||||
|
case ']':
|
||||||
|
// ESC ] Operating System Command (OSC is 0x9d)
|
||||||
|
_params.Clear();
|
||||||
|
_currentParam = 0;
|
||||||
|
_state = ParserState.Osc;
|
||||||
|
return true;
|
||||||
|
case 'P':
|
||||||
|
// ESC P Device Control String (DCS is 0x90)
|
||||||
|
_params.Clear();
|
||||||
|
_currentParam = 0;
|
||||||
|
_state = ParserState.Dcs;
|
||||||
|
return true;
|
||||||
|
case '_':
|
||||||
|
// ESC _ Application Program Command ( APC is 0x9f).
|
||||||
|
_state = ParserState.Ignore;
|
||||||
|
return true;
|
||||||
|
case '^':
|
||||||
|
// ESC ^ Privacy Message ( PM is 0x9e).
|
||||||
|
_state = ParserState.Ignore;
|
||||||
|
return true;
|
||||||
|
case 'c':
|
||||||
|
// ESC c Full Reset (RIS).
|
||||||
|
Emit(TerminalCodeType.Reset);
|
||||||
|
return true;
|
||||||
|
case 'E':
|
||||||
|
// ESC E Next Line ( NEL is 0x85).
|
||||||
|
// terminal.x = 0;
|
||||||
|
// termina.index();
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
return true;
|
||||||
|
case 'D':
|
||||||
|
// ESC D Index ( IND is 0x84).
|
||||||
|
// terminal.index();
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
return true;
|
||||||
|
case 'M':
|
||||||
|
// ESC M Reverse Index ( RI is 0x8d).
|
||||||
|
// terminal.reverseIndex();
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
return true;
|
||||||
|
case '%':
|
||||||
|
// ESC % Select default/utf-8 character set.
|
||||||
|
// @ = default, G = utf-8
|
||||||
|
// terminal.setgLevel(0);
|
||||||
|
// terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
// parser.skipNextChar();
|
||||||
|
return true;
|
||||||
|
case C0.CAN:
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CsiStateHandler(char ch)
|
||||||
|
{
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case 'A':
|
||||||
|
Emit(new TerminalCode(TerminalCodeType.CursorUp, (int)_params[0], 0));
|
||||||
|
return true;
|
||||||
|
case 'G':
|
||||||
|
Emit(new TerminalCode(TerminalCodeType.CursorCharAbsolute, 0, (int)_params[0] - 1));
|
||||||
|
return true;
|
||||||
|
case 'H':
|
||||||
|
var line = _params.Count > 1 ? (int)_params[1] - 1 : 0; // make conpty happy
|
||||||
|
Emit(new TerminalCode(TerminalCodeType.CursorPosition, line, (int)_params[0] - 1));
|
||||||
|
return true;
|
||||||
|
case 'K':
|
||||||
|
Emit(TerminalCodeType.EraseInLine);
|
||||||
|
return true;
|
||||||
|
case 'J':
|
||||||
|
Emit(TerminalCodeType.EraseInDisplay);
|
||||||
|
return true;
|
||||||
|
case 'h':
|
||||||
|
Emit(TerminalCodeType.SetMode);
|
||||||
|
return true;
|
||||||
|
case 'l':
|
||||||
|
Emit(TerminalCodeType.ResetMode);
|
||||||
|
return true;
|
||||||
|
case 'n':
|
||||||
|
return true;
|
||||||
|
case 'm':
|
||||||
|
Emit(new TerminalCode(TerminalCodeType.CharAttributes, GetCharAttributes()));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CsiParamStateHandler(char ch)
|
||||||
|
{
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case '?':
|
||||||
|
case '>':
|
||||||
|
case '!':
|
||||||
|
case '$':
|
||||||
|
case '"':
|
||||||
|
case ' ':
|
||||||
|
case '\'':
|
||||||
|
_prefix = ch.ToString();
|
||||||
|
return true;
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
_currentParam = (int)_currentParam * 10 + (ch - '0');
|
||||||
|
return true;
|
||||||
|
case ';':
|
||||||
|
FinaliseParam();
|
||||||
|
return true;
|
||||||
|
case C0.CAN:
|
||||||
|
_state = ParserState.Normal;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinaliseParam()
|
||||||
|
{
|
||||||
|
_params.Add(_currentParam);
|
||||||
|
_currentParam = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharAttributes GetCharAttributes()
|
||||||
|
{
|
||||||
|
var attributes = new CharAttributes();
|
||||||
|
for (int i = 0; i < _params.Count; i++)
|
||||||
|
{
|
||||||
|
int p = (int)_params[i];
|
||||||
|
if (p >= 30 && p <= 37)
|
||||||
|
{
|
||||||
|
// fg color 8
|
||||||
|
attributes.ForegroundColor = p - 30;
|
||||||
|
}
|
||||||
|
else if (p >= 40 && p <= 47)
|
||||||
|
{
|
||||||
|
// bg color 8
|
||||||
|
attributes.BackgroundColor = p - 40;
|
||||||
|
}
|
||||||
|
else if (p >= 90 && p <= 97)
|
||||||
|
{
|
||||||
|
// fg color 16
|
||||||
|
p += 8;
|
||||||
|
attributes.ForegroundColor = p - 90;
|
||||||
|
}
|
||||||
|
else if (p == 0)
|
||||||
|
{
|
||||||
|
attributes.Flags = 0;
|
||||||
|
attributes.ForegroundColor = 0;
|
||||||
|
attributes.BackgroundColor = 0;
|
||||||
|
}
|
||||||
|
else if (p == 1)
|
||||||
|
{
|
||||||
|
attributes.Flags |= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Emit(TerminalCodeType type)
|
||||||
|
{
|
||||||
|
Emit(new TerminalCode(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Emit(char c)
|
||||||
|
{
|
||||||
|
_currentTextEmit.Append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Emit(TerminalCode code)
|
||||||
|
{
|
||||||
|
FinialiseEmit();
|
||||||
|
_terminalCodes.Add(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinialiseEmit()
|
||||||
|
{
|
||||||
|
if (_currentTextEmit.Length != 0)
|
||||||
|
{
|
||||||
|
string text = _currentTextEmit.ToString();
|
||||||
|
_currentTextEmit.Clear();
|
||||||
|
_terminalCodes.Add(new TerminalCode(TerminalCodeType.Text, text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ParserState
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Escaped,
|
||||||
|
CsiParam,
|
||||||
|
Csi,
|
||||||
|
Osc,
|
||||||
|
Charset,
|
||||||
|
Dcs,
|
||||||
|
Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Ansi
|
||||||
|
{
|
||||||
|
public class C0
|
||||||
|
{
|
||||||
|
/// <summary>Null (Caret = ^@, C = \0)</summary>
|
||||||
|
public const char NUL = (char)0x00;
|
||||||
|
/// <summary>Start of Heading (Caret = ^A)</summary>
|
||||||
|
public const char SOH = (char)0x01;
|
||||||
|
/// <summary>Start of Text (Caret = ^B)</summary>
|
||||||
|
public const char STX = (char)0x02;
|
||||||
|
/// <summary>End of Text (Caret = ^C)</summary>
|
||||||
|
public const char ETX = (char)0x03;
|
||||||
|
/// <summary>End of Transmission (Caret = ^D)</summary>
|
||||||
|
public const char EOT = (char)0x04;
|
||||||
|
/// <summary>Enquiry (Caret = ^E)</summary>
|
||||||
|
public const char ENQ = (char)0x05;
|
||||||
|
/// <summary>Acknowledge (Caret = ^F)</summary>
|
||||||
|
public const char ACK = (char)0x06;
|
||||||
|
/// <summary>Bell (Caret = ^G, C = \a)</summary>
|
||||||
|
public const char BEL = (char)0x07;
|
||||||
|
/// <summary>Backspace (Caret = ^H, C = \b)</summary>
|
||||||
|
public const char BS = (char)0x08;
|
||||||
|
/// <summary>Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t)</summary>
|
||||||
|
public const char HT = (char)0x09;
|
||||||
|
/// <summary>Line Feed (Caret = ^J, C = \n)</summary>
|
||||||
|
public const char LF = (char)0x0a;
|
||||||
|
/// <summary>Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v)</summary>
|
||||||
|
public const char VT = (char)0x0b;
|
||||||
|
/// <summary>Form Feed (Caret = ^L, C = \f)</summary>
|
||||||
|
public const char FF = (char)0x0c;
|
||||||
|
/// <summary>Carriage Return (Caret = ^M, C = \r)</summary>
|
||||||
|
public const char CR = (char)0x0d;
|
||||||
|
/// <summary>Shift Out (Caret = ^N)</summary>
|
||||||
|
public const char SO = (char)0x0e;
|
||||||
|
/// <summary>Shift In (Caret = ^O)</summary>
|
||||||
|
public const char SI = (char)0x0f;
|
||||||
|
/// <summary>Data Link Escape (Caret = ^P)</summary>
|
||||||
|
public const char DLE = (char)0x10;
|
||||||
|
/// <summary>Device Control One (XON) (Caret = ^Q)</summary>
|
||||||
|
public const char DC1 = (char)0x11;
|
||||||
|
/// <summary>Device Control Two (Caret = ^R)</summary>
|
||||||
|
public const char DC2 = (char)0x12;
|
||||||
|
/// <summary>Device Control Three (XOFF) (Caret = ^S)</summary>
|
||||||
|
public const char DC3 = (char)0x13;
|
||||||
|
/// <summary>Device Control Four (Caret = ^T)</summary>
|
||||||
|
public const char DC4 = (char)0x14;
|
||||||
|
/// <summary>Negative Acknowledge (Caret = ^U)</summary>
|
||||||
|
public const char NAK = (char)0x15;
|
||||||
|
/// <summary>Synchronous Idle (Caret = ^V)</summary>
|
||||||
|
public const char SYN = (char)0x16;
|
||||||
|
/// <summary>End of Transmission Block (Caret = ^W)</summary>
|
||||||
|
public const char ETB = (char)0x17;
|
||||||
|
/// <summary>Cancel (Caret = ^X)</summary>
|
||||||
|
public const char CAN = (char)0x18;
|
||||||
|
/// <summary>End of Medium (Caret = ^Y)</summary>
|
||||||
|
public const char EM = (char)0x19;
|
||||||
|
/// <summary>Substitute (Caret = ^Z)</summary>
|
||||||
|
public const char SUB = (char)0x1a;
|
||||||
|
/// <summary>Escape (Caret = ^[, C = \e)</summary>
|
||||||
|
public const char ESC = (char)0x1b;
|
||||||
|
/// <summary>File Separator (Caret = ^\)</summary>
|
||||||
|
public const char FS = (char)0x1c;
|
||||||
|
/// <summary>Group Separator (Caret = ^])</summary>
|
||||||
|
public const char GS = (char)0x1d;
|
||||||
|
/// <summary>Record Separator (Caret = ^^)</summary>
|
||||||
|
public const char RS = (char)0x1e;
|
||||||
|
/// <summary>Unit Separator (Caret = ^_)</summary>
|
||||||
|
public const char US = (char)0x1f;
|
||||||
|
/// <summary>Space</summary>
|
||||||
|
public const char SP = (char)0x20;
|
||||||
|
/// <summary>Delete (Caret = ^?)</summary>
|
||||||
|
public const char DEL = (char)0x7f;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Ansi
|
||||||
|
{
|
||||||
|
public struct CharAttributes : IEquatable<CharAttributes>
|
||||||
|
{
|
||||||
|
public int Flags { get; set; }
|
||||||
|
public int BackgroundColor { get; set; }
|
||||||
|
public int ForegroundColor { get; set; }
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CharAttributes other)
|
||||||
|
{
|
||||||
|
return base.Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(CharAttributes a, CharAttributes b)
|
||||||
|
{
|
||||||
|
return a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(CharAttributes a, CharAttributes b)
|
||||||
|
{
|
||||||
|
return !a.Equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Ansi
|
||||||
|
{
|
||||||
|
internal struct TerminalCode
|
||||||
|
{
|
||||||
|
public TerminalCodeType Type { get; }
|
||||||
|
public int Line { get; }
|
||||||
|
public int Column { get; }
|
||||||
|
public string Text { get; }
|
||||||
|
public CharAttributes CharAttributes { get; }
|
||||||
|
|
||||||
|
public TerminalCode(TerminalCodeType type) : this()
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalCode(TerminalCodeType type, string text) : this(type)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalCode(TerminalCodeType type, int line, int column) : this(type)
|
||||||
|
{
|
||||||
|
Line = line;
|
||||||
|
Column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalCode(TerminalCodeType type, CharAttributes charAttributes) : this(type)
|
||||||
|
{
|
||||||
|
CharAttributes = charAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Type == TerminalCodeType.Text)
|
||||||
|
{
|
||||||
|
return Text;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Type.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Ansi
|
||||||
|
{
|
||||||
|
public enum TerminalCodeType
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Bell,
|
||||||
|
LineFeed,
|
||||||
|
CarriageReturn,
|
||||||
|
Backspace,
|
||||||
|
Tab,
|
||||||
|
ShiftOut,
|
||||||
|
ShiftIn,
|
||||||
|
SaveCursor,
|
||||||
|
Reset,
|
||||||
|
SetTitle,
|
||||||
|
RestoreCursor,
|
||||||
|
TabSet,
|
||||||
|
|
||||||
|
CursorPosition,
|
||||||
|
CursorCharAbsolute,
|
||||||
|
CursorUp,
|
||||||
|
CursorDown,
|
||||||
|
CursorForward,
|
||||||
|
CursorBackward,
|
||||||
|
RestoreCursorPosition,
|
||||||
|
EraseInDisplay,
|
||||||
|
EraseInLine,
|
||||||
|
SetGraphicsMode,
|
||||||
|
SetMode,
|
||||||
|
CharAttributes,
|
||||||
|
ResetMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
public enum SelectionMode
|
||||||
|
{
|
||||||
|
Stream,
|
||||||
|
Block
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
internal static class SpecialColorIds
|
||||||
|
{
|
||||||
|
public const int Cursor = -1;
|
||||||
|
public const int Selection = -2;
|
||||||
|
public const int Historic = -3;
|
||||||
|
public const int Futuristic = -4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
using FluentTerminal.App.Views.NativeFrontend.Ansi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
internal class TerminalBuffer
|
||||||
|
{
|
||||||
|
private const int MaxHistorySize = 1024;
|
||||||
|
|
||||||
|
private TerminalBufferChar[] _buffer;
|
||||||
|
private TerminalSize _size;
|
||||||
|
private readonly List<TerminalBufferLine> _history = new List<TerminalBufferLine>();
|
||||||
|
|
||||||
|
public bool ShowCursor { get; set; }
|
||||||
|
public int CursorX { get; set; }
|
||||||
|
public int CursorY { get; set; }
|
||||||
|
public CharAttributes CurrentCharAttributes { get; set; }
|
||||||
|
public TerminalSelection Selection { get; set; }
|
||||||
|
|
||||||
|
public int WindowTop { get; set; }
|
||||||
|
public int WindowBottom
|
||||||
|
{
|
||||||
|
get => WindowTop + _size.Rows - 1;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
WindowTop = value - _size.Rows + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalBuffer(TerminalSize size)
|
||||||
|
{
|
||||||
|
Initialise(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialise(TerminalSize size)
|
||||||
|
{
|
||||||
|
_buffer = new TerminalBufferChar[size.Rows * size.Columns];
|
||||||
|
_size = size;
|
||||||
|
CursorX = 0;
|
||||||
|
CursorY = 0;
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalSize Size
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != _size)
|
||||||
|
{
|
||||||
|
var srcSize = _size;
|
||||||
|
var dstSize = value;
|
||||||
|
|
||||||
|
var srcBuffer = _buffer;
|
||||||
|
var dstBuffer = new TerminalBufferChar[dstSize.Rows * dstSize.Columns];
|
||||||
|
|
||||||
|
int srcLeft = 0;
|
||||||
|
int srcRight = Math.Min(srcSize.Columns, dstSize.Columns) - 1;
|
||||||
|
int srcTop = Math.Max(0, CursorY - dstSize.Rows + 1);
|
||||||
|
int srcBottom = srcTop + Math.Min(srcSize.Rows, dstSize.Rows) - 1;
|
||||||
|
int dstLeft = 0;
|
||||||
|
int dstTop = 0;
|
||||||
|
|
||||||
|
CopyBufferToBuffer(srcBuffer, srcSize, srcLeft, srcTop, srcRight, srcBottom,
|
||||||
|
dstBuffer, dstSize, dstLeft, dstTop);
|
||||||
|
|
||||||
|
_buffer = dstBuffer;
|
||||||
|
_size = dstSize;
|
||||||
|
|
||||||
|
CursorY = Math.Min(CursorY, _size.Rows - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
ClearBlock(0, 0, _size.Columns - 1, _size.Rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearBlock(int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
left = Math.Max(0, left);
|
||||||
|
top = Math.Max(0, top);
|
||||||
|
right = Math.Min(right, _size.Columns - 1);
|
||||||
|
bottom = Math.Min(bottom, _size.Rows - 1);
|
||||||
|
|
||||||
|
for (int y = top; y <= bottom; y++)
|
||||||
|
{
|
||||||
|
for (int x = left; x <= right; x++)
|
||||||
|
{
|
||||||
|
int index = GetBufferIndex(x, y);
|
||||||
|
_buffer[index] = new TerminalBufferChar(' ', default(CharAttributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollToCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Type(char c)
|
||||||
|
{
|
||||||
|
if (IsInBuffer(CursorX, CursorY))
|
||||||
|
{
|
||||||
|
int index = GetBufferIndex(CursorX, CursorY);
|
||||||
|
_buffer[index] = new TerminalBufferChar(c, CurrentCharAttributes);
|
||||||
|
CursorX++;
|
||||||
|
}
|
||||||
|
ScrollToCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Type(string text)
|
||||||
|
{
|
||||||
|
foreach (char c in text)
|
||||||
|
{
|
||||||
|
if (IsInBuffer(CursorX, CursorY))
|
||||||
|
{
|
||||||
|
int index = GetBufferIndex(CursorX, CursorY);
|
||||||
|
_buffer[index] = new TerminalBufferChar(c, CurrentCharAttributes);
|
||||||
|
CursorX++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollToCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShiftUp()
|
||||||
|
{
|
||||||
|
var topLine = GetBufferLine(0);
|
||||||
|
AddHistory(topLine);
|
||||||
|
|
||||||
|
for (int y = 0; y < _size.Rows - 1; y++)
|
||||||
|
{
|
||||||
|
Array.Copy(_buffer, (y + 1) * _size.Columns, _buffer, y * _size.Columns, _size.Columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetText(int y)
|
||||||
|
{
|
||||||
|
return GetText(0, y, _size.Columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetText(int left, int top, int right, int bottom)
|
||||||
|
{
|
||||||
|
int width = right - left + 1;
|
||||||
|
int height = bottom - top + 1;
|
||||||
|
string[] result = new string[height];
|
||||||
|
for (int i = 0; i < height; i++)
|
||||||
|
{
|
||||||
|
result[i] = GetText(left, top + i, width);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetText(int x, int y, int length)
|
||||||
|
{
|
||||||
|
if (x + length > _size.Columns)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Range overflows line length.", nameof(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
(var buffer, int startIndex, int endIndex) = GetLineBufferRange(y);
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxLength = endIndex - startIndex;
|
||||||
|
string line = GetTextAtIndex(buffer, startIndex + x, Math.Min(length, maxLength));
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTextAtIndex(IList<TerminalBufferChar> buffer, int index, int length)
|
||||||
|
{
|
||||||
|
var chars = new char[length];
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
chars[i] = buffer[index + i].Char;
|
||||||
|
}
|
||||||
|
string line = new string(chars);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetSelectionText()
|
||||||
|
{
|
||||||
|
var selection = Selection;
|
||||||
|
if (selection.Mode == SelectionMode.Stream)
|
||||||
|
{
|
||||||
|
(var start, var end) = selection.GetMinMax();
|
||||||
|
var result = new List<string>();
|
||||||
|
for (int y = start.Row; y <= end.Row; y++)
|
||||||
|
{
|
||||||
|
int x0 = 0;
|
||||||
|
int x1 = _size.Columns - 1;
|
||||||
|
if (y == start.Row)
|
||||||
|
{
|
||||||
|
x0 = start.Column;
|
||||||
|
}
|
||||||
|
if (y == end.Row)
|
||||||
|
{
|
||||||
|
x1 = end.Column;
|
||||||
|
}
|
||||||
|
result.Add(GetText(x0, y, x1 - x0 + 1));
|
||||||
|
}
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
else if (selection.Mode == SelectionMode.Block)
|
||||||
|
{
|
||||||
|
int left = Math.Min(selection.Start.Column, selection.End.Column);
|
||||||
|
int right = Math.Max(selection.Start.Column, selection.End.Column);
|
||||||
|
int top = Math.Min(selection.Start.Row, selection.End.Row);
|
||||||
|
int bottom = Math.Max(selection.Start.Row, selection.End.Row);
|
||||||
|
return GetText(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unknown selection mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopySelection()
|
||||||
|
{
|
||||||
|
//string[] selectionText = GetSelectionText()
|
||||||
|
// .Select(x => x.TrimEnd())
|
||||||
|
// .ToArray();
|
||||||
|
//string text = String.Join(Environment.NewLine, selectionText);
|
||||||
|
//var dataObject = new DataObject();
|
||||||
|
//dataObject.SetText(text);
|
||||||
|
//dataObject.SetData("MSDEVColumnSelect", true);
|
||||||
|
//Clipboard.SetDataObject(dataObject, copy: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalBufferLine GetBufferLine(int y)
|
||||||
|
{
|
||||||
|
int startIndex = GetBufferIndex(0, y);
|
||||||
|
var bufferLine = new TerminalBufferLine(_buffer, startIndex, _size.Columns);
|
||||||
|
return bufferLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalTagArray GetFormattedLine(int y)
|
||||||
|
{
|
||||||
|
var tags = ImmutableArray.CreateBuilder<TerminalTag>(initialCapacity: _size.Columns);
|
||||||
|
|
||||||
|
(var buffer, int startIndex, int endIndex) = GetLineBufferRange(y);
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
AddFillerTags(tags, 0, y, _size.Columns);
|
||||||
|
return new TerminalTagArray(tags.ToImmutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group sequentially by attribute
|
||||||
|
int lineLength = endIndex - startIndex;
|
||||||
|
var currentTagStartIndex = startIndex;
|
||||||
|
var currentTagAttribute = GetAttributesAt(buffer[startIndex], 0, y);
|
||||||
|
for (int i = startIndex + 1; i < endIndex; i++)
|
||||||
|
{
|
||||||
|
int x = i - startIndex;
|
||||||
|
var c = buffer[i];
|
||||||
|
var attr = GetAttributesAt(c, x, y);
|
||||||
|
if (!CanContinueTag(currentTagAttribute, attr, c.Char))
|
||||||
|
{
|
||||||
|
string tagText = GetTextAtIndex(buffer, currentTagStartIndex, i - currentTagStartIndex);
|
||||||
|
tags.Add(new TerminalTag(tagText, currentTagAttribute));
|
||||||
|
|
||||||
|
currentTagStartIndex = i;
|
||||||
|
currentTagAttribute = attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last tag
|
||||||
|
{
|
||||||
|
string tagText = GetTextAtIndex(buffer, currentTagStartIndex, endIndex - currentTagStartIndex);
|
||||||
|
tags.Add(new TerminalTag(tagText, currentTagAttribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filler tags
|
||||||
|
if (lineLength < _size.Columns)
|
||||||
|
{
|
||||||
|
AddFillerTags(tags, lineLength, y, _size.Columns - lineLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TerminalTagArray(tags.ToImmutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFillerTags(ImmutableArray<TerminalTag>.Builder builder, int x, int y, int length)
|
||||||
|
{
|
||||||
|
int tagLength = 0;
|
||||||
|
var lastAttr = GetAttributesAt(default(TerminalBufferChar), x, y);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
tagLength++;
|
||||||
|
var attr = GetAttributesAt(default(TerminalBufferChar), x + i, y);
|
||||||
|
if (attr != lastAttr)
|
||||||
|
{
|
||||||
|
string text = new string(' ', tagLength);
|
||||||
|
builder.Add(new TerminalTag(text, lastAttr));
|
||||||
|
lastAttr = attr;
|
||||||
|
tagLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last tag
|
||||||
|
{
|
||||||
|
string text = new string(' ', tagLength);
|
||||||
|
builder.Add(new TerminalTag(text, lastAttr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharAttributes GetAttributesAt(TerminalBufferChar bufferChar, int x, int y)
|
||||||
|
{
|
||||||
|
CharAttributes attr = bufferChar.Attributes;
|
||||||
|
if (ShowCursor && x == CursorX && y == CursorY)
|
||||||
|
{
|
||||||
|
attr.BackgroundColor = SpecialColorIds.Cursor;
|
||||||
|
}
|
||||||
|
else if (IsPointInSelection(x, y))
|
||||||
|
{
|
||||||
|
attr.BackgroundColor = SpecialColorIds.Selection;
|
||||||
|
}
|
||||||
|
else if (y < 0)
|
||||||
|
{
|
||||||
|
attr.BackgroundColor = SpecialColorIds.Historic;
|
||||||
|
}
|
||||||
|
else if (y >= _size.Rows)
|
||||||
|
{
|
||||||
|
attr.BackgroundColor = SpecialColorIds.Futuristic;
|
||||||
|
}
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (IList<TerminalBufferChar> buffer, int startIndex, int endIndex) GetLineBufferRange(int y)
|
||||||
|
{
|
||||||
|
if (y < 0)
|
||||||
|
{
|
||||||
|
int historyIndex = _history.Count + y;
|
||||||
|
if (historyIndex < 0)
|
||||||
|
{
|
||||||
|
return (null, 0, 0);
|
||||||
|
}
|
||||||
|
var historyLine = _history[historyIndex];
|
||||||
|
var buffer = historyLine.Buffer;
|
||||||
|
return (buffer, 0, Math.Min(buffer.Length, _size.Columns));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (y >= _size.Rows)
|
||||||
|
{
|
||||||
|
return (null, 0, 0);
|
||||||
|
}
|
||||||
|
int startIndex = GetBufferIndex(0, y);
|
||||||
|
return (_buffer, startIndex, startIndex + _size.Columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPointInSelection(int x, int y)
|
||||||
|
{
|
||||||
|
var selection = Selection;
|
||||||
|
if (selection == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (selection.Mode == SelectionMode.Stream)
|
||||||
|
{
|
||||||
|
(var start, var end) = selection.GetMinMax();
|
||||||
|
if (y == start.Row)
|
||||||
|
{
|
||||||
|
if (x < start.Column)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (y == end.Row)
|
||||||
|
{
|
||||||
|
if (x > end.Column)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (y < start.Row || y > end.Row)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (selection.Mode == SelectionMode.Block)
|
||||||
|
{
|
||||||
|
int left = Math.Min(selection.Start.Column, selection.End.Column);
|
||||||
|
int right = Math.Max(selection.Start.Column, selection.End.Column);
|
||||||
|
int top = Math.Min(selection.Start.Row, selection.End.Row);
|
||||||
|
int bottom = Math.Max(selection.Start.Row, selection.End.Row);
|
||||||
|
return (x >= left && x <= right &&
|
||||||
|
y >= top && y <= bottom);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unknown selection mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanContinueTag(CharAttributes previous, CharAttributes next, char nextC)
|
||||||
|
{
|
||||||
|
if (nextC == ' ')
|
||||||
|
{
|
||||||
|
return previous.BackgroundColor == next.BackgroundColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return previous == next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInBuffer(int x, int y)
|
||||||
|
{
|
||||||
|
return (x >= 0 && x < _size.Columns && y >= 0 && y < _size.Rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetBufferIndex(int x, int y)
|
||||||
|
{
|
||||||
|
return x + (y * _size.Columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPoint GetBufferPoint(int index)
|
||||||
|
{
|
||||||
|
return new TerminalPoint(index % _size.Columns, index % _size.Columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddHistory(TerminalBufferLine line)
|
||||||
|
{
|
||||||
|
if (_history.Count >= MaxHistorySize)
|
||||||
|
{
|
||||||
|
_history.RemoveAt(0);
|
||||||
|
}
|
||||||
|
_history.Add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CurrentHistoryLength => _history.Count;
|
||||||
|
|
||||||
|
public void Scroll(int scroll)
|
||||||
|
{
|
||||||
|
int top = WindowTop + scroll;
|
||||||
|
top = Math.Max(top, -_history.Count);
|
||||||
|
// top = Math.Min(top, _size.Rows - 1);
|
||||||
|
top = Math.Min(top, CursorY);
|
||||||
|
WindowTop = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScrollToCursor()
|
||||||
|
{
|
||||||
|
int windowTop = WindowTop;
|
||||||
|
int windowBottom = WindowBottom;
|
||||||
|
if (CursorY < windowTop)
|
||||||
|
{
|
||||||
|
WindowTop = CursorY;
|
||||||
|
}
|
||||||
|
else if (CursorY > windowBottom)
|
||||||
|
{
|
||||||
|
WindowBottom = CursorY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyBufferToBuffer(TerminalBufferChar[] srcBuffer, TerminalSize srcSize, int srcLeft, int srcTop, int srcRight, int srcBottom,
|
||||||
|
TerminalBufferChar[] dstBuffer, TerminalSize dstSize, int dstLeft, int dstTop)
|
||||||
|
{
|
||||||
|
int cols = srcRight - srcLeft + 1;
|
||||||
|
int rows = srcBottom - srcTop + 1;
|
||||||
|
for (int y = 0; y < rows; y++)
|
||||||
|
{
|
||||||
|
int srcY = srcTop + y;
|
||||||
|
int dstY = dstTop + y;
|
||||||
|
int srcIndex = srcLeft + (srcY * srcSize.Columns);
|
||||||
|
int dstIndex = dstLeft + (dstY * dstSize.Columns);
|
||||||
|
Array.Copy(srcBuffer, srcIndex, dstBuffer, dstIndex, cols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using FluentTerminal.App.Views.NativeFrontend.Ansi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
public struct TerminalBufferChar
|
||||||
|
{
|
||||||
|
public char Char { get; }
|
||||||
|
public CharAttributes Attributes { get; }
|
||||||
|
|
||||||
|
public TerminalBufferChar(char c, CharAttributes attr)
|
||||||
|
{
|
||||||
|
Char = c;
|
||||||
|
Attributes = attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Char.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single immutable line from a terminal buffer.
|
||||||
|
/// </summary>
|
||||||
|
internal class TerminalBufferLine
|
||||||
|
{
|
||||||
|
public int Columns { get; }
|
||||||
|
public ImmutableArray<TerminalBufferChar> Buffer { get; }
|
||||||
|
|
||||||
|
public TerminalBufferLine(TerminalBufferChar[] buffer, int index, int length)
|
||||||
|
{
|
||||||
|
Buffer = buffer.Skip(index).Take(length).ToImmutableArray();
|
||||||
|
Columns = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return new string(Buffer.Select(x => x.Char).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{Column}, {Row}")]
|
||||||
|
public struct TerminalPoint : IEquatable<TerminalPoint>, IComparable<TerminalPoint>
|
||||||
|
{
|
||||||
|
public int Column { get; set; }
|
||||||
|
public int Row { get; set; }
|
||||||
|
|
||||||
|
public TerminalPoint(int col, int row)
|
||||||
|
{
|
||||||
|
Column = col;
|
||||||
|
Row = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TerminalPoint other)
|
||||||
|
{
|
||||||
|
return Column == other.Column &&
|
||||||
|
Row == other.Row;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(TerminalPoint other)
|
||||||
|
{
|
||||||
|
if (Row < other.Row) return -1;
|
||||||
|
if (Row > other.Row) return 1;
|
||||||
|
if (Column < other.Column) return -1;
|
||||||
|
if (Column > other.Column) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return !a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return a.CompareTo(b) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return a.CompareTo(b) == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >=(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return a.CompareTo(b) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <=(TerminalPoint a, TerminalPoint b)
|
||||||
|
{
|
||||||
|
return a.CompareTo(b) <= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
internal class TerminalSelection
|
||||||
|
{
|
||||||
|
public SelectionMode Mode { get; }
|
||||||
|
public TerminalPoint Start { get; }
|
||||||
|
public TerminalPoint End { get; }
|
||||||
|
public bool IsReversed { get; }
|
||||||
|
|
||||||
|
public TerminalSelection(SelectionMode mode, TerminalPoint start, TerminalPoint end)
|
||||||
|
{
|
||||||
|
Mode = mode;
|
||||||
|
Start = start;
|
||||||
|
End = end;
|
||||||
|
IsReversed = start > end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (TerminalPoint, TerminalPoint) GetMinMax()
|
||||||
|
{
|
||||||
|
return IsReversed ? (End, Start) : (Start, End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
public struct TerminalSize : IEquatable<TerminalSize>
|
||||||
|
{
|
||||||
|
public int Columns { get; }
|
||||||
|
public int Rows { get; }
|
||||||
|
|
||||||
|
public TerminalSize(int columns, int rows)
|
||||||
|
{
|
||||||
|
Columns = columns;
|
||||||
|
Rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TerminalSize other)
|
||||||
|
{
|
||||||
|
return base.Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Columns} x {Rows}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(TerminalSize a, TerminalSize b)
|
||||||
|
{
|
||||||
|
return a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(TerminalSize a, TerminalSize b)
|
||||||
|
{
|
||||||
|
return !a.Equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using FluentTerminal.App.Views.NativeFrontend.Ansi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
public struct TerminalTag : IEquatable<TerminalTag>
|
||||||
|
{
|
||||||
|
public string Text { get; }
|
||||||
|
public CharAttributes Attributes { get; }
|
||||||
|
|
||||||
|
public TerminalTag(string text, CharAttributes attributes)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return Equals((TerminalTag)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TerminalTag other)
|
||||||
|
{
|
||||||
|
return Text == other.Text &&
|
||||||
|
Attributes == other.Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalTag Substring(int index)
|
||||||
|
{
|
||||||
|
return new TerminalTag(Text.Substring(index), Attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalTag Substring(int index, int length)
|
||||||
|
{
|
||||||
|
return new TerminalTag(Text.Substring(index, length), Attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(TerminalTag a, TerminalTag b)
|
||||||
|
{
|
||||||
|
return a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(TerminalTag a, TerminalTag b)
|
||||||
|
{
|
||||||
|
return !a.Equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Terminal
|
||||||
|
{
|
||||||
|
public struct TerminalTagArray : IEquatable<TerminalTagArray>, IEnumerable<TerminalTag>
|
||||||
|
{
|
||||||
|
private readonly ImmutableArray<TerminalTag> _tags;
|
||||||
|
private readonly int _hash;
|
||||||
|
|
||||||
|
public int Length => _tags.IsDefaultOrEmpty ? 0 : _tags.Length;
|
||||||
|
|
||||||
|
public TerminalTagArray(ImmutableArray<TerminalTag> tags)
|
||||||
|
{
|
||||||
|
_tags = tags;
|
||||||
|
_hash = GetHashCode(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetHashCode(ImmutableArray<TerminalTag> tags)
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hash = 17;
|
||||||
|
for (int i = 0; i < tags.Length; i++)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + tags[i].GetHashCode();
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return Equals((ImmutableArray<TerminalTag>)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(TerminalTagArray other)
|
||||||
|
{
|
||||||
|
if (_hash != other._hash)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagsA = _tags;
|
||||||
|
var tagsB = other._tags;
|
||||||
|
if (tagsA != tagsB)
|
||||||
|
{
|
||||||
|
if (tagsA.IsDefaultOrEmpty || tagsB.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tagsA.Length != tagsB.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < tagsA.Length; i++)
|
||||||
|
{
|
||||||
|
if (tagsA[i] != tagsB[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return base.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<TerminalTag> GetEnumerator()
|
||||||
|
{
|
||||||
|
if (_tags.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<TerminalTag>().GetEnumerator();
|
||||||
|
}
|
||||||
|
return ((IEnumerable<TerminalTag>)_tags).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(TerminalTagArray a, TerminalTagArray b)
|
||||||
|
{
|
||||||
|
return a.Equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(TerminalTagArray a, TerminalTagArray b)
|
||||||
|
{
|
||||||
|
return !a.Equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using FluentTerminal.App.Utilities;
|
||||||
|
using FluentTerminal.App.Views.NativeFrontend.Terminal;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.UI;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend
|
||||||
|
{
|
||||||
|
internal class TerminalColorHelper
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, Brush> _brushDictionary = new Dictionary<int, Brush>();
|
||||||
|
private readonly Brush _defaultForegroundBrush = new SolidColorBrush(Color.FromArgb(255, 204, 204, 204));
|
||||||
|
|
||||||
|
public Brush GetForegroundBrush(int id)
|
||||||
|
{
|
||||||
|
Brush brush = GetBrush(id);
|
||||||
|
if (brush == null)
|
||||||
|
{
|
||||||
|
brush = _defaultForegroundBrush;
|
||||||
|
}
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush GetBackgroundBrush(int id)
|
||||||
|
{
|
||||||
|
return GetBrush(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush GetBrush(int id)
|
||||||
|
{
|
||||||
|
Brush result = null;
|
||||||
|
if (id != 0)
|
||||||
|
{
|
||||||
|
if (!_brushDictionary.TryGetValue(id, out result))
|
||||||
|
{
|
||||||
|
result = new SolidColorBrush(GetColour(id));
|
||||||
|
_brushDictionary.Add(id, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color GetColour(int id)
|
||||||
|
{
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case SpecialColorIds.Cursor:
|
||||||
|
return Color.FromArgb(255, 204, 204, 204);
|
||||||
|
case SpecialColorIds.Selection:
|
||||||
|
return Color.FromArgb(255, 203, 203, 203);
|
||||||
|
case SpecialColorIds.Historic:
|
||||||
|
case SpecialColorIds.Futuristic:
|
||||||
|
return Color.FromArgb(255, 24, 24, 24);
|
||||||
|
default:
|
||||||
|
return TangoColours[id % 16].FromString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors 0-15
|
||||||
|
private readonly static string[] TangoColours =
|
||||||
|
{
|
||||||
|
// dark:
|
||||||
|
"#2e3436",
|
||||||
|
"#cc0000",
|
||||||
|
"#0dbc79",
|
||||||
|
"#c4a000",
|
||||||
|
"#3465a4",
|
||||||
|
"#75507b",
|
||||||
|
"#06989a",
|
||||||
|
"#d3d7cf",
|
||||||
|
|
||||||
|
// bright:
|
||||||
|
"#555753",
|
||||||
|
"#ef2929",
|
||||||
|
"#23D18B",
|
||||||
|
"#fce94f",
|
||||||
|
"#729fcf",
|
||||||
|
"#ad7fa8",
|
||||||
|
"#34e2e2",
|
||||||
|
"#eeeeec"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="FluentTerminal.App.Views.NativeFrontend.TerminalControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:FluentTerminal.App.Views.NativeFrontend"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:xaml="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||||
|
d:DesignHeight="300"
|
||||||
|
d:DesignWidth="400"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<ScrollViewer x:Name="scroll" VerticalScrollBarVisibility="Auto">
|
||||||
|
<xaml:CanvasVirtualControl x:Name="canvas" IsTabStop="True" RegionsInvalidated="OnRegionsInvalidated"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,476 @@
|
|||||||
|
using FluentTerminal.App.Services;
|
||||||
|
using FluentTerminal.App.ViewModels;
|
||||||
|
using FluentTerminal.App.Views.NativeFrontend.Ansi;
|
||||||
|
using FluentTerminal.App.Views.NativeFrontend.Terminal;
|
||||||
|
using FluentTerminal.App.Views.NativeFrontend.Utility;
|
||||||
|
using FluentTerminal.Models;
|
||||||
|
using FluentTerminal.Models.Enums;
|
||||||
|
using Microsoft.Graphics.Canvas;
|
||||||
|
using Microsoft.Graphics.Canvas.Text;
|
||||||
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.System;
|
||||||
|
using Windows.UI;
|
||||||
|
using Windows.UI.Core;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Input;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend
|
||||||
|
{
|
||||||
|
public sealed partial class TerminalControl : UserControl, ITerminalView
|
||||||
|
{
|
||||||
|
private TerminalBuffer _buffer;
|
||||||
|
private double _characterHeight;
|
||||||
|
private double _characterWidth;
|
||||||
|
private TerminalColorHelper _colorHelper = new TerminalColorHelper();
|
||||||
|
private readonly TerminalOptions _terminalOptions;
|
||||||
|
private int _cols;
|
||||||
|
private CanvasTextFormat _format;
|
||||||
|
private Ansi.AnsiParser _parser;
|
||||||
|
private int _rows;
|
||||||
|
private int _gridSize = 150;
|
||||||
|
private CoreDispatcher _dispatcher;
|
||||||
|
|
||||||
|
public TerminalControl(ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
canvas.KeyUp += _canvas_KeyUp;
|
||||||
|
canvas.GotFocus += _canvas_GotFocus;
|
||||||
|
canvas.CharacterReceived += Canvas_CharacterReceived;
|
||||||
|
|
||||||
|
_terminalOptions = settingsService.GetTerminalOptions();
|
||||||
|
_dispatcher = Window.Current.CoreWindow.Dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
int regionsInvalidatedEventCount = 0;
|
||||||
|
int regionsInvalidatedCount = 0;
|
||||||
|
|
||||||
|
private void OnRegionsInvalidated(CanvasVirtualControl sender, CanvasRegionsInvalidatedEventArgs args)
|
||||||
|
{
|
||||||
|
++regionsInvalidatedEventCount;
|
||||||
|
|
||||||
|
var invalidatedRegions = args.InvalidatedRegions;
|
||||||
|
regionsInvalidatedCount += invalidatedRegions.Count();
|
||||||
|
|
||||||
|
foreach (var region in invalidatedRegions)
|
||||||
|
{
|
||||||
|
DrawRegion(sender, region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRegion(CanvasVirtualControl sender, Rect region)
|
||||||
|
{
|
||||||
|
var top = (int)(region.Top / _characterHeight);
|
||||||
|
var bottom = top + (int)(region.Height / _characterHeight);
|
||||||
|
|
||||||
|
using (var ds = sender.CreateDrawingSession(region))
|
||||||
|
{
|
||||||
|
var y = 0.0;
|
||||||
|
for (int i = top; i < bottom; i++)
|
||||||
|
{
|
||||||
|
var x = 0.0;
|
||||||
|
var line = _buffer.GetFormattedLine(i);
|
||||||
|
foreach (var item in line)
|
||||||
|
{
|
||||||
|
if (item.Attributes.BackgroundColor != 0)
|
||||||
|
{
|
||||||
|
ds.FillRectangle(new Windows.Foundation.Rect { Height = _characterHeight, Width = _characterWidth * item.Text.Length, X = x, Y = y }, _colorHelper.GetColour(item.Attributes.BackgroundColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
var foreground = Colors.White;
|
||||||
|
if (item.Attributes.ForegroundColor != 0)
|
||||||
|
{
|
||||||
|
foreground = _colorHelper.GetColour(item.Attributes.ForegroundColor);
|
||||||
|
}
|
||||||
|
var pos = new System.Numerics.Vector2 { X = (float)x, Y = (float)y };
|
||||||
|
|
||||||
|
ds.DrawText(item.Text, pos, foreground, _format);
|
||||||
|
x += item.Text.Length * _characterWidth;
|
||||||
|
}
|
||||||
|
y += _characterHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Measure()
|
||||||
|
{
|
||||||
|
var device = CanvasDevice.GetSharedDevice();
|
||||||
|
using (CanvasRenderTarget offscreen = new CanvasRenderTarget(device, (float)256, (float)256, canvas.Dpi))
|
||||||
|
{
|
||||||
|
using (CanvasDrawingSession ds = offscreen.CreateDrawingSession())
|
||||||
|
{
|
||||||
|
_format = new CanvasTextFormat
|
||||||
|
{
|
||||||
|
FontSize = _terminalOptions.FontSize,
|
||||||
|
FontFamily = _terminalOptions.FontFamily,
|
||||||
|
FontWeight = _terminalOptions.FontWeight
|
||||||
|
};
|
||||||
|
|
||||||
|
var textLayout = new CanvasTextLayout(ds, "\u2560", _format, 0.0f, 0.0f);
|
||||||
|
_characterWidth = textLayout.DrawBounds.Right;
|
||||||
|
_characterHeight = textLayout.DrawBounds.Bottom;
|
||||||
|
|
||||||
|
_cols = (int)Math.Floor(Window.Current.CoreWindow.Bounds.Width / _characterWidth);
|
||||||
|
_rows = (int)Math.Floor((Window.Current.CoreWindow.Bounds.Height - 32) / _characterHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void UserControl_Unloaded(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
// Explicitly remove references to allow the Win2D controls to get garbage collected
|
||||||
|
canvas.RemoveFromVisualTree();
|
||||||
|
canvas = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalViewModel ViewModel { get; private set; }
|
||||||
|
|
||||||
|
public Task ChangeKeyBindings()
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChangeOptions(TerminalOptions options)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChangeTheme(TerminalTheme theme)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FindNext(string searchText)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FindPrevious(string searchText)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FocusTerminal()
|
||||||
|
{
|
||||||
|
canvas.Focus(FocusState.Programmatic);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Initialize(TerminalViewModel viewModel)
|
||||||
|
{
|
||||||
|
Measure();
|
||||||
|
ViewModel = viewModel;
|
||||||
|
ViewModel.Terminal.OutputReceived += Terminal_OutputReceived;
|
||||||
|
//ViewModel.Terminal.RegisterSelectedTextCallback(() => ExecuteScriptAsync("term.getSelection()"));
|
||||||
|
//ViewModel.Terminal.Closed += Terminal_Closed;
|
||||||
|
|
||||||
|
var options = ViewModel.SettingsService.GetTerminalOptions();
|
||||||
|
var keyBindings = ViewModel.SettingsService.GetCommandKeyBindings();
|
||||||
|
var profiles = ViewModel.SettingsService.GetShellProfiles();
|
||||||
|
var settings = ViewModel.SettingsService.GetApplicationSettings();
|
||||||
|
var theme = ViewModel.TerminalTheme;
|
||||||
|
var sessionType = SessionType.Unknown;
|
||||||
|
if (settings.AlwaysUseWinPty || !ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7))
|
||||||
|
{
|
||||||
|
sessionType = SessionType.WinPty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sessionType = SessionType.ConPty;
|
||||||
|
}
|
||||||
|
var size = new Models.TerminalSize { Rows = _rows, Columns = _cols };
|
||||||
|
_buffer = new TerminalBuffer(new Terminal.TerminalSize(size.Columns, size.Rows));
|
||||||
|
_parser = new Ansi.AnsiParser();
|
||||||
|
var response = await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, size, sessionType).ConfigureAwait(true);
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
await ViewModel.DialogService.ShowMessageDialogAsnyc("Error", response.Error, DialogButton.OK).ConfigureAwait(true);
|
||||||
|
ViewModel.Terminal.ReportLauchFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.Focus(FocusState.Keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _canvas_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("focus");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _canvas_KeyUp(object sender, KeyRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var controlPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
var shiftPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
var windowsPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.LeftWindows).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
var altPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Menu).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
|
||||||
|
int modCode = 0;
|
||||||
|
if (shiftPressed) modCode |= 1;
|
||||||
|
if (altPressed) modCode |= 2;
|
||||||
|
if (controlPressed) modCode |= 4;
|
||||||
|
if (windowsPressed) modCode |= 8;
|
||||||
|
|
||||||
|
//if (IsSelecting && !modifiers.HasFlag(ModifierKeys.Alt))
|
||||||
|
//{
|
||||||
|
// ClearSelection();
|
||||||
|
// if (e.Key == Key.Escape)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
string text = string.Empty;
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case VirtualKey.Escape:
|
||||||
|
text = $"{C0.ESC}{C0.ESC}{C0.ESC}";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Back:
|
||||||
|
text = shiftPressed ?
|
||||||
|
C0.BS.ToString() :
|
||||||
|
C0.DEL.ToString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Delete:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[3~" :
|
||||||
|
$"{C0.ESC}[3;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Tab:
|
||||||
|
text = shiftPressed ?
|
||||||
|
$"{C0.ESC}[Z" :
|
||||||
|
C0.HT.ToString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Up:
|
||||||
|
text = Construct(1, 'A');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Down:
|
||||||
|
text = Construct(1, 'B');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Right:
|
||||||
|
text = Construct(1, 'C');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Left:
|
||||||
|
text = Construct(1, 'D');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Home:
|
||||||
|
text = Construct(1, 'H');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.End:
|
||||||
|
text = Construct(1, 'F');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.PageUp:
|
||||||
|
text = $"{C0.ESC}[5~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.PageDown:
|
||||||
|
text = $"{C0.ESC}[6~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Enter:
|
||||||
|
text = C0.CR.ToString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.Space:
|
||||||
|
text = " ";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F1:
|
||||||
|
text = Construct(1, 'P');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F2:
|
||||||
|
text = Construct(1, 'Q');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F3:
|
||||||
|
text = Construct(1, 'R');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F4:
|
||||||
|
text = Construct(1, 'S');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F5:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[15~" :
|
||||||
|
$"{C0.ESC}[15;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F6:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[17~" :
|
||||||
|
$"{C0.ESC}[17;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F7:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[18~" :
|
||||||
|
$"{C0.ESC}[18;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F8:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[19~" :
|
||||||
|
$"{C0.ESC}[19;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F9:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[20~" :
|
||||||
|
$"{C0.ESC}[20;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F10:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[21~" :
|
||||||
|
$"{C0.ESC}[21;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F11:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[23~" :
|
||||||
|
$"{C0.ESC}[23;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VirtualKey.F12:
|
||||||
|
text = (modCode == 0) ?
|
||||||
|
$"{C0.ESC}[24~" :
|
||||||
|
$"{C0.ESC}[24;{modCode + 1}~";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (text != string.Empty)
|
||||||
|
{
|
||||||
|
ViewModel.Terminal.Write(Encoding.UTF8.GetBytes(text));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Construct(int a, char c)
|
||||||
|
{
|
||||||
|
return (modCode == 0) ?
|
||||||
|
$"{C0.ESC}O{c}" :
|
||||||
|
$"{C0.ESC}[{a};{modCode + 1}{c}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Canvas_CharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var text = Encoding.UTF8.GetBytes(new[] { args.Character });
|
||||||
|
ViewModel.Terminal.Write(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessTerminalCode(TerminalCode code)
|
||||||
|
{
|
||||||
|
switch (code.Type)
|
||||||
|
{
|
||||||
|
case TerminalCodeType.ResetMode:
|
||||||
|
_buffer.ShowCursor = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.SetMode:
|
||||||
|
_buffer.ShowCursor = true;
|
||||||
|
|
||||||
|
// HACK We want clear to reset the window position but not general typing.
|
||||||
|
// We therefore reset the window only if the cursor is moved to the top.
|
||||||
|
if (_buffer.CursorY == 0)
|
||||||
|
{
|
||||||
|
_buffer.WindowTop = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.Text:
|
||||||
|
_buffer.Type(code.Text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.LineFeed:
|
||||||
|
if (_buffer.CursorY == _buffer.Size.Rows - 1)
|
||||||
|
{
|
||||||
|
_buffer.ShiftUp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_buffer.CursorY++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.CarriageReturn:
|
||||||
|
_buffer.CursorX = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.CharAttributes:
|
||||||
|
_buffer.CurrentCharAttributes = code.CharAttributes;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.CursorPosition:
|
||||||
|
_buffer.CursorX = code.Column;
|
||||||
|
_buffer.CursorY = code.Line;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.CursorUp:
|
||||||
|
_buffer.CursorY -= code.Line;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.CursorCharAbsolute:
|
||||||
|
_buffer.CursorX = code.Column;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.EraseInLine:
|
||||||
|
if (code.Line == 0)
|
||||||
|
{
|
||||||
|
_buffer.ClearBlock(_buffer.CursorX, _buffer.CursorY, _buffer.Size.Columns - 1, _buffer.CursorY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.EraseInDisplay:
|
||||||
|
_buffer.Clear();
|
||||||
|
_buffer.CursorX = 0;
|
||||||
|
_buffer.CursorY = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerminalCodeType.SetTitle:
|
||||||
|
//ViewModel.Terminal.SetTitle(code.Text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Terminal_OutputReceived(object sender, byte[] e)
|
||||||
|
{
|
||||||
|
|
||||||
|
var text = Encoding.UTF8.GetString(e, 0, e.Length);
|
||||||
|
var reader = new ArrayReader<char>(text.ToCharArray());
|
||||||
|
var codes = _parser.Parse(reader);
|
||||||
|
foreach (var code in codes)
|
||||||
|
{
|
||||||
|
ProcessTerminalCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||||
|
{
|
||||||
|
canvas.Height = Math.Max(_buffer.CurrentHistoryLength * _characterHeight, _buffer.Size.Rows * _characterHeight);
|
||||||
|
canvas.Invalidate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.NativeFrontend.Utility
|
||||||
|
{
|
||||||
|
internal class ArrayReader<T>
|
||||||
|
{
|
||||||
|
private readonly T[] _array;
|
||||||
|
|
||||||
|
public int Offset { get; private set; }
|
||||||
|
public int RemainingLength { get; private set; }
|
||||||
|
|
||||||
|
public ArrayReader(T[] array)
|
||||||
|
{
|
||||||
|
_array = array;
|
||||||
|
Offset = 0;
|
||||||
|
RemainingLength = array.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayReader(T[] array, int offset, int count)
|
||||||
|
{
|
||||||
|
_array = array;
|
||||||
|
Offset = offset;
|
||||||
|
RemainingLength = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayReader(ArraySegment<T> segment)
|
||||||
|
{
|
||||||
|
_array = segment.Array;
|
||||||
|
Offset = segment.Offset;
|
||||||
|
RemainingLength = segment.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRead(out T element)
|
||||||
|
{
|
||||||
|
if (RemainingLength > 0)
|
||||||
|
{
|
||||||
|
element = _array[Offset];
|
||||||
|
Offset++;
|
||||||
|
RemainingLength--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element = default(T);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryPeek(out T element)
|
||||||
|
{
|
||||||
|
if (RemainingLength > 0)
|
||||||
|
{
|
||||||
|
element = _array[Offset];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element = default(T);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Read()
|
||||||
|
{
|
||||||
|
T element;
|
||||||
|
if (!TryRead(out element))
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Peek()
|
||||||
|
{
|
||||||
|
T element;
|
||||||
|
if (!TryPeek(out element))
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -292,6 +292,10 @@
|
|||||||
Content="Convert line endings to CR+LF"
|
Content="Convert line endings to CR+LF"
|
||||||
GroupName="LineEndingStyle"
|
GroupName="LineEndingStyle"
|
||||||
IsChecked="{x:Bind ToCRLFIsSelected, Mode=TwoWay}" />
|
IsChecked="{x:Bind ToCRLFIsSelected, Mode=TwoWay}" />
|
||||||
|
<RadioButton
|
||||||
|
Content="Convert line endings to CR"
|
||||||
|
GroupName="LineEndingStyle"
|
||||||
|
IsChecked="{x:Bind ToCRIsSelected, Mode=TwoWay}" />
|
||||||
<RadioButton
|
<RadioButton
|
||||||
Content="Convert line endings to LF"
|
Content="Convert line endings to LF"
|
||||||
GroupName="LineEndingStyle"
|
GroupName="LineEndingStyle"
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ namespace FluentTerminal.App.Views
|
|||||||
ViewModel.FindNextRequested += OnFindNextRequested;
|
ViewModel.FindNextRequested += OnFindNextRequested;
|
||||||
ViewModel.FindPreviousRequested += OnFindPreviousRequested;
|
ViewModel.FindPreviousRequested += OnFindPreviousRequested;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_terminalView = new XtermTerminalView();
|
_terminalView = new NativeFrontend.TerminalControl(ViewModel.SettingsService);
|
||||||
|
//_terminalView = new VtNetCoreFrontend.VtNetCoreTerminal();
|
||||||
|
//_terminalView = new XtermTerminalView();
|
||||||
TerminalContainer.Children.Add((UIElement)_terminalView);
|
TerminalContainer.Children.Add((UIElement)_terminalView);
|
||||||
_terminalView.Initialize(ViewModel);
|
_terminalView.Initialize(ViewModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="FluentTerminal.App.Views.VtNetCoreFrontend.VtNetCoreTerminal"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:FluentTerminal.App.Views.VtNetCoreFrontend"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:xaml="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||||
|
d:DesignHeight="300"
|
||||||
|
d:DesignWidth="400"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<xaml:CanvasControl x:Name="canvas" IsTabStop="true" />
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentTerminal.App.Services;
|
||||||
|
using FluentTerminal.App.Utilities;
|
||||||
|
using FluentTerminal.App.ViewModels;
|
||||||
|
using FluentTerminal.Models;
|
||||||
|
using FluentTerminal.Models.Enums;
|
||||||
|
using Microsoft.Graphics.Canvas;
|
||||||
|
using Microsoft.Graphics.Canvas.Text;
|
||||||
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||||
|
using VtNetCore.VirtualTerminal;
|
||||||
|
using VtNetCore.XTermParser;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using Windows.UI.Core;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Controls.Primitives;
|
||||||
|
using Windows.UI.Xaml.Data;
|
||||||
|
using Windows.UI.Xaml.Input;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
|
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
|
||||||
|
|
||||||
|
namespace FluentTerminal.App.Views.VtNetCoreFrontend
|
||||||
|
{
|
||||||
|
public sealed partial class VtNetCoreTerminal : UserControl, ITerminalView
|
||||||
|
{
|
||||||
|
private double _characterHeight;
|
||||||
|
private double _characterWidth;
|
||||||
|
private int _cols;
|
||||||
|
private CanvasTextFormat _format;
|
||||||
|
private int _rows;
|
||||||
|
|
||||||
|
public VtNetCoreTerminal()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
|
||||||
|
canvas.Draw += _canvas_Draw;
|
||||||
|
canvas.KeyDown += Canvas_KeyDown;
|
||||||
|
canvas.GotFocus += Canvas_GotFocus;
|
||||||
|
canvas.LostFocus += Canvas_LostFocus;
|
||||||
|
canvas.Tapped += Canvas_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Canvas_LostFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Canvas_Tapped(object sender, TappedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
canvas.Focus(FocusState.Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Canvas_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
|
||||||
|
Debug.WriteLine("focus");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Canvas_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var controlPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
var shiftPressed = (Window.Current.CoreWindow.GetKeyState(Windows.System.VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down));
|
||||||
|
|
||||||
|
//Scrolling = false;
|
||||||
|
|
||||||
|
// Since I get the same key twice in TerminalKeyDown and in CoreWindow_CharacterReceived
|
||||||
|
// I lookup whether KeyPressed should handle the key here or there.
|
||||||
|
var code = _terminal.GetKeySequence(e.Key.ToString(), controlPressed, shiftPressed);
|
||||||
|
if (code != null)
|
||||||
|
e.Handled = _terminal.KeyPressed(e.Key.ToString(), controlPressed, shiftPressed);
|
||||||
|
|
||||||
|
canvas.Invalidate();
|
||||||
|
|
||||||
|
//if (ViewTop != _terminal.ViewPort.TopRow)
|
||||||
|
//{
|
||||||
|
// ViewTop = _terminal.ViewPort.TopRow;
|
||||||
|
// canvas.Invalidate();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CoreWindow_CharacterReceived(CoreWindow sender, CharacterReceivedEventArgs args)
|
||||||
|
{
|
||||||
|
//if (!Connected)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
var ch = char.ConvertFromUtf32((int)args.KeyCode);
|
||||||
|
switch (ch)
|
||||||
|
{
|
||||||
|
case "\b":
|
||||||
|
case "\t":
|
||||||
|
case "\n":
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "\r":
|
||||||
|
////LogText = "";
|
||||||
|
//lock(_rawText)
|
||||||
|
//{
|
||||||
|
// _rawTextLength = 0;
|
||||||
|
// _rawTextChanged = true;
|
||||||
|
//}
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since I get the same key twice in TerminalKeyDown and in CoreWindow_CharacterReceived
|
||||||
|
// I lookup whether KeyPressed should handle the key here or there.
|
||||||
|
var code = _terminal.GetKeySequence(ch, false, false);
|
||||||
|
if (code == null)
|
||||||
|
args.Handled = _terminal.KeyPressed(ch, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
|
||||||
|
{
|
||||||
|
var spans = _terminal.ViewPort.GetPageSpans(0, _rows, _cols);
|
||||||
|
|
||||||
|
for (var i = 0; i < spans.Count; i++)
|
||||||
|
{
|
||||||
|
var span = spans[i];
|
||||||
|
var x = 0.0;
|
||||||
|
foreach (var item in span.Spans)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (item.BackgroundColor != "#000000")
|
||||||
|
{
|
||||||
|
var background = item.BackgroundColor.FromString();
|
||||||
|
args.DrawingSession.FillRectangle(new Rect { Height = _characterHeight, Width = _characterWidth * item.Text.Length, X = x, Y = i * 16 }, background);
|
||||||
|
}
|
||||||
|
|
||||||
|
var foreground = item.ForgroundColor.FromString();
|
||||||
|
args.DrawingSession.DrawText(item.Text, new System.Numerics.Vector2 { X = (float)x, Y = i * 16 }, foreground, _format);
|
||||||
|
|
||||||
|
|
||||||
|
x += item.Text.Length * _characterWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChangeKeyBindings()
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChangeOptions(TerminalOptions options)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ChangeTheme(TerminalTheme theme)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FindNext(string searchText)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FindPrevious(string searchText)
|
||||||
|
{
|
||||||
|
//throw new NotImplementedException();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FocusTerminal()
|
||||||
|
{
|
||||||
|
canvas.Focus(FocusState.Programmatic);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TerminalViewModel ViewModel { get; private set; }
|
||||||
|
|
||||||
|
public async Task Initialize(TerminalViewModel viewModel)
|
||||||
|
{
|
||||||
|
Measure();
|
||||||
|
|
||||||
|
ViewModel = viewModel;
|
||||||
|
ViewModel.Terminal.OutputReceived += Terminal_OutputReceived;
|
||||||
|
//ViewModel.Terminal.RegisterSelectedTextCallback(() => ExecuteScriptAsync("term.getSelection()"));
|
||||||
|
//ViewModel.Terminal.Closed += Terminal_Closed;
|
||||||
|
|
||||||
|
var options = ViewModel.SettingsService.GetTerminalOptions();
|
||||||
|
var keyBindings = ViewModel.SettingsService.GetCommandKeyBindings();
|
||||||
|
var profiles = ViewModel.SettingsService.GetShellProfiles();
|
||||||
|
var settings = ViewModel.SettingsService.GetApplicationSettings();
|
||||||
|
var theme = ViewModel.TerminalTheme;
|
||||||
|
var sessionType = SessionType.Unknown;
|
||||||
|
if (settings.AlwaysUseWinPty || !ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7))
|
||||||
|
{
|
||||||
|
sessionType = SessionType.WinPty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sessionType = SessionType.ConPty;
|
||||||
|
}
|
||||||
|
var size = new Models.TerminalSize { Rows = _rows, Columns = _cols };
|
||||||
|
|
||||||
|
_terminal = new VirtualTerminalController();
|
||||||
|
_terminal.SendData = new EventHandler<SendDataEventArgs>((s, e) => { });
|
||||||
|
_dataConsumer = new DataConsumer(_terminal);
|
||||||
|
|
||||||
|
var response = await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, size, sessionType).ConfigureAwait(true);
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
await ViewModel.DialogService.ShowMessageDialogAsnyc("Error", response.Error, DialogButton.OK).ConfigureAwait(true);
|
||||||
|
ViewModel.Terminal.ReportLauchFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.Focus(FocusState.Keyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataConsumer _dataConsumer;
|
||||||
|
private VirtualTerminalController _terminal;
|
||||||
|
|
||||||
|
private void Terminal_OutputReceived(object sender, byte[] e)
|
||||||
|
{
|
||||||
|
_dataConsumer.Push(e);
|
||||||
|
|
||||||
|
if (_terminal.Changed)
|
||||||
|
{
|
||||||
|
_terminal.ClearChanges();
|
||||||
|
canvas.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Measure()
|
||||||
|
{
|
||||||
|
var device = CanvasDevice.GetSharedDevice();
|
||||||
|
using (CanvasRenderTarget offscreen = new CanvasRenderTarget(device, (float)256, (float)256, canvas.Dpi))
|
||||||
|
{
|
||||||
|
using (CanvasDrawingSession ds = offscreen.CreateDrawingSession())
|
||||||
|
{
|
||||||
|
_format = new CanvasTextFormat
|
||||||
|
{
|
||||||
|
FontSize = 13,
|
||||||
|
FontFamily = "Consolas NF"
|
||||||
|
};
|
||||||
|
|
||||||
|
var textLayout = new CanvasTextLayout(ds, "\u2560", _format, 0.0f, 0.0f);
|
||||||
|
_characterWidth = textLayout.DrawBounds.Right;
|
||||||
|
_characterHeight = textLayout.DrawBounds.Bottom;
|
||||||
|
|
||||||
|
_cols = (int)Math.Floor(Window.Current.CoreWindow.Bounds.Width / _characterWidth);
|
||||||
|
_rows = (int)Math.Floor((Window.Current.CoreWindow.Bounds.Height - 32) / _characterHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,7 +142,13 @@ namespace FluentTerminal.App.Views
|
|||||||
var port = await ViewModel.TrayProcessCommunicationService.GetAvailablePort().ConfigureAwait(true);
|
var port = await ViewModel.TrayProcessCommunicationService.GetAvailablePort().ConfigureAwait(true);
|
||||||
await CreateWebSocketServer(port.Port).ConfigureAwait(true);
|
await CreateWebSocketServer(port.Port).ConfigureAwait(true);
|
||||||
_connectedEvent.Wait();
|
_connectedEvent.Wait();
|
||||||
await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, size, sessionType).ConfigureAwait(true);
|
var response = await ViewModel.Terminal.StartShellProcess(ViewModel.ShellProfile, size, sessionType).ConfigureAwait(true);
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
await ViewModel.DialogService.ShowMessageDialogAsnyc("Error", response.Error, DialogButton.OK).ConfigureAwait(true);
|
||||||
|
ViewModel.Terminal.ReportLauchFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
_webView.Focus(FocusState.Programmatic);
|
_webView.Focus(FocusState.Programmatic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as Terminal from '../node_modules/xterm/dist/xterm';
|
import { Terminal } from "xterm";
|
||||||
import * as attach from '../node_modules/xterm/dist/addons/attach/attach';
|
import * as attach from "xterm/lib/addons/attach/attach";
|
||||||
import * as fit from '../node_modules/xterm/dist/addons/fit/fit';
|
import * as fit from "xterm/lib/addons/fit/fit";
|
||||||
import * as winptyCompat from '../node_modules/xterm/dist/addons/winptyCompat/winptyCompat';
|
import * as winptyCompat from "xterm/lib/addons/winptyCompat/winptyCompat";
|
||||||
import * as search from '../node_modules/xterm/dist/addons/search/search';
|
import * as search from "xterm/lib/addons/search/search";
|
||||||
|
|
||||||
|
|
||||||
Terminal.applyAddon(attach);
|
Terminal.applyAddon(attach);
|
||||||
Terminal.applyAddon(fit);
|
Terminal.applyAddon(fit);
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace FluentTerminal.Models.Enums
|
|||||||
[Description("Configurable new tab")]
|
[Description("Configurable new tab")]
|
||||||
ConfigurableNewTab,
|
ConfigurableNewTab,
|
||||||
|
|
||||||
|
[Description("New remote tab")]
|
||||||
|
NewRemoteTab,
|
||||||
|
|
||||||
[Description("Change tab title")]
|
[Description("Change tab title")]
|
||||||
ChangeTabTitle,
|
ChangeTabTitle,
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ namespace FluentTerminal.Models.Enums
|
|||||||
[Description("Do not modify")]
|
[Description("Do not modify")]
|
||||||
DoNotModify,
|
DoNotModify,
|
||||||
|
|
||||||
|
[Description("Convert to CR")]
|
||||||
|
ToCR,
|
||||||
|
|
||||||
[Description("Convert to CRLF")]
|
[Description("Convert to CRLF")]
|
||||||
ToCRLF,
|
ToCRLF,
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ namespace FluentTerminal.Models
|
|||||||
{
|
{
|
||||||
switch (LineEndingTranslation)
|
switch (LineEndingTranslation)
|
||||||
{
|
{
|
||||||
|
case LineEndingStyle.ToCR:
|
||||||
|
return NewlinePattern.Replace(content, "\r");
|
||||||
case LineEndingStyle.ToCRLF:
|
case LineEndingStyle.ToCRLF:
|
||||||
return NewlinePattern.Replace(content, "\r\n");
|
return NewlinePattern.Replace(content, "\r\n");
|
||||||
case LineEndingStyle.ToLF:
|
case LineEndingStyle.ToLF:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using FluentTerminal.Models.Enums;
|
using FluentTerminal.Models.Enums;
|
||||||
|
using Windows.UI.Text;
|
||||||
|
|
||||||
namespace FluentTerminal.Models
|
namespace FluentTerminal.Models
|
||||||
{
|
{
|
||||||
@@ -23,5 +24,10 @@ namespace FluentTerminal.Models
|
|||||||
public int Padding { get; set; }
|
public int Padding { get; set; }
|
||||||
|
|
||||||
public uint ScrollBackLimit { get; set; }
|
public uint ScrollBackLimit { get; set; }
|
||||||
|
|
||||||
|
public FontWeight FontWeight
|
||||||
|
{
|
||||||
|
get => BoldText ? FontWeights.Bold : FontWeights.Normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ namespace FluentTerminal.SystemTray.Services.ConPty
|
|||||||
private TerminalsManager _terminalsManager;
|
private TerminalsManager _terminalsManager;
|
||||||
private Terminal _terminal;
|
private Terminal _terminal;
|
||||||
private bool _exited;
|
private bool _exited;
|
||||||
|
private TerminalSize _terminalSize;
|
||||||
|
|
||||||
public int Id { get; private set; }
|
public int Id { get; private set; }
|
||||||
|
|
||||||
@@ -26,12 +27,14 @@ namespace FluentTerminal.SystemTray.Services.ConPty
|
|||||||
public void Resize(TerminalSize size)
|
public void Resize(TerminalSize size)
|
||||||
{
|
{
|
||||||
_terminal.Resize(size.Columns, size.Rows);
|
_terminal.Resize(size.Columns, size.Rows);
|
||||||
|
_terminalSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(CreateTerminalRequest request, TerminalsManager terminalsManager)
|
public void Start(CreateTerminalRequest request, TerminalsManager terminalsManager)
|
||||||
{
|
{
|
||||||
Id = request.Id;
|
Id = request.Id;
|
||||||
_terminalsManager = terminalsManager;
|
_terminalsManager = terminalsManager;
|
||||||
|
_terminalSize = request.Size;
|
||||||
|
|
||||||
ShellExecutableName = Path.GetFileNameWithoutExtension(request.Profile.Location);
|
ShellExecutableName = Path.GetFileNameWithoutExtension(request.Profile.Location);
|
||||||
var cwd = GetWorkingDirectory(request.Profile);
|
var cwd = GetWorkingDirectory(request.Profile);
|
||||||
@@ -80,7 +83,7 @@ namespace FluentTerminal.SystemTray.Services.ConPty
|
|||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var buffer = new byte[1024];
|
var buffer = new byte[Math.Max(1024, _terminalSize.Columns * _terminalSize.Rows * 4)];
|
||||||
var readBytes = await _terminal.ConsoleOutStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
var readBytes = await _terminal.ConsoleOutStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
var read = new byte[readBytes];
|
var read = new byte[readBytes];
|
||||||
Buffer.BlockCopy(buffer, 0, read, 0, readBytes);
|
Buffer.BlockCopy(buffer, 0, read, 0, readBytes);
|
||||||
|
|||||||
@@ -108,9 +108,9 @@ namespace FluentTerminal.SystemTray.Services
|
|||||||
public string GetDefaultEnvironmentVariableString()
|
public string GetDefaultEnvironmentVariableString()
|
||||||
{
|
{
|
||||||
var environmentVariables = Environment.GetEnvironmentVariables();
|
var environmentVariables = Environment.GetEnvironmentVariables();
|
||||||
environmentVariables.Add("TERM", "xterm-256color");
|
environmentVariables["TERM"] = "xterm-256color";
|
||||||
environmentVariables.Add("TERM_PROGRAM", "FluentTerminal");
|
environmentVariables["TERM_PROGRAM"] = "FluentTerminal";
|
||||||
environmentVariables.Add("TERM_PROGRAM_VERSION", $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}.{Package.Current.Id.Version.Build}.{Package.Current.Id.Version.Revision}");
|
environmentVariables["TERM_PROGRAM_VERSION"] = $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}.{Package.Current.Id.Version.Build}.{Package.Current.Id.Version.Revision}";
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ namespace FluentTerminal.SystemTray.Services.WinPty
|
|||||||
private TerminalsManager _terminalsManager;
|
private TerminalsManager _terminalsManager;
|
||||||
private Process _shellProcess;
|
private Process _shellProcess;
|
||||||
private bool _exited;
|
private bool _exited;
|
||||||
|
private TerminalSize _terminalSize;
|
||||||
|
|
||||||
public void Start(CreateTerminalRequest request, TerminalsManager terminalsManager)
|
public void Start(CreateTerminalRequest request, TerminalsManager terminalsManager)
|
||||||
{
|
{
|
||||||
Id = request.Id;
|
Id = request.Id;
|
||||||
_terminalsManager = terminalsManager;
|
_terminalsManager = terminalsManager;
|
||||||
|
_terminalSize = request.Size;
|
||||||
|
|
||||||
var configHandle = IntPtr.Zero;
|
var configHandle = IntPtr.Zero;
|
||||||
var spawnConfigHandle = IntPtr.Zero;
|
var spawnConfigHandle = IntPtr.Zero;
|
||||||
@@ -63,7 +65,7 @@ namespace FluentTerminal.SystemTray.Services.WinPty
|
|||||||
|
|
||||||
if (!winpty_spawn(_handle, spawnConfigHandle, out IntPtr process, out IntPtr thread, out int procError, out errorHandle))
|
if (!winpty_spawn(_handle, spawnConfigHandle, out IntPtr process, out IntPtr thread, out int procError, out errorHandle))
|
||||||
{
|
{
|
||||||
throw new Exception($@"Failed to start the shell process. Please check your shell settings.\nTried to start: {request.Profile.Location} ""{request.Profile.Arguments}""");
|
throw new Exception($@"Failed to start the shell process. Please check your shell settings.{Environment.NewLine}Tried to start: {request.Profile.Location} ""{request.Profile.Arguments}""");
|
||||||
}
|
}
|
||||||
|
|
||||||
var shellProcessId = ProcessApi.GetProcessId(process);
|
var shellProcessId = ProcessApi.GetProcessId(process);
|
||||||
@@ -145,6 +147,7 @@ namespace FluentTerminal.SystemTray.Services.WinPty
|
|||||||
{
|
{
|
||||||
throw new Exception(winpty_error_msg(errorHandle));
|
throw new Exception(winpty_error_msg(errorHandle));
|
||||||
}
|
}
|
||||||
|
_terminalSize = size;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -198,7 +201,7 @@ namespace FluentTerminal.SystemTray.Services.WinPty
|
|||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var buffer = new byte[1024];
|
var buffer = new byte[Math.Max(1024, _terminalSize.Columns * _terminalSize.Rows * 4)];
|
||||||
var readBytes = await _stdout.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
var readBytes = await _stdout.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
var read = new byte[readBytes];
|
var read = new byte[readBytes];
|
||||||
Buffer.BlockCopy(buffer, 0, read, 0, readBytes);
|
Buffer.BlockCopy(buffer, 0, read, 0, readBytes);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
<g id="Artboard1" transform="matrix(0.808159,0,0,0.784348,-1.18203,-1.41183)">
|
||||||
|
<rect x="1.463" y="1.8" width="31.3" height="32.3" style="fill:none;"/>
|
||||||
|
<g transform="matrix(1.14645,0,0,1.14469,-3.01401,-2.1485)">
|
||||||
|
<g transform="matrix(1.09077,0,0,1.17654,1.78153,0.746853)">
|
||||||
|
<rect x="15.397" y="22.096" width="9.398" height="2.833"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.83321,-1.36916,0.754014,1.07511,-39.0326,25.3746)">
|
||||||
|
<rect x="15.397" y="22.096" width="9.398" height="2.833"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-1.26155,-1.3975,0.941009,-0.904614,21.3951,61.6015)">
|
||||||
|
<path d="M24.795,22.096L15.397,22.096L15.173,24.952L24.795,24.929L24.795,22.096Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 26 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
<g id="Artboard1" transform="matrix(0.808159,0,0,0.784348,-1.18203,-1.41183)">
|
||||||
|
<rect x="1.463" y="1.8" width="31.3" height="32.3" style="fill:none;"/>
|
||||||
|
<g transform="matrix(1.14645,0,0,1.14469,-3.01401,-2.1485)">
|
||||||
|
<g transform="matrix(1.09077,0,0,1.17654,1.78153,0.746853)">
|
||||||
|
<rect x="15.397" y="22.096" width="9.398" height="2.833" style="fill:rgb(251,251,251);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.83321,-1.36916,0.754014,1.07511,-39.0326,25.3746)">
|
||||||
|
<rect x="15.397" y="22.096" width="9.398" height="2.833" style="fill:rgb(251,251,251);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-1.26155,-1.3975,0.941009,-0.904614,21.3951,61.6015)">
|
||||||
|
<path d="M24.795,22.096L15.397,22.096L15.173,24.952L24.795,24.929L24.795,22.096Z" style="fill:rgb(251,251,251);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
+4
-4
@@ -328,7 +328,7 @@ function CheckIfNeedToEnableSideloading
|
|||||||
if (Test-Path $RegistryPath)
|
if (Test-Path $RegistryPath)
|
||||||
{
|
{
|
||||||
$SideloadingEnabledProperty = (Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue)
|
$SideloadingEnabledProperty = (Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue)
|
||||||
if ($SideloadingEnabledProperty -ne $null -and $SideloadingEnabledProperty.$ValueName -eq 1)
|
if ($null -ne $SideloadingEnabledProperty -and $SideloadingEnabledProperty.$ValueName -eq 1)
|
||||||
{
|
{
|
||||||
$Result = $false
|
$Result = $false
|
||||||
}
|
}
|
||||||
@@ -497,19 +497,19 @@ function InstallContextMenuIntegration
|
|||||||
-RegistryPath "hkcu:\Software\Classes\Directory\shell\Open Fluent Terminal here\command"`
|
-RegistryPath "hkcu:\Software\Classes\Directory\shell\Open Fluent Terminal here\command"`
|
||||||
-Name "(Default)"`
|
-Name "(Default)"`
|
||||||
-Type "ExpandString"`
|
-Type "ExpandString"`
|
||||||
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new %1"
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%1`""
|
||||||
|
|
||||||
ForceAddRegKeyValue `
|
ForceAddRegKeyValue `
|
||||||
-RegistryPath "hkcu:\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command"`
|
-RegistryPath "hkcu:\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command"`
|
||||||
-Name "(Default)"`
|
-Name "(Default)"`
|
||||||
-Type "ExpandString"`
|
-Type "ExpandString"`
|
||||||
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new %V"
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
|
||||||
|
|
||||||
ForceAddRegKeyValue `
|
ForceAddRegKeyValue `
|
||||||
-RegistryPath "hkcu:\Software\Classes\Directory\LibraryFolder\shell\Open Fluent Terminal here\command"`
|
-RegistryPath "hkcu:\Software\Classes\Directory\LibraryFolder\shell\Open Fluent Terminal here\command"`
|
||||||
-Name "(Default)"`
|
-Name "(Default)"`
|
||||||
-Type "ExpandString"`
|
-Type "ExpandString"`
|
||||||
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new %V"
|
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ A Terminal Emulator based on UWP and web technologies.
|
|||||||
|
|
||||||
### Chocolatey package manager installation
|
### Chocolatey package manager installation
|
||||||
|
|
||||||
- In **Settings**, click **For Developers**, and select **Developer mode**.
|
|
||||||
- Install [Chocolatey](https://chocolatey.org/)
|
- Install [Chocolatey](https://chocolatey.org/)
|
||||||
- From an elevated/admin shell, execute `choco install fluent-terminal`
|
- From an elevated/admin shell, execute `choco install fluent-terminal`
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user