Compare commits

..
73 changed files with 5320 additions and 3536 deletions

No files matched your search

+3 -3
View File
@@ -13,11 +13,11 @@ jobs:
- name: Build client - name: Build client
working-directory: ./FluentTerminal.Client working-directory: ./FluentTerminal.Client
run: | run: |
yarn npm install
yarn build npm run build
- name: Setup MSBuild.exe - name: Setup MSBuild.exe
uses: warrenbuckley/Setup-MSBuild@v1 uses: warrenbuckley/Setup-MSBuild@v1
- name: MSBuild - name: MSBuild
run: msbuild FluentTerminal.sln -t:build -p:Configuration=Debug -p:Platform=x64 -p:AppxPackage=false -m -restore run: msbuild FluentTerminal.sln -t:build -p:Configuration=Debug -p:Platform=x64 -m -restore
@@ -1,23 +0,0 @@
using System;
namespace FluentTerminal.App.Services.Exceptions
{
public class ReadTextFileException : Exception
{
public ReadTextFileException() : base()
{
}
public ReadTextFileException(string message) : base(message)
{
}
public ReadTextFileException(string message, Exception innerException) : base(message, innerException)
{
}
protected ReadTextFileException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
}
}
@@ -0,0 +1,12 @@
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services
{
// This validator is needed because accelerator key has to be member of VirtualKey enum.
public interface IAcceleratorKeyValidator
{
bool Valid(int key);
bool Valid(ExtendedVirtualKey key);
}
}
@@ -35,8 +35,6 @@ namespace FluentTerminal.App.Services
Task SaveTextFileAsync(string path, string content); Task SaveTextFileAsync(string path, string content);
Task<string> ReadTextFileAsync(string path);
Task<string> GetSshConfigDirAsync(); Task<string> GetSshConfigDirAsync();
Task<string[]> GetFilesFromSshConfigDirAsync(); Task<string[]> GetFilesFromSshConfigDirAsync();
@@ -1,4 +1,5 @@
using FluentTerminal.Models; using FluentTerminal.App.Services.Utilities;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums; using FluentTerminal.Models.Enums;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -114,18 +115,6 @@ namespace FluentTerminal.App.Services.Implementation
} }
}; };
case Command.ReconnectTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ReconnectTab),
Ctrl = true,
Alt = true,
Key = (int)ExtendedVirtualKey.R
}
};
case Command.NewTab: case Command.NewTab:
return new List<KeyBinding> return new List<KeyBinding>
{ {
@@ -230,7 +219,8 @@ namespace FluentTerminal.App.Services.Implementation
Command = nameof(Command.ShowSettings), Command = nameof(Command.ShowSettings),
Ctrl = true, Ctrl = true,
Shift = true, Shift = true,
Key = (int)ExtendedVirtualKey.Comma // I've had to replace ExtendedVirtualKey.Comma because it isn't a member of VirtualKey enum, thus cannot be shown in menu.
Key = (int)ExtendedVirtualKey.S
} }
}; };
@@ -340,7 +330,7 @@ namespace FluentTerminal.App.Services.Implementation
new TabTheme new TabTheme
{ {
Id = 0, Id = 0,
Name = "TabTheme.None", Name = I18N.Translate("TabTheme.None"),
BackgroundOpacity = double.NaN, BackgroundOpacity = double.NaN,
BackgroundPointerOverOpacity = double.NaN, BackgroundPointerOverOpacity = double.NaN,
BackgroundPressedOpacity = double.NaN, BackgroundPressedOpacity = double.NaN,
@@ -351,37 +341,37 @@ namespace FluentTerminal.App.Services.Implementation
new TabTheme new TabTheme
{ {
Id = 1, Id = 1,
Name = "TabTheme.Red", Name = I18N.Translate("TabTheme.Red"),
Color = "#E81123" Color = "#E81123"
}, },
new TabTheme new TabTheme
{ {
Id = 2, Id = 2,
Name = "TabTheme.Green", Name = I18N.Translate("TabTheme.Green"),
Color = "#10893E" Color = "#10893E"
}, },
new TabTheme new TabTheme
{ {
Id = 3, Id = 3,
Name = "TabTheme.Blue", Name = I18N.Translate("TabTheme.Blue"),
Color = "#0078D7" Color = "#0078D7"
}, },
new TabTheme new TabTheme
{ {
Id = 4, Id = 4,
Name = "TabTheme.Purple", Name = I18N.Translate("TabTheme.Purple"),
Color = "#881798" Color = "#881798"
}, },
new TabTheme new TabTheme
{ {
Id = 5, Id = 5,
Name = "TabTheme.Orange", Name = I18N.Translate("TabTheme.Orange"),
Color = "#FF8C00" Color = "#FF8C00"
}, },
new TabTheme new TabTheme
{ {
Id = 6, Id = 6,
Name = "TabTheme.Teal", Name = I18N.Translate("TabTheme.Teal"),
Color = "#00B7C3" Color = "#00B7C3"
} }
}; };
@@ -71,7 +71,7 @@ namespace FluentTerminal.App.Services.Implementation
config.Themes.Add(theme); config.Themes.Add(theme);
} }
foreach (var profile in GetShellProfiles()) foreach (var profile in GetShellProfiles().Where(x => !x.PreInstalled))
{ {
config.Profiles.Add(profile); config.Profiles.Add(profile);
} }
@@ -117,18 +117,21 @@ namespace FluentTerminal.App.Services.Implementation
SaveTheme(theme, existingTheme != null); SaveTheme(theme, existingTheme != null);
} }
foreach (var profile in config.Profiles) // Can't create pre-installed profiles
foreach (var profile in config.Profiles.Where(x => !x.PreInstalled))
{ {
var existingProfile = GetShellProfile(profile.Id); var existingProfile = GetShellProfile(profile.Id);
var isNew = existingProfile == default; var isNew = existingProfile.EqualTo(default);
// You can only edit certain parts of preinstalled profiles
if (!isNew && existingProfile.PreInstalled) if (!isNew && existingProfile.PreInstalled)
{ {
// those cannot be edited existingProfile.WorkingDirectory = profile.WorkingDirectory;
profile.Name = existingProfile.Name; existingProfile.Arguments = profile.Arguments;
profile.Location = existingProfile.Location; existingProfile.TabThemeId = profile.TabThemeId;
existingProfile.TerminalThemeId = profile.TerminalThemeId;
SaveShellProfile(profile, false); existingProfile.KeyBindings = profile.KeyBindings;
SaveShellProfile(profile, isNew);
continue; continue;
} }
SaveShellProfile(profile, isNew); SaveShellProfile(profile, isNew);
@@ -137,7 +140,7 @@ namespace FluentTerminal.App.Services.Implementation
foreach (var profile in config.SshProfiles) foreach (var profile in config.SshProfiles)
{ {
var existingProfile = GetSshProfile(profile.Id); var existingProfile = GetSshProfile(profile.Id);
var isNew = existingProfile == default; var isNew = existingProfile.EqualTo(default(SshProfile));
SaveSshProfile(profile, isNew); SaveSshProfile(profile, isNew);
} }
@@ -84,18 +84,6 @@ namespace FluentTerminal.App.Services.Implementation
} }
} }
public async Task<string> ReadTextFileAsync(string path)
{
var response = await GetResponseAsync<StringValueResponse>(new ReadTextFileRequest { Path = path }).ConfigureAwait(false);
if (!response.Success)
{
throw new ReadTextFileException(string.IsNullOrWhiteSpace(response.Error) ? "Failed to read the file." : response.Error);
}
return response.Value;
}
public async Task<string> GetSshConfigDirAsync() public async Task<string> GetSshConfigDirAsync()
{ {
if (!string.IsNullOrEmpty(_sshConfigDir)) if (!string.IsNullOrEmpty(_sshConfigDir))
-10
View File
@@ -24,12 +24,6 @@ namespace FluentTerminal.App.Services
_requireShellProcessStart = !terminalId.HasValue; _requireShellProcessStart = !terminalId.HasValue;
} }
public void Reconnect()
{
_exited = false;
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
}
private void OnTerminalExited(object sender, TerminalExitStatus status) private void OnTerminalExited(object sender, TerminalExitStatus status)
{ {
if (status.TerminalId != Id) if (status.TerminalId != Id)
@@ -77,8 +71,6 @@ namespace FluentTerminal.App.Services
public byte Id { get; } public byte Id { get; }
public ShellProfile Profile { get; private set; }
/// <summary> /// <summary>
/// To be called by either view or viewmodel /// To be called by either view or viewmodel
/// </summary> /// </summary>
@@ -145,8 +137,6 @@ namespace FluentTerminal.App.Services
/// </summary> /// </summary>
public async Task<TerminalResponse> StartShellProcessAsync(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState) public async Task<TerminalResponse> StartShellProcessAsync(ShellProfile shellProfile, TerminalSize size, SessionType sessionType, string termState)
{ {
Profile = shellProfile;
if (!_requireShellProcessStart && !string.IsNullOrEmpty(termState)) if (!_requireShellProcessStart && !string.IsNullOrEmpty(termState))
{ {
OutputReceived?.Invoke(this, Encoding.UTF8.GetBytes(termState)); OutputReceived?.Invoke(this, Encoding.UTF8.GetBytes(termState));
@@ -10,7 +10,6 @@ namespace FluentTerminal.App.ViewModels
Task ChangeKeyBindingsAsync(); Task ChangeKeyBindingsAsync();
Task ChangeOptionsAsync(TerminalOptions options); Task ChangeOptionsAsync(TerminalOptions options);
Task InitializeAsync(TerminalViewModel viewModel); Task InitializeAsync(TerminalViewModel viewModel);
Task ReconnectAsync();
void DisposalPrepare(); void DisposalPrepare();
Task FindNextAsync(SearchRequest request); Task FindNextAsync(SearchRequest request);
Task FindPreviousAsync(SearchRequest request); Task FindPreviousAsync(SearchRequest request);
+18 -6
View File
@@ -28,6 +28,7 @@ namespace FluentTerminal.App.ViewModels
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private readonly ICommandHistoryService _commandHistoryService; private readonly ICommandHistoryService _commandHistoryService;
private readonly IAcceleratorKeyValidator _acceleratorKeyValidator;
private ApplicationSettings _applicationSettings; private ApplicationSettings _applicationSettings;
private bool _useAcrylicBackground; private bool _useAcrylicBackground;
private double _backgroundOpacity; private double _backgroundOpacity;
@@ -37,7 +38,7 @@ namespace FluentTerminal.App.ViewModels
private IDictionary<string, ICollection<KeyBinding>> _keyBindings; private IDictionary<string, ICollection<KeyBinding>> _keyBindings;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService, public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
IApplicationView applicationView, IClipboardService clipboardService, ICommandHistoryService commandHistoryService) IApplicationView applicationView, IClipboardService clipboardService, ICommandHistoryService commandHistoryService, IAcceleratorKeyValidator acceleratorKeyValidator)
{ {
MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged); MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
MessengerInstance.Register<ShellProfileAddedMessage>(this, OnShellProfileAdded); MessengerInstance.Register<ShellProfileAddedMessage>(this, OnShellProfileAdded);
@@ -56,6 +57,7 @@ namespace FluentTerminal.App.ViewModels
_clipboardService = clipboardService; _clipboardService = clipboardService;
_keyboardCommandService = keyboardCommandService; _keyboardCommandService = keyboardCommandService;
_commandHistoryService = commandHistoryService; _commandHistoryService = commandHistoryService;
_acceleratorKeyValidator = acceleratorKeyValidator;
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), async () => await AddDefaultProfileAsync(NewTerminalLocation.Tab)); _keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), async () => await AddDefaultProfileAsync(NewTerminalLocation.Tab));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), async () => await AddDefaultProfileAsync(NewTerminalLocation.Window)); _keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), async () => await AddDefaultProfileAsync(NewTerminalLocation.Window));
@@ -69,7 +71,6 @@ namespace FluentTerminal.App.ViewModels
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), async () => await SelectedTerminal.EditTitleAsync()); _keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), async () => await SelectedTerminal.EditTitleAsync());
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab); _keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.DuplicateTab), async () => await AddTabAsync(SelectedTerminal.ShellProfile.Clone())); _keyboardCommandService.RegisterCommandHandler(nameof(Command.DuplicateTab), async () => await AddTabAsync(SelectedTerminal.ShellProfile.Clone()));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ReconnectTab), async () => { if (SelectedTerminal.ReconnectTabCommand.CanExecute()) await SelectedTerminal.ReconnectTabAsync(); });
// Add all of the commands for switching to a tab of a given ID, if there's one open there // Add all of the commands for switching to a tab of a given ID, if there's one open there
for (int i = 0; i < 9; i++) for (int i = 0; i < 9; i++)
@@ -675,7 +676,8 @@ namespace FluentTerminal.App.ViewModels
icon: Mdl2Icon.Settings()); icon: Mdl2Icon.Settings());
if (_keyBindings.TryGetValue(nameof(Command.ShowSettings), out var keyBindings) && if (_keyBindings.TryGetValue(nameof(Command.ShowSettings), out var keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding settingsKeyBinding) keyBindings.FirstOrDefault() is KeyBinding settingsKeyBinding &&
_acceleratorKeyValidator.Valid(settingsKeyBinding.Key))
{ {
LoadKeyBindingsFromModel(settingsItem, settingsKeyBinding); LoadKeyBindingsFromModel(settingsItem, settingsKeyBinding);
} }
@@ -717,7 +719,8 @@ namespace FluentTerminal.App.ViewModels
var defaultProfileCommand = tab ? nameof(Command.NewTab) : nameof(Command.NewWindow); var defaultProfileCommand = tab ? nameof(Command.NewTab) : nameof(Command.NewWindow);
if (_keyBindings.TryGetValue(defaultProfileCommand, out var kbs) && if (_keyBindings.TryGetValue(defaultProfileCommand, out var kbs) &&
kbs.FirstOrDefault() is KeyBinding tabKeyBindings) kbs.FirstOrDefault() is KeyBinding tabKeyBindings &&
_acceleratorKeyValidator.Valid(tabKeyBindings.Key))
{ {
LoadKeyBindingsFromModel(defaultProfileItem, tabKeyBindings); LoadKeyBindingsFromModel(defaultProfileItem, tabKeyBindings);
} }
@@ -739,7 +742,8 @@ namespace FluentTerminal.App.ViewModels
var command = tab ? nameof(Command.NewSshTab) : nameof(Command.NewSshWindow); var command = tab ? nameof(Command.NewSshTab) : nameof(Command.NewSshWindow);
if (_keyBindings.TryGetValue(command, out var keyBindings) && if (_keyBindings.TryGetValue(command, out var keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding remoteTabKeyBinding) keyBindings.FirstOrDefault() is KeyBinding remoteTabKeyBinding &&
_acceleratorKeyValidator.Valid(remoteTabKeyBinding.Key))
{ {
LoadKeyBindingsFromModel(remoteConnectItem, remoteTabKeyBinding); LoadKeyBindingsFromModel(remoteConnectItem, remoteTabKeyBinding);
} }
@@ -760,7 +764,8 @@ namespace FluentTerminal.App.ViewModels
command = tab ? nameof(Command.NewCustomCommandTab) : nameof(Command.NewCustomCommandWindow); command = tab ? nameof(Command.NewCustomCommandTab) : nameof(Command.NewCustomCommandWindow);
if (_keyBindings.TryGetValue(command, out keyBindings) && if (_keyBindings.TryGetValue(command, out keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding quickTabKeyBinding) keyBindings.FirstOrDefault() is KeyBinding quickTabKeyBinding &&
_acceleratorKeyValidator.Valid(quickTabKeyBinding.Key))
{ {
LoadKeyBindingsFromModel(quickLaunchItem, quickTabKeyBinding); LoadKeyBindingsFromModel(quickLaunchItem, quickTabKeyBinding);
} }
@@ -789,6 +794,13 @@ namespace FluentTerminal.App.ViewModels
private void LoadKeyBindingsFromModel(MenuItemViewModel menuItemViewModel, KeyBinding keyBinding) private void LoadKeyBindingsFromModel(MenuItemViewModel menuItemViewModel, KeyBinding keyBinding)
{ {
if (!_acceleratorKeyValidator.Valid(keyBinding.Key))
{
menuItemViewModel.KeyBinding = null;
return;
}
if (menuItemViewModel.KeyBinding == null) if (menuItemViewModel.KeyBinding == null)
{ {
menuItemViewModel.KeyBinding = new MenuItemKeyBindingViewModel(keyBinding); menuItemViewModel.KeyBinding = new MenuItemKeyBindingViewModel(keyBinding);
@@ -1,15 +1,12 @@
using FluentTerminal.App.Services.Utilities; using FluentTerminal.Models;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using Windows.System;
namespace FluentTerminal.App.ViewModels.Menu namespace FluentTerminal.App.ViewModels.Menu
{ {
public class MenuItemKeyBindingViewModel : ViewModelBase public class MenuItemKeyBindingViewModel : ViewModelBase
{ {
#region Properties
private int _key; private int _key;
public int Key public int Key
@@ -83,21 +80,9 @@ namespace FluentTerminal.App.ViewModels.Menu
} }
} }
public bool IsExtendedVirtualKey => !Enum.IsDefined(typeof(VirtualKey), Key); #endregion Properties
public string GetOverrideText() #region Constructors
{
var segments = new List<string>();
if (Ctrl) segments.Add("Ctrl");
if (Alt) segments.Add("Alt");
if (Windows) segments.Add("Windows");
if (Shift) segments.Add("Shift");
segments.Add(EnumHelper.GetEnumDescription((ExtendedVirtualKey)Key));
return string.Join("+", segments);
}
public MenuItemKeyBindingViewModel(int key = 0, bool ctrl = false, bool alt = false, bool shift = false, public MenuItemKeyBindingViewModel(int key = 0, bool ctrl = false, bool alt = false, bool shift = false,
bool windows = false) bool windows = false)
@@ -116,7 +101,9 @@ namespace FluentTerminal.App.ViewModels.Menu
{ {
} }
#endregion Constructors
#region Methods
private void SetKeyModifiers() private void SetKeyModifiers()
{ {
@@ -155,5 +142,6 @@ namespace FluentTerminal.App.ViewModels.Menu
return _key == other._key && _keyModifiers == other._keyModifiers; return _key == other._key && _keyModifiers == other._keyModifiers;
} }
#endregion Methods
} }
} }
@@ -111,7 +111,13 @@ namespace FluentTerminal.App.ViewModels.Profiles
if (theme == null) if (theme == null)
{ {
return; // How to handle attempt to setting null theme?
// 1) Throw an exception:
//throw new ArgumentNullException();
// 2) Ignore the attempt:
//return;
// 3) Use default theme (probably the best):
theme = TerminalThemes.First();
} }
else if (!TerminalThemes.Contains(theme)) else if (!TerminalThemes.Contains(theme))
{ {
@@ -199,8 +199,8 @@ namespace FluentTerminal.App.ViewModels.Profiles
} }
return _sshPort == SshProfile.DefaultSshPort return _sshPort == SshProfile.DefaultSshPort
? $"-i \"{_identityFile}\"" ? $"-i '{_identityFile}'"
: $"-p {_sshPort:#####} -i \"{_identityFile}\""; : $"-p {_sshPort:#####} -i '{_identityFile}'";
} }
protected override void LoadFromProfile(ShellProfile profile) protected override void LoadFromProfile(ShellProfile profile)
@@ -116,7 +116,6 @@ namespace FluentTerminal.App.ViewModels
private string _hoveredUri; private string _hoveredUri;
private MenuViewModel _contextMenu; private MenuViewModel _contextMenu;
private MenuViewModel _tabContextMenu; private MenuViewModel _tabContextMenu;
private readonly TabThemeViewModel _transparentTabThemeViewModel;
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile, IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile,
@@ -152,7 +151,6 @@ namespace FluentTerminal.App.ViewModels
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel, CanExecuteCommand); CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel, CanExecuteCommand);
EditTitleCommand = new AsyncCommand(EditTitleAsync, CanExecuteCommand); EditTitleCommand = new AsyncCommand(EditTitleAsync, CanExecuteCommand);
DuplicateTabCommand = new RelayCommand(DuplicateTab, CanExecuteCommand); DuplicateTabCommand = new RelayCommand(DuplicateTab, CanExecuteCommand);
ReconnectTabCommand = new AsyncCommand(ReconnectTabAsync, () => CanExecuteCommand() && HasExitedWithError);
CopyCommand = new AsyncCommand(Copy, () => HasSelection); CopyCommand = new AsyncCommand(Copy, () => HasSelection);
PasteCommand = new AsyncCommand(Paste); PasteCommand = new AsyncCommand(Paste);
CopyLinkCommand = new AsyncCommand(() => CopyTextAsync(HoveredUri), () => !string.IsNullOrWhiteSpace(HoveredUri)); CopyLinkCommand = new AsyncCommand(() => CopyTextAsync(HoveredUri), () => !string.IsNullOrWhiteSpace(HoveredUri));
@@ -168,8 +166,6 @@ namespace FluentTerminal.App.ViewModels
defaultTabTheme.IsSelected = true; defaultTabTheme.IsSelected = true;
} }
_transparentTabThemeViewModel = TabThemes.FirstOrDefault(t => t.Theme.Id == 0);
Terminal = new Terminal(TrayProcessCommunicationService, _terminalId); Terminal = new Terminal(TrayProcessCommunicationService, _terminalId);
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived; Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
Terminal.OutputReceived += Terminal_OutputReceived; Terminal.OutputReceived += Terminal_OutputReceived;
@@ -221,13 +217,13 @@ namespace FluentTerminal.App.ViewModels
public IApplicationView ApplicationView { get; } public IApplicationView ApplicationView { get; }
public TabThemeViewModel BackgroundTabTheme public TabTheme BackgroundTabTheme
{ {
// The effective background theme depends on whether it is selected (use the theme), or if it is inactive // The effective background theme depends on whether it is selected (use the theme), or if it is inactive
// (if we're set to underline inactive tabs, use the null theme). // (if we're set to underline inactive tabs, use the null theme).
get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ? get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ?
TabTheme : _tabTheme?.Theme :
_transparentTabThemeViewModel; SettingsService.GetTabThemes().FirstOrDefault(t => t.Color == null);
} }
public IClipboardService ClipboardService { get; } public IClipboardService ClipboardService { get; }
@@ -254,8 +250,6 @@ namespace FluentTerminal.App.ViewModels
public RelayCommand DuplicateTabCommand { get; } public RelayCommand DuplicateTabCommand { get; }
public AsyncCommand ReconnectTabCommand { get; }
public ICommand CopyLinkCommand { get; private set; } public ICommand CopyLinkCommand { get; private set; }
public ICommand CopyCommand { get; private set; } public ICommand CopyCommand { get; private set; }
@@ -342,7 +336,6 @@ namespace FluentTerminal.App.ViewModels
{ {
HasNewOutput = false; HasNewOutput = false;
} }
ReconnectTabCommand.RaiseCanExecuteChanged();
} }
} }
@@ -549,12 +542,6 @@ namespace FluentTerminal.App.ViewModels
DuplicateTabRequested?.Invoke(this, EventArgs.Empty); DuplicateTabRequested?.Invoke(this, EventArgs.Empty);
} }
public async Task ReconnectTabAsync()
{
HasExitedWithError = false;
await TerminalView?.ReconnectAsync();
}
private void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message) private void OnApplicationSettingsChanged(ApplicationSettingsChangedMessage message)
{ {
ApplicationSettings = message.ApplicationSettings; ApplicationSettings = message.ApplicationSettings;
@@ -692,7 +679,6 @@ namespace FluentTerminal.App.ViewModels
private void Terminal_TitleChanged(object sender, string e) private void Terminal_TitleChanged(object sender, string e)
{ {
if (e == Terminal.Profile.Location) return;
ApplicationView.ExecuteOnUiThreadAsync(() => ShellTitle = e); ApplicationView.ExecuteOnUiThreadAsync(() => ShellTitle = e);
} }
@@ -781,7 +767,7 @@ namespace FluentTerminal.App.ViewModels
{ {
//todo localise name //todo localise name
var icon = tabTheme.Theme.Id == 0 ? Mdl2Icon.PaginationDotOutline10() : Mdl2Icon.PaginationDotSolid10(tabTheme.Theme.Color); var icon = tabTheme.Theme.Id == 0 ? Mdl2Icon.PaginationDotOutline10() : Mdl2Icon.PaginationDotSolid10(tabTheme.Theme.Color);
return new RadioMenuItemViewModel(I18N.Translate($"{tabTheme.Theme.Name}"), $"{Terminal.Id}_tabTheme", tabTheme, bindingPath: nameof(TabThemeViewModel.IsSelected) , icon: icon); return new RadioMenuItemViewModel(I18N.Translate($"TabTheme.{tabTheme.Theme.Name}"), $"{Terminal.Id}_tabTheme", tabTheme, bindingPath: nameof(TabThemeViewModel.IsSelected) , icon: icon);
} }
var tabColor = new ExpandableMenuItemViewModel(I18N.Translate("Color.Text")); var tabColor = new ExpandableMenuItemViewModel(I18N.Translate("Color.Text"));
+149 -193
View File
@@ -47,7 +47,6 @@ namespace FluentTerminal.App
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService; private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly INotificationService _notificationService;
private bool _alreadyLaunched; private bool _alreadyLaunched;
private bool _isLaunching; private bool _isLaunching;
private ApplicationSettings _applicationSettings; private ApplicationSettings _applicationSettings;
@@ -111,13 +110,13 @@ namespace FluentTerminal.App
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance(); builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
builder.RegisterType<ApplicationLanguageService>().As<IApplicationLanguageService>().SingleInstance(); builder.RegisterType<ApplicationLanguageService>().As<IApplicationLanguageService>().SingleInstance();
builder.RegisterType<ShellProfileMigrationService>().As<IShellProfileMigrationService>().SingleInstance(); builder.RegisterType<ShellProfileMigrationService>().As<IShellProfileMigrationService>().SingleInstance();
builder.RegisterType<AcceleratorKeyValidator>().As<IAcceleratorKeyValidator>().SingleInstance();
_container = builder.Build(); _container = builder.Build();
Messenger.Default.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged); Messenger.Default.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
_settingsService = _container.Resolve<ISettingsService>(); _settingsService = _container.Resolve<ISettingsService>();
_notificationService = _container.Resolve<INotificationService>();
var shellProfileMigrationService = _container.Resolve<IShellProfileMigrationService>(); var shellProfileMigrationService = _container.Resolve<IShellProfileMigrationService>();
foreach (var profile in _settingsService.GetShellProfiles()) foreach (var profile in _settingsService.GetShellProfiles())
@@ -187,189 +186,7 @@ namespace FluentTerminal.App
protected override async void OnActivated(IActivatedEventArgs args) protected override async void OnActivated(IActivatedEventArgs args)
{ {
await OnLaunchOrActivate(args); if (args is ProtocolActivatedEventArgs protocolActivated)
base.OnActivated(args);
}
private async Task ParseCommandLineArgumentsAsync(object verb, CommandLineActivatedEventArgs commandLineActivated)
{
using var deferral = commandLineActivated.Operation.GetDeferral();
if (verb is SettingsVerb settingsVerb)
{
if (string.IsNullOrWhiteSpace(settingsVerb.Import) && !settingsVerb.Export)
{
await ShowSettingsAsync();
}
else if (settingsVerb.Export)
{
var settings = _settingsService.ExportSettings();
var path = Path.Combine(commandLineActivated.Operation.CurrentDirectoryPath, "config.json");
try
{
await StartSystemTray().ConfigureAwait(false);
await _trayProcessCommunicationService.SaveTextFileAsync(path, settings).ConfigureAwait(false);
_notificationService.ShowNotification("Export settings", $"Settings were exported to {path}.");
}
catch (Exception e)
{
_notificationService.ShowNotification("Export settings", $"Failed with error: {e.Message}");
commandLineActivated.Operation.ExitCode = 1;
}
if (!_isLaunching && !_alreadyLaunched)
{
Exit();
}
}
else if (!string.IsNullOrWhiteSpace(settingsVerb.Import))
{
var path = Path.Combine(commandLineActivated.Operation.CurrentDirectoryPath, settingsVerb.Import.Replace(".\\", string.Empty));
try
{
await StartSystemTray().ConfigureAwait(false);
var content = await _trayProcessCommunicationService.ReadTextFileAsync(path).ConfigureAwait(false);
_settingsService.ImportSettings(content);
_notificationService.ShowNotification("Import settings", $"Successfully imported settings from {path}.");
}
catch (Exception e)
{
_notificationService.ShowNotification("Import settings", $"Failed with error: {e.Message}");
commandLineActivated.Operation.ExitCode = 1;
}
if (!_isLaunching && !_alreadyLaunched)
{
Exit();
}
}
}
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) && newVerb.Directory != ".")
{
profile.WorkingDirectory = newVerb.Directory;
}
else
{
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
}
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
: NewTerminalLocation.Window;
await CreateTerminalAsync(profile, location);
}
else if (verb is RunVerb runVerb)
{
var profile = new ShellProfile
{
Id = Guid.Empty,
Location = null,
Arguments = runVerb.Command,
WorkingDirectory = runVerb.Directory
};
if (runVerb.SmartBuffer.HasValue)
{
profile.UseBuffer = runVerb.SmartBuffer.Value;
}
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 CreateTerminalAsync(profile, location);
}
}
private Task ShowOrCreateWindowAsync(ActivationViewSwitcher viewSwitcher)
{
var viewModel = _mainViewModels.Find(o => o.ApplicationView.Id == _activeWindowId) ??
_mainViewModels.LastOrDefault();
return viewModel == null
? CreateTerminalAsync(_settingsService.GetDefaultShellProfile(), NewTerminalLocation.Tab, viewSwitcher)
: ShowAsStandaloneAsync(viewModel, viewSwitcher);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
await OnLaunchOrActivate(args);
}
public async Task OnLaunchOrActivate(IActivatedEventArgs args)
{
if (args is LaunchActivatedEventArgs launchActivated)
{
if (_isLaunching)
{
return;
}
_isLaunching = true;
if (!_alreadyLaunched)
{
await InitializeLoggerAsync();
// ReSharper disable once AssignmentIsFullyDiscarded
_ = JumpListHelper.UpdateAsync(_settingsService);
var viewModel = _container.Resolve<MainViewModel>();
if (launchActivated.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
{
await viewModel.AddProfileByGuidAsync(Guid.Parse(launchActivated.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
}
else
{
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
}
await CreateMainViewAsync(typeof(MainPage), viewModel, true);
Window.Current.Activate();
}
else if (launchActivated.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
{
var location = _applicationSettings.NewTerminalLocation;
var profile = _settingsService.GetShellProfile(Guid.Parse(launchActivated.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
await CreateTerminalAsync(profile, location, launchActivated.ViewSwitcher);
}
else
{
var viewModel = await CreateNewTerminalWindowAsync();
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
await ShowAsStandaloneAsync(viewModel, launchActivated.ViewSwitcher);
}
_isLaunching = false;
}
else if (args is ProtocolActivatedEventArgs protocolActivated)
{ {
if (protocolActivated.Uri == new Uri("ftcmd://fluent.terminal?focus")) if (protocolActivated.Uri == new Uri("ftcmd://fluent.terminal?focus"))
{ {
@@ -445,7 +262,7 @@ namespace FluentTerminal.App
var error = await vm.AcceptChangesAsync(true); var error = await vm.AcceptChangesAsync(true);
var profile = (SshProfile)vm.Model; var profile = (SshProfile) vm.Model;
if (!string.IsNullOrEmpty(error)) if (!string.IsNullOrEmpty(error))
{ {
@@ -530,7 +347,8 @@ namespace FluentTerminal.App
return; return;
} }
else if (args is CommandLineActivatedEventArgs commandLineActivated)
if (args is CommandLineActivatedEventArgs commandLineActivated)
{ {
var arguments = commandLineActivated.Operation.Arguments; var arguments = commandLineActivated.Operation.Arguments;
if (string.IsNullOrWhiteSpace(arguments)) if (string.IsNullOrWhiteSpace(arguments))
@@ -540,10 +358,153 @@ namespace FluentTerminal.App
_commandLineParser _commandLineParser
.ParseArguments(SplitArguments(arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb)) .ParseArguments(SplitArguments(arguments), typeof(NewVerb), typeof(RunVerb), typeof(SettingsVerb))
.WithParsed(async verb => await ParseCommandLineArgumentsAsync(verb, commandLineActivated)); .WithParsed(async verb => await CliRunAsync(verb, commandLineActivated));
} }
} }
private async Task CliRunAsync(object verb, CommandLineActivatedEventArgs commandLineActivated)
{
if (verb is SettingsVerb settingsVerb)
{
if (!settingsVerb.Import && !settingsVerb.Export)
{
await ShowSettingsAsync();
}
else if (settingsVerb.Export)
{
var exportFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("config.json", CreationCollisionOption.OpenIfExists);
var settings = _settingsService.ExportSettings();
await FileIO.WriteTextAsync(exportFile, settings);
await new MessageDialog($"{I18N.Translate("SettingsExported")} {exportFile.Path}").ShowAsync();
}
else if (settingsVerb.Import)
{
var file = await ApplicationData.Current.LocalFolder.GetFileAsync("config.json");
var content = await FileIO.ReadTextAsync(file);
_settingsService.ImportSettings(content);
await new MessageDialog($"{I18N.Translate("SettingsImported")} {file.Path}").ShowAsync();
}
}
else if (verb is NewVerb newVerb)
{
var profile = default(ShellProfile);
if (!string.IsNullOrWhiteSpace(newVerb.Profile))
{
profile = _settingsService.GetShellProfiles().FirstOrDefault(x => x.Name.Equals(newVerb.Profile, StringComparison.CurrentCultureIgnoreCase));
}
if (profile == null)
{
profile = _settingsService.GetDefaultShellProfile();
}
if (!string.IsNullOrWhiteSpace(newVerb.Directory) && newVerb.Directory != ".")
{
profile.WorkingDirectory = newVerb.Directory;
}
else
{
profile.WorkingDirectory = commandLineActivated.Operation.CurrentDirectoryPath;
}
var location = newVerb.Target == Target.Default ? _applicationSettings.NewTerminalLocation
: newVerb.Target == Target.Tab ? NewTerminalLocation.Tab
: NewTerminalLocation.Window;
await CreateTerminalAsync(profile, location);
}
else if (verb is RunVerb runVerb)
{
var profile = new ShellProfile
{
Id = Guid.Empty,
Location = null,
Arguments = runVerb.Command,
WorkingDirectory = runVerb.Directory
};
if (!string.IsNullOrEmpty(runVerb.Buffer) && bool.TryParse(runVerb.Buffer, out var useBuffer))
{
profile.UseBuffer = useBuffer;
}
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 CreateTerminalAsync(profile, location);
}
}
private Task ShowOrCreateWindowAsync(ActivationViewSwitcher viewSwitcher)
{
var viewModel = _mainViewModels.Find(o => o.ApplicationView.Id == _activeWindowId) ??
_mainViewModels.LastOrDefault();
return viewModel == null
? CreateTerminalAsync(_settingsService.GetDefaultShellProfile(), NewTerminalLocation.Tab, viewSwitcher)
: ShowAsStandaloneAsync(viewModel, viewSwitcher);
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (_isLaunching)
{
return;
}
_isLaunching = true;
if (!_alreadyLaunched)
{
await InitializeLoggerAsync();
// ReSharper disable once AssignmentIsFullyDiscarded
_ = JumpListHelper.UpdateAsync(_settingsService);
var viewModel = _container.Resolve<MainViewModel>();
if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
{
await viewModel.AddProfileByGuidAsync(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
}
else
{
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
}
await CreateMainViewAsync(typeof(MainPage), viewModel, true);
Window.Current.Activate();
}
else if (args.Arguments.StartsWith(JumpListHelper.ShellProfileFlag))
{
var location = _applicationSettings.NewTerminalLocation;
var profile = _settingsService.GetShellProfile(Guid.Parse(args.Arguments.Replace(JumpListHelper.ShellProfileFlag, string.Empty)));
await CreateTerminalAsync(profile, location, args.ViewSwitcher);
}
else
{
var viewModel = await CreateNewTerminalWindowAsync();
await viewModel.AddDefaultProfileAsync(NewTerminalLocation.Tab);
await ShowAsStandaloneAsync(viewModel, args.ViewSwitcher);
}
_isLaunching = false;
}
private static async Task InitializeLoggerAsync() private static async Task InitializeLoggerAsync()
{ {
var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists); var logDirectory = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("Logs", CreationCollisionOption.OpenIfExists);
@@ -798,11 +759,6 @@ namespace FluentTerminal.App
private Task StartSystemTray() private Task StartSystemTray()
{ {
if (_trayReady.Task.IsCompleted)
{
return Task.CompletedTask;
}
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask(); var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
return Task.WhenAll(launch, _trayReady.Task).ContinueWith( return Task.WhenAll(launch, _trayReady.Task).ContinueWith(
@@ -14,8 +14,8 @@ namespace FluentTerminal.App.CommandLineArguments
[Option("theme")] [Option("theme")]
public string Theme { get; set; } public string Theme { get; set; }
[Option("smart-buffer")] [Option("buffer")]
public bool? SmartBuffer { get; set; } public string Buffer { get; set; }
[Option("target")] [Option("target")]
public Target Target { get; set; } public Target Target { get; set; }
@@ -6,8 +6,8 @@ namespace FluentTerminal.App.CommandLineArguments
public class SettingsVerb public class SettingsVerb
{ {
[Option('i', "import", [Option('i', "import",
HelpText = "Import settings from specified file.")] HelpText = "Import settings.")]
public string Import { get; set; } public bool Import { get; set; }
[Option('e', "export", [Option('e', "export",
HelpText = "Export settings.")] HelpText = "Export settings.")]
@@ -20,10 +20,6 @@ namespace FluentTerminal.App.Converters
{ {
return Translate($"{enumType}.{enumValue}"); return Translate($"{enumType}.{enumValue}");
} }
else if (value is string stringValue)
{
return Translate(stringValue);
}
else if (parameter is string resource) else if (parameter is string resource)
{ {
return Translate(resource); return Translate(resource);
@@ -7,7 +7,6 @@ using Windows.UI.Xaml.Input;
using FluentTerminal.App.ViewModels.Menu; using FluentTerminal.App.ViewModels.Menu;
using FluentTerminal.App.Views; using FluentTerminal.App.Views;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Metadata;
// ReSharper disable LocalizableElement // ReSharper disable LocalizableElement
@@ -91,22 +90,12 @@ namespace FluentTerminal.App.Converters
if (viewModel.KeyBinding != null) if (viewModel.KeyBinding != null)
{ {
if (viewModel.KeyBinding.IsExtendedVirtualKey) item.KeyboardAccelerators?.Add(new KeyboardAccelerator
{ {
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6)) Key = (VirtualKey) viewModel.KeyBinding.Key,
{ Modifiers = (VirtualKeyModifiers) viewModel.KeyBinding.KeyModifiers,
item.KeyboardAcceleratorTextOverride = viewModel.KeyBinding.GetOverrideText(); IsEnabled = true
} });
}
else
{
item.KeyboardAccelerators?.Add(new KeyboardAccelerator
{
Key = (VirtualKey)viewModel.KeyBinding.Key,
Modifiers = (VirtualKeyModifiers)viewModel.KeyBinding.KeyModifiers,
IsEnabled = true
});
}
} }
return item; return item;
@@ -16,6 +16,8 @@ namespace FluentTerminal.App.Dialogs
// ReSharper disable once RedundantExtendsListEntry // ReSharper disable once RedundantExtendsListEntry
public sealed partial class CreateKeyBindingDialog : ContentDialog, ICreateKeyBindingDialog, INotifyPropertyChanged public sealed partial class CreateKeyBindingDialog : ContentDialog, ICreateKeyBindingDialog, INotifyPropertyChanged
{ {
private readonly IAcceleratorKeyValidator _acceleratorKeyValidator;
private bool _ctrl; private bool _ctrl;
private bool _shift; private bool _shift;
private bool _alt; private bool _alt;
@@ -51,12 +53,17 @@ namespace FluentTerminal.App.Dialogs
get => _key; get => _key;
set set
{ {
Set(ref _key, value); // We're ignoring invalid keys.
if (_acceleratorKeyValidator.Valid(value))
{
Set(ref _key, value);
}
} }
} }
public CreateKeyBindingDialog() public CreateKeyBindingDialog(IAcceleratorKeyValidator acceleratorKeyValidator)
{ {
_acceleratorKeyValidator = acceleratorKeyValidator;
InitializeComponent(); InitializeComponent();
ResetCommand = new RelayCommand(Reset); ResetCommand = new RelayCommand(Reset);
PreviewKeyDown += RegisterKeyBindingDialog_PreviewKeyDown; PreviewKeyDown += RegisterKeyBindingDialog_PreviewKeyDown;
@@ -133,6 +133,7 @@
<Compile Include="Dialogs\AboutDialog.xaml.cs"> <Compile Include="Dialogs\AboutDialog.xaml.cs">
<DependentUpon>AboutDialog.xaml</DependentUpon> <DependentUpon>AboutDialog.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Services\AcceleratorKeyValidator.cs" />
<Compile Include="Services\CommandHistoryService.cs" /> <Compile Include="Services\CommandHistoryService.cs" />
<Compile Include="Utilities\DelayedAction.cs" /> <Compile Include="Utilities\DelayedAction.cs" />
<Compile Include="Utilities\DispatcherExtensions.cs" /> <Compile Include="Utilities\DispatcherExtensions.cs" />
@@ -0,0 +1,13 @@
using System;
using Windows.System;
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services
{
public class AcceleratorKeyValidator : IAcceleratorKeyValidator
{
public bool Valid(int key) => Enum.IsDefined(typeof(VirtualKey), key);
public bool Valid(ExtendedVirtualKey key) => Valid((int) key);
}
}
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1087,12 +1087,6 @@ Type &quot;أخرج من منفذ الأوامر &quot; then type the name in th
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1077,12 +1077,6 @@ Fuzzy</comment>
<value>Auswahl kopieren oder Einfügen</value> <value>Auswahl kopieren oder Einfügen</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Smart Buffer verwenden</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Neu starten</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Sitzung wurde getrennt.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1109,12 +1109,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+79 -85
View File
@@ -542,7 +542,7 @@ Fuzzy</comment>
<comment>ShellProfileSettings.xaml</comment> <comment>ShellProfileSettings.xaml</comment>
</data> </data>
<data name="ConfirmCloseTab" xml:space="preserve"> <data name="ConfirmCloseTab" xml:space="preserve">
<value>¿Estás seguro que quieres cerrar la pestaña &quot;{0}&quot;?</value> <value>Are you sure you want to close &quot;{0}&quot; tab?</value>
<comment>ViewModel</comment> <comment>ViewModel</comment>
</data> </data>
<data name="ConfirmCloseWindow" xml:space="preserve"> <data name="ConfirmCloseWindow" xml:space="preserve">
@@ -831,246 +831,240 @@ Fuzzy</comment>
<value>¿Quieres eliminar este perfil?</value> <value>¿Quieres eliminar este perfil?</value>
</data> </data>
<data name="Command.NewSshWindow" xml:space="preserve"> <data name="Command.NewSshWindow" xml:space="preserve">
<value>Nueva ventana SSH</value> <value>New SSH window</value>
</data> </data>
<data name="Setting_SShProfiles.Content" xml:space="preserve"> <data name="Setting_SShProfiles.Content" xml:space="preserve">
<value>Perfiles SSH</value> <value>SSH Profiles</value>
</data> </data>
<data name="SshInfoDialog_TabColor.Text" xml:space="preserve"> <data name="SshInfoDialog_TabColor.Text" xml:space="preserve">
<value>Color de pestaña:</value> <value>Tab color:</value>
</data> </data>
<data name="UseConPty" xml:space="preserve"> <data name="UseConPty" xml:space="preserve">
<value>Usar ConPty</value> <value>Use ConPty</value>
</data> </data>
<data name="Username.Header" xml:space="preserve"> <data name="Username.Header" xml:space="preserve">
<value>Nombre de usuario</value> <value>Username</value>
</data> </data>
<data name="MoshPortFrom.Header" xml:space="preserve"> <data name="MoshPortFrom.Header" xml:space="preserve">
<value>Puerto desde</value> <value>From port</value>
</data> </data>
<data name="MoshPortTo.Header" xml:space="preserve"> <data name="MoshPortTo.Header" xml:space="preserve">
<value>a puerto</value> <value>to port</value>
</data> </data>
<data name="MoshPortRange.Text" xml:space="preserve"> <data name="MoshPortRange.Text" xml:space="preserve">
<value>Rango de puertos Mosh</value> <value>Mosh port range</value>
</data> </data>
<data name="Authenticate" xml:space="preserve"> <data name="Authenticate" xml:space="preserve">
<value>Autenticar</value> <value>Authenticate</value>
</data> </data>
<data name="SettingsExported" xml:space="preserve"> <data name="SettingsExported" xml:space="preserve">
<value>Opciones exportadas a archivo</value> <value>Settings were exported to file</value>
</data> </data>
<data name="SettingsImported" xml:space="preserve"> <data name="SettingsImported" xml:space="preserve">
<value>Opciones importadas desde archivo</value> <value>Settings were imported from file</value>
</data> </data>
<data name="SshConnectionInfoValidationResult.IdentityFileDoesNotExist" xml:space="preserve"> <data name="SshConnectionInfoValidationResult.IdentityFileDoesNotExist" xml:space="preserve">
<value>No existe el archivo de identidad</value> <value>Identify file does not exist</value>
</data> </data>
<data name="AutoFallbackToWindowsUsername.Header" xml:space="preserve"> <data name="AutoFallbackToWindowsUsername.Header" xml:space="preserve">
<value>Usar nombre de Windows al abrir urls SSH que no especifican uno</value> <value>Use Windows username when opening SSH URLs that don&apos;t specify one</value>
</data> </data>
<data name="UseMoshByDefault.Header" xml:space="preserve"> <data name="UseMoshByDefault.Header" xml:space="preserve">
<value>Mosh para conexiones SSH por defecto</value> <value>Default to Mosh for SSH connections</value>
</data> </data>
<data name="RTrimCopiedLines.Header" xml:space="preserve"> <data name="RTrimCopiedLines.Header" xml:space="preserve">
<value>Recortar a la derecha las líneas copiadas</value> <value>Right trim copied lines</value>
</data> </data>
<data name="QuickSshDefault.Header" xml:space="preserve"> <data name="QuickSshDefault.Header" xml:space="preserve">
<value>Usar SSH rápido por defecto</value> <value>Use quick SSH by default</value>
</data> </data>
<data name="InvalidCommand" xml:space="preserve"> <data name="InvalidCommand" xml:space="preserve">
<value>Comando inválido.</value> <value>Invalid command.</value>
</data> </data>
<data name="LocationCantBeEmpty" xml:space="preserve"> <data name="LocationCantBeEmpty" xml:space="preserve">
<value>La ubicación no puede estar vacía.</value> <value>Location cannot be empty.</value>
</data> </data>
<data name="MuteTerminalBeeps.Header" xml:space="preserve"> <data name="MuteTerminalBeeps.Header" xml:space="preserve">
<value>Silenciar sonidos de terminal</value> <value>Mute terminal beeps</value>
</data> </data>
<data name="CommandTextBox.PlaceholderText" xml:space="preserve"> <data name="CommandTextBox.PlaceholderText" xml:space="preserve">
<value>Introduce un comando (p.e. &apos;ssh username@host&apos;)</value> <value>Enter a command (e.g. &apos;ssh username@host&apos;)</value>
</data> </data>
<data name="CommandLine.Header" xml:space="preserve"> <data name="CommandLine.Header" xml:space="preserve">
<value>Comando</value> <value>Command</value>
</data> </data>
<data name="UnsupportedCommand" xml:space="preserve"> <data name="UnsupportedCommand" xml:space="preserve">
<value>Comando no soportado:</value> <value>Unsupported command:</value>
</data> </data>
<data name="CommandArgumentsMandatory" xml:space="preserve"> <data name="CommandArgumentsMandatory" xml:space="preserve">
<value>Faltan argumentos del comando.</value> <value>Command arguments are missing.</value>
</data> </data>
<data name="QuickCommandDialog.Title" xml:space="preserve"> <data name="QuickCommandDialog.Title" xml:space="preserve">
<value>Lanzamiento rápido</value> <value>Quick Launch</value>
</data> </data>
<data name="Command.NewCustomCommandTab" xml:space="preserve"> <data name="Command.NewCustomCommandTab" xml:space="preserve">
<value>Nueva pestaña de inicio rápido</value> <value>New quick launch tab</value>
</data> </data>
<data name="Command.NewCustomCommandWindow" xml:space="preserve"> <data name="Command.NewCustomCommandWindow" xml:space="preserve">
<value>Lanzar nueva ventana rápida</value> <value>New quick launch window</value>
</data> </data>
<data name="NewQuickTab.Text" xml:space="preserve"> <data name="NewQuickTab.Text" xml:space="preserve">
<value>Nueva pestaña rápida</value> <value>New quick tab</value>
</data> </data>
<data name="FileNotFound" xml:space="preserve"> <data name="FileNotFound" xml:space="preserve">
<value>Archivo no encontrado:</value> <value>File not found:</value>
</data> </data>
<data name="InvalidLink" xml:space="preserve"> <data name="InvalidLink" xml:space="preserve">
<value>Enlace inválido.</value> <value>Invalid link.</value>
</data> </data>
<data name="DropTabHere" xml:space="preserve"> <data name="DropTabHere" xml:space="preserve">
<value>Suelta la pestaña aquí</value> <value>Drop tab here</value>
</data> </data>
<data name="CloseLeft.Text" xml:space="preserve"> <data name="CloseLeft.Text" xml:space="preserve">
<value>Cerrar pestañas a la izquierda</value> <value>Close tabs to the left</value>
</data> </data>
<data name="CloseOther.Text" xml:space="preserve"> <data name="CloseOther.Text" xml:space="preserve">
<value>Cerrar el resto de pestañas</value> <value>Close other tabs</value>
</data> </data>
<data name="CloseRight.Text" xml:space="preserve"> <data name="CloseRight.Text" xml:space="preserve">
<value>Cerrar pestañas a la derecha</value> <value>Close tabs to the right</value>
</data> </data>
<data name="Command.DuplicateTab" xml:space="preserve"> <data name="Command.DuplicateTab" xml:space="preserve">
<value>Duplicar pestaña</value> <value>Duplicate tab</value>
</data> </data>
<data name="Duplicate.Text" xml:space="preserve"> <data name="Duplicate.Text" xml:space="preserve">
<value>Duplicar</value> <value>Duplicate</value>
</data> </data>
<data name="DuplicateEnvironmentVariable" xml:space="preserve"> <data name="DuplicateEnvironmentVariable" xml:space="preserve">
<value>No puedes asignar dos variables de entorno con el mismo nombre.</value> <value>Cannot define two environment variables with the same name.</value>
</data> </data>
<data name="EmptyEnvironmentVariableName" xml:space="preserve"> <data name="EmptyEnvironmentVariableName" xml:space="preserve">
<value>No puedes definir una variable de entorno sin nombre.</value> <value>Cannot define an environment variable without a name.</value>
</data> </data>
<data name="EnvironmentVariables.Text" xml:space="preserve"> <data name="EnvironmentVariables.Text" xml:space="preserve">
<value>Variables de entorno</value> <value>Environment variables</value>
</data> </data>
<data name="CloseSearchPanel.ToolTipService.ToolTip" xml:space="preserve"> <data name="CloseSearchPanel.ToolTipService.ToolTip" xml:space="preserve">
<value>Cerrar panel de búsqueda</value> <value>Close search panel</value>
</data> </data>
<data name="FindNext.ToolTipService.ToolTip" xml:space="preserve"> <data name="FindNext.ToolTipService.ToolTip" xml:space="preserve">
<value>Buscar siguiente</value> <value>Find next</value>
</data> </data>
<data name="FindPrevious.ToolTipService.ToolTip" xml:space="preserve"> <data name="FindPrevious.ToolTipService.ToolTip" xml:space="preserve">
<value>Buscar anterior</value> <value>Find previous</value>
</data> </data>
<data name="SearchTextBox.PlaceholderText" xml:space="preserve"> <data name="SearchTextBox.PlaceholderText" xml:space="preserve">
<value>Ingrese el término de búsqueda</value> <value>Enter search term</value>
</data> </data>
<data name="Search.Text" xml:space="preserve"> <data name="Search.Text" xml:space="preserve">
<value>Buscar</value> <value>Search</value>
</data> </data>
<data name="PrintableOutputOnly.Content" xml:space="preserve"> <data name="PrintableOutputOnly.Content" xml:space="preserve">
<value>Solamente salida imprimible</value> <value>Printable output only</value>
</data> </data>
<data name="EnableLogging.Header" xml:space="preserve"> <data name="EnableLogging.Header" xml:space="preserve">
<value>Registar salida para debugeo</value> <value>Log output for debugging</value>
</data> </data>
<data name="WordSeparator.Header" xml:space="preserve"> <data name="WordSeparator.Header" xml:space="preserve">
<value>Caracteres separadores de palabras</value> <value>Word separator characters</value>
</data> </data>
<data name="Recent.Text" xml:space="preserve"> <data name="Recent.Text" xml:space="preserve">
<value>Reciente</value> <value>Recent</value>
</data> </data>
<data name="NewTab_Description" xml:space="preserve"> <data name="NewTab_Description" xml:space="preserve">
<value>Abre el perfil predeterminado en una nueva pestaña.</value> <value>Opens default profile in a new tab.</value>
</data> </data>
<data name="NewSshTab_Description" xml:space="preserve"> <data name="NewSshTab_Description" xml:space="preserve">
<value>Abre una nueva sesión SSH o Mosh en una nueva pestaña.</value> <value>Opens a new SSH or Mosh session in a new tab.</value>
</data> </data>
<data name="NewQuickTab_Description" xml:space="preserve"> <data name="NewQuickTab_Description" xml:space="preserve">
<value>Abre el cuadro de diálogo &quot;Inicio rápido&quot; e inicia la sesión en una pestaña nueva.</value> <value>Opens &quot;Quick Launch&quot; dialog and starts session in a new tab.</value>
</data> </data>
<data name="Settings_Description" xml:space="preserve"> <data name="Settings_Description" xml:space="preserve">
<value>Abrir ventana de opciones.</value> <value>Opens settings window.</value>
</data> </data>
<data name="About_Description" xml:space="preserve"> <data name="About_Description" xml:space="preserve">
<value>Información básica sobre la app.</value> <value>Basic info about the app.</value>
</data> </data>
<data name="Recent_Description" xml:space="preserve"> <data name="Recent_Description" xml:space="preserve">
<value>Sesiones abiertas recientemente.</value> <value>Recently opened sessions.</value>
</data> </data>
<data name="MenuItem_DefaultProfile_Description" xml:space="preserve"> <data name="MenuItem_DefaultProfile_Description" xml:space="preserve">
<value>Inicia una nueva sesión de terminal basada en el perfil predeterminado.</value> <value>Starts new terminal session based on the default profile.</value>
</data> </data>
<data name="MenuItem_Remote_Text" xml:space="preserve"> <data name="MenuItem_Remote_Text" xml:space="preserve">
<value>Conexión remota...</value> <value>Remote Connect...</value>
</data> </data>
<data name="MenuItem_Remote_Description" xml:space="preserve"> <data name="MenuItem_Remote_Description" xml:space="preserve">
<value>Abre un cuadro de diálogo para iniciar una nueva sesión de terminal SSH o Mosh.</value> <value>Opens a dialog for launching a new SSH or Mosh terminal session.</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Text" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Text" xml:space="preserve">
<value>Comida rápida...</value> <value>Quick Launch...</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Description" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Description" xml:space="preserve">
<value>Abre un cuadro de diálogo &quot;Inicio rápido&quot; para iniciar una nueva sesión de terminal.</value> <value>Opens a &quot;Quick Launch&quot; dialog for starting a new terminal session.</value>
</data> </data>
<data name="MenuItem_NewWindow_Text" xml:space="preserve"> <data name="MenuItem_NewWindow_Text" xml:space="preserve">
<value>Nueva ventana</value> <value>New Window</value>
</data> </data>
<data name="MenuItem_NewTab_Text" xml:space="preserve"> <data name="MenuItem_NewTab_Text" xml:space="preserve">
<value>Nueva pestaña</value> <value>New Tab</value>
</data> </data>
<data name="MenuItem_NewTab_Description" xml:space="preserve"> <data name="MenuItem_NewTab_Description" xml:space="preserve">
<value>Inicia una sesión en una nueva pestaña.</value> <value>Launches a session in a new tab.</value>
</data> </data>
<data name="MenuItem_NewWindow_Description" xml:space="preserve"> <data name="MenuItem_NewWindow_Description" xml:space="preserve">
<value>Lanzar nueva sesión en una nueva ventana.</value> <value>Launches a session in a new window.</value>
</data> </data>
<data name="AppMenuType.Text" xml:space="preserve"> <data name="AppMenuType.Text" xml:space="preserve">
<value>Tipo de menú de la aplicación</value> <value>Application menu type</value>
</data> </data>
<data name="AppMenuType_Flat.Content" xml:space="preserve"> <data name="AppMenuType_Flat.Content" xml:space="preserve">
<value>Plano, honrando la configuración &quot;Abrir nuevos terminales como&quot;</value> <value>Flat, honoring &quot;Open new terminals as&quot; setting</value>
</data> </data>
<data name="AppMenuType_Cascading.Content" xml:space="preserve"> <data name="AppMenuType_Cascading.Content" xml:space="preserve">
<value>&quot;Nueva pestaña&quot; / &quot;Nueva ventana&quot; en cascada</value> <value>&quot;New Tab&quot; / &quot;New Window&quot; cascading</value>
</data> </data>
<data name="Quit.Text" xml:space="preserve"> <data name="Quit.Text" xml:space="preserve">
<value>Salir</value> <value>Quit</value>
</data> </data>
<data name="Quit.Description" xml:space="preserve"> <data name="Quit.Description" xml:space="preserve">
<value>Salir de Fluent Terminal</value> <value>Quit Fluent Terminal</value>
</data> </data>
<data name="EnabledByPolicy" xml:space="preserve"> <data name="EnabledByPolicy" xml:space="preserve">
<value>Habilitado por la política.</value> <value>Enabled by policy.</value>
</data> </data>
<data name="BackgroundImage.Content" xml:space="preserve"> <data name="BackgroundImage.Content" xml:space="preserve">
<value>Imagen de fondo</value> <value>Background image</value>
</data> </data>
<data name="AcrylicBackground.Header" xml:space="preserve"> <data name="AcrylicBackground.Header" xml:space="preserve">
<value>Usar fondo acrílico</value> <value>Use acrylic background</value>
</data> </data>
<data name="ConfirmDeleteBackgroundImage" xml:space="preserve"> <data name="ConfirmDeleteBackgroundImage" xml:space="preserve">
<value>¿Estás seguro de querer borrar esta imagen de fondo?</value> <value>Are you sure you want to delete this background image?</value>
</data> </data>
<data name="Colors.Text" xml:space="preserve"> <data name="Colors.Text" xml:space="preserve">
<value>Colores</value> <value>Colors</value>
</data> </data>
<data name="BrightColors.Text" xml:space="preserve"> <data name="BrightColors.Text" xml:space="preserve">
<value>Colores brillantes</value> <value>Bright colors</value>
</data> </data>
<data name="CopyLink" xml:space="preserve"> <data name="CopyLink" xml:space="preserve">
<value>Copiar enlace</value> <value>Copy link</value>
</data> </data>
<data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve">
<value>Regex</value> <value>Regex</value>
</data> </data>
<data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve">
<value>Toda la palabra</value> <value>Whole word</value>
</data> </data>
<data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve">
<value>Distingue mayúsculas y minúsculas</value> <value>Case sensitive</value>
</data> </data>
<data name="MouseCopySelectionOrPaste.Content" xml:space="preserve"> <data name="MouseCopySelectionOrPaste.Content" xml:space="preserve">
<value>Copiar selección o pegar</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Usar búfer inteligente</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+2 -8
View File
@@ -1061,7 +1061,7 @@ Fuzzy</comment>
<value>Application menu type</value> <value>Application menu type</value>
</data> </data>
<data name="AppMenuType_Flat.Content" xml:space="preserve"> <data name="AppMenuType_Flat.Content" xml:space="preserve">
<value>Plat, honorant le paramètre &quot;Ouvrir de nouveaux terminaux comme&quot;</value> <value>Flat, honoring &quot;Open new terminals as&quot; setting</value>
</data> </data>
<data name="AppMenuType_Cascading.Content" xml:space="preserve"> <data name="AppMenuType_Cascading.Content" xml:space="preserve">
<value>&quot;Nouvel onglet&quot; / &quot;Nouvelle fenêtre&quot; en cascade.</value> <value>&quot;Nouvel onglet&quot; / &quot;Nouvelle fenêtre&quot; en cascade.</value>
@@ -1106,12 +1106,6 @@ Fuzzy</comment>
<value>Copier la sélection ou coller</value> <value>Copier la sélection ou coller</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1053,12 +1053,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Salin yang terpilih atau tempel</value> <value>Salin yang terpilih atau tempel</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+24 -30
View File
@@ -969,7 +969,7 @@ Fuzzy</comment>
<value>Apre una sessione SSH o Mosh in una nuova scheda</value> <value>Apre una sessione SSH o Mosh in una nuova scheda</value>
</data> </data>
<data name="NewQuickTab_Description" xml:space="preserve"> <data name="NewQuickTab_Description" xml:space="preserve">
<value>Apre la schermata di &quot;Avvio rapido&quot; e avvia la sessione in una nuova scheda</value> <value>Opens &quot;Quick Launch&quot; dialog and starts session in a new tab.</value>
</data> </data>
<data name="Settings_Description" xml:space="preserve"> <data name="Settings_Description" xml:space="preserve">
<value>Apre la finestra impostazioni</value> <value>Apre la finestra impostazioni</value>
@@ -981,87 +981,81 @@ Fuzzy</comment>
<value>Sessioni aperte di recente</value> <value>Sessioni aperte di recente</value>
</data> </data>
<data name="MenuItem_DefaultProfile_Description" xml:space="preserve"> <data name="MenuItem_DefaultProfile_Description" xml:space="preserve">
<value>Avvia una nuova sessione di terminale con il profilo predefinito</value> <value>Starts new terminal session based on the default profile.</value>
</data> </data>
<data name="MenuItem_Remote_Text" xml:space="preserve"> <data name="MenuItem_Remote_Text" xml:space="preserve">
<value>Connessione remota...</value> <value>Connessione remota...</value>
</data> </data>
<data name="MenuItem_Remote_Description" xml:space="preserve"> <data name="MenuItem_Remote_Description" xml:space="preserve">
<value>Apre una schermata per la creazione di una sessione SSH o Mosh</value> <value>Opens a dialog for launching a new SSH or Mosh terminal session.</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Text" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Text" xml:space="preserve">
<value>Avvio rapido...</value> <value>Avvio rapido...</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Description" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Description" xml:space="preserve">
<value>Apre la schermata di &quot;Avvio rapido&quot; per una nuova sessione di terminale</value> <value>Opens a &quot;Quick Launch&quot; dialog for starting a new terminal session.</value>
</data> </data>
<data name="MenuItem_NewWindow_Text" xml:space="preserve"> <data name="MenuItem_NewWindow_Text" xml:space="preserve">
<value>Nuova finestra</value> <value>New Window</value>
</data> </data>
<data name="MenuItem_NewTab_Text" xml:space="preserve"> <data name="MenuItem_NewTab_Text" xml:space="preserve">
<value>Nuova scheda</value> <value>New Tab</value>
</data> </data>
<data name="MenuItem_NewTab_Description" xml:space="preserve"> <data name="MenuItem_NewTab_Description" xml:space="preserve">
<value>Avvia la sessione in una nuova scheda</value> <value>Launches a session in a new tab.</value>
</data> </data>
<data name="MenuItem_NewWindow_Description" xml:space="preserve"> <data name="MenuItem_NewWindow_Description" xml:space="preserve">
<value>Avvia la sessione in una nuova finestra</value> <value>Launches a session in a new window.</value>
</data> </data>
<data name="AppMenuType.Text" xml:space="preserve"> <data name="AppMenuType.Text" xml:space="preserve">
<value>Formato menu applicazione</value> <value>Application menu type</value>
</data> </data>
<data name="AppMenuType_Flat.Content" xml:space="preserve"> <data name="AppMenuType_Flat.Content" xml:space="preserve">
<value>Lineare, le azioni seguiranno l&apos;impostazione &quot;Apri i nuovi terminali come&quot;</value> <value>Flat, honoring &quot;Open new terminals as&quot; setting</value>
</data> </data>
<data name="AppMenuType_Cascading.Content" xml:space="preserve"> <data name="AppMenuType_Cascading.Content" xml:space="preserve">
<value>Menù a cascata &quot;Nuova Scheda&quot; / &quot;Nuova finestra&quot;</value> <value>&quot;New Tab&quot; / &quot;New Window&quot; cascading</value>
</data> </data>
<data name="Quit.Text" xml:space="preserve"> <data name="Quit.Text" xml:space="preserve">
<value>Chiudi</value> <value>Quit</value>
</data> </data>
<data name="Quit.Description" xml:space="preserve"> <data name="Quit.Description" xml:space="preserve">
<value>Chiudi Fluent Terminal</value> <value>Quit Fluent Terminal</value>
</data> </data>
<data name="EnabledByPolicy" xml:space="preserve"> <data name="EnabledByPolicy" xml:space="preserve">
<value>Attivato da policy</value> <value>Enabled by policy.</value>
</data> </data>
<data name="BackgroundImage.Content" xml:space="preserve"> <data name="BackgroundImage.Content" xml:space="preserve">
<value>Immagine di sfondo</value> <value>Background image</value>
</data> </data>
<data name="AcrylicBackground.Header" xml:space="preserve"> <data name="AcrylicBackground.Header" xml:space="preserve">
<value>Usa la sfumatura acrilica per lo sfondo</value> <value>Use acrylic background</value>
</data> </data>
<data name="ConfirmDeleteBackgroundImage" xml:space="preserve"> <data name="ConfirmDeleteBackgroundImage" xml:space="preserve">
<value>Vuoi davvero eliminare questa immagine di sfondo?</value> <value>Are you sure you want to delete this background image?</value>
</data> </data>
<data name="Colors.Text" xml:space="preserve"> <data name="Colors.Text" xml:space="preserve">
<value>Colori</value> <value>Colors</value>
</data> </data>
<data name="BrightColors.Text" xml:space="preserve"> <data name="BrightColors.Text" xml:space="preserve">
<value>Colori brillanti</value> <value>Bright colors</value>
</data> </data>
<data name="CopyLink" xml:space="preserve"> <data name="CopyLink" xml:space="preserve">
<value>Copia link</value> <value>Copy link</value>
</data> </data>
<data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve">
<value>Regex</value> <value>Regex</value>
</data> </data>
<data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve">
<value>Parola intera</value> <value>Whole word</value>
</data> </data>
<data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve">
<value>Sensibile a maiuscole/minuscole</value> <value>Case sensitive</value>
</data> </data>
<data name="MouseCopySelectionOrPaste.Content" xml:space="preserve"> <data name="MouseCopySelectionOrPaste.Content" xml:space="preserve">
<value>Copia selezione o incolla</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Usa smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1053,12 +1053,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1108,12 +1108,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1045,12 +1045,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
@@ -1045,12 +1045,6 @@ Fuzzy</comment>
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1045,12 +1045,6 @@
<value>Скопировать выделение или вставить</value> <value>Скопировать выделение или вставить</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+1 -7
View File
@@ -1044,12 +1044,6 @@
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
@@ -386,7 +386,7 @@ Fuzzy</comment>
<comment>ShellProfileSettings.xaml</comment> <comment>ShellProfileSettings.xaml</comment>
</data> </data>
<data name="LineEndingStyle.DoNotModify" xml:space="preserve"> <data name="LineEndingStyle.DoNotModify" xml:space="preserve">
<value>不改变结束行</value> <value>不改变</value>
<comment>ShellProfileSettings.xaml</comment> <comment>ShellProfileSettings.xaml</comment>
</data> </data>
<data name="EditAppBarButton.Label" xml:space="preserve"> <data name="EditAppBarButton.Label" xml:space="preserve">
@@ -968,52 +968,52 @@ Fuzzy</comment>
<value>单词分隔符</value> <value>单词分隔符</value>
</data> </data>
<data name="Recent.Text" xml:space="preserve"> <data name="Recent.Text" xml:space="preserve">
<value>最近</value> <value>Recent</value>
</data> </data>
<data name="NewTab_Description" xml:space="preserve"> <data name="NewTab_Description" xml:space="preserve">
<value>Opens default profile in a new tab.</value> <value>Opens default profile in a new tab.</value>
</data> </data>
<data name="NewSshTab_Description" xml:space="preserve"> <data name="NewSshTab_Description" xml:space="preserve">
<value>从新标签页打开SSH或Mosh会话</value> <value>Opens a new SSH or Mosh session in a new tab.</value>
</data> </data>
<data name="NewQuickTab_Description" xml:space="preserve"> <data name="NewQuickTab_Description" xml:space="preserve">
<value>Opens &quot;Quick Launch&quot; dialog and starts session in a new tab.</value> <value>Opens &quot;Quick Launch&quot; dialog and starts session in a new tab.</value>
</data> </data>
<data name="Settings_Description" xml:space="preserve"> <data name="Settings_Description" xml:space="preserve">
<value>打开设置页面</value> <value>Opens settings window.</value>
</data> </data>
<data name="About_Description" xml:space="preserve"> <data name="About_Description" xml:space="preserve">
<value>关于</value> <value>Basic info about the app.</value>
</data> </data>
<data name="Recent_Description" xml:space="preserve"> <data name="Recent_Description" xml:space="preserve">
<value>最近打开的会话</value> <value>Recently opened sessions.</value>
</data> </data>
<data name="MenuItem_DefaultProfile_Description" xml:space="preserve"> <data name="MenuItem_DefaultProfile_Description" xml:space="preserve">
<value>Starts new terminal session based on the default profile.</value> <value>Starts new terminal session based on the default profile.</value>
</data> </data>
<data name="MenuItem_Remote_Text" xml:space="preserve"> <data name="MenuItem_Remote_Text" xml:space="preserve">
<value>远程连接</value> <value>Remote Connect...</value>
</data> </data>
<data name="MenuItem_Remote_Description" xml:space="preserve"> <data name="MenuItem_Remote_Description" xml:space="preserve">
<value>Opens a dialog for launching a new SSH or Mosh terminal session.</value> <value>Opens a dialog for launching a new SSH or Mosh terminal session.</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Text" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Text" xml:space="preserve">
<value>快捷启动</value> <value>Quick Launch...</value>
</data> </data>
<data name="MenuItem_QuickLaunch_Description" xml:space="preserve"> <data name="MenuItem_QuickLaunch_Description" xml:space="preserve">
<value>Opens a &quot;Quick Launch&quot; dialog for starting a new terminal session.</value> <value>Opens a &quot;Quick Launch&quot; dialog for starting a new terminal session.</value>
</data> </data>
<data name="MenuItem_NewWindow_Text" xml:space="preserve"> <data name="MenuItem_NewWindow_Text" xml:space="preserve">
<value>新建窗口</value> <value>New Window</value>
</data> </data>
<data name="MenuItem_NewTab_Text" xml:space="preserve"> <data name="MenuItem_NewTab_Text" xml:space="preserve">
<value>新建标签页</value> <value>New Tab</value>
</data> </data>
<data name="MenuItem_NewTab_Description" xml:space="preserve"> <data name="MenuItem_NewTab_Description" xml:space="preserve">
<value>从新标签页启动会话</value> <value>Launches a session in a new tab.</value>
</data> </data>
<data name="MenuItem_NewWindow_Description" xml:space="preserve"> <data name="MenuItem_NewWindow_Description" xml:space="preserve">
<value>从新窗口启动会话</value> <value>Launches a session in a new window.</value>
</data> </data>
<data name="AppMenuType.Text" xml:space="preserve"> <data name="AppMenuType.Text" xml:space="preserve">
<value>Application menu type</value> <value>Application menu type</value>
@@ -1025,16 +1025,16 @@ Fuzzy</comment>
<value>&quot;New Tab&quot; / &quot;New Window&quot; cascading</value> <value>&quot;New Tab&quot; / &quot;New Window&quot; cascading</value>
</data> </data>
<data name="Quit.Text" xml:space="preserve"> <data name="Quit.Text" xml:space="preserve">
<value>退出</value> <value>Quit</value>
</data> </data>
<data name="Quit.Description" xml:space="preserve"> <data name="Quit.Description" xml:space="preserve">
<value>退出关于</value> <value>Quit Fluent Terminal</value>
</data> </data>
<data name="EnabledByPolicy" xml:space="preserve"> <data name="EnabledByPolicy" xml:space="preserve">
<value>Enabled by policy.</value> <value>Enabled by policy.</value>
</data> </data>
<data name="BackgroundImage.Content" xml:space="preserve"> <data name="BackgroundImage.Content" xml:space="preserve">
<value>背景图片</value> <value>Background image</value>
</data> </data>
<data name="AcrylicBackground.Header" xml:space="preserve"> <data name="AcrylicBackground.Header" xml:space="preserve">
<value>Use acrylic background</value> <value>Use acrylic background</value>
@@ -1043,33 +1043,27 @@ Fuzzy</comment>
<value>Are you sure you want to delete this background image?</value> <value>Are you sure you want to delete this background image?</value>
</data> </data>
<data name="Colors.Text" xml:space="preserve"> <data name="Colors.Text" xml:space="preserve">
<value>颜色</value> <value>Colors</value>
</data> </data>
<data name="BrightColors.Text" xml:space="preserve"> <data name="BrightColors.Text" xml:space="preserve">
<value>亮色</value> <value>Bright colors</value>
</data> </data>
<data name="CopyLink" xml:space="preserve"> <data name="CopyLink" xml:space="preserve">
<value>复制链接</value> <value>Copy link</value>
</data> </data>
<data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWithRegex.ToolTipService.ToolTip" xml:space="preserve">
<value>正则表达式</value> <value>Regex</value>
</data> </data>
<data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchWholeWord.ToolTipService.ToolTip" xml:space="preserve">
<value>Whole word</value> <value>Whole word</value>
</data> </data>
<data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve"> <data name="SearchCaseSensitive.ToolTipService.ToolTip" xml:space="preserve">
<value>大小写敏感</value> <value>Case sensitive</value>
</data> </data>
<data name="MouseCopySelectionOrPaste.Content" xml:space="preserve"> <data name="MouseCopySelectionOrPaste.Content" xml:space="preserve">
<value>Copy selection or paste</value> <value>Copy selection or paste</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
@@ -1044,12 +1044,6 @@
<value>复制或粘贴选中区域</value> <value>复制或粘贴选中区域</value>
</data> </data>
<data name="UseBuffer" xml:space="preserve"> <data name="UseBuffer" xml:space="preserve">
<value>Use smart buffer</value> <value>Use buffer</value>
</data>
<data name="RestartSession.Content" xml:space="preserve">
<value>Restart</value>
</data>
<data name="SessionDisconnected.Text" xml:space="preserve">
<value>Session disconnected.</value>
</data> </data>
</root> </root>
+15 -15
View File
@@ -1,19 +1,19 @@
<ResourceDictionary <ResourceDictionary
x:Class="FluentTerminal.App.Views.TemplateSelectors" x:Class="FluentTerminal.App.Views.TemplateSelectors"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:FluentTerminal.App.Views"
xmlns:models="using:FluentTerminal.Models" xmlns:models="using:FluentTerminal.Models"
xmlns:settings="using:FluentTerminal.App.ViewModels.Settings" xmlns:settings="using:FluentTerminal.App.ViewModels.Settings">
xmlns:views="using:FluentTerminal.App.Views">
<views:TabThemeTemplateSelector x:Key="TabThemeTemplateSelector"> <views:TabThemeTemplateSelector x:Key="TabThemeTemplateSelector">
<views:TabThemeTemplateSelector.DefaultTemplate> <views:TabThemeTemplateSelector.DefaultTemplate>
<DataTemplate x:DataType="models:TabTheme"> <DataTemplate x:DataType="models:TabTheme">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<FontIcon <FontIcon
Margin="0,0,6,0" Margin="0,0,6,0"
FontFamily="{StaticResource SymbolThemeFontFamily}" FontFamily="{StaticResource SymbolThemeFontFamily}"
Glyph="&#xF126;" /> Glyph="&#xF126;" />
<TextBlock Text="{x:Bind Name, Mode=OneWay, Converter={StaticResource I18NConverter}}" /> <TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</views:TabThemeTemplateSelector.DefaultTemplate> </views:TabThemeTemplateSelector.DefaultTemplate>
@@ -21,11 +21,11 @@
<DataTemplate x:DataType="models:TabTheme"> <DataTemplate x:DataType="models:TabTheme">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<FontIcon <FontIcon
Margin="0,0,6,0" Margin="0,0,6,0"
FontFamily="{StaticResource SymbolThemeFontFamily}" FontFamily="{StaticResource SymbolThemeFontFamily}"
Foreground="{x:Bind Color}" Foreground="{x:Bind Color}"
Glyph="&#xF127;" /> Glyph="&#xF127;" />
<TextBlock Text="{x:Bind Name, Mode=OneWay, Converter={StaticResource I18NConverter}}" /> <TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</views:TabThemeTemplateSelector.ColoredTemplate> </views:TabThemeTemplateSelector.ColoredTemplate>
@@ -51,9 +51,9 @@
<views:TerminalKeybindTemplateSelector.KeyBindingsTemplate> <views:TerminalKeybindTemplateSelector.KeyBindingsTemplate>
<DataTemplate x:DataType="settings:KeyBindingsViewModel"> <DataTemplate x:DataType="settings:KeyBindingsViewModel">
<views:KeyBindingsView <views:KeyBindingsView
Margin="0,6,0,0" Margin="0,6,0,0"
ShowCommandName="False" ShowCommandName="False"
ViewModel="{x:Bind}" /> ViewModel="{x:Bind}" />
</DataTemplate> </DataTemplate>
</views:TerminalKeybindTemplateSelector.KeyBindingsTemplate> </views:TerminalKeybindTemplateSelector.KeyBindingsTemplate>
</views:TerminalKeybindTemplateSelector> </views:TerminalKeybindTemplateSelector>
@@ -124,34 +124,5 @@
</Button> </Button>
</StackPanel> </StackPanel>
</controls:DropShadowPanel> </controls:DropShadowPanel>
<controls:DropShadowPanel
Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
ShadowOpacity="0.5"
Visibility="{x:Bind ViewModel.HasExitedWithError, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}"
Color="Black">
<StackPanel
Padding="6"
CornerRadius="4"
Orientation="Horizontal">
<StackPanel.Background>
<SolidColorBrush Color="{ThemeResource SystemAccentColor}" />
</StackPanel.Background>
<TextBlock
x:Uid="SessionDisconnected"
Margin="6,0,24,0"
VerticalAlignment="Center"
Text="Session disconnected" />
<Button
x:Uid="RestartSession"
Margin="0,0,6,0"
Command="{x:Bind ViewModel.ReconnectTabCommand, Mode=OneWay}"
Content="Restart"
IsEnabled="{x:Bind ViewModel.HasExitedWithError, Mode=OneWay}" />
<Button Command="{x:Bind ViewModel.CloseCommand, Mode=OneWay}" Content="{x:Bind Converter={StaticResource I18NConverter}, ConverterParameter=Close}" />
</StackPanel>
</controls:DropShadowPanel>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -224,18 +224,7 @@ namespace FluentTerminal.App.Views
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task ReconnectAsync()
{
// Fill full screen with new output to not allow erasing of available output on start of new shell
Terminal_OutputReceived(this, System.Text.Encoding.UTF8.GetBytes(new String('\n', _requestedSize.Rows + 1)));
if (true == await StartShellProcessAsync(_requestedSize).ConfigureAwait(false))
{
ViewModel.Terminal.Reconnect();
}
}
public async Task InitializeAsync(TerminalViewModel viewModel) public async Task InitializeAsync(TerminalViewModel viewModel)
{ {
ViewModel = viewModel; ViewModel = viewModel;
@@ -266,6 +255,12 @@ namespace FluentTerminal.App.Views
// Waiting for IxtermEventListener.OnInitialized() call to happen // Waiting for IxtermEventListener.OnInitialized() call to happen
await _tcsConnected.Task; await _tcsConnected.Task;
var sessionType =
ViewModel.ShellProfile.UseConPty &&
ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7)
? SessionType.ConPty
: SessionType.WinPty;
lock (_resizeLock) lock (_resizeLock)
{ {
// Check to see if some resizing has happened meanwhile // Check to see if some resizing has happened meanwhile
@@ -279,8 +274,14 @@ namespace FluentTerminal.App.Views
} }
} }
if (false == await StartShellProcessAsync(size).ConfigureAwait(false)) var response = await ViewModel.Terminal.StartShellProcessAsync(ViewModel.ShellProfile, size, sessionType, ViewModel.XtermBufferState).ConfigureAwait(false);
if (!response.Success)
{ {
await Dispatcher.ExecuteAsync(async () =>
await ViewModel.DialogService.ShowMessageDialogAsync("Error", response.Error, DialogButton.OK))
.ConfigureAwait(false);
ViewModel.Terminal.ReportLauchFailed();
return; return;
} }
@@ -561,25 +562,5 @@ namespace FluentTerminal.App.Views
} }
public void Paste(string text) => OnPaste?.Invoke(this, text); public void Paste(string text) => OnPaste?.Invoke(this, text);
private async Task<bool> StartShellProcessAsync(TerminalSize size)
{
var sessionType = ViewModel.ShellProfile.UseConPty &&
ViewModel.ApplicationView.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7)
? SessionType.ConPty
: SessionType.WinPty;
var response = await ViewModel.Terminal.StartShellProcessAsync(ViewModel.ShellProfile, size, sessionType, ViewModel.XtermBufferState).ConfigureAwait(false);
if (!response.Success)
{
await Dispatcher.ExecuteAsync(async () =>
await ViewModel.DialogService.ShowMessageDialogAsync("Error", response.Error, DialogButton.OK))
.ConfigureAwait(false);
ViewModel.Terminal.ReportLauchFailed();
return false;
}
return true;
}
} }
} }
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -5,7 +5,7 @@
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "xcopy /y /s node_modules\\xterm\\css\\xterm.css dist\\ && xcopy /y /s src\\style.css dist\\ && xcopy /y /s src\\index.html dist\\ && cd node_modules/xterm && yarn && yarn package && cd ../.. && npx webpack --config webpack.config.js" "build": "xcopy /y /s node_modules\\xterm\\css\\xterm.css dist\\ && xcopy /y /s src\\style.css dist\\ && xcopy /y /s src\\index.html dist\\ && npx webpack --config webpack.config.js"
}, },
"author": "FS Apps", "author": "FS Apps",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -16,7 +16,7 @@
"webpack-cli": "^3.2.3" "webpack-cli": "^3.2.3"
}, },
"dependencies": { "dependencies": {
"xterm": "felixse/xterm.js#fix-edge-selection-color", "xterm": "^4.4.0",
"xterm-addon-fit": "^0.3.0", "xterm-addon-fit": "^0.3.0",
"xterm-addon-search": "^0.5.0", "xterm-addon-search": "^0.5.0",
"xterm-addon-serialize": "^0.1.2", "xterm-addon-serialize": "^0.1.2",
+1 -9
View File
@@ -29,7 +29,6 @@ let searchAddon: SearchAddon;
let serializeAddon: SerializeAddon; let serializeAddon: SerializeAddon;
let webLinksAddon: WebLinksAddon; let webLinksAddon: WebLinksAddon;
let unicode11Addon: Unicode11Addon; let unicode11Addon: Unicode11Addon;
let altGrPressed = false;
const terminalContainer = document.getElementById('terminal-container'); const terminalContainer = document.getElementById('terminal-container');
@@ -166,16 +165,9 @@ window.createTerminal = (options, theme, keyBindings) => {
}); });
term.attachCustomKeyEventHandler(function (e) { term.attachCustomKeyEventHandler(function (e) {
if (e.altKey && e.type === "keydown" && e.location === 2) { if (e.type != "keydown") {
altGrPressed = true;
} else if (e.altKey && e.type === "keyup" && e.key === "Control") {
altGrPressed = false
}
if (e.type != "keydown" || altGrPressed) {
return true; return true;
} }
for (var i = 0; i < window.keyBindings.length; i++) { for (var i = 0; i < window.keyBindings.length; i++) {
var keyBinding = window.keyBindings[i]; var keyBinding = window.keyBindings[i];
if (keyBinding.ctrl == e.ctrlKey if (keyBinding.ctrl == e.ctrlKey
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -30,7 +30,6 @@
SwitchToTerm7, SwitchToTerm7,
SwitchToTerm8, SwitchToTerm8,
SwitchToTerm9, SwitchToTerm9,
DuplicateTab, DuplicateTab
ReconnectTab
} }
} }
+1 -2
View File
@@ -20,7 +20,6 @@
GetCommandPathRequest = 15, GetCommandPathRequest = 15,
PauseTerminalOutputRequest = 16, PauseTerminalOutputRequest = 16,
PauseTerminalOutputResponse = 17, PauseTerminalOutputResponse = 17,
QuitApplicationRequest = 18, QuitApplicationRequest = 18
ReadTextFileRequest = 19
} }
} }
@@ -1,9 +0,0 @@
namespace FluentTerminal.Models.Requests
{
public class ReadTextFileRequest : IMessage
{
public byte Identifier => (byte)MessageIdentifiers.ReadTextFileRequest;
public string Path { get; set; }
}
}
@@ -1,7 +1,11 @@
namespace FluentTerminal.Models.Responses namespace FluentTerminal.Models.Responses
{ {
public class CommonResponse : TerminalResponse public class CommonResponse : IMessage
{ {
public override byte Identifier => (byte) MessageIdentifiers.CommonResponse; public virtual byte Identifier => (byte) MessageIdentifiers.CommonResponse;
public bool Success { get; set; }
public string Error { get; set; }
} }
} }
@@ -1,8 +1,8 @@
namespace FluentTerminal.Models.Responses namespace FluentTerminal.Models.Responses
{ {
public class CreateTerminalResponse : TerminalResponse public class CreateTerminalResponse : TerminalResponse, IMessage
{ {
public override byte Identifier => (byte) MessageIdentifiers.CreateTerminalResponse; public byte Identifier => (byte) MessageIdentifiers.CreateTerminalResponse;
public string Name { get; set; } public string Name { get; set; }
} }
@@ -1,7 +1,7 @@
namespace FluentTerminal.Models.Responses namespace FluentTerminal.Models.Responses
{ {
public class PauseTerminalOutputResponse : TerminalResponse public class PauseTerminalOutputResponse : TerminalResponse, IMessage
{ {
public override byte Identifier => (byte) MessageIdentifiers.PauseTerminalOutputResponse; public byte Identifier => (byte) MessageIdentifiers.PauseTerminalOutputResponse;
} }
} }
@@ -1,8 +1,11 @@
namespace FluentTerminal.Models.Responses using System;
using System.Collections.Generic;
using System.Text;
namespace FluentTerminal.Models.Responses
{ {
public abstract class TerminalResponse : IMessage public class TerminalResponse
{ {
public abstract byte Identifier { get; }
public bool Success { get; set; } public bool Success { get; set; }
public string Error { get; set; } public string Error { get; set; }
} }
@@ -59,6 +59,7 @@
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en-US</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled> <AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<PackageCertificateKeyFile>FluentTerminal.App_StoreKey.pfx</PackageCertificateKeyFile>
<EntryPointProjectUniqueName>$(SolutionDir)FluentTerminal.App\FluentTerminal.App.csproj</EntryPointProjectUniqueName> <EntryPointProjectUniqueName>$(SolutionDir)FluentTerminal.App\FluentTerminal.App.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile> <GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision> <AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
@@ -66,8 +67,7 @@
<AppxBundlePlatforms>x64</AppxBundlePlatforms> <AppxBundlePlatforms>x64</AppxBundlePlatforms>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks> <HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm> <AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<PackageCertificateThumbprint>9E39FAC27F1AEA1BAC9BCF60FF29B32B354E0E93</PackageCertificateThumbprint> <PackageCertificateThumbprint>8A5DE5B20B663BD6A99B44C5BA278289521F419F</PackageCertificateThumbprint>
<AppxPackageSigningTimestampServerUrl>http://timestamp.comodoca.com</AppxPackageSigningTimestampServerUrl>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
+1 -1
View File
@@ -8,7 +8,7 @@
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
IgnorableNamespaces="uap rescap desktop"> IgnorableNamespaces="uap rescap desktop">
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=Felix Seidl, O=Felix Seidl, STREET=Kapellenstr. 4, L=Nürnberg, S=Bayern, PostalCode=90478, C=DE" Version="0.7.0.0" /> <Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.6.1.0" />
<Properties> <Properties>
<DisplayName>Fluent Terminal</DisplayName> <DisplayName>Fluent Terminal</DisplayName>
@@ -53,9 +53,6 @@ namespace FluentTerminal.SystemTray
{ {
while (true) while (true)
{ {
// Allow CPU to jump between TerminalSessions' ReadingLoop Tasks
await Task.Delay(1).ConfigureAwait(false);
bool paused; bool paused;
lock (_lock) lock (_lock)
@@ -134,9 +134,6 @@ namespace FluentTerminal.SystemTray.Services
case MessageIdentifiers.QuitApplicationRequest: case MessageIdentifiers.QuitApplicationRequest:
HandleQuitApplicationRequest(); HandleQuitApplicationRequest();
return; return;
case MessageIdentifiers.ReadTextFileRequest:
await HandleReadTextFileRequestAsync(args).ConfigureAwait(false);
return;
default: default:
Logger.Instance.Error("Received unknown message type: {messageType}", messageType); Logger.Instance.Error("Received unknown message type: {messageType}", messageType);
return; return;
@@ -217,31 +214,6 @@ namespace FluentTerminal.SystemTray.Services
deferral.Complete(); deferral.Complete();
} }
private async Task HandleReadTextFileRequestAsync(AppServiceRequestReceivedEventArgs args)
{
var deferreal = args.GetDeferral();
var deferral = args.GetDeferral();
var messageContent = (string)args.Request.Message[MessageKeys.Content];
var request = JsonConvert.DeserializeObject<ReadTextFileRequest>(messageContent);
var response = new StringValueResponse();
try
{
var content = Utilities.ReadFile(request.Path);
response.Success = true;
response.Value = content;
}
catch (Exception e)
{
response.Success = false;
response.Error = e.Message;
}
await args.Request.SendResponseAsync(CreateMessage(response));
deferral.Complete();
}
private async Task HandleGetSshConfigFolderRequestAsync(AppServiceRequestReceivedEventArgs args) private async Task HandleGetSshConfigFolderRequestAsync(AppServiceRequestReceivedEventArgs args)
{ {
var deferral = args.GetDeferral(); var deferral = args.GetDeferral();
@@ -22,7 +22,6 @@ namespace FluentTerminal.SystemTray.Services.ConPty
public void Close() public void Close()
{ {
_terminal?.ConsoleOutStream?.Close();
_reader?.Dispose(); _reader?.Dispose();
ConnectionClosed?.Invoke(this, _terminal.ExitCode); ConnectionClosed?.Invoke(this, _terminal.ExitCode);
@@ -92,11 +91,6 @@ namespace FluentTerminal.SystemTray.Services.ConPty
_reader?.SetPaused(value); _reader?.SetPaused(value);
} }
~ConPtySession()
{
Dispose(false);
}
#region IDisposable Support #region IDisposable Support
private bool _disposed; private bool _disposed;
@@ -119,7 +113,6 @@ namespace FluentTerminal.SystemTray.Services.ConPty
public void Dispose() public void Dispose()
{ {
_terminal.Exited -= _terminal_Exited; _terminal.Exited -= _terminal_Exited;
_terminal.OutputReady -= _terminal_OutputReady;
Dispose(true); Dispose(true);
} }
@@ -175,7 +175,6 @@ namespace FluentTerminal.SystemTray.Services.ConPty
if (disposeManaged) if (disposeManaged)
{ {
ConsoleOutStream.Dispose();
_consoleInputWriter?.Dispose(); _consoleInputWriter?.Dispose();
_process?.Dispose(); _process?.Dispose();
_pseudoConsole?.Dispose(); _pseudoConsole?.Dispose();
-5
View File
@@ -536,11 +536,6 @@ namespace FluentTerminal.SystemTray
} }
} }
internal static string ReadFile(string path)
{
return File.ReadAllText(path);
}
private static bool? MuteProcess(int id, bool mute, bool force = false) private static bool? MuteProcess(int id, bool mute, bool force = false)
{ {
bool? isAudioSessionMuted = VolumeControl.GetAudioSessionMute(id); bool? isAudioSessionMuted = VolumeControl.GetAudioSessionMute(id);
+643
View File
@@ -0,0 +1,643 @@
#
# Install.ps1 is a PowerShell script designed to sideload the Fluent Terminal
# app. To run this script from Explorer, right-click on its icon and choose
# "Run with PowerShell".
#
# It is heavily based on the Add-AppxDevPackage.ps1 script generated by
# Visual Studio. Visual Studio supplies this script in the folder generated
# with its "Prepare Package" command. The same folder will also contain the
# app package (a .appx file), the signing certificate (a .cer file), and a
# "Dependencies" subfolder containing all the framework packages used by the
# app.
#
# This script simplifies installing these packages by automating the
# following functions:
# 1. Find the app package and signing certificate in the script directory
# 2. Prompt the user to enable sideloading apps and to install the
# certificate if necessary
# 3. Find dependency packages that are applicable to the operating system's
# CPU architecture
# 4. Install the package along with all applicable dependencies
#
# All command line parameters are reserved for use internally by the script.
# Users should launch this script from Explorer.
#
# .Link
# http://go.microsoft.com/fwlink/?LinkId=243053
param(
[switch]$Force = $false,
[switch]$ForceContextMenu = $false,
[switch]$GetDeveloperLicense = $false,
[string]$CertificatePath = $null
)
$ErrorActionPreference = "Stop"
# UI Strings
class UiStrings
{
static [string] $PromptYesString = "&Yes"
static [string] $PromptNoString = "&No"
static [string] $BundleFound = "Found bundle: {0}"
static [string] $PackageFound = "Found package: {0}"
static [string] $CertificateFound = "Found certificate: {0}"
static [string] $DependenciesFound = "Found dependency package(s):"
static [string] $GettingDeveloperLicense = "Please select `"Sideload apps`" or `"Developer mode`" in the following window."
static [string] $InstallingCertificate = "Installing certificate..."
static [string] $InstallingPackage = "`nInstalling app..."
static [string] $AcquireLicenseSuccessful = "Sideloading was successfully enabled."
static [string] $InstallCertificateSuccessful = "The certificate was successfully installed."
static [string] $PromptContextMenuInstall = "Fluent Terminal can be integrated into the Windows Explorer context menu, allowing you to quickly open the terminal to a directory.`n`nDo you want to enable this integration?`n`n"
static [string] $Success = "`nSuccess: Your app was successfully installed."
static [string] $WarningInstallCert = "`nYou are about to install a digital certificate to your computer's Trusted People certificate store. Doing so carries serious security risk and should only be done if you trust the originator of this digital certificate.`n`nWhen you are done using this app, you should manually remove the associated digital certificate. Instructions for doing so can be found here: http://go.microsoft.com/fwlink/?LinkId=243053`n`nAre you sure you wish to continue?`n`n"
static [string] $ElevateActions = "`nBefore installing this app, you need to do the following:"
static [string] $ElevateActionDevLicense = "`t- Enable sideloading apps"
static [string] $ElevateActionCertificate = "`t- Install the signing certificate"
static [string] $ElevateActionsContinue = "Administrator credentials are required to continue. Please accept the UAC prompt and provide your administrator password if asked."
static [string] $ErrorForceElevate = "You must provide administrator credentials to proceed. Please run this script without the -Force parameter or from an elevated PowerShell window."
static [string] $ErrorForceDeveloperLicense = "Enabling sideloading requires user interaction. Please rerun the script without the -Force parameter."
static [string] $ErrorLaunchAdminFailed = "Error: Could not start a new process as administrator."
static [string] $ErrorNoScriptPath = "Error: You must launch this script from a file."
static [string] $ErrorNoPackageFound = "Error: No package or bundle found in the script directory. Please make sure the package or bundle you want to install is placed in the same directory as this script."
static [string] $ErrorManyPackagesFound = "Error: More than one package or bundle found in the script directory. Please make sure only the package or bundle you want to install is placed in the same directory as this script."
static [string] $ErrorPackageUnsigned = "Error: The package or bundle is not digitally signed or its signature is corrupted."
static [string] $ErrorNoCertificateFound = "Error: No certificate found in the script directory. Please make sure the certificate used to sign the package or bundle you are installing is placed in the same directory as this script."
static [string] $ErrorManyCertificatesFound = "Error: More than one certificate found in the script directory. Please make sure only the certificate used to sign the package or bundle you are installing is placed in the same directory as this script."
static [string] $ErrorBadCertificate = "Error: The file `"{0}`" is not a valid digital certificate. CertUtil returned with error code {1}."
static [string] $ErrorExpiredCertificate = "Error: The developer certificate `"{0}`" has expired. One possible cause is the system clock isn't set to the correct date and time. If the system settings are correct, contact the app owner to re-create a package or bundle with a valid certificate."
static [string] $ErrorCertificateMismatch = "Error: The certificate does not match the one used to sign the package or bundle."
static [string] $ErrorCertIsCA = "Error: The certificate can't be a certificate authority."
static [string] $ErrorBannedKeyUsage = "Error: The certificate can't have the following key usage: {0}. Key usage must be unspecified or equal to `"DigitalSignature`"."
static [string] $ErrorBannedEKU = "Error: The certificate can't have the following extended key usage: {0}. Only the Code Signing and Lifetime Signing EKUs are allowed."
static [string] $ErrorNoBasicConstraints = "Error: The certificate is missing the basic constraints extension."
static [string] $ErrorNoCodeSigningEku = "Error: The certificate is missing the extended key usage for Code Signing."
static [string] $ErrorInstallCertificateCancelled = "Error: Installation of the certificate was cancelled."
static [string] $ErrorCertUtilInstallFailed = "Error: Could not install the certificate. CertUtil returned with error code {0}."
static [string] $ErrorGetDeveloperLicenseFailed = "Error: Sideloading is not enabled."
static [string] $ErrorInstallCertificateFailed = "Error: Could not install the certificate. Status: {0}. For more information, see http://go.microsoft.com/fwlink/?LinkID=252740."
static [string] $ErrorAddPackageFailed = "Error: Could not install the app."
static [string] $ErrorAddPackageFailedWithCert = "Error: Could not install the app. To ensure security, please consider uninstalling the signing certificate until you can install the app. Instructions for doing so can be found here:`nhttp://go.microsoft.com/fwlink/?LinkId=243053}"
}
$ScriptPath = $null
try
{
$ScriptPath = (Get-Variable MyInvocation).Value.MyCommand.Path
$ScriptDir = Split-Path -Parent $ScriptPath
}
catch {}
if (!$ScriptPath)
{
PrintMessageAndExit ([UiStrings]::ErrorNoScriptPath) $ErrorCodes.NoScriptPath
}
$ErrorCodes = Data {
ConvertFrom-StringData @'
Success = 0
NoScriptPath = 1
NoPackageFound = 2
ManyPackagesFound = 3
NoCertificateFound = 4
ManyCertificatesFound = 5
BadCertificate = 6
PackageUnsigned = 7
CertificateMismatch = 8
ForceElevate = 9
LaunchAdminFailed = 10
GetDeveloperLicenseFailed = 11
InstallCertificateFailed = 12
AddPackageFailed = 13
ForceDeveloperLicense = 14
CertUtilInstallFailed = 17
CertIsCA = 18
BannedEKU = 19
NoBasicConstraints = 20
NoCodeSigningEku = 21
InstallCertificateCancelled = 22
BannedKeyUsage = 23
ExpiredCertificate = 24
'@
}
function PrintMessageAndExit($ErrorMessage, $ReturnCode)
{
Write-Host $ErrorMessage
if (!$Force)
{
Pause
}
exit $ReturnCode
}
#
# Warns the user about installing certificates, and presents a Yes/No prompt
# to confirm the action. The default is set to No.
#
function ConfirmCertificateInstall
{
$Answer = $host.UI.PromptForChoice(
"",
([UiStrings]::WarningInstallCert),
[System.Management.Automation.Host.ChoiceDescription[]]@(([UiStrings]::PromptYesString), ([UiStrings]::PromptNoString)),
1)
return $Answer -eq 0
}
#
# Asks the user whether they want to install context menu integration for Fluent Terminal,
# and presents a Yes/No prompt to confirm the action. The default is set to No.
#
function ConfirmContextMenuInstall
{
$Answer = $host.UI.PromptForChoice(
"",
([UiStrings]::PromptContextMenuInstall),
[System.Management.Automation.Host.ChoiceDescription[]]@(([UiStrings]::PromptYesString), ([UiStrings]::PromptNoString)),
1)
return $Answer -eq 0
}
#
# Validates whether a file is a valid certificate using CertUtil.
# This needs to be done before calling Get-PfxCertificate on the file, otherwise
# the user will get a cryptic "Password: " prompt for invalid certs.
#
function ValidateCertificateFormat($FilePath)
{
# certutil -verify prints a lot of text that we don't need, so it's redirected to $null here
certutil.exe -verify $FilePath > $null
if ($LastExitCode -lt 0)
{
PrintMessageAndExit (([UiStrings]::ErrorBadCertificate) -f $FilePath, $LastExitCode) $ErrorCodes.BadCertificate
}
# Check if certificate is expired
$cert = Get-PfxCertificate $FilePath
if (($cert.NotBefore -gt (Get-Date)) -or ($cert.NotAfter -lt (Get-Date)))
{
PrintMessageAndExit (([UiStrings]::ErrorExpiredCertificate) -f $FilePath) $ErrorCodes.ExpiredCertificate
}
}
#
# Verify that the developer certificate meets the following restrictions:
# - The certificate must contain a Basic Constraints extension, and its
# Certificate Authority (CA) property must be false.
# - The certificate's Key Usage extension must be either absent, or set to
# only DigitalSignature.
# - The certificate must contain an Extended Key Usage (EKU) extension with
# Code Signing usage.
# - The certificate must NOT contain any other EKU except Code Signing and
# Lifetime Signing.
#
# These restrictions are enforced to decrease security risks that arise from
# trusting digital certificates.
#
function CheckCertificateRestrictions
{
Set-Variable -Name BasicConstraintsExtensionOid -Value "2.5.29.19" -Option Constant
Set-Variable -Name KeyUsageExtensionOid -Value "2.5.29.15" -Option Constant
Set-Variable -Name EkuExtensionOid -Value "2.5.29.37" -Option Constant
Set-Variable -Name CodeSigningEkuOid -Value "1.3.6.1.5.5.7.3.3" -Option Constant
Set-Variable -Name LifetimeSigningEkuOid -Value "1.3.6.1.4.1.311.10.3.13" -Option Constant
$CertificateExtensions = (Get-PfxCertificate $CertificatePath).Extensions
$HasBasicConstraints = $false
$HasCodeSigningEku = $false
foreach ($Extension in $CertificateExtensions)
{
# Certificate must contain the Basic Constraints extension
if ($Extension.oid.value -eq $BasicConstraintsExtensionOid)
{
# CA property must be false
if ($Extension.CertificateAuthority)
{
PrintMessageAndExit ([UiStrings]::ErrorCertIsCA) $ErrorCodes.CertIsCA
}
$HasBasicConstraints = $true
}
# If key usage is present, it must be set to digital signature
elseif ($Extension.oid.value -eq $KeyUsageExtensionOid)
{
if ($Extension.KeyUsages -ne "DigitalSignature")
{
PrintMessageAndExit (([UiStrings]::ErrorBannedKeyUsage) -f $Extension.KeyUsages) $ErrorCodes.BannedKeyUsage
}
}
elseif ($Extension.oid.value -eq $EkuExtensionOid)
{
# Certificate must contain the Code Signing EKU
$EKUs = $Extension.EnhancedKeyUsages.Value
if ($EKUs -contains $CodeSigningEkuOid)
{
$HasCodeSigningEKU = $True
}
# EKUs other than code signing and lifetime signing are not allowed
foreach ($EKU in $EKUs)
{
if ($EKU -ne $CodeSigningEkuOid -and $EKU -ne $LifetimeSigningEkuOid)
{
PrintMessageAndExit (([UiStrings]::ErrorBannedEKU) -f $EKU) $ErrorCodes.BannedEKU
}
}
}
}
if (!$HasBasicConstraints)
{
PrintMessageAndExit ([UiStrings]::ErrorNoBasicConstraints) $ErrorCodes.NoBasicConstraints
}
if (!$HasCodeSigningEKU)
{
PrintMessageAndExit ([UiStrings]::ErrorNoCodeSigningEku) $ErrorCodes.NoCodeSigningEku
}
}
#
# Performs operations that require administrative privileges:
# - Prompt the user to obtain a developer license
# - Install the developer certificate (if -Force is not specified, also prompts the user to confirm)
#
function DoElevatedOperations
{
if ($GetDeveloperLicense)
{
if ($Force)
{
PrintMessageAndExit ([UiStrings]::ErrorForceDeveloperLicense) $ErrorCodes.ForceDeveloperLicense
}
Write-Host ([UiStrings]::GettingDeveloperLicense)
Pause
try
{
Show-WindowsDeveloperLicenseRegistration
Pause
}
catch
{
$Error[0] # Dump details about the last error
PrintMessageAndExit ([UiStrings]::ErrorGetDeveloperLicenseFailed) $ErrorCodes.GetDeveloperLicenseFailed
}
}
if ($CertificatePath)
{
Write-Host ([UiStrings]::InstallingCertificate)
# Make sure certificate format is valid and usage constraints are followed
ValidateCertificateFormat $CertificatePath
CheckCertificateRestrictions
# If -Force is not specified, warn the user and get consent
if ($Force -or (ConfirmCertificateInstall))
{
# Add cert to store
certutil.exe -addstore TrustedPeople $CertificatePath
if ($LastExitCode -lt 0)
{
PrintMessageAndExit (([UiStrings]::ErrorCertUtilInstallFailed) -f $LastExitCode) $ErrorCodes.CertUtilInstallFailed
}
}
else
{
PrintMessageAndExit ([UiStrings]::ErrorInstallCertificateCancelled) $ErrorCodes.InstallCertificateCancelled
}
}
}
#
# Checks whether the machine has app sideloading enabled.
#
function CheckIfNeedToEnableSideloading
{
$RegistryPath = "hklm:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
$ValueName = "AllowAllTrustedApps"
$Result = $true
if (Test-Path $RegistryPath)
{
$SideloadingEnabledProperty = (Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue)
if ($null -ne $SideloadingEnabledProperty -and $SideloadingEnabledProperty.$ValueName -eq 1)
{
$Result = $false
}
}
return $Result
}
#
# Launches an elevated process running the current script to perform tasks
# that require administrative privileges. This function waits until the
# elevated process terminates, and checks whether those tasks were successful.
#
function LaunchElevated
{
# Set up command line arguments to the elevated process
$RelaunchArgs = '-ExecutionPolicy Unrestricted -file "' + $ScriptPath + '"'
if ($Force)
{
$RelaunchArgs += ' -Force'
}
if ($NeedDeveloperLicense)
{
$RelaunchArgs += ' -GetDeveloperLicense'
}
if ($NeedInstallCertificate)
{
$RelaunchArgs += ' -CertificatePath "' + $DeveloperCertificatePath.FullName + '"'
}
# Launch the process and wait for it to finish
try
{
$AdminProcess = Start-Process "$PsHome\PowerShell.exe" -Verb RunAs -ArgumentList $RelaunchArgs -PassThru
}
catch
{
$Error[0] # Dump details about the last error
PrintMessageAndExit ([UiStrings]::ErrorLaunchAdminFailed) $ErrorCodes.LaunchAdminFailed
}
while (!($AdminProcess.HasExited))
{
Start-Sleep -Seconds 2
}
# Check if all elevated operations were successful
if ($NeedDeveloperLicense)
{
if (CheckIfNeedToEnableSideloading)
{
PrintMessageAndExit ([UiStrings]::ErrorGetDeveloperLicenseFailed) $ErrorCodes.GetDeveloperLicenseFailed
}
else
{
Write-Host ([UiStrings]::AcquireLicenseSuccessful)
}
}
if ($NeedInstallCertificate)
{
$Signature = Get-AuthenticodeSignature $DeveloperPackagePath -Verbose
if ($Signature.Status -ne "Valid")
{
PrintMessageAndExit (([UiStrings]::ErrorInstallCertificateFailed) -f $Signature.Status) $ErrorCodes.InstallCertificateFailed
}
else
{
Write-Host ([UiStrings]::InstallCertificateSuccessful)
}
}
}
#
# Finds all applicable dependency packages according to OS architecture, and
# installs the developer package with its dependencies. The expected layout
# of dependencies is:
#
# <current dir>
# \Dependencies
# <Architecture neutral dependencies>.appx
# \x86
# <x86 dependencies>.appx
# \x64
# <x64 dependencies>.appx
# \arm
# <arm dependencies>.appx
#
function InstallPackageWithDependencies
{
$DependencyPackagesDir = (Join-Path $ScriptDir "Dependencies")
$DependencyPackages = @()
if (Test-Path $DependencyPackagesDir)
{
# Get architecture-neutral dependencies
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "*.appx") | Where-Object { $_.Mode -NotMatch "d" }
# Get architecture-specific dependencies
if (($Env:Processor_Architecture -eq "x86" -or $Env:Processor_Architecture -eq "amd64") -and (Test-Path (Join-Path $DependencyPackagesDir "x86")))
{
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "x86\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
}
if (($Env:Processor_Architecture -eq "amd64") -and (Test-Path (Join-Path $DependencyPackagesDir "x64")))
{
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "x64\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
}
if (($Env:Processor_Architecture -eq "arm") -and (Test-Path (Join-Path $DependencyPackagesDir "arm")))
{
$DependencyPackages += Get-ChildItem (Join-Path $DependencyPackagesDir "arm\*.appx") | Where-Object { $_.Mode -NotMatch "d" }
}
}
Write-Host ([UiStrings]::InstallingPackage)
$AddPackageSucceeded = $False
try
{
if ($DependencyPackages.FullName.Count -gt 0)
{
Write-Host ([UiStrings]::DependenciesFound)
$DependencyPackages.FullName
Add-AppxPackage -Path $DeveloperPackagePath.FullName -DependencyPath $DependencyPackages.FullName -ForceApplicationShutdown -ForceUpdateFromAnyVersion
}
else
{
Add-AppxPackage -Path $DeveloperPackagePath.FullName -ForceApplicationShutdown -ForceUpdateFromAnyVersion
}
$AddPackageSucceeded = $?
}
catch
{
$Error[0] # Dump details about the last error
}
if (!$AddPackageSucceeded)
{
if ($NeedInstallCertificate)
{
PrintMessageAndExit ([UiStrings]::ErrorAddPackageFailedWithCert) $ErrorCodes.AddPackageFailed
}
else
{
PrintMessageAndExit ([UiStrings]::ErrorAddPackageFailed) $ErrorCodes.AddPackageFailed
}
}
}
#
# Adds a regkey with the specified value, creating any missing keys as needed.
#
function ForceAddRegKeyValue($RegistryPath, $Name, $Type, $Value)
{
if (!(Test-Path $RegistryPath))
{
New-Item -Path $RegistryPath -Force | Out-Null
}
New-ItemProperty -Path $RegistryPath -Name $Name -PropertyType $Type -Value $Value -Force | Out-Null
}
#
# Adds the needed regkeys to enable Explorer context menu integration.
#
function InstallContextMenuIntegration
{
ForceAddRegKeyValue `
-RegistryPath "hkcu:\Software\Classes\Directory\shell\Open Fluent Terminal here\command"`
-Name "(Default)"`
-Type "ExpandString"`
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%1`""
ForceAddRegKeyValue `
-RegistryPath "hkcu:\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command"`
-Name "(Default)"`
-Type "ExpandString"`
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
ForceAddRegKeyValue `
-RegistryPath "hkcu:\Software\Classes\Directory\LibraryFolder\shell\Open Fluent Terminal here\command"`
-Name "(Default)"`
-Type "ExpandString"`
-Value "`"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe`" new `"%V`""
}
#
# Main script logic when the user launches the script without parameters.
#
function DoStandardOperations
{
# List all .appxbundle files in the script directory
$BundlePath = Get-ChildItem (Join-Path $ScriptDir "*.appxbundle") | Where-Object { $_.Mode -NotMatch "d" }
$BundleCount = ($BundlePath | Measure-Object).Count
# There must be exactly one bundle
if ($BundleCount -lt 1)
{
PrintMessageAndExit ([UiStrings]::ErrorNoPackageFound) $ErrorCodes.NoPackageFound
}
elseif ($BundleCount -gt 1)
{
PrintMessageAndExit ([UiStrings]::ErrorManyPackagesFound) $ErrorCodes.ManyPackagesFound
}
# First attempt to install a bundle.
if ($BundleCount -eq 1)
{
$DeveloperPackagePath = $BundlePath
Write-Host (([UiStrings]::BundleFound) -f $DeveloperPackagePath.FullName)
}
# The package must be signed
$PackageSignature = Get-AuthenticodeSignature $DeveloperPackagePath
$PackageCertificate = $PackageSignature.SignerCertificate
if (!$PackageCertificate)
{
PrintMessageAndExit ([UiStrings]::ErrorPackageUnsigned) $ErrorCodes.PackageUnsigned
}
# Test if the package signature is trusted. If not, the corresponding certificate
# needs to be present in the current directory and needs to be installed.
$NeedInstallCertificate = ($PackageSignature.Status -ne "Valid")
if ($NeedInstallCertificate)
{
# List all .cer files in the script directory
$DeveloperCertificatePath = Get-ChildItem (Join-Path $ScriptDir "*.cer") | Where-Object { $_.Mode -NotMatch "d" }
$DeveloperCertificateCount = ($DeveloperCertificatePath | Measure-Object).Count
# There must be exactly 1 certificate
if ($DeveloperCertificateCount -lt 1)
{
PrintMessageAndExit ([UiStrings]::ErrorNoCertificateFound) $ErrorCodes.NoCertificateFound
}
elseif ($DeveloperCertificateCount -gt 1)
{
PrintMessageAndExit ([UiStrings]::ErrorManyCertificatesFound) $ErrorCodes.ManyCertificatesFound
}
Write-Host (([UiStrings]::CertificateFound) -f $DeveloperCertificatePath.FullName)
# The .cer file must have the format of a valid certificate
ValidateCertificateFormat $DeveloperCertificatePath
# The package signature must match the certificate file
if ($PackageCertificate -ne (Get-PfxCertificate $DeveloperCertificatePath))
{
PrintMessageAndExit ([UiStrings]::ErrorCertificateMismatch) $ErrorCodes.CertificateMismatch
}
}
$NeedDeveloperLicense = CheckIfNeedToEnableSideloading
# Relaunch the script elevated with the necessary parameters if needed
if ($NeedDeveloperLicense -or $NeedInstallCertificate)
{
Write-Host ([UiStrings]::ElevateActions)
if ($NeedDeveloperLicense)
{
Write-Host ([UiStrings]::ElevateActionDevLicense)
}
if ($NeedInstallCertificate)
{
Write-Host ([UiStrings]::ElevateActionCertificate)
}
$IsAlreadyElevated = ([Security.Principal.WindowsIdentity]::GetCurrent().Groups.Value -contains "S-1-5-32-544")
if ($IsAlreadyElevated)
{
if ($Force -and $NeedDeveloperLicense)
{
PrintMessageAndExit ([UiStrings]::ErrorForceDeveloperLicense) $ErrorCodes.ForceDeveloperLicense
}
if ($Force -and $NeedInstallCertificate)
{
Write-Warning ([UiStrings]::WarningInstallCert)
}
}
else
{
if ($Force)
{
PrintMessageAndExit ([UiStrings]::ErrorForceElevate) $ErrorCodes.ForceElevate
}
else
{
Write-Host ([UiStrings]::ElevateActionsContinue)
Pause
}
}
LaunchElevated
}
InstallPackageWithDependencies
if ($ForceContextMenu -or (ConfirmContextMenuInstall))
{
InstallContextMenuIntegration
}
}
#
# Main script entry point
#
if ($GetDeveloperLicense -or $CertificatePath)
{
DoElevatedOperations
}
else
{
DoStandardOperations
PrintMessageAndExit ([UiStrings]::Success) $ErrorCodes.Success
}
+38 -19
View File
@@ -28,35 +28,35 @@ A Terminal Emulator based on UWP and web technologies.
## Screenshots ## Screenshots
![Terminal window](Screenshots/terminal.png) ![Terminal window](Screenshots/terminal.jpg)
![Settings window](Screenshots/settings.png) ![Settings window](Screenshots/settings.jpg)
## Languages ## Languages
- Arabic
- Arabic (Iraq)
- Azerbaijani
- Chinese (Simplified)
- Chinese (Traditional)
- Dutch
- English - English
- French
- German - German
- Hebrew - Spanish
- French
- Simplified Chinese
- Hindi - Hindi
- Indonesian
- Italian - Italian
- Japanese - Dutch
- Korean
- Polish - Polish
- Portuguese (Brazilian) - Portuguese (Brazilian)
- Romanian - Romanian
- Russian - Russians
- Spanish - Hebrew
- Ukrainian
Sign up to [POEditor](https://poeditor.com/join/project/TfKNIt826z) to help with existing translations or add a new language Sign up to [POEditor](https://poeditor.com/join/project/TfKNIt826z) to help with existing translations or add a new language
## Up Next
- ~~Launch shell profile with a custom defined keybinding~~
- Copy & Paste options
- Improved tabs
- Split screen support
## How to install (as an end-user) ## How to install (as an end-user)
### Prerequisite ### Prerequisite
@@ -68,6 +68,8 @@ Sign up to [POEditor](https://poeditor.com/join/project/TfKNIt826z) to help with
- From an elevated/admin shell, execute `choco install fluent-terminal` - From an elevated/admin shell, execute `choco install fluent-terminal`
### Scoop package manager installation ### Scoop package manager installation
- [Enable sideloading apps](https://www.windowscentral.com/how-enable-windows-10-sideload-apps-outside-store)
- Alternatively, [enable developer mode](https://docs.microsoft.com/en-US/windows/uwp/get-started/enable-your-device-for-development) if you plan to do UWP app development. **For most users that do not intend to do UWP app development, developer mode will not be necessary.**
- Install Scoop - Install Scoop
- Install 7zip and git (to add buckets): `scoop install 7zip git` - Install 7zip and git (to add buckets): `scoop install 7zip git`
- From an elevated/admin shell, execute - From an elevated/admin shell, execute
@@ -76,10 +78,27 @@ Sign up to [POEditor](https://poeditor.com/join/project/TfKNIt826z) to help with
scoop install fluent-terminal-np scoop install fluent-terminal-np
``` ```
### Manual ### Bundled install script
- Download and extract the latest [release](https://github.com/felixse/FluentTerminal/releases). - Download and extract the latest [release](https://github.com/felixse/FluentTerminal/releases).
- Double click the *.appxbundle file. - If not already present, download [`Install.ps1`](Install.ps1) to the extracted directory.
- Right-click on `Install.ps1`, and choose "Run with Powershell".
- The installer script will walk you through the rest of the process.
### Manual install
- Download the latest [release](https://github.com/felixse/FluentTerminal/releases)
- [Enable sideloading apps](https://www.windowscentral.com/how-enable-windows-10-sideload-apps-outside-store)
- Alternatively, [enable developer mode](https://docs.microsoft.com/en-US/windows/uwp/get-started/enable-your-device-for-development) if you plan to do UWP app development. **For most users that do not intend to do UWP app development, developer mode will not be necessary.**
- Install the *.cer file into `Local Machine` -> `Trusted Root Certification Authorities`
- This will require administrator elevation. If you installed the certificate and did not have to elevate, you likely installed it into an incorrect certificate store.
![Right-Click then choose Install Certificate](Screenshots/right-click_install-certificate.png)
![Install Certificate into Local Machine](Screenshots/install-certificate_local-machine.png)
- double click the *.appxbundle
- **Optional:** Install Context menu integration from [here](https://github.com/felixse/FluentTerminal/tree/master/Explorer%20Context%20Menu%20Integration)
## How to set up a development environment ## How to set up a development environment
Please refer to [this Wiki page](https://github.com/felixse/FluentTerminal/wiki/How-to-set-up-a-development-environment) Please refer to [this Wiki page](https://github.com/felixse/FluentTerminal/wiki/How-to-set-up-a-development-environment)
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB