From 973b1a49635594aa35f34bae69b56d0af6de8fdd Mon Sep 17 00:00:00 2001 From: Aleksandar Pesic Date: Fri, 5 Apr 2019 10:53:17 +0200 Subject: [PATCH] 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 --- .../DialogServiceTests.cs | 14 +- .../Dialogs/ISshConnectionInfo.cs | 11 + .../Dialogs/ISshConnectionInfoDialog.cs | 9 + FluentTerminal.App.Services/IDialogService.cs | 3 + .../Implementation/DefaultValueProvider.cs | 13 + .../Implementation/DialogService.cs | 9 +- .../MainViewModel.cs | 35 +- .../SshConnectionInfoViewModel.cs | 32 + FluentTerminal.App/App.xaml.cs | 903 +++++++++--------- FluentTerminal.App/Dialogs/SshInfoDialog.xaml | 37 + .../Dialogs/SshInfoDialog.xaml.cs | 26 + FluentTerminal.App/FluentTerminal.App.csproj | 871 ++++++++--------- FluentTerminal.Models/Enums/Command.cs | 3 + 13 files changed, 1073 insertions(+), 893 deletions(-) create mode 100644 FluentTerminal.App.Services/Dialogs/ISshConnectionInfo.cs create mode 100644 FluentTerminal.App.Services/Dialogs/ISshConnectionInfoDialog.cs create mode 100644 FluentTerminal.App.ViewModels/SshConnectionInfoViewModel.cs create mode 100644 FluentTerminal.App/Dialogs/SshInfoDialog.xaml create mode 100644 FluentTerminal.App/Dialogs/SshInfoDialog.xaml.cs diff --git a/FluentTerminal.App.Services.Test/DialogServiceTests.cs b/FluentTerminal.App.Services.Test/DialogServiceTests.cs index 1827d46e..ea2a0127 100644 --- a/FluentTerminal.App.Services.Test/DialogServiceTests.cs +++ b/FluentTerminal.App.Services.Test/DialogServiceTests.cs @@ -23,7 +23,7 @@ public DialogServiceTests() public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog() { var createKeyBindingDialog = new Mock(); - var dialogService = new DialogService(Mock.Of, Mock.Of, () => createKeyBindingDialog.Object, Mock.Of); + var dialogService = new DialogService(Mock.Of, Mock.Of, () => createKeyBindingDialog.Object, Mock.Of, Mock.Of); dialogService.ShowCreateKeyBindingDialog(); @@ -36,7 +36,7 @@ public void ShowMessageDialogAsnyc_TitleIsEmpty_ThrowsArgumentNullException() var title = string.Empty; var content = _fixture.Create(); var buttons = _fixture.CreateMany(2); - var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of); + var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of, Mock.Of); Func> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1)); @@ -49,7 +49,7 @@ public void ShowMessageDialogAsnyc_ContentIsEmpty_ThrowsArgumentNullException() var title = _fixture.Create(); var content = string.Empty; var buttons = _fixture.CreateMany(2); - var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of); + var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of, Mock.Of); Func> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1)); @@ -61,7 +61,7 @@ public void ShowMessageDialogAsnyc_NoButtonsPassed_ThrowsArgumentException() { var title = _fixture.Create(); var content = _fixture.Create(); - var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of); + var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, Mock.Of, Mock.Of); Func> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content); @@ -75,7 +75,7 @@ public void ShowMessageDialogAsnyc_Default_UsesMessageDialog() var content = _fixture.Create(); var buttons = _fixture.CreateMany(2); var messageDialog = new Mock(); - var dialogService = new DialogService(Mock.Of, () => messageDialog.Object, Mock.Of, Mock.Of); + var dialogService = new DialogService(Mock.Of, () => messageDialog.Object, Mock.Of, Mock.Of, Mock.Of); dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1)); @@ -86,7 +86,7 @@ public void ShowMessageDialogAsnyc_Default_UsesMessageDialog() public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog() { var shellProfileSelectionDialog = new Mock(); - var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of, Mock.Of, Mock.Of); + var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of, Mock.Of, Mock.Of, Mock.Of); dialogService.ShowProfileSelectionDialogAsync(); @@ -98,7 +98,7 @@ public void ShowInputDialogAsync_UsesIInputDialogSetTitle() { var title = "title"; var inputDialog = new Mock(); - var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, () => inputDialog.Object); + var dialogService = new DialogService(Mock.Of, Mock.Of, Mock.Of, () => inputDialog.Object, Mock.Of); dialogService.ShowInputDialogAsync(title); diff --git a/FluentTerminal.App.Services/Dialogs/ISshConnectionInfo.cs b/FluentTerminal.App.Services/Dialogs/ISshConnectionInfo.cs new file mode 100644 index 00000000..2b5471db --- /dev/null +++ b/FluentTerminal.App.Services/Dialogs/ISshConnectionInfo.cs @@ -0,0 +1,11 @@ +namespace FluentTerminal.App.Services.Dialogs +{ + public interface ISshConnectionInfo + { + string Host { get; set; } + + ushort Port { get; set; } + + string Username { get; set; } + } +} \ No newline at end of file diff --git a/FluentTerminal.App.Services/Dialogs/ISshConnectionInfoDialog.cs b/FluentTerminal.App.Services/Dialogs/ISshConnectionInfoDialog.cs new file mode 100644 index 00000000..3b6bd2e7 --- /dev/null +++ b/FluentTerminal.App.Services/Dialogs/ISshConnectionInfoDialog.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace FluentTerminal.App.Services.Dialogs +{ + public interface ISshConnectionInfoDialog + { + Task GetSshConnectionInfoAsync(); + } +} \ No newline at end of file diff --git a/FluentTerminal.App.Services/IDialogService.cs b/FluentTerminal.App.Services/IDialogService.cs index 2b6c18b1..2cdf521f 100644 --- a/FluentTerminal.App.Services/IDialogService.cs +++ b/FluentTerminal.App.Services/IDialogService.cs @@ -1,5 +1,6 @@ using FluentTerminal.Models; using System.Threading.Tasks; +using FluentTerminal.App.Services.Dialogs; namespace FluentTerminal.App.Services { @@ -17,5 +18,7 @@ public interface IDialogService Task ShowCreateKeyBindingDialog(); Task ShowInputDialogAsync(string title); + + Task ShowSshConnectionInfoDialogAsync(); } } \ No newline at end of file diff --git a/FluentTerminal.App.Services/Implementation/DefaultValueProvider.cs b/FluentTerminal.App.Services/Implementation/DefaultValueProvider.cs index 39f846df..c38015f6 100644 --- a/FluentTerminal.App.Services/Implementation/DefaultValueProvider.cs +++ b/FluentTerminal.App.Services/Implementation/DefaultValueProvider.cs @@ -118,6 +118,19 @@ public ICollection GetDefaultKeyBindings(Command command) } }; + case Command.NewRemoteTab: + return new List + { + new KeyBinding + { + Command = nameof(Command.NewRemoteTab), + Ctrl = false, + Alt = true, + Shift = false, + Key = (int)ExtendedVirtualKey.T + } + }; + case Command.ChangeTabTitle: return new List { diff --git a/FluentTerminal.App.Services/Implementation/DialogService.cs b/FluentTerminal.App.Services/Implementation/DialogService.cs index 8e846061..2ab5eb65 100644 --- a/FluentTerminal.App.Services/Implementation/DialogService.cs +++ b/FluentTerminal.App.Services/Implementation/DialogService.cs @@ -11,13 +11,17 @@ public class DialogService : IDialogService private readonly Func _messageDialogFactory; private readonly Func _createKeyBindingDialogFactory; private readonly Func _inputDialogFactory; + private readonly Func _sshConnectionInfoDialogFactory; - public DialogService(Func shellProfileSelectionDialogFactory, Func messageDialogFactory, Func createKeyBindingDialogFactory, Func inputDialogFactory) + public DialogService(Func shellProfileSelectionDialogFactory, + Func messageDialogFactory, Func createKeyBindingDialogFactory, + Func inputDialogFactory, Func sshConnectionInfoDialogFactory) { _shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory; _messageDialogFactory = messageDialogFactory; _createKeyBindingDialogFactory = createKeyBindingDialogFactory; _inputDialogFactory = inputDialogFactory; + _sshConnectionInfoDialogFactory = sshConnectionInfoDialogFactory; } public Task ShowCreateKeyBindingDialog() @@ -70,5 +74,8 @@ public Task ShowProfileSelectionDialogAsync() return dialog.SelectProfile(); } + + public Task ShowSshConnectionInfoDialogAsync() => + _sshConnectionInfoDialogFactory().GetSshConnectionInfoAsync(); } } \ No newline at end of file diff --git a/FluentTerminal.App.ViewModels/MainViewModel.cs b/FluentTerminal.App.ViewModels/MainViewModel.cs index e6d065c3..aed2299d 100644 --- a/FluentTerminal.App.ViewModels/MainViewModel.cs +++ b/FluentTerminal.App.ViewModels/MainViewModel.cs @@ -9,6 +9,7 @@ using System.Collections.Specialized; using System.Linq; using System.Threading.Tasks; +using FluentTerminal.App.Services.Implementation; namespace FluentTerminal.App.ViewModels { @@ -20,6 +21,7 @@ public class MainViewModel : ViewModelBase private readonly IKeyboardCommandService _keyboardCommandService; private readonly ISettingsService _settingsService; private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; + private readonly IDefaultValueProvider _defaultValueProvider; private ApplicationSettings _applicationSettings; private string _background; private double _backgroundOpacity; @@ -28,7 +30,7 @@ public class MainViewModel : ViewModelBase private string _windowTitle; 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.CurrentThemeChanged += OnCurrentThemeChanged; @@ -42,8 +44,10 @@ public class MainViewModel : ViewModelBase ApplicationView = applicationView; _dispatcherTimer = dispatcherTimer; _clipboardService = clipboardService; + _defaultValueProvider = defaultValueProvider; _keyboardCommandService = keyboardCommandService; _keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal()); + _keyboardCommandService.RegisterCommandHandler(nameof(Command.NewRemoteTab), () => AddRemoteTerminal()); _keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddConfigurableTerminal()); _keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle()); _keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab); @@ -214,6 +218,34 @@ public Task AddConfigurableTerminal() }); } + 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() { var profile = _settingsService.GetDefaultShellProfile(); @@ -232,6 +264,7 @@ public void AddTerminal(ShellProfile profile) { var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService, _applicationSettings, profile, ApplicationView, _dispatcherTimer, _clipboardService); + terminal.Closed += OnTerminalClosed; terminal.ShellTitleChanged += Terminal_ShellTitleChanged; terminal.CustomTitleChanged += Terminal_CustomTitleChanged; diff --git a/FluentTerminal.App.ViewModels/SshConnectionInfoViewModel.cs b/FluentTerminal.App.ViewModels/SshConnectionInfoViewModel.cs new file mode 100644 index 00000000..53ca2eac --- /dev/null +++ b/FluentTerminal.App.ViewModels/SshConnectionInfoViewModel.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/FluentTerminal.App/App.xaml.cs b/FluentTerminal.App/App.xaml.cs index 242c8f40..a54c1c50 100644 --- a/FluentTerminal.App/App.xaml.cs +++ b/FluentTerminal.App/App.xaml.cs @@ -1,452 +1,453 @@ -using Autofac; -using CommandLine; -using FluentTerminal.App.Adapters; -using FluentTerminal.App.CommandLineArguments; -using FluentTerminal.App.Dialogs; -using FluentTerminal.App.Services; -using FluentTerminal.App.Services.Adapters; -using FluentTerminal.App.Services.Dialogs; -using FluentTerminal.App.Services.EventArgs; -using FluentTerminal.App.Services.Implementation; -using FluentTerminal.App.ViewModels; -using FluentTerminal.App.Views; -using FluentTerminal.Models; -using FluentTerminal.Models.Enums; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Windows.ApplicationModel; -using Windows.ApplicationModel.Activation; -using Windows.ApplicationModel.AppService; -using Windows.ApplicationModel.Background; -using Windows.ApplicationModel.Core; -using Windows.Storage; -using Windows.UI.Core; -using Windows.UI.ViewManagement; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using IContainer = Autofac.IContainer; - -namespace FluentTerminal.App -{ - public sealed partial class App : Application - { - public TaskCompletionSource _trayReady = new TaskCompletionSource(); - private readonly ISettingsService _settingsService; - private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; - private bool _alreadyLaunched; - private ApplicationSettings _applicationSettings; - private readonly IContainer _container; - private readonly List _mainViewModels; - private SettingsViewModel _settingsViewModel; - private int? _settingsWindowId; - private IAppServiceConnection _appServiceConnection; - private BackgroundTaskDeferral _appServiceDeferral; - private Parser _commandLineParser; - - public App() - { - _mainViewModels = new List(); - - InitializeComponent(); - - UnhandledException += OnUnhandledException; - - var applicationDataContainers = new ApplicationDataContainers - { - LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings), - RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings), - KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)), - ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)), - Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always)) - }; - - var builder = new ContainerBuilder(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().InstancePerDependency(); - builder.RegisterType().InstancePerDependency(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterInstance(applicationDataContainers); - - _container = builder.Build(); - - _settingsService = _container.Resolve(); - _settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged; - - _trayProcessCommunicationService = _container.Resolve(); - - _applicationSettings = _settingsService.GetApplicationSettings(); - - JsonConvert.DefaultSettings = () => - { - var settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - }; - settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy))); - - return settings; - }; - - _commandLineParser = new Parser(settings => - { - settings.CaseSensitive = false; - settings.CaseInsensitiveEnumValues = true; - }); - } - - private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) - { - Logger.Instance.Error(e.Exception, "Unhandled Exception"); - } - - private static IEnumerable SplitArguments(string arguments) - { - var chars = arguments.ToCharArray(); - var inQuote = false; - - for (var i = 0; i < chars.Length; i++) - { - if (chars[i] == '"') - { - inQuote = !inQuote; - } - - if (!inQuote && chars[i] == ' ') - { - chars[i] = '\n'; - } - } - - foreach (var value in new string(chars).Split('\n')) - { - yield return value.Trim('"'); - } - } - - protected override void OnActivated(IActivatedEventArgs args) - { - if (args is CommandLineActivatedEventArgs commandLineActivated) - { - if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments)) - { - return; - } - - _commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb => - { - if (verb is SettingsVerb) - { - await ShowSettings().ConfigureAwait(true); - } - else if (verb is NewVerb newVerb) - { - var profile = default(ShellProfile); - if (!string.IsNullOrWhiteSpace(newVerb.Profile)) - { - profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase)); - } - - if (profile == null) - { - profile = _settingsService.GetDefaultShellProfile(); - } - - if (!string.IsNullOrWhiteSpace(newVerb.Directory)) - { - profile.WorkingDirectory = newVerb.Directory; - } - - var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation - : newVerb.Target == Target.Tab ? NewTerminalLocation.Tab - : NewTerminalLocation.Window; - - await CreateTerminal(profile, location).ConfigureAwait(true); - } - else if (verb is RunVerb runVerb) - { - var profile = new ShellProfile - { - Id = Guid.Empty, - Location = null, - Arguments = runVerb.Command, - WorkingDirectory = runVerb.Directory - }; - - if (!string.IsNullOrWhiteSpace(runVerb.Theme)) - { - var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase)); - if (theme != null) - { - profile.TerminalThemeId = theme.Id; - } - } - - if (string.IsNullOrWhiteSpace(profile.WorkingDirectory)) - { - profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath; - } - - var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation - : runVerb.Target == Target.Tab ? NewTerminalLocation.Tab - : NewTerminalLocation.Window; - - await CreateTerminal(profile, location).ConfigureAwait(true); - } - }); - } - } - - protected override async void OnLaunched(LaunchActivatedEventArgs args) - { - if (!_alreadyLaunched) - { - var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists); - var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log"); - var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists); - var configContent = await FileIO.ReadTextAsync(configFile); - - if (string.IsNullOrWhiteSpace(configContent)) - { - configContent = JsonConvert.SerializeObject(new Logger.Configuration()); - await FileIO.WriteTextAsync(configFile, configContent); - } - - var config = JsonConvert.DeserializeObject(configContent) ?? new Logger.Configuration(); - - Logger.Instance.Initialize(logFile, config); - - var viewModel = _container.Resolve(); - viewModel.AddTerminal(); - await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true); - Window.Current.Activate(); - } - else if (_mainViewModels.Count == 0) - { - await CreateSecondaryView(typeof(MainPage), true).ConfigureAwait(true); - } - } - - protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) - { - base.OnBackgroundActivated(args); - - if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details) - { - if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName) - { - _appServiceDeferral = args.TaskInstance.GetDeferral(); - args.TaskInstance.Canceled += OnTaskCanceled; - - _appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection); - - _trayReady.SetResult(0); - } - } - } - - private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) - { - _appServiceDeferral?.Complete(); - _appServiceDeferral = null; - _appServiceConnection = null; - - Application.Current.Exit(); - } - - private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar) - { - await StartSystemTray().ConfigureAwait(true); - - Frame rootFrame = Window.Current.Content as Frame; - if (rootFrame == null) - { - rootFrame = new Frame(); - Window.Current.Content = rootFrame; - } - - if (rootFrame.Content == null) - { - CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar; - - if (viewModel is MainViewModel mainViewModel) - { - mainViewModel.Closed += OnMainViewModelClosed; - mainViewModel.NewWindowRequested += OnNewWindowRequested; - mainViewModel.ShowSettingsRequested += OnShowSettingsRequested; - mainViewModel.ShowAboutRequested += OnShowAboutRequested; - _mainViewModels.Add(mainViewModel); - } - - rootFrame.Navigate(pageType, viewModel); - } - _alreadyLaunched = true; - Window.Current.Activate(); - } - - private async Task CreateNewTerminalWindow() - { - var viewModel = await CreateSecondaryView(typeof(MainPage), true).ConfigureAwait(true); - viewModel.Closed += OnMainViewModelClosed; - viewModel.NewWindowRequested += OnNewWindowRequested; - viewModel.ShowSettingsRequested += OnShowSettingsRequested; - viewModel.ShowAboutRequested += OnShowAboutRequested; - _mainViewModels.Add(viewModel); - - return viewModel; - } - - private async Task CreateSecondaryView(Type pageType, bool ExtendViewIntoTitleBar) - { - int windowId = 0; - TViewModel viewModel = default; - await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - viewModel = _container.Resolve(); - - CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar; - var frame = new Frame(); - frame.Navigate(pageType, viewModel); - Window.Current.Content = frame; - Window.Current.Activate(); - - windowId = ApplicationView.GetForCurrentView().Id; - }); - - if (viewModel is SettingsViewModel settingsViewModel) - { - _settingsViewModel = settingsViewModel; - _settingsViewModel.Closed += OnSettingsClosed; - _settingsWindowId = windowId; - } - - await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId); - - return viewModel; - } - - private void OnApplicationSettingsChanged(object sender, ApplicationSettings e) - { - _applicationSettings = e; - } - - private void OnMainViewModelClosed(object sender, EventArgs e) - { - if (sender is MainViewModel viewModel) - { - Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id); - - viewModel.Closed -= OnMainViewModelClosed; - viewModel.NewWindowRequested -= OnNewWindowRequested; - viewModel.ShowSettingsRequested -= OnShowSettingsRequested; - viewModel.ShowAboutRequested -= OnShowAboutRequested; - - _mainViewModels.Remove(viewModel); - } - } - - private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e) - { - var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true); - - if (e.ShowProfileSelection) - { - await viewModel.AddConfigurableTerminal().ConfigureAwait(true); - } - else - { - viewModel.AddTerminal(); - } - } - - private void OnSettingsClosed(object sender, EventArgs e) - { - _settingsViewModel.Closed -= OnSettingsClosed; - _settingsViewModel = null; - _settingsWindowId = null; - } - - private void OnShowAboutRequested(object sender, EventArgs e) - { - ShowAbout().ConfigureAwait(true); - } - - private async void OnShowSettingsRequested(object sender, EventArgs e) - { - await ShowSettings().ConfigureAwait(true); - } - - private async Task ShowAbout() - { - await ShowSettings().ConfigureAwait(true); - _settingsViewModel.NavigateToAboutPage(); - } - - private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location) - { - if (!_alreadyLaunched) - { - var viewModel = _container.Resolve(); - viewModel.AddTerminal(profile); - await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true); - } - else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0) - { - _mainViewModels.Last().AddTerminal(profile); - } - else - { - var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true); - viewModel.AddTerminal(profile); - } - } - - private async Task ShowSettings() - { - if (!_alreadyLaunched) - { - var viewModel = _container.Resolve(); - await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true); - } - else if (_settingsViewModel == null) - { - await CreateSecondaryView(typeof(SettingsPage), true).ConfigureAwait(true); - } - else - { - await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value); - } - } - - private async Task StartSystemTray() - { - var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask(); - await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true); - _trayProcessCommunicationService.Initialize(_appServiceConnection); - } - } +using Autofac; +using CommandLine; +using FluentTerminal.App.Adapters; +using FluentTerminal.App.CommandLineArguments; +using FluentTerminal.App.Dialogs; +using FluentTerminal.App.Services; +using FluentTerminal.App.Services.Adapters; +using FluentTerminal.App.Services.Dialogs; +using FluentTerminal.App.Services.EventArgs; +using FluentTerminal.App.Services.Implementation; +using FluentTerminal.App.ViewModels; +using FluentTerminal.App.Views; +using FluentTerminal.Models; +using FluentTerminal.Models.Enums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.ApplicationModel.AppService; +using Windows.ApplicationModel.Background; +using Windows.ApplicationModel.Core; +using Windows.Storage; +using Windows.UI.Core; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using IContainer = Autofac.IContainer; + +namespace FluentTerminal.App +{ + public sealed partial class App : Application + { + public TaskCompletionSource _trayReady = new TaskCompletionSource(); + private readonly ISettingsService _settingsService; + private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; + private bool _alreadyLaunched; + private ApplicationSettings _applicationSettings; + private readonly IContainer _container; + private readonly List _mainViewModels; + private SettingsViewModel _settingsViewModel; + private int? _settingsWindowId; + private IAppServiceConnection _appServiceConnection; + private BackgroundTaskDeferral _appServiceDeferral; + private Parser _commandLineParser; + + public App() + { + _mainViewModels = new List(); + + InitializeComponent(); + + UnhandledException += OnUnhandledException; + + var applicationDataContainers = new ApplicationDataContainers + { + LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings), + RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings), + KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)), + ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)), + Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always)) + }; + + var builder = new ContainerBuilder(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); + builder.RegisterType().InstancePerDependency(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterInstance(applicationDataContainers); + + _container = builder.Build(); + + _settingsService = _container.Resolve(); + _settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged; + + _trayProcessCommunicationService = _container.Resolve(); + + _applicationSettings = _settingsService.GetApplicationSettings(); + + JsonConvert.DefaultSettings = () => + { + var settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + }; + settings.Converters.Add(new StringEnumConverter(typeof(CamelCaseNamingStrategy))); + + return settings; + }; + + _commandLineParser = new Parser(settings => + { + settings.CaseSensitive = false; + settings.CaseInsensitiveEnumValues = true; + }); + } + + private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) + { + Logger.Instance.Error(e.Exception, "Unhandled Exception"); + } + + private static IEnumerable SplitArguments(string arguments) + { + var chars = arguments.ToCharArray(); + var inQuote = false; + + for (var i = 0; i < chars.Length; i++) + { + if (chars[i] == '"') + { + inQuote = !inQuote; + } + + if (!inQuote && chars[i] == ' ') + { + chars[i] = '\n'; + } + } + + foreach (var value in new string(chars).Split('\n')) + { + yield return value.Trim('"'); + } + } + + protected override void OnActivated(IActivatedEventArgs args) + { + if (args is CommandLineActivatedEventArgs commandLineActivated) + { + if (string.IsNullOrWhiteSpace(commandLineActivated.Operation.Arguments)) + { + return; + } + + _commandLineParser.ParseArguments(SplitArguments(commandLineActivated.Operation.Arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)).WithParsed(async verb => + { + if (verb is SettingsVerb) + { + await ShowSettings().ConfigureAwait(true); + } + else if (verb is NewVerb newVerb) + { + var profile = default(ShellProfile); + if (!string.IsNullOrWhiteSpace(newVerb.Profile)) + { + profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase)); + } + + if (profile == null) + { + profile = _settingsService.GetDefaultShellProfile(); + } + + if (!string.IsNullOrWhiteSpace(newVerb.Directory)) + { + profile.WorkingDirectory = newVerb.Directory; + } + + var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation + : newVerb.Target == Target.Tab ? NewTerminalLocation.Tab + : NewTerminalLocation.Window; + + await CreateTerminal(profile, location).ConfigureAwait(true); + } + else if (verb is RunVerb runVerb) + { + var profile = new ShellProfile + { + Id = Guid.Empty, + Location = null, + Arguments = runVerb.Command, + WorkingDirectory = runVerb.Directory + }; + + if (!string.IsNullOrWhiteSpace(runVerb.Theme)) + { + var theme = _settingsService.GetThemes().FirstOrDefault(x => x.Name.Equals(runVerb.Theme, StringComparison.CurrentCultureIgnoreCase)); + if (theme != null) + { + profile.TerminalThemeId = theme.Id; + } + } + + if (string.IsNullOrWhiteSpace(profile.WorkingDirectory)) + { + profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath; + } + + var location = runVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation + : runVerb.Target == Target.Tab ? NewTerminalLocation.Tab + : NewTerminalLocation.Window; + + await CreateTerminal(profile, location).ConfigureAwait(true); + } + }); + } + } + + protected override async void OnLaunched(LaunchActivatedEventArgs args) + { + if (!_alreadyLaunched) + { + var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists); + var logFile = Path.Combine(logDirectory.Path, "fluentterminal.app.log"); + var configFile = await logDirectory.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists); + var configContent = await FileIO.ReadTextAsync(configFile); + + if (string.IsNullOrWhiteSpace(configContent)) + { + configContent = JsonConvert.SerializeObject(new Logger.Configuration()); + await FileIO.WriteTextAsync(configFile, configContent); + } + + var config = JsonConvert.DeserializeObject(configContent) ?? new Logger.Configuration(); + + Logger.Instance.Initialize(logFile, config); + + var viewModel = _container.Resolve(); + viewModel.AddTerminal(); + await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true); + Window.Current.Activate(); + } + else if (_mainViewModels.Count == 0) + { + await CreateSecondaryView(typeof(MainPage), true).ConfigureAwait(true); + } + } + + protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) + { + base.OnBackgroundActivated(args); + + if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details) + { + if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName) + { + _appServiceDeferral = args.TaskInstance.GetDeferral(); + args.TaskInstance.Canceled += OnTaskCanceled; + + _appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection); + + _trayReady.SetResult(0); + } + } + } + + private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) + { + _appServiceDeferral?.Complete(); + _appServiceDeferral = null; + _appServiceConnection = null; + + Application.Current.Exit(); + } + + private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar) + { + await StartSystemTray().ConfigureAwait(true); + + Frame rootFrame = Window.Current.Content as Frame; + if (rootFrame == null) + { + rootFrame = new Frame(); + Window.Current.Content = rootFrame; + } + + if (rootFrame.Content == null) + { + CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar; + + if (viewModel is MainViewModel mainViewModel) + { + mainViewModel.Closed += OnMainViewModelClosed; + mainViewModel.NewWindowRequested += OnNewWindowRequested; + mainViewModel.ShowSettingsRequested += OnShowSettingsRequested; + mainViewModel.ShowAboutRequested += OnShowAboutRequested; + _mainViewModels.Add(mainViewModel); + } + + rootFrame.Navigate(pageType, viewModel); + } + _alreadyLaunched = true; + Window.Current.Activate(); + } + + private async Task CreateNewTerminalWindow() + { + var viewModel = await CreateSecondaryView(typeof(MainPage), true).ConfigureAwait(true); + viewModel.Closed += OnMainViewModelClosed; + viewModel.NewWindowRequested += OnNewWindowRequested; + viewModel.ShowSettingsRequested += OnShowSettingsRequested; + viewModel.ShowAboutRequested += OnShowAboutRequested; + _mainViewModels.Add(viewModel); + + return viewModel; + } + + private async Task CreateSecondaryView(Type pageType, bool ExtendViewIntoTitleBar) + { + int windowId = 0; + TViewModel viewModel = default; + await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + viewModel = _container.Resolve(); + + CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar; + var frame = new Frame(); + frame.Navigate(pageType, viewModel); + Window.Current.Content = frame; + Window.Current.Activate(); + + windowId = ApplicationView.GetForCurrentView().Id; + }); + + if (viewModel is SettingsViewModel settingsViewModel) + { + _settingsViewModel = settingsViewModel; + _settingsViewModel.Closed += OnSettingsClosed; + _settingsWindowId = windowId; + } + + await ApplicationViewSwitcher.TryShowAsStandaloneAsync(windowId); + + return viewModel; + } + + private void OnApplicationSettingsChanged(object sender, ApplicationSettings e) + { + _applicationSettings = e; + } + + private void OnMainViewModelClosed(object sender, EventArgs e) + { + if (sender is MainViewModel viewModel) + { + Logger.Instance.Debug("MainViewModel with ApplicationView Id: {@id} closed.", viewModel.ApplicationView.Id); + + viewModel.Closed -= OnMainViewModelClosed; + viewModel.NewWindowRequested -= OnNewWindowRequested; + viewModel.ShowSettingsRequested -= OnShowSettingsRequested; + viewModel.ShowAboutRequested -= OnShowAboutRequested; + + _mainViewModels.Remove(viewModel); + } + } + + private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e) + { + var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true); + + if (e.ShowProfileSelection) + { + await viewModel.AddConfigurableTerminal().ConfigureAwait(true); + } + else + { + viewModel.AddTerminal(); + } + } + + private void OnSettingsClosed(object sender, EventArgs e) + { + _settingsViewModel.Closed -= OnSettingsClosed; + _settingsViewModel = null; + _settingsWindowId = null; + } + + private void OnShowAboutRequested(object sender, EventArgs e) + { + ShowAbout().ConfigureAwait(true); + } + + private async void OnShowSettingsRequested(object sender, EventArgs e) + { + await ShowSettings().ConfigureAwait(true); + } + + private async Task ShowAbout() + { + await ShowSettings().ConfigureAwait(true); + _settingsViewModel.NavigateToAboutPage(); + } + + private async Task CreateTerminal(ShellProfile profile, NewTerminalLocation location) + { + if (!_alreadyLaunched) + { + var viewModel = _container.Resolve(); + viewModel.AddTerminal(profile); + await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true); + } + else if (location == NewTerminalLocation.Tab && _mainViewModels.Count > 0) + { + _mainViewModels.Last().AddTerminal(profile); + } + else + { + var viewModel = await CreateNewTerminalWindow().ConfigureAwait(true); + viewModel.AddTerminal(profile); + } + } + + private async Task ShowSettings() + { + if (!_alreadyLaunched) + { + var viewModel = _container.Resolve(); + await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true); + } + else if (_settingsViewModel == null) + { + await CreateSecondaryView(typeof(SettingsPage), true).ConfigureAwait(true); + } + else + { + await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value); + } + } + + private async Task StartSystemTray() + { + var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask(); + await Task.WhenAll(launch, _trayReady.Task).ConfigureAwait(true); + _trayProcessCommunicationService.Initialize(_appServiceConnection); + } + } } \ No newline at end of file diff --git a/FluentTerminal.App/Dialogs/SshInfoDialog.xaml b/FluentTerminal.App/Dialogs/SshInfoDialog.xaml new file mode 100644 index 00000000..ef217f9b --- /dev/null +++ b/FluentTerminal.App/Dialogs/SshInfoDialog.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/FluentTerminal.App/Dialogs/SshInfoDialog.xaml.cs b/FluentTerminal.App/Dialogs/SshInfoDialog.xaml.cs new file mode 100644 index 00000000..82dce598 --- /dev/null +++ b/FluentTerminal.App/Dialogs/SshInfoDialog.xaml.cs @@ -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 GetSshConnectionInfoAsync() + { + ContentDialogResult result = await ShowAsync(); + + return result == ContentDialogResult.Primary ? (ISshConnectionInfo)DataContext : null; + } + } +} diff --git a/FluentTerminal.App/FluentTerminal.App.csproj b/FluentTerminal.App/FluentTerminal.App.csproj index fe4994aa..4299e5df 100644 --- a/FluentTerminal.App/FluentTerminal.App.csproj +++ b/FluentTerminal.App/FluentTerminal.App.csproj @@ -1,440 +1,445 @@ - - - - - Debug - x86 - {41020100-3414-4E0A-9A8E-92D3C4179DF8} - AppContainerExe - Properties - FluentTerminal.App - FluentTerminal.App - en-US - UAP - 10.0.16299.0 - 10.0.16299.0 - 14 - 512 - {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - FluentTerminal.App_TemporaryKey.pfx - F96F3E0E485F07E8E98DA6E5C06AA4B6F112B498 - False - Always - x86|x64 - False - 1 - OnApplicationRun - True - - - true - bin\x86\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - x86 - false - prompt - true - 7.1 - - - bin\x86\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - x86 - false - prompt - true - true - 7.1 - - - true - bin\ARM\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - ARM - false - prompt - true - - - bin\ARM\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - ARM - false - prompt - true - true - - - true - bin\x64\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - ;2008 - full - x64 - false - prompt - true - 7.1 - - - bin\x64\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - ;2008 - pdbonly - x64 - false - prompt - true - true - 7.1 - - - PackageReference - - - - - - - App.xaml - - - - - - - - - - - - - - - - - - - - - - - - InputDialog.xaml - - - ShellProfileSelectionDialog.xaml - - - - - - - - - - KeyBindingsView.xaml - - - MainPage.xaml - - - - CreateKeyBindingDialog.xaml - - - SettingsPage.xaml - - - About.xaml - - - MouseSettings.xaml - - - ThemeSettings.xaml - - - TerminalSettings.xaml - - - GeneralSettings.xaml - - - KeyBindingSettings.xaml - - - ShellProfileSettings.xaml - - - TabBar.xaml - - - - - - TerminalColorPicker.xaml - - - - - - TerminalView.xaml - - - XtermTerminalView.xaml - - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - PreserveNewest - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - - - 4.9.1 - - - 2.4.3 - - - 6.2.8 - - - 5.1.0 - - - 5.1.0 - - - 2.0.0 - - - 12.0.1 - - - 1.21.0 - - - - - Windows Desktop Extensions for the UWP - - - - - {a1ad7385-f92e-46ac-a349-43dc99b1e72d} - FluentTerminal.App.Services - - - {44e8e225-f46c-47ec-9290-ffa08fccf1b5} - FluentTerminal.App.ViewModels - - - {5EA40453-A0AA-449E-8406-D6EA51D7E70E} - FluentTerminal.Models - - - {BAAE6239-343F-454C-977F-F81F91715961} - FluentTerminal.RuntimeComponent - - - - - 14.0 - - - - - - - - xcopy /y "$(SolutionDir)FluentTerminal.Client\dist" "$(ProjectDir)Client\" - + + + + + Debug + x86 + {41020100-3414-4E0A-9A8E-92D3C4179DF8} + AppContainerExe + Properties + FluentTerminal.App + FluentTerminal.App + en-US + UAP + 10.0.16299.0 + 10.0.16299.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + FluentTerminal.App_TemporaryKey.pfx + F96F3E0E485F07E8E98DA6E5C06AA4B6F112B498 + False + Always + x86|x64 + False + 1 + OnApplicationRun + True + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + 7.1 + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + 7.1 + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + 7.1 + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + 7.1 + + + PackageReference + + + + + + + App.xaml + + + + + + + + + + + + + + + + + + + + + + + + InputDialog.xaml + + + ShellProfileSelectionDialog.xaml + + + + + + + + + + KeyBindingsView.xaml + + + MainPage.xaml + + + + CreateKeyBindingDialog.xaml + + + SettingsPage.xaml + + + About.xaml + + + MouseSettings.xaml + + + ThemeSettings.xaml + + + TerminalSettings.xaml + + + GeneralSettings.xaml + + + KeyBindingSettings.xaml + + + ShellProfileSettings.xaml + + + SshInfoDialog.xaml + + + TabBar.xaml + + + + + + TerminalColorPicker.xaml + + + + + + TerminalView.xaml + + + XtermTerminalView.xaml + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + PreserveNewest + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + 4.9.1 + + + 2.4.3 + + + 6.2.8 + + + 5.1.0 + + + 5.1.0 + + + 2.0.0 + + + 12.0.1 + + + 1.21.0 + + + + + Windows Desktop Extensions for the UWP + + + + + {a1ad7385-f92e-46ac-a349-43dc99b1e72d} + FluentTerminal.App.Services + + + {44e8e225-f46c-47ec-9290-ffa08fccf1b5} + FluentTerminal.App.ViewModels + + + {5EA40453-A0AA-449E-8406-D6EA51D7E70E} + FluentTerminal.Models + + + {BAAE6239-343F-454C-977F-F81F91715961} + FluentTerminal.RuntimeComponent + + + + + 14.0 + + + + + + + + xcopy /y "$(SolutionDir)FluentTerminal.Client\dist" "$(ProjectDir)Client\" + + --> \ No newline at end of file diff --git a/FluentTerminal.Models/Enums/Command.cs b/FluentTerminal.Models/Enums/Command.cs index ddab658e..d4dc1047 100644 --- a/FluentTerminal.Models/Enums/Command.cs +++ b/FluentTerminal.Models/Enums/Command.cs @@ -19,6 +19,9 @@ public enum Command [Description("Configurable new tab")] ConfigurableNewTab, + [Description("New remote tab")] + NewRemoteTab, + [Description("Change tab title")] ChangeTabTitle,