Compare commits

...
64 Commits
Author SHA1 Message Date
FelixSe dcdf8d1854 Added search function 2018-05-27 20:22:21 +02:00
FelixSe fb717b3750 Fix crashes on delete or restore keybindings 2018-05-27 13:49:53 +02:00
FelixSe aff7b636f1 Add option to select shell profile while creating a new tab (ctrl+shift+t by default) 2018-05-27 13:35:02 +02:00
FelixSe 99a7de25d3 Close tab/window when shell process has exited 2018-05-26 22:32:15 +02:00
FelixSe 28d950ed9a Support multiple shell profiles 2018-05-26 22:05:49 +02:00
Felix 1490470e91 Merge pull request #22 from skipmeister123/master
Added "Homebrew" theme
2018-05-24 21:52:38 +02:00
Hans Kokx 236e0ff2e1 Added "Homebrew" theme 2018-05-24 15:43:24 -04:00
Felix 5cb9158077 Update README.md 2018-05-23 21:47:16 +02:00
FelixSe 941c7c1948 update to xterm 3.4.0 and use dynamic charAtlas 2018-05-20 12:16:03 +02:00
Felix 45e096c033 Update README.md 2018-05-06 19:49:08 +02:00
FelixSe d37da0ef31 fixed last ambiguous xcopy 2018-05-06 18:52:29 +02:00
FelixSe 49abf56ec6 fixed another ambiguous xcopy 2018-05-02 22:42:22 +02:00
FelixSe 618a2485e1 fix postbuild event when target directory is not yet created 2018-05-02 22:27:02 +02:00
felixse cdeeeb6fa8 Make hintpath in Models.csproj use a variable for program files (x86) 2018-05-02 11:02:00 +02:00
felixse be833cc174 Make hintpaths in SystemTray.csproj use a variable for program files (x86) 2018-05-02 10:40:22 +02:00
FelixSe 39955bcb3e Started writing some unit tests 2018-05-01 21:22:34 +02:00
FelixSe e9b76a6867 Code cleanup 2018-05-01 13:10:47 +02:00
FelixSe 89bce37dbd Fix bug while adding secondary keybindings 2018-05-01 13:10:41 +02:00
FelixSe 82a195c2f2 Use AppService instead of WebAPI for inter process communication 2018-04-29 15:52:57 +02:00
FelixSe c48889cbbc Execute copy only when selection is not empty, this allows for ctrl + c to be used as the default copy keybinding 2018-04-22 13:12:50 +02:00
FelixSe dd9d5d2e72 Extend key enum, improve displaying 2018-04-22 12:45:56 +02:00
FelixSe 7f7f15ea03 Support meta key for keybindings 2018-04-22 12:45:30 +02:00
FelixSe f502303426 Update xterm to 3.3.0 2018-04-15 16:23:24 +02:00
FelixSe 2f2ed06d58 Refactored App startup 2018-04-15 16:13:20 +02:00
FelixSe 809167a872 removed unnecessary quotes in context menu integration script 2018-04-15 16:11:26 +02:00
FelixSe 5d3fd25098 remove unused field 2018-04-15 16:10:41 +02:00
FelixSe 6674fd0ad9 Fix for open Fluent Terminal here failing if no other terminal window open 2018-03-18 14:07:34 +01:00
FelixSe f874cb070d Fix race condition while retrieving the systemtrays API port 2018-03-18 13:37:20 +01:00
FelixSe 40230bfce8 Rename tray command to 'new terminal' since it does not necessarily create a new window 2018-03-14 22:20:04 +01:00
FelixSe 3afa2b5f25 Remove App.Instance singleton 2018-03-11 00:26:23 +01:00
FelixSe 94ba1b8324 Update xterm.js to 3.2.0 2018-03-10 23:33:41 +01:00
FelixSe 6dfacdfebd Remove almost unused AppService in favor of using flute.exe 2018-03-10 00:06:25 +01:00
FelixSe 1ca7371103 No longer depending on a static port for communication with Tray process 2018-03-06 21:45:24 +01:00
Felix 1cb388d4af Update README.md 2018-03-05 21:16:56 +01:00
FelixSe 3bdd96c388 Screenshots look better without cursors.. 2018-03-05 21:02:10 +01:00
FelixSe 5bc5bf224f Add Screenshots 2018-03-05 20:56:45 +01:00
FelixSe 01815db1fe Fix typo, set correct c# version in csproj, increase version 2018-03-05 19:26:33 +01:00
FelixSe 80cf43d3bf Store client in app data instead of serving it 2018-03-04 22:26:30 +01:00
FelixSe 050f162c08 Split SettingsPageViewModel into one per page, fix buggy radio buttons 2018-03-04 14:40:05 +01:00
FelixSe f5e03c9856 Register converters only once 2018-03-04 13:22:27 +01:00
FelixSe f763798683 Added option to create new terminals (e.g from context menu) as tabs instead of windows 2018-03-04 13:07:32 +01:00
FelixSe 46120e3431 Make toggle window hotkey configurable 2018-03-03 11:44:46 +01:00
FelixSe 829077883b Improved layout for keybindings settings page 2018-02-26 22:00:58 +01:00
FelixSe 8c928faa09 Some cosmetic changes 2018-02-25 22:31:52 +01:00
FelixSe 1654e9f1f5 Propagate changed keyBindings immediately 2018-02-25 21:52:17 +01:00
FelixSe f542331192 Editable keybindings 2018-02-25 15:40:36 +01:00
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
121 changed files with 5164 additions and 8355 deletions

No files matched your search

+2 -1
View File
@@ -1,6 +1,7 @@
# Project specific
FluentTerminal.App/Win32
FluentTerminal.SystemTray/Client
FluentTerminal.App/Client/
FluentTerminal.Client/dist
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -1,3 +1,3 @@
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" \"new %%1\"" /f
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" \"new %%V\"" /f
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" \"new %%V\"" /f
reg add "HKCU\Software\Classes\Directory\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%1" /f
reg add "HKCU\Software\Classes\Directory\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
reg add "HKCU\Software\Classes\LibraryFolder\Background\shell\Open Fluent Terminal here\command" /d "\"%LOCALAPPDATA%\Microsoft\WindowsApps\flute.exe\" new %%V" /f
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{812A09E4-3A05-41AB-8E9A-DC4389B5D73E}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FluentTerminal.App.Test</RootNamespace>
<AssemblyName>FluentTerminal.App.Test</AssemblyName>
<DefaultLanguage>de-DE</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>FluentTerminal.App.Test_TemporaryKey.pfx</PackageCertificateKeyFile>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<SDKReference Include="TestPlatform.Universal, Version=$(UnitTestPlatformVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\DialogServiceTests.cs" />
<Compile Include="Services\KeyboardCommandServiceTests.cs" />
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="FluentTerminal.App.Test_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\UnitTestApp.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoFixture">
<Version>4.2.1</Version>
</PackageReference>
<PackageReference Include="FluentAssertions">
<Version>5.3.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.2</Version>
</PackageReference>
<PackageReference Include="Moq">
<Version>4.8.2</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>1.2.1</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>1.2.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluentTerminal.App\FluentTerminal.App.csproj">
<Project>{41020100-3414-4e0a-9a8e-92d3c4179df8}</Project>
<Name>FluentTerminal.App</Name>
</ProjectReference>
<ProjectReference Include="..\FluentTerminal.Models\FluentTerminal.Models.csproj">
<Project>{5EA40453-A0AA-449E-8406-D6EA51D7E70E}</Project>
<Name>FluentTerminal.Models</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -0,0 +1,45 @@
<?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"
IgnorableNamespaces="uap mp">
<Identity Name="63c2a56f-7075-408a-a37d-24bb7fb21665"
Publisher="CN=Felix"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="63c2a56f-7075-408a-a37d-24bb7fb21665" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>FluentTerminal.App.Test</DisplayName>
<PublisherDisplayName>Felix</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="vstest.executionengine.universal.App"
Executable="$targetnametoken$.exe"
EntryPoint="FluentTerminal.App.Test.App">
<uap:VisualElements
DisplayName="FluentTerminal.App.Test"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="FluentTerminal.App.Test"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClientServer" />
</Capabilities>
</Package>
@@ -0,0 +1,18 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("FluentTerminal.App.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FluentTerminal.App.Test")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyMetadata("TargetPlatform","UAP")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
@@ -0,0 +1,29 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at http://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Seralize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>
@@ -0,0 +1,62 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Implementation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace FluentTerminal.App.Test.Services
{
[TestClass]
public class DialogServiceTests
{
private Fixture _fixture;
[TestInitialize]
public void TestInitialize()
{
_fixture = new Fixture();
}
[TestMethod]
public void ShowDialogAsync_TitleIsEmpty_ThrowsArgumentNullException()
{
var title = string.Empty;
var content = _fixture.Create<string>();
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
}
[TestMethod]
public void ShowDialogAsync_ContentIsEmpty_ThrowsArgumentNullException()
{
var title = _fixture.Create<string>();
var content = string.Empty;
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
}
[TestMethod]
public void ShowDialogAsync_NoButtonsPassed_ThrowsArgumentException()
{
var title = _fixture.Create<string>();
var content = _fixture.Create<string>();
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content);
invoke.Should().Throw<ArgumentException>().And.ParamName.Should().Be("buttons");
}
}
}
@@ -0,0 +1,71 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace FluentTerminal.App.Test.Services
{
[TestClass]
public class KeyboardCommandServiceTests
{
private Fixture _fixture;
[TestInitialize]
public void TestInitialize()
{
_fixture = new Fixture();
}
[TestMethod]
public void RegisterCommandHandler_HandlerIsNull_ThrowsArgumentNullException()
{
var command = _fixture.Create<Command>();
Action handler = null;
var keyboardCommandService = new KeyboardCommandService();
Action invoke = () => keyboardCommandService.RegisterCommandHandler(command, handler);
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("handler");
}
[TestMethod]
public void RegisterCommandHandler_CommandAlreadyRegisted_ThrowsInvalidOperationException()
{
var command = _fixture.Create<Command>();
var handler = _fixture.Create<Action>();
var keyboardCommandService = new KeyboardCommandService();
keyboardCommandService.RegisterCommandHandler(command, handler);
Action invoke = () => keyboardCommandService.RegisterCommandHandler(command, handler);
invoke.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void SendCommand_CommandIsRegistered_HandlerGetsInvoked()
{
var command = _fixture.Create<Command>();
var handlerCalled = false;
var keyboardCommandService = new KeyboardCommandService();
keyboardCommandService.RegisterCommandHandler(command, () => handlerCalled = true);
keyboardCommandService.SendCommand(command);
handlerCalled.Should().BeTrue();
}
[TestMethod]
public void SendCommand_CommandIsNotRegisted_KeyNotFoundExceptionIsThrown()
{
var command = _fixture.Create<Command>();
var keyboardCommandService = new KeyboardCommandService();
Action invoke = () => keyboardCommandService.SendCommand(command);
invoke.Should().Throw<KeyNotFoundException>();
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<Application
x:Class="FluentTerminal.App.Test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FluentTerminal.App.Test"
RequestedTheme="Light">
</Application>
+102
View File
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Test
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
// Ensure the current window is active
Window.Current.Activate();
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}
+21 -1
View File
@@ -1,12 +1,32 @@
<Application
x:Class="FluentTerminal.App.App"
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:converters="using:FluentTerminal.App.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles\Custom.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:IntToVisibilityConverter
x:Key="OneToVisibleConverter"
Else="Collapsed"
If="1"
Then="Visible" />
<converters:IntToVisibilityConverter
x:Key="OneToCollapsedConverter"
Else="Visible"
If="1"
Then="Collapsed" />
<converters:NegateConverter x:Key="NegateConverter" />
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
<converters:IntToVisibilityConverter
x:Key="ZeroToCollapsedConverter"
Else="Visible"
If="0"
Then="Collapsed" />
<converters:IntToExtendedVirtualKeyConverter x:Key="IntToExtendedVirtualKeyConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
+134 -70
View File
@@ -3,8 +3,12 @@ using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.Views;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
@@ -19,58 +23,43 @@ using IContainer = Autofac.IContainer;
namespace FluentTerminal.App
{
sealed partial class App : Application
public sealed partial class App : Application
{
public static BackgroundTaskDeferral AppServiceDeferral = null;
public static AppServiceConnection Connection = null;
public static App Instance;
public TaskCompletionSource<int> _trayReady = new TaskCompletionSource<int>();
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private bool _alreadyLaunched;
private IContainer _container;
private int _openTerminalWindows;
private ApplicationSettings _applicationSettings;
private readonly IContainer _container;
private readonly List<MainViewModel> _mainViewModels;
private SettingsViewModel _settingsViewModel;
private int? _settingsWindowId;
private AppServiceConnection _appServiceConnection;
private BackgroundTaskDeferral _appServiceDeferral;
public App()
{
_mainViewModels = new List<MainViewModel>();
InitializeComponent();
Instance = this;
var builder = new ContainerBuilder();
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
builder.RegisterType<TerminalService>().As<ITerminalService>().SingleInstance();
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
builder.RegisterType<MainViewModel>().InstancePerDependency();
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
_container = builder.Build();
}
public async Task CreateNewTerminalWindow(string startupDirectory)
{
var viewModel = _container.Resolve<MainViewModel>();
viewModel.AddTerminal(startupDirectory);
var id = await CreateSecondaryView(typeof(MainPage), viewModel, true);
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
}
_settingsService = _container.Resolve<ISettingsService>();
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
public void SettingsWindowClosed()
{
_settingsWindowId = null;
}
_trayProcessCommunicationService = _container.Resolve<ITrayProcessCommunicationService>();
public async Task ShowSettings()
{
if (_settingsWindowId == null)
{
var viewModel = _container.Resolve<SettingsViewModel>();
_settingsWindowId = await CreateSecondaryView(typeof(SettingsPage), viewModel, true);
}
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
}
public void TerminalWindowClosed()
{
_openTerminalWindows--;
_applicationSettings = _settingsService.GetApplicationSettings();
}
protected override async void OnActivated(IActivatedEventArgs args)
@@ -90,11 +79,18 @@ namespace FluentTerminal.App
{
if (command == "settings")
{
await ShowSettings();
await ShowSettings().ConfigureAwait(true);
}
else if (command == "new")
{
await CreateNewTerminalWindow(parameter);
if (_applicationSettings.NewTerminalLocation == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
{
await _mainViewModels.Last().AddTerminal(parameter, false).ConfigureAwait(true);
}
else
{
await CreateNewTerminalWindow(parameter).ConfigureAwait(true);
}
}
}
else
@@ -102,52 +98,63 @@ namespace FluentTerminal.App
if (command == "settings")
{
var viewModel = _container.Resolve<SettingsViewModel>();
await CreateMainView(typeof(SettingsPage), viewModel, true);
await CreateMainView(typeof(SettingsPage), viewModel, true).ConfigureAwait(true);
}
else if (command == "new")
{
var viewModel = _container.Resolve<MainViewModel>();
viewModel.AddTerminal(parameter);
await CreateMainView(typeof(MainPage), viewModel, true);
await viewModel.AddTerminal(parameter, false).ConfigureAwait(true);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
}
}
}
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
if (!_alreadyLaunched)
{
var viewModel = _container.Resolve<MainViewModel>();
await viewModel.AddTerminal(null, false).ConfigureAwait(true);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
Window.Current.Activate();
}
else if (_mainViewModels.Count == 0)
{
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, string.Empty).ConfigureAwait(true);
}
}
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
base.OnBackgroundActivated(args);
if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
{
AppServiceDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnTaskCanceled;
Connection = details.AppServiceConnection;
Connection.RequestReceived += OnRequestReceived;
if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
_appServiceDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnTaskCanceled;
_appServiceConnection = details.AppServiceConnection;
_trayReady.SetResult(0);
}
}
}
protected override async void OnLaunched(LaunchActivatedEventArgs e)
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
if (!_alreadyLaunched)
{
var viewModel = _container.Resolve<MainViewModel>();
viewModel.AddTerminal(null);
await CreateMainView(typeof(MainPage), viewModel, true);
Window.Current.Activate();
}
else if (_openTerminalWindows == 0)
{
var viewModel = _container.Resolve<MainViewModel>();
viewModel.AddTerminal(null);
await CreateSecondaryView(typeof(MainPage), viewModel, true);
}
_appServiceDeferral?.Complete();
_appServiceDeferral = null;
_appServiceConnection = null;
Application.Current.Exit();
}
private async Task CreateMainView(Type pageType, INotifyPropertyChanged viewModel, bool extendViewIntoTitleBar)
{
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync().AsTask();
var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask();
await Task.WhenAll(launch, clearCache);
await StartSystemTray().ConfigureAwait(true);
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
@@ -160,9 +167,12 @@ namespace FluentTerminal.App
{
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = extendViewIntoTitleBar;
if (pageType == typeof(MainPage))
if (viewModel is MainViewModel mainViewModel)
{
_openTerminalWindows++;
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
_mainViewModels.Add(mainViewModel);
}
rootFrame.Navigate(pageType, viewModel);
@@ -171,11 +181,20 @@ namespace FluentTerminal.App
Window.Current.Activate();
}
private async Task<int> CreateSecondaryView(Type pageType, INotifyPropertyChanged viewModel, bool ExtendViewIntoTitleBar)
private async Task CreateNewTerminalWindow(string startupDirectory)
{
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, startupDirectory).ConfigureAwait(true);
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
}
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, object parameter)
{
int windowId = 0;
TViewModel viewModel = default;
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
viewModel = _container.Resolve<TViewModel>();
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = ExtendViewIntoTitleBar;
var frame = new Frame();
frame.Navigate(pageType, viewModel);
@@ -185,28 +204,73 @@ namespace FluentTerminal.App
windowId = ApplicationView.GetForCurrentView().Id;
});
if (pageType == typeof(MainPage))
if (viewModel is MainViewModel mainViewModel && parameter is string directory)
{
_openTerminalWindows++;
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
_mainViewModels.Add(mainViewModel);
await mainViewModel.AddTerminal(directory, false).ConfigureAwait(true);
}
if (viewModel is SettingsViewModel settingsViewModel)
{
_settingsViewModel = settingsViewModel;
_settingsViewModel.Closed += OnSettingsClosed;
}
return windowId;
}
private void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
private void OnApplicationSettingsChanged(object sender, EventArgs e)
{
if (args.Request.Message.ContainsKey("exit"))
_applicationSettings = _settingsService.GetApplicationSettings();
}
private void OnMainViewModelClosed(object sender, EventArgs e)
{
if (sender is MainViewModel viewModel)
{
App.Current.Exit();
viewModel.Closed -= OnMainViewModelClosed;
viewModel.NewWindowRequested -= OnNewWindowRequested;
viewModel.ShowSettingsRequested -= OnShowSettingsRequested;
_mainViewModels.Remove(viewModel);
}
}
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
private async void OnNewWindowRequested(object sender, EventArgs e)
{
if (AppServiceDeferral != null)
await CreateNewTerminalWindow(string.Empty).ConfigureAwait(true);
}
private void OnSettingsClosed(object sender, EventArgs e)
{
_settingsViewModel.Closed -= OnSettingsClosed;
_settingsViewModel = null;
_settingsWindowId = null;
}
private async void OnShowSettingsRequested(object sender, EventArgs e)
{
await ShowSettings().ConfigureAwait(true);
}
private async Task ShowSettings()
{
if (_settingsViewModel == null)
{
AppServiceDeferral.Complete();
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, null).ConfigureAwait(true);
}
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
}
private async Task StartSystemTray()
{
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync().AsTask();
var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask();
await Task.WhenAll(launch, clearCache, _trayReady.Task).ConfigureAwait(true);
await _trayProcessCommunicationService.Initialize(_appServiceConnection).ConfigureAwait(true);
}
}
}
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,43 @@
using FluentTerminal.Models.Enums;
using System;
using System.ComponentModel;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class IntToExtendedVirtualKeyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int intValue)
{
return GetEnumDescription((ExtendedVirtualKey)intValue);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
private static string GetEnumDescription(Enum value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
if (fieldInfo == null)
{
return value.ToString();
}
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes?.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
}
}
@@ -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();
}
}
}
@@ -8,7 +8,7 @@ namespace FluentTerminal.App.Converters
{
internal class TerminalViewModelToViewConverter : IValueConverter
{
private Dictionary<int, TerminalView> _viewDictionary;
private readonly Dictionary<int, TerminalView> _viewDictionary;
public TerminalViewModelToViewConverter()
{
@@ -0,0 +1,18 @@
<ContentDialog
x:Class="FluentTerminal.App.Dialogs.ShellProfileSelectionDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Select Profile"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
CloseButtonText="Cancel"
PrimaryButtonText="Start"
RequestedTheme="Dark"
mc:Ignorable="d">
<ComboBox
HorizontalAlignment="Stretch"
DisplayMemberPath="Name"
ItemsSource="{x:Bind Profiles}"
SelectedItem="{x:Bind SelectedProfile, Mode=TwoWay}" />
</ContentDialog>
@@ -0,0 +1,22 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using System.Collections.Generic;
using System.Linq;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Dialogs
{
public sealed partial class ShellProfileSelectionDialog : ContentDialog
{
public IEnumerable<ShellProfile> Profiles { get; }
public ShellProfile SelectedProfile { get; private set; }
public ShellProfileSelectionDialog(ISettingsService settingsService)
{
Profiles = settingsService.GetShellProfiles();
SelectedProfile = Profiles.First();
this.InitializeComponent();
}
}
}
+80 -145
View File
@@ -45,6 +45,7 @@
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
@@ -79,6 +80,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
@@ -91,6 +93,7 @@
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
@@ -99,32 +102,52 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Behaviors\MiddleClickBehavior.cs" />
<Compile Include="Converters\FalseToVisibleConverter.cs" />
<Compile Include="Converters\IntToExtendedVirtualKeyConverter.cs" />
<Compile Include="Converters\IntToVisibilityConverter.cs" />
<Compile Include="Converters\NegateConverter.cs" />
<Compile Include="Converters\TerminalViewModelToViewConverter.cs" />
<Compile Include="Converters\TrueToVisibleConverter.cs" />
<Compile Include="Dialogs\ShellProfileSelectionDialog.xaml.cs">
<DependentUpon>ShellProfileSelectionDialog.xaml</DependentUpon>
</Compile>
<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" />
<Compile Include="Services\Implementation\SettingsService.cs" />
<Compile Include="Services\Implementation\TerminalService.cs" />
<Compile Include="Services\Implementation\TrayProcessCommunicationService.cs" />
<Compile Include="Services\ISettingsService.cs" />
<Compile Include="Services\ITerminalService.cs" />
<Compile Include="Services\ITrayProcessCommunicationService.cs" />
<Compile Include="Utilities\ColorExtensions.cs" />
<Compile Include="Utilities\HttpJsonContent.cs" />
<Compile Include="Utilities\IHttpContentExtensions.cs" />
<Compile Include="ViewModels\MainViewModel.cs" />
<Compile Include="ViewModels\SettingsViewModel.cs" />
<Compile Include="ViewModels\Settings\GeneralPageViewModel.cs" />
<Compile Include="ViewModels\Settings\KeyBindingsPageViewModel.cs" />
<Compile Include="ViewModels\Settings\KeyBindingsViewModel.cs" />
<Compile Include="ViewModels\Settings\KeyBindingViewModel.cs" />
<Compile Include="ViewModels\Settings\ProfilesPageViewModel.cs" />
<Compile Include="ViewModels\Settings\TerminalPageViewModel.cs" />
<Compile Include="ViewModels\Settings\ThemesPageViewModel.cs" />
<Compile Include="ViewModels\ShellProfileViewModel.cs" />
<Compile Include="ViewModels\ThemeViewModel.cs" />
<Compile Include="ViewModels\TerminalViewModel.cs" />
<Compile Include="Views\BindableColorsTitleBar.cs" />
<Compile Include="Views\ITerminalView.cs" />
<Compile Include="Views\KeyBindingsView.xaml.cs">
<DependentUpon>KeyBindingsView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Views\CreateKeyBindingDialog.xaml.cs">
<DependentUpon>CreateKeyBindingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon>
</Compile>
@@ -140,8 +163,8 @@
<Compile Include="Views\SettingsPages\KeyBindingSettings.xaml.cs">
<DependentUpon>KeyBindingSettings.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPages\ShellSettings.xaml.cs">
<DependentUpon>ShellSettings.xaml</DependentUpon>
<Compile Include="Views\SettingsPages\ShellProfileSettings.xaml.cs">
<DependentUpon>ShellProfileSettings.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TerminalColorPicker.xaml.cs">
<DependentUpon>TerminalColorPicker.xaml</DependentUpon>
@@ -154,10 +177,37 @@
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="FluentTerminal.App_StoreKey.pfx" />
<None Include="FluentTerminal.App_TemporaryKey.pfx" />
<Content Include="Assets\settingsicons.ttf" />
<Content Include="Win32\Autofac.dll" />
<Content Include="Win32\Fleck.dll" />
<Content Include="Win32\FluentTerminal.Models.dll" />
<Content Include="Win32\FluentTerminal.Models.pdb" />
<Content Include="Win32\FluentTerminal.SystemTray.exe" />
<Content Include="Win32\FluentTerminal.SystemTray.pdb" />
<Content Include="Win32\GlobalHotKey.dll" />
<Content Include="Win32\Newtonsoft.Json.dll" />
<Content Include="Win32\System.Data.Common.dll" />
<Content Include="Win32\System.Diagnostics.StackTrace.dll" />
<Content Include="Win32\System.Diagnostics.Tracing.dll" />
<Content Include="Win32\System.Globalization.Extensions.dll" />
<Content Include="Win32\System.IO.Compression.dll" />
<Content Include="Win32\System.Net.Http.dll" />
<Content Include="Win32\System.Net.Sockets.dll" />
<Content Include="Win32\System.Runtime.Serialization.Primitives.dll" />
<Content Include="Win32\System.Security.Cryptography.Algorithms.dll" />
<Content Include="Win32\System.Security.SecureString.dll" />
<Content Include="Win32\System.Threading.Overlapped.dll" />
<Content Include="Win32\System.Xml.XPath.XDocument.dll" />
<Content Include="Win32\winpty-agent.exe" />
<Content Include="Win32\winpty-debugserver.exe" />
<Content Include="Win32\winpty.dll" />
<Content Include="Win32\winpty.NET.dll" />
<None Include="FluentTerminal.App_StoreKey.pfx" />
<None Include="FluentTerminal.App_TemporaryKey.pfx" />
<Content Include="Client\bundle.js" />
<Content Include="Client\index.html" />
<Content Include="Client\style.css" />
<Content Include="Client\xterm.css" />
<Content Include="Win32\FluentTerminal.SystemTray.exe.config" />
</ItemGroup>
<ItemGroup>
@@ -208,146 +258,20 @@
<Content Include="Assets\AppIcons\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
<Content Include="Properties\Default.rd.xml" />
<Content Include="Win32\Client\bundle.js" />
<Content Include="Win32\Client\index.html" />
<Content Include="Win32\Client\style.css" />
<Content Include="Win32\Client\xterm.css" />
<Content Include="Win32\CommonServiceLocator.dll" />
<Content Include="Win32\CommonServiceLocator.pdb" />
<Content Include="Win32\Fleck.dll" />
<Content Include="Win32\FluentTerminal.SystemTray.exe" />
<Content Include="Win32\FluentTerminal.SystemTray.pdb" />
<Content Include="Win32\Microsoft.Owin.dll" />
<Content Include="Win32\Microsoft.Owin.FileSystems.dll" />
<Content Include="Win32\Microsoft.Owin.Host.HttpListener.dll" />
<Content Include="Win32\Microsoft.Owin.Hosting.dll" />
<Content Include="Win32\Microsoft.Owin.StaticFiles.dll" />
<Content Include="Win32\Microsoft.Win32.Primitives.dll" />
<Content Include="Win32\netstandard.dll" />
<Content Include="Win32\Newtonsoft.Json.dll" />
<Content Include="Win32\Owin.dll" />
<Content Include="Win32\System.AppContext.dll" />
<Content Include="Win32\System.Collections.Concurrent.dll" />
<Content Include="Win32\System.Collections.dll" />
<Content Include="Win32\System.Collections.NonGeneric.dll" />
<Content Include="Win32\System.Collections.Specialized.dll" />
<Content Include="Win32\System.ComponentModel.dll" />
<Content Include="Win32\System.ComponentModel.EventBasedAsync.dll" />
<Content Include="Win32\System.ComponentModel.Primitives.dll" />
<Content Include="Win32\System.ComponentModel.TypeConverter.dll" />
<Content Include="Win32\System.Console.dll" />
<Content Include="Win32\System.Data.Common.dll" />
<Content Include="Win32\System.Diagnostics.Contracts.dll" />
<Content Include="Win32\System.Diagnostics.Debug.dll" />
<Content Include="Win32\System.Diagnostics.FileVersionInfo.dll" />
<Content Include="Win32\System.Diagnostics.Process.dll" />
<Content Include="Win32\System.Diagnostics.StackTrace.dll" />
<Content Include="Win32\System.Diagnostics.TextWriterTraceListener.dll" />
<Content Include="Win32\System.Diagnostics.Tools.dll" />
<Content Include="Win32\System.Diagnostics.TraceSource.dll" />
<Content Include="Win32\System.Diagnostics.Tracing.dll" />
<Content Include="Win32\System.Drawing.Primitives.dll" />
<Content Include="Win32\System.Dynamic.Runtime.dll" />
<Content Include="Win32\System.Globalization.Calendars.dll" />
<Content Include="Win32\System.Globalization.dll" />
<Content Include="Win32\System.Globalization.Extensions.dll" />
<Content Include="Win32\System.IO.Compression.dll" />
<Content Include="Win32\System.IO.Compression.ZipFile.dll" />
<Content Include="Win32\System.IO.dll" />
<Content Include="Win32\System.IO.FileSystem.dll" />
<Content Include="Win32\System.IO.FileSystem.DriveInfo.dll" />
<Content Include="Win32\System.IO.FileSystem.Primitives.dll" />
<Content Include="Win32\System.IO.FileSystem.Watcher.dll" />
<Content Include="Win32\System.IO.IsolatedStorage.dll" />
<Content Include="Win32\System.IO.MemoryMappedFiles.dll" />
<Content Include="Win32\System.IO.Pipes.dll" />
<Content Include="Win32\System.IO.UnmanagedMemoryStream.dll" />
<Content Include="Win32\System.Linq.dll" />
<Content Include="Win32\System.Linq.Expressions.dll" />
<Content Include="Win32\System.Linq.Parallel.dll" />
<Content Include="Win32\System.Linq.Queryable.dll" />
<Content Include="Win32\System.Net.Http.dll" />
<Content Include="Win32\System.Net.Http.Formatting.dll" />
<Content Include="Win32\System.Net.NameResolution.dll" />
<Content Include="Win32\System.Net.NetworkInformation.dll" />
<Content Include="Win32\System.Net.Ping.dll" />
<Content Include="Win32\System.Net.Primitives.dll" />
<Content Include="Win32\System.Net.Requests.dll" />
<Content Include="Win32\System.Net.Security.dll" />
<Content Include="Win32\System.Net.Sockets.dll" />
<Content Include="Win32\System.Net.WebHeaderCollection.dll" />
<Content Include="Win32\System.Net.WebSockets.Client.dll" />
<Content Include="Win32\System.Net.WebSockets.dll" />
<Content Include="Win32\System.ObjectModel.dll" />
<Content Include="Win32\System.Reflection.dll" />
<Content Include="Win32\System.Reflection.Extensions.dll" />
<Content Include="Win32\System.Reflection.Primitives.dll" />
<Content Include="Win32\System.Resources.Reader.dll" />
<Content Include="Win32\System.Resources.ResourceManager.dll" />
<Content Include="Win32\System.Resources.Writer.dll" />
<Content Include="Win32\System.Runtime.CompilerServices.VisualC.dll" />
<Content Include="Win32\System.Runtime.dll" />
<Content Include="Win32\System.Runtime.Extensions.dll" />
<Content Include="Win32\System.Runtime.Handles.dll" />
<Content Include="Win32\System.Runtime.InteropServices.dll" />
<Content Include="Win32\System.Runtime.InteropServices.RuntimeInformation.dll" />
<Content Include="Win32\System.Runtime.Numerics.dll" />
<Content Include="Win32\System.Runtime.Serialization.Formatters.dll" />
<Content Include="Win32\System.Runtime.Serialization.Json.dll" />
<Content Include="Win32\System.Runtime.Serialization.Primitives.dll" />
<Content Include="Win32\System.Runtime.Serialization.Xml.dll" />
<Content Include="Win32\System.Security.Claims.dll" />
<Content Include="Win32\System.Security.Cryptography.Algorithms.dll" />
<Content Include="Win32\System.Security.Cryptography.Csp.dll" />
<Content Include="Win32\System.Security.Cryptography.Encoding.dll" />
<Content Include="Win32\System.Security.Cryptography.Primitives.dll" />
<Content Include="Win32\System.Security.Cryptography.X509Certificates.dll" />
<Content Include="Win32\System.Security.Principal.dll" />
<Content Include="Win32\System.Security.SecureString.dll" />
<Content Include="Win32\System.Text.Encoding.dll" />
<Content Include="Win32\System.Text.Encoding.Extensions.dll" />
<Content Include="Win32\System.Text.RegularExpressions.dll" />
<Content Include="Win32\System.Threading.dll" />
<Content Include="Win32\System.Threading.Overlapped.dll" />
<Content Include="Win32\System.Threading.Tasks.dll" />
<Content Include="Win32\System.Threading.Tasks.Parallel.dll" />
<Content Include="Win32\System.Threading.Thread.dll" />
<Content Include="Win32\System.Threading.ThreadPool.dll" />
<Content Include="Win32\System.Threading.Timer.dll" />
<Content Include="Win32\System.ValueTuple.dll" />
<Content Include="Win32\System.Web.Http.dll" />
<Content Include="Win32\System.Web.Http.Owin.dll" />
<Content Include="Win32\System.Xml.ReaderWriter.dll" />
<Content Include="Win32\System.Xml.XDocument.dll" />
<Content Include="Win32\System.Xml.XmlDocument.dll" />
<Content Include="Win32\System.Xml.XmlSerializer.dll" />
<Content Include="Win32\System.Xml.XPath.dll" />
<Content Include="Win32\System.Xml.XPath.XDocument.dll" />
<Content Include="Win32\Unity.Abstractions.dll" />
<Content Include="Win32\Unity.Abstractions.pdb" />
<Content Include="Win32\Unity.Configuration.dll" />
<Content Include="Win32\Unity.Configuration.pdb" />
<Content Include="Win32\Unity.Container.dll" />
<Content Include="Win32\Unity.Container.pdb" />
<Content Include="Win32\Unity.Interception.Configuration.dll" />
<Content Include="Win32\Unity.Interception.Configuration.pdb" />
<Content Include="Win32\Unity.Interception.dll" />
<Content Include="Win32\Unity.Interception.pdb" />
<Content Include="Win32\Unity.RegistrationByConvention.dll" />
<Content Include="Win32\Unity.RegistrationByConvention.pdb" />
<Content Include="Win32\Unity.ServiceLocation.dll" />
<Content Include="Win32\Unity.ServiceLocation.pdb" />
<Content Include="Win32\Unity.WebApi.dll" />
<Content Include="Win32\winpty-agent.exe" />
<Content Include="Win32\winpty-debugserver.exe" />
<Content Include="Win32\winpty.dll" />
<Content Include="Win32\winpty.NET.dll" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Dialogs\ShellProfileSelectionDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\KeyBindingsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@@ -357,6 +281,10 @@
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Page>
<Page Include="Views\CreateKeyBindingDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -377,7 +305,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPages\ShellSettings.xaml">
<Page Include="Views\SettingsPages\ShellProfileSettings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -392,7 +320,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac">
<Version>4.6.2</Version>
<Version>4.8.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.0.6</Version>
@@ -403,11 +331,14 @@
<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>
<PackageReference Include="Newtonsoft.Json">
<Version>10.0.3</Version>
<Version>11.0.2</Version>
</PackageReference>
<PackageReference Include="Win2D.uwp">
<Version>1.21.0</Version>
@@ -428,6 +359,7 @@
<Name>FluentTerminal.RuntimeComponent</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
@@ -436,6 +368,9 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>xcopy /y "$(SolutionDir)FluentTerminal.Client\dist" "$(ProjectDir)Client\"</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+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.7.0" />
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Fluent Terminal</DisplayName>
@@ -6,8 +6,11 @@ namespace FluentTerminal.App.Services
{
public interface IDefaultValueProvider
{
ShellConfiguration GetDefaultShellConfiguration();
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
Guid GetDefaultShellProfileId();
TerminalOptions GetDefaultTerminalOptions();
KeyBindings GetDefaultKeyBindings();
ApplicationSettings GetDefaultApplicationSettings();
Guid GetDefaultThemeId();
IEnumerable<TerminalTheme> GetPreInstalledThemes();
}
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using FluentTerminal.Models;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
@@ -10,6 +11,8 @@ namespace FluentTerminal.App.Services
public interface IDialogService
{
Task<DialogButton> ShowDialogAsnyc(string title, string content, params DialogButton[] buttons);
Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons);
Task<ShellProfile> ShowProfileSelectionDialogAsync();
}
}
@@ -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,13 +8,26 @@ namespace FluentTerminal.App.Services
{
event EventHandler CurrentThemeChanged;
event EventHandler TerminalOptionsChanged;
event EventHandler ApplicationSettingsChanged;
event EventHandler KeyBindingsChanged;
ShellConfiguration GetShellConfiguration();
void SaveShellConfiguration(ShellConfiguration shellConfiguration);
Guid GetDefaultShellProfileId();
ShellProfile GetDefaultShellProfile();
void SaveDefaultShellProfileId(Guid id);
IEnumerable<ShellProfile> GetShellProfiles();
void SaveShellProfile(ShellProfile shellProfile);
void DeleteShellProfile(Guid id);
TerminalOptions GetTerminalOptions();
void SaveTerminalOptions(TerminalOptions terminalOptions);
ApplicationSettings GetApplicationSettings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
KeyBindings GetKeyBindings();
void SaveKeyBindings(KeyBindings keyBindings);
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
TerminalTheme GetTheme(Guid id);
@@ -1,11 +0,0 @@
using FluentTerminal.Models;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface ITerminalService
{
Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellConfiguration shellConfiguration);
Task ResizeTerminal(int id, TerminalSize size);
}
}
@@ -0,0 +1,15 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Responses;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
namespace FluentTerminal.App.Services
{
public interface ITrayProcessCommunicationService
{
Task Initialize(AppServiceConnection appServiceConnection);
Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile);
Task ResizeTerminal(int id, TerminalSize size);
Task UpdateToggleWindowKeyBindings();
}
}
@@ -2,22 +2,155 @@
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
using Windows.System;
namespace FluentTerminal.App.Services.Implementation
{
internal class DefaultValueProvider : IDefaultValueProvider
{
public ShellConfiguration GetDefaultShellConfiguration()
public ApplicationSettings GetDefaultApplicationSettings()
{
return new ShellConfiguration
return new ApplicationSettings
{
Shell = ShellType.PowerShell,
CustomShellLocation = string.Empty,
WorkingDirectory = string.Empty,
Arguments = string.Empty
ConfirmClosingTabs = false,
ConfirmClosingWindows = false,
NewTerminalLocation = NewTerminalLocation.Tab
};
}
public KeyBindings GetDefaultKeyBindings()
{
return new KeyBindings
{
NewTab = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.NewTab,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.T
}
},
ConfigurableNewTab = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.ConfigurableNewTab,
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)VirtualKey.T
}
},
CloseTab = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.CloseTab,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.W
}
},
NextTab = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.NextTab,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.Tab
}
},
PreviousTab = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.PreviousTab,
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)VirtualKey.Tab
}
},
NewWindow = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.NewWindow,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.N
}
},
ShowSettings = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.ShowSettings,
Ctrl = true,
Alt = false,
Shift = false,
Key = 188 // comma
}
},
Copy = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.Copy,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.C
}
},
Paste = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.Paste,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.V
}
},
ToggleWindow = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.ToggleWindow,
Ctrl = false,
Alt = false,
Shift = false,
Key = (int)VirtualKey.Scroll
}
},
Search = new List<KeyBinding>
{
new KeyBinding
{
Command = Command.Search,
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)VirtualKey.F
}
}
};
}
public Guid GetDefaultShellProfileId()
{
return Guid.Parse("813f2298-210a-481a-bdbf-c17bc637a3e2");
}
public TerminalOptions GetDefaultTerminalOptions()
{
return new TerminalOptions
@@ -35,6 +168,31 @@ namespace FluentTerminal.App.Services.Implementation
return Guid.Parse("281e4352-bb50-47b7-a691-2b13830df95e");
}
public IEnumerable<ShellProfile> GetPreinstalledShellProfiles()
{
return new[]
{
new ShellProfile
{
Id = GetDefaultShellProfileId(),
Name = "Powershell",
Arguments = string.Empty,
Location = @"C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe",
PreInstalled = true,
WorkingDirectory = string.Empty
},
new ShellProfile
{
Id = Guid.Parse("ab942a61-7673-4755-9bd8-765aff91d9a3"),
Name = "CMD",
Arguments = string.Empty,
Location = @"C:\Windows\System32\cmd.exe",
PreInstalled = true,
WorkingDirectory = string.Empty
}
};
}
public IEnumerable<TerminalTheme> GetPreInstalledThemes()
{
var defaultXterm = new TerminalTheme
@@ -103,7 +261,40 @@ namespace FluentTerminal.App.Services.Implementation
}
};
return new[] { defaultXterm, powerShell };
var homebrew = new TerminalTheme
{
Id = Guid.Parse("5f034210-067b-40e2-b9c9-6a25eb6fb8e6"),
Author = "Hans Kokx",
Name = "Homebrew",
PreInstalled = true,
BackgroundOpacity = 0.8,
Colors = new TerminalColors
{
Black = "#000000",
Red = "#A0160B",
Green = "#00AF21",
Yellow = "#A1A222",
Blue = "#192AB7",
Magenta = "#AA2FAE",
Cyan = "#12B1BC",
White = "#BBB5AF",
BrightBlack = "#747876",
BrightRed = "#E52213",
BrightGreen = "#00D92B",
BrightYellow = "#E6E435",
BrightBlue = "#283EF9",
BrightMagenta = "#EB43E6",
BrightCyan = "#15E7E8",
BrightWhite = "#E9E9E9",
Foreground = "#00D92B",
Background = "#000000",
Cursor = "#00D92B",
CursorAccent = "#000000",
Selection = "#4CFFFFFF"
}
};
return new[] { defaultXterm, powerShell, homebrew };
}
}
}
}
@@ -1,13 +1,38 @@
using System;
using System.Threading.Tasks;
using FluentTerminal.App.Dialogs;
using FluentTerminal.Models;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Services.Implementation
{
internal class DialogService : IDialogService
public class DialogService : IDialogService
{
public async Task<DialogButton> ShowDialogAsnyc(string title, string content, params DialogButton[] buttons)
private readonly ISettingsService _settingsService;
public DialogService(ISettingsService settingsService)
{
_settingsService = settingsService;
}
public async Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons)
{
if (string.IsNullOrWhiteSpace(title))
{
throw new ArgumentNullException(nameof(title));
}
if (string.IsNullOrWhiteSpace(content))
{
throw new ArgumentNullException(nameof(content));
}
if (buttons.Length == 0)
{
throw new ArgumentException("Must not be empty", nameof(buttons));
}
var dialog = new MessageDialog(content, title);
foreach (var button in buttons)
@@ -19,5 +44,18 @@ namespace FluentTerminal.App.Services.Implementation
return Enum.Parse<DialogButton>(result.Label);
}
public async Task<ShellProfile> ShowProfileSelectionDialogAsync()
{
var dialog = new ShellProfileSelectionDialog(_settingsService);
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
return dialog.SelectedProfile;
}
return null;
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services.Implementation
{
public class KeyboardCommandService : IKeyboardCommandService
{
private readonly Dictionary<Command, Action> _commandHandlers = new Dictionary<Command, Action>();
public void RegisterCommandHandler(Command command, Action handler)
{
if (_commandHandlers.ContainsKey(command))
{
throw new InvalidOperationException("command already registered");
}
_commandHandlers[command] = handler ?? throw new ArgumentNullException(nameof(handler));
}
public void SendCommand(Command command)
{
if (_commandHandlers.TryGetValue(command, out Action handler))
{
handler.Invoke();
return;
}
throw new KeyNotFoundException($"No handler registered for command: {command}");
}
}
}
@@ -10,13 +10,21 @@ namespace FluentTerminal.App.Services.Implementation
{
internal class SettingsService : ISettingsService
{
public const string ThemesContainerName = "Themes";
public const string ShellProfilesContainerName = "ShellProfiles";
public const string CurrentThemeKey = "CurrentTheme";
public const string DefaultShellProfileKey = "DefaultShellProfile";
private readonly IDefaultValueProvider _defaultValueProvider;
private ApplicationDataContainer _localSettings;
private ApplicationDataContainer _themes;
private ApplicationDataContainer _roamingSettings;
private readonly ApplicationDataContainer _localSettings;
private readonly ApplicationDataContainer _themes;
private readonly ApplicationDataContainer _shellProfiles;
private readonly ApplicationDataContainer _roamingSettings;
public event EventHandler CurrentThemeChanged;
public event EventHandler TerminalOptionsChanged;
public event EventHandler ApplicationSettingsChanged;
public event EventHandler KeyBindingsChanged;
public SettingsService(IDefaultValueProvider defaultValueProvider)
{
@@ -24,29 +32,18 @@ namespace FluentTerminal.App.Services.Implementation
_localSettings = ApplicationData.Current.LocalSettings;
_roamingSettings = ApplicationData.Current.RoamingSettings;
_themes = _roamingSettings.CreateContainer("Themes", ApplicationDataCreateDisposition.Always);
_themes = _roamingSettings.CreateContainer(ThemesContainerName, ApplicationDataCreateDisposition.Always);
_shellProfiles = _roamingSettings.CreateContainer(ShellProfilesContainerName, ApplicationDataCreateDisposition.Always);
foreach (var theme in _defaultValueProvider.GetPreInstalledThemes())
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
}
ApplicationData.Current.DataChanged += OnDataChanged;
}
private void OnDataChanged(ApplicationData sender, object args)
{
//todo handle data changed for roaming settings
}
public ShellConfiguration GetShellConfiguration()
{
return _localSettings.ReadValueFromJson(nameof(ShellConfiguration), _defaultValueProvider.GetDefaultShellConfiguration());
}
public void SaveShellConfiguration(ShellConfiguration shellConfiguration)
{
_localSettings.WriteValueAsJson(nameof(ShellConfiguration), shellConfiguration);
foreach (var shellProfile in _defaultValueProvider.GetPreinstalledShellProfiles())
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
}
}
public TerminalTheme GetCurrentTheme()
@@ -57,7 +54,7 @@ namespace FluentTerminal.App.Services.Implementation
public Guid GetCurrentThemeId()
{
if (_roamingSettings.Values.TryGetValue("CurrentTheme", out object value))
if (_roamingSettings.Values.TryGetValue(CurrentThemeKey, out object value))
{
return (Guid)value;
}
@@ -66,7 +63,7 @@ namespace FluentTerminal.App.Services.Implementation
public void SaveCurrentThemeId(Guid id)
{
_roamingSettings.Values["CurrentTheme"] = id;
_roamingSettings.Values[CurrentThemeKey] = id;
CurrentThemeChanged?.Invoke(this, EventArgs.Empty);
}
@@ -98,13 +95,83 @@ 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 KeyBindings GetKeyBindings()
{
var defaults = _defaultValueProvider.GetDefaultKeyBindings();
var keyBindings = _roamingSettings.ReadValueFromJson<KeyBindings>(nameof(KeyBindings), null);
if (keyBindings.ConfigurableNewTab == null)
{
keyBindings.ConfigurableNewTab = defaults.ConfigurableNewTab;
}
if (keyBindings.Search == null)
{
keyBindings.Search = defaults.Search;
}
return keyBindings ?? _defaultValueProvider.GetDefaultKeyBindings();
}
public void SaveKeyBindings(KeyBindings keyBindings)
{
_roamingSettings.WriteValueAsJson(nameof(KeyBindings), keyBindings);
KeyBindingsChanged?.Invoke(this, EventArgs.Empty);
}
public Guid GetDefaultShellProfileId()
{
if (_roamingSettings.Values.TryGetValue(DefaultShellProfileKey, out object value))
{
return (Guid)value;
}
return _defaultValueProvider.GetDefaultShellProfileId();
}
public ShellProfile GetDefaultShellProfile()
{
var id = GetDefaultShellProfileId();
return _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
}
public void SaveDefaultShellProfileId(Guid id)
{
_roamingSettings.Values[DefaultShellProfileKey] = id;
}
public IEnumerable<ShellProfile> GetShellProfiles()
{
return _shellProfiles.Values.Select(x => JsonConvert.DeserializeObject<ShellProfile>((string)x.Value)).ToList();
}
public void SaveShellProfile(ShellProfile shellProfile)
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
}
public void DeleteShellProfile(Guid id)
{
_shellProfiles.Values.Remove(id.ToString());
}
}
}
@@ -1,37 +0,0 @@
using System;
using System.Threading.Tasks;
using FluentTerminal.App.Utilities;
using FluentTerminal.Models;
using Windows.Web.Http;
namespace FluentTerminal.App.Services.Implementation
{
internal class TerminalService : ITerminalService
{
private readonly HttpClient _httpClient;
public TerminalService()
{
_httpClient = new HttpClient();
}
public async Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellConfiguration shellConfiguration)
{
var request = new CreateTerminalRequest
{
Size = size,
Configuration = shellConfiguration
};
var response = await _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals"), HttpJsonContent.From(request));
var createTerminalResponse = await response.Content.ReadAs<CreateTerminalResponse>();
return createTerminalResponse;
}
public Task ResizeTerminal(int id, TerminalSize size)
{
return _httpClient.PostAsync(new Uri($"http://localhost:9000/terminals/{id}/resize"), HttpJsonContent.From(size)).AsTask();
}
}
}
@@ -0,0 +1,82 @@
using System;
using System.Threading.Tasks;
using FluentTerminal.Models;
using FluentTerminal.Models.Requests;
using FluentTerminal.Models.Responses;
using Newtonsoft.Json;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;
namespace FluentTerminal.App.Services.Implementation
{
internal class TrayProcessCommunicationService : ITrayProcessCommunicationService
{
private readonly ISettingsService _settingsService;
private AppServiceConnection _appServiceConnection;
public TrayProcessCommunicationService(ISettingsService settingsService)
{
_settingsService = settingsService;
}
public async Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile)
{
var request = new CreateTerminalRequest
{
Size = size,
Profile = shellProfile
};
var message = new ValueSet
{
{ MessageKeys.Type, MessageTypes.CreateTerminalRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
};
var responseMessage = await _appServiceConnection.SendMessageAsync(message);
return JsonConvert.DeserializeObject<CreateTerminalResponse>((string)responseMessage.Message[MessageKeys.Content]);
}
public Task Initialize(AppServiceConnection appServiceConnection)
{
_appServiceConnection = appServiceConnection;
return UpdateToggleWindowKeyBindings();
}
public Task ResizeTerminal(int id, TerminalSize size)
{
var request = new ResizeTerminalRequest
{
TerminalId = id,
NewSize = size
};
var message = new ValueSet
{
{ MessageKeys.Type, MessageTypes.ResizeTerminalRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
};
return _appServiceConnection.SendMessageAsync(message).AsTask();
}
public Task UpdateToggleWindowKeyBindings()
{
var keyBindings = _settingsService.GetKeyBindings().ToggleWindow;
var request = new SetToggleWindowKeyBindingsRequest
{
KeyBindings = keyBindings
};
var message = new ValueSet
{
{ MessageKeys.Type, MessageTypes.SetToggleWindowKeyBindingsRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
};
return _appServiceConnection.SendMessageAsync(message).AsTask();
}
}
}
+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"
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using Windows.Storage;
namespace FluentTerminal.App.Utilities
@@ -10,7 +11,7 @@ namespace FluentTerminal.App.Utilities
{
if (container.Values.TryGetValue(name, out object value))
{
if (fallbackValue == null)
if (EqualityComparer<T>.Default.Equals(fallbackValue, default))
{
fallbackValue = Activator.CreateInstance<T>();
}
@@ -22,8 +23,7 @@ namespace FluentTerminal.App.Utilities
public static void WriteValueAsJson<T>(this ApplicationDataContainer container, string name, T value)
{
var serialized = JsonConvert.SerializeObject(value);
container.Values[name] = serialized;
container.Values[name] = JsonConvert.SerializeObject(value);
}
}
}
@@ -40,7 +40,7 @@ namespace FluentTerminal.App.Utilities
B = byte.Parse(parts[2])
};
}
else
else
{
return colorString.ToColor();
}
@@ -1,14 +0,0 @@
using Newtonsoft.Json;
using Windows.Web.Http;
namespace FluentTerminal.App.Utilities
{
public static class HttpJsonContent
{
public static HttpStringContent From(object content)
{
var serialized = JsonConvert.SerializeObject(content);
return new HttpStringContent(serialized, Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json");
}
}
}
@@ -1,16 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;
using Windows.Web.Http;
namespace FluentTerminal.App.Utilities
{
public static class IHttpContentExtensions
{
public static async Task<T> ReadAs<T>(this IHttpContent content)
{
var contentBody = await content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(contentBody);
}
}
}
+173 -33
View File
@@ -1,62 +1,93 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Dialogs;
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;
using Windows.UI.ViewManagement;
namespace FluentTerminal.App.ViewModels
{
public class MainViewModel : ViewModelBase
{
private const string FallbackTitle = "Fluent Terminal";
private readonly ISettingsService _settingsService;
private readonly ITerminalService _terminalService;
private readonly IDialogService _dialogService;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private ApplicationSettings _applicationSettings;
private string _background;
private double _backgroundOpacity;
private CoreDispatcher _dispatcher;
private readonly CoreDispatcher _dispatcher;
private int _nextTerminalId;
private TerminalViewModel _selectedTerminal;
private string _title;
public MainViewModel(ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService)
public event EventHandler Closed;
public event EventHandler NewWindowRequested;
public event EventHandler ShowSettingsRequested;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService)
{
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_terminalService = terminalService;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_trayProcessCommunicationService = trayProcessCommunicationService;
_dialogService = dialogService;
Title = FallbackTitle;
_keyboardCommandService = keyboardCommandService;
_keyboardCommandService.RegisterCommandHandler(Command.NewTab, () => AddTerminal(null, false));
_keyboardCommandService.RegisterCommandHandler(Command.ConfigurableNewTab, () => AddTerminal(null, true));
_keyboardCommandService.RegisterCommandHandler(Command.CloseTab, CloseCurrentTab);
_keyboardCommandService.RegisterCommandHandler(Command.NextTab, SelectNextTab);
_keyboardCommandService.RegisterCommandHandler(Command.PreviousTab, SelectPreviousTab);
_keyboardCommandService.RegisterCommandHandler(Command.NewWindow, NewWindow);
_keyboardCommandService.RegisterCommandHandler(Command.ShowSettings, 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());
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false));
ShowSettingsCommand = new RelayCommand(ShowSettings);
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
Title = "Fluent Terminal";
_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; }
@@ -69,26 +100,86 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _title, value);
}
public void AddTerminal(string startupDirectory)
public Task AddTerminal(string startupDirectory, bool showProfileSelection)
{
var terminal = new TerminalViewModel(_settingsService, _terminalService, _dialogService, startupDirectory);
terminal.PropertyChanged += (s, e) =>
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
if (e.PropertyName == nameof(TerminalViewModel.Title) && Terminals.Count == 1)
ShellProfile profile = null;
if (showProfileSelection)
{
if (string.IsNullOrWhiteSpace(terminal.Title))
profile = await _dialogService.ShowProfileSelectionDialogAsync();
if (profile == null)
{
Title = FallbackTitle;
}
else
{
Title = terminal.Title;
return;
}
}
};
Terminals.Add(terminal);
else
{
profile = _settingsService.GetDefaultShellProfile();
}
SelectedTerminal = terminal;
var terminal = new TerminalViewModel(GetNextTerminalId(), _settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService, _applicationSettings, startupDirectory, profile);
terminal.CloseRequested += OnTerminalCloseRequested;
terminal.TitleChanged += OnTitleChanged;
Terminals.Add(terminal);
SelectedTerminal = terminal;
}).AsTask();
}
public void CloseAllTerminals()
{
foreach (var terminal in Terminals)
{
terminal.CloseView();
}
}
private void CloseCurrentTab()
{
SelectedTerminal?.CloseCommand.Execute(null);
}
private int GetNextTerminalId()
{
return _nextTerminalId++;
}
private void NewWindow()
{
NewWindowRequested?.Invoke(this, EventArgs.Empty);
}
private async void OnApplicationSettingsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => _applicationSettings = _settingsService.GetApplicationSettings());
}
private async void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
var deferral = e.GetDeferral();
if (_applicationSettings.ConfirmClosingWindows)
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this window?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
if (result == DialogButton.OK)
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
}
else
{
e.Handled = true;
}
}
else
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
}
deferral.Complete();
}
private async void OnCurrentThemeChanged(object sender, System.EventArgs e)
@@ -101,9 +192,58 @@ namespace FluentTerminal.App.ViewModels
});
}
private Task ShowSettings()
private void OnTerminalCloseRequested(object sender, EventArgs e)
{
return App.Instance.ShowSettings();
if (sender is TerminalViewModel terminal)
{
terminal.CloseView();
if (SelectedTerminal == terminal)
{
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
}
Terminals.Remove(terminal);
if (Terminals.Count == 0)
{
Closed?.Invoke(this, EventArgs.Empty);
ApplicationView.GetForCurrentView().TryConsolidateAsync();
}
}
}
private void OnTerminalsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (Terminals.Any())
{
Title = Terminals.First().Title;
}
}
private void OnTitleChanged(object sender, string e)
{
if (sender is TerminalViewModel terminal && Terminals.First() == terminal)
{
Title = e;
}
}
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 void ShowSettings()
{
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,101 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class GeneralPageViewModel : ViewModelBase
{
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private readonly ApplicationSettings _applicationSettings;
private bool _editingNewTerminalLocation;
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_applicationSettings = _settingsService.GetApplicationSettings();
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
}
public bool ConfirmClosingTabs
{
get => _applicationSettings.ConfirmClosingTabs;
set
{
if (_applicationSettings.ConfirmClosingTabs != value)
{
_applicationSettings.ConfirmClosingTabs = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool ConfirmClosingWindows
{
get => _applicationSettings.ConfirmClosingWindows;
set
{
if (_applicationSettings.ConfirmClosingWindows != value)
{
_applicationSettings.ConfirmClosingWindows = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public NewTerminalLocation NewTerminalLocation
{
get => _applicationSettings.NewTerminalLocation;
set
{
if (_applicationSettings.NewTerminalLocation != value && !_editingNewTerminalLocation)
{
_editingNewTerminalLocation = true;
_applicationSettings.NewTerminalLocation = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(WindowIsSelected));
RaisePropertyChanged(nameof(TabIsSelected));
_editingNewTerminalLocation = false;
}
}
}
public RelayCommand RestoreDefaultsCommand { get; }
public bool TabIsSelected
{
get => NewTerminalLocation == NewTerminalLocation.Tab;
set => NewTerminalLocation = NewTerminalLocation.Tab;
}
public bool WindowIsSelected
{
get => NewTerminalLocation == NewTerminalLocation.Window;
set => NewTerminalLocation = NewTerminalLocation.Window;
}
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the general settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
if (result == DialogButton.OK)
{
var defaults = _defaultValueProvider.GetDefaultApplicationSettings();
ConfirmClosingWindows = defaults.ConfirmClosingWindows;
ConfirmClosingTabs = defaults.ConfirmClosingTabs;
NewTerminalLocation = defaults.NewTerminalLocation;
}
}
}
}
@@ -0,0 +1,129 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Views;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.ViewModels.Settings
{
public class KeyBindingViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
public KeyBindingViewModel(KeyBinding keyBinding, IDialogService dialogService)
{
Model = keyBinding;
_dialogService = dialogService;
EditCommand = new RelayCommand(async () => await Edit().ConfigureAwait(false));
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false));
}
public event EventHandler Deleted;
public event EventHandler Edited;
public bool Meta
{
get => Model.Meta;
set
{
if (Model.Meta != value)
{
Model.Meta = value;
RaisePropertyChanged();
}
}
}
public bool Alt
{
get => Model.Alt;
set
{
if (Model.Alt != value)
{
Model.Alt = value;
RaisePropertyChanged();
}
}
}
public bool Ctrl
{
get => Model.Ctrl;
set
{
if (Model.Ctrl != value)
{
Model.Ctrl = value;
RaisePropertyChanged();
}
}
}
public RelayCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public int Key
{
get => Model.Key;
set
{
if (Model.Key != value)
{
Model.Key = value;
RaisePropertyChanged();
}
}
}
public KeyBinding Model { get; }
public bool Shift
{
get => Model.Shift;
set
{
if (Model.Shift != value)
{
Model.Shift = value;
RaisePropertyChanged();
}
}
}
public async Task<bool> Edit()
{
var dialog = new CreateKeyBindingDialog(_dialogService);
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
Alt = dialog.KeyBinding.Alt;
Ctrl = dialog.KeyBinding.Ctrl;
Shift = dialog.KeyBinding.Shift;
Meta = dialog.KeyBinding.Meta;
Key = dialog.KeyBinding.Key;
Edited?.Invoke(this, EventArgs.Empty);
return true;
}
return false;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this keybinding?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
}
}
@@ -0,0 +1,131 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class KeyBindingsPageViewModel : ViewModelBase
{
private KeyBindings _keyBindings;
private readonly ISettingsService _settingsService;
private readonly IDialogService _dialogService;
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
public KeyBindingsPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, ITrayProcessCommunicationService trayProcessCommunicationService)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_trayProcessCommunicationService = trayProcessCommunicationService;
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
AddCommand = new RelayCommand<Command>(async command => await Add(command).ConfigureAwait(false));
Initialize(_settingsService.GetKeyBindings());
}
private void Initialize(KeyBindings keyBindings)
{
_keyBindings = keyBindings;
NewTab = CreateViewModel(Command.NewTab, _keyBindings.NewTab);
ConfigurableNewTab = CreateViewModel(Command.ConfigurableNewTab, _keyBindings.ConfigurableNewTab);
ToggleWindow = CreateViewModel(Command.ToggleWindow, _keyBindings.ToggleWindow);
NextTab = CreateViewModel(Command.NextTab, _keyBindings.NextTab);
PreviousTab = CreateViewModel(Command.PreviousTab, _keyBindings.PreviousTab);
CloseTab = CreateViewModel(Command.CloseTab, _keyBindings.CloseTab);
NewWindow = CreateViewModel(Command.NewWindow, _keyBindings.NewWindow);
ShowSettings = CreateViewModel(Command.ShowSettings, _keyBindings.ShowSettings);
Copy = CreateViewModel(Command.Copy, _keyBindings.Copy);
Paste = CreateViewModel(Command.Paste, _keyBindings.Paste);
Search = CreateViewModel(Command.Search, _keyBindings.Search);
RaisePropertyChanged(nameof(NewTab));
RaisePropertyChanged(nameof(ConfigurableNewTab));
RaisePropertyChanged(nameof(ToggleWindow));
RaisePropertyChanged(nameof(NextTab));
RaisePropertyChanged(nameof(PreviousTab));
RaisePropertyChanged(nameof(CloseTab));
RaisePropertyChanged(nameof(NewWindow));
RaisePropertyChanged(nameof(ShowSettings));
RaisePropertyChanged(nameof(Copy));
RaisePropertyChanged(nameof(Paste));
RaisePropertyChanged(nameof(Search));
}
public KeyBindingsViewModel NewTab { get; private set; }
public KeyBindingsViewModel ConfigurableNewTab { get; private set; }
public KeyBindingsViewModel ToggleWindow { get; private set; }
public KeyBindingsViewModel NextTab { get; private set; }
public KeyBindingsViewModel PreviousTab { get; private set; }
public KeyBindingsViewModel CloseTab { get; private set; }
public KeyBindingsViewModel NewWindow { get; private set; }
public KeyBindingsViewModel ShowSettings { get; private set; }
public KeyBindingsViewModel Copy { get; private set; }
public KeyBindingsViewModel Paste { get; private set; }
public KeyBindingsViewModel Search { get; private set; }
public RelayCommand RestoreDefaultsCommand { get; }
public RelayCommand<Command> AddCommand { get; }
private Task Add(Command command)
{
switch (command)
{
case Command.ToggleWindow:
return ToggleWindow.Add();
case Command.NextTab:
return NextTab.Add();
case Command.PreviousTab:
return PreviousTab.Add();
case Command.NewTab:
return NewTab.Add();
case Command.ConfigurableNewTab:
return ConfigurableNewTab.Add();
case Command.CloseTab:
return CloseTab.Add();
case Command.NewWindow:
return NewWindow.Add();
case Command.ShowSettings:
return ShowSettings.Add();
case Command.Copy:
return Copy.Add();
case Command.Paste:
return Paste.Add();
case Command.Search:
return Search.Add();
}
return Task.CompletedTask;
}
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the default keybindings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Initialize(_defaultValueProvider.GetDefaultKeyBindings());
_settingsService.SaveKeyBindings(_keyBindings);
}
}
private KeyBindingsViewModel CreateViewModel(Command command, ICollection<KeyBinding> keyBindings)
{
var viewModel = new KeyBindingsViewModel(command, keyBindings, _dialogService);
viewModel.Edited += OnEdited;
return viewModel;
}
private void OnEdited(object sender, System.EventArgs e)
{
_settingsService.SaveKeyBindings(_keyBindings);
_trayProcessCommunicationService.UpdateToggleWindowKeyBindings();
}
}
}
@@ -0,0 +1,66 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class KeyBindingsViewModel : ViewModelBase
{
private readonly Command _command;
private readonly IDialogService _dialogService;
private readonly ICollection<KeyBinding> _keyBindings;
public KeyBindingsViewModel(Command command, ICollection<KeyBinding> keyBindings, IDialogService dialogService)
{
_command = command;
_keyBindings = keyBindings;
_dialogService = dialogService;
foreach (var binding in keyBindings)
{
var viewModel = new KeyBindingViewModel(binding, _dialogService);
viewModel.Deleted += ViewModel_Deleted;
viewModel.Edited += ViewModel_Edited;
KeyBindings.Add(viewModel);
}
}
public event EventHandler Edited;
public ObservableCollection<KeyBindingViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingViewModel>();
public async Task Add()
{
var newKeyBinding = new KeyBindingViewModel(new KeyBinding { Command = _command }, _dialogService);
if (await newKeyBinding.Edit().ConfigureAwait(true))
{
newKeyBinding.Deleted += ViewModel_Deleted;
newKeyBinding.Edited += ViewModel_Edited;
KeyBindings.Add(newKeyBinding);
_keyBindings.Add(newKeyBinding.Model);
Edited?.Invoke(this, EventArgs.Empty);
}
}
private void ViewModel_Deleted(object sender, EventArgs e)
{
if (sender is KeyBindingViewModel keyBinding)
{
_keyBindings.Remove(keyBinding.Model);
KeyBindings.Remove(keyBinding);
Edited?.Invoke(this, EventArgs.Empty);
}
}
private void ViewModel_Edited(object sender, EventArgs e)
{
Edited?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,104 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace FluentTerminal.App.ViewModels.Settings
{
public class ProfilesPageViewModel : ViewModelBase
{
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private ShellProfileViewModel _selectedShellProfile;
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
CreateShellProfileCommand = new RelayCommand(CreateShellProfile);
var defaultShellProfileId = _settingsService.GetDefaultShellProfileId();
foreach (var shellProfile in _settingsService.GetShellProfiles())
{
var viewModel = new ShellProfileViewModel(shellProfile, settingsService, dialogService);
viewModel.Deleted += OnShellProfileDeleted;
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
if (shellProfile.Id == defaultShellProfileId)
{
viewModel.IsDefault = true;
}
ShellProfiles.Add(viewModel);
}
SelectedShellProfile = ShellProfiles.First(p => p.IsDefault);
}
private void OnShellProfileSetAsDefault(object sender, EventArgs e)
{
if (sender is ShellProfileViewModel defaultShellProfile)
{
_settingsService.SaveDefaultShellProfileId(defaultShellProfile.Id);
foreach (var shellProfile in ShellProfiles)
{
shellProfile.IsDefault = shellProfile.Id == defaultShellProfile.Id;
}
}
}
private void OnShellProfileDeleted(object sender, EventArgs e)
{
if (sender is ShellProfileViewModel shellProfile)
{
if (SelectedShellProfile == shellProfile)
{
SelectedShellProfile = ShellProfiles.First();
}
ShellProfiles.Remove(shellProfile);
if (shellProfile.IsDefault)
{
ShellProfiles.First().IsDefault = true;
_settingsService.SaveDefaultShellProfileId(ShellProfiles.First().Id);
}
_settingsService.DeleteShellProfile(shellProfile.Id);
}
}
public RelayCommand CreateShellProfileCommand { get; }
private void CreateShellProfile()
{
var shellProfile = new ShellProfile
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = "New profile"
};
_settingsService.SaveShellProfile(shellProfile);
var viewModel = new ShellProfileViewModel(shellProfile, _settingsService, _dialogService);
viewModel.EditCommand.Execute(null);
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
viewModel.Deleted += OnShellProfileDeleted;
ShellProfiles.Add(viewModel);
SelectedShellProfile = viewModel;
}
public ShellProfileViewModel SelectedShellProfile
{
get => _selectedShellProfile;
set => Set(ref _selectedShellProfile, value);
}
public ObservableCollection<ShellProfileViewModel> ShellProfiles { get; } = new ObservableCollection<ShellProfileViewModel>();
}
}
@@ -0,0 +1,134 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Graphics.Canvas.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class TerminalPageViewModel : ViewModelBase
{
private readonly TerminalOptions _terminalOptions;
private readonly ISettingsService _settingsService;
private readonly IDialogService _dialogService;
private readonly IDefaultValueProvider _defaultValueProvider;
private bool _isEditingCursorStyle;
public bool BarIsSelected
{
get => CursorStyle == CursorStyle.Bar;
set => CursorStyle = CursorStyle.Bar;
}
public bool BlockIsSelected
{
get => CursorStyle == CursorStyle.Block;
set => CursorStyle = CursorStyle.Block;
}
public bool CursorBlink
{
get => _terminalOptions.CursorBlink;
set
{
if (_terminalOptions.CursorBlink != value)
{
_terminalOptions.CursorBlink = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public string FontFamily
{
get => _terminalOptions.FontFamily;
set
{
if (_terminalOptions.FontFamily != value)
{
_terminalOptions.FontFamily = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public IEnumerable<string> Fonts { get; }
public int FontSize
{
get => _terminalOptions.FontSize;
set
{
if (_terminalOptions.FontSize != value)
{
_terminalOptions.FontSize = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
public IEnumerable<int> Sizes { get; }
public bool UnderlineIsSelected
{
get => CursorStyle == CursorStyle.Underline;
set => CursorStyle = CursorStyle.Underline;
}
public RelayCommand RestoreDefaultsCommand { get; }
private CursorStyle CursorStyle
{
get => _terminalOptions.CursorStyle;
set
{
if (_terminalOptions.CursorStyle != value && !_isEditingCursorStyle)
{
_isEditingCursorStyle = true;
_terminalOptions.CursorStyle = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BlockIsSelected));
RaisePropertyChanged(nameof(BarIsSelected));
RaisePropertyChanged(nameof(UnderlineIsSelected));
_isEditingCursorStyle = false;
}
}
}
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the default terminal options?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
if (result == DialogButton.OK)
{
var defaults = _defaultValueProvider.GetDefaultTerminalOptions();
CursorBlink = defaults.CursorBlink;
CursorStyle = defaults.CursorStyle;
FontFamily = defaults.FontFamily;
FontSize = defaults.FontSize;
}
}
public TerminalPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
Fonts = CanvasTextFormat.GetSystemFontFamilies().OrderBy(s => s);
Sizes = Enumerable.Range(1, 72);
_terminalOptions = _settingsService.GetTerminalOptions();
}
}
}
@@ -0,0 +1,106 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace FluentTerminal.App.ViewModels.Settings
{
public class ThemesPageViewModel : ViewModelBase
{
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private ThemeViewModel _selectedTheme;
public ThemesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
CreateThemeCommand = new RelayCommand(CreateTheme);
var activeThemeId = _settingsService.GetCurrentThemeId();
foreach (var theme in _settingsService.GetThemes())
{
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
if (theme.Id == activeThemeId)
{
viewModel.IsActive = true;
}
Themes.Add(viewModel);
}
SelectedTheme = Themes.First(t => t.IsActive);
}
public RelayCommand CreateThemeCommand { get; }
public ThemeViewModel SelectedTheme
{
get => _selectedTheme;
set => Set(ref _selectedTheme, value);
}
public ObservableCollection<ThemeViewModel> Themes { get; } = new ObservableCollection<ThemeViewModel>();
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);
}
}
}
}
@@ -1,333 +1,32 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.App.ViewModels.Settings;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Graphics.Canvas.Text;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
namespace FluentTerminal.App.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
private readonly ISettingsService _settingsService;
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private ShellConfiguration _shellConfiguration;
private TerminalOptions _terminalOptions;
private ThemeViewModel _selectedTheme;
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService)
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService, ITrayProcessCommunicationService trayProcessCommunicationService)
{
_settingsService = settingsService;
_defaultValueProvider = defaultValueProvider;
_dialogService = dialogService;
BrowseForCustomShellCommand = new RelayCommand(async () => await BrowseForCustomShell());
BrowseForWorkingDirectoryCommand = new RelayCommand(async () => await BrowseForWorkingDirectory());
RestoreDefaultsCommand = new RelayCommand<string>(async (area) => await RestoreDefaults(area));
CreateThemeCommand = new RelayCommand(CreateTheme);
_shellConfiguration = _settingsService.GetShellConfiguration();
_terminalOptions = _settingsService.GetTerminalOptions();
ShellType = _shellConfiguration.Shell;
var activeThemeId = _settingsService.GetCurrentThemeId();
foreach (var theme in _settingsService.GetThemes())
{
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
if (theme.Id == activeThemeId)
{
viewModel.IsActive = true;
}
Themes.Add(viewModel);
}
SelectedTheme = Themes.First();
Fonts = CanvasTextFormat.GetSystemFontFamilies().OrderBy(s => s);
Sizes = Enumerable.Range(1, 72);
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, defaultValueProvider, trayProcessCommunicationService);
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider);
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider);
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider);
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider);
}
private void OnThemeActivated(object sender, EventArgs e)
public event EventHandler Closed;
public GeneralPageViewModel General { get; }
public KeyBindingsPageViewModel KeyBindings { get; }
public ProfilesPageViewModel Shell { get; }
public TerminalPageViewModel Terminal { get; }
public ThemesPageViewModel Themes { get; }
public void Close()
{
if (sender is ThemeViewModel activatedTheme)
{
_settingsService.SaveCurrentThemeId(activatedTheme.Id);
foreach (var theme in Themes)
{
theme.IsActive = theme.Id == activatedTheme.Id;
}
}
}
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 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);
}
}
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
{
get => _selectedTheme;
set => Set(ref _selectedTheme, value);
}
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));
}
}
}
public bool CursorBlink
{
get => _terminalOptions.CursorBlink;
set
{
if (_terminalOptions.CursorBlink != value)
{
_terminalOptions.CursorBlink = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
}
}
}
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;
set => ShellType = ShellType.Custom;
}
public string CustomShellLocation
{
get => _shellConfiguration.CustomShellLocation;
set
{
if (_shellConfiguration.CustomShellLocation != value)
{
_shellConfiguration.CustomShellLocation = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
}
}
}
public string WorkingDirectory
{
get => _shellConfiguration.WorkingDirectory;
set
{
if (_shellConfiguration.WorkingDirectory != value)
{
_shellConfiguration.WorkingDirectory = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
}
}
}
public string Arguments
{
get => _shellConfiguration.Arguments;
set
{
if (_shellConfiguration.Arguments != value)
{
_shellConfiguration.Arguments = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
}
}
}
public bool PoweShellIsSelected
{
get => ShellType == ShellType.PowerShell;
set => ShellType = ShellType.PowerShell;
}
public ShellType ShellType
{
get => _shellConfiguration.Shell;
set
{
if (_shellConfiguration.Shell != value)
{
_shellConfiguration.Shell = value;
_settingsService.SaveShellConfiguration(_shellConfiguration);
RaisePropertyChanged();
RaisePropertyChanged(nameof(CustomShellIsSelected));
RaisePropertyChanged(nameof(PoweShellIsSelected));
RaisePropertyChanged(nameof(CMDIsSelected));
}
}
}
private async Task BrowseForCustomShell()
{
var picker = new FileOpenPicker();
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync().AsTask();
if (file != null)
{
CustomShellLocation = file.Path;
}
}
private async Task BrowseForWorkingDirectory()
{
var picker = new FolderPicker();
picker.FileTypeFilter.Add(".whatever"); // else a ComException is thrown
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
WorkingDirectory = folder.Path;
}
}
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);
if (result == DialogButton.OK && area == "shell")
{
var defaults = _defaultValueProvider.GetDefaultShellConfiguration();
ShellType = defaults.Shell;
CustomShellLocation = defaults.CustomShellLocation;
WorkingDirectory = defaults.WorkingDirectory;
Arguments = defaults.Arguments;
}
else if(result == DialogButton.OK && area == "terminal")
{
var defaults = _defaultValueProvider.GetDefaultTerminalOptions();
CursorBlink = defaults.CursorBlink;
CursorStyle = defaults.CursorStyle;
FontFamily = defaults.FontFamily;
FontSize = defaults.FontSize;
}
Closed?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,186 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
namespace FluentTerminal.App.ViewModels
{
public class ShellProfileViewModel : ViewModelBase
{
private readonly ShellProfile _shellProfile;
private string _name;
private string _arguments;
private string _location;
private string _workingDirectory;
private string _fallbackName;
private string _fallbackArguments;
private string _fallbackLocation;
private string _fallbackWorkingDirectory;
private bool _isDefault;
private bool _inEditMode;
private readonly ISettingsService _settingsService;
private readonly IDialogService _dialogService;
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService)
{
_shellProfile = shellProfile;
_settingsService = settingsService;
_dialogService = dialogService;
Id = shellProfile.Id;
Name = shellProfile.Name;
Arguments = shellProfile.Arguments;
Location = shellProfile.Location;
WorkingDirectory = shellProfile.WorkingDirectory;
SetDefaultCommand = new RelayCommand(SetDefault);
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), CanDelete);
EditCommand = new RelayCommand(Edit, CanEdit);
CancelEditCommand = new RelayCommand(async () => await CancelEdit().ConfigureAwait(false));
SaveChangesCommand = new RelayCommand(SaveChanges);
BrowseForCustomShellCommand = new RelayCommand(async () => await BrowseForCustomShell().ConfigureAwait(false));
BrowseForWorkingDirectoryCommand = new RelayCommand(async () => await BrowseForWorkingDirectory().ConfigureAwait(false));
}
public event EventHandler Deleted;
public event EventHandler SetAsDefault;
public RelayCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public RelayCommand SaveChangesCommand { get; }
public RelayCommand CancelEditCommand { get; }
public RelayCommand SetDefaultCommand { get; }
public RelayCommand BrowseForCustomShellCommand { get; }
public RelayCommand BrowseForWorkingDirectoryCommand { get; }
public Guid Id { get; }
public string Name
{
get => _name;
set => Set(ref _name, value);
}
public string Arguments
{
get => _arguments;
set => Set(ref _arguments, value);
}
public string Location
{
get => _location;
set => Set(ref _location, value);
}
public string WorkingDirectory
{
get => _workingDirectory;
set => Set(ref _workingDirectory, value);
}
public bool IsDefault
{
get => _isDefault;
set => Set(ref _isDefault, value);
}
public bool InEditMode
{
get => _inEditMode;
set => Set(ref _inEditMode, value);
}
public void SaveChanges()
{
_shellProfile.Arguments = Arguments;
_shellProfile.Location = Location;
_shellProfile.Name = Name;
_shellProfile.WorkingDirectory = WorkingDirectory;
_settingsService.SaveShellProfile(_shellProfile);
InEditMode = false;
}
private async Task CancelEdit()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Arguments = _fallbackArguments;
Location = _fallbackLocation;
Name = _fallbackName;
WorkingDirectory = _fallbackWorkingDirectory;
InEditMode = false;
}
}
private void Edit()
{
_fallbackArguments = Arguments;
_fallbackLocation = Location;
_fallbackName = Name;
_fallbackWorkingDirectory = WorkingDirectory;
InEditMode = true;
}
private bool CanDelete()
{
return !_shellProfile.PreInstalled;
}
private bool CanEdit()
{
return !_shellProfile.PreInstalled;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
private void SetDefault()
{
SetAsDefault?.Invoke(this, EventArgs.Empty);
}
private async Task BrowseForCustomShell()
{
var picker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.ComputerFolder
};
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync();
if (file != null)
{
Location = file.Path;
}
}
private async Task BrowseForWorkingDirectory()
{
var picker = new FolderPicker();
picker.FileTypeFilter.Add(".whatever"); // else a ComException is thrown
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
WorkingDirectory = folder.Path;
}
}
}
}
@@ -1,10 +1,14 @@
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.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Core;
using Windows.UI.Xaml;
@@ -12,37 +16,91 @@ namespace FluentTerminal.App.ViewModels
{
public class TerminalViewModel : ViewModelBase
{
private readonly ISettingsService _settingsService;
private readonly ITerminalService _terminalService;
private readonly IDialogService _dialogService;
private CoreDispatcher _dispatcher;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private ApplicationSettings _applicationSettings;
private readonly CoreDispatcher _dispatcher;
private string _resizeOverlayContent;
private DispatcherTimer _resizeOverlayTimer;
private readonly DispatcherTimer _resizeOverlayTimer;
private bool _showResizeOverlay;
private string _startupDirectory;
private readonly string _startupDirectory;
private int _terminalId;
private ITerminalView _terminalView;
private string _title;
private readonly ShellProfile _shellProfile;
private bool _showSearchPanel;
private string _searchText;
public TerminalViewModel(ISettingsService settingsService, ITerminalService terminalService, IDialogService dialogService, string startupDirectory)
public TerminalViewModel(int id, ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile)
{
Id = id;
Title = DefaultTitle;
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
_terminalService = terminalService;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_settingsService.KeyBindingsChanged += OnKeyBindingsChanged;
_trayProcessCommunicationService = trayProcessCommunicationService;
_dialogService = dialogService;
_keyboardCommandService = keyboardCommandService;
_applicationSettings = applicationSettings;
_startupDirectory = startupDirectory;
_resizeOverlayTimer = new DispatcherTimer();
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
_shellProfile = shellProfile;
_resizeOverlayTimer = new DispatcherTimer
{
Interval = new TimeSpan(0, 0, 2)
};
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
CloseCommand = new RelayCommand(async () => await InvokeCloseRequested().ConfigureAwait(false));
FindNextCommand = new RelayCommand(async () => await FindNext().ConfigureAwait(false));
FindPreviousCommand = new RelayCommand(async () => await FindPrevious().ConfigureAwait(false));
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel);
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
}
public int Id { get; private set; }
private async void OnKeyBindingsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
var keyBindings = _settingsService.GetKeyBindings();
await _terminalView.ChangeKeyBindings(GetKeyBindingsCollection(keyBindings)).ConfigureAwait(false);
});
}
public event EventHandler CloseRequested;
public event EventHandler<string> TitleChanged;
public RelayCommand CloseCommand { get; }
public RelayCommand FindPreviousCommand { get; }
public RelayCommand FindNextCommand { get; }
public RelayCommand CloseSearchPanelCommand { get; }
public int Id { get; }
public bool Initialized { get; private set; }
public string DefaultTitle { get; private set; } = "Fluent Terminal";
public string SearchText
{
get => _searchText;
set => Set(ref _searchText, value);
}
public bool ShowSearchPanel
{
get => _showSearchPanel;
set => Set(ref _showSearchPanel, value);
}
public string ResizeOverlayContent
{
get => _resizeOverlayContent;
@@ -69,7 +127,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,30 +154,56 @@ 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 configuration = _settingsService.GetShellConfiguration();
var size = await _terminalView.CreateTerminal(options, theme.Colors, GetKeyBindingsCollection(keyBindings)).ConfigureAwait(true);
if (!string.IsNullOrWhiteSpace(_startupDirectory))
{
configuration.WorkingDirectory = _startupDirectory;
_shellProfile.WorkingDirectory = _startupDirectory;
}
var response = await _terminalService.CreateTerminal(size, configuration);
var response = await _trayProcessCommunicationService.CreateTerminal(size, _shellProfile).ConfigureAwait(true);
if (response.Success)
{
_terminalId = response.Id;
_terminalView.TerminalSizeChanged += OnTerminalSizeChanged;
_terminalView.TerminalTitleChanged += OnTerminalTitleChanged;
_terminalView.KeyboardCommandReceived += OnKeyboardCommandReceived;
await _terminalView.ConnectToSocket(response.WebSocketUrl);
DefaultTitle = response.ShellExecutableName;
Title = DefaultTitle;
await _terminalView.ConnectToSocket(response.WebSocketUrl).ConfigureAwait(true);
Initialized = true;
}
else
{
await _dialogService.ShowDialogAsnyc("Error", response.Error, DialogButton.OK);
await _dialogService.ShowMessageDialogAsnyc("Error", response.Error, DialogButton.OK).ConfigureAwait(true);
}
await FocusTerminal().ConfigureAwait(true);
}
private async Task InvokeCloseRequested()
{
if (_applicationSettings.ConfirmClosingTabs)
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.Cancel)
{
return;
}
}
CloseRequested?.Invoke(this, EventArgs.Empty);
}
private async void OnApplicationSettingsChanged(object sender, EventArgs e)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => _applicationSettings = _settingsService.GetApplicationSettings());
}
private async void OnCurrentThemeChanged(object sender, EventArgs e)
@@ -109,25 +211,57 @@ namespace FluentTerminal.App.ViewModels
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
var currentTheme = _settingsService.GetCurrentTheme();
await _terminalView.ChangeTheme(currentTheme.Colors);
await _terminalView.ChangeTheme(currentTheme.Colors).ConfigureAwait(true);
});
}
private async void OnKeyboardCommandReceived(object sender, Command e)
{
if (e == Command.Copy)
{
var selection = await _terminalView.GetSelection().ConfigureAwait(true);
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).ConfigureAwait(true);
}
}
else if (e == Command.Search)
{
ShowSearchPanel = !ShowSearchPanel;
if (ShowSearchPanel)
{
_terminalView.FocusSearchTextBox();
}
}
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 () =>
{
var options = _settingsService.GetTerminalOptions();
await _terminalView.ChangeOptions(options);
await _terminalView.ChangeOptions(options).ConfigureAwait(true);
});
}
private void OnResizeOverlayTimerFinished(object sender, object e)
{
_resizeOverlayTimer.Stop();
ShowResizeOverlay = false;
}
private async void OnTerminalSizeChanged(object sender, TerminalSize e)
{
if (!Initialized)
@@ -136,12 +270,46 @@ namespace FluentTerminal.App.ViewModels
}
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
ShowResizeOverlay = true;
await _terminalService.ResizeTerminal(_terminalId, e);
await _trayProcessCommunicationService.ResizeTerminal(_terminalId, e).ConfigureAwait(true);
}
private void OnTerminalTitleChanged(object sender, string e)
{
Title = e;
}
private IEnumerable<KeyBinding> GetKeyBindingsCollection(KeyBindings keyBindings)
{
var list = new List<KeyBinding>();
list.AddRange(keyBindings.CloseTab);
list.AddRange(keyBindings.Copy);
list.AddRange(keyBindings.NewTab);
list.AddRange(keyBindings.ConfigurableNewTab);
list.AddRange(keyBindings.NewWindow);
list.AddRange(keyBindings.NextTab);
list.AddRange(keyBindings.Paste);
list.AddRange(keyBindings.PreviousTab);
list.AddRange(keyBindings.ShowSettings);
list.AddRange(keyBindings.Search);
return list;
}
private Task FindNext()
{
return _terminalView.FindNext(SearchText);
}
private Task FindPrevious()
{
return _terminalView.FindPrevious(SearchText);
}
private void CloseSearchPanel()
{
SearchText = string.Empty;
ShowSearchPanel = false;
_terminalView.FocusTerminal();
}
}
}
@@ -42,7 +42,7 @@ namespace FluentTerminal.App.ViewModels
private string _name;
private Color _red;
private Color _selection;
private TerminalTheme _theme;
private readonly TerminalTheme _theme;
private Color _white;
private Color _yellow;
@@ -82,9 +82,9 @@ namespace FluentTerminal.App.ViewModels
Selection = _theme.Colors.Selection.FromString();
SetActiveCommand = new RelayCommand(SetActive);
DeleteCommand = new RelayCommand(async () => await Delete(), CanDelete);
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), CanDelete);
EditCommand = new RelayCommand(Edit, CanEdit);
CancelEditCommand = new RelayCommand(async () => await CancelEdit());
CancelEditCommand = new RelayCommand(async () => await CancelEdit().ConfigureAwait(false));
SaveChangesCommand = new RelayCommand(SaveChanges);
}
@@ -296,7 +296,7 @@ namespace FluentTerminal.App.ViewModels
private async Task CancelEdit()
{
var result = await _dialogService.ShowDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel);
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
@@ -344,7 +344,7 @@ namespace FluentTerminal.App.ViewModels
private async Task Delete()
{
var result = await _dialogService.ShowDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel);
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
@@ -1,13 +1,19 @@
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using System;
namespace FluentTerminal.App.Views
{
public sealed class BindableColorsTitleBar : Control
{
private ApplicationViewTitleBar _titleBar;
private readonly ApplicationViewTitleBar _titleBar;
private readonly UISettings _uiSettings;
private readonly CoreDispatcher _dispatcher;
private bool _customValues;
private Color? _buttonForeground;
private Color? _buttonHoverBackground;
@@ -18,12 +24,32 @@ namespace FluentTerminal.App.Views
public BindableColorsTitleBar()
{
DefaultStyleKey = typeof(BindableColorsTitleBar);
_uiSettings = new UISettings();
_uiSettings.ColorValuesChanged += OnColorValuesChanged;
_titleBar = ApplicationView.GetForCurrentView().TitleBar;
_titleBar = ApplicationView.GetForCurrentView().TitleBar;
_titleBar.ButtonBackgroundColor = Colors.Transparent;
_titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
_titleBar.ButtonForegroundColor = (Color)Resources["SystemBaseHighColor"];
_dispatcher = Window.Current.Dispatcher;
_buttonHoverBackground = _titleBar.ButtonHoverBackgroundColor = Color.FromArgb(24, 255, 255, 255);
_buttonPressedBackground = _titleBar.ButtonPressedBackgroundColor = Color.FromArgb(48, 255, 255, 255);
}
private async void OnColorValuesChanged(UISettings sender, object args)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (!_customValues)
{
_titleBar.ButtonForegroundColor = (Color)Resources["SystemBaseHighColor"];
}
});
}
public Color TitleBarForeground
{
get { return (Color)GetValue(TitleBarForegroundProperty); }
@@ -55,16 +81,20 @@ namespace FluentTerminal.App.Views
_titleBar.ButtonPressedForegroundColor = _buttonPressedForeground;
_titleBar.ButtonHoverForegroundColor = _buttonHoverForeground;
_titleBar.InactiveForegroundColor = _inactiveForeground;
_customValues = true;
}
public void ResetTitleBar()
{
_titleBar.ButtonForegroundColor = (Color)this.Resources["SystemBaseHighColor"];
_titleBar.ButtonForegroundColor = (Color)Resources["SystemBaseHighColor"];
_titleBar.ButtonHoverBackgroundColor = null;
_titleBar.ButtonPressedBackgroundColor = null;
_titleBar.ButtonPressedForegroundColor = null;
_titleBar.ButtonHoverForegroundColor = null;
_titleBar.InactiveForegroundColor = null;
_customValues = false;
}
}
}
@@ -0,0 +1,79 @@
<ContentDialog
x:Class="FluentTerminal.App.Views.CreateKeyBindingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
PrimaryButtonText="OK"
SecondaryButtonText="Cancel"
mc:Ignorable="d">
<StackPanel>
<TextBlock Margin="0,0,0,24" Text="Enter your keybinding now" />
<StackPanel x:Name="Root" Orientation="Horizontal">
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind KeyBinding.Ctrl, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Ctrl" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind KeyBinding.Meta, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="f" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind KeyBinding.Alt, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Alt" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind KeyBinding.Shift, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Shift" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind KeyBinding.Key, Mode=OneWay, Converter={StaticResource ZeroToCollapsedConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{x:Bind KeyBinding.Key, Mode=OneWay, Converter={StaticResource IntToExtendedVirtualKeyConverter}}" />
</Grid>
<HyperlinkButton
x:Name="ResetButton"
Margin="24,0,0,0"
Command="{x:Bind ResetCommand}"
Content="Reset"
PreviewKeyDown="OnResetButtonPreviewKeyDown" />
</StackPanel>
</StackPanel>
</ContentDialog>
@@ -0,0 +1,72 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.ViewModels.Settings;
using FluentTerminal.Models;
using GalaSoft.MvvmLight.Command;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Views
{
public sealed partial class CreateKeyBindingDialog : ContentDialog
{
public static readonly DependencyProperty KeyBindingProperty =
DependencyProperty.Register(nameof(KeyBinding), typeof(KeyBindingViewModel), typeof(CreateKeyBindingDialog), new PropertyMetadata(null));
private readonly IDialogService _dialogService;
public CreateKeyBindingDialog(IDialogService dialogService)
{
_dialogService = dialogService;
InitializeComponent();
ResetCommand = new RelayCommand(Reset);
PreviewKeyDown += RegisterKeyBindingDialog_PreviewKeyDown;
Reset();
}
public KeyBindingViewModel KeyBinding
{
get { return (KeyBindingViewModel)GetValue(KeyBindingProperty); }
set { SetValue(KeyBindingProperty, value); }
}
public RelayCommand ResetCommand { get; }
private void OnResetButtonPreviewKeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
{
e.Handled = true;
}
private void RegisterKeyBindingDialog_PreviewKeyDown(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
{
switch (e.Key)
{
case VirtualKey.Control:
KeyBinding.Ctrl = true;
break;
case VirtualKey.Shift:
KeyBinding.Shift = true;
break;
case VirtualKey.Menu:
KeyBinding.Alt = true;
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
KeyBinding.Meta = true;
break;
default:
KeyBinding.Key = (int)e.Key;
break;
}
ResetButton.Visibility = Visibility.Visible;
e.Handled = true;
}
private void Reset()
{
KeyBinding = new KeyBindingViewModel(new KeyBinding(), _dialogService);
ResetButton.Visibility = Visibility.Collapsed;
}
}
}
+12 -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,19 @@ 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 ChangeKeyBindings(IEnumerable<KeyBinding> keyBindings);
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);
Task FindNext(string searchText);
Task FindPrevious(string searchText);
void FocusSearchTextBox();
}
}
@@ -0,0 +1,91 @@
<UserControl
x:Class="FluentTerminal.App.Views.KeyBindingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:settings="using:FluentTerminal.App.ViewModels.Settings"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{x:Bind ViewModel.KeyBindings, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="settings:KeyBindingViewModel">
<StackPanel
x:Name="Root"
Margin="0,0,0,6"
Orientation="Horizontal">
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind Ctrl, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Ctrl" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind Meta, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="f" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind Alt, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Alt" />
</Grid>
<Grid
Width="48"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}"
Visibility="{x:Bind Shift, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Shift" />
</Grid>
<Grid
Width="64"
Height="32"
Margin="0,0,6,0"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{x:Bind Key, Mode=OneWay, Converter={StaticResource IntToExtendedVirtualKeyConverter}}" />
</Grid>
<HyperlinkButton
Margin="12,0,0,0"
Command="{x:Bind EditCommand}"
Content="Edit" />
<HyperlinkButton
Margin="12,0,0,0"
Command="{x:Bind DeleteCommand}"
Content="Delete" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
@@ -0,0 +1,23 @@
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Views
{
public sealed partial class KeyBindingsView : UserControl
{
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(KeyBindingsViewModel), typeof(KeyBindingsView), new PropertyMetadata(null));
public KeyBindingsView()
{
InitializeComponent();
}
public KeyBindingsViewModel ViewModel
{
get { return (KeyBindingsViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
}
}
+101 -25
View File
@@ -2,9 +2,13 @@
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>
@@ -22,34 +26,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.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"
+9 -8
View File
@@ -3,7 +3,6 @@ using System.ComponentModel;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -45,7 +44,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,16 +57,11 @@ 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)
{
await terminal.FocusWebView();
await terminal.FocusWebView().ConfigureAwait(true);
}
}
@@ -109,5 +102,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;
}
}
}
}
+42 -27
View File
@@ -2,15 +2,10 @@
x:Class="FluentTerminal.App.Views.SettingsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:FluentTerminal.App.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
</Page.Resources>
<Grid x:Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="Root">
<TextBlock
x:Name="AppTitle"
Canvas.ZIndex="1"
@@ -29,28 +24,48 @@
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="Profiles" Tag="profiles">
<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>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Frame x:Name="ContentFrame" />
<TextBlock
Margin="12,8,0,0"
+27 -30
View File
@@ -2,7 +2,6 @@
using FluentTerminal.App.Views.SettingsPages;
using System.Linq;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.Core.Preview;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
@@ -13,25 +12,15 @@ namespace FluentTerminal.App.Views
{
public sealed partial class SettingsPage : Page
{
public double CoreTitleBarHeight { get; }
public SettingsViewModel ViewModel { get; private set; }
public SettingsPage()
{
InitializeComponent();
Root.DataContext = this;
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
titleBar.ButtonForegroundColor = (Color)this.Resources["SystemBaseHighColor"];
ApplicationView.PreferredLaunchViewSize = new Windows.Foundation.Size(1024, 768);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
ApplicationView.GetForCurrentView().Title = "Settings";
CoreApplicationViewTitleBar coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.LayoutMetricsChanged += TitleBar_LayoutMetricsChanged;
@@ -40,6 +29,10 @@ namespace FluentTerminal.App.Views
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += SettingsPage_CloseRequested;
}
public double CoreTitleBarHeight { get; }
public SettingsViewModel ViewModel { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is SettingsViewModel viewModel)
@@ -48,19 +41,9 @@ namespace FluentTerminal.App.Views
}
}
private void SettingsPage_CloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
App.Instance.SettingsWindowClosed();
}
private void TitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
{
AppTitle.Margin = new Thickness(CoreApplication.GetCurrentView().TitleBar.SystemOverlayLeftInset + 12, 8, 0, 0);
}
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)
@@ -70,22 +53,36 @@ namespace FluentTerminal.App.Views
switch (item.Tag)
{
case "general":
ContentFrame.Navigate(typeof(GeneralSettings), ViewModel);
ContentFrame.Navigate(typeof(GeneralSettings), ViewModel.General);
break;
case "shell":
ContentFrame.Navigate(typeof(ShellSettings), ViewModel);
case "profiles":
ContentFrame.Navigate(typeof(ShellProfileSettings), ViewModel.Shell);
break;
case "themes":
ContentFrame.Navigate(typeof(ThemeSettings), ViewModel);
ContentFrame.Navigate(typeof(ThemeSettings), ViewModel.Themes);
break;
case "terminal":
ContentFrame.Navigate(typeof(TerminalSettings), ViewModel);
ContentFrame.Navigate(typeof(TerminalSettings), ViewModel.Terminal);
break;
case "keyBindings":
ContentFrame.Navigate(typeof(KeyBindingSettings), ViewModel);
ContentFrame.Navigate(typeof(KeyBindingSettings), ViewModel.KeyBindings);
break;
}
}
}
}
private void SettingsPage_CloseRequested(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
ViewModel.Close();
}
private void TitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
{
AppTitle.Margin = new Thickness(CoreApplication.GetCurrentView().TitleBar.SystemOverlayLeftInset + 12, 8, 0, 0);
}
}
}
}
@@ -4,7 +4,49 @@
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 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="{x:Bind ViewModel.RestoreDefaultsCommand}"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<TextBlock Margin="0,0,0,8" Text="Open new terminals as" />
<RadioButton Content="Tabs" IsChecked="{x:Bind ViewModel.TabIsSelected, Mode=TwoWay}" />
<RadioButton
Margin="0,0,0,24"
Content="Windows"
IsChecked="{x:Bind ViewModel.WindowIsSelected, Mode=TwoWay}" />
<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.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Views.SettingsPages
{
public sealed partial class GeneralSettings : Page
{
public GeneralPageViewModel ViewModel { get; private set; }
public GeneralSettings()
{
this.InitializeComponent();
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is GeneralPageViewModel viewModel)
{
ViewModel = viewModel;
}
}
}
}
@@ -3,9 +3,242 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:FluentTerminal.App.Views.SettingsPages"
xmlns:enums="using:FluentTerminal.Models.Enums"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:FluentTerminal.App.Views"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" Margin="24,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Margin="0,10,0,0"
VerticalAlignment="Center"
FontSize="28"
Style="{StaticResource TitleTextBlockStyle}"
Text="Keybindings" />
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
DefaultLabelPosition="Right">
<AppBarButton Icon="Add" Label="Add">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="New tab">
<MenuFlyoutItem.CommandParameter>
<enums:Command>NewTab</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="New configurable tab">
<MenuFlyoutItem.CommandParameter>
<enums:Command>ConfigurableNewTab</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Close tab">
<MenuFlyoutItem.CommandParameter>
<enums:Command>CloseTab</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Next tab">
<MenuFlyoutItem.CommandParameter>
<enums:Command>NextTab</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Previous tab">
<MenuFlyoutItem.CommandParameter>
<enums:Command>PreviousTab</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="New window">
<MenuFlyoutItem.CommandParameter>
<enums:Command>NewWindow</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Search">
<MenuFlyoutItem.CommandParameter>
<enums:Command>Search</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Show settings">
<MenuFlyoutItem.CommandParameter>
<enums:Command>ShowSettings</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Toggle window">
<MenuFlyoutItem.CommandParameter>
<enums:Command>ToggleWindow</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Copy">
<MenuFlyoutItem.CommandParameter>
<enums:Command>Copy</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
<MenuFlyoutItem Command="{x:Bind ViewModel.AddCommand}" Text="Paste">
<MenuFlyoutItem.CommandParameter>
<enums:Command>Paste</enums:Command>
</MenuFlyoutItem.CommandParameter>
</MenuFlyoutItem>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton
Command="{x:Bind ViewModel.RestoreDefaultsCommand}"
CommandParameter="keyBindings"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
</Grid>
<ScrollViewer Grid.Row="2" Margin="24,24,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="0,6,24,0"
Text="New tab" />
<views:KeyBindingsView
Grid.Row="0"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.NewTab, Mode=OneWay}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="0,6,24,0"
Text="New configurable tab" />
<views:KeyBindingsView
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.ConfigurableNewTab, Mode=OneWay}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="0,6,24,0"
Text="Close tab" />
<views:KeyBindingsView
Grid.Row="2"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.CloseTab, Mode=OneWay}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="0,6,24,0"
Text="Next tab" />
<views:KeyBindingsView
Grid.Row="3"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.NextTab, Mode=OneWay}" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="0,6,24,0"
Text="Previous tab" />
<views:KeyBindingsView
Grid.Row="4"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.PreviousTab, Mode=OneWay}" />
<TextBlock
Grid.Row="5"
Grid.Column="0"
Margin="0,6,24,0"
Text="New window" />
<views:KeyBindingsView
Grid.Row="5"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.NewWindow, Mode=OneWay}" />
<TextBlock
Grid.Row="6"
Grid.Column="0"
Margin="0,6,24,0"
Text="Search" />
<views:KeyBindingsView
Grid.Row="6"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.Search, Mode=OneWay}" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="0,6,24,0"
Text="Show settings" />
<views:KeyBindingsView
Grid.Row="7"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.ShowSettings, Mode=OneWay}" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="0,6,24,0"
Text="Toggle window" />
<views:KeyBindingsView
Grid.Row="8"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.ToggleWindow, Mode=OneWay}" />
<TextBlock
Grid.Row="9"
Grid.Column="0"
Margin="0,6,24,0"
Text="Copy" />
<views:KeyBindingsView
Grid.Row="9"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.Copy, Mode=OneWay}" />
<TextBlock
Grid.Row="10"
Grid.Column="0"
Margin="0,6,24,0"
Text="Paste" />
<views:KeyBindingsView
Grid.Row="10"
Grid.Column="1"
Margin="0,0,0,24"
ViewModel="{x:Bind ViewModel.Paste, Mode=OneWay}" />
</Grid>
</ScrollViewer>
</Grid>
</Page>
@@ -1,30 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// Die Elementvorlage "Leere Seite" wird unter https://go.microsoft.com/fwlink/?LinkId=234238 dokumentiert.
namespace FluentTerminal.App.Views.SettingsPages
{
/// <summary>
/// Eine leere Seite, die eigenständig verwendet oder zu der innerhalb eines Rahmens navigiert werden kann.
/// </summary>
public sealed partial class KeyBindingSettings : Page
{
public KeyBindingsPageViewModel ViewModel { get; private set; }
public KeyBindingSettings()
{
this.InitializeComponent();
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is KeyBindingsPageViewModel viewModel)
{
ViewModel = viewModel;
}
}
}
}
@@ -0,0 +1,201 @@
<Page
x:Class="FluentTerminal.App.Views.SettingsPages.ShellProfileSettings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Grid.RowSpan="3"
Grid.Column="0"
Background="{ThemeResource SystemControlAcrylicWindowBrush}" />
<Grid
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0,0,0,24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="24,10,0,0"
VerticalAlignment="Center"
FontSize="28"
Style="{StaticResource TitleTextBlockStyle}"
Text="Profiles" />
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="Transparent"
DefaultLabelPosition="Right"
Visibility="{x:Bind ViewModel.SelectedShellProfile.InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<AppBarButton
Command="{x:Bind ViewModel.SelectedShellProfile.SetDefaultCommand, Mode=OneWay}"
Icon="Accept"
IsEnabled="{x:Bind ViewModel.SelectedShellProfile.IsDefault, Mode=OneWay, Converter={StaticResource NegateConverter}}"
Label="Set Default" />
<AppBarButton
Command="{x:Bind ViewModel.SelectedShellProfile.EditCommand, Mode=OneWay}"
Icon="Edit"
Label="Edit" />
<AppBarButton
Command="{x:Bind ViewModel.SelectedShellProfile.DeleteCommand, Mode=OneWay}"
Icon="Delete"
Label="Delete" />
</CommandBar>
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="Transparent"
DefaultLabelPosition="Right"
Visibility="{x:Bind ViewModel.SelectedShellProfile.InEditMode, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<AppBarButton
Command="{x:Bind ViewModel.SelectedShellProfile.SaveChangesCommand, Mode=OneWay}"
Icon="Save"
Label="Save" />
<AppBarButton
Command="{x:Bind ViewModel.SelectedShellProfile.CancelEditCommand, Mode=OneWay}"
Icon="Cancel"
Label="Cancel" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Grid.Column="0">
<ListView ItemsSource="{x:Bind ViewModel.ShellProfiles}" SelectedItem="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Name, Mode=OneWay}" />
<Viewbox
Grid.Column="1"
Width="16"
Height="16"
HorizontalAlignment="Right">
<SymbolIcon Symbol="Accept" Visibility="{x:Bind IsDefault, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
</Viewbox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button
Height="48"
Margin="0,12,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Background="Transparent"
Command="{x:Bind ViewModel.CreateShellProfileCommand}"
Style="{StaticResource ButtonRevealStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="Create new" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
<ContentControl
Grid.Row="2"
Grid.Column="1"
Margin="24,0,0,0"
Content="{x:Bind ViewModel.SelectedShellProfile, Mode=TwoWay}">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="viewmodels:ShellProfileViewModel">
<Grid>
<StackPanel Visibility="{x:Bind InEditMode, Converter={StaticResource TrueToVisibleConverter}, Mode=OneWay}">
<TextBox
Width="300"
HorizontalAlignment="Left"
Header="Name"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Arguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
<TextBox
Width="300"
Header="Shell executable location"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind BrowseForCustomShellCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
<TextBox
Width="300"
Header="Working directory"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind WorkingDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind BrowseForWorkingDirectoryCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<TextBox
Width="300"
Margin="0,24,0,0"
HorizontalAlignment="Left"
Header="Arguments"
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Arguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Name, Mode=OneWay}" />
<controls:HeaderedTextBlock
Margin="0,58,0,0"
Header="Shell executable location"
Text="{x:Bind Location, Mode=OneWay}" />
<controls:HeaderedTextBlock
Margin="0,36,0,0"
Header="Working directory"
Text="{x:Bind WorkingDirectory, Mode=OneWay}" />
<controls:HeaderedTextBlock
Margin="0,36,0,0"
Header="Arguments"
Text="{x:Bind Arguments, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Page>
@@ -1,21 +1,21 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Views.SettingsPages
{
public sealed partial class ShellSettings : Page
public sealed partial class ShellProfileSettings : Page
{
public SettingsViewModel ViewModel { get; private set; }
public ProfilesPageViewModel ViewModel { get; private set; }
public ShellSettings()
public ShellProfileSettings()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is SettingsViewModel viewModel)
if (e.Parameter is ProfilesPageViewModel viewModel)
{
ViewModel = viewModel;
}
@@ -1,86 +0,0 @@
<Page
x:Class="FluentTerminal.App.Views.SettingsPages.ShellSettings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
NavigationCacheMode="Required"
mc:Ignorable="d">
<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="Shell" />
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding ViewModel.RestoreDefaultsCommand}"
CommandParameter="shell"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<TextBlock Margin="0,0,0,8" Text="Type" />
<RadioButton Content="PowerShell" IsChecked="{Binding ViewModel.PoweShellIsSelected, Mode=TwoWay}" />
<RadioButton Content="CMD" IsChecked="{Binding ViewModel.CMDIsSelected, Mode=TwoWay}" />
<RadioButton Content="Custom" IsChecked="{Binding ViewModel.CustomShellIsSelected, Mode=TwoWay}" />
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
<TextBox
Width="400"
Header="Custom Shell Location"
IsEnabled="{x:Bind ViewModel.CustomShellIsSelected, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind ViewModel.CustomShellLocation, Mode=TwoWay}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind ViewModel.BrowseForCustomShellCommand}"
Content="..."
IsEnabled="{x:Bind ViewModel.CustomShellIsSelected, Mode=OneWay}"
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<StackPanel Margin="0,24,0,0" Orientation="Horizontal">
<TextBox
Width="400"
Header="Working Directory"
IsSpellCheckEnabled="False"
Text="{x:Bind ViewModel.WorkingDirectory, Mode=TwoWay}" />
<Button
Margin="6,0,0,0"
VerticalAlignment="Bottom"
Command="{x:Bind ViewModel.BrowseForWorkingDirectoryCommand}"
Content="..."
Style="{StaticResource ButtonRevealStyle}" />
</StackPanel>
<TextBox
Width="400"
Margin="0,24,0,0"
HorizontalAlignment="Left"
Header="Arguments"
IsSpellCheckEnabled="False"
Text="{x:Bind ViewModel.Arguments, Mode=TwoWay}" />
</StackPanel>
</Grid>
</Page>
@@ -29,8 +29,7 @@
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding ViewModel.RestoreDefaultsCommand}"
CommandParameter="terminal"
Command="{x:Bind ViewModel.RestoreDefaultsCommand}"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
@@ -53,9 +52,9 @@
Header="Blinking Cursor"
IsOn="{x:Bind ViewModel.CursorBlink, Mode=TwoWay}" />
<TextBlock Margin="0,0,0,8" Text="Cursor Style" />
<RadioButton Content="Block" IsChecked="{Binding ViewModel.BlockIsSelected, Mode=TwoWay}" />
<RadioButton Content="Underline" IsChecked="{Binding ViewModel.UnderlineIsSelected, Mode=TwoWay}" />
<RadioButton Content="Bar" IsChecked="{Binding ViewModel.BarIsSelected, Mode=TwoWay}" />
<RadioButton Content="Block" IsChecked="{x:Bind ViewModel.BlockIsSelected, Mode=TwoWay}" />
<RadioButton Content="Underline" IsChecked="{x:Bind ViewModel.UnderlineIsSelected, Mode=TwoWay}" />
<RadioButton Content="Bar" IsChecked="{x:Bind ViewModel.BarIsSelected, Mode=TwoWay}" />
</StackPanel>
</Grid>
</Page>
@@ -1,4 +1,4 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
@@ -11,11 +11,11 @@ namespace FluentTerminal.App.Views.SettingsPages
InitializeComponent();
}
public SettingsViewModel ViewModel { get; private set; }
public TerminalPageViewModel ViewModel { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is SettingsViewModel viewModel)
if (e.Parameter is TerminalPageViewModel viewModel)
{
ViewModel = viewModel;
}
@@ -2,18 +2,11 @@
x:Class="FluentTerminal.App.Views.SettingsPages.ThemeSettings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:FluentTerminal.App.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
xmlns:views="using:FluentTerminal.App.Views"
NavigationCacheMode="Required"
mc:Ignorable="d">
<Page.Resources>
<converters:NegateConverter x:Key="NegateConverter" />
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
</Page.Resources>
<Grid x:Name="Root">
<Grid.Background>
<AcrylicBrush
@@ -135,12 +128,12 @@
<Button.Content>
<StackPanel Orientation="Horizontal">
<Viewbox
Width="16"
Height="16"
Width="12"
Height="12"
Margin="0,0,6,0">
<SymbolIcon Symbol="Add" />
</Viewbox>
<TextBlock Text="Create New" />
<TextBlock Text="Create new" Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Button.Content>
</Button>
@@ -1,4 +1,4 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
@@ -6,7 +6,7 @@ namespace FluentTerminal.App.Views.SettingsPages
{
public sealed partial class ThemeSettings : Page
{
public SettingsViewModel ViewModel { get; private set; }
public ThemesPageViewModel ViewModel { get; private set; }
public ThemeSettings()
{
@@ -16,7 +16,7 @@ namespace FluentTerminal.App.Views.SettingsPages
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is SettingsViewModel viewModel)
if (e.Parameter is ThemesPageViewModel viewModel)
{
ViewModel = viewModel;
}
@@ -27,6 +27,5 @@ namespace FluentTerminal.App.Views.SettingsPages
{
TitleBar.ResetTitleBar();
}
}
}
@@ -17,12 +17,12 @@ namespace FluentTerminal.App.Views
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(TerminalColorPicker), new PropertyMetadata(Colors.Transparent, (s, e) =>
{
if (s is TerminalColorPicker terminalColorPicker)
{
terminalColorPicker.colorPicker.Color = (Color)e.NewValue;
}
}));
{
if (s is TerminalColorPicker terminalColorPicker)
{
terminalColorPicker.colorPicker.Color = (Color)e.NewValue;
}
}));
public TerminalColorPicker()
{
+57 -8
View File
@@ -2,26 +2,75 @@
x:Class="FluentTerminal.App.Views.TerminalView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:FluentTerminal.App.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:FluentTerminal.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
d:DesignWidth="600"
mc:Ignorable="d">
<UserControl.Resources>
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
</UserControl.Resources>
<Grid>
<Grid x:Name="WebViewContainer" Margin="12" />
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid
x:Name="WebViewContainer"
Grid.Row="0"
Margin="12" />
<Grid
Grid.Row="0"
Margin="12"
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>
<RelativePanel
Grid.Row="1"
Padding="12"
Visibility="{x:Bind ViewModel.ShowSearchPanel, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}">
<TextBlock
Name="SearchLabel"
Margin="0,6,0,0"
RelativePanel.AlignLeftWithPanel="True"
Text="Search" />
<TextBox
Name="SearchTextBox"
Width="300"
Margin="6,0,6,0"
KeyUp="SearchTextBox_KeyUp"
PlaceholderText="Enter search term"
RelativePanel.RightOf="SearchLabel"
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Name="PreviousButton"
Margin="0,0,6,0"
Command="{x:Bind ViewModel.FindPreviousCommand}"
RelativePanel.RightOf="SearchTextBox"
ToolTipService.ToolTip="Find previous">
<Button.Content>
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE76B;" />
</Button.Content>
</Button>
<Button
Command="{x:Bind ViewModel.FindNextCommand}"
RelativePanel.RightOf="PreviousButton"
ToolTipService.ToolTip="Find next">
<Button.Content>
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE76C;" />
</Button.Content>
</Button>
<Button
Command="{x:Bind ViewModel.CloseSearchPanelCommand}"
RelativePanel.AlignRightWithPanel="True"
ToolTipService.ToolTip="Close search panel">
<Button.Content>
<SymbolIcon Symbol="Cancel" />
</Button.Content>
</Button>
</RelativePanel>
</Grid>
</UserControl>
+177 -78
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,17 @@ 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.System;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -21,26 +26,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 readonly 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 +53,50 @@ 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 Task ChangeKeyBindings(IEnumerable<KeyBinding> keyBindings)
{
var serialized = JsonConvert.SerializeObject(keyBindings);
return ExecuteScriptAsync($"changeKeyBindings('{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}')").ConfigureAwait(true);
return JsonConvert.DeserializeObject<TerminalSize>(size);
}
public Task<string> ExecuteScriptAsync(string script)
{
try
@@ -81,6 +110,124 @@ namespace FluentTerminal.App.Views
return Task.FromResult(string.Empty);
}
public async Task FocusTerminal()
{
if (_webView != null)
{
_webView.Focus(FocusState.Programmatic);
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
}
}
public async Task FocusWebView()
{
if (_webView != null)
{
_webView.Focus(FocusState.Programmatic);
await ExecuteScriptAsync("document.focus();").ConfigureAwait(true);
}
}
public Task<string> GetSelection() => 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}')");
}
public Task FindNext(string searchText)
{
return ExecuteScriptAsync($"findNext('{searchText}')");
}
public Task FindPrevious(string searchText)
{
return ExecuteScriptAsync($"findPrevious('{searchText}')");
}
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(nameof(Command.Copy));
}
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("ms-appx-web:///Client/index.html"));
await _loaded.WaitAsync().ConfigureAwait(true);
_webView.Focus(FocusState.Programmatic);
await ViewModel.OnViewIsReady(this).ConfigureAwait(true);
}
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(nameof(Command.Paste));
}
private void StartMediatorTask()
{
_dispatcherJobs = new BlockingCollection<Action>();
@@ -101,69 +248,21 @@ namespace FluentTerminal.App.Views
});
}
private async void OnLoaded(object sender, RoutedEventArgs e)
private void SearchTextBox_KeyUp(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
{
_webView = new WebView(WebViewExecutionMode.SeparateThread)
if (e.Key == VirtualKey.Escape)
{
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);
ViewModel.CloseSearchPanelCommand.Execute(null);
}
else if (e.Key == VirtualKey.Enter)
{
ViewModel.FindNextCommand.Execute(null);
}
}
private void _webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
public void FocusSearchTextBox()
{
_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}')");
SearchTextBox.Focus(FocusState.Programmatic);
}
}
}
}
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;
}
+3 -3
View File
@@ -3013,9 +3013,9 @@
"dev": true
},
"xterm": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.1.0.tgz",
"integrity": "sha512-xv4QQQAu8PjAPZE7m0h+i9X9z3vR82wPe0H3g4D9CbyDuQDNt1e+lxeTrkLwuMH3jcxqfHayu3Mu+iyQh62ECg=="
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.4.0.tgz",
"integrity": "sha512-eHH9CuKZOdKscBoUPmBxE1nTAuaKCMavcTQilyQf2u6MqB366BJP7SbkGWz+HZj7V5OjldFLLDLzlliqUzoiHQ=="
},
"y18n": {
"version": "3.2.1",
+1 -1
View File
@@ -13,6 +13,6 @@
"webpack": "^3.10.0"
},
"dependencies": {
"xterm": "^3.1.0"
"xterm": "^3.4.0"
}
}
+71 -3
View File
@@ -2,16 +2,18 @@ import * as Terminal from '../node_modules/xterm/dist/xterm';
import * as attach from '../node_modules/xterm/dist/addons/attach/attach';
import * as fit from '../node_modules/xterm/dist/addons/fit/fit';
import * as winptyCompat from '../node_modules/xterm/dist/addons/winptyCompat/winptyCompat';
import * as search from '../node_modules/xterm/dist/addons/search/search';
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
Terminal.applyAddon(winptyCompat);
Terminal.applyAddon(search);
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 +21,8 @@ function createTerminal(options, theme) {
theme = JSON.parse(theme);
theme.background = 'transparent';
window.keyBindings = JSON.parse(keyBindings);
options = JSON.parse(options);
var terminalOptions = {
@@ -28,7 +32,8 @@ function createTerminal(options, theme) {
cursorBlink: options.cursorBlink,
bellStyle: options.bellStyle,
allowTransparency: true,
theme: theme
theme: theme,
experimentalCharAtlas: 'dynamic'
};
term = new Terminal(terminalOptions);
@@ -54,6 +59,34 @@ 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.meta == e.metaKey
&& keyBinding.alt == e.altKey
&& keyBinding.shift == e.shiftKey
&& keyBinding.key == e.keyCode) {
if (document.visibilityState == 'visible') {
if (keyBinding.command == 'copy' && term.getSelection() == '') {
return true;
}
e.preventDefault();
terminalBridge.invokeCommand(keyBinding.command);
}
return false;
}
}
return true;
});
return JSON.stringify({
rows: term.rows,
columns: term.cols
@@ -68,6 +101,9 @@ function attachTerminal() {
function connectToWebSocket(url) {
socket = new WebSocket(url);
socket.onopen = attachTerminal;
socket.onclose = function() {
terminalBridge.invokeCommand('CloseTab');
};
}
function changeTheme(theme) {
@@ -87,7 +123,39 @@ function changeOptions(options) {
term.setOption('fontSize', options.fontSize);
}
function changeKeyBindings(keyBindings) {
keyBindings = JSON.parse(keyBindings);
window.keyBindings = keyBindings;
}
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(''))
}
function findNext(content) {
term.findNext(content);
}
function findPrevious(content) {
term.findPrevious(content);
}
document.oncontextmenu = function () {
return false;
};
window.createTerminal = createTerminal;
window.connectToWebSocket = connectToWebSocket;
window.changeTheme = changeTheme;
window.changeOptions = changeOptions;
window.changeOptions = changeOptions;
window.changeKeyBindings = changeKeyBindings;
window.paste = paste;
window.findNext = findNext;
window.findPrevious = findPrevious;
@@ -0,0 +1,11 @@
using FluentTerminal.Models.Enums;
namespace FluentTerminal.Models
{
public class ApplicationSettings
{
public bool ConfirmClosingTabs { get; set; }
public bool ConfirmClosingWindows { get; set; }
public NewTerminalLocation NewTerminalLocation { get; set; }
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace FluentTerminal.Models.Enums
{
public enum Command
{
ToggleWindow,
NextTab,
PreviousTab,
NewTab,
ConfigurableNewTab,
CloseTab,
NewWindow,
ShowSettings,
Copy,
Paste,
Search
}
}
@@ -0,0 +1,211 @@
using System.ComponentModel;
namespace FluentTerminal.Models.Enums
{
public enum ExtendedVirtualKey
{
None = 0,
LeftButton = 1,
RightButton = 2,
Cancel = 3,
MiddleButton = 4,
XButton1 = 5,
XButton2 = 6,
Back = 8,
Tab = 9,
Clear = 12,
Enter = 13,
Shift = 16,
Control = 17,
Menu = 18,
Pause = 19,
CapitalLock = 20,
Kana = 21,
Hangul = 21,
Junja = 23,
Final = 24,
Hanja = 25,
Kanji = 25,
Escape = 27,
Convert = 28,
NonConvert = 29,
Accept = 30,
ModeChange = 31,
Space = 32,
PageUp = 33,
PageDown = 34,
End = 35,
Home = 36,
Left = 37,
Up = 38,
Right = 39,
Down = 40,
Select = 41,
Print = 42,
Execute = 43,
Snapshot = 44,
Insert = 45,
Delete = 46,
Help = 47,
[Description("0")]
Number0 = 48,
[Description("1")]
Number1 = 49,
[Description("2")]
Number2 = 50,
[Description("3")]
Number3 = 51,
[Description("4")]
Number4 = 52,
[Description("5")]
Number5 = 53,
[Description("6")]
Number6 = 54,
[Description("7")]
Number7 = 55,
[Description("8")]
Number8 = 56,
[Description("9")]
Number9 = 57,
A = 65,
B = 66,
C = 67,
D = 68,
E = 69,
F = 70,
G = 71,
H = 72,
I = 73,
J = 74,
K = 75,
L = 76,
M = 77,
N = 78,
O = 79,
P = 80,
Q = 81,
R = 82,
S = 83,
T = 84,
U = 85,
V = 86,
W = 87,
X = 88,
Y = 89,
Z = 90,
LeftWindows = 91,
RightWindows = 92,
Application = 93,
Sleep = 95,
NumberPad0 = 96,
NumberPad1 = 97,
NumberPad2 = 98,
NumberPad3 = 99,
NumberPad4 = 100,
NumberPad5 = 101,
NumberPad6 = 102,
NumberPad7 = 103,
NumberPad8 = 104,
NumberPad9 = 105,
Multiply = 106,
Add = 107,
Separator = 108,
Subtract = 109,
Decimal = 110,
Divide = 111,
F1 = 112,
F2 = 113,
F3 = 114,
F4 = 115,
F5 = 116,
F6 = 117,
F7 = 118,
F8 = 119,
F9 = 120,
F10 = 121,
F11 = 122,
F12 = 123,
F13 = 124,
F14 = 125,
F15 = 126,
F16 = 127,
F17 = 128,
F18 = 129,
F19 = 130,
F20 = 131,
F21 = 132,
F22 = 133,
F23 = 134,
F24 = 135,
NavigationView = 136,
NavigationMenu = 137,
NavigationUp = 138,
NavigationDown = 139,
NavigationLeft = 140,
NavigationRight = 141,
NavigationAccept = 142,
NavigationCancel = 143,
NumberKeyLock = 144,
Scroll = 145,
LeftShift = 160,
RightShift = 161,
LeftControl = 162,
RightControl = 163,
LeftMenu = 164,
RightMenu = 165,
GoBack = 166,
GoForward = 167,
Refresh = 168,
Stop = 169,
Search = 170,
Favorites = 171,
GoHome = 172,
VolumeMute = 173,
VolumeDown = 174,
VolumeUp = 175,
MediaNext = 176,
MediaPrevious = 177,
MediaStop = 178,
MediaPlayPause = 179,
[Description("+")]
Plus = 187,
[Description(",")]
Comma = 188,
[Description("-")]
Minus = 189,
[Description(".")]
Period = 190,
[Description("#")]
NumberSign = 191,
GamepadA = 195,
GamepadB = 196,
GamepadX = 197,
GamepadY = 198,
GamepadRightShoulder = 199,
GamepadLeftShoulder = 200,
GamepadLeftTrigger = 201,
GamepadRightTrigger = 202,
GamepadDPadUp = 203,
GamepadDPadDown = 204,
GamepadDPadLeft = 205,
GamepadDPadRight = 206,
GamepadMenu = 207,
GamepadView = 208,
GamepadLeftThumbstickButton = 209,
GamepadRightThumbstickButton = 210,
GamepadLeftThumbstickUp = 211,
GamepadLeftThumbstickDown = 212,
GamepadLeftThumbstickRight = 213,
GamepadLeftThumbstickLeft = 214,
GamepadRightThumbstickUp = 215,
GamepadRightThumbstickDown = 216,
GamepadRightThumbstickRight = 217,
GamepadRightThumbstickLeft = 218,
[Description(")")]
OpenBrackets = 219,
[Description("^")]
Circumflex = 220,
[Description("´")]
AcuteAccent = 221
}
}
@@ -0,0 +1,8 @@
namespace FluentTerminal.Models.Enums
{
public enum NewTerminalLocation
{
Tab,
Window
}
}
-9
View File
@@ -1,9 +0,0 @@
namespace FluentTerminal.Models.Enums
{
public enum ShellType
{
CMD,
PowerShell,
Custom
}
}
@@ -4,4 +4,11 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Windows">
<HintPath>$(MSBuildProgramFiles32)\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 Shift { get; set; }
public bool Meta { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace FluentTerminal.Models
{
public class KeyBindings
{
public ICollection<KeyBinding> ToggleWindow { get; set; }
public ICollection<KeyBinding> NextTab { get; set; }
public ICollection<KeyBinding> PreviousTab { get; set; }
public ICollection<KeyBinding> NewTab { get; set; }
public ICollection<KeyBinding> ConfigurableNewTab { get; set; }
public ICollection<KeyBinding> CloseTab { get; set; }
public ICollection<KeyBinding> NewWindow { get; set; }
public ICollection<KeyBinding> ShowSettings { get; set; }
public ICollection<KeyBinding> Copy { get; set; }
public ICollection<KeyBinding> Paste { get; set; }
public ICollection<KeyBinding> Search { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace FluentTerminal.Models
{
public static class MessageKeys
{
public static string Type => nameof(Type);
public static string Content => nameof(Content);
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace FluentTerminal.Models
{
public static class MessageTypes
{
public static string CreateTerminalRequest => nameof(CreateTerminalRequest);
public static string CreateTerminalResponse => nameof(CreateTerminalResponse);
public static string ResizeTerminalRequest => nameof(ResizeTerminalRequest);
public static string SetToggleWindowKeyBindingsRequest => nameof(SetToggleWindowKeyBindingsRequest);
}
}
@@ -1,8 +1,8 @@
namespace FluentTerminal.Models
namespace FluentTerminal.Models.Requests
{
public class CreateTerminalRequest
{
public TerminalSize Size { get; set; }
public ShellConfiguration Configuration { get; set; }
public ShellProfile Profile { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace FluentTerminal.Models.Requests
{
public class ResizeTerminalRequest
{
public int TerminalId { get; set; }
public TerminalSize NewSize { get; set; }
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace FluentTerminal.Models.Requests
{
public class SetToggleWindowKeyBindingsRequest
{
public IEnumerable<KeyBinding> KeyBindings { get; set; }
}
}
@@ -1,4 +1,4 @@
namespace FluentTerminal.Models
namespace FluentTerminal.Models.Responses
{
public class CreateTerminalResponse
{
@@ -6,5 +6,6 @@
public bool Success { get; set; }
public string WebSocketUrl { get; set; }
public string Error { get; set; }
public string ShellExecutableName { get; set; }
}
}
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff. Show more