Compare commits

...

62 Commits

Author SHA1 Message Date
Dustin Howett
00db8704b6 HACK: Add Version to the propsheet 2019-05-30 17:59:26 -07:00
Ian Frosst
71e19cd825 "Color scheme" is two words (#1054)
* Update ColorTool comments

* Update profile key

* Add ability to load settings from old key
2019-05-30 13:32:05 -05:00
Nicholas Baron
dadd74c3c6 Improvements to TerminalInput (#690)
* Specified the destructor of TerminalInput as default

* Simplified GetKeymappingLength

* Simplified GetKeyMapping

* Removed a redundant assignment

* Added auto deduction to some variables

* Merged the public sections of TerminalInput

* Implied the destructor for TerminalInput

* Removed GetKeyMappingLength and GetKeyMapping from public interface

Rearranged public section to be above private.

* Deleted or defaulted all six special member functions.

* Removed extraneous newlines

* Deleted all move and copy operations.

The default constructor is also deleted.
The destructor is defaulted.

* Converted tabs to 4 spaces
2019-05-30 11:15:37 -07:00
MelulekiDube
1c16b2c06b Removed using namespace directive from header files (#955)
* Removed using namespace directive from header files and put these in cpp files where they are used

* Fixed tabbing issues by replacing them with spaces.
Also regrouped the using directives.

* Update src/host/exemain.cpp

Co-Authored-By: Mike Griese <migrie@microsoft.com>

* Update src/interactivity/win32/find.cpp

Co-Authored-By: Mike Griese <migrie@microsoft.com>
2019-05-30 11:14:21 -07:00
fghzxm
c3e32eb1ca Highlight the default profile in new-tab flyout (#888)
* Highlight the default profile in new-tab flyout

This commit makes the default profile in the new-tab flyout to show up
at the top and gives it bold text.
2019-05-30 09:24:34 -07:00
Shawn Walker-Salas
e52170e2cf Apply [[nodiscard]] to functions returning error codes (#953)
* Apply [[nodiscard]] to functions returning error codes

- applied [[nodiscard]] for all HRESULT, LRESULT, and NTSTATUS functions
- fixed IntelliSense declaration complaints leading to function not
  implemented warnings
- deleted declared but never implemented functions
- fixed unused parameter warnings

How verified:
- bcz dbg
- opencon
- testcon
- VS2019 debug build

* - use LOG_IF_FAILED where applicable
- remove use of goto
- make MakeAltRasterFont return void

* - add missing [[nodiscard]]
- remove vestigal function declarations
- fix inconsistent function declaration
2019-05-30 16:20:42 +00:00
Kyle Sabo
3d7160d731 Use a ComPtr to avoid leaking font. (#1063)
Fixes #768
2019-05-30 15:54:46 +00:00
Michael Ratanapintha
d24d647c0d Turn on Text Buffer unit tests in Azure DevOps CI build (#1057)
* rename TextBuffer.UnitTests.dll -> TextBuffer.Unit.Tests.dll

* renamed the project file as well
2019-05-29 19:51:17 -07:00
Flo56958
e2b5fecd48 Some Typo-Fixes in Comments (#1049)
* Typo fixes
2019-05-29 14:27:30 -07:00
d-bingham
097f7d32a6 Background image support (#853)
* Initial code check in for background images

* Cleaning up whitespace

* Whitespace cleanup

* Added/fixed comments

* Fixing tabs

* Reverting erroneous file add

* Removing custom enum for image stretching mode and using Windows::UI::Xaml::Media::Stretch instead.

* Removing now-superfluous static_cast when setting stretch mode.

* Updating code to use wstring_view (per #925)

* One last set of wstring -> wstring_view changes

* Split off brush-intialization function from TermControl::_BackgroundColorChanged and added code to prevent flicker on resetting acrylic or image backgrounds.
2019-05-29 13:35:46 -05:00
Michael Ratanapintha
2f88c46350 Allow tools\razzle & nuget restore to work with NuGet 5 and later (#1046)
Since version 5.0.2, NuGet has used the PATH environment variable
to find MSBuild.exe before looking in other file paths.
See NuGet change
21f2b07f2c
(https://github.com/NuGet/NuGet.Client/pull/2687 ).

Unfortunately, in PR
https://github.com/microsoft/terminal/pull/606 ,
`tools\razzle.cmd` was changed to add the MSBuild.exe folder path
in _quotes_ to the PATH environment variable.
Windows itself is fine with this (you can type `msbuild` and
MSBuild runs), but some tools are not, including NuGet itself,
so you would get errors like this:

```
D:\GitHub\metathinker\console> where nuget
C:\ProgramData\chocolatey\bin\nuget.exe
D:\GitHub\metathinker\console\dep\nuget\nuget.exe

D:\GitHub\metathinker\console> nuget restore OpenConsole.sln
Illegal characters in path.
```

`razzle.cmd` runs NuGet itself, but does so before adding
the MSBuild folder to the PATH, so it was not affected by this
problem.

This change fixes the issue by dequotifying the PATH,
so that if you already had a newer version of NuGet on your PATH
before running `tools\razzle.cmd`, that version will continue
to work should you need to run `nuget restore` again
(such as after a `git clean -dx`).
2019-05-29 10:02:48 -07:00
Michael Niksa
8baba4b46c Guard try_query calls with a null check on the pointer we're QI-ing from (#1044)
Even wil::com_ptr_nothrow can still inadvertantly throw an 'access violation exception' when null pointer deref-ing (WIL won't check if it's null before attempting, CComQIPtr apparently didn't care.
2019-05-28 16:03:22 -07:00
Summon528
cfc72cee5d Make sure cursor blinks after opening new tab (#1030) 2019-05-28 11:18:28 -07:00
Michael Ratanapintha
9ad2544033 Fix #936: misuse of uninitialized objects causes AppVerifier breaks on Windows Terminal startup (#1015)
* move the render thread init up; gets rid of verifier stops

* s/INVALID_HANDLE_VALUE/NULL/g since CreateEvent() and CreateThread() return a NULL HANDLE on failure; resolves another cause of AppVerifier breaks
2019-05-28 16:56:36 +00:00
nathan-castlehow
5f938a0465 Update Terminal.cpp (#1034) 2019-05-28 16:53:03 +00:00
subhasan
4c47631bf4 Cleanup - termDispatch.hpp & adaptDispatch.hpp overrides (#1004)
* Fix for https://github.com/microsoft/terminal/issues/896

* Fixing spaces

* Base Class destructor is virtual, derived class destructor shouldn't be declared vitual or override

* Update src/terminal/adapter/termDispatch.hpp

nit: remove space

Co-Authored-By: Mike Griese <migrie@microsoft.com>
2019-05-24 15:29:12 -07:00
adiviness
cc30475955 add audit mode to ci (#948)
* add audit mode to ci
2019-05-24 14:48:10 -07:00
Mike Griese
80f107965d Fix the bell sound when Alt+key is pressed. (#1006) 2019-05-24 16:43:46 -05:00
Mike Griese
42e87ed3e3 fix build break from using await instead of co_await (#1009) 2019-05-24 21:27:45 +00:00
Michael Niksa
40b557a4b6 Update manifest to correct 1903 version, unref param fix (#1008)
* Update manifest to correct 1903 version

While messing around with building with VS2019/14.2/etc, I noticed that the version we're using in the compatibility manifest doesn't match guidance for XAML Islands. This puts the version information from the public guidance into the manifest and leaves a link back to the page where I got this idea from.

* comment out unused params in IslandWindow::OnResize
2019-05-24 14:26:40 -07:00
Mike Griese
0f62ec81d8 Eat all tap keypresses no matter what. (#985)
Fixes #744
2019-05-24 15:04:00 -05:00
Dustin L. Howett (MSFT)
ce0eaab9ac inbox: Merge accumulated build fixes from RS_ONECORE_DEP_ACIOSS (#1002)
* Merged PR 3302855

[Git2Git] Git Train: Merge of building/rs_onecore_dep_acioss/190523-1700 into official/rs_onecore_dep_acioss Retrieved from official/rs_onecore_dep_acioss 3fceea90bee761aa93d91c0184a7217d1e2d404b

Related work items: #18974333
2019-05-24 12:28:30 -07:00
Benjamin Staneck
1c50968333 add .editorconfig file (#585)
* add .editorconfig file

* drop charset from editorconfig

* Update .editorconfig

Co-Authored-By: Michael Niksa <miniksa@microsoft.com>
2019-05-24 18:20:17 +00:00
Joel Bennett
efd69990c6 Add support for OSC 10 and 11 to set the default colors (#891)
* Support OSC to set default background and foreground colors

* Update the Terminal theme when the background changes

* Fix whitespace per code-review

* Add Documentation Comments

Also fix a few outdated comments and whitespace

* Update Telemetry codes per code review

* Add Unit Tests for OSC ForegroundColor and BackgroundColor

* Add a couple additional test cases

* Minor doc and whitespace change per PR review

* Update comment help per code review

* Add another OSC 10 & 11 test case, improve output

* Comments and syntax cleanup per code reviews
2019-05-24 09:53:00 -07:00
Mike Griese
2fdcb679ab Update the default settings (#918)
* Update the default settings

  * [x] `alwaysShowTabs` -> `true`
  * [x] `experimental_showTabsInTitlebar` -> `true`
  * [x] always include Windows Powershell (`background`: `#012456`)
  * [x] include PowerShell Core separately (`background`: unset)
  * [x] drop `Courier New` for powershell
  * [x] drop `experimental_` for `experimental_showTabsInTitlebar`
  * [x] reduce default font size to 10pt.

  Fixes #869
2019-05-23 17:02:32 -05:00
Kapperchino
1191a59681 Update scroll bar with scroll (#920)
* added another method to scroll with keyboard

* set lastscrolloffset to 0

* fixed unused variable

* renamed ViewPort to Viewport

* changed keyBoard to keyboard in the functions, and added expliantion for function
2019-05-23 13:39:29 -07:00
Mike Griese
8dab297bd1 Add an error dialog when we fail to parse settings (#903)
* Load messages from the Resources.resw file
  * Display a message when we fail to parse the settings on an initial parse, or
    on a reload.
2019-05-23 15:09:35 -05:00
Dustin L. Howett (MSFT)
3f95d58805 Remove ATL from .vsconfig! (#954)
🎉
2019-05-23 17:46:29 +00:00
Anirudh Rayabharam
2d4eca7f4f Added support for DECSCUSR sequences (#941)
* Falling back to legacy cursor for higher values of CursorStyle

Co-Authored-By: Michael Niksa <miniksa@microsoft.com>
2019-05-23 10:44:27 -07:00
Ilya Shipitsin
547cba968c fix couple of null pointer dereferences (#927)
found by cppcheck

[src/propsheet/OptionsPage.cpp:216] -> [src/propsheet/OptionsPage.cpp:242]: (warning) Either the condition 'lParam' is redundant or there is possible null pointer dereference: pshn.
[src/propsheet/TerminalPage.cpp:352] -> [src/propsheet/TerminalPage.cpp:378]: (warning) Either the condition 'lParam' is redundant or there is possible null pointer dereference: pshn.
2019-05-23 12:42:39 -05:00
Dustin L. Howett (MSFT)
798912c2f4 Enable C++/WinRT Optimizations for local component builds (#949)
Fixes #945.
2019-05-23 10:36:29 -07:00
Eric Budai
06a5583c86 Fix a bunch of static analysis issues (#553)
* static analysis fixes
* using C++ style casts
* explicit delete changed to reset(nullptr)
* fix for null apiMsg.OtherId during tracing in Compare()
* changed INVALID_ID macro to constexpr
* properly handle null ReplyMsg in ConsoleIoThread()
* Fixed wrong static_cast for State.InputBuffer
* compensate for null reply message to fix deref problem of ReplyMsg in srvinit.cpp by changing signature in DeviceComm.h
2019-05-23 10:35:30 -07:00
Maks Naumov
82e75ce3e2 Utf8ToWideCharParser: Fix memory leak in case of error (#836) 2019-05-23 11:05:57 -05:00
Juris Bogusevs
5ec7c0325e Closing the tab shifts focus to the right (#767)
* On closing the tab - the focus is shifted to the right.
2019-05-23 08:24:40 -05:00
Dustin L. Howett
bbbd3e0323 Use the right Windows PowerShell icon
Fixes #952.
2019-05-22 22:15:02 -07:00
Shawn Walker-Salas
1d9cdb3d31 set identifying environment variable for new connections (#897)
* set identifying environment variable for new connections

Set a new 'WT_SESSION' environment variable when creating new terminal
connections to allow shells to detect a unique Windows Terminal session.
The value of the variable is a stringified GUID as returned by
CoCreateGuid.

How verified:
- "razzle" & vs debug build
- runut
- manual inspection

* * use winrt::guid type for connection guid
* use Utils::GuidToString for guid stringification
* expose guid parameter in ITerminalConnection idl

* - poke guid through ITerminalConnection
- misc. review fixes
- throw if CreateConPty fails in ConhostConnection::Start
- apply [[nodiscard]] and noexcept in various places

* - simplify environment variable extraction in UpdateEnvironmentMapW

* - use Utils::CreateGuid instead of CoCreateGuid in ConHostConnection()
2019-05-22 13:24:22 -07:00
Dustin L. Howett (MSFT)
8c3af2d066 Add default icons for the default profiles (#934)
This commit introduces a handful of default icons whose paths will be
emitted into the default profiles.

Icons are named after the profile GUIDs, which for the default profiles
are stable v5 UUIDs based on the name of the profile. The plan is that
we'll never have a duplicate default profile, and if the user wants to
duplicate it they'll need to issue it a new GUID.

Eventually, when icons can be inserted through the settings UI, we can
keep the GUID name (to unique them among all icons for all profiles) and
move them into ms-appdata:///roaming/.

The currently included icons are named for the following profiles:

"cmd" `{0caa0dad-35be-5f56-a8ff-afceeeaa6101}`
"PowerShell Core" `{574e775e-4f2a-5b96-ac1e-a2962a402336}`
"Windows PowerShell" `{61c54bbd-c2c6-5271-96e7-009a87ff44bf}`
"WSL" `{9acb9455-ca41-5af7-950f-6bca1bc9722f}`

The PowerShell profile names aren't being used yet, but this is in
preparation for #918 merging.

Fixes #933.
2019-05-22 13:03:10 -07:00
Tim Heuer
e9a3d16286 Adding auto-UI shortcuts to menu based on keymappings (#924)
* Adding vsconfig file for VS2019 help to prompt for missing components requried.

* Adding a keybinding for launching the settings.  Suggested fix for #683

* Modified to comma per PR feedback

* Implements 791 for profile and settings shortcuts (most frequent and have shortcuts)

* Quick change for consistency (missed in first checkin due to using ENUM) on using 'Ctrl' instead of 'Control'

* Adding UI shortcut generation to new keybinding mappings.  Resolving #791

* Making a few changes on reviewer feedback for shortcut UI.

* Additional reviewer feedback on variable name change (not a member var)
2019-05-22 15:01:33 -05:00
Dustin L. Howett (MSFT)
83b139596f Re-enable ARM64 in CI (#931)
Fixes #722.
2019-05-22 10:28:50 -07:00
Carlos Zamora
6a79025027 Bugfix: padding offsets selection (#906)
Closes #660.
2019-05-22 09:34:20 -07:00
The Oddball
20359d40e4 Remove satement about 1903 being available only on Insider (#928) 2019-05-22 11:14:02 -05:00
Mikael Olenfalk
6c7dfd2ce4 Use wstring_view for constants instead of wstring (#925) 2019-05-21 15:39:26 -07:00
Mike Skowronek
080843f826 Update README.md - Build the Code section (#899)
* Update README.md

Fix readme to show correct path of build tools

* Update README.md

Add mention about recommended cli tool for building.

* Update README.md

Cover powershell in build section
2019-05-21 21:44:22 +00:00
Dreamer
db637021fd Fix memory leak, use unique_ptr for Core::Terminal object (#914) 2019-05-21 14:07:03 -07:00
Dustin L. Howett (MSFT)
8da6737d64 Switch to v5 UUIDs as profile GUIDs for the default profiles (#913)
This commit switches the GUIDs for default profiles from being randomly generated to being version 5 UUIDs. More info in #870.

## PR Checklist
* [x] Closes #870
* [x] CLA signed
* [x] Tests added/passed
* [x] Requires documentation to be updated (#883)
* [x] I've discussed this with core contributors already.

## Detailed Description of the Pull Request / Additional comments
This commit has a number of changes that seem ancillary, but they're general goodness. Let me explain:

* I've added a whole new Types test library with only two tests in
* Since UUIDv5 generation requires SHA1, we needed to take a dependency on bcrypt
* I honestly don't think we should have to link bcrypt in conhost, but LTO should take care of that
  * I considered adding a new Terminal-specific Utils/Types library, but that seemed like a waste
* The best way to link bcrypt turned out to be in line with a discussion @miniksa and I had, where we decided we both love APISets and think that the console should link against them exclusively... so I've added `onecore_apiset.lib` to the front of the link line, where it will deflect the linker away from most of the other libs automagically.

```
StartGroup: UuidTests::TestV5UuidU8String
Verify: AreEqual(uuidExpected, uuidActual)
EndGroup: UuidTests::TestV5UuidU8String [Passed]

StartGroup: UuidTests::TestV5UuidU16String
Verify: AreEqual(uuidExpected, uuidActual)
EndGroup: UuidTests::TestV5UuidU16String [Passed]
```
2019-05-21 13:29:16 -07:00
Richard Yu
fd2fb07bcf Remove ATL dependencies (#676) (#719)
* Remove CComBSTR dependency.
* Replace CStructureArray with std::vector.
* Remove CComPtr dependency.
* Add try blocks.
* Remove CString dependency.
* Add comments for string helper functions.
* Remove CComAllocator dependency.

Fixes #676.
2019-05-21 10:32:43 -07:00
Heiko Voigt
68d0c23246 make copying of files windows localization agnostic (#741)
* make copying of files windows localization agnostic

On a german Windows when building I get the following error:

(D = Datei, V = Verzeichnis)? Ist das Ziel ...\Terminal\x64\Debug\TerminalSettings.pdb ein Dateiname
oder ein Verzeichnisname
(D = Datei, V = Verzeichnis)? f

The trick with piping 'f' for file into stdin does not work here, since
in german file is called 'Datei'. Due to the fact that the UI is
translated a 'd' is expected.

Lets use '*' at the end of the target filename which is a hack to trick
'xcopy' into assuming it is a filename her a target is a folder, if the
target does not exist.

* start fixing commandline tools to run new windows terminal

  * opencas should do the same as openterm.
  * correct the filename in openterm

openterm is able to start the terminal again, but it does not start
properly because of a missing dependency.

* remove openterm command

There is currently no plan on fixing this, because WindowsTerminal.exe
does not support unpackaged activation. Let's remove them for now.
2019-05-21 16:25:54 +00:00
Mike Griese
29e380824f Support remapping keybindings (#748)
* Add support for serializing keybindings
2019-05-21 09:26:04 -05:00
Hermès BÉLUSCA - MAÏTO
acabbe0459 Fix it's versus its typo. (#911) 2019-05-21 06:15:44 +00:00
Heath Stewart
461c8b53fa Add behavior of .vsconfig to README (#907) 2019-05-21 05:00:43 +00:00
Dustin L. Howett (MSFT)
dd9bc6ee45 inbox PR 3285709: Add chafa resource into the DLL built by Windows Razzle (#912)
[Git2Git] Merged PR 3285709: Add chafa resource into the DLL built by Windows Razzle #21439265
2019-05-20 17:06:21 -07:00
Gabriele
0060614173 Added requestedTheme option into terminal settings (#710)
* added requestedTheme option into terminal settings

* fix tabs to 4 spaces

* removed newline

* fix option requestedTheme not shown in profiles.json

* fix indentation

* fix indentation part 2

Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com>
2019-05-20 20:54:19 +00:00
Oscar Calvo
37ea2dce48 Simplify DPI logic (#829)
* Simply DPI logic

* Apply PR comments

* Update src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp

Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com>

* Add comments

* Update src/cascadia/WindowsTerminal/BaseWindow.h

Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com>

* Apply PR feedback
2019-05-20 19:49:28 +00:00
Mitchell Blowey
9f4ad6d1ce Fix #670: Terminal selects a char when bringing window to foreground (#856)
* Added focus tracking to TermControl to prevent clicks which refocus the terminal window from selecting text.

* Moved open brace to a new line per repo code style.

* Moved the TermControl's _MouseClickHandler's focus check into the Mouse specific block of code. This lets any touch and drag events scroll the terminal's contents.

Fixes #670.
2019-05-20 12:05:58 -07:00
Michael Niksa
a0ebd2ed1b Create Bot.md (#884)
* Create Bot.md

#882

* Update Bot.md

* Rename Bot.md to bot.md

* Update doc/bot.md

Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com>

* Update bot.md

* Update bot.md
2019-05-20 14:27:36 +00:00
Donghyeok Tak
67f68ebf62 doc: Fix non-grammatical sentence (#895)
Replace `your` with `you` as the word `your` doesn't fit this context.
2019-05-18 13:37:00 -07:00
Neil McAlister
41a6d8ed3a Add GUIConsole sample (#285)
* Add GUIConsole sample

* Remove acrylic native functions, add a title bar

* Fix WPF app namespaces

* Respond to PR feedback

* Removed unused native calls, and fix up some stray spaces

* Switch pwsh to powershell

* Missed a spot.

* Fix typo, add newlines
2019-05-18 18:17:36 +00:00
Michael Ratanapintha
7533b31cbd Fix #453: Setting historySize=32767 causes WindowsTerminal to hang (#843)
* fix for historySize=32767 hang (except for historySize=0 case); tests still in progress

* tests run and almost pass - failure is a real bug in my change

* fixed bug that caused tests to fail, but it seems another bug causes the app to crash with a zero row count

* fix the additional bug (at a higher layer) mentioned in previous commit description

* Fix chk build assertion failures in new tests

It seems C++/WinRT doesn't like it when you implement a Windows Runtime
interface but then create instances of the implementing class
with function-call lifetime (aka stack allocation). That makes sense
given that WinRT objects are COM objects, but in my defense I was following
this example where they are just fine instantiating the `App` object
on the stack:
https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/author-apis#if-youre-not-authoring-a-runtime-class

* tabs to spaces

* CR feedback

* fix minor CR feedback (incorrect test log message)
2019-05-18 03:57:34 +00:00
Bartosz Brachaczek
73ad742c12 Fix signatures of some callback functions (#871)
* Fix signatures of callback functions

* Fix calling conventions of callback functions

* Remove now-unnecessary casts of pointers to callback functions
2019-05-17 20:32:51 +00:00
Mario Kneidinger
fd98145af2 Delete the keybinding for NewTabProfile9 and SwitchToTab9 (#831)
* Deleted keybinding for NewTabProfile0

* Readded NewTabProfile9 with unbounded shortcut

* Untabify

* Deleted NewTabProfile9 and SwitchToTab9
2019-05-17 15:52:01 +00:00
ChristianBoehm
2b41fad198 fixed on build error xcopy on localized machines (#847)
* fixed on build error xcopy on localized machines

echo ( f | xcopy ) will not work, can get around with putting an '*' at the end, xcopy will treat then as file. This solutions builds 
fine on DE German machine.

* removed echo | f it's not longer needed 

set cmd switch to capital /Y from lower
2019-05-17 00:48:00 +00:00
Christian Aashamar
251505b96b doc: Changed "docs" to "doc" (#862) 2019-05-16 17:32:28 -07:00
331 changed files with 5902 additions and 2219 deletions

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
root = true
[*]
trim_trailing_whitespace = true
insert_final_newline = true
[{*.cpp,*.c,*.hpp,*.h,*.cs}]
indent_style = space
indent_size = 4
[*.json]
indent_style = space
indent_size = 2

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@
x64/
x86/
ARM64/
build/
bld/
[Bb]in/
[Oo]bj/

View File

@@ -29,8 +29,6 @@
"Microsoft.VisualStudio.Component.VC.v141.ARM64",
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v141",
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
"Microsoft.VisualStudio.Component.VC.v141.ATL",
"Microsoft.VisualStudio.Component.VC.v141.ATL.ARM64"
"Microsoft.VisualStudio.Component.UWP.VC.ARM64"
]
}
}

View File

@@ -71,7 +71,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host
{06EC74CB-9A12-429C-B551-8562EC954747} = {06EC74CB-9A12-429C-B551-8562EC954747}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TextBuffer.UnitTests", "src\buffer\out\ut_textbuffer\TextBuffer.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8BBD-04164CB628C9}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TextBuffer.Unit.Tests", "src\buffer\out\ut_textbuffer\TextBuffer.Unit.Tests.vcxproj", "{531C23E7-4B76-4C08-8BBD-04164CB628C9}"
ProjectSection(ProjectDependencies) = postProject
{0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {0CF235BD-2DA0-407E-90EE-C467E8BBC714}
EndProjectSection
@@ -228,6 +228,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Buffer", "Buffer", "{1E4A06
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{89CDCC5C-9F53-4054-97A4-639D99F169CD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types.Unit.Tests", "src\types\ut_types\Types.Unit.Tests.vcxproj", "{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|ARM64 = AuditMode|ARM64
@@ -1099,6 +1101,24 @@ Global
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x64.Build.0 = Release|x64
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.ActiveCfg = Release|Win32
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.Build.0 = Release|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.ActiveCfg = AuditMode|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.Build.0 = AuditMode|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.Build.0 = AuditMode|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.ActiveCfg = Debug|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.Build.0 = Debug|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.ActiveCfg = Debug|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.Build.0 = Debug|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.ActiveCfg = Debug|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.Build.0 = Debug|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.ActiveCfg = Release|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.Build.0 = Release|ARM64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.ActiveCfg = Release|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.Build.0 = Release|x64
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.ActiveCfg = Release|Win32
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1157,6 +1177,7 @@ Global
{F1995847-4AE5-479A-BBAF-382E51A63532} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

View File

@@ -63,7 +63,7 @@ Otherwise, you'll need to wait until Mid-June for an official preview build to d
## I built and ran the new Terminal, but I just get a blank window app!
Make sure your are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
Make sure you are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
To check your OS architecture go to Settings -> System -> About (or Win+X -> System) and under `Device specifications` check for the `System type`
## I built and ran the new Terminal, but it looks just like the old console! What gives?
@@ -82,14 +82,10 @@ Secondly, try pressing <kbd>Ctrl</kbd> + <kbd>T</kbd>. The tabs are hidden when
## Prerequisites
* You must be running Windows 1903 (build >= 10.0.18362.0) or above in order to run Windows Terminal
- **As of May 2019** this build is only available through Windows Insider Program. You may register and configure Insider Program through your device's system settings.
* You must have the [1903 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) (build 10.0.18362.0) installed
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed
* You must install the following Workloads via the VS Installer:
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed.
* You must install the following Workloads via the VS Installer. If you're running VS 2019, opening the solution will [prompt you to install missing components automatically](https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/).
- Desktop Development with C++
- If you're running VS2019, you'll also need to install the following Individual Components:
- MSVC v141 - VS 2017 C++ (x86 and x64) build tools
- C++ ATL for v141 build tools (x86 and x64)
- Universal Windows Platform Development
- Also install the following Individual Component:
- C++ (v141) Universal Windows Platform Tools
@@ -112,7 +108,7 @@ We ask that **before you start work on a feature that you would like to contribu
## Documentation
All documentation is located in the `./docs` folder. If you would like to contribute to the documentation, please submit a pull request.
All documentation is located in the `./doc` folder. If you would like to contribute to the documentation, please submit a pull request.
## Communicating with the Team
@@ -140,7 +136,17 @@ This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-S
git submodule update --init --recursive
```
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line:
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line, find your shell below.
### PowerShell
```powershell
Import-Module .\tools\OpenConsole.psm1
Set-MsBuildDevEnvironment
Invoke-OpenConsoleBuild
```
### CMD
```shell
.\tools\razzle.cmd

View File

@@ -19,6 +19,10 @@ pr:
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
jobs:
- template: ./templates/build-console-audit-job.yml
parameters:
platform: x64
- template: ./templates/build-console-ci.yml
parameters:
platform: x64
@@ -26,3 +30,7 @@ jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: x86
- template: ./templates/build-console-ci.yml
parameters:
platform: ARM64

View File

@@ -16,6 +16,10 @@ variables:
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
jobs:
- template: ./templates/build-console-audit-job.yml
parameters:
platform: x64
- template: ./templates/build-console-int.yml
parameters:
platform: x64

View File

@@ -0,0 +1,53 @@
parameters:
platform: ''
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}AuditMode
displayName: Static Analysis Build ${{ parameters.platform }}
variables:
BuildConfiguration: AuditMode
BuildPlatform: ${{ parameters.platform }}
pool: { vmImage: vs2017-win2016 }
steps:
- checkout: self
submodules: true
clean: true
- task: NuGetToolInstaller@0
displayName: Ensure NuGet 4.8.1
inputs:
versionSpec: 4.8.1
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
# This should be `task: NuGetCommand@2`
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: Restore NuGet packages
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: OpenConsole.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
displayName: 'NuGet restore packages for CI'
inputs:
command: restore
restoreSolution: build/.nuget/packages.config
feedsToUse: config
externalFeedCredentials: 'TAEF NuGet Feed'
nugetConfigPath: build/config/NuGet.config
restoreDirectory: '$(Build.SourcesDirectory)/packages'
- task: VSBuild@1
displayName: 'Build solution **\OpenConsole.sln'
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 15.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}
clean: true
maximumCpuCount: true

View File

@@ -4,8 +4,8 @@ parameters:
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}
displayName: Build ${{ parameters.platform }}
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}

View File

@@ -4,8 +4,8 @@ parameters:
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}
displayName: Build ${{ parameters.platform }}
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}

View File

@@ -6,16 +6,18 @@ jobs:
displayName: Sign and Deploy for ${{ parameters.configuration }}
dependsOn:
- Buildx64
- Buildx86
- Buildarm64
- Buildx64AuditMode
- Buildx64Release
- Buildx86Release
- Buildarm64Release
condition: |
and
(
in(dependencies.Buildx64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx86.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildarm64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
in(dependencies.Buildx64AuditMode.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx86Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildarm64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
variables:
BuildConfiguration: ${{ parameters.configuration }}

View File

@@ -586,6 +586,8 @@ typedef struct _CONSOLE_STATE_INFO {
COLORREF DefaultForeground;
COLORREF DefaultBackground;
BOOL TerminalScrolling;
LPWSTR VersionString;
/* END V2 CONSOLE_STATE_INFO */
} CONSOLE_STATE_INFO, *PCONSOLE_STATE_INFO;

89
doc/bot.md Normal file
View File

@@ -0,0 +1,89 @@
# Issue/PR Management Bot Information
## Overview
The goal here is to help us automate, manage, and narrow down what we actually need to focus on in this repository.
We'll be using tags, primarily, to help us understand what needs attention, what is sitting around and turning stale, etc.
### Quick-Guidance to Core Contributors
1. Look at `Needs-Attention` as top priority
1. Look at `Needs-Triage` during triage meetings to get a handle on what's new and sort it out
1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged impoperly
1. Manually add `Needs-Author-Feedback` when there's something we need the author to follow up on and want attention if they return it or an auto-close for inactivity if it goes stale.
### Tagging/Process Details
1. When new issues arrive, or when issues are not properly tagged... we'll mark them as `Needs-Triage` automatically.
- The core contributor team will then come through and mark them up as appropriate. The goal is to have a tag that fits the `Product`, `Area`, and `Issue` category.
- The `Needs-Triage` tag will be removed manually by the core contributor team during a triage meeting. (Exception, triage may also be done offline by senior team members during high-volume times.)
- An issue may or may not be assigned to a contributor during triage. It is not necessary to assign someone to complete it.
- We're not focusing on Projects yet.
1. When core contributors need to ask something of the author, they will manually assign the `Needs-Author-Feedback` tag.
- This tag will automatically drop off when the author comes back around and applies activity to the thread.
- When this tag drops off, the bot will apply the `Needs-Attention` tag to get the core contribution team's attention again. If an author cares enough to be active, we will attempt to prioritize engaging with that author.
- If the author doesn't come back around in a while, this will become a `No-Recent-Activity` tag.
- If there's activity on an issue, the `No-Recent-Activity` tag will automatically drop.
- If the `No-Recent-Activity` stays, the issue will be closed as stale.
1. PRs will automatically get a `Needs-Author-Feedback` tag when reviewers wait on the author
- This follows a similar decay strategy to issues.
- If the author responds, the `Needs-Author-Feedback` tag will drop.
- If there is no activity in a while, the `No-Recent-Activity` tag will appear.
- If the `No-Recent-Activity` tag exists for a while, the PR will be closed as stale.
1. Issues manually marked as `Resolution-Duplicate` will be closed shortly after activity stops
1. Pull requests manually marked as `AutoMerge` will permit the bot to complete the PR and do cleanup when certain conditions are met. See details below.
## Rules
### Issue Management
#### Mark as Triage Needed
- When an issue doesn't meet triage criteria, applies `Needs-Triage` tag. Right now, this is just when it's opened.
#### Author Has Responded
- When an issue with `Needs-Author-Feedback` gets an author response, drops that tag in favor of `Needs-Attention` to flag core contributors to drop by.
#### Remove Activity Tag
- When an issue with `No-Recent-Activity` has activity, drops this tag
#### Close Stale
- Every hour, checks if there's an issue with `Needs-Author-Feedback` and `No-Recent-Activity` for 3 days. Closes as stale.
#### Tag as No Activity
- Every hour, checks if there's been no activity in 4 days on an issue that `Needs-Author-Feedback`. If it's been 4 days, mark `No-Recent-Activity` as well.
#### Close Duplicate Issues
- Every hour, checks if there's been a day since the last activity on an issue with tag `Resolution-Duplicate` and closes it if inactive.
#### Enforce tag system
- When an issue is opened or labels are changed in any way, we will check if the tagging matches the system. If not, it will get `Needs-Tag-Fix`. The system is to have an `Area-`, `Issue-`, and `Product-` tag for all open things, and also a `Resolution-` for closed ones.
- When the tags from appropriate categories are applied, it will auto-remove the `Needs-Tag-Fix` tag.
### PR Management
#### Codeflow Link *(Disabled)*
- Bumps a PR with a link to the Microsoft CodeFlow tool for reviewing PRs
#### Marks PR as Awaiting Author Feedback
- When a reviewer marks the PR as changes requested, the `Needs-Author-Feedback` tag will be applied
#### Removes Awaiting Author Feedback
- When the PR author updates the pull request, comments on it, or responds to a review, the `Needs-Author-Feedback` tag is removed.
#### Removes No Recent Activity
- When anyone touches the pull request, the `No-Recent-Activity` tag is removed.
#### Markup stale pull requests
- Every hour, if a pull request `Needs-Author-Feedback` and hasn't been touched in 7 days, it will get the `No-Recent-Activity` tag.
#### Close stale pull requests
- Every hour, if a pull request has `No-Recent-Activity` and hasn't been touched in a further 7 days, it will be closed.
#### Auto-Merge pull requests
- When a pull request has the `AutoMerge` label...
- If it has been at least 480 minutes and all the statuses pass, merge it in.
- Will use Squash merge stratgy
- Will attempt to delete branch after merge, if possible
- Will automatically remove the `AutoMerge` label if changes are pushed by someone *without* Write Access.
- More information on bot-logic that can be controlled with comments is [here](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Advanced-auto-merge)
## Admin Panel
[Here](https://fabric-cp.azurewebsites.net/bot/)

View File

@@ -1,5 +1,5 @@
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Consle sample application.
// Copyright © 2018, Microsoft
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Console sample application.
// Copyright © 2018, Microsoft
#include "stdafx.h"
#include <Windows.h>

View File

@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's Console API.
/// </summary>
static class ConsoleApi
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
internal delegate bool ConsoleEventDelegate(CtrlTypes ctrlType);
internal enum CtrlTypes : uint
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's Process API.
/// </summary>
static class ProcessApi
{
internal const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool InitializeProcThreadAttributeList(
IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool UpdateProcThreadAttribute(
IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue,
IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hObject);
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
namespace GUIConsole.ConPTY.Native
{
/// <summary>
/// PInvoke signatures for Win32's PseudoConsole API.
/// </summary>
static class PseudoConsoleApi
{
internal const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
[StructLayout(LayoutKind.Sequential)]
internal struct COORD
{
public short X;
public short Y;
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int CreatePseudoConsole(COORD size, SafeFileHandle hInput, SafeFileHandle hOutput, uint dwFlags, out IntPtr phPC);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int ClosePseudoConsole(IntPtr hPC);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, IntPtr lpPipeAttributes, int nSize);
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.ProcessApi;
namespace GUIConsole.ConPTY.Processes
{
/// <summary>
/// Represents an instance of a process.
/// </summary>
internal sealed class Process : IDisposable
{
public Process(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo)
{
StartupInfo = startupInfo;
ProcessInfo = processInfo;
}
public STARTUPINFOEX StartupInfo { get; }
public PROCESS_INFORMATION ProcessInfo { get; }
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// dispose managed state (managed objects).
}
// dispose unmanaged state
// Free the attribute list
if (StartupInfo.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(StartupInfo.lpAttributeList);
Marshal.FreeHGlobal(StartupInfo.lpAttributeList);
}
// Close process and thread handles
if (ProcessInfo.hProcess != IntPtr.Zero)
{
CloseHandle(ProcessInfo.hProcess);
}
if (ProcessInfo.hThread != IntPtr.Zero)
{
CloseHandle(ProcessInfo.hThread);
}
disposedValue = true;
}
}
~Process()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.ProcessApi;
namespace GUIConsole.ConPTY.Processes
{
/// <summary>
/// Support for starting and configuring processes.
/// </summary>
/// <remarks>
/// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute
/// </remarks>
static class ProcessFactory
{
/// <summary>
/// Start and configure a process. The return value represents the process and should be disposed.
/// </summary>
internal static Process Start(string command, IntPtr attributes, IntPtr hPC)
{
var startupInfo = ConfigureProcessThread(hPC, attributes);
var processInfo = RunProcess(ref startupInfo, command);
return new Process(startupInfo, processInfo);
}
private static STARTUPINFOEX ConfigureProcessThread(IntPtr hPC, IntPtr attributes)
{
// this method implements the behavior described in https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#preparing-for-creation-of-the-child-process
var lpSize = IntPtr.Zero;
var success = InitializeProcThreadAttributeList(
lpAttributeList: IntPtr.Zero,
dwAttributeCount: 1,
dwFlags: 0,
lpSize: ref lpSize
);
if (success || lpSize == IntPtr.Zero) // we're not expecting `success` here, we just want to get the calculated lpSize
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not calculate the number of bytes for the attribute list.");
}
var startupInfo = new STARTUPINFOEX();
startupInfo.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
success = InitializeProcThreadAttributeList(
lpAttributeList: startupInfo.lpAttributeList,
dwAttributeCount: 1,
dwFlags: 0,
lpSize: ref lpSize
);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not set up attribute list.");
}
success = UpdateProcThreadAttribute(
lpAttributeList: startupInfo.lpAttributeList,
dwFlags: 0,
attribute: attributes,
lpValue: hPC,
cbSize: (IntPtr)IntPtr.Size,
lpPreviousValue: IntPtr.Zero,
lpReturnSize: IntPtr.Zero
);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not set pseudoconsole thread attribute.");
}
return startupInfo;
}
private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine)
{
int securityAttributeSize = Marshal.SizeOf<SECURITY_ATTRIBUTES>();
var pSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
var tSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
var success = CreateProcess(
lpApplicationName: null,
lpCommandLine: commandLine,
lpProcessAttributes: ref pSec,
lpThreadAttributes: ref tSec,
bInheritHandles: false,
dwCreationFlags: EXTENDED_STARTUPINFO_PRESENT,
lpEnvironment: IntPtr.Zero,
lpCurrentDirectory: null,
lpStartupInfo: ref sInfoEx,
lpProcessInformation: out PROCESS_INFORMATION pInfo
);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not create process.");
}
return pInfo;
}
}
}

View File

@@ -0,0 +1,40 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// Utility functions around the new Pseudo Console APIs.
/// </summary>
internal sealed class PseudoConsole : IDisposable
{
public static readonly IntPtr PseudoConsoleThreadAttribute = (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE;
public IntPtr Handle { get; }
private PseudoConsole(IntPtr handle)
{
this.Handle = handle;
}
internal static PseudoConsole Create(SafeFileHandle inputReadSide, SafeFileHandle outputWriteSide, int width, int height)
{
var createResult = CreatePseudoConsole(
new COORD { X = (short)width, Y = (short)height },
inputReadSide, outputWriteSide,
0, out IntPtr hPC);
if(createResult != 0)
{
throw new Win32Exception(createResult, "Could not create pseudo console.");
}
return new PseudoConsole(hPC);
}
public void Dispose()
{
ClosePseudoConsole(Handle);
}
}
}

View File

@@ -0,0 +1,48 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// A pipe used to talk to the pseudoconsole, as described in:
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
/// </summary>
/// <remarks>
/// We'll have two instances of this class, one for input and one for output.
/// </remarks>
internal sealed class PseudoConsolePipe : IDisposable
{
public readonly SafeFileHandle ReadSide;
public readonly SafeFileHandle WriteSide;
public PseudoConsolePipe()
{
if (!CreatePipe(out ReadSide, out WriteSide, IntPtr.Zero, 0))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "failed to create pipe");
}
}
#region IDisposable
void Dispose(bool disposing)
{
if (disposing)
{
ReadSide?.Dispose();
WriteSide?.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,113 @@
using GUIConsole.ConPTY.Processes;
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Threading;
using static GUIConsole.ConPTY.Native.ConsoleApi;
namespace GUIConsole.ConPTY
{
/// <summary>
/// Class for managing communication with the underlying console, and communicating with its pseudoconsole.
/// </summary>
public sealed class Terminal
{
private const string ExitCommand = "exit\r";
private const string CtrlC_Command = "\x3";
private SafeFileHandle _consoleInputPipeWriteHandle;
private StreamWriter _consoleInputWriter;
/// <summary>
/// A stream of VT-100-enabled output from the console.
/// </summary>
public FileStream ConsoleOutStream { get; private set; }
/// <summary>
/// Fired once the console has been hooked up and is ready to receive input.
/// </summary>
public event EventHandler OutputReady;
public Terminal()
{
}
/// <summary>
/// Start the psuedoconsole and run the process as shown in
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
/// </summary>
/// <param name="command">the command to run, e.g. cmd.exe</param>
/// <param name="consoleHeight">The height (in characters) to start the pseudoconsole with. Defaults to 80.</param>
/// <param name="consoleWidth">The width (in characters) to start the pseudoconsole with. Defaults to 30.</param>
public void Start(string command, int consoleWidth = 80, int consoleHeight = 30)
{
using (var inputPipe = new PseudoConsolePipe())
using (var outputPipe = new PseudoConsolePipe())
using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, consoleWidth, consoleHeight))
using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle))
{
// copy all pseudoconsole output to a FileStream and expose it to the rest of the app
ConsoleOutStream = new FileStream(outputPipe.ReadSide, FileAccess.Read);
OutputReady.Invoke(this, EventArgs.Empty);
// Store input pipe handle, and a writer for later reuse
_consoleInputPipeWriteHandle = inputPipe.WriteSide;
_consoleInputWriter = new StreamWriter(new FileStream(_consoleInputPipeWriteHandle, FileAccess.Write))
{
AutoFlush = true
};
// free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar)
OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe, _consoleInputWriter));
WaitForExit(process).WaitOne(Timeout.Infinite);
}
}
/// <summary>
/// Sends the given string to the anonymous pipe that writes to the active pseudoconsole.
/// </summary>
/// <param name="input">A string of characters to write to the console. Supports VT-100 codes.</param>
public void WriteToPseudoConsole(string input)
{
if (_consoleInputWriter == null)
{
throw new InvalidOperationException("There is no writer attached to a pseudoconsole. Have you called Start on this instance yet?");
}
_consoleInputWriter.Write(input);
}
/// <summary>
/// Get an AutoResetEvent that signals when the process exits
/// </summary>
private static AutoResetEvent WaitForExit(Process process) =>
new AutoResetEvent(false)
{
SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, ownsHandle: false)
};
/// <summary>
/// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button).
/// Intended for resource cleanup logic.
/// </summary>
private static void OnClose(Action handler)
{
SetConsoleCtrlHandler(eventType =>
{
if (eventType == CtrlTypes.CTRL_CLOSE_EVENT)
{
handler();
}
return false;
}, true);
}
private void DisposeResources(params IDisposable[] disposables)
{
foreach (var disposable in disposables)
{
disposable.Dispose();
}
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
</configuration>

View File

@@ -0,0 +1,8 @@
<Application x:Class="GUIConsole.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,15 @@
using System.Windows;
namespace GUIConsole.Wpf
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
}
}
}

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" 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)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FD2109FE-F78A-4E31-8317-11D1B66B69AF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>GUIConsole.Wpf</RootNamespace>
<AssemblyName>GUIConsole.Wpf</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GUIConsole.ConPTY\GUIConsole.ConPTY.csproj">
<Project>{96634c74-0c52-4381-9477-97e1d58fe5b5}</Project>
<Name>GUIConsole.ConPTY</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,84 @@
<Window x:Class="GUIConsole.Wpf.MainWindow"
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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Background="#C7000000"
AllowsTransparency="True"
WindowStyle="None"
MouseDown="Window_MouseDown"
BorderThickness="1"
BorderBrush="LightSlateGray"
KeyDown="Window_KeyDown"
Loaded="Window_Loaded">
<Window.Resources>
<Style x:Key="TitleBarButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Width" Value="46"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource TitleBarButtonStyle}">
<Setter Property="Content" Value="&#xE10A;"/>
<!--Remove the default Button template's Triggers, otherwise they'll override our trigger below.-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Button.Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TitleBarTitle" Grid.Column="0" VerticalAlignment="Center" Padding="10 0" Foreground="White">
GUIConsole
</TextBlock>
<StackPanel x:Name="TitleBarButtons" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top">
<Button x:Name="MinimizeButton"
Click="MinimizeButton_Click"
Content="&#xE108;"
Style="{StaticResource TitleBarButtonStyle}"/>
<Button x:Name="MaximizeRestoreButton"
Click="MaximizeRestoreButton_Click"
Content="&#xE922;"
FontSize="12"
Style="{StaticResource TitleBarButtonStyle}"/>
<Button x:Name="CloseButton"
Click="CloseButton_Click"
FontSize="12"
Style="{StaticResource CloseButtonStyle}"/>
</StackPanel>
</Grid>
<ScrollViewer x:Name="TerminalHistoryViewer" Grid.Row="1" ScrollChanged="ScrollViewer_ScrollChanged">
<TextBlock x:Name="TerminalHistoryBlock" FontFamily="Consolas" TextWrapping="Wrap" Foreground="White"/>
</ScrollViewer>
</Grid>
</Window>

View File

@@ -0,0 +1,121 @@
using GUIConsole.ConPTY;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace GUIConsole.Wpf
{
public partial class MainWindow : Window
{
private Terminal _terminal;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Start up the console, and point it to cmd.exe.
_terminal = new Terminal();
Task.Run(() => _terminal.Start("powershell.exe"));
_terminal.OutputReady += Terminal_OutputReady;
}
private void Terminal_OutputReady(object sender, EventArgs e)
{
// Start a long-lived thread for the "read console" task, so that we don't use a standard thread pool thread.
Task.Factory.StartNew(() => CopyConsoleToWindow(), TaskCreationOptions.LongRunning);
Dispatcher.Invoke(() => { TitleBarTitle.Text = "GUIConsole - powershell.exe"; });
}
private void CopyConsoleToWindow()
{
using (StreamReader reader = new StreamReader(_terminal.ConsoleOutStream))
{
// Read the console's output 1 character at a time
int bytesRead;
char[] buf = new char[1];
while ((bytesRead = reader.ReadBlock(buf, 0, 1)) != 0)
{
// This is where you'd parse and tokenize the incoming VT100 text, most likely.
Dispatcher.Invoke(() =>
{
// ...and then you'd do something to render it.
// For now, just emit raw VT100 to the primary TextBlock.
TerminalHistoryBlock.Text += new string(buf.Take(bytesRead).ToArray());
});
}
}
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (!e.Handled)
{
// This is where you'd take the pressed key, and convert it to a
// VT100 code before sending it along. For now, we'll just send _something_.
_terminal.WriteToPseudoConsole(e.Key.ToString());
}
}
private bool _autoScroll = true;
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// User scrolled...
if (e.ExtentHeightChange == 0)
{
//...down to the bottom. Re-engage autoscrolling.
if (TerminalHistoryViewer.VerticalOffset == TerminalHistoryViewer.ScrollableHeight)
{
_autoScroll = true;
}
//...elsewhere. Disengage autoscrolling.
else
{
_autoScroll = false;
}
// Autoscrolling is enabled, and content caused scrolling:
if (_autoScroll && e.ExtentHeightChange != 0)
{
TerminalHistoryViewer.ScrollToEnd();
}
}
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) { DragMove(); }
}
private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e)
{
if (WindowState == WindowState.Normal)
{
WindowState = WindowState.Maximized;
MaximizeRestoreButton.Content = "\uE923";
}
else if (WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
MaximizeRestoreButton.Content = "\uE922";
}
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GUIConsole.Wpf")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GUIConsole.Wpf")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUIConsole.Wpf.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUIConsole.Wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUIConsole.Wpf.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@@ -0,0 +1,61 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUIConsole.WPF", "GUIConsole.WPF\GUIConsole.WPF.csproj", "{FD2109FE-F78A-4E31-8317-11D1B66B69AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GUIConsole.ConPTY", "GUIConsole.ConPTY\GUIConsole.ConPTY.csproj", "{96634C74-0C52-4381-9477-97E1D58FE5B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.ActiveCfg = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.Build.0 = Debug|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.Build.0 = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.ActiveCfg = Release|Any CPU
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.ActiveCfg = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.Build.0 = Debug|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.Build.0 = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.ActiveCfg = Release|Any CPU
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0066B3A2-194D-471B-A56D-E25BB5AC0EB4}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,9 @@
# GUIConsole
This is an example of what the skeleton of a custom WPF console might look like.
The `GUIConsole.WPF` project is a WPF application targeting .NET 4.6.1. It creates a single WPF `Window` that acts as the console, and keeps the underlying console visible.
The `GUIConsole.ConPTY` project is a .NET Standard 2.0 library that handles the creation of the console, and enables pseudoconsole behavior. `Terminal.cs` contains the publicly visible pieces that the WPF application will interact with. `Terminal.cs` exposes two things that allow reading from, and writing to, the console:
* `ConsoleOutStream`, a `FileStream` hooked up to the pseudoconsole's output pipe. This will output VT100.
* `WriteToPseudoConsole(string input)`, a method that will take the given string and write it to the pseudoconsole via its input pipe. This accepts VT100.

View File

@@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Console Rules" Description="These rules enforce static analysis on console code." ToolsVersion="15.0">
<Include Path="cppcorecheckrules.ruleset" Action="Default" />
<Rules AnalyzerId="Microsoft.Analyzers.NativeCodeAnalysis" RuleNamespace="Microsoft.Rules.Native">
<Rule Id="C6001" Action="Error" />
<Rule Id="C6011" Action="Error" />
</Rules>
</RuleSet>

View File

@@ -25,7 +25,7 @@ Author:
class InvalidCharInfoConversionException : public std::exception
{
const char* what() noexcept
const char* what() const noexcept
{
return "Cannot convert to CHAR_INFO without explicit TextAttribute";
}

View File

@@ -238,8 +238,8 @@ void TextAttribute::SetDefaultBackground() noexcept
}
// Method Description:
// - Returns true if this attribute indicates it's foreground is the "default"
// foreground. It's _rgbForeground will contain the actual value of the
// - Returns true if this attribute indicates its foreground is the "default"
// foreground. Its _rgbForeground will contain the actual value of the
// default foreground. If the default colors are ever changed, this method
// should be used to identify attributes with the default fg value, and
// update them accordingly.
@@ -253,8 +253,8 @@ bool TextAttribute::ForegroundIsDefault() const noexcept
}
// Method Description:
// - Returns true if this attribute indicates it's background is the "default"
// background. It's _rgbBackground will contain the actual value of the
// - Returns true if this attribute indicates its background is the "default"
// background. Its _rgbBackground will contain the actual value of the
// default background. If the default colors are ever changed, this method
// should be used to identify attributes with the default bg value, and
// update them accordingly.

View File

@@ -32,7 +32,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
}
// Routine Description:
// - erases key and it's associated data from the storage
// - erases key and its associated data from the storage
// Arguments:
// - key - the key to remove
void UnicodeStorage::Erase(const key_type key) noexcept

View File

@@ -21,7 +21,6 @@ Abstract:
#include "LibraryIncludes.h"
#pragma warning(push)
#pragma warning(disable: ALL_CPPCORECHECK_WARNINGS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#endif

View File

@@ -21,8 +21,8 @@
<ProjectGuid>{531C23E7-4B76-4C08-8BBD-04164CB628C9}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>TextBufferUnitTests</RootNamespace>
<ProjectName>TextBuffer.UnitTests</ProjectName>
<TargetName>TextBuffer.UnitTests</TargetName>
<ProjectName>TextBuffer.Unit.Tests</ProjectName>
<TargetName>TextBuffer.Unit.Tests</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>

View File

@@ -99,6 +99,14 @@
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-100.png" />
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-200.png" />
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-100.png" />
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-200.png" />
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-100.png" />
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-200.png" />
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-100.png" />
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-200.png" />
<PRIResource Include="Resources\en-US\Resources.resw" />
</ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -123,6 +123,21 @@
<data name="AppName" xml:space="preserve">
<value>Windows Terminal (Preview)</value>
</data>
<data name="InitialJsonParseErrorText" xml:space="preserve">
<value>Settings could not be loaded from file - temporarily using the default settings. Check for syntax errors, including trailing commas.</value>
</data>
<data name="InitialJsonParseErrorTitle" xml:space="preserve">
<value>Failed to load settings</value>
</data>
<data name="Ok" xml:space="preserve">
<value>Ok</value>
</data>
<data name="ReloadJsonParseErrorText" xml:space="preserve">
<value>Settings could not be reloaded from file. Check for syntax errors, including trailing commas.</value>
</data>
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
<value>Failed to reload settings</value>
</data>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
</data>

View File

@@ -5,6 +5,8 @@
#include "XamlApplication.h"
#include "XamlApplication.g.cpp"
namespace xaml = ::winrt::Windows::UI::Xaml;
extern "C" {

View File

@@ -6,9 +6,13 @@
#include <shellapi.h>
#include <filesystem>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include <winrt/Windows.ApplicationModel.Resources.h>
#include "App.g.cpp"
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
@@ -42,7 +46,9 @@ namespace winrt::TerminalApp::implementation
base_type(parentProvider),
_settings{ },
_tabs{ },
_loadedInitialSettings{ false }
_loadedInitialSettings{ false },
_settingsLoadedResult{ S_OK },
_dialogLock{}
{
// For your own sanity, it's better to do setup outside the ctor.
// If you do any setup in the ctor that ends up throwing an exception,
@@ -159,6 +165,7 @@ namespace winrt::TerminalApp::implementation
{
IInspectable g = res.Lookup(key);
winrt::Windows::UI::Xaml::Style style = g.try_as<winrt::Windows::UI::Xaml::Style>();
_root.Style(style);
_tabRow.Style(style);
}
@@ -166,6 +173,68 @@ namespace winrt::TerminalApp::implementation
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
_OpenNewTab(std::nullopt);
_root.Loaded({ this, &App::_OnLoaded });
}
// Method Description:
// - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the
// the title and text from our Resources using the provided keys.
// - Only one dialog can be visible at a time. If another dialog is visible
// when this is called, nothing happens.
// Arguments:
// - titleKey: The key to use to lookup the title text from our resources.
// - contentKey: The key to use to lookup the content text from our resources.
fire_and_forget App::_ShowOkDialog(const winrt::hstring& titleKey,
const winrt::hstring& contentKey)
{
// DON'T release this lock in a wil::scope_exit. The scope_exit will get
// called when we await, which is not what we want.
std::unique_lock lock{ _dialogLock, std::try_to_lock };
if (!lock)
{
// Another dialog is visible.
return;
}
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
auto title = resourceLoader.GetString(titleKey);
auto message = resourceLoader.GetString(contentKey);
auto buttonText = resourceLoader.GetString(L"Ok");
Controls::ContentDialog dialog;
dialog.Title(winrt::box_value(title));
dialog.Content(winrt::box_value(message));
dialog.CloseButtonText(buttonText);
// IMPORTANT: Add the dialog to the _root UIElement before you show it,
// so it knows how to attach to the XAML content.
_root.Children().Append(dialog);
// Display the dialog.
Controls::ContentDialogResult result = co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
// After the dialog is dismissed, the dialog lock (held by `lock`) will
// be released so another can be shown.
}
// Method Description:
// - Triggered when the application is fiished loading. If we failed to load
// the settings, then this will display the error dialog. This is done
// here instead of when loading the settings, because we need our UI to be
// visible to display the dialog, and when we're loading the settings,
// the UI might not be visible yet.
// Arguments:
// - <unused>
void App::_OnLoaded(const IInspectable& /*sender*/,
const RoutedEventArgs& /*eventArgs*/)
{
if (FAILED(_settingsLoadedResult))
{
const winrt::hstring titleKey = L"InitialJsonParseErrorTitle";
const winrt::hstring textKey = L"InitialJsonParseErrorText";
_ShowOkDialog(titleKey, textKey);
}
}
// Method Description:
@@ -215,11 +284,27 @@ namespace winrt::TerminalApp::implementation
void App::_CreateNewTabFlyout()
{
auto newTabFlyout = Controls::MenuFlyout{};
auto keyBindings = _settings->GetKeybindings();
const GUID defaultProfileGuid = _settings->GlobalSettings().GetDefaultProfile();
for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++)
{
const auto& profile = _settings->GetProfiles()[profileIndex];
auto profileMenuItem = Controls::MenuFlyoutItem{};
// add the keyboard shortcuts for the first 9 profiles
if (profileIndex < 9)
{
// enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0
auto profileKeyChord = keyBindings.GetKeyBinding(static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0)));
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
}
auto profileName = profile.GetName();
winrt::hstring hName{ profileName };
profileMenuItem.Text(hName);
@@ -231,6 +316,12 @@ namespace winrt::TerminalApp::implementation
profileMenuItem.Icon(_GetIconFromProfile(profile));
}
if (profile.GetGuid() == defaultProfileGuid)
{
// Contrast the default profile with others in font weight.
profileMenuItem.FontWeight(FontWeights::Bold());
}
profileMenuItem.Click([this, profileIndex](auto&&, auto&&){
this->_OpenNewTab({ profileIndex });
});
@@ -254,6 +345,12 @@ namespace winrt::TerminalApp::implementation
settingsItem.Click({ this, &App::_SettingsButtonOnClick });
newTabFlyout.Items().Append(settingsItem);
auto settingsKeyChord = keyBindings.GetKeyBinding(ShortcutAction::OpenSettings);
if (settingsKeyChord)
{
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
}
// Create the feedback button.
auto feedbackFlyout = Controls::MenuFlyoutItem{};
feedbackFlyout.Text(L"Feedback");
@@ -333,6 +430,38 @@ namespace winrt::TerminalApp::implementation
bindings.OpenSettings([this]() { _OpenSettings(); });
}
// Method Description:
// - Attempt to load the settings. If we fail for any reason, returns an error.
// Arguments:
// - saveOnLoad: If true, after loading the settings, we should re-write
// them to the file, to make sure the schema is updated. See
// `CascadiaSettings::LoadAll` for details.
// Return Value:
// - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT.
[[nodiscard]]
HRESULT App::_TryLoadSettings(const bool saveOnLoad) noexcept
{
HRESULT hr = E_FAIL;
try
{
auto newSettings = CascadiaSettings::LoadAll(saveOnLoad);
_settings = std::move(newSettings);
hr = S_OK;
}
catch (const winrt::hresult_error& e)
{
hr = e.code();
LOG_HR(hr);
}
catch (...)
{
hr = wil::ResultFromCaughtException();
LOG_HR(hr);
}
return hr;
}
// Method Description:
// - Initialized our settings. See CascadiaSettings for more details.
// Additionally hooks up our callbacks for keybinding events to the
@@ -342,7 +471,21 @@ namespace winrt::TerminalApp::implementation
// happening during startup, it'll need to happen on a background thread.
void App::LoadSettings()
{
_settings = CascadiaSettings::LoadAll();
// Attempt to load the settings.
// If it fails,
// - use Default settings,
// - don't persist them (LoadAll won't save them in this case).
// - _settingsLoadedResult will be set to an error, indicating that
// we should display the loading error.
// * We can't display the error now, because we might not have a
// UI yet. We'll display the error in _OnLoaded.
_settingsLoadedResult = _TryLoadSettings(true);
if (FAILED(_settingsLoadedResult))
{
_settings = std::make_unique<CascadiaSettings>();
_settings->CreateDefaults();
}
_HookupKeyBindings(_settings->GetKeybindings());
@@ -399,19 +542,36 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Reloads the settings from the profile.json.
// Arguments:
// - <none>
// Return Value:
// - <none>
void App::_ReloadSettings()
{
_settings = CascadiaSettings::LoadAll();
// Attempt to load our settings.
// If it fails,
// - don't change the settings (and don't actually apply the new settings)
// - don't persist them.
// - display a loading error
_settingsLoadedResult = _TryLoadSettings(false);
if (FAILED(_settingsLoadedResult))
{
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
const winrt::hstring titleKey = L"ReloadJsonParseErrorTitle";
const winrt::hstring textKey = L"ReloadJsonParseErrorText";
_ShowOkDialog(titleKey, textKey);
});
return;
}
// Here, we successfully reloaded the settings, and created a new
// TerminalSettings object.
// Re-wire the keybindings to their handlers, as we'll have created a
// new AppKeyBindings object.
_HookupKeyBindings(_settings->GetKeybindings());
auto profiles = _settings->GetProfiles();
// Refresh UI elements
auto profiles = _settings->GetProfiles();
for (auto &profile : profiles)
{
const GUID profileGuid = profile.GetGuid();
@@ -690,7 +850,7 @@ namespace winrt::TerminalApp::implementation
// Negative values of `delta` will move the view up by one page, and positive values
// will move the viewport down by one page.
// Arguments:
// - delta: The direction to move the view relative to the current viewport(it
// - delta: The direction to move the view relative to the current viewport(it
// is clamped between -1 and 1)
void App::_ScrollPage(int delta)
{
@@ -853,18 +1013,26 @@ namespace winrt::TerminalApp::implementation
}
uint32_t tabIndexFromControl = 0;
_tabView.Items().IndexOf(tabViewItem, tabIndexFromControl);
if (tabIndexFromControl == _GetFocusedTabIndex())
{
_tabView.SelectedIndex((tabIndexFromControl > 0) ? tabIndexFromControl - 1 : 1);
}
auto focusedTabIndex = _GetFocusedTabIndex();
// Removing the tab from the collection will destroy its control and disconnect its connection.
_tabs.erase(_tabs.begin() + tabIndexFromControl);
_tabView.Items().RemoveAt(tabIndexFromControl);
// ensure tabs and focus is sync
_tabView.SelectedIndex(tabIndexFromControl > 0 ? tabIndexFromControl - 1 : 0);
if (tabIndexFromControl == focusedTabIndex)
{
if (focusedTabIndex >= _tabs.size())
{
focusedTabIndex = _tabs.size() - 1;
}
if (focusedTabIndex < 0)
{
focusedTabIndex = 0;
}
_SelectTab(focusedTabIndex);
}
}
// Method Description:
@@ -899,6 +1067,40 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display.
// Takes into account a special case for an error condition for a comma
// Arguments:
// - MenuFlyoutItem that will be displayed, and a KeyChord to map an accelerator
void App::_SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord)
{
// work around https://github.com/microsoft/microsoft-ui-xaml/issues/708 in case of VK_OEM_COMMA
if (keyChord.Vkey() != VK_OEM_COMMA)
{
// use the XAML shortcut to give us the automatic capabilities
auto menuShortcut = Windows::UI::Xaml::Input::KeyboardAccelerator{};
// TODO: Modify this when https://github.com/microsoft/terminal/issues/877 is resolved
menuShortcut.Key(static_cast<Windows::System::VirtualKey>(keyChord.Vkey()));
// inspect the modifiers from the KeyChord and set the flags int he XAML value
auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers());
// add the modifiers to the shortcut
menuShortcut.Modifiers(modifiers);
// add to the menu
menuItem.KeyboardAccelerators().Append(menuShortcut);
}
else // we've got a comma, so need to just use the alternate method
{
// extract the modifier and key to a nice format
auto overrideString = AppKeyBindings::FormatOverrideShortcutText(keyChord.Modifiers());
menuItem.KeyboardAcceleratorTextOverride(overrideString + L" ,");
}
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -8,8 +8,6 @@
#include "App.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include <wil/filesystem.h>
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
@@ -64,14 +62,22 @@ namespace winrt::TerminalApp::implementation
std::vector<std::shared_ptr<Tab>> _tabs;
std::unique_ptr<::TerminalApp::CascadiaSettings> _settings;
std::unique_ptr<TerminalApp::AppKeyBindings> _keyBindings;
HRESULT _settingsLoadedResult;
bool _loadedInitialSettings;
std::shared_mutex _dialogLock;
wil::unique_folder_change_reader_nothrow _reader;
void _Create();
void _CreateNewTabFlyout();
fire_and_forget _ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
[[nodiscard]]
HRESULT _TryLoadSettings(const bool saveOnLoad) noexcept;
void _LoadSettings();
void _OpenSettings();
@@ -101,6 +107,7 @@ namespace winrt::TerminalApp::implementation
// MSFT:20641986: Add keybindings for New Window
void _ScrollPage(int delta);
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
void _OnTabClosing(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabClosingEventArgs& eventArgs);
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
@@ -111,6 +118,7 @@ namespace winrt::TerminalApp::implementation
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile);
static void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
};
}

View File

@@ -1,3 +1,5 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<MSMarkup:XamlApplication
x:Class="TerminalApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
@@ -16,7 +18,6 @@
<ResourceDictionary.MergedDictionaries>
<!-- Include the MUX Controls resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls"/>
<ResourceDictionary>
<!-- We're going to apply this style to the root Grid acting
@@ -29,6 +30,20 @@
<Setter Property="Background" Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
</Style>
<!-- Manually theme the CloseButton of a ContentDialog. We
need to do this, because for whatever reason, if we show a
ContentDialog when the app theme is opposite the system
theme, the buttons will appear transparent. This only
applies to the Close button of the dialog, since we're only
using the Close button of the dialog. If we ever add other
dialogs with more buttons, we'll probably want to make sure
the buttons are styled differently. -->
<Style TargetType="ContentDialog">
<!-- the value `AccentButtonStyle` is taken straight
from the ContentDialog source -->
<Setter Target="CloseButtonStyle" Value="{StaticResource AccentButtonStyle}" />
</Style>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- Define resources for Dark mode here -->
@@ -39,8 +54,11 @@
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</MSMarkup:XamlApplication.Resources>
</MSMarkup:XamlApplication>

View File

@@ -1,158 +1,411 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppKeyBindings.h"
using namespace winrt::Microsoft::Terminal;
namespace winrt::TerminalApp::implementation
{
void AppKeyBindings::SetKeyBinding(TerminalApp::ShortcutAction const& action,
Settings::KeyChord const& chord)
{
_keyShortcuts[chord] = action;
}
bool AppKeyBindings::TryKeyChord(Settings::KeyChord const& kc)
{
const auto keyIter = _keyShortcuts.find(kc);
if (keyIter != _keyShortcuts.end())
{
const auto action = keyIter->second;
return _DoAction(action);
}
return false;
}
bool AppKeyBindings::_DoAction(ShortcutAction action)
{
switch (action)
{
case ShortcutAction::CopyText:
_CopyTextHandlers();
return true;
case ShortcutAction::PasteText:
_PasteTextHandlers();
return true;
case ShortcutAction::NewTab:
_NewTabHandlers();
return true;
case ShortcutAction::OpenSettings:
_OpenSettingsHandlers();
return true;
case ShortcutAction::NewTabProfile0:
_NewTabWithProfileHandlers(0);
return true;
case ShortcutAction::NewTabProfile1:
_NewTabWithProfileHandlers(1);
return true;
case ShortcutAction::NewTabProfile2:
_NewTabWithProfileHandlers(2);
return true;
case ShortcutAction::NewTabProfile3:
_NewTabWithProfileHandlers(3);
return true;
case ShortcutAction::NewTabProfile4:
_NewTabWithProfileHandlers(4);
return true;
case ShortcutAction::NewTabProfile5:
_NewTabWithProfileHandlers(5);
return true;
case ShortcutAction::NewTabProfile6:
_NewTabWithProfileHandlers(6);
return true;
case ShortcutAction::NewTabProfile7:
_NewTabWithProfileHandlers(7);
return true;
case ShortcutAction::NewTabProfile8:
_NewTabWithProfileHandlers(8);
return true;
case ShortcutAction::NewTabProfile9:
_NewTabWithProfileHandlers(9);
return true;
case ShortcutAction::NewWindow:
_NewWindowHandlers();
return true;
case ShortcutAction::CloseWindow:
_CloseWindowHandlers();
return true;
case ShortcutAction::CloseTab:
_CloseTabHandlers();
return true;
case ShortcutAction::ScrollUp:
_ScrollUpHandlers();
return true;
case ShortcutAction::ScrollDown:
_ScrollDownHandlers();
return true;
case ShortcutAction::ScrollUpPage:
_ScrollUpPageHandlers();
return true;
case ShortcutAction::ScrollDownPage:
_ScrollDownPageHandlers();
return true;
case ShortcutAction::NextTab:
_NextTabHandlers();
return true;
case ShortcutAction::PrevTab:
_PrevTabHandlers();
return true;
case ShortcutAction::SwitchToTab0:
_SwitchToTabHandlers(0);
return true;
case ShortcutAction::SwitchToTab1:
_SwitchToTabHandlers(1);
return true;
case ShortcutAction::SwitchToTab2:
_SwitchToTabHandlers(2);
return true;
case ShortcutAction::SwitchToTab3:
_SwitchToTabHandlers(3);
return true;
case ShortcutAction::SwitchToTab4:
_SwitchToTabHandlers(4);
return true;
case ShortcutAction::SwitchToTab5:
_SwitchToTabHandlers(5);
return true;
case ShortcutAction::SwitchToTab6:
_SwitchToTabHandlers(6);
return true;
case ShortcutAction::SwitchToTab7:
_SwitchToTabHandlers(7);
return true;
case ShortcutAction::SwitchToTab8:
_SwitchToTabHandlers(8);
return true;
case ShortcutAction::SwitchToTab9:
_SwitchToTabHandlers(9);
return true;
}
return false;
}
// -------------------------------- Events ---------------------------------
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppKeyBindings.h"
#include "KeyChordSerialization.h"
#include "AppKeyBindings.g.cpp"
using namespace winrt::Microsoft::Terminal;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
static constexpr std::wstring_view KEYS_KEY{ L"keys" };
static constexpr std::wstring_view COMMAND_KEY{ L"command" };
static constexpr std::wstring_view COPYTEXT_KEY{ L"copy" };
static constexpr std::wstring_view PASTETEXT_KEY{ L"paste" };
static constexpr std::wstring_view NEWTAB_KEY{ L"newTab" };
static constexpr std::wstring_view NEWTABWITHPROFILE0_KEY{ L"newTabProfile0" };
static constexpr std::wstring_view NEWTABWITHPROFILE1_KEY{ L"newTabProfile1" };
static constexpr std::wstring_view NEWTABWITHPROFILE2_KEY{ L"newTabProfile2" };
static constexpr std::wstring_view NEWTABWITHPROFILE3_KEY{ L"newTabProfile3" };
static constexpr std::wstring_view NEWTABWITHPROFILE4_KEY{ L"newTabProfile4" };
static constexpr std::wstring_view NEWTABWITHPROFILE5_KEY{ L"newTabProfile5" };
static constexpr std::wstring_view NEWTABWITHPROFILE6_KEY{ L"newTabProfile6" };
static constexpr std::wstring_view NEWTABWITHPROFILE7_KEY{ L"newTabProfile7" };
static constexpr std::wstring_view NEWTABWITHPROFILE8_KEY{ L"newTabProfile8" };
static constexpr std::wstring_view NEWWINDOW_KEY{ L"newWindow" };
static constexpr std::wstring_view CLOSEWINDOW_KEY{ L"closeWindow" };
static constexpr std::wstring_view CLOSETAB_KEY{ L"closeTab" };
static constexpr std::wstring_view SWITCHTOTAB_KEY{ L"switchToTab" };
static constexpr std::wstring_view NEXTTAB_KEY{ L"nextTab" };
static constexpr std::wstring_view PREVTAB_KEY{ L"prevTab" };
static constexpr std::wstring_view INCREASEFONTSIZE_KEY{ L"increaseFontSize" };
static constexpr std::wstring_view DECREASEFONTSIZE_KEY{ L"decreaseFontSize" };
static constexpr std::wstring_view SCROLLUP_KEY{ L"scrollUp" };
static constexpr std::wstring_view SCROLLDOWN_KEY{ L"scrollDown" };
static constexpr std::wstring_view SCROLLUPPAGE_KEY{ L"scrollUpPage" };
static constexpr std::wstring_view SCROLLDOWNPAGE_KEY{ L"scrollDownPage" };
static constexpr std::wstring_view SWITCHTOTAB0_KEY{ L"switchToTab0" };
static constexpr std::wstring_view SWITCHTOTAB1_KEY{ L"switchToTab1" };
static constexpr std::wstring_view SWITCHTOTAB2_KEY{ L"switchToTab2" };
static constexpr std::wstring_view SWITCHTOTAB3_KEY{ L"switchToTab3" };
static constexpr std::wstring_view SWITCHTOTAB4_KEY{ L"switchToTab4" };
static constexpr std::wstring_view SWITCHTOTAB5_KEY{ L"switchToTab5" };
static constexpr std::wstring_view SWITCHTOTAB6_KEY{ L"switchToTab6" };
static constexpr std::wstring_view SWITCHTOTAB7_KEY{ L"switchToTab7" };
static constexpr std::wstring_view SWITCHTOTAB8_KEY{ L"switchToTab8" };
static constexpr std::wstring_view OPENSETTINGS_KEY{ L"openSettings" };
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
static const std::map<std::wstring_view, ShortcutAction> commandNames {
{ COPYTEXT_KEY, ShortcutAction::CopyText },
{ PASTETEXT_KEY, ShortcutAction::PasteText },
{ NEWTAB_KEY, ShortcutAction::NewTab },
{ NEWTABWITHPROFILE0_KEY, ShortcutAction::NewTabProfile0 },
{ NEWTABWITHPROFILE1_KEY, ShortcutAction::NewTabProfile1 },
{ NEWTABWITHPROFILE2_KEY, ShortcutAction::NewTabProfile2 },
{ NEWTABWITHPROFILE3_KEY, ShortcutAction::NewTabProfile3 },
{ NEWTABWITHPROFILE4_KEY, ShortcutAction::NewTabProfile4 },
{ NEWTABWITHPROFILE5_KEY, ShortcutAction::NewTabProfile5 },
{ NEWTABWITHPROFILE6_KEY, ShortcutAction::NewTabProfile6 },
{ NEWTABWITHPROFILE7_KEY, ShortcutAction::NewTabProfile7 },
{ NEWTABWITHPROFILE8_KEY, ShortcutAction::NewTabProfile8 },
{ NEWWINDOW_KEY, ShortcutAction::NewWindow },
{ CLOSEWINDOW_KEY, ShortcutAction::CloseWindow },
{ CLOSETAB_KEY, ShortcutAction::CloseTab },
{ NEXTTAB_KEY, ShortcutAction::NextTab },
{ PREVTAB_KEY, ShortcutAction::PrevTab },
{ INCREASEFONTSIZE_KEY, ShortcutAction::IncreaseFontSize },
{ DECREASEFONTSIZE_KEY, ShortcutAction::DecreaseFontSize },
{ SCROLLUP_KEY, ShortcutAction::ScrollUp },
{ SCROLLDOWN_KEY, ShortcutAction::ScrollDown },
{ SCROLLUPPAGE_KEY, ShortcutAction::ScrollUpPage },
{ SCROLLDOWNPAGE_KEY, ShortcutAction::ScrollDownPage },
{ SWITCHTOTAB0_KEY, ShortcutAction::SwitchToTab0 },
{ SWITCHTOTAB1_KEY, ShortcutAction::SwitchToTab1 },
{ SWITCHTOTAB2_KEY, ShortcutAction::SwitchToTab2 },
{ SWITCHTOTAB3_KEY, ShortcutAction::SwitchToTab3 },
{ SWITCHTOTAB4_KEY, ShortcutAction::SwitchToTab4 },
{ SWITCHTOTAB5_KEY, ShortcutAction::SwitchToTab5 },
{ SWITCHTOTAB6_KEY, ShortcutAction::SwitchToTab6 },
{ SWITCHTOTAB7_KEY, ShortcutAction::SwitchToTab7 },
{ SWITCHTOTAB8_KEY, ShortcutAction::SwitchToTab8 },
{ OPENSETTINGS_KEY, ShortcutAction::OpenSettings },
};
namespace winrt::TerminalApp::implementation
{
void AppKeyBindings::SetKeyBinding(const TerminalApp::ShortcutAction& action,
const Settings::KeyChord& chord)
{
_keyShortcuts[chord] = action;
}
Microsoft::Terminal::Settings::KeyChord AppKeyBindings::GetKeyBinding(TerminalApp::ShortcutAction const& action)
{
for (auto& kv : _keyShortcuts)
{
if (kv.second == action) return kv.first;
}
return { nullptr };
}
bool AppKeyBindings::TryKeyChord(const Settings::KeyChord& kc)
{
const auto keyIter = _keyShortcuts.find(kc);
if (keyIter != _keyShortcuts.end())
{
const auto action = keyIter->second;
return _DoAction(action);
}
return false;
}
bool AppKeyBindings::_DoAction(ShortcutAction action)
{
switch (action)
{
case ShortcutAction::CopyText:
_CopyTextHandlers();
return true;
case ShortcutAction::PasteText:
_PasteTextHandlers();
return true;
case ShortcutAction::NewTab:
_NewTabHandlers();
return true;
case ShortcutAction::OpenSettings:
_OpenSettingsHandlers();
return true;
case ShortcutAction::NewTabProfile0:
_NewTabWithProfileHandlers(0);
return true;
case ShortcutAction::NewTabProfile1:
_NewTabWithProfileHandlers(1);
return true;
case ShortcutAction::NewTabProfile2:
_NewTabWithProfileHandlers(2);
return true;
case ShortcutAction::NewTabProfile3:
_NewTabWithProfileHandlers(3);
return true;
case ShortcutAction::NewTabProfile4:
_NewTabWithProfileHandlers(4);
return true;
case ShortcutAction::NewTabProfile5:
_NewTabWithProfileHandlers(5);
return true;
case ShortcutAction::NewTabProfile6:
_NewTabWithProfileHandlers(6);
return true;
case ShortcutAction::NewTabProfile7:
_NewTabWithProfileHandlers(7);
return true;
case ShortcutAction::NewTabProfile8:
_NewTabWithProfileHandlers(8);
return true;
case ShortcutAction::NewWindow:
_NewWindowHandlers();
return true;
case ShortcutAction::CloseWindow:
_CloseWindowHandlers();
return true;
case ShortcutAction::CloseTab:
_CloseTabHandlers();
return true;
case ShortcutAction::ScrollUp:
_ScrollUpHandlers();
return true;
case ShortcutAction::ScrollDown:
_ScrollDownHandlers();
return true;
case ShortcutAction::ScrollUpPage:
_ScrollUpPageHandlers();
return true;
case ShortcutAction::ScrollDownPage:
_ScrollDownPageHandlers();
return true;
case ShortcutAction::NextTab:
_NextTabHandlers();
return true;
case ShortcutAction::PrevTab:
_PrevTabHandlers();
return true;
case ShortcutAction::SwitchToTab0:
_SwitchToTabHandlers(0);
return true;
case ShortcutAction::SwitchToTab1:
_SwitchToTabHandlers(1);
return true;
case ShortcutAction::SwitchToTab2:
_SwitchToTabHandlers(2);
return true;
case ShortcutAction::SwitchToTab3:
_SwitchToTabHandlers(3);
return true;
case ShortcutAction::SwitchToTab4:
_SwitchToTabHandlers(4);
return true;
case ShortcutAction::SwitchToTab5:
_SwitchToTabHandlers(5);
return true;
case ShortcutAction::SwitchToTab6:
_SwitchToTabHandlers(6);
return true;
case ShortcutAction::SwitchToTab7:
_SwitchToTabHandlers(7);
return true;
case ShortcutAction::SwitchToTab8:
_SwitchToTabHandlers(8);
return true;
default:
return false;
}
return false;
}
// -------------------------------- Events ---------------------------------
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
// Method Description:
// - Deserialize an AppKeyBindings from the key mappings that are in the
// array `json`. The json array should contain an array of objects with
// both a `command` string and a `keys` array, where `command` is one of
// the names listed in `commandNames`, and `keys` is an array of
// keypresses. Currently, the array should contain a single string, which
// can be deserialized into a KeyChord.
// Arguments:
// - json: and array of JsonObject's to deserialize into our _keyShortcuts mapping.
// Return Value:
// - the newly constructed AppKeyBindings object.
TerminalApp::AppKeyBindings AppKeyBindings::FromJson(const JsonArray& json)
{
TerminalApp::AppKeyBindings newBindings{};
for (const auto& value : json)
{
if (value.ValueType() == JsonValueType::Object)
{
JsonObject obj = value.GetObjectW();
if (obj.HasKey(COMMAND_KEY) && obj.HasKey(KEYS_KEY))
{
const auto commandString = obj.GetNamedString(COMMAND_KEY);
const auto keys = obj.GetNamedArray(KEYS_KEY);
if (keys.Size() != 1)
{
continue;
}
const auto keyChordString = keys.GetAt(0).GetString();
ShortcutAction action;
// Try matching the command to one we have
auto found = commandNames.find(commandString);
if (found != commandNames.end())
{
action = found->second;
}
else
{
continue;
}
// Try parsing the chord
try
{
auto chord = KeyChordSerialization::FromString(keyChordString);
newBindings.SetKeyBinding(action, chord);
}
catch (...)
{
continue;
}
}
}
}
return newBindings;
}
// Function Description:
// - Small helper to insert a given KeyChord, ShortcutAction pair into the
// given json array
// Arguments:
// - bindingsArray: The JsonArray to insert the object into.
// - chord: The KeyChord to serailize and place in the json array
// - actionName: the name of the ShortcutAction to use with this KeyChord
static void _AddShortcutToJsonArray(const JsonArray& bindingsArray,
const Settings::KeyChord& chord,
const std::wstring_view& actionName)
{
const auto keyString = KeyChordSerialization::ToString(chord);
if (keyString == L"")
{
return;
}
winrt::Windows::Data::Json::JsonObject jsonObject;
winrt::Windows::Data::Json::JsonArray keysArray;
keysArray.Append(JsonValue::CreateStringValue(keyString));
jsonObject.Insert(KEYS_KEY, keysArray);
jsonObject.Insert(COMMAND_KEY, JsonValue::CreateStringValue(actionName));
bindingsArray.Append(jsonObject);
}
// Method Description:
// - Serialize this AppKeyBindings to a json array of objects. Each object
// in the array represents a single keybinding, mapping a KeyChord to a
// ShortcutAction.
// Return Value:
// - a JsonArray which is an equivalent serialization of this object.
Windows::Data::Json::JsonArray AppKeyBindings::ToJson()
{
winrt::Windows::Data::Json::JsonArray bindingsArray;
// Iterate over all the possible actions in the names list, and see if
// it has a binding.
for (auto& actionName : commandNames)
{
const auto searchedForName = actionName.first;
const auto searchedForAction = actionName.second;
for (const auto& kv : _keyShortcuts)
{
const auto chord = kv.first;
const auto command = kv.second;
if (command == searchedForAction)
{
_AddShortcutToJsonArray(bindingsArray, chord, searchedForName);
}
}
}
return bindingsArray;
}
// Method Description:
// - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML
// Return Value:
// - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used.
Windows::System::VirtualKeyModifiers AppKeyBindings::ConvertVKModifiers(Settings::KeyModifiers modifiers)
{
Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None;
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
{
keyModifiers |= Windows::System::VirtualKeyModifiers::Control;
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
{
keyModifiers |= Windows::System::VirtualKeyModifiers::Shift;
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
{
// note: Menu is the Alt VK_MENU
keyModifiers |= Windows::System::VirtualKeyModifiers::Menu;
}
return keyModifiers;
}
// Method Description:
// - Handles the special case of providing a text override for the UI shortcut due to VK_OEM_COMMA issue.
// Looks at the flags from the KeyChord modifiers and provides a concatenated string value of all
// in the same order that XAML would put them as well.
// Return Value:
// - a WinRT hstring representation of the key modifiers for the shortcut
//NOTE: This needs to be localized with https://github.com/microsoft/terminal/issues/794 if XAML framework issue not resolved before then
winrt::hstring AppKeyBindings::FormatOverrideShortcutText(Settings::KeyModifiers modifiers)
{
std::wstring buffer{ L"" };
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
{
buffer += L"Ctrl+";
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
{
buffer += L"Shift+";
}
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
{
buffer += L"Alt+";
}
return winrt::hstring{ buffer };
}
}

View File

@@ -32,8 +32,14 @@ namespace winrt::TerminalApp::implementation
{
AppKeyBindings() = default;
static TerminalApp::AppKeyBindings FromJson(Windows::Data::Json::JsonArray const& json);
Windows::Data::Json::JsonArray ToJson();
static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
static winrt::hstring FormatOverrideShortcutText(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc);
void SetKeyBinding(TerminalApp::ShortcutAction const& action, winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
Microsoft::Terminal::Settings::KeyChord GetKeyBinding(TerminalApp::ShortcutAction const& action);
DECLARE_EVENT(CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
DECLARE_EVENT(PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);

View File

@@ -17,7 +17,6 @@ namespace TerminalApp
NewTabProfile6,
NewTabProfile7,
NewTabProfile8,
NewTabProfile9,
NewWindow,
CloseWindow,
CloseTab,
@@ -32,7 +31,6 @@ namespace TerminalApp
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
SwitchToTab9,
IncreaseFontSize,
DecreaseFontSize,
ScrollUp,
@@ -65,7 +63,11 @@ namespace TerminalApp
{
AppKeyBindings();
Windows.Data.Json.JsonArray ToJson();
static AppKeyBindings FromJson(Windows.Data.Json.JsonArray json);
void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);
Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action);
event CopyTextEventArgs CopyText;
event PasteTextEventArgs PasteText;

View File

@@ -12,6 +12,15 @@ using namespace winrt::Microsoft::Terminal::Settings;
using namespace ::TerminalApp;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
using namespace Microsoft::Console;
// {2bde4a90-d05f-401c-9492-e40884ead1d8}
// uuidv5 properties: name format is UTF-16LE bytes
static constexpr GUID TERMINAL_PROFILE_NAMESPACE_GUID =
{ 0x2bde4a90, 0xd05f, 0x401c, { 0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8 } };
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" };
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
CascadiaSettings::CascadiaSettings() :
_globals{},
@@ -32,8 +41,8 @@ ColorScheme _CreateCampbellScheme()
RGB(12, 12, 12) };
auto& campbellTable = campbellScheme.GetTable();
auto campbellSpan = gsl::span<COLORREF>(&campbellTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
Microsoft::Console::Utils::InitializeCampbellColorTable(campbellSpan);
Microsoft::Console::Utils::SetColorTableAlpha(campbellSpan, 0xff);
Utils::InitializeCampbellColorTable(campbellSpan);
Utils::SetColorTableAlpha(campbellSpan, 0xff);
return campbellScheme;
}
@@ -64,7 +73,7 @@ ColorScheme _CreateOneHalfDarkScheme()
oneHalfDarkTable[13] = RGB(198, 120, 221); // magenta
oneHalfDarkTable[14] = RGB( 86, 182, 194); // cyan
oneHalfDarkTable[15] = RGB(220, 223, 228); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
return oneHalfDarkScheme;
}
@@ -94,7 +103,7 @@ ColorScheme _CreateOneHalfLightScheme()
oneHalfLightTable[13] = RGB(197, 119, 221); // magenta
oneHalfLightTable[14] = RGB( 86, 181, 193); // cyan
oneHalfLightTable[15] = RGB(255, 255, 255); // white
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
return oneHalfLightScheme;
}
@@ -122,7 +131,7 @@ ColorScheme _CreateSolarizedDarkScheme()
solarizedDarkTable[13] = RGB(108, 113, 196);
solarizedDarkTable[14] = RGB(147, 161, 161);
solarizedDarkTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
return solarizedDarkScheme;
}
@@ -150,7 +159,7 @@ ColorScheme _CreateSolarizedLightScheme()
solarizedLightTable[13] = RGB(108, 113, 196);
solarizedLightTable[14] = RGB(147, 161, 161);
solarizedLightTable[15] = RGB(253, 246, 227);
Microsoft::Console::Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
return solarizedLightScheme;
}
@@ -174,48 +183,50 @@ void CascadiaSettings::_CreateDefaultSchemes()
// Method Description:
// - Create a set of profiles to use as the "default" profiles when initializing
// the terminal. Currently, we create two profiles: one for cmd.exe, and
// one for powershell.
// Arguments:
// - <none>
// Return Value:
// - <none>
// the terminal. Currently, we create two or three profiles:
// * one for cmd.exe
// * one for powershell.exe (inbox Windows Powershell)
// * if Powershell Core (pwsh.exe) is installed, we'll create another for
// Powershell Core.
void CascadiaSettings::_CreateDefaultProfiles()
{
Profile cmdProfile{};
auto cmdProfile{ _CreateDefaultProfile(L"cmd") };
cmdProfile.SetFontFace(L"Consolas");
cmdProfile.SetCommandline(L"cmd.exe");
cmdProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
cmdProfile.SetColorScheme({ L"Campbell" });
cmdProfile.SetAcrylicOpacity(0.75);
cmdProfile.SetUseAcrylic(true);
cmdProfile.SetName(L"cmd");
Profile powershellProfile{};
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
std::wstring psCmdline = L"powershell.exe";
std::filesystem::path psCoreCmdline{};
if (_IsPowerShellCoreInstalled(L"%ProgramFiles%", psCoreCmdline))
{
psCmdline = psCoreCmdline;
}
else if (_IsPowerShellCoreInstalled(L"%ProgramFiles(x86)%", psCoreCmdline))
{
psCmdline = psCoreCmdline;
}
powershellProfile.SetFontFace(L"Courier New");
powershellProfile.SetCommandline(psCmdline);
auto powershellProfile{ _CreateDefaultProfile(L"Windows PowerShell") };
powershellProfile.SetCommandline(L"powershell.exe");
powershellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
powershellProfile.SetColorScheme({ L"Campbell" });
powershellProfile.SetDefaultBackground(RGB(1, 36, 86));
powershellProfile.SetDefaultBackground(POWERSHELL_BLUE);
powershellProfile.SetUseAcrylic(false);
powershellProfile.SetName(L"PowerShell");
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
std::filesystem::path psCoreCmdline{};
if (_isPowerShellCoreInstalled(psCoreCmdline))
{
auto pwshProfile{ _CreateDefaultProfile(L"PowerShell Core") };
pwshProfile.SetCommandline(psCoreCmdline);
pwshProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
pwshProfile.SetColorScheme({ L"Campbell" });
// If powershell core is installed, we'll use that as the default.
// Otherwise, we'll use normal Windows Powershell as the default.
_profiles.emplace_back(pwshProfile);
_globals.SetDefaultProfile(pwshProfile.GetGuid());
}
else
{
_globals.SetDefaultProfile(powershellProfile.GetGuid());
}
_profiles.emplace_back(powershellProfile);
_profiles.emplace_back(cmdProfile);
_globals.SetDefaultProfile(powershellProfile.GetGuid());
}
// Method Description:
@@ -278,9 +289,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile8,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile9,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('0') });
keyBindings.SetKeyBinding(ShortcutAction::ScrollUp,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
@@ -321,9 +329,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab9,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('0') });
}
// Method Description:
@@ -332,7 +337,7 @@ void CascadiaSettings::_CreateDefaultKeybindings()
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_CreateDefaults()
void CascadiaSettings::CreateDefaults()
{
_CreateDefaultProfiles();
_CreateDefaultSchemes();
@@ -421,14 +426,28 @@ GlobalAppSettings& CascadiaSettings::GlobalSettings()
return _globals;
}
// Function Description:
// - Returns true if the user has installed PowerShell Core. This will check
// both %ProgramFiles% and %ProgramFiles(x86)%, and will return true if
// powershell core was installed in either location.
// Arguments:
// - A ref of a path that receives the result of PowerShell Core pwsh.exe full path.
// Return Value:
// - true iff powershell core (pwsh.exe) is present.
bool CascadiaSettings::_isPowerShellCoreInstalled(std::filesystem::path& cmdline)
{
return _isPowerShellCoreInstalledInPath(L"%ProgramFiles%", cmdline) ||
_isPowerShellCoreInstalledInPath(L"%ProgramFiles(x86)%", cmdline);
}
// Function Description:
// - Returns true if the user has installed PowerShell Core.
// Arguments:
// - A string that contains an environment-variable string in the form: %variableName%.
// - A ref of a path that receives the result of PowerShell Core pwsh.exe full path.
// Return Value:
// - true or false.
bool CascadiaSettings::_IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline)
// - true iff powershell core (pwsh.exe) is present in the given path
bool CascadiaSettings::_isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline)
{
std::filesystem::path psCorePath = ExpandEnvironmentVariableString(programFileEnv.data());
psCorePath /= L"PowerShell";
@@ -468,3 +487,26 @@ std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view
result.resize(requiredSize-1);
return result;
}
// Method Description:
// - Helper function for creating a skeleton default profile with a pre-populated
// guid and name.
// Arguments:
// - name: the name of the new profile.
// Return Value:
// - A Profile, ready to be filled in
Profile CascadiaSettings::_CreateDefaultProfile(const std::wstring_view name)
{
auto profileGuid{ Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))) };
Profile newProfile{ profileGuid };
newProfile.SetName(static_cast<std::wstring>(name));
std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH };
iconPath.append(Utils::GuidToString(profileGuid));
iconPath.append(PACKAGED_PROFILE_ICON_EXTENSION);
newProfile.SetIconPath(iconPath);
return newProfile;
}

View File

@@ -50,6 +50,8 @@ public:
static winrt::hstring GetSettingsPath();
const Profile* FindProfile(GUID profileGuid) const noexcept;
void CreateDefaults();
private:
GlobalAppSettings _globals;
std::vector<Profile> _profiles;
@@ -58,7 +60,6 @@ private:
void _CreateDefaultKeybindings();
void _CreateDefaultSchemes();
void _CreateDefaultProfiles();
void _CreateDefaults();
static bool _IsPackaged();
static void _SaveAsPackagedApp(const winrt::hstring content);
@@ -67,6 +68,8 @@ private:
static winrt::hstring _GetPackagedSettingsPath();
static std::optional<winrt::hstring> _LoadAsPackagedApp();
static std::optional<winrt::hstring> _LoadAsUnpackagedApp();
static bool _IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline);
static bool _isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline);
static bool _isPowerShellCoreInstalled(std::filesystem::path& cmdline);
static std::wstring ExpandEnvironmentVariableString(std::wstring_view source);
static Profile _CreateDefaultProfile(const std::wstring_view name);
};

View File

@@ -6,8 +6,6 @@
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include <appmodel.h>
#include <wil/com.h>
#include <wil/filesystem.h>
#include <shlobj.h>
using namespace ::TerminalApp;
@@ -18,12 +16,12 @@ using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
using namespace ::Microsoft::Console;
static const std::wstring FILENAME { L"profiles.json" };
static const std::wstring SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
static constexpr std::wstring_view FILENAME { L"profiles.json" };
static constexpr std::wstring_view SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
static const std::wstring PROFILES_KEY{ L"profiles" };
static const std::wstring KEYBINDINGS_KEY{ L"keybindings" };
static const std::wstring SCHEMES_KEY{ L"schemes" };
static constexpr std::wstring_view PROFILES_KEY{ L"profiles" };
static constexpr std::wstring_view KEYBINDINGS_KEY{ L"keybindings" };
static constexpr std::wstring_view SCHEMES_KEY{ L"schemes" };
// Method Description:
// - Creates a CascadiaSettings from whatever's saved on disk, or instantiates
@@ -47,39 +45,29 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll(const bool saveOnLoa
{
const auto actualData = fileData.value();
JsonValue root{ nullptr };
bool parsedSuccessfully = JsonValue::TryParse(actualData, root);
// TODO:MSFT:20737698 - Display an error if we failed to parse settings
if (parsedSuccessfully)
{
JsonObject obj = root.GetObjectW();
resultPtr = FromJson(obj);
// If Parse fails, it'll throw a hresult_error
JsonObject root = JsonObject::Parse(actualData);
// Update profile only if it has changed.
if (saveOnLoad)
resultPtr = FromJson(root);
// Update profile only if it has changed.
if (saveOnLoad)
{
const JsonObject json = resultPtr->ToJson();
auto serializedSettings = json.Stringify();
if (actualData != serializedSettings)
{
const JsonObject json = resultPtr->ToJson();
auto serializedSettings = json.Stringify();
if (actualData != serializedSettings)
{
resultPtr->SaveAll();
}
resultPtr->SaveAll();
}
}
else
{
// Until 20737698 is done, throw an error, so debugging can trace
// the exception here, instead of later on in unrelated code
THROW_HR(E_INVALIDARG);
}
}
else
{
resultPtr = std::make_unique<CascadiaSettings>();
resultPtr->_CreateDefaults();
resultPtr->CreateDefaults();
// The settings file does not exist. Let's commit one.
// The settings file does not exist. Let's commit one.
resultPtr->SaveAll();
}
@@ -137,6 +125,9 @@ JsonObject CascadiaSettings::ToJson() const
jsonObject.Insert(PROFILES_KEY, profilesArray);
jsonObject.Insert(SCHEMES_KEY, schemesArray);
jsonObject.Insert(KEYBINDINGS_KEY,
_globals.GetKeybindings().ToJson());
return jsonObject;
}
@@ -190,9 +181,18 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::FromJson(JsonObject json)
}
}
// TODO:MSFT:20700157
// Load the keybindings from the file as well
resultPtr->_CreateDefaultKeybindings();
if (json.HasKey(KEYBINDINGS_KEY))
{
const auto keybindingsObj = json.GetNamedArray(KEYBINDINGS_KEY);
auto loadedBindings = AppKeyBindings::FromJson(keybindingsObj);
resultPtr->_globals.SetKeybindings(loadedBindings);
}
else
{
// Create the default keybindings if we couldn't find any keybindings.
resultPtr->_CreateDefaultKeybindings();
}
return resultPtr;
}
@@ -366,7 +366,7 @@ std::optional<winrt::hstring> CascadiaSettings::_LoadAsUnpackagedApp()
// function Description:
// - Returns the full path to the settings file, either within the application
// package, or in it's unpackaged location.
// package, or in its unpackaged location.
// Arguments:
// - <none>
// Return Value:
@@ -378,7 +378,7 @@ winrt::hstring CascadiaSettings::GetSettingsPath()
}
// Function Description:
// - Get the full path to settings file in it's packaged location.
// - Get the full path to settings file in its packaged location.
// Arguments:
// - <none>
// Return Value:

View File

@@ -12,10 +12,10 @@ using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
static const std::wstring NAME_KEY{ L"name" };
static const std::wstring TABLE_KEY{ L"colors" };
static const std::wstring FOREGROUND_KEY{ L"foreground" };
static const std::wstring BACKGROUND_KEY{ L"background" };
static constexpr std::wstring_view NAME_KEY{ L"name" };
static constexpr std::wstring_view TABLE_KEY{ L"colors" };
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
static const std::array<std::wstring, 16> TABLE_COLORS =
{
L"black",

View File

@@ -13,28 +13,27 @@ using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::UI::Xaml;
using namespace ::Microsoft::Console;
static const std::wstring DEFAULTPROFILE_KEY{ L"defaultProfile" };
static const std::wstring ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
static const std::wstring INITIALROWS_KEY{ L"initialRows" };
static const std::wstring INITIALCOLS_KEY{ L"initialCols" };
static const std::wstring SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
static const std::wstring REQUESTED_THEME_KEY{ L"requestedTheme" };
static constexpr std::wstring_view DEFAULTPROFILE_KEY{ L"defaultProfile" };
static constexpr std::wstring_view ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
static constexpr std::wstring_view INITIALROWS_KEY{ L"initialRows" };
static constexpr std::wstring_view INITIALCOLS_KEY{ L"initialCols" };
static constexpr std::wstring_view SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
static constexpr std::wstring_view REQUESTED_THEME_KEY{ L"requestedTheme" };
static constexpr std::wstring_view SHOW_TABS_IN_TITLEBAR_KEY{ L"showTabsInTitlebar" };
static const std::wstring SHOW_TABS_IN_TITLEBAR_KEY{ L"experimental_showTabsInTitlebar" };
static const std::wstring LIGHT_THEME_VALUE{ L"light" };
static const std::wstring DARK_THEME_VALUE{ L"dark" };
static const std::wstring SYSTEM_THEME_VALUE{ L"system" };
static constexpr std::wstring_view LIGHT_THEME_VALUE{ L"light" };
static constexpr std::wstring_view DARK_THEME_VALUE{ L"dark" };
static constexpr std::wstring_view SYSTEM_THEME_VALUE{ L"system" };
GlobalAppSettings::GlobalAppSettings() :
_keybindings{},
_colorSchemes{},
_defaultProfile{},
_alwaysShowTabs{ false },
_alwaysShowTabs{ true },
_initialRows{ DEFAULT_ROWS },
_initialCols{ DEFAULT_COLS },
_showTitleInTitlebar{ true },
_showTabsInTitlebar{ false },
_showTabsInTitlebar{ true },
_requestedTheme{ ElementTheme::Default }
{
@@ -71,6 +70,11 @@ AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept
return _keybindings;
}
void GlobalAppSettings::SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept
{
_keybindings = newBindings;
}
bool GlobalAppSettings::GetAlwaysShowTabs() const noexcept
{
return _alwaysShowTabs;
@@ -96,6 +100,10 @@ ElementTheme GlobalAppSettings::GetRequestedTheme() const noexcept
return _requestedTheme;
}
void GlobalAppSettings::SetRequestedTheme(const ElementTheme requestedTheme) noexcept
{
_requestedTheme = requestedTheme;
}
#pragma region ExperimentalSettings
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
@@ -147,11 +155,11 @@ JsonObject GlobalAppSettings::ToJson() const
jsonObject.Insert(SHOW_TABS_IN_TITLEBAR_KEY,
JsonValue::CreateBooleanValue(_showTabsInTitlebar));
if (_requestedTheme != ElementTheme::Default)
{
jsonObject.Insert(REQUESTED_THEME_KEY,
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
}
jsonObject.Insert(REQUESTED_THEME_KEY,
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
// We'll add the keybindings later in CascadiaSettings, because if we do it
// here, they'll appear before the profiles.
return jsonObject;
}
@@ -227,13 +235,13 @@ ElementTheme GlobalAppSettings::_ParseTheme(const std::wstring& themeString) noe
}
// Method Description:
// - Helper function for converting a CursorStyle to it's corresponding string
// - Helper function for converting a CursorStyle to its corresponding string
// value.
// Arguments:
// - theme: The enum value to convert to a string.
// Return Value:
// - The string value for the given CursorStyle
std::wstring GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
std::wstring_view GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
{
switch (theme)
{

View File

@@ -35,6 +35,7 @@ public:
GUID GetDefaultProfile() const noexcept;
winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
void SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept;
bool GetAlwaysShowTabs() const noexcept;
void SetAlwaysShowTabs(const bool showTabs) noexcept;
@@ -42,6 +43,8 @@ public:
bool GetShowTitleInTitlebar() const noexcept;
void SetShowTitleInTitlebar(const bool showTitleInTitlebar) noexcept;
void SetRequestedTheme(const winrt::Windows::UI::Xaml::ElementTheme requestedTheme) noexcept;
bool GetShowTabsInTitlebar() const noexcept;
void SetShowTabsInTitlebar(const bool showTabsInTitlebar) noexcept;
@@ -69,6 +72,6 @@ private:
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
static std::wstring _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
};

View File

@@ -0,0 +1,249 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "KeyChordSerialization.h"
using namespace winrt::Microsoft::Terminal::Settings;
static constexpr std::wstring_view CTRL_KEY{ L"ctrl" };
static constexpr std::wstring_view SHIFT_KEY{ L"shift" };
static constexpr std::wstring_view ALT_KEY{ L"alt" };
static constexpr int MAX_CHORD_PARTS = 4;
static const std::unordered_map<int32_t, std::wstring_view> vkeyNamePairs {
{ VK_BACK , L"backspace"},
{ VK_TAB , L"tab"},
{ VK_RETURN , L"enter" },
{ VK_ESCAPE , L"esc" },
{ VK_SPACE , L"space" },
{ VK_PRIOR , L"pgup" },
{ VK_NEXT , L"pgdn" },
{ VK_END , L"end" },
{ VK_HOME , L"home" },
{ VK_LEFT , L"left" },
{ VK_UP , L"up" },
{ VK_RIGHT , L"right" },
{ VK_DOWN , L"down" },
{ VK_INSERT , L"insert" },
{ VK_DELETE , L"delete" },
{ VK_NUMPAD0 , L"numpad_0" },
{ VK_NUMPAD1 , L"numpad_1" },
{ VK_NUMPAD2 , L"numpad_2" },
{ VK_NUMPAD3 , L"numpad_3" },
{ VK_NUMPAD4 , L"numpad_4" },
{ VK_NUMPAD5 , L"numpad_5" },
{ VK_NUMPAD6 , L"numpad_6" },
{ VK_NUMPAD7 , L"numpad_7" },
{ VK_NUMPAD8 , L"numpad_8" },
{ VK_NUMPAD9 , L"numpad_9" },
{ VK_MULTIPLY , L"numpad_multiply" },
{ VK_ADD , L"numpad_plus" },
{ VK_SUBTRACT , L"numpad_minus" },
{ VK_DECIMAL , L"numpad_period" },
{ VK_DIVIDE , L"numpad_divide" },
{ VK_F1 , L"f1" },
{ VK_F2 , L"f2" },
{ VK_F3 , L"f3" },
{ VK_F4 , L"f4" },
{ VK_F5 , L"f5" },
{ VK_F6 , L"f6" },
{ VK_F7 , L"f7" },
{ VK_F8 , L"f8" },
{ VK_F9 , L"f9" },
{ VK_F10 , L"f10" },
{ VK_F11 , L"f11" },
{ VK_F12 , L"f12" },
{ VK_F13 , L"f13" },
{ VK_F14 , L"f14" },
{ VK_F15 , L"f15" },
{ VK_F16 , L"f16" },
{ VK_F17 , L"f17" },
{ VK_F18 , L"f18" },
{ VK_F19 , L"f19" },
{ VK_F20 , L"f20" },
{ VK_F21 , L"f21" },
{ VK_F22 , L"f22" },
{ VK_F23 , L"f23" },
{ VK_F24 , L"f24" },
{ VK_OEM_PLUS , L"plus" },
{ VK_OEM_COMMA , L"," },
{ VK_OEM_MINUS , L"-" },
{ VK_OEM_PERIOD , L"." }
// TODO:
// These all look like they'd be good keybindings, but change based on keyboard
// layout. How do we deal with that?
// #define VK_OEM_NEC_EQUAL 0x92 // '=' key on numpad
// #define VK_OEM_1 0xBA // ';:' for US
// #define VK_OEM_2 0xBF // '/?' for US
// #define VK_OEM_3 0xC0 // '`~' for US
// #define VK_OEM_4 0xDB // '[{' for US
// #define VK_OEM_5 0xDC // '\|' for US
// #define VK_OEM_6 0xDD // ']}' for US
// #define VK_OEM_7 0xDE // ''"' for US
};
// Function Description:
// - Deserializes the given string into a new KeyChord instance. If this
// fails to translate the string into a keychord, it will throw a
// hresult_invalid_argument exception.
// - The string should fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-zA-Z.
// Arguments:
// - hstr: the string to parse into a keychord.
// Return Value:
// - a newly constructed KeyChord
winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
{
std::wstring wstr{ hstr };
// Split the string on '+'
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(wstr);
while(std::getline(wss, temp, L'+'))
{
parts.push_back(temp);
// If we have > 4, something's wrong.
if (parts.size() > MAX_CHORD_PARTS)
{
throw winrt::hresult_invalid_argument();
}
}
KeyModifiers modifiers = KeyModifiers::None;
int32_t vkey = 0;
// Look for ctrl, shift, alt. Anything else might be a key
for (const auto& part : parts)
{
std::wstring lowercase = part;
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), std::towlower);
if (lowercase == CTRL_KEY)
{
modifiers |= KeyModifiers::Ctrl;
}
else if (lowercase == ALT_KEY)
{
modifiers |= KeyModifiers::Alt;
}
else if (lowercase == SHIFT_KEY)
{
modifiers |= KeyModifiers::Shift;
}
else
{
bool foundKey = false;
// For potential keys, look through the pairs of strings and vkeys
if (part.size() == 1)
{
const wchar_t wch = part.at(0);
// Quick lookup: ranges of vkeys that correlate directly to a key.
if (wch >= L'0' && wch <= L'9')
{
vkey = static_cast<int32_t>(wch);
foundKey = true;
}
else if (wch >= L'a' && wch <= L'z')
{
// subtract 0x20 to shift to uppercase
vkey = static_cast<int32_t>(wch - 0x20);
foundKey = true;
}
else if (wch >= L'A' && wch <= L'Z')
{
vkey = static_cast<int32_t>(wch);
foundKey = true;
}
}
// If we didn't find the key with a quick lookup, search the
// table to see if we have a matching name.
if (!foundKey)
{
for (const auto& pair : vkeyNamePairs)
{
if (pair.second == part)
{
vkey = pair.first;
foundKey = true;
break;
}
}
}
// If we weren't able to find a match, throw an exception.
if (!foundKey)
{
throw winrt::hresult_invalid_argument();
}
}
}
return winrt::Microsoft::Terminal::Settings::KeyChord{ modifiers, vkey };
}
// Function Description:
// - Serialize this keychord into a string represenation.
// - The string will fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-z.
// Return Value:
// - a string which is an equivalent serialization of this object.
winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
{
bool serializedSuccessfully = false;
const auto modifiers = chord.Modifiers();
const auto vkey = chord.Vkey();
std::wstring buffer{ L"" };
// Add modifiers
if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl))
{
buffer += CTRL_KEY;
buffer += L"+";
}
if (WI_IsFlagSet(modifiers, KeyModifiers::Alt))
{
buffer += ALT_KEY;
buffer += L"+";
}
if (WI_IsFlagSet(modifiers, KeyModifiers::Shift))
{
buffer += SHIFT_KEY;
buffer += L"+";
}
// Quick lookup: ranges of vkeys that correlate directly to a key.
if (vkey >= L'0' && vkey <= L'9')
{
buffer += std::wstring(1, static_cast<wchar_t>(vkey));
serializedSuccessfully = true;
}
else if (vkey >= L'A' && vkey <= L'Z')
{
// add 0x20 to shift to lowercase
buffer += std::wstring(1, static_cast<wchar_t>(vkey + 0x20));
serializedSuccessfully = true;
}
else
{
if (vkeyNamePairs.find(vkey) != vkeyNamePairs.end())
{
buffer += vkeyNamePairs.at(vkey);
serializedSuccessfully = true;
}
}
if (!serializedSuccessfully)
{
buffer = L"";
}
return winrt::hstring{ buffer };
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <winrt/Microsoft.Terminal.Settings.h>
class KeyChordSerialization final
{
public:
static winrt::Microsoft::Terminal::Settings::KeyChord FromString(const winrt::hstring& str);
static winrt::hstring ToString(const winrt::Microsoft::Terminal::Settings::KeyChord& chord);
};

View File

@@ -12,44 +12,58 @@ using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
using namespace ::Microsoft::Console;
static constexpr std::wstring_view NAME_KEY{ L"name" };
static constexpr std::wstring_view GUID_KEY{ L"guid" };
static constexpr std::wstring_view COLORSCHEME_KEY{ L"colorScheme" };
static constexpr std::wstring_view COLORSCHEME_KEY_OLD{ L"colorscheme" };
static const std::wstring NAME_KEY{ L"name" };
static const std::wstring GUID_KEY{ L"guid" };
static const std::wstring COLORSCHEME_KEY{ L"colorscheme" };
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
static constexpr std::wstring_view COLORTABLE_KEY{ L"colorTable" };
static constexpr std::wstring_view HISTORYSIZE_KEY{ L"historySize" };
static constexpr std::wstring_view SNAPONINPUT_KEY{ L"snapOnInput" };
static constexpr std::wstring_view CURSORCOLOR_KEY{ L"cursorColor" };
static constexpr std::wstring_view CURSORSHAPE_KEY{ L"cursorShape" };
static constexpr std::wstring_view CURSORHEIGHT_KEY{ L"cursorHeight" };
static const std::wstring FOREGROUND_KEY{ L"foreground" };
static const std::wstring BACKGROUND_KEY{ L"background" };
static const std::wstring COLORTABLE_KEY{ L"colorTable" };
static const std::wstring HISTORYSIZE_KEY{ L"historySize" };
static const std::wstring SNAPONINPUT_KEY{ L"snapOnInput" };
static const std::wstring CURSORCOLOR_KEY{ L"cursorColor" };
static const std::wstring CURSORSHAPE_KEY{ L"cursorShape" };
static const std::wstring CURSORHEIGHT_KEY{ L"cursorHeight" };
static const std::wstring COMMANDLINE_KEY{ L"commandline" };
static const std::wstring FONTFACE_KEY{ L"fontFace" };
static const std::wstring FONTSIZE_KEY{ L"fontSize" };
static const std::wstring ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
static const std::wstring USEACRYLIC_KEY{ L"useAcrylic" };
static const std::wstring SCROLLBARSTATE_KEY{ L"scrollbarState" };
static const std::wstring CLOSEONEXIT_KEY{ L"closeOnExit" };
static const std::wstring PADDING_KEY{ L"padding" };
static const std::wstring STARTINGDIRECTORY_KEY{ L"startingDirectory" };
static const std::wstring ICON_KEY{ L"icon" };
static constexpr std::wstring_view COMMANDLINE_KEY{ L"commandline" };
static constexpr std::wstring_view FONTFACE_KEY{ L"fontFace" };
static constexpr std::wstring_view FONTSIZE_KEY{ L"fontSize" };
static constexpr std::wstring_view ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
static constexpr std::wstring_view USEACRYLIC_KEY{ L"useAcrylic" };
static constexpr std::wstring_view SCROLLBARSTATE_KEY{ L"scrollbarState" };
static constexpr std::wstring_view CLOSEONEXIT_KEY{ L"closeOnExit" };
static constexpr std::wstring_view PADDING_KEY{ L"padding" };
static constexpr std::wstring_view STARTINGDIRECTORY_KEY{ L"startingDirectory" };
static constexpr std::wstring_view ICON_KEY{ L"icon" };
static constexpr std::wstring_view BACKGROUNDIMAGE_KEY{ L"backgroundImage" };
static constexpr std::wstring_view BACKGROUNDIMAGEOPACITY_KEY{ L"backgroundImageOpacity" };
static constexpr std::wstring_view BACKGROUNDIMAGESTRETCHMODE_KEY{ L"backgroundImageStretchMode" };
// Possible values for Scrollbar state
static const std::wstring ALWAYS_VISIBLE{ L"visible" };
static const std::wstring ALWAYS_HIDE{ L"hidden" };
static constexpr std::wstring_view ALWAYS_VISIBLE{ L"visible" };
static constexpr std::wstring_view ALWAYS_HIDE{ L"hidden" };
// Possible values for Cursor Shape
static const std::wstring CURSORSHAPE_VINTAGE{ L"vintage" };
static const std::wstring CURSORSHAPE_BAR{ L"bar" };
static const std::wstring CURSORSHAPE_UNDERSCORE{ L"underscore" };
static const std::wstring CURSORSHAPE_FILLEDBOX{ L"filledBox" };
static const std::wstring CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
static constexpr std::wstring_view CURSORSHAPE_VINTAGE{ L"vintage" };
static constexpr std::wstring_view CURSORSHAPE_BAR{ L"bar" };
static constexpr std::wstring_view CURSORSHAPE_UNDERSCORE{ L"underscore" };
static constexpr std::wstring_view CURSORSHAPE_FILLEDBOX{ L"filledBox" };
static constexpr std::wstring_view CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
// Possible values for Image Stretch Mode
static constexpr std::wstring_view IMAGESTRETCHMODE_NONE{ L"none" };
static constexpr std::wstring_view IMAGESTRETCHMODE_FILL{ L"fill" };
static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORM{ L"uniform" };
static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORMTOFILL{ L"uniformToFill" };
Profile::Profile() :
_guid{},
Profile(Utils::CreateGuid())
{
}
Profile::Profile(const winrt::guid& guid):
_guid(guid),
_name{ L"Default" },
_schemeName{},
@@ -71,9 +85,11 @@ Profile::Profile() :
_scrollbarState{ },
_closeOnExit{ true },
_padding{ DEFAULT_PADDING },
_icon{ }
_icon{ },
_backgroundImage{ },
_backgroundImageOpacity{ },
_backgroundImageStretchMode{ }
{
UuidCreate(&_guid);
}
Profile::~Profile()
@@ -109,7 +125,7 @@ const ColorScheme* _FindScheme(const std::vector<ColorScheme>& schemes,
// Method Description:
// - Create a TerminalSettings from this object. Apply our settings, as well as
// any colors from our colorscheme, if we have one.
// any colors from our color scheme, if we have one.
// Arguments:
// - schemes: a list of schemes to look for our color scheme in, if we have one.
// Return Value:
@@ -169,6 +185,21 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
terminalSettings.ScrollState(result);
}
if (_backgroundImage)
{
terminalSettings.BackgroundImage(_backgroundImage.value());
}
if (_backgroundImageOpacity)
{
terminalSettings.BackgroundImageOpacity(_backgroundImageOpacity.value());
}
if (_backgroundImageStretchMode)
{
terminalSettings.BackgroundImageStretchMode(_backgroundImageStretchMode.value());
}
return terminalSettings;
}
@@ -270,6 +301,25 @@ JsonObject Profile::ToJson() const
jsonObject.Insert(ICON_KEY, icon);
}
if (_backgroundImage)
{
const auto backgroundImage = JsonValue::CreateStringValue(_backgroundImage.value());
jsonObject.Insert(BACKGROUNDIMAGE_KEY, backgroundImage);
}
if (_backgroundImageOpacity)
{
const auto opacity = JsonValue::CreateNumberValue(_backgroundImageOpacity.value());
jsonObject.Insert(BACKGROUNDIMAGEOPACITY_KEY, opacity);
}
if (_backgroundImageStretchMode)
{
const auto imageStretchMode = JsonValue::CreateStringValue(
SerializeImageStretchMode(_backgroundImageStretchMode.value()));
jsonObject.Insert(BACKGROUNDIMAGESTRETCHMODE_KEY, imageStretchMode);
}
return jsonObject;
}
@@ -315,23 +365,25 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json)
{
result._schemeName = json.GetNamedString(COLORSCHEME_KEY);
}
else
else if (json.HasKey(COLORSCHEME_KEY_OLD))
{
if (json.HasKey(COLORTABLE_KEY))
// TODO: deprecate old settings key
result._schemeName = json.GetNamedString(COLORSCHEME_KEY_OLD);
}
else if (json.HasKey(COLORTABLE_KEY))
{
const auto table = json.GetNamedArray(COLORTABLE_KEY);
int i = 0;
for (auto v : table)
{
const auto table = json.GetNamedArray(COLORTABLE_KEY);
int i = 0;
for (auto v : table)
if (v.ValueType() == JsonValueType::String)
{
if (v.ValueType() == JsonValueType::String)
{
const auto str = v.GetString();
// TODO: MSFT:20737698 - if this fails, display an approriate error
const auto color = Utils::ColorFromHexString(str.c_str());
result._colorTable[i] = color;
}
i++;
const auto str = v.GetString();
// TODO: MSFT:20737698 - if this fails, display an approriate error
const auto color = Utils::ColorFromHexString(str.c_str());
result._colorTable[i] = color;
}
i++;
}
}
if (json.HasKey(HISTORYSIZE_KEY))
@@ -401,6 +453,19 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json)
{
result._icon = json.GetNamedString(ICON_KEY);
}
if (json.HasKey(BACKGROUNDIMAGE_KEY))
{
result._backgroundImage = json.GetNamedString(BACKGROUNDIMAGE_KEY);
}
if (json.HasKey(BACKGROUNDIMAGEOPACITY_KEY))
{
result._backgroundImageOpacity = json.GetNamedNumber(BACKGROUNDIMAGEOPACITY_KEY);
}
if (json.HasKey(BACKGROUNDIMAGESTRETCHMODE_KEY))
{
const auto stretchMode = json.GetNamedString(BACKGROUNDIMAGESTRETCHMODE_KEY);
result._backgroundImageStretchMode = ParseImageStretchMode(stretchMode.c_str());
}
return result;
}
@@ -457,6 +522,15 @@ bool Profile::HasIcon() const noexcept
return _icon.has_value();
}
// Method Description:
// - Sets this profile's icon path.
// Arguments:
// - path: the path
void Profile::SetIconPath(std::wstring_view path) noexcept
{
_icon.emplace(path);
}
// Method Description:
// - Returns this profile's icon path, if one is set. Otherwise returns the empty string.
// Return Value:
@@ -538,6 +612,58 @@ ScrollbarState Profile::ParseScrollbarState(const std::wstring& scrollbarState)
}
}
// Method Description:
// - Helper function for converting a user-specified image stretch mode
// to the appropriate enum value
// Arguments:
// - The value from the profiles.json file
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
winrt::Windows::UI::Xaml::Media::Stretch Profile::ParseImageStretchMode(const std::wstring& imageStretchMode)
{
if (imageStretchMode == IMAGESTRETCHMODE_NONE)
{
return winrt::Windows::UI::Xaml::Media::Stretch::None;
}
else if (imageStretchMode == IMAGESTRETCHMODE_FILL)
{
return winrt::Windows::UI::Xaml::Media::Stretch::Fill;
}
else if (imageStretchMode == IMAGESTRETCHMODE_UNIFORM)
{
return winrt::Windows::UI::Xaml::Media::Stretch::Uniform;
}
else // Fall through to default behavior
{
return winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill;
}
}
// Method Description:
// - Helper function for converting an ImageStretchMode to the
// correct string value.
// Arguments:
// - imageStretchMode: The enum value to convert to a string.
// Return Value:
// - The string value for the given ImageStretchMode
std::wstring_view Profile::SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode)
{
switch (imageStretchMode)
{
case winrt::Windows::UI::Xaml::Media::Stretch::None:
return IMAGESTRETCHMODE_NONE;
case winrt::Windows::UI::Xaml::Media::Stretch::Fill:
return IMAGESTRETCHMODE_FILL;
case winrt::Windows::UI::Xaml::Media::Stretch::Uniform:
return IMAGESTRETCHMODE_UNIFORM;
default:
case winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill:
return IMAGESTRETCHMODE_UNIFORMTOFILL;
}
}
// Method Description:
// - Helper function for converting a user-specified cursor style corresponding
// CursorStyle enum value
@@ -572,13 +698,13 @@ CursorStyle Profile::_ParseCursorShape(const std::wstring& cursorShapeString)
}
// Method Description:
// - Helper function for converting a CursorStyle to it's corresponding string
// - Helper function for converting a CursorStyle to its corresponding string
// value.
// Arguments:
// - cursorShape: The enum value to convert to a string.
// Return Value:
// - The string value for the given CursorStyle
std::wstring Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
std::wstring_view Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
{
switch (cursorShape)
{

View File

@@ -25,7 +25,9 @@ class TerminalApp::Profile final
{
public:
Profile(const winrt::guid& guid);
Profile();
~Profile();
winrt::Microsoft::Terminal::Settings::TerminalSettings CreateTerminalSettings(const std::vector<::TerminalApp::ColorScheme>& schemes) const;
@@ -48,6 +50,7 @@ public:
bool HasIcon() const noexcept;
std::wstring_view GetIconPath() const noexcept;
void SetIconPath(std::wstring_view path) noexcept;
bool GetCloseOnExit() const noexcept;
@@ -56,8 +59,10 @@ private:
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState);
static winrt::Windows::UI::Xaml::Media::Stretch ParseImageStretchMode(const std::wstring& imageStretchMode);
static std::wstring_view SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode);
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
static std::wstring _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
GUID _guid;
std::wstring _name;
@@ -81,6 +86,10 @@ private:
double _acrylicTransparency;
bool _useAcrylic;
std::optional<std::wstring> _backgroundImage;
std::optional<double> _backgroundImageOpacity;
std::optional<winrt::Windows::UI::Xaml::Media::Stretch> _backgroundImageStretchMode;
std::optional<std::wstring> _scrollbarState;
bool _closeOnExit;
std::wstring _padding;

View File

@@ -20,7 +20,7 @@ Tab::~Tab()
{
// When we're destructed, winrt will automatically decrement the refcount
// of our terminalcontrol.
// Assuming that refcount hits 0, it'll destruct it on it's own, including
// Assuming that refcount hits 0, it'll destruct it on its own, including
// calling Close on the terminal and connection.
}
@@ -87,6 +87,6 @@ void Tab::Scroll(int delta)
{
_control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
const auto currentOffset = _control.GetScrollOffset();
_control.ScrollViewport(currentOffset + delta);
_control.KeyboardScrollViewport(currentOffset + delta);
});
}

View File

@@ -41,6 +41,7 @@
<ClInclude Include="GlobalAppSettings.h" />
<ClInclude Include="Profile.h" />
<ClInclude Include="CascadiaSettings.h" />
<ClInclude Include="KeyChordSerialization.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="AppKeyBindings.h">
<DependentUpon>AppKeyBindings.idl</DependentUpon>
@@ -58,6 +59,7 @@
<ClCompile Include="Profile.cpp" />
<ClCompile Include="CascadiaSettings.cpp" />
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
<ClCompile Include="KeyChordSerialization.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@@ -128,4 +130,4 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -29,6 +29,7 @@
#include <winrt/Windows.Data.Json.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.Text.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>

View File

@@ -11,28 +11,35 @@
#define STARTF_USESTDHANDLES 0x00000100
#endif
#include "ConhostConnection.g.cpp"
#include <conpty-universal.h>
#include "../../types/inc/Utils.hpp"
using namespace ::Microsoft::Console;
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
ConhostConnection::ConhostConnection(hstring const& commandline,
hstring const& startingDirectory,
uint32_t initialRows,
uint32_t initialCols) :
_connected{ false },
_inPipe{ INVALID_HANDLE_VALUE },
_outPipe{ INVALID_HANDLE_VALUE },
_signalPipe{ INVALID_HANDLE_VALUE },
_outputThreadId{ 0 },
_hOutputThread{ INVALID_HANDLE_VALUE },
_piConhost{ 0 },
_closing{ false }
ConhostConnection::ConhostConnection(const hstring& commandline,
const hstring& startingDirectory,
const uint32_t initialRows,
const uint32_t initialCols,
const guid& initialGuid) :
_initialRows{ initialRows },
_initialCols{ initialCols },
_commandline{ commandline },
_startingDirectory{ startingDirectory },
_guid{ initialGuid }
{
_commandline = commandline;
_startingDirectory = startingDirectory;
_initialRows = initialRows;
_initialCols = initialCols;
if (_guid == guid())
{
_guid = Utils::CreateGuid();
}
}
winrt::guid ConhostConnection::Guid() const noexcept
{
return _guid;
}
winrt::event_token ConhostConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
@@ -57,30 +64,44 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConhostConnection::Start()
{
std::wstring cmdline = _commandline.c_str();
std::wstring cmdline{ _commandline.c_str() };
std::optional<std::wstring> startingDirectory;
if (!_startingDirectory.empty())
{
startingDirectory = _startingDirectory;
}
CreateConPty(cmdline,
startingDirectory,
static_cast<short>(_initialCols),
static_cast<short>(_initialRows),
&_inPipe,
&_outPipe,
&_signalPipe,
&_piConhost);
EnvironmentVariableMapW extraEnvVars;
{
// Convert connection Guid to string and ignore the enclosing '{}'.
std::wstring wsGuid{ Utils::GuidToString(_guid) };
wsGuid.pop_back();
const wchar_t* const pwszGuid{ wsGuid.data() + 1 };
// Ensure every connection has the unique identifier in the environment.
extraEnvVars.emplace(L"WT_SESSION", pwszGuid);
}
THROW_IF_FAILED(
CreateConPty(cmdline,
startingDirectory,
static_cast<short>(_initialCols),
static_cast<short>(_initialRows),
&_inPipe,
&_outPipe,
&_signalPipe,
&_piConhost,
extraEnvVars));
_connected = true;
// Create our own output handling thread
// Each console needs to make sure to drain the output from it's backing host.
_outputThreadId = (DWORD)-1;
// Each console needs to make sure to drain the output from its backing host.
_outputThreadId = static_cast<DWORD>(-1);
_hOutputThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
StaticOutputThreadProc,
this,
0,
&_outputThreadId);
@@ -131,7 +152,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
CloseHandle(_piConhost.hProcess);
}
DWORD ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
DWORD WINAPI ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
{
ConhostConnection* const pInstance = (ConhostConnection*)lpParameter;
return pInstance->_OutputThread();

View File

@@ -9,7 +9,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConhostConnection : ConhostConnectionT<ConhostConnection>
{
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, uint32_t rows, uint32_t cols);
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid);
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
void TerminalOutput(winrt::event_token const& token) noexcept;
@@ -20,26 +20,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Resize(uint32_t rows, uint32_t columns);
void Close();
winrt::guid Guid() const noexcept;
private:
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
uint32_t _initialRows;
uint32_t _initialCols;
hstring _commandline;
hstring _startingDirectory;
uint32_t _initialRows{};
uint32_t _initialCols{};
hstring _commandline{};
hstring _startingDirectory{};
bool _connected;
HANDLE _inPipe; // The pipe for writing input to
HANDLE _outPipe; // The pipe for reading output from
HANDLE _signalPipe;
//HPCON _hPC;
DWORD _outputThreadId;
HANDLE _hOutputThread;
PROCESS_INFORMATION _piConhost;
bool _closing;
bool _connected{};
HANDLE _inPipe{ INVALID_HANDLE_VALUE }; // The pipe for writing input to
HANDLE _outPipe{ INVALID_HANDLE_VALUE }; // The pipe for reading output from
HANDLE _signalPipe{ INVALID_HANDLE_VALUE };
DWORD _outputThreadId{};
HANDLE _hOutputThread{ INVALID_HANDLE_VALUE };
PROCESS_INFORMATION _piConhost{};
guid _guid{}; // A "unique" session identifier for connected client
bool _closing{};
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
DWORD _OutputThread();
};
}

View File

@@ -8,7 +8,9 @@ namespace Microsoft.Terminal.TerminalConnection
[default_interface]
runtimeclass ConhostConnection : ITerminalConnection
{
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns);
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid);
Guid Guid { get; };
};
}

View File

@@ -60,7 +60,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_outputThreadId = (DWORD)-1;
_hOutputThread = CreateThread(nullptr,
0,
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
StaticOutputThreadProc,
this,
0,
&_outputThreadId);
@@ -222,7 +222,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
DWORD ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
DWORD WINAPI ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
{
ConptyConnection* const pInstance = (ConptyConnection*)lpParameter;
return pInstance->_OutputThread();

View File

@@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
HANDLE _hOutputThread;
PROCESS_INFORMATION _piClient;
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
void _CreatePseudoConsole();
DWORD _OutputThread();
};

View File

@@ -5,6 +5,8 @@
#include "EchoConnection.h"
#include <sstream>
#include "EchoConnection.g.cpp"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
EchoConnection::EchoConnection()

View File

@@ -52,6 +52,18 @@
</ItemDefinitionGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!--
the packaging project won't recurse through our dependencies, you have to
make sure that if you add a cppwinrt dependency to any of these projects,
you also update all the consumers
-->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<!--
DON'T REDIRECT OUR OUTPUT.

View File

@@ -7,5 +7,10 @@
#pragma once
#include <LibraryIncludes.h>
// Must be included before any WinRT headers.
#include <unknwn.h>
#include "winrt/Windows.Foundation.h"
#include <Windows.h>

View File

@@ -10,6 +10,8 @@
#include <WinUser.h>
#include "..\..\types\inc\GlyphWidth.hpp"
#include "TermControl.g.cpp"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::UI::Xaml;
@@ -26,7 +28,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
TermControl::TermControl(Settings::IControlSettings settings) :
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80) },
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80, winrt::guid()) },
_initializedTerminal{ false },
_root{ nullptr },
_controlRoot{ nullptr },
@@ -133,7 +135,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal,[this](){
// Update our control settings
_ApplyUISettings();
// Update the terminal core with it's new Core settings
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
// Refresh our font with the renderer
@@ -156,8 +158,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - Style our UI elements based on the values in our _settings, and set up
// other control-specific settings. This method will be called whenever
// the settings are reloaded.
// * Sets up the background of the control with the provided BG color,
// acrylic or not, and if acrylic, then uses the opacity from _settings.
// * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible
// for the control's background
// * Calls _BackgroundColorChanged to style the background of the control
// - Core settings will be passed to the terminal in _InitializeTerminal
// Arguments:
// - <none>
@@ -165,37 +168,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControl::_ApplyUISettings()
{
winrt::Windows::UI::Color bgColor{};
_InitializeBackgroundBrush();
uint32_t bg = _settings.DefaultBackground();
const auto R = GetRValue(bg);
const auto G = GetGValue(bg);
const auto B = GetBValue(bg);
bgColor.R = R;
bgColor.G = G;
bgColor.B = B;
bgColor.A = 255;
if (_settings.UseAcrylic())
{
Media::AcrylicBrush acrylic{};
acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
acrylic.TintOpacity(_settings.TintOpacity());
_root.Background(acrylic);
// If we're acrylic, we want to make sure that the default BG color
// is transparent, so we can see the acrylic effect on text with the
// default BG color.
_settings.DefaultBackground(ARGB(0, R, G, B));
}
else
{
Media::SolidColorBrush solidColor{};
solidColor.Color(bgColor);
_root.Background(solidColor);
_settings.DefaultBackground(RGB(R, G, B));
}
_BackgroundColorChanged(bg);
// Apply padding to the root Grid
auto thickness = _ParseThicknessFromPadding(_settings.Padding());
@@ -214,6 +190,147 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_desiredFont = { _actualFont };
}
// Method Description:
// - Set up the brush used to display the control's background.
// - Respects the settings for acrylic, background image and opacity from
// _settings.
// * Prioritizes the acrylic background if chosen, respecting acrylicOpacity
// from _settings.
// * If acrylic is not enabled and a backgroundImage is present, it is used,
// respecting the opacity and stretch mode settings from _settings.
// * Falls back to a solid color background from _settings if acrylic is not
// enabled and no background image is present.
// - Avoids image flickering and acrylic brush redraw if settings are changed
// but the appropriate brush is still in place.
// - Does not apply background color; _BackgroundColorChanged must be called
// to do so.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TermControl::_InitializeBackgroundBrush()
{
if (_settings.UseAcrylic())
{
// See if we've already got an acrylic background brush
// to avoid the flicker when setting up a new one
auto acrylic = _root.Background().try_as<Media::AcrylicBrush>();
// Instantiate a brush if there's not already one there
if (acrylic == nullptr)
{
acrylic = Media::AcrylicBrush{};
acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
}
// Apply brush settings
acrylic.TintOpacity(_settings.TintOpacity());
// Apply brush to control if it's not already there
if (_root.Background() != acrylic)
{
_root.Background(acrylic);
}
}
else if (!_settings.BackgroundImage().empty())
{
Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() };
// Check if the existing brush is an image brush, and if not
// construct a new one
auto brush = _root.Background().try_as<Media::ImageBrush>();
if (brush == nullptr)
{
brush = Media::ImageBrush{};
}
// Check if the image brush is already pointing to the image
// in the modified settings; if it isn't (or isn't there),
// set a new image source for the brush
auto imageSource = brush.ImageSource().try_as<Media::Imaging::BitmapImage>();
if (imageSource == nullptr || imageSource.UriSource() == nullptr
|| imageSource.UriSource().RawUri() != imageUri.RawUri())
{
// Note that BitmapImage handles the image load asynchronously,
// which is especially important since the image
// may well be both large and somewhere out on the
// internet.
Media::Imaging::BitmapImage image(imageUri);
brush.ImageSource(image);
}
// Apply stretch and opacity settings
brush.Stretch(_settings.BackgroundImageStretchMode());
brush.Opacity(_settings.BackgroundImageOpacity());
// Apply brush if it isn't already there
if (_root.Background() != brush)
{
_root.Background(brush);
}
}
else
{
Media::SolidColorBrush solidColor{};
_root.Background(solidColor);
}
}
// Method Description:
// - Style the background of the control with the provided background color
// Arguments:
// - color: The background color to use as a uint32 (aka DWORD COLORREF)
// Return Value:
// - <none>
void TermControl::_BackgroundColorChanged(const uint32_t color)
{
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, color]() {
const auto R = GetRValue(color);
const auto G = GetGValue(color);
const auto B = GetBValue(color);
winrt::Windows::UI::Color bgColor{};
bgColor.R = R;
bgColor.G = G;
bgColor.B = B;
bgColor.A = 255;
if (_settings.UseAcrylic())
{
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
{
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
}
// If we're acrylic, we want to make sure that the default BG color
// is transparent, so we can see the acrylic effect on text with the
// default BG color.
_settings.DefaultBackground(ARGB(0, R, G, B));
}
else if (!_settings.BackgroundImage().empty())
{
// This currently applies no changes to the image background
// brush itself.
// Set the default background as transparent to prevent the
// DX layer from overwriting the background image
_settings.DefaultBackground(ARGB(0, R, G, B));
}
else
{
if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
{
solidColor.Color(bgColor);
}
_settings.DefaultBackground(RGB(R, G, B));
}
});
}
// Method Description:
// - Create a connection based on the values in our settings object.
// * Gets the commandline and working directory out of the _settings and
@@ -221,7 +338,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// directory.
void TermControl::_ApplyConnectionSettings()
{
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80);
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80, winrt::guid());
}
TermControl::~TermControl()
@@ -278,22 +395,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = _swapChainPanel.ActualHeight();
_terminal = new ::Microsoft::Terminal::Core::Terminal();
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
// First create the render thread.
// Then stash a local pointer to the render thread so we can initialize it and enable it
// to paint itself *after* we hand off its ownership to the renderer.
// We split up construction and initialization of the render thread object this way
// because the renderer and render thread have circular references to each other.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
// Stash a local pointer to the render thread, so we can enable it after
// we hand off ownership to the renderer.
auto* const localPointerToThread = renderThread.get();
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal, nullptr, 0, std::move(renderThread));
// Now create the renderer and initialize the render thread.
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
// Set up the renderer to be used to calculate the width of a glyph,
// should we be unable to figure out it's width another way.
// should we be unable to figure out its width another way.
auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
@@ -334,8 +457,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
_terminal->SetWriteInputCallback(inputFn);
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
auto chain = _renderEngine->GetSwapChain();
_swapChainPanel.Dispatcher().RunAsync(CoreDispatcherPriority::High, [this, chain]()
{
@@ -404,6 +525,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
_terminal->SetTitleChangedCallback(pfnTitleChanged);
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
@@ -415,9 +539,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_cursorTimer = std::make_optional(DispatcherTimer());
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
_cursorTimer.value().Tick({ this, &TermControl::_BlinkCursor });
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
_cursorTimer.value().Start();
}
else
{
@@ -425,6 +547,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_cursorTimer = std::nullopt;
}
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
// Focus the control here. If we do it up above (in _Create_), then the
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
@@ -533,6 +658,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
// Manually prevent keyboard navigation with tab. We want to send tab to
// the terminal, and we don't want to be able to escape focus of the
// control with tab.
if (e.OriginalKey() == VirtualKey::Tab)
{
handled = true;
}
e.Handled(handled);
}
@@ -549,6 +682,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse)
{
// Ignore mouse events while the terminal does not have focus.
// This prevents the user from selecting and copying text if they
// click inside the current tab to refocus the terminal window.
if (!_focused)
{
args.Handled(true);
return;
}
const auto modifiers = args.KeyModifiers();
const auto altEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu);
const auto shiftEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift);
@@ -556,13 +698,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
const auto cursorPosition = point.Position();
const auto fontSize = _actualFont.GetSize();
const COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X / fontSize.X),
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
};
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// save location before rendering
_terminal->SetSelectionAnchor(terminalPosition);
@@ -618,13 +754,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
const auto cursorPosition = point.Position();
const auto fontSize = _actualFont.GetSize();
const COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X / fontSize.X),
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
};
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// save location (for rendering) + render
_terminal->SetEndSelectionPosition(terminalPosition);
@@ -828,8 +958,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
_focused = true;
if (_cursorTimer.has_value())
{
_cursorTimer.value().Start();
}
}
// Method Description:
@@ -838,6 +972,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
_focused = false;
if (_cursorTimer.has_value())
{
_cursorTimer.value().Stop();
@@ -907,6 +1043,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
{
if (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible())
{
return;
}
_terminal->SetCursorVisible(!_terminal->IsCursorVisible());
}
@@ -1035,6 +1175,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_terminal->UserScrollViewport(viewTop);
}
// Method Description:
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly
// Arguments:
// - viewTop: the viewTop to scroll to
// The difference between this function and ScrollViewport is that this one also
// updates the _scrollBar after the viewport scroll. The reason _scrollBar is not updated in
// ScrollViewport is because ScrollViewport is being called by _ScrollbarChangeHandler
void TermControl::KeyboardScrollViewport(int viewTop)
{
_terminal->UserScrollViewport(viewTop);
_lastScrollOffset = std::nullopt;
_scrollBar.Value(static_cast<int>(viewTop));
}
int TermControl::GetScrollOffset()
{
return _terminal->GetScrollOffset();
@@ -1077,8 +1231,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
FontInfo actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false };
FontInfoDesired desiredFont = { actualFont };
const auto cols = settings.InitialCols();
const auto rows = settings.InitialRows();
// If the settings have negative or zero row or column counts, ignore those counts.
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
// we may eventually impose different ones depending on how many pixels we can address.)
const auto cols = std::max(settings.InitialCols(), 1);
const auto rows = std::max(settings.InitialRows(), 1);
// Create a DX engine and initialize it with our font and DPI. We'll
// then use it to measure how much space the requested rows and columns
@@ -1199,6 +1356,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
(shift ? Settings::KeyModifiers::Shift : Settings::KeyModifiers::None) };
}
// Method Description:
// - Gets the corresponding viewport terminal position for the cursor
// by excluding the padding and normalizing with the font size.
// This is used for selection.
// Arguments:
// - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor).
// NOTE: origin (0,0) is top-left.
// Return Value:
// - the corresponding viewport terminal position for the given Point parameter
const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition)
{
// Exclude padding from cursor position calculation
COORD terminalPosition =
{
static_cast<SHORT>(cursorPosition.X - _root.Padding().Left),
static_cast<SHORT>(cursorPosition.Y - _root.Padding().Top)
};
const auto fontSize = _actualFont.GetSize();
FAIL_FAST_IF(fontSize.X == 0);
FAIL_FAST_IF(fontSize.Y == 0);
// Normalize to terminal coordinates by using font size
terminalPosition.X /= fontSize.X;
terminalPosition.Y /= fontSize.Y;
return terminalPosition;
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void Close();
void ScrollViewport(int viewTop);
void KeyboardScrollViewport(int viewTop);
int GetScrollOffset();
int GetViewHeight() const;
@@ -69,12 +70,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
event_token _connectionOutputEventToken;
::Microsoft::Terminal::Core::Terminal* _terminal;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
Settings::IControlSettings _settings;
bool _focused;
bool _closing;
FontInfoDesired _desiredFont;
@@ -93,6 +95,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _Create();
void _ApplyUISettings();
void _InitializeBackgroundBrush();
void _BackgroundColorChanged(const uint32_t color);
void _ApplyConnectionSettings();
void _InitializeTerminal();
void _UpdateFont();
@@ -123,6 +127,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Settings::KeyModifiers _GetPressedModifierKeys() const;
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
};
}

View File

@@ -35,6 +35,7 @@ namespace Microsoft.Terminal.TerminalControl
void Close();
void ScrollViewport(Int32 viewTop);
void KeyboardScrollViewport(Int32 viewTop);
Int32 GetScrollOffset();
Int32 GetViewHeight();
event ScrollPositionChangedEventArgs ScrollPositionChanged;

View File

@@ -26,6 +26,7 @@
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.ui.xaml.media.imaging.h>
#include <winrt/Windows.ui.xaml.input.h>
#include <windows.ui.xaml.media.dxinterop.h>

View File

@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "../../terminal/adapter/DispatchTypes.hpp"
namespace Microsoft::Terminal::Core
{
class ITerminalApi
@@ -27,5 +29,10 @@ namespace Microsoft::Terminal::Core
virtual bool SetWindowTitle(std::wstring_view title) = 0;
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) = 0;
virtual bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) = 0;
virtual bool SetDefaultForeground(const DWORD dwColor) = 0;
virtual bool SetDefaultBackground(const DWORD dwColor) = 0;
};
}

View File

@@ -18,7 +18,12 @@ using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::VirtualTerminal;
std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
static constexpr short _ClampToShortMax(int value, short min)
{
return static_cast<short>(std::clamp(value, static_cast<int>(min), SHRT_MAX));
}
static std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
{
std::wstring wstr = L"";
for(auto& ev : inEventsToWrite)
@@ -65,23 +70,23 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
{
_mutableViewport = Viewport::FromDimensions({ 0,0 }, viewportSize);
_scrollbackLines = scrollbackLines;
COORD bufferSize { viewportSize.X, viewportSize.Y + scrollbackLines };
TextAttribute attr{};
UINT cursorSize = 12;
const COORD bufferSize { viewportSize.X, _ClampToShortMax(viewportSize.Y + scrollbackLines, 1) };
const TextAttribute attr{};
const UINT cursorSize = 12;
_buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
}
// Method Description:
// - Initializes the Temrinal from the given set of settings.
// - Initializes the Terminal from the given set of settings.
// Arguments:
// - settings: the set of CoreSettings we need to use to initialize the terminal
// - renderTarget: A render target the terminal can use for paint invalidation.
void Terminal::CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings,
Microsoft::Console::Render::IRenderTarget& renderTarget)
{
const COORD viewportSize{ static_cast<short>(settings.InitialCols()), static_cast<short>(settings.InitialRows()) };
const COORD viewportSize{ _ClampToShortMax(settings.InitialCols(), 1), _ClampToShortMax(settings.InitialRows(), 1) };
// TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1
Create(viewportSize, static_cast<short>(settings.HistorySize()), renderTarget);
Create(viewportSize, _ClampToShortMax(settings.HistorySize(), 0), renderTarget);
UpdateSettings(settings);
}
@@ -437,6 +442,15 @@ void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, co
_pfnScrollPositionChanged = pfn;
}
// Method Description:
// - Allows setting a callback for when the background color is changed
// Arguments:
// - pfn: a function callback that takes a uint32 (DWORD COLORREF) color in the format 0x00BBGGRR
void Terminal::SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept
{
_pfnBackgroundColorChanged = pfn;
}
// Method Description:
// - Checks if selection is active
// Return Value:
@@ -600,3 +614,9 @@ void Terminal::SetCursorVisible(const bool isVisible) noexcept
auto& cursor = _buffer->GetCursor();
cursor.SetIsVisible(isVisible);
}
bool Terminal::IsCursorBlinkingAllowed() const noexcept
{
const auto& cursor = _buffer->GetCursor();
return cursor.IsBlinkingAllowed();
}

View File

@@ -70,7 +70,10 @@ public:
COORD GetCursorPosition() override;
bool EraseCharacters(const unsigned int numChars) override;
bool SetWindowTitle(std::wstring_view title) override;
bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) override;
bool SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor) override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
bool SetDefaultForeground(const COLORREF dwColor) override;
bool SetDefaultBackground(const COLORREF dwColor) override;
#pragma endregion
#pragma region ITerminalInput
@@ -112,8 +115,10 @@ public:
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
void SetCursorVisible(const bool isVisible) noexcept;
bool IsCursorBlinkingAllowed() const noexcept;
#pragma region TextSelection
const bool IsSelectionActive() const noexcept;
@@ -129,6 +134,7 @@ public:
std::function<void(std::wstring&)> _pfnWriteInput;
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const uint32_t)> _pfnBackgroundColorChanged;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;

View File

@@ -157,7 +157,7 @@ bool Terminal::SetWindowTitle(std::wstring_view title)
// - dwColor: the new COLORREF to use as that color table value.
// Return Value:
// - true iff we successfully updated the color table entry.
bool Terminal::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
bool Terminal::SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor)
{
if (tableIndex > _colorTable.size())
{
@@ -169,3 +169,84 @@ bool Terminal::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
// Method Description:
// - Sets the cursor style to the given style.
// Arguments:
// - cursorStyle: the style to be set for the cursor
// Return Value:
// - true iff we successfully set the cursor style
bool Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
{
CursorType finalCursorType;
bool fShouldBlink;
switch (cursorStyle)
{
case DispatchTypes::CursorStyle::BlinkingBlockDefault:
[[fallthrough]];
case DispatchTypes::CursorStyle::BlinkingBlock:
finalCursorType = CursorType::FullBox;
fShouldBlink = true;
break;
case DispatchTypes::CursorStyle::SteadyBlock:
finalCursorType = CursorType::FullBox;
fShouldBlink = false;
break;
case DispatchTypes::CursorStyle::BlinkingUnderline:
finalCursorType = CursorType::Underscore;
fShouldBlink = true;
break;
case DispatchTypes::CursorStyle::SteadyUnderline:
finalCursorType = CursorType::Underscore;
fShouldBlink = false;
break;
case DispatchTypes::CursorStyle::BlinkingBar:
finalCursorType = CursorType::VerticalBar;
fShouldBlink = true;
break;
case DispatchTypes::CursorStyle::SteadyBar:
finalCursorType = CursorType::VerticalBar;
fShouldBlink = false;
break;
default:
finalCursorType = CursorType::Legacy;
fShouldBlink = false;
}
_buffer->GetCursor().SetType(finalCursorType);
_buffer->GetCursor().SetBlinkingAllowed(fShouldBlink);
return true;
}
// Method Description:
// - Updates the default foreground color from a COLORREF, format 0x00BBGGRR.
// Arguments:
// - dwColor: the new COLORREF to use as the default foreground color
// Return Value:
// - true
bool Terminal::SetDefaultForeground(const COLORREF dwColor)
{
_defaultFg = dwColor;
// Repaint everything - the colors might have changed
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}
// Method Description:
// - Updates the default background color from a COLORREF, format 0x00BBGGRR.
// Arguments:
// - dwColor: the new COLORREF to use as the default background color
// Return Value:
// - true
bool Terminal::SetDefaultBackground(const COLORREF dwColor)
{
_defaultBg = dwColor;
_pfnBackgroundColorChanged(dwColor);
// Repaint everything - the colors might have changed
_buffer->GetRenderTarget().TriggerRedrawAll();
return true;
}

View File

@@ -64,9 +64,36 @@ bool TerminalDispatch::SetWindowTitle(std::wstring_view title)
// - tableIndex: The VT color table index
// - dwColor: The new RGB color value to use.
// Return Value:
// True if handled successfully. False othewise.
// True if handled successfully. False otherwise.
bool TerminalDispatch::SetColorTableEntry(const size_t tableIndex,
const DWORD dwColor)
{
return _terminalApi.SetColorTableEntry(tableIndex, dwColor);
}
bool TerminalDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
{
return _terminalApi.SetCursorStyle(cursorStyle);
}
// Method Description:
// - Sets the default foreground color to a new value
// Arguments:
// - dwColor: The new RGB color value to use, in 0x00BBGGRR form
// Return Value:
// True if handled successfully. False otherwise.
bool TerminalDispatch::SetDefaultForeground(const DWORD dwColor)
{
return _terminalApi.SetDefaultForeground(dwColor);
}
// Method Description:
// - Sets the default background color to a new value
// Arguments:
// - dwColor: The new RGB color value to use, in 0x00BBGGRR form
// Return Value:
// True if handled successfully. False otherwise.
bool TerminalDispatch::SetDefaultBackground(const DWORD dwColor)
{
return _terminalApi.SetDefaultBackground(dwColor);
}

View File

@@ -25,6 +25,10 @@ public:
bool SetWindowTitle(std::wstring_view title) override;
bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
bool SetDefaultForeground(const DWORD dwColor) override;
bool SetDefaultBackground(const DWORD dwColor) override;
private:
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;

View File

@@ -34,5 +34,8 @@ namespace Microsoft.Terminal.Settings
String StartingDirectory;
String EnvironmentVariables;
String BackgroundImage;
Double BackgroundImageOpacity;
Windows.UI.Xaml.Media.Stretch BackgroundImageStretchMode;
};
}

View File

@@ -4,6 +4,8 @@
#include "pch.h"
#include "KeyChord.h"
#include "KeyChord.g.cpp"
namespace winrt::Microsoft::Terminal::Settings::implementation
{
KeyChord::KeyChord(bool ctrl, bool alt, bool shift, int32_t vkey) :

View File

@@ -5,6 +5,8 @@
#include "TerminalSettings.h"
#include <DefaultSettings.h>
#include "TerminalSettings.g.cpp"
namespace winrt::Microsoft::Terminal::Settings::implementation
{
TerminalSettings::TerminalSettings() :
@@ -24,6 +26,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_padding{ DEFAULT_PADDING },
_fontFace{ DEFAULT_FONT_FACE },
_fontSize{ DEFAULT_FONT_SIZE },
_backgroundImage{},
_backgroundImageOpacity{ 1.0 },
_backgroundImageStretchMode{ winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill },
_keyBindings{ nullptr },
_scrollbarState{ ScrollbarState::Visible }
{
@@ -191,6 +196,36 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_fontSize = value;
}
void TerminalSettings::BackgroundImage(hstring const& value)
{
_backgroundImage = value;
}
hstring TerminalSettings::BackgroundImage()
{
return _backgroundImage;
}
void TerminalSettings::BackgroundImageOpacity(double value)
{
_backgroundImageOpacity = value;
}
double TerminalSettings::BackgroundImageOpacity()
{
return _backgroundImageOpacity;
}
winrt::Windows::UI::Xaml::Media::Stretch TerminalSettings::BackgroundImageStretchMode()
{
return _backgroundImageStretchMode;
}
void TerminalSettings::BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value)
{
_backgroundImageStretchMode = value;
}
Settings::IKeyBindings TerminalSettings::KeyBindings()
{
return _keyBindings;

View File

@@ -61,6 +61,13 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
int32_t FontSize();
void FontSize(int32_t value);
hstring BackgroundImage();
void BackgroundImage(hstring const& value);
double BackgroundImageOpacity();
void BackgroundImageOpacity(double value);
winrt::Windows::UI::Xaml::Media::Stretch BackgroundImageStretchMode();
void BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value);
winrt::Microsoft::Terminal::Settings::IKeyBindings KeyBindings();
void KeyBindings(winrt::Microsoft::Terminal::Settings::IKeyBindings const& value);
@@ -94,6 +101,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
hstring _fontFace;
int32_t _fontSize;
hstring _padding;
hstring _backgroundImage;
double _backgroundImageOpacity;
winrt::Windows::UI::Xaml::Media::Stretch _backgroundImageStretchMode;
hstring _commandline;
hstring _startingDir;
hstring _envVars;

View File

@@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include <WexTestClass.h>
#include "DefaultSettings.h"
#include "../cascadia/TerminalCore/Terminal.hpp"
#include "../renderer/inc/DummyRenderTarget.hpp"
#include "consoletaeftemplates.hpp"
#include "winrt/Microsoft.Terminal.Settings.h"
using namespace winrt::Microsoft::Terminal::Settings;
using namespace Microsoft::Terminal::Core;
namespace TerminalCoreUnitTests
{
class MockTermSettings : public winrt::implements<MockTermSettings, ICoreSettings>
{
public:
MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) :
_historySize(historySize),
_initialRows(initialRows),
_initialCols(initialCols)
{ }
// property getters - all implemented
int32_t HistorySize() { return _historySize; }
int32_t InitialRows() { return _initialRows; }
int32_t InitialCols() { return _initialCols; }
uint32_t DefaultForeground() { return COLOR_WHITE; }
uint32_t DefaultBackground() { return COLOR_BLACK; }
bool SnapOnInput() { return false; }
uint32_t CursorColor() { return COLOR_WHITE; }
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
uint32_t CursorHeight() { return 42UL; }
// other implemented methods
uint32_t GetColorTableEntry(int32_t) const { return 123; }
// property setters - all unimplemented
void HistorySize(int32_t) { }
void InitialRows(int32_t) { }
void InitialCols(int32_t) { }
void DefaultForeground(uint32_t) { }
void DefaultBackground(uint32_t) { }
void SnapOnInput(bool) { }
void CursorColor(uint32_t) { }
void CursorShape(CursorStyle const&) noexcept { }
void CursorHeight(uint32_t) { }
// other unimplemented methods
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) { }
private:
int32_t _historySize;
int32_t _initialRows;
int32_t _initialCols;
};
#define WCS(x) WCSHELPER(x)
#define WCSHELPER(x) L#x
class ScreenSizeLimitsTest
{
TEST_CLASS(ScreenSizeLimitsTest);
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
{
DummyRenderTarget emptyRenderTarget;
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
};
}

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="ScreenSizeLimitsTest.cpp" />
<ClCompile Include="SelectionTest.cpp" />
<ClCompile Include="precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
@@ -36,7 +38,7 @@
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
</ClCompile>
<Link>

View File

@@ -16,6 +16,7 @@ public:
return reinterpret_cast<T *>(GetWindowLongPtr(window, GWLP_USERDATA));
}
[[nodiscard]]
static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
WINRT_ASSERT(window);
@@ -40,6 +41,7 @@ public:
return DefWindowProc(window, message, wparam, lparam);
}
[[nodiscard]]
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
switch (message) {
@@ -89,7 +91,6 @@ public:
}
case CM_UPDATE_TITLE:
{
SetWindowTextW(_window, _title.c_str());
break;
}
@@ -99,6 +100,7 @@ public:
}
// DPI Change handler. on WM_DPICHANGE resize the window
[[nodiscard]]
LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam)
{
_inDpiChange = true;
@@ -115,30 +117,67 @@ public:
SWP_NOZORDER | SWP_NOACTIVATE);
_currentDpi = uDpi;
NewScale(uDpi);
}
_inDpiChange = false;
return 0;
}
virtual void NewScale(UINT dpi) = 0;
virtual void OnResize(const UINT width, const UINT height) = 0;
virtual void OnMinimize() = 0;
virtual void OnRestore() = 0;
RECT GetWindowRect() const
RECT GetWindowRect() const noexcept
{
RECT rc = { 0 };
::GetWindowRect(_window, &rc);
return rc;
}
HWND GetHandle() noexcept
HWND GetHandle() const noexcept
{
return _window;
};
float GetCurrentDpiScale() const noexcept
{
const auto dpi = ::GetDpiForWindow(_window);
const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
return scale;
}
//// Gets the physical size of the client area of the HWND in _window
SIZE GetPhysicalSize() const noexcept
{
RECT rect = {};
GetClientRect(_window, &rect);
const auto windowsWidth = rect.right - rect.left;
const auto windowsHeight = rect.bottom - rect.top;
return SIZE{ windowsWidth, windowsHeight };
}
//// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize
//// Remarks:
//// XAML coordinate system is always in Display Indepenent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons)
//// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels.
//// The formula to transform is:
//// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
//// See also:
//// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels
//// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness
winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept
{
const auto dpi = GetCurrentDpiScale();
// 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
const auto logicalWidth = (physicalSize.cx / dpi) + 0.5f;
const auto logicalHeigth = (physicalSize.cy / dpi) + 0.5f;
return winrt::Windows::Foundation::Size(logicalWidth, logicalHeigth);
}
winrt::Windows::Foundation::Size GetLogicalSize() const noexcept
{
return GetLogicalSize(GetPhysicalSize());
}
// Method Description:
// - Sends a message to our message loop to update the title of the window.
// Arguments:

View File

@@ -17,10 +17,7 @@ using namespace ::Microsoft::Console::Types;
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
IslandWindow::IslandWindow() noexcept :
_currentWidth{ 0 },
_currentHeight{ 0 },
_interopWindowHandle{ nullptr },
_scale{ nullptr },
_rootGrid{ nullptr },
_source{ nullptr },
_pfnCreateCallback{ nullptr }
@@ -49,7 +46,7 @@ void IslandWindow::MakeWindow() noexcept
WINRT_ASSERT(!_window);
// Create the window with the default size here - During the creation of the
// window, the system will give us a chance to set it's size in WM_CREATE.
// window, the system will give us a chance to set its size in WM_CREATE.
// WM_CREATE will be handled synchronously, before CreateWindow returns.
WINRT_VERIFY(CreateWindow(wc.lpszClassName,
L"Windows Terminal",
@@ -126,42 +123,28 @@ void IslandWindow::Initialize()
// stash the child interop handle so we can resize it when the main hwnd is resized
interop->get_WindowHandle(&_interopWindowHandle);
if (!initialized)
{
_InitXamlContent();
}
_rootGrid = winrt::Windows::UI::Xaml::Controls::Grid();
_source.Content(_rootGrid);
// Do a quick resize to force the island to paint
OnSize();
}
void IslandWindow::_InitXamlContent()
{
// setup a root grid that will be used to apply DPI scaling
winrt::Windows::UI::Xaml::Media::ScaleTransform dpiScaleTransform;
winrt::Windows::UI::Xaml::Controls::Grid dpiAdjustmentGrid;
const auto dpi = GetDpiForWindow(_window);
const double scale = double(dpi) / double(USER_DEFAULT_SCREEN_DPI);
_rootGrid = dpiAdjustmentGrid;
_scale = dpiScaleTransform;
_scale.ScaleX(scale);
_scale.ScaleY(scale);
}
void IslandWindow::OnSize()
{
const auto physicalSize = GetPhysicalSize();
// update the interop window size
SetWindowPos(_interopWindowHandle, 0, 0, 0, _currentWidth, _currentHeight, SWP_SHOWWINDOW);
_rootGrid.Width(_currentWidth);
_rootGrid.Height(_currentHeight);
SetWindowPos(_interopWindowHandle, 0, 0, 0, physicalSize.cx, physicalSize.cy, SWP_SHOWWINDOW);
if (_rootGrid)
{
const auto size = GetLogicalSize();
_rootGrid.Width(size.Width);
_rootGrid.Height(size.Height);
}
}
[[nodiscard]]
LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
{
switch (message) {
@@ -180,83 +163,27 @@ LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LP
return 0; // eat the message
}
}
case WM_MENUCHAR:
{
// GH#891: return this LRESULT here to prevent the app from making a
// bell when alt+key is pressed. A menu is active and the user presses a
// key that does not correspond to any mnemonic or accelerator key,
return MAKELRESULT(0, MNC_CLOSE);
}
}
// TODO: handle messages here...
return base_type::MessageHandler(message, wparam, lparam);
}
// Method Description:
// - Called when the DPI of this window changes. Updates the XAML content sizing to match the client area of our window.
// Arguments:
// - dpi: new DPI to use. The default is 96, as defined by USER_DEFAULT_SCREEN_DPI.
// Return Value:
// - <none>
void IslandWindow::NewScale(UINT dpi)
{
const double scaleFactor = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
if (_scale != nullptr)
{
_scale.ScaleX(scaleFactor);
_scale.ScaleY(scaleFactor);
}
ApplyCorrection(scaleFactor);
}
// Method Description:
// - This method updates the padding that exists off the edge of the window to
// make sure to keep the XAML content size the same as the actual window size.
// Arguments:
// - scaleFactor: the DPI scaling multiplier to use. for a dpi of 96, this would
// be 1, for 144, this would be 1.5.
// Return Value:
// - <none>
void IslandWindow::ApplyCorrection(double scaleFactor)
{
// Get the dimensions of the XAML content grid.
const auto realWidth = _rootGrid.Width();
const auto realHeight = _rootGrid.Height();
// Scale those dimensions by our dpi scaling. This is how big the XAML
// content thinks it should be.
const auto dpiAwareWidth = realWidth * scaleFactor;
const auto dpiAwareHeight = realHeight * scaleFactor;
// Get the difference between what xaml thinks and the actual client area
// of our window.
const auto deltaX = dpiAwareWidth - realWidth;
const auto deltaY = dpiAwareHeight - realHeight;
// correct for the scaling we applied above
const auto dividedDeltaX = deltaX / scaleFactor;
const auto dividedDeltaY = deltaY / scaleFactor;
const double rightCorrection = dividedDeltaX;
const double bottomCorrection = dividedDeltaY;
// Apply padding to the root grid, so that it's content is the same size as
// our actual window size.
// Without this, XAML content will seem to spill off the side/bottom of the window
_rootGrid.Padding(Xaml::ThicknessHelper::FromLengths(0, 0, rightCorrection, bottomCorrection));
}
// Method Description:
// - Called when the window has been resized (or maximized)
// Arguments:
// - width: the new width of the window _in pixels_
// - height: the new height of the window _in pixels_
void IslandWindow::OnResize(const UINT width, const UINT height)
void IslandWindow::OnResize(const UINT /*width*/, const UINT /*height*/)
{
_currentWidth = width;
_currentHeight = height;
if (nullptr != _rootGrid)
{
OnSize();
ApplyCorrection(_scale.ScaleX());
}
OnSize();
}
// Method Description:
@@ -276,6 +203,6 @@ void IslandWindow::OnRestore()
void IslandWindow::SetRootContent(winrt::Windows::UI::Xaml::UIElement content)
{
_rootGrid.Children().Clear();
ApplyCorrection(_scale.ScaleX());
_rootGrid.Children().Append(content);
}

View File

@@ -16,9 +16,8 @@ public:
void Close();
virtual void OnSize();
[[nodiscard]]
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
void ApplyCorrection(double scaleFactor);
void NewScale(UINT dpi) override;
void OnResize(const UINT width, const UINT height) override;
void OnMinimize() override;
void OnRestore() override;
@@ -29,18 +28,13 @@ public:
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
protected:
unsigned int _currentWidth;
unsigned int _currentHeight;
HWND _interopWindowHandle;
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source;
winrt::Windows::UI::Xaml::Media::ScaleTransform _scale;
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
std::function<void(const HWND, const RECT)> _pfnCreateCallback;
void _InitXamlContent();
void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
};

View File

@@ -68,7 +68,6 @@ void NonClientIslandWindow::Initialize()
void NonClientIslandWindow::SetNonClientContent(winrt::Windows::UI::Xaml::UIElement content)
{
_nonClientRootGrid.Children().Clear();
ApplyCorrection(_scale.ScaleX());
_nonClientRootGrid.Children().Append(content);
}
@@ -91,13 +90,15 @@ void NonClientIslandWindow::SetNonClientHeight(const int contentHeight) noexcept
// content, in window coordinates.
Viewport NonClientIslandWindow::GetTitlebarContentArea() const noexcept
{
const auto dpi = GetDpiForWindow(_window);
const double scale = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
const auto scale = GetCurrentDpiScale();
const auto titlebarContentHeight = _titlebarUnscaledContentHeight * scale;
const auto titlebarMarginRight = _titlebarMarginRight;
auto titlebarWidth = _currentWidth - (_windowMarginSides + titlebarMarginRight);
const auto physicalSize = GetPhysicalSize();
const auto clientWidth = physicalSize.cx;
auto titlebarWidth = clientWidth - (_windowMarginSides + titlebarMarginRight);
// Adjust for maximized margins
titlebarWidth -= (_maximizedMargins.cxLeftWidth + _maximizedMargins.cxRightWidth);
@@ -133,8 +134,9 @@ Viewport NonClientIslandWindow::GetClientContentArea() const noexcept
COORD clientOrigin = { static_cast<short>(margins.cxLeftWidth),
static_cast<short>(margins.cyTopHeight) };
auto clientWidth = _currentWidth;
auto clientHeight = _currentHeight;
const auto physicalSize = GetPhysicalSize();
auto clientWidth = physicalSize.cx;
auto clientHeight = physicalSize.cy;
// If we're maximized, we don't want to use the frame as our margins,
// instead we want to use the margins from the maximization. If we included
@@ -177,8 +179,13 @@ void NonClientIslandWindow::OnSize()
clientArea.Height(),
SWP_SHOWWINDOW);
_rootGrid.Width(clientArea.Width());
_rootGrid.Height(clientArea.Height());
if (_rootGrid)
{
const SIZE physicalSize{ clientArea.Width(), clientArea.Height() };
const auto logicalSize = GetLogicalSize(physicalSize);
_rootGrid.Width(logicalSize.Width);
_rootGrid.Height(logicalSize.Height);
}
// update the interop window size
SetWindowPos(_nonClientInteropWindowHandle, 0,
@@ -202,6 +209,7 @@ void NonClientIslandWindow::OnSize()
// NOTE:
// Largely taken from code on:
// https://docs.microsoft.com/en-us/windows/desktop/dwm/customframe
[[nodiscard]]
LRESULT NonClientIslandWindow::HitTestNCA(POINT ptMouse) const noexcept
{
// Get the window rectangle.
@@ -277,6 +285,7 @@ MARGINS NonClientIslandWindow::GetFrameMargins() const noexcept
// - <none>
// Return Value:
// - the HRESULT returned by DwmExtendFrameIntoClientArea.
[[nodiscard]]
HRESULT NonClientIslandWindow::_UpdateFrameMargins() const noexcept
{
// Get the size of the borders we want to use. The sides and bottom will
@@ -380,6 +389,7 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT * const prcSugge
// Return Value:
// - The return value is the result of the message processing and depends on the
// message sent.
[[nodiscard]]
LRESULT NonClientIslandWindow::MessageHandler(UINT const message,
WPARAM const wParam,
LPARAM const lParam) noexcept
@@ -482,7 +492,7 @@ void NonClientIslandWindow::_HandleActivateWindow()
// _titlebarUnscaledContentHeight is set with SetNonClientHeight by the app
// hosting us.
_UpdateFrameMargins();
THROW_IF_FAILED(_UpdateFrameMargins());
}
// Method Description:
@@ -626,7 +636,7 @@ bool NonClientIslandWindow::_HandleWindowPosChanging(WINDOWPOS* const windowPos)
_maximizedMargins.cyBottomHeight = -offset;
_isMaximized = true;
_UpdateFrameMargins();
THROW_IF_FAILED(_UpdateFrameMargins());
}
}
else
@@ -640,7 +650,7 @@ bool NonClientIslandWindow::_HandleWindowPosChanging(WINDOWPOS* const windowPos)
// keep this here _in general_ for dragging across DPI boundaries.
if (!_isMaximized)
{
_UpdateFrameMargins();
THROW_IF_FAILED(_UpdateFrameMargins());
}
_isMaximized = false;

Some files were not shown because too many files have changed in this diff Show More