Compare commits

..
Author SHA1 Message Date
FelixSe 7bbed15874 Merge branch 'master' into Dev/NativeRenderer 2019-04-07 21:09:22 +02:00
Max Risuhin 39960dd755 Read whole terminal frame at once to avoid console data append glitches (#299)
* Read whole terminal frame at once to avoid console data append glitches

* Simplify calculation logic with Math.Max
2019-04-07 15:24:21 +02:00
Kim Chan 8e5d3dea0a fix: new line bug in verbatim string (#294) 2019-04-05 11:48:15 +02:00
Aleksandar Pesic 973b1a4963 Ap/ssh Implemented SSH by using the exsting infrastructure (#293)
* Appveyor builds (#6)

* SSH implemented.

* Implementing SSH by using the existing app infrastructure.

* Refactored to new command instead of a profile

* Update .gitignore
2019-04-05 10:53:17 +02:00
FelixSe 997cb9814e fixed wrong thread event, made canvas resize 2019-04-03 23:03:24 +02:00
Mike MacCana dbd8632076 Add dark and light mode SVG icons for #203 (#290) 2019-04-03 19:41:45 +02:00
MinchinWeb fffa7ebcb9 Un-swap Ubuntu yellow and cyan (#291)
The build-in Ubuntu profile has the yellows and cyans swapped. This fixes that.
2019-04-03 19:40:20 +02:00
Andrew Kralovec 6524d14687 Native Renderer Collabate PR (#284)
* Load terminal options and add the inital scroll viewer

* Current Virtual Cavnas, problem with event handler calling the canvas on draw
2019-04-03 07:26:07 +02:00
FelixSe 7876984327 proof of concepts for vtnetcore, tterm inspired 2019-03-31 21:58:02 +02:00
Andrew Kralovec ded043db1d move the $null to the left side of the operator (#259) 2019-03-30 11:00:10 +01:00
Paul Galbraith 61ad64519b Revert "Fix missing Developer mode requirement for Chocolatey install (#235)" (#265)
This reverts commit 1a0c51c6d4.
2019-03-30 10:59:52 +01:00
FelixSe ef7f909d26 Increase version 2019-03-27 21:57:16 +01:00
FelixSe f2618fa940 Fix launch error when TERM was already defined 2019-03-27 21:13:23 +01:00
ivanjh 363f885aea Fix shell profile radio button line-ending binding (#257)
* Fix shell profile radio button line-ending binding
Only change when updated from a checked radio button

* Fix cancelling profile edit showing default line translation

* call NotifyPropertyChanged
2019-03-26 22:46:02 +01:00
Meir Blachman ed4a5adb16 added test for DialogService.ShowInputDialogAsync (#258) 2019-03-26 22:45:46 +01:00
Felix 82a3532e90 Add missing quotes (#253) 2019-03-25 22:48:26 +01:00
Felix 15d713191c Handle launch fail (#254) 2019-03-25 22:48:17 +01:00
ivanjh 575022139a Add convert line endings to CR option (#251)
* Add convert line endings to CR option

* Change order of line ending handling options (None,CRLF,CR,LF)
2019-03-25 22:48:06 +01:00
Harsh Chokshi b9686ac4ec Use idiomatic JS import style in web client (#245)
Add-ons imported as per Xterm documentation recommendations:
https://xtermjs.org/docs/guides/using-addons/
2019-03-25 20:49:13 +01:00
52 changed files with 3500 additions and 507 deletions

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();
} }
} }
+11 -1
View File
@@ -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);
}
} }
} }
+34 -1
View File
@@ -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
View File
@@ -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;
}
}
}
+41 -2
View File
@@ -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 -1
View File
@@ -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);
} }
+5 -6
View File
@@ -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);
+3
View File
@@ -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,
+2
View File
@@ -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:
+6
View File
@@ -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);
+18
View File
@@ -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

+18
View File
@@ -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
View File
@@ -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`""
} }
# #
-1
View File
@@ -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`