Compare commits

..
18 Commits
Author SHA1 Message Date
FelixSe 6830adb788 Increase version, add generated files to .gitignore 2018-02-22 22:33:18 +01:00
FelixSe 02e015371c Supress webview native context menu 2018-02-22 22:07:21 +01:00
FelixSe 80bdfa012c Show notification if registering hotkey for toggle window fails 2018-02-20 22:04:23 +01:00
FelixSe 70326e9585 Add missing whitespace in product name 2018-02-20 22:04:20 +01:00
FelixSe c0e1415b6e Use shell executable name as tab title 2018-02-20 21:56:51 +01:00
FelixSe 13a3402b58 Improve error message for failed shell startup 2018-02-20 18:41:34 +01:00
FelixSe 73703412f8 Fix for shells no longer getting cleaned up on window close 2018-02-20 18:18:45 +01:00
FelixSe ab226d95d4 Added option to confirm before closing tab/window 2018-02-19 23:34:22 +01:00
FelixSe de7a76bb3b Add icons to settings pages 2018-02-19 21:01:09 +01:00
FelixSe 379f58b4b8 Rename open to show in tray icon 2018-02-19 20:38:37 +01:00
FelixSe 3a04cd3db2 Close tabs with middle click 2018-02-19 20:37:25 +01:00
FelixSe df0d3f080b Use non acrylic accent color for resize overlay 2018-02-19 19:34:54 +01:00
FelixSe 04131d271a Improve size of titlebar button icons 2018-02-19 19:31:31 +01:00
FelixSe d66fe26634 Global hotkey for show/hide window 2018-02-18 23:10:05 +01:00
FelixSe 8870f0621c Keyboard shortcuts 2018-02-18 21:08:22 +01:00
FelixSe e5250a7ae8 Reorderable tabs for windows 2018-02-17 17:32:48 +01:00
FelixSe b2b6cedf2c Increase version to 0.1.5.0 2018-02-16 17:37:54 +01:00
Felix e777c2c5ac Update README.md 2018-02-16 17:31:46 +01:00
42 changed files with 1233 additions and 7415 deletions

No files matched your search

+2
View File
@@ -1,6 +1,7 @@
# Project specific
FluentTerminal.App/Win32
FluentTerminal.SystemTray/Client
FluentTerminal.Client/dist
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -290,3 +291,4 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
FluentTerminal.Client/dist/bundle.js
+1
View File
@@ -39,6 +39,7 @@ namespace FluentTerminal.App
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
builder.RegisterType<TerminalService>().As<ITerminalService>().SingleInstance();
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
builder.RegisterType<MainViewModel>().InstancePerDependency();
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
Binary file not shown.
@@ -0,0 +1,40 @@
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Input;
namespace FluentTerminal.App.Behaviors
{
public class MiddleClickBehavior : Trigger<UIElement>
{
private bool _middleButtonPressed;
protected override void OnAttached()
{
AssociatedObject.PointerPressed += OnPointerPressed;
AssociatedObject.PointerReleased += OnPointerReleased;
}
protected override void OnDetaching()
{
AssociatedObject.PointerPressed -= OnPointerPressed;
AssociatedObject.PointerReleased -= OnPointerReleased;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(AssociatedObject);
_middleButtonPressed = point.Properties.IsMiddleButtonPressed;
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(AssociatedObject);
var middleButtonStillPressed = point.Properties.IsMiddleButtonPressed;
if (_middleButtonPressed && !middleButtonStillPressed)
{
Interaction.ExecuteActions(AssociatedObject, Actions, null);
}
}
}
}
@@ -0,0 +1,50 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class IntToVisibilityConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty ElseProperty =
DependencyProperty.Register(nameof(Else), typeof(Visibility), typeof(IntToVisibilityConverter), new PropertyMetadata(Visibility.Collapsed));
public static readonly DependencyProperty IfProperty =
DependencyProperty.Register(nameof(If), typeof(int), typeof(IntToVisibilityConverter), new PropertyMetadata(0));
public static readonly DependencyProperty ThenProperty =
DependencyProperty.Register(nameof(Then), typeof(Visibility), typeof(IntToVisibilityConverter), new PropertyMetadata(Visibility.Visible));
public Visibility Else
{
get { return (Visibility)GetValue(ElseProperty); }
set { SetValue(ElseProperty, value); }
}
public int If
{
get { return (int)GetValue(IfProperty); }
set { SetValue(IfProperty, value); }
}
public Visibility Then
{
get { return (Visibility)GetValue(ThenProperty); }
set { SetValue(ThenProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int count)
{
return count == If ? Then : Else;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -99,12 +99,16 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Behaviors\MiddleClickBehavior.cs" />
<Compile Include="Converters\FalseToVisibleConverter.cs" />
<Compile Include="Converters\IntToVisibilityConverter.cs" />
<Compile Include="Converters\NegateConverter.cs" />
<Compile Include="Converters\TerminalViewModelToViewConverter.cs" />
<Compile Include="Converters\TrueToVisibleConverter.cs" />
<Compile Include="Services\IDialogService.cs" />
<Compile Include="Services\Implementation\DialogService.cs" />
<Compile Include="Services\Implementation\KeyboardCommandService.cs" />
<Compile Include="Services\IKeyboardCommandService.cs" />
<Compile Include="Utilities\ApplicationDataContainerExtensions.cs" />
<Compile Include="Services\IDefaultValueProvider.cs" />
<Compile Include="Services\Implementation\DefaultValueProvider.cs" />
@@ -154,6 +158,7 @@
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<Content Include="Assets\settingsicons.ttf" />
<None Include="FluentTerminal.App_StoreKey.pfx" />
<None Include="FluentTerminal.App_TemporaryKey.pfx" />
<Content Include="Win32\FluentTerminal.Models.dll" />
@@ -217,6 +222,7 @@
<Content Include="Win32\Fleck.dll" />
<Content Include="Win32\FluentTerminal.SystemTray.exe" />
<Content Include="Win32\FluentTerminal.SystemTray.pdb" />
<Content Include="Win32\GlobalHotKey.dll" />
<Content Include="Win32\Microsoft.Owin.dll" />
<Content Include="Win32\Microsoft.Owin.FileSystems.dll" />
<Content Include="Win32\Microsoft.Owin.Host.HttpListener.dll" />
@@ -403,6 +409,9 @@
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>2.1.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.Xaml.Behaviors.Uwp.Managed">
<Version>2.0.0</Version>
</PackageReference>
<PackageReference Include="MvvmLightLibs">
<Version>5.3.0</Version>
</PackageReference>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap mp desktop rescap">
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.1.4.0" />
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.1.6.0" />
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Fluent Terminal</DisplayName>
@@ -8,6 +8,8 @@ namespace FluentTerminal.App.Services
{
ShellConfiguration GetDefaultShellConfiguration();
TerminalOptions GetDefaultTerminalOptions();
IEnumerable<KeyBinding> GetDefaultKeyBindings();
ApplicationSettings GetDefaultApplicationSettings();
Guid GetDefaultThemeId();
IEnumerable<TerminalTheme> GetPreInstalledThemes();
}
@@ -0,0 +1,11 @@
using FluentTerminal.Models.Enums;
using System;
namespace FluentTerminal.App.Services
{
public interface IKeyboardCommandService
{
void RegisterCommandHandler(Command command, Action handler);
void SendCommand(Command command);
}
}
@@ -8,6 +8,7 @@ namespace FluentTerminal.App.Services
{
event EventHandler CurrentThemeChanged;
event EventHandler TerminalOptionsChanged;
event EventHandler ApplicationSettingsChanged;
ShellConfiguration GetShellConfiguration();
void SaveShellConfiguration(ShellConfiguration shellConfiguration);
@@ -15,6 +16,11 @@ namespace FluentTerminal.App.Services
TerminalOptions GetTerminalOptions();
void SaveTerminalOptions(TerminalOptions terminalOptions);
ApplicationSettings GetApplicationSettings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
IEnumerable<KeyBinding> GetKeyBindings();
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
TerminalTheme GetTheme(Guid id);
@@ -2,6 +2,7 @@
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
using Windows.System;
namespace FluentTerminal.App.Services.Implementation
{
@@ -105,5 +106,93 @@ namespace FluentTerminal.App.Services.Implementation
return new[] { defaultXterm, powerShell };
}
public IEnumerable<KeyBinding> GetDefaultKeyBindings()
{
return new[]
{
new KeyBinding
{
Command = Command.NewTab,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = (int)VirtualKey.T
},
new KeyBinding
{
Command = Command.CloseTab,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = (int)VirtualKey.W
},
new KeyBinding
{
Command = Command.NextTab,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = (int)VirtualKey.Tab
},
new KeyBinding
{
Command = Command.PreviousTab,
Ctrl = true,
Alt = false,
Meta = false,
Shift = true,
Key = (int)VirtualKey.Tab
},
new KeyBinding
{
Command = Command.NewWindow,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = (int)VirtualKey.N
},
new KeyBinding
{
Command = Command.ShowSettings,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = 188 // comma
},
new KeyBinding
{
Command = Command.Copy,
Ctrl = true,
Alt = false,
Meta = false,
Shift = true,
Key = (int)VirtualKey.C
},
new KeyBinding
{
Command = Command.Paste,
Ctrl = true,
Alt = false,
Meta = false,
Shift = false,
Key = (int)VirtualKey.V
}
};
}
public ApplicationSettings GetDefaultApplicationSettings()
{
return new ApplicationSettings
{
ConfirmClosingTabs = false,
ConfirmClosingWindows = false
};
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services.Implementation
{
public class KeyboardCommandService : IKeyboardCommandService
{
private Dictionary<Command, Action> _commandHandlers = new Dictionary<Command, Action>();
public void RegisterCommandHandler(Command command, Action handler)
{
_commandHandlers[command] = handler;
}
public void SendCommand(Command command)
{
if (_commandHandlers.TryGetValue(command, out Action handler))
{
handler.Invoke();
return;
}
throw new Exception($"No handler registered for command: {command}");
}
}
}
@@ -17,6 +17,7 @@ namespace FluentTerminal.App.Services.Implementation
public event EventHandler CurrentThemeChanged;
public event EventHandler TerminalOptionsChanged;
public event EventHandler ApplicationSettingsChanged;
public SettingsService(IDefaultValueProvider defaultValueProvider)
{
@@ -98,13 +99,29 @@ namespace FluentTerminal.App.Services.Implementation
public TerminalOptions GetTerminalOptions()
{
return _localSettings.ReadValueFromJson(nameof(TerminalOptions), _defaultValueProvider.GetDefaultTerminalOptions());
return _roamingSettings.ReadValueFromJson(nameof(TerminalOptions), _defaultValueProvider.GetDefaultTerminalOptions());
}
public void SaveTerminalOptions(TerminalOptions terminalOptions)
{
_localSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
_roamingSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
TerminalOptionsChanged?.Invoke(this, EventArgs.Empty);
}
public ApplicationSettings GetApplicationSettings()
{
return _roamingSettings.ReadValueFromJson(nameof(ApplicationSettings), _defaultValueProvider.GetDefaultApplicationSettings());
}
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
{
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
ApplicationSettingsChanged?.Invoke(this, EventArgs.Empty);
}
public IEnumerable<KeyBinding> GetKeyBindings()
{
return _roamingSettings.ReadValueFromJson("KeyBindings", _defaultValueProvider.GetDefaultKeyBindings());
}
}
}
+1 -4
View File
@@ -1,7 +1,4 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:input="using:Telerik.UI.Xaml.Controls.Input">
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style
x:Key="WindowTitleTextBlockStyle"
+123 -34
View File
@@ -1,96 +1,153 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
namespace FluentTerminal.App.ViewModels
{
public class MainViewModel : ViewModelBase
{
private const string FallbackTitle = "Fluent Terminal";
private readonly IDialogService _dialogService;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly ISettingsService _settingsService;
private readonly ITerminalService _terminalService;
private readonly IDialogService _dialogService;
private ApplicationSettings _applicationSettings;
private string _background;
private double _backgroundOpacity;
private CoreDispatcher _dispatcher;
private int _nextTerminalId;
private TerminalViewModel _selectedTerminal;
private string _title;
public MainViewModel(ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService)
public MainViewModel(ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService)
{
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_terminalService = terminalService;
_dialogService = dialogService;
Title = FallbackTitle;
_keyboardCommandService = keyboardCommandService;
_keyboardCommandService.RegisterCommandHandler(Command.NewTab, () => AddTerminal(null));
_keyboardCommandService.RegisterCommandHandler(Command.CloseTab, CloseCurrentTab);
_keyboardCommandService.RegisterCommandHandler(Command.NextTab, SelectNextTab);
_keyboardCommandService.RegisterCommandHandler(Command.PreviousTab, SelectPreviousTab);
_keyboardCommandService.RegisterCommandHandler(Command.NewWindow, async () => await NewWindow());
_keyboardCommandService.RegisterCommandHandler(Command.ShowSettings, async () => await ShowSettings());
var currentTheme = _settingsService.GetCurrentTheme();
Background = currentTheme.Colors.Background;
BackgroundOpacity = currentTheme.BackgroundOpacity;
_applicationSettings = _settingsService.GetApplicationSettings();
AddTerminalCommand = new RelayCommand(() => AddTerminal(null));
ShowSettingsCommand = new RelayCommand(async () => await ShowSettings());
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
}
public RelayCommand AddTerminalCommand { get; }
public double BackgroundOpacity
{
get => _backgroundOpacity;
set => Set(ref _backgroundOpacity, value);
}
public string Background
{
get => _background;
set => Set(ref _background, value);
}
public double BackgroundOpacity
{
get => _backgroundOpacity;
set => Set(ref _backgroundOpacity, value);
}
public TerminalViewModel SelectedTerminal
{
get => _selectedTerminal;
set => Set(ref _selectedTerminal, value);
set
{
if (Set(ref _selectedTerminal, value))
{
SelectedTerminal?.FocusTerminal();
}
}
}
public RelayCommand ShowSettingsCommand { get; }
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
public string Title
{
get => _title;
set => Set(ref _title, value);
}
public void AddTerminal(string startupDirectory)
{
var terminal = new TerminalViewModel(_settingsService, _terminalService, _dialogService, startupDirectory);
terminal.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TerminalViewModel.Title) && Terminals.Count == 1)
{
if (string.IsNullOrWhiteSpace(terminal.Title))
{
Title = FallbackTitle;
}
else
{
Title = terminal.Title;
}
}
};
var terminal = new TerminalViewModel(GetNextTerminalId(), _settingsService, _terminalService, _dialogService, _keyboardCommandService, _applicationSettings, startupDirectory);
terminal.CloseRequested += OnTerminalCloseRequested;
Terminals.Add(terminal);
SelectedTerminal = terminal;
}
public void CloseAllTerminals()
{
foreach (var terminal in Terminals)
{
terminal.CloseView();
}
}
private void CloseCurrentTab()
{
SelectedTerminal?.CloseCommand.Execute(null);
}
private int GetNextTerminalId()
{
return _nextTerminalId++;
}
private Task NewWindow()
{
return App.Instance.CreateNewTerminalWindow(null);
}
private async void OnApplicationSettingsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
_applicationSettings = _settingsService.GetApplicationSettings();
});
}
private async void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
var deferral = e.GetDeferral();
if (_applicationSettings.ConfirmClosingWindows)
{
var result = await _dialogService.ShowDialogAsnyc("Please confirm", "Are you sure you want to close this window?", DialogButton.OK, DialogButton.Cancel);
if (result == DialogButton.OK)
{
CloseAllTerminals();
App.Instance.TerminalWindowClosed();
}
else
{
e.Handled = true;
}
}
else
{
CloseAllTerminals();
App.Instance.TerminalWindowClosed();
}
deferral.Complete();
}
private async void OnCurrentThemeChanged(object sender, System.EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
@@ -101,6 +158,38 @@ namespace FluentTerminal.App.ViewModels
});
}
private void OnTerminalCloseRequested(object sender, EventArgs e)
{
if (Terminals.Count == 1)
{
return;
}
if (sender is TerminalViewModel terminal)
{
terminal.CloseView();
if (SelectedTerminal == terminal)
{
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
}
Terminals.Remove(terminal);
}
}
private void SelectNextTab()
{
var currentIndex = Terminals.IndexOf(SelectedTerminal);
var nextIndex = (currentIndex + 1) % Terminals.Count;
SelectedTerminal = Terminals[nextIndex];
}
private void SelectPreviousTab()
{
var currentIndex = Terminals.IndexOf(SelectedTerminal);
var previousIndex = (currentIndex - 1 + Terminals.Count) % Terminals.Count;
SelectedTerminal = Terminals[previousIndex];
}
private Task ShowSettings()
{
return App.Instance.ShowSettings();
+163 -125
View File
@@ -15,12 +15,13 @@ namespace FluentTerminal.App.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
private readonly ISettingsService _settingsService;
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private ApplicationSettings _applicationSettings;
private ThemeViewModel _selectedTheme;
private ShellConfiguration _shellConfiguration;
private TerminalOptions _terminalOptions;
private ThemeViewModel _selectedTheme;
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService)
{
@@ -34,6 +35,7 @@ namespace FluentTerminal.App.ViewModels
_shellConfiguration = _settingsService.GetShellConfiguration();
_terminalOptions = _settingsService.GetTerminalOptions();
_applicationSettings = _settingsService.GetApplicationSettings();
ShellType = _shellConfiguration.Shell;
var activeThemeId = _settingsService.GetCurrentThemeId();
@@ -57,94 +59,72 @@ namespace FluentTerminal.App.ViewModels
Sizes = Enumerable.Range(1, 72);
}
private void OnThemeActivated(object sender, EventArgs e)
public string Arguments
{
if (sender is ThemeViewModel activatedTheme)
get => _shellConfiguration.Arguments;
set
{
_settingsService.SaveCurrentThemeId(activatedTheme.Id);
foreach (var theme in Themes)
if (_shellConfiguration.Arguments != value)
{
theme.IsActive = theme.Id == activatedTheme.Id;
_shellConfiguration.Arguments = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
}
}
}
private void CreateTheme()
public bool BarIsSelected
{
var defaultTheme = _settingsService.GetTheme(_defaultValueProvider.GetDefaultThemeId());
var theme = new TerminalTheme
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = "New Theme",
BackgroundOpacity = defaultTheme.BackgroundOpacity,
Colors = new TerminalColors(defaultTheme.Colors)
};
_settingsService.SaveTheme(theme);
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
Themes.Add(viewModel);
SelectedTheme = viewModel;
get => CursorStyle == CursorStyle.Bar;
set => CursorStyle = CursorStyle.Bar;
}
private void OnThemeDeleted(object sender, EventArgs e)
public bool BlockIsSelected
{
if (sender is ThemeViewModel theme)
{
if (SelectedTheme == theme)
{
SelectedTheme = Themes.First();
}
Themes.Remove(theme);
if (theme.IsActive)
{
Themes.First().IsActive = true;
_settingsService.SaveCurrentThemeId(Themes.First().Id);
}
_settingsService.DeleteTheme(theme.Id);
}
get => CursorStyle == CursorStyle.Block;
set => CursorStyle = CursorStyle.Block;
}
public RelayCommand BrowseForCustomShellCommand { get; }
public RelayCommand BrowseForWorkingDirectoryCommand { get; }
public RelayCommand<string> RestoreDefaultsCommand { get; }
public RelayCommand CreateThemeCommand { get; set; }
public ObservableCollection<ThemeViewModel> Themes { get; } = new ObservableCollection<ThemeViewModel>();
public IEnumerable<string> Fonts { get; }
public IEnumerable<int> Sizes { get; }
public ThemeViewModel SelectedTheme
public bool CMDIsSelected
{
get => _selectedTheme;
set => Set(ref _selectedTheme, value);
get => ShellType == ShellType.CMD;
set => ShellType = ShellType.CMD;
}
private CursorStyle CursorStyle
public bool ConfirmClosingTabs
{
get => _terminalOptions.CursorStyle;
get => _applicationSettings.ConfirmClosingTabs;
set
{
if (_terminalOptions.CursorStyle != value)
if (_applicationSettings.ConfirmClosingTabs != value)
{
_terminalOptions.CursorStyle = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
_applicationSettings.ConfirmClosingTabs = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BlockIsSelected));
RaisePropertyChanged(nameof(BarIsSelected));
RaisePropertyChanged(nameof(UnderlineIsSelected));
}
}
}
public bool ConfirmClosingWindows
{
get => _applicationSettings.ConfirmClosingWindows;
set
{
if (_applicationSettings.ConfirmClosingWindows != value)
{
_applicationSettings.ConfirmClosingWindows = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public RelayCommand CreateThemeCommand { get; set; }
public bool CursorBlink
{
get => _terminalOptions.CursorBlink;
@@ -159,58 +139,6 @@ namespace FluentTerminal.App.ViewModels
}
}
public int FontSize
{
get => _terminalOptions.FontSize;
set
{
if (_terminalOptions.FontSize != value)
{
_terminalOptions.FontSize = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public string FontFamily
{
get => _terminalOptions.FontFamily;
set
{
if (_terminalOptions.FontFamily != value)
{
_terminalOptions.FontFamily = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public bool BlockIsSelected
{
get => CursorStyle == CursorStyle.Block;
set => CursorStyle = CursorStyle.Block;
}
public bool BarIsSelected
{
get => CursorStyle == CursorStyle.Bar;
set => CursorStyle = CursorStyle.Bar;
}
public bool UnderlineIsSelected
{
get => CursorStyle == CursorStyle.Underline;
set => CursorStyle = CursorStyle.Underline;
}
public bool CMDIsSelected
{
get => ShellType == ShellType.CMD;
set => ShellType = ShellType.CMD;
}
public bool CustomShellIsSelected
{
get => ShellType == ShellType.Custom;
@@ -231,29 +159,31 @@ namespace FluentTerminal.App.ViewModels
}
}
public string WorkingDirectory
public string FontFamily
{
get => _shellConfiguration.WorkingDirectory;
get => _terminalOptions.FontFamily;
set
{
if (_shellConfiguration.WorkingDirectory != value)
if (_terminalOptions.FontFamily != value)
{
_shellConfiguration.WorkingDirectory = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
_terminalOptions.FontFamily = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public string Arguments
public IEnumerable<string> Fonts { get; }
public int FontSize
{
get => _shellConfiguration.Arguments;
get => _terminalOptions.FontSize;
set
{
if (_shellConfiguration.Arguments != value)
if (_terminalOptions.FontSize != value)
{
_shellConfiguration.Arguments = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
_terminalOptions.FontSize = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
@@ -265,6 +195,14 @@ namespace FluentTerminal.App.ViewModels
set => ShellType = ShellType.PowerShell;
}
public RelayCommand<string> RestoreDefaultsCommand { get; }
public ThemeViewModel SelectedTheme
{
get => _selectedTheme;
set => Set(ref _selectedTheme, value);
}
public ShellType ShellType
{
get => _shellConfiguration.Shell;
@@ -282,6 +220,47 @@ namespace FluentTerminal.App.ViewModels
}
}
public IEnumerable<int> Sizes { get; }
public ObservableCollection<ThemeViewModel> Themes { get; } = new ObservableCollection<ThemeViewModel>();
public bool UnderlineIsSelected
{
get => CursorStyle == CursorStyle.Underline;
set => CursorStyle = CursorStyle.Underline;
}
public string WorkingDirectory
{
get => _shellConfiguration.WorkingDirectory;
set
{
if (_shellConfiguration.WorkingDirectory != value)
{
_shellConfiguration.WorkingDirectory = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
}
}
}
private CursorStyle CursorStyle
{
get => _terminalOptions.CursorStyle;
set
{
if (_terminalOptions.CursorStyle != value)
{
_terminalOptions.CursorStyle = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BlockIsSelected));
RaisePropertyChanged(nameof(BarIsSelected));
RaisePropertyChanged(nameof(UnderlineIsSelected));
}
}
}
private async Task BrowseForCustomShell()
{
var picker = new FileOpenPicker();
@@ -308,6 +287,59 @@ namespace FluentTerminal.App.ViewModels
}
}
private void CreateTheme()
{
var defaultTheme = _settingsService.GetTheme(_defaultValueProvider.GetDefaultThemeId());
var theme = new TerminalTheme
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = "New Theme",
BackgroundOpacity = defaultTheme.BackgroundOpacity,
Colors = new TerminalColors(defaultTheme.Colors)
};
_settingsService.SaveTheme(theme);
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
Themes.Add(viewModel);
SelectedTheme = viewModel;
}
private void OnThemeActivated(object sender, EventArgs e)
{
if (sender is ThemeViewModel activatedTheme)
{
_settingsService.SaveCurrentThemeId(activatedTheme.Id);
foreach (var theme in Themes)
{
theme.IsActive = theme.Id == activatedTheme.Id;
}
}
}
private void OnThemeDeleted(object sender, EventArgs e)
{
if (sender is ThemeViewModel theme)
{
if (SelectedTheme == theme)
{
SelectedTheme = Themes.First();
}
Themes.Remove(theme);
if (theme.IsActive)
{
Themes.First().IsActive = true;
_settingsService.SaveCurrentThemeId(Themes.First().Id);
}
_settingsService.DeleteTheme(theme.Id);
}
}
private async Task RestoreDefaults(string area)
{
var result = await _dialogService.ShowDialogAsnyc("Please confirm", "Are you sure you want to restore default values for this page?", DialogButton.OK, DialogButton.Cancel);
@@ -320,7 +352,7 @@ namespace FluentTerminal.App.ViewModels
WorkingDirectory = defaults.WorkingDirectory;
Arguments = defaults.Arguments;
}
else if(result == DialogButton.OK && area == "terminal")
else if (result == DialogButton.OK && area == "terminal")
{
var defaults = _defaultValueProvider.GetDefaultTerminalOptions();
CursorBlink = defaults.CursorBlink;
@@ -328,6 +360,12 @@ namespace FluentTerminal.App.ViewModels
FontFamily = defaults.FontFamily;
FontSize = defaults.FontSize;
}
else if (result == DialogButton.OK && area == "general")
{
var defaults = _defaultValueProvider.GetDefaultApplicationSettings();
ConfirmClosingWindows = defaults.ConfirmClosingWindows;
ConfirmClosingTabs = defaults.ConfirmClosingTabs;
}
}
}
}
@@ -1,10 +1,13 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Views;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Core;
using Windows.UI.Xaml;
@@ -12,9 +15,11 @@ namespace FluentTerminal.App.ViewModels
{
public class TerminalViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly ISettingsService _settingsService;
private readonly ITerminalService _terminalService;
private readonly IDialogService _dialogService;
private ApplicationSettings _applicationSettings;
private CoreDispatcher _dispatcher;
private string _resizeOverlayContent;
private DispatcherTimer _resizeOverlayTimer;
@@ -24,25 +29,41 @@ namespace FluentTerminal.App.ViewModels
private ITerminalView _terminalView;
private string _title;
public TerminalViewModel(ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService, string startupDirectory)
public TerminalViewModel(int id, ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory)
{
Id = id;
Title = DefaultTitle;
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_terminalService = terminalService;
_dialogService = dialogService;
_keyboardCommandService = keyboardCommandService;
_applicationSettings = applicationSettings;
_startupDirectory = startupDirectory;
_resizeOverlayTimer = new DispatcherTimer();
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
CloseCommand = new RelayCommand(async () => await InvokeCloseRequested());
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
}
public event EventHandler CloseRequested;
public event EventHandler<string> TitleChanged;
public RelayCommand CloseCommand { get; }
public int Id { get; private set; }
public bool Initialized { get; private set; }
public string DefaultTitle { get; private set; } = "Fluent Terminal";
public string ResizeOverlayContent
{
get => _resizeOverlayContent;
@@ -69,7 +90,25 @@ namespace FluentTerminal.App.ViewModels
public string Title
{
get => _title;
set => Set(ref _title, value);
set
{
value = string.IsNullOrWhiteSpace(value) ? DefaultTitle: value;
if (Set(ref _title, value))
{
TitleChanged?.Invoke(this, Title);
}
}
}
public void CloseView()
{
_terminalView.Close();
}
public Task FocusTerminal()
{
return _terminalView?.FocusTerminal();
}
public async Task OnViewIsReady(ITerminalView terminalView)
@@ -78,8 +117,9 @@ namespace FluentTerminal.App.ViewModels
var options = _settingsService.GetTerminalOptions();
var theme = _settingsService.GetCurrentTheme();
var keyBindings = _settingsService.GetKeyBindings();
var size = await _terminalView.CreateTerminal(options, theme.Colors);
var size = await _terminalView.CreateTerminal(options, theme.Colors, keyBindings);
var configuration = _settingsService.GetShellConfiguration();
if (!string.IsNullOrWhiteSpace(_startupDirectory))
@@ -94,6 +134,10 @@ namespace FluentTerminal.App.ViewModels
_terminalId = response.Id;
_terminalView.TerminalSizeChanged += OnTerminalSizeChanged;
_terminalView.TerminalTitleChanged += OnTerminalTitleChanged;
_terminalView.KeyboardCommandReceived += OnKeyboardCommandReceived;
DefaultTitle = response.ShellExecutableName;
Title = DefaultTitle;
await _terminalView.ConnectToSocket(response.WebSocketUrl);
Initialized = true;
@@ -102,6 +146,31 @@ namespace FluentTerminal.App.ViewModels
{
await _dialogService.ShowDialogAsnyc("Error", response.Error, DialogButton.OK);
}
await FocusTerminal();
}
private async Task InvokeCloseRequested()
{
if (_applicationSettings.ConfirmClosingTabs)
{
var result = await _dialogService.ShowDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel);
if (result == DialogButton.Cancel)
{
return;
}
}
CloseRequested?.Invoke(this, EventArgs.Empty);
}
private async void OnApplicationSettingsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
_applicationSettings = _settingsService.GetApplicationSettings();
});
}
private async void OnCurrentThemeChanged(object sender, EventArgs e)
@@ -113,6 +182,36 @@ namespace FluentTerminal.App.ViewModels
});
}
private async void OnKeyboardCommandReceived(object sender, Command e)
{
if (e == Command.Copy)
{
var selection = await _terminalView.GetSelection();
var dataPackage = new DataPackage();
dataPackage.SetText(selection);
Clipboard.SetContent(dataPackage);
}
else if (e == Command.Paste)
{
var content = Clipboard.GetContent();
if (content.Contains(StandardDataFormats.Text))
{
var text = await content.GetTextAsync();
await _terminalView.Write(text);
}
}
else
{
_keyboardCommandService.SendCommand(e);
}
}
private void OnResizeOverlayTimerFinished(object sender, object e)
{
_resizeOverlayTimer.Stop();
ShowResizeOverlay = false;
}
private async void OnTerminalOptionsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
@@ -122,12 +221,6 @@ namespace FluentTerminal.App.ViewModels
});
}
private void OnResizeOverlayTimerFinished(object sender, object e)
{
_resizeOverlayTimer.Stop();
ShowResizeOverlay = false;
}
private async void OnTerminalSizeChanged(object sender, TerminalSize e)
{
if (!Initialized)
+8 -1
View File
@@ -1,5 +1,7 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FluentTerminal.App.Views
@@ -8,10 +10,15 @@ namespace FluentTerminal.App.Views
{
event EventHandler<TerminalSize> TerminalSizeChanged;
event EventHandler<string> TerminalTitleChanged;
event EventHandler<Command> KeyboardCommandReceived;
Task ChangeTheme(TerminalColors theme);
Task ChangeOptions(TerminalOptions options);
Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme);
Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings);
Task ConnectToSocket(string url);
void Close();
Task FocusTerminal();
Task<string> GetSelection();
Task Write(string text);
}
}
+111 -25
View File
@@ -2,13 +2,27 @@
x:Class="FluentTerminal.App.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:FluentTerminal.App.Behaviors"
xmlns:converters="using:FluentTerminal.App.Converters"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
RequestedTheme="Dark"
mc:Ignorable="d">
<Page.Resources>
<converters:TerminalViewModelToViewConverter x:Key="TerminalViewModelToViewConverter" />
<converters:IntToVisibilityConverter
x:Key="OneToVisibleConverter"
Else="Collapsed"
If="1"
Then="Visible" />
<converters:IntToVisibilityConverter
x:Key="OneToCollapsedConverter"
Else="Visible"
If="1"
Then="Collapsed" />
</Page.Resources>
<Grid x:Name="Root">
<Grid.Background>
@@ -22,34 +36,106 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle x:Name="TitleBar" Fill="Transparent" />
<Grid
Grid.Row="0"
Height="{Binding CoreTitleBarHeight, Mode=OneWay}"
Canvas.ZIndex="3">
<Rectangle x:Name="TitleBar" Fill="Transparent" />
<Grid Padding="{Binding CoreTitleBarPadding, Mode=OneWay}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Canvas.ZIndex="9"
IsHitTestVisible="False"
Style="{StaticResource WindowTitleTextBlockStyle}"
Text="{x:Bind ViewModel.Title, Mode=OneWay}" />
<Button
Grid.Column="2"
Command="{x:Bind ViewModel.ShowSettingsCommand}"
Style="{StaticResource TitleBarButtonStyle}">
<Viewbox Width="16" Height="16">
<SymbolIcon Symbol="Setting" />
</Viewbox>
</Button>
</Grid>
</Grid>
Margin="{Binding CoreTitleBarPadding, Mode=OneWay}"
SizeChanged="Grid_SizeChanged">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
IsHitTestVisible="False"
Style="{StaticResource WindowTitleTextBlockStyle}"
Text="{x:Bind ViewModel.Terminals[0].Title, Mode=OneWay}"
Visibility="{x:Bind ViewModel.Terminals.Count, Mode=OneWay, Converter={StaticResource OneToVisibleConverter}}" />
<ListView
x:Name="Tabs"
Grid.Column="0"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
ItemsSource="{x:Bind ViewModel.Terminals}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Auto"
SelectedItem="{x:Bind ViewModel.SelectedTerminal, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.Terminals.Count, Mode=OneWay, Converter={StaticResource OneToCollapsedConverter}}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:TerminalViewModel">
<Grid
Height="32"
Margin="0"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Interactivity:Interaction.Behaviors>
<behaviors:MiddleClickBehavior>
<behaviors:MiddleClickBehavior.Actions>
<core:InvokeCommandAction Command="{x:Bind CloseCommand}" />
</behaviors:MiddleClickBehavior.Actions>
</behaviors:MiddleClickBehavior>
</Interactivity:Interaction.Behaviors>
<TextBlock
Grid.Column="0"
Margin="12,8,0,0"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Title, Mode=OneWay}" />
<Button
Grid.Column="2"
Width="32"
Command="{x:Bind CloseCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Close">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Cancel" />
</Viewbox>
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="MinWidth" Value="24" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<StackPanel
x:Name="TitleBarButtons"
Grid.Column="2"
Orientation="Horizontal">
<Button
Command="{x:Bind ViewModel.AddTerminalCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="New Tab">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Button>
<Button
HorizontalAlignment="Right"
Command="{x:Bind ViewModel.ShowSettingsCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Settings">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Setting" />
</Viewbox>
</Button>
</StackPanel>
</Grid>
<ContentControl
x:Name="TerminalContainer"
+8 -6
View File
@@ -45,7 +45,6 @@ namespace FluentTerminal.App.Views
Loaded += OnLoaded;
Unloaded += OnUnloaded;
Window.Current.Activated += OnWindowActivated;
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
ApplicationView.PreferredLaunchViewSize = new Windows.Foundation.Size(800, 600);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
@@ -59,11 +58,6 @@ namespace FluentTerminal.App.Views
}
}
private void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
App.Instance.TerminalWindowClosed();
}
private async void OnWindowActivated(object sender, WindowActivatedEventArgs e)
{
if (e.WindowActivationState != CoreWindowActivationState.Deactivated && TerminalContainer.Content is TerminalView terminal)
@@ -109,5 +103,13 @@ namespace FluentTerminal.App.Views
PropertyChanged(this, new PropertyChangedEventArgs(nameof(CoreTitleBarPadding)));
}
}
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is Grid grid)
{
Tabs.MaxWidth = grid.ActualWidth - grid.ColumnDefinitions[2].ActualWidth;
}
}
}
}
+40 -20
View File
@@ -29,26 +29,46 @@
OpenPaneLength="200"
SelectionChanged="NavigationView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem
Content="General"
Icon="Setting"
Tag="general" />
<NavigationViewItem
Content="Terminal"
Icon="Setting"
Tag="terminal" />
<NavigationViewItem
Content="Shell"
Icon="Setting"
Tag="shell" />
<NavigationViewItem
Content="Key Bindings"
Icon="Setting"
Tag="keyBindings" />
<NavigationViewItem
Content="Themes"
Icon="Setting"
Tag="themes" />
<NavigationViewItem Content="General" Tag="general">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="e"
ToolTipService.ToolTip="General" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Terminal" Tag="terminal">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="d"
ToolTipService.ToolTip="Terminal" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Shell" Tag="shell">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="a"
ToolTipService.ToolTip="Shell" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Keybindings" Tag="keyBindings">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="b"
ToolTipService.ToolTip="Keybindings" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Themes" Tag="themes">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="c"
ToolTipService.ToolTip="Themes" />
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<Grid>
<Frame x:Name="ContentFrame" />
@@ -60,7 +60,7 @@ namespace FluentTerminal.App.Views
private void NavigationView_Loaded(object sender, RoutedEventArgs e)
{
NavigationView.SelectedItem = NavigationView.MenuItems.Cast<NavigationViewItemBase>().FirstOrDefault(m => m.Tag.ToString() == "shell");
NavigationView.SelectedItem = NavigationView.MenuItems.Cast<NavigationViewItemBase>().FirstOrDefault(m => m.Tag.ToString() == "general");
}
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
@@ -6,5 +6,44 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
NavigationCacheMode="Required"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" Margin="24,00,0,24">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Margin="0,10,0,0"
VerticalAlignment="Center"
FontSize="28"
Style="{StaticResource TitleTextBlockStyle}"
Text="General" />
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding ViewModel.RestoreDefaultsCommand}"
CommandParameter="general"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<ToggleSwitch
Margin="0,0,0,24"
Header="Ask before closing windows"
IsOn="{x:Bind ViewModel.ConfirmClosingWindows, Mode=TwoWay}" />
<ToggleSwitch Header="Ask before closing tabs" IsOn="{x:Bind ViewModel.ConfirmClosingTabs, Mode=TwoWay}" />
</StackPanel>
</Grid>
</Page>
@@ -1,12 +1,24 @@
using Windows.UI.Xaml.Controls;
using FluentTerminal.App.ViewModels;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Views.SettingsPages
{
public sealed partial class GeneralSettings : Page
{
public SettingsViewModel ViewModel { get; private set; }
public GeneralSettings()
{
this.InitializeComponent();
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is SettingsViewModel viewModel)
{
ViewModel = viewModel;
}
}
}
}
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
NavigationCacheMode="Required"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
+3 -1
View File
@@ -19,8 +19,10 @@
Padding="6"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemControlAccentAcrylicWindowAccentMediumHighBrush}"
Visibility="{x:Bind ViewModel.ShowResizeOverlay, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<Grid.Background>
<SolidColorBrush Color="{ThemeResource SystemAccentColor}" />
</Grid.Background>
<TextBlock Foreground="White" Text="{x:Bind ViewModel.ResizeOverlayContent, Mode=OneWay}" />
</Grid>
</Grid>
+153 -85
View File
@@ -1,5 +1,6 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.RuntimeComponent.Interfaces;
using FluentTerminal.RuntimeComponent.WebAllowedObjects;
using Newtonsoft.Json;
@@ -7,13 +8,16 @@ using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -21,26 +25,21 @@ namespace FluentTerminal.App.Views
{
public sealed partial class TerminalView : UserControl, ITerminalView, ITerminalEventListener
{
private WebView _webView;
private SemaphoreSlim _loaded;
private MenuFlyoutItem _copyMenuItem;
private BlockingCollection<Action> _dispatcherJobs;
public event EventHandler<TerminalSize> TerminalSizeChanged;
public event EventHandler<string> TerminalTitleChanged;
public TerminalViewModel ViewModel { get; }
private SemaphoreSlim _loaded;
private MenuFlyoutItem _pasteMenuItem;
private WebView _webView;
public TerminalView(TerminalViewModel viewModel)
{
ViewModel = viewModel;
InitializeComponent();
Loaded += OnLoaded;
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequested; //todo move to mainpage
_loaded = new SemaphoreSlim(0, 1);
JsonConvert.DefaultSettings = () => {
JsonConvert.DefaultSettings = () =>
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
@@ -53,21 +52,44 @@ namespace FluentTerminal.App.Views
StartMediatorTask();
}
public async Task FocusWebView()
public event EventHandler<Command> KeyboardCommandReceived;
public event EventHandler<TerminalSize> TerminalSizeChanged;
public event EventHandler<string> TerminalTitleChanged;
public TerminalViewModel ViewModel { get; }
public Task ChangeOptions(TerminalOptions options)
{
if (_webView != null)
{
_webView.Focus(FocusState.Programmatic);
await ExecuteScriptAsync("document.focus();");
}
var serialized = JsonConvert.SerializeObject(options);
return ExecuteScriptAsync($"changeOptions('{serialized}')");
}
private void OnCloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
public Task ChangeTheme(TerminalColors theme)
{
var serialized = JsonConvert.SerializeObject(theme);
return ExecuteScriptAsync($"changeTheme('{serialized}')");
}
public void Close()
{
_webView?.Navigate(new Uri("about:blank"));
_webView = null;
}
public Task ConnectToSocket(string url)
{
return ExecuteScriptAsync($"connectToWebSocket('{url}');");
}
public async Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings)
{
var serializedOptions = JsonConvert.SerializeObject(options);
var serializedTheme = JsonConvert.SerializeObject(theme);
var serializedKeyBindings = JsonConvert.SerializeObject(keyBindings);
var size = await ExecuteScriptAsync($"createTerminal('{serializedOptions}', '{serializedTheme}', '{serializedKeyBindings}')");
return JsonConvert.DeserializeObject<TerminalSize>(size);
}
public Task<string> ExecuteScriptAsync(string script)
{
try
@@ -81,6 +103,117 @@ namespace FluentTerminal.App.Views
return Task.FromResult(string.Empty);
}
public async Task FocusTerminal()
{
if (_webView != null)
{
_webView.Focus(FocusState.Programmatic);
await ExecuteScriptAsync("document.focus();");
}
}
public async Task FocusWebView()
{
if (_webView != null)
{
_webView.Focus(FocusState.Programmatic);
await ExecuteScriptAsync("document.focus();");
}
}
public Task<string> GetSelection()
{
return ExecuteScriptAsync("term.getSelection()");
}
public void OnKeyboardCommand(string command)
{
_dispatcherJobs.Add(() => KeyboardCommandReceived?.Invoke(this, Enum.Parse<Command>(command, true)));
}
public void OnTerminalResized(int columns, int rows)
{
_dispatcherJobs.Add(() => TerminalSizeChanged?.Invoke(this, new TerminalSize { Columns = columns, Rows = rows }));
}
public void OnTitleChanged(string title)
{
_dispatcherJobs.Add(() => TerminalTitleChanged?.Invoke(this, title));
}
public Task Write(string text)
{
text = Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
return ExecuteScriptAsync($"paste('{text}')");
}
private void _webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
_loaded.Release();
}
private void _webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
var bridge = new TerminalBridge(this);
_webView.AddWebAllowedObject("terminalBridge", bridge);
}
private void Copy_Click(object sender, RoutedEventArgs e)
{
OnKeyboardCommand(Command.Copy.ToString());
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
if (WebViewContainer.Children.Any())
{
return;
}
_webView = new WebView(WebViewExecutionMode.SeparateThread)
{
DefaultBackgroundColor = Colors.Transparent
};
WebViewContainer.Children.Add(_webView);
_webView.NavigationCompleted += _webView_NavigationCompleted;
_webView.NavigationStarting += _webView_NavigationStarting;
_copyMenuItem = new MenuFlyoutItem { Text = "Copy" };
_copyMenuItem.Click += Copy_Click;
_pasteMenuItem = new MenuFlyoutItem { Text = "Paste" };
_pasteMenuItem.Click += Paste_Click;
_webView.ContextFlyout = new MenuFlyout
{
Items = { _copyMenuItem, _pasteMenuItem }
};
_webView.Navigate(new Uri("http://localhost:9000/Client/index.html"));
await _loaded.WaitAsync();
_webView.Focus(FocusState.Programmatic);
await ViewModel.OnViewIsReady(this);
}
public void OnRightClick(int x, int y, bool hasSelection)
{
_dispatcherJobs.Add(() =>
{
var flyout = (MenuFlyout)_webView.ContextFlyout;
_copyMenuItem.IsEnabled = hasSelection;
flyout.ShowAt(_webView, new Point(x, y));
});
}
private void Paste_Click(object sender, RoutedEventArgs e)
{
OnKeyboardCommand(Command.Paste.ToString());
}
private void StartMediatorTask()
{
_dispatcherJobs = new BlockingCollection<Action>();
@@ -100,70 +233,5 @@ namespace FluentTerminal.App.Views
}
});
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
_webView = new WebView(WebViewExecutionMode.SeparateThread)
{
DefaultBackgroundColor = Colors.Transparent
};
WebViewContainer.Children.Add(_webView);
_webView.NavigationCompleted += _webView_NavigationCompleted;
_webView.NavigationStarting += _webView_NavigationStarting;
_webView.Navigate(new Uri("http://localhost:9000/Client/index.html"));
await _loaded.WaitAsync();
_webView.Focus(FocusState.Programmatic);
await ViewModel.OnViewIsReady(this);
}
private void _webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
_loaded.Release();
}
private void _webView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
var bridge = new TerminalBridge(this);
_webView.AddWebAllowedObject("terminalBridge", bridge);
}
public async Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme)
{
var serializedOptions = JsonConvert.SerializeObject(options);
var serializedTheme = JsonConvert.SerializeObject(theme);
var size = await ExecuteScriptAsync($"createTerminal('{serializedOptions}', '{serializedTheme}')");
return JsonConvert.DeserializeObject<TerminalSize>(size);
}
public Task ConnectToSocket(string url)
{
return ExecuteScriptAsync($"connectToWebSocket('{url}');");
}
public void OnTerminalResized(int columns, int rows)
{
_dispatcherJobs.Add(() => TerminalSizeChanged?.Invoke(this, new TerminalSize { Columns = columns, Rows = rows }));
}
public void OnTitleChanged(string title)
{
_dispatcherJobs.Add(() => TerminalTitleChanged?.Invoke(this, title));
}
public Task ChangeTheme(TerminalColors theme)
{
var serialized = JsonConvert.SerializeObject(theme);
return ExecuteScriptAsync($"changeTheme('{serialized}')");
}
public Task ChangeOptions(TerminalOptions options)
{
var serialized = JsonConvert.SerializeObject(options);
return ExecuteScriptAsync($"changeOptions('{serialized}')");
}
}
}
}
File diff suppressed because it is too large. Load diff
-134
View File
@@ -1,134 +0,0 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-family: courier-new, courier, monospace;
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm:not(.enable-mouse-events) {
cursor: text;
}
+44 -2
View File
@@ -11,7 +11,7 @@ Terminal.applyAddon(winptyCompat);
var term, socket;
var terminalContainer = document.getElementById('terminal-container');
function createTerminal(options, theme) {
function createTerminal(options, theme, keyBindings) {
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
@@ -19,6 +19,8 @@ function createTerminal(options, theme) {
theme = JSON.parse(theme);
theme.background = 'transparent';
window.keyBindings = JSON.parse(keyBindings);
options = JSON.parse(options);
var terminalOptions = {
@@ -54,6 +56,30 @@ function createTerminal(options, theme) {
resizeTimeout = setTimeout(term.fit(), 500);
}
window.onmouseup = function (e) {
if (e.button == 2) {
terminalBridge.notifyRightClick(e.clientX, e.clientY, term.hasSelection());
}
}
term.attachCustomKeyEventHandler(function (e) {
for (var i = 0; i < window.keyBindings.length; i++) {
var keyBinding = window.keyBindings[i];
if (keyBinding.ctrl == e.ctrlKey
&& keyBinding.alt == e.altKey
&& keyBinding.meta == e.metaKey
&& keyBinding.shift == e.shiftKey
&& keyBinding.key == e.keyCode) {
if (document.visibilityState == 'visible') {
e.preventDefault();
terminalBridge.invokeCommand(keyBinding.command);
}
return false;
}
}
return true;
});
return JSON.stringify({
rows: term.rows,
columns: term.cols
@@ -87,7 +113,23 @@ function changeOptions(options) {
term.setOption('fontSize', options.fontSize);
}
function paste(content) {
content = b64DecodeUnicode(content);
term.send(content);
}
function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
}
document.oncontextmenu = function () {
return false;
};
window.createTerminal = createTerminal;
window.connectToWebSocket = connectToWebSocket;
window.changeTheme = changeTheme;
window.changeOptions = changeOptions;
window.changeOptions = changeOptions;
window.paste = paste;
@@ -0,0 +1,8 @@
namespace FluentTerminal.Models
{
public class ApplicationSettings
{
public bool ConfirmClosingTabs { get; set; }
public bool ConfirmClosingWindows { get; set; }
}
}
@@ -6,5 +6,6 @@
public bool Success { get; set; }
public string WebSocketUrl { get; set; }
public string Error { get; set; }
public string ShellExecutableName { get; set; }
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace FluentTerminal.Models.Enums
{
public enum Command
{
ToggleWindow,
NextTab,
PreviousTab,
NewTab,
CloseTab,
NewWindow,
ShowSettings,
Copy,
Paste
}
}
@@ -4,4 +4,11 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Windows">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.16299.0\Windows.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
</Reference>
</ItemGroup>
</Project>
+15
View File
@@ -0,0 +1,15 @@
using FluentTerminal.Models.Enums;
using Windows.System;
namespace FluentTerminal.Models
{
public class KeyBinding
{
public Command Command { get; set; }
public int Key { get; set; }
public bool Ctrl { get; set; }
public bool Alt { get; set; }
public bool Meta { get; set; }
public bool Shift { get; set; }
}
}
@@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FluentTerminal.RuntimeComponent.Interfaces
namespace FluentTerminal.RuntimeComponent.Interfaces
{
public interface ITerminalEventListener
{
void OnTerminalResized(int columns, int rows);
void OnTitleChanged(string title);
void OnKeyboardCommand(string command);
void OnRightClick(int x, int y, bool hasSelection);
}
}
@@ -22,5 +22,15 @@ namespace FluentTerminal.RuntimeComponent.WebAllowedObjects
{
_terminalEventListener.OnTitleChanged(title);
}
public void InvokeCommand(string command)
{
_terminalEventListener.OnKeyboardCommand(command);
}
public void NotifyRightClick(int x, int y, bool hasSelection)
{
_terminalEventListener.OnRightClick(x, y, hasSelection);
}
}
}
@@ -152,6 +152,9 @@
<PackageReference Include="Fleck">
<Version>0.14.0.59</Version>
</PackageReference>
<PackageReference Include="GlobalHotKey">
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.WebApi.OwinSelfHost">
<Version>5.2.3</Version>
</PackageReference>
@@ -51,8 +51,10 @@ namespace FluentTerminal.SystemTray.Services
if (!winpty_spawn(_handle, spawnConfigHandle, out IntPtr process, out IntPtr thread, out int procError, out errorHandle))
{
throw new Exception(winpty_error_msg(errorHandle).ToString());
throw new Exception($"Failed to start the shell process. Please check your shell settings.\nTried to start: {args}");
}
ShellExecutableName = Path.GetFileNameWithoutExtension(exe);
}
finally
{
@@ -87,7 +89,7 @@ namespace FluentTerminal.SystemTray.Services
};
socket.OnMessage = message =>
{
var bytes = Encoding.ASCII.GetBytes(message);
var bytes = Encoding.UTF8.GetBytes(message);
_stdin.Write(bytes, 0, bytes.Length);
};
});
@@ -128,6 +130,7 @@ namespace FluentTerminal.SystemTray.Services
public int Id { get; }
public string WebSocketUrl { get; }
public string ShellExecutableName { get; }
public void Dispose()
{
@@ -33,7 +33,8 @@ namespace FluentTerminal.SystemTray.Services
{
Success = true,
Id = terminal.Id,
WebSocketUrl = terminal.WebSocketUrl
WebSocketUrl = terminal.WebSocketUrl,
ShellExecutableName = terminal.ShellExecutableName
};
}
@@ -1,25 +1,31 @@
using System;
using GlobalHotKey;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Core;
using Windows.Data.Xml.Dom;
using Windows.Foundation.Collections;
using Windows.UI.Notifications;
namespace FluentTerminal.SystemTray
{
public class SystemTrayApplicationContext : ApplicationContext
{
private AppServiceConnection connection = null;
private HotKeyManager hotKeyManager = null;
private NotifyIcon notifyIcon = null;
public SystemTrayApplicationContext()
{
var openMenuItem = new MenuItem("Open", new EventHandler(OpenApp));
var openMenuItem = new MenuItem("Show", new EventHandler(OpenApp));
var newWindowItem = new MenuItem("New Window", new EventHandler(NewWindow));
var settingsMenuItem = new MenuItem("Show Settings", new EventHandler(ShowSettings));
var exitMenuItem = new MenuItem("Exit", new EventHandler(Exit));
@@ -32,22 +38,35 @@ namespace FluentTerminal.SystemTray
notifyIcon.Icon = Properties.Resources.Square44x44Logo_scale_100;
notifyIcon.ContextMenu = new ContextMenu(new MenuItem[] { openMenuItem, newWindowItem, settingsMenuItem, exitMenuItem });
notifyIcon.Visible = true;
hotKeyManager = new HotKeyManager();
try
{
hotKeyManager.Register(Key.Scroll, ModifierKeys.None);
hotKeyManager.KeyPressed += HotkeyManager_KeyPressed;
}
catch (Exception)
{
ShowNotification("Fluent Terminal", "Unable to register hotkey for toggle window command");
}
}
private async void OpenApp(object sender, EventArgs e)
[DllImport("user32.dll")]
public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);
protected override void Dispose(bool disposing)
{
IEnumerable<AppListEntry> appListEntries = await Package.Current.GetAppListEntriesAsync();
await appListEntries.First().LaunchAsync();
hotKeyManager.Dispose();
base.Dispose(disposing);
}
private void ShowSettings(object sender, EventArgs e)
{
Process.Start("flute.exe", "settings");
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
private void NewWindow(object sender, EventArgs e)
private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
Process.Start("flute.exe", "new");
connection.ServiceClosed -= Connection_ServiceClosed;
connection = null;
}
private async void Exit(object sender, EventArgs e)
@@ -60,6 +79,47 @@ namespace FluentTerminal.SystemTray
Application.Exit();
}
private string GetActiveProcessFileName()
{
try
{
IntPtr hwnd = GetForegroundWindow();
GetWindowThreadProcessId(hwnd, out uint pid);
Process p = Process.GetProcessById((int)pid);
return p.MainWindowTitle;
}
catch (Exception e)
{
return null;
}
}
private void HotkeyManager_KeyPressed(object sender, KeyPressedEventArgs e)
{
if (e.HotKey.Key == Key.Scroll)
{
if (GetActiveProcessFileName() == "Fluent Terminal")
{
SendKeys.Send("%{Tab}");
}
else
{
OpenApp(this, EventArgs.Empty);
}
}
}
private void NewWindow(object sender, EventArgs e)
{
Process.Start("flute.exe", "new");
}
private async void OpenApp(object sender, EventArgs e)
{
IEnumerable<AppListEntry> appListEntries = await Package.Current.GetAppListEntriesAsync();
await appListEntries.First().LaunchAsync();
}
private async Task SendMessage(ValueSet message)
{
@@ -82,10 +142,27 @@ namespace FluentTerminal.SystemTray
await connection.SendMessageAsync(message);
}
private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
private void ShowNotification(string title, string content)
{
connection.ServiceClosed -= Connection_ServiceClosed;
connection = null;
string xml = $@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>{title}</text>
<text>{content}</text>
</binding>
</visual>
</toast>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var toast = new ToastNotification(doc);
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
private void ShowSettings(object sender, EventArgs e)
{
Process.Start("flute.exe", "settings");
}
}
}
+2 -1
View File
@@ -8,8 +8,9 @@ A Terminal Emulator based on UWP and web technologies. This is far from finished
- Resizing
- Configuration of the startup shell
- Multiple windows
- Theming
- Appearance Configuration
## Up Next:
- Appearance configuration
- Tabs
- Key bindings