Compare commits

...

215 Commits

Author SHA1 Message Date
Dustin Howett
723ff47789 inbox: Reflect Windows inbox changes from 20190516 2019-05-16 16:21:33 -07:00
Dustin L. Howett (MSFT)
7291121112 doc: add some new issue templates (#838)
Co-Authored-By: Michael Niksa <miniksa@microsoft.com>
Co-Authored-By: Kayla Cinnamon <48369326+cinnamon-msft@users.noreply.github.com>

Fixes #751.
2019-05-16 11:24:14 -07:00
Dustin L. Howett (MSFT)
22103ff9c6 ci: Restore Taef.TestAdapter before build (#811)
Fixes #775
2019-05-16 11:22:22 -07:00
Jamie
e0f131121b Fixed typo's and improved consistency. (#704) 2019-05-15 21:02:42 -07:00
Mikhail Paulyshka
bc925d8909 tools: search for MSBuild in prerelease versions of MSVS (#795) 2019-05-15 20:57:26 -07:00
Kayla Cinnamon
e3764b2081 Added documentation policy to README.md (#834)
* Added documentation policy to README.md

* Update README.md

Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com>
2019-05-15 14:09:58 -07:00
Mario Kneidinger
a12521ffd3 Changed "Windows Internal Library" to "Windows Implementation Library" (#827) 2019-05-15 12:28:22 -07:00
Bharat Raghunathan
f867a2d4a4 Added a pull request template (#762)
* Close microsoft#752 by adding a pull request template

* Apply suggestions from code review by @miniksa and @bitcrazed

Co-Authored-By: Michael Niksa <miniksa@microsoft.com>
Co-Authored-By: Rich Turner <rich@bitcrazed.com>

* Fixed checkboxes

* Make placeholder uniform

[skip ci]
2019-05-15 12:27:31 -07:00
Fergal Reilly
3a27b29afc Amend Color array to typed values (#742)
* Amend Color array to typed values

* Re-add the original deserialization code.

* Re-added original deserialization

* Update comment spacing

Co-Authored-By: Michael Niksa <miniksa@microsoft.com>

* Replace tabs with spaces

* Replace array definition and update for loops.

* swapped _table calls to use .at()
2019-05-15 11:12:00 -07:00
Kapperchino
781d779b37 Added Keybindings for go up and go down page (#747)
* added keybindings

* untabfied the files

* fixed spacing issues and renamed termheight

* changed function names and other improvements

* made some auto variables const auto

* fixed tabs

* another try for the broken spacing
2019-05-15 08:21:14 -05:00
woachk
bc69d1a99a Changes to be able to quit the application via exit inside a CLI prompt. (#746)
* Changes to be able to quit the application via exit inside a CLI prompt.
2019-05-15 07:22:16 -05:00
Jef LeCompte
de24334898 Make's README list consistent (#790)
Made bullets consistent with other bullets.

[skip ci]
2019-05-14 16:19:54 -07:00
Dustin L. Howett (MSFT)
507d787fe8 inbox: reflect incoming changes from Windows 2019-05-14 16:16:43 -07:00
fghzxm
ad27906db7 Convert copy to move (#717)
This commit converts 3 spots of copy construction into move
construction.

`return data` was not converted to a move because it should be easily
RVO'able.

Signed-off-by: Fred Miller <fghzxm@outlook.com>
2019-05-14 15:05:07 -07:00
Michael Niksa
fb72dca939 Add link to related console-docs GH repository (#784)
The console-docs repository is very related to this one. I feel it should be linked somewhere prominently in this one.
[skip ci]
2019-05-14 15:04:01 -07:00
Gil Mishal
ea5270e563 added quotes to commands (#785)
thanks!
2019-05-14 14:27:39 -07:00
Mike Griese
b5eeddfb0f Change the Feedback link to take you to github (#789)
Fixes #787.

  Considering we're just duping all feedback hub issues to github, lets cut out
  the middleman and take them straight here.
2019-05-14 13:16:40 -07:00
Michael Niksa
7c6278de44 Fix WIL doc summary (#786)
- "Windows Internal Library" got named "Windows Implementation Library" for its GH release
- Fixed the links to point to the files in the WIL GH instead of the local copies.
- Left the rest of this as general guidance to how we use it.
2019-05-14 13:11:41 -07:00
Tim Heuer
303e227f44 Updating readme for more explicit VS2019 Instructions (#560)
* Updating readme for more explicit VS2019 Instructions

Added some more explicit and correct terms for the VS 2019 components that are needed.

* Update Install Instructions

I put the components in sub bullets and reworded some things.
2019-05-14 12:44:46 -07:00
sebastian gomez
a8cf3d6f4a Update README.md (#777)
Solution Platform must match the computer architecture
2019-05-14 13:51:17 -05:00
Keith Hill
71229239d4 Add one half color schemes (#466)
* Add One Half Dark & Light color schemes

* WIP: Add One Half Dark/Lite schemes to settings

* Address PR feedback - use gsl::narrow()

* Fix reversed OneHalfLight fg/bg colors

Added in customized colortool scheme colors for last 8 colors
2019-05-14 11:01:15 -07:00
Summon528
639d5f3f93 Reflect new AppNameDev in readme (#765)
due to #558, app that is built from source will be named "Windows Terminal (Dev Build)” instead of "Windows Terminal (Preview)”
2019-05-14 08:51:19 -07:00
Dustin L. Howett (MSFT)
df789a4e75 Exclude Windows Terminal sources and some other files from git2git (#749)
* Remove Windows Terminal sources from git2git

This will remove Windows Terminal sources from any replications driven by git2git.

* Exclude .nuget and .github as well.

[skip ci]
2019-05-14 08:33:46 -07:00
Hao
ef8e20af51 fix tab control and tabs is not synced after focused tab is removed (#737)
* trim duplicated github627

* fix tab control and tabs is not synced after focused tab is removed
2019-05-14 08:14:23 -05:00
David Teresi
8c177fab4f Add cursor blinking (#686)
It even respects the user's cursor blink speed setting!
2019-05-13 18:25:54 -07:00
pythias
2c1ab620bf Tab to spaces (#578)
* tab to spaces

* change tab size to 4.
2019-05-13 18:06:36 -07:00
Syed Faheel Ahmad
af3a421938 doc: improve the formatting of keyboard keys (#730)
Use the `<kbd>` HTML tag

[skip ci]
2019-05-13 18:05:53 -07:00
Tim Heuer
04c7b944bd Adding keymapping for access to Settings (#684)
This commit adds the keychord Ctrl+Comma, which launches settings.
2019-05-13 18:02:06 -07:00
Malcolm Smith
c2ee6277f8 Fix downlevel support for traditional console (#562) 2019-05-13 16:10:46 -07:00
koteski
5b1183a4b3 doc: append Debugging section with instructions to enable debugging from VS (#726) 2019-05-13 12:46:18 -07:00
Daniel Gillespie
6c80ab8017 added windows insider info to readme (#631)
* added windows insider info to readme

* improved readme description for insider program

* minor improvement to readme insider program
2019-05-13 09:35:30 -07:00
Andy Muehlhausen
fc49caca8a Update EXCEPTIONS.md (#736)
updated to indicate HRESULT is preferred over NTSTATUS, as suggested in 
https://github.com/Microsoft/Terminal/blob/master/doc/STYLE.md

[skip ci]
2019-05-13 09:14:41 -07:00
Mario Kneidinger
aeef340bdc Fixed duplicate line in TermControl (#732) (#739)
* Fixed duplicate line in TermControl #732

* Deleted lines, because values were unnecessarily set to default values.
2019-05-13 09:11:31 -07:00
That's My Face
9ba3a53b4b Fixed typo (#723) 2019-05-13 08:59:56 -05:00
Ghosty141
1e478ae99d Bugfix: The opacity of the text background color was set to 0.9 (#677) (#688) 2019-05-12 19:27:16 -05:00
Joel Bennett
dc7fff7ab0 Fix the Generated Files .gitignore (#697)
[skip ci]
2019-05-11 22:56:10 -07:00
fghzxm
6088134832 Improve startingDirectory functionality (#604)
* Improve `startingDirectory` functionality

This commit adds the `startingDirectory` property to the default-created
`cmd` and `powershell` profiles, with the default value
`%HOMEDRIVE%%HOMEPATH%`.

Signed-off-by: Fred Miller <fghzxm@outlook.com>

* Use %USERPROFILE% to replace %HOMEDRIVE%%HOMEPATH%

This commit changes `%USERPROFILE%` in the default profiles to
`%HOMEDRIVE%%HOMEPATH%`.

https://stackoverflow.com/posts/36392591/revisions says `%USERPROFILE%`
is better than `%HOMEDRIVE%%HOMEPATH%`, so changed it.

Signed-off-by: Fred Miller <fghzxm@outlook.com>

* Improve `startingDirectory` functionality

This commit adds the `startingDirectory` property to the default-created
`cmd` and `powershell` profiles, with the default value
`%HOMEDRIVE%%HOMEPATH%`.

Signed-off-by: Fred Miller <fghzxm@outlook.com>

* Use %USERPROFILE% to replace %HOMEDRIVE%%HOMEPATH%

This commit changes `%USERPROFILE%` in the default profiles to
`%HOMEDRIVE%%HOMEPATH%`.

https://stackoverflow.com/posts/36392591/revisions says `%USERPROFILE%`
is better than `%HOMEDRIVE%%HOMEPATH%`, so changed it.

Signed-off-by: Fred Miller <fghzxm@outlook.com>

* Consolidate constant

Refer to the externally defined constant in code.

Signed-off-by: Fred Miller <fghzxm@outlook.com>
2019-05-11 00:02:28 -07:00
Carlos Zamora
bf460ab7fe Bugfix: don't allow closing last tab with middle click (#648)
* Bugfix: don't allow closing last tab with middle click
(Also add a few of the TODOs for similar areas)

* Replaced MSFT TODO with GitHub TODO
2019-05-10 15:11:23 -07:00
Matthew
5c707032a7 Make powershell the default profile (#639)
* Make powershell the default profile

Sets powershell as the default profile.

* Apply suggestions from code review

Co-Authored-By: Gabriel <gabriel@potter.fr>

* Update src/cascadia/TerminalApp/CascadiaSettings.cpp

Co-Authored-By: Gabriel <gabriel@potter.fr>

* Change profile order
2019-05-10 15:09:22 -07:00
Dustin L. Howett (MSFT)
660d31ac52 Add a dev manifest, which will be used by default (#558)
* Add a dev manifest, which will be used by default

To build a package named Microsoft.WindowsTerminal, you must build with
/p:WindowsTerminalReleaseBuild=true. This is to improve the SxS
developer/user scenario.

* Change dev manifest version to 0.0.1.0.
2019-05-10 11:56:06 -07:00
Carlos Zamora
644cd3ec6c Bugfix: ESC didn't clear selection (except CMD) (#647)
* Bugfix: ESC didn't clear selection (except CMD)

* Bugfix: ESC didn't clear selection - moved TriggerSelection() to ClearSelection()
2019-05-10 11:16:59 -07:00
Artem Chernousov
99555ef9e9 Check null pointer before fclose (#586)
* Check null pointer before fclose, because fclose(NULL) will lead to undefined behavior

* Update main.cpp

Cast to one code style

* Update main.cpp

Remove redundant ==
2019-05-10 11:02:24 -07:00
Adam Weiss
cafe59d73c Update razzle to use vswhere (#13) (#606)
* Update razzle to use vswhere

* Make vswhere pickup build tools

* Make razzle handle errors better

* Make bcz handle MSBUILD with spaces

* Update readmes to use bcz and fix typo
2019-05-10 10:40:25 -07:00
Hao
f74a9d3e0b add shortcut alt-* for select tab (#623)
* add shortcut alt-* for select tab

* all right, 0 for 10th
2019-05-10 09:48:36 -07:00
Alessandro (Ale) Segala
6c98fc19f5 Lowercase GH org name in .gitmodules (#629)
The `Microsoft` org has been renamed to `microsoft`. While casing isn't an issue with GitHub, just correcting it in case some implementations are case-sensitive.
2019-05-09 12:19:45 -07:00
Ian Frosst
37fd00c822 Add ARM64 output directories to .gitignore (#630)
[skip ci]
2019-05-09 12:18:05 -07:00
何智权
5dc7d0e843 close one tab by press ctrl-w and hide the bar (#628)
close one tab by press ctrl-w and hide the bar

fix #614
2019-05-09 12:32:57 -05:00
lstefano71
32f4f7133c make closeOnExit: true the default (#599)
* make closeOnExit: true the default

* another very similar instance of _closeOnExit
2019-05-09 09:17:33 -05:00
Huo Yaoyuan
af7316c130 Add .vsconfig for required components to build (#566)
[skip ci]
2019-05-08 21:36:26 -07:00
Samuel Kelemen
5a8e746d82 shared: Fix some Spelling issues in InputStateMachine (#588) 2019-05-08 21:35:30 -07:00
Hao
b3b98373c6 doc: Explicitly mention tools document in root README (#495)
[skip ci]
2019-05-08 21:34:57 -07:00
RB
ec44bf0068 doc: improve some grammar in the README (#542)
Grammatical Errors and sentence structure

[skip ci]
2019-05-08 21:34:36 -07:00
jroberts101
cb17115c72 doc: fix some more instances of "it's" (#551)
part 2/2

[skip ci]
2019-05-08 21:31:15 -07:00
jroberts101
2aed13ac37 doc: fix some instances of "it's" (#552)
part 1/2

[skip ci]
2019-05-08 21:30:41 -07:00
Benjamin Staneck
e37ba7a923 doc: actually link to the Azure CI from the badge (#582) 2019-05-08 07:18:29 -07:00
Yan Reznikov
a7404a2df9 Clarify where prerequisites packages are installed in VS, could be unclear as 'packages' is overloaded term (#544) 2019-05-08 08:59:36 -05:00
Xiaoshi Sha
d5b8e7c32f Add repositorypath to NuGet config. (#503) 2019-05-07 13:01:49 -07:00
Michael Niksa
ec38580042 Put Terminal build pipeline badge on README 2019-05-07 12:57:30 -07:00
Hermès BÉLUSCA - MAÏTO
599a8dff0f Fix casts warnings. (#509) 2019-05-07 12:08:26 -07:00
沈嘉欢
e6767acf46 minor readme fix (#494) 2019-05-07 13:51:41 -05:00
Hugh Wells
5948b95cd8 Fix grammatical error (#450)
* Fix grammatical error

* Use American English
2019-05-07 13:51:14 -05:00
Mike Griese
79c74aadff Add an FAQ to the README (#518)
* Add an FAQ to the README

* drastically->dramatically
2019-05-07 11:36:38 -05:00
Aaron
58ec47236d Fix build errors in VS2019 (#449) 2019-05-07 10:59:33 -05:00
SLaks
501a4a5e59 doc: Fix typo (#434) 2019-05-07 08:32:37 -07:00
0xflotus
dda4ef23c8 Update WindowsTestPasses.md (#470) 2019-05-07 08:31:41 -07:00
Lorenz Nickel
1c345515b8 fix: replaced outdated url (#515)
http://colororacle.cartography.ch/ moved to https://colororacle.org/
2019-05-07 10:30:14 -05:00
Jack Owens
590eb1fc91 Grammar fixes/improvements (#511) 2019-05-07 08:30:01 -07:00
David Ralph
b35c801093 Update GitHub URL link (#505)
"console" -> "Terminal"
2019-05-07 10:28:50 -05:00
Mike Griese
688483c3af Add some prerequisites to the readme (#429)
* add some prerequisites to the readme

Add some really basic guidance on how to get started with the Terminal project

* Add note about VS2019
2019-05-07 10:23:47 -05:00
Dustin L. Howett (MSFT)
fc83699c1d ci: check out submodules, too (#512) 2019-05-07 07:57:46 -07:00
Dustin L. Howett (MSFT)
f9f2525c72 build: port our Azure CI pipeline to YAML (#510) 2019-05-07 07:35:43 -07:00
Cyandev
47cebce11c docs: fix ORGANIZATION.md hierarchy issues (#478) 2019-05-07 07:28:50 -07:00
Ganbarukamo41
f1309ee211 tools: add a few more possible locations of MSBuild (#436)
* Add few more possible locations of MSBuild, including VS2019 and VS2017 Professional
2019-05-07 07:27:36 -07:00
Michael Niksa
35229a775d Merge pull request #425 from Microsoft/miniksa-i-want-in
Add myself to list of contacts on README
2019-05-06 11:29:57 -07:00
Michael Niksa
82b9efc1c6 Add myself to list of contacts on README
Hey guys, I want in on the fun. Added my twitter handle and information to the contact list.
2019-05-06 11:26:32 -07:00
Dustin L. Howett
b726a3d05d Add a README and a CODE_OF_CONDUCT 2019-05-05 22:01:21 -07:00
Dustin Howett
23f85d01f0 Merge https://github.com/Microsoft/Console into master 2019-05-02 17:34:28 -07:00
Dustin Howett
4ab4051f63 Merged PR 3219702: Fix elevation by putting Markup & App in the manifest
Related work items: #21424135
2019-05-02 22:47:33 +00:00
Michael Niksa
87e85603b9 Merged PR 3215853: Fix spacing/layout for block characters and many retroactively-recategorized emoji (and more!)
This encompasses a handful of problems with column counting.

The Terminal project didn't set a fallback column counter. Oops. I've fixed this to use the `DxEngine` as the fallback.

The `DxEngine` didn't implement its fallback method. Oops. I've fixed this to use the `CustomTextLayout` to figure out the advances based on the same font and fallback pattern as the real final layout, just without "rounding" it into cells yet.
- `CustomTextLayout` has been updated to move the advance-correction into a separate phase from glyph shaping. Previously, we corrected the advances to nice round cell counts during shaping, which is fine for drawing, but hard for column count analysis.
- Now that there are separate phases, an `Analyze` method was added to the `CustomTextLayout` which just performs the text analysis steps and the glyph shaping, but no advance correction to column boundaries nor actual drawing.

I've taken the caching code that I was working on to improve chafa, and I've brought it into this. Now that we're doing a lot of fallback and heavy lifting in terms of analysis via the layout, we should cache the results until the font changes.

I've adjusted how column counting is done overall. It's always been in these phases:
1. We used a quick-lookup of ranges of characters we knew to rapidly decide `Narrow`, `Wide` or `Invalid` (a.k.a. "I dunno")
2. If it was `Invalid`, we consulted a table based off of the Unicode standard that has either `Narrow`, `Wide`, or `Ambiguous` as a result.
3. If it's still `Ambiguous`, we consult a render engine fallback (usually GDI or now DX) to see how many columns it would take.
4. If we still don't know, then it's `Wide` to be safe.
- I've added an additional flow here. The quick-lookup can now return `Ambiguous` off the bat for some glyph characters in the x2000-x3000 range that used to just be simple shapes but have been retroactively recategorized as emoji and are frequently now using full width color glyphs.
- This new state causes the lookup to go immediately to the render engine if it is available instead of consulting the Unicode standard table first because the half/fullwidth table doesn't appear to have been updated for this nuance to reclass these characters as ambiguous, but we'd like to keep that table as a "generated from the spec" sort of table and keep our exceptions in the "quick lookup" function.

I have confirmed the following things "just work" now:
- The windows logo flag from the demo. (💖🌌😊)
- The dotted chart on the side of crossterm demo (•)
- The powerline characters that make arrows with the Consolas patched font (██)
- An accented é
- The warning and checkmark symbols appearing same size as the X. (✔⚠🔥)

Related work items: #21167256, #21237515, #21243859, #21274645, #21296827
2019-05-02 15:29:10 -07:00
Dustin L. Howett (MSFT)
00bb050826 cleanup: move ISSUE_TEMPLATE to .github/ (#423) 2019-04-30 12:27:12 -07:00
Dustin L. Howett (MSFT)
864f45fa11 Move ColorTool to src/ (#422) 2019-04-30 12:27:06 -07:00
Michael Niksa
723efc70e2 Merge pull request #418 from waf/fix-parser-and-registry-bugs-with-refactor
Fix ColorTool parser and registry bugs, and refactor
2019-04-29 12:12:01 -07:00
Michael Niksa
2d1055d153 Merge pull request #421 from oising/move-readconsoleinputstream-demo
moved readconsoleinputstream to samples folder
2019-04-29 12:10:01 -07:00
oising
987805ebaf moved readconsoleinputstream to samples folder; added readme; updated root readme. 2019-04-29 14:50:38 -04:00
Will Fuqua
f8f4f263a5 standardize casing on PascalCase
Feedback from review. I've decided to go with PascalCase as that's more standard in C# and recommended by MS (see the "Field" row in the table on https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions)
2019-04-26 12:34:55 +07:00
Michael Niksa
2e0fd58bc5 Merge pull request #414 from oising/readconsoleinputstream-demo
ReadConsoleInputStream demo
2019-04-25 10:08:52 -07:00
oising
cfe3eb9624 address PR comments (root namespace) 2019-04-25 10:15:02 -04:00
Rich Turner
370cea5cab Retargeted EchoCon sample project to 17763 now Win10 1809 has shipped (#340) 2019-04-24 17:20:20 -07:00
oising
5bd3f887b1 remove empty xmldoc 2019-04-23 17:40:10 -04:00
oising
2dc178b852 address issues and suggestions in PR review 2019-04-23 17:38:35 -04:00
Will Fuqua
12fff3126b add support for writing foreground / background indices to registry
This functionality was implemented for the "current console" but was never implemented for writing to the registry, which affects all future consoles.
2019-04-23 22:42:32 +07:00
Will Fuqua
b61cb830c3 allow scheme parsers to opt out of attempting to parse a file
This fixes the issue where the INI file parser can throw errors because it's attempting to parse an `.itermcolors` (xml) file.
2019-04-23 22:04:18 +07:00
Will Fuqua
05f518db5b replace mutable public fields with properties
The properties are made readonly where possible, which is possible in almost all cases.
2019-04-23 21:51:05 +07:00
Will Fuqua
7daea0a25c pull logic out of Program.cs
There aren't any user-facing changes in this commit, just pulling logic out of Program.cs. All that remains in Program.cs is command line parsing.

- The functions that wrote to the registry, the console, and the virtual terminal (--xterm) are now in their own files, implementing the `IConsoleTarget` interface
- Move the utility method UIntToColor into ColorScheme, where it can be used as an indexer, e.g. myColorScheme[i] returns a System.Drawing.Color
- The "export to INI" functionality is now in a "SchemeWriters" namespace; Parsers are now in a "SchemeParsers" namespace
- Printing the color table is now in the ColorTable class.
2019-04-23 21:10:16 +07:00
oising
7eea98d4ea add sln file 2019-04-21 13:32:04 -04:00
oising
cade139e0c initial commit for tools/readconsoleinputstream PR 2019-04-21 13:31:36 -04:00
Michael Niksa
619a80ea14 Merge pull request #400 from LokiMidgard/back-and-forground-index-export
Back and forground index export
2019-04-02 10:21:23 -07:00
Patrick Kranz
2661fbe0b9 put console attributes in own variable 2019-04-02 19:09:59 +02:00
Patrick Kranz
a247624e90 encapsule console attributes in struct 2019-04-02 19:09:54 +02:00
Patrick Kranz
3484e07089 Fix spelling of foreground (was forground) 2019-04-02 19:09:36 +02:00
Patrick Kranz
9bf9a6f62c add popup color support to json format 2019-04-02 19:08:53 +02:00
Patrick Kranz
c1e1f5124c Merge branch 'master' into back-and-forground-index-export 2019-04-02 19:04:50 +02:00
Michael Niksa
3990a68770 Merge pull request #402 from LokiMidgard/json-parser-screen-color-support
added Support for parsing screen color in json
2019-04-01 19:16:01 -07:00
Mike Griese
ecea0c9f40 Merge pull request #401 from avdi/master
Document loading colortool schemes from current dir
2019-04-01 07:30:37 -07:00
Patrick Kranz
b38f6ffbd1 Added check if screen or popup colors were not found. 2019-03-29 21:53:50 +01:00
Patrick Kranz
e6500864bc added Support for parsing screen color in json 2019-03-29 21:52:04 +01:00
Avdi Grimm
ee8589110a Document loading colortool schemes from current dir 2019-03-29 15:04:58 -05:00
Patrick Kranz
99f71a0cc5 Export now writes screen and popup indexes in ini 2019-03-29 17:18:26 +01:00
Patrick Kranz
16b1b059a4 added abblity to parse popup and screen color from ini file 2019-03-29 17:09:09 +01:00
Patrick Kranz
cafe71c50b added popup color to scheme 2019-03-29 16:44:41 +01:00
Dustin L. Howett (MSFT)
1145336538 Merge pull request #278 from JakeHL/master
Added location flag to colortool and updated help.
2019-03-28 16:59:38 -07:00
Dustin L. Howett (MSFT)
a30f56645a Merge branch 'master' into master 2019-03-28 16:59:03 -07:00
Jake Langford
14f9cfc389 Removed trailing slashes on schemes directory 2019-03-28 17:20:08 +00:00
Michael Niksa
52ef47533b Merge pull request #378 from devhawk/devhawk/errorsArg
Don't report scheme parse errors by default
2019-02-27 12:57:12 -08:00
Harry Pierson
f3e53f1dac updated resources file 2019-02-27 09:50:08 -08:00
Harry Pierson
ae5be18556 add --errors cmd line arg to enable scheme parsing error reporting 2019-02-27 08:33:06 -08:00
Harry Pierson
4a30b1868b change ISchemeParser ParseScheme reportErrors param default to false 2019-02-27 08:32:45 -08:00
Michael Niksa
7e5c034b7f Merge pull request #314 from Microsoft/signing
Add signing configuration information to repository.
2018-11-29 12:42:22 -08:00
Michael Niksa
058b3f5d19 Austin should be in the list. 2018-11-29 12:38:36 -08:00
Michael Niksa
df1843c87d Add signing configuration information to repository. 2018-11-29 10:32:04 -08:00
AzureAD\JakeLangford
10b05fbe23 fixed trailing slash at the end of path 2018-10-12 09:11:48 +01:00
Jake Langford
66bc1f547e Added location flag to colortool and updated help. Also used path combine for schemes directory 2018-10-10 21:25:36 +01:00
Mike Griese
34ff272cfa Merge pull request #272 from waf/add-conpty-samples-to-readme
Add the new ConPTY samples to the readme
2018-10-08 09:09:01 -07:00
Will Fuqua
9971abf4e4 add the new ConPTY samples to the readme
Now that both the ConPTY samples (https://github.com/Microsoft/console/pull/247 and https://github.com/Microsoft/console/pull/260) are merged, mention them on the main repository README.
2018-10-06 17:49:48 +07:00
Michael Niksa
5456666d35 Merge pull request #260 from waf/add-csharp-conpty-sample
Add csharp conpty sample
2018-10-04 10:18:34 -07:00
Michael Niksa
bb795f5258 Merge pull request #266 from Microsoft/version
Correct version lookup for ES autoincremented value
2018-10-02 11:27:09 -07:00
Michael Niksa
2791753780 Adjust version lookup to use file version info stamp which is automatically incremented/generated by engineering system. 2018-10-02 11:18:00 -07:00
Michael Niksa
e5aa14ea7b Merge pull request #265 from Microsoft/fix-accessdenied-write
Wrap file system export write in try/catch.
2018-10-02 10:49:38 -07:00
Michael Niksa
e25ca32022 Wrap file system export write in try/catch. 2018-10-02 10:43:03 -07:00
Michael Niksa
bb5088ae6c Merge pull request #50 from Microsoft/osc-color
Add support for using ColorTool in WSL
2018-10-02 10:24:38 -07:00
Michael Niksa
4272e9c8e9 These masks were unused after I used the color helper. Removing. 2018-10-02 10:16:36 -07:00
Michael Niksa
15c2e57b96 Use helper and move constant for STD_OUTPUT_HANDLE. Use string interpolation for colors. Use Color object and UIntToColor helper for creating our pattern. Add SetLastError annotations to native functions. 2018-10-02 10:03:26 -07:00
Michael Niksa
0c3872d577 Merge branch 'master' into osc-color 2018-10-02 09:46:57 -07:00
Michael Niksa
7371ed764d Set to LF line endings. 2018-10-02 09:46:15 -07:00
Michael Niksa
f1627dd571 merge master 2018-10-02 09:25:28 -07:00
Michael Niksa
046475f7ab Merge pull request #181 from mikemaccana/master
Adjust 'ANSI 8' color to be more visible against background. Fixes #180
2018-10-02 09:11:49 -07:00
Michael Niksa
2c33edcba9 Merge pull request #236 from Jaykul/feature/schemeslist
Fix UIntTocolor
2018-10-02 09:09:48 -07:00
Will Fuqua
08b436f1a5 move Process and ProcessFactory classes into separate files 2018-09-21 21:54:01 +07:00
Will Fuqua
3a1ee61476 fix exit behavior
old behavior was whenever the user types "exit" to stop the entire terminal, which is not correct (e.g. does not work correctly for nested cmd.exe sessions). Now we wait for the top-level process to exit, which I think is more correct.
Also contains a minor rename, Process -> ProcessFactory, ProcessResources -> Process.
2018-09-21 21:50:18 +07:00
Will Fuqua
bf32b8d48f implement dispose pattern
- Full Dispose Pattern for ProcessResources since it has unmanaged resources
- Basic Dispose Pattern for PseudoConsolePipe since it has managed resources
- Fix naming of iStdOut to hStdOut
- Change parameter order of Process.Start to make more sense
2018-09-21 20:47:18 +07:00
Will Fuqua
637c57473e add c# files
- Move from rather ad-hoc, error-prone resource management to IDisposable, which should give us a bit more enforcement.
- Optimistically remove "buggy" from readme because the known bugs are now fixed! The main source of bugs was the incorrect InitializeProcThreadAttributeList usage.
- Handle ctrl-c by forwarding it to the PseudoConsole
- Handle terminal close when the window close button is used
- Use .NET's CopyTo in the CopyPipeToOutput, it's much simpler code and seems more robust than the ReadFile/WriteFile approach
- Minor refactor to split native APIs to multiple files
2018-09-20 22:13:37 +07:00
Will Fuqua
e09359138e add pinvoke signatures 2018-09-20 22:11:55 +07:00
Will Fuqua
0884a1bb1d add project infrastructure (sln, csproj, readme, etc) 2018-09-20 22:11:24 +07:00
Mike Griese
d8ab20d970 Merge pull request #252 from devhawk/devhawk/concfg-support
Add support for parsing concfg presets
2018-09-12 08:54:03 -07:00
Rich Turner
ac843745fa Add an example application that uses the pseudoconsole APIs (#247)
This sample implements a simple "Echo Console" that illustrates the mechanism by which a caller can directly invoke & communicate with Command-Line applications.

1. Creates two pipes - one for output, the second for output
1. Creates a Pseudo Console attached to the other end of the pipes
1. Creates a child process (an instance of `ping.exe` in this case), attached to the Pseudo Console
1. Creates a thread that reads the input pipe, displaying received text on the screen
2018-09-10 20:07:17 -07:00
Harry Pierson
1e6232b751 Add support for parsing concfg (https://github.com/lukesampson/concfg) presets 2018-09-10 09:38:22 -07:00
Joel Bennett
8dacee626a Fix UIntTocolor 2018-08-19 01:14:29 -04:00
Michael Niksa
da53ff957f Merge pull request #197 from atifaziz/consolidate-schemes-search
Consolidate schemes path search code
2018-06-06 14:54:29 -07:00
Michael Niksa
11a65ef0c7 Update README.md 2018-06-05 10:52:57 -07:00
Atif Aziz
aa20e89a84 Consolidate schemes path search code 2018-05-31 12:58:28 +02:00
Michael Niksa
f334ba68c9 Merge pull request #186 from minhhai2209/patch-1
Fixed Markdown link
2018-05-21 08:09:34 -07:00
minhhai2209
8afb12e747 Fixed Markdown link 2018-05-20 14:39:20 +07:00
Michael Niksa
b3b9f719fa Merge pull request #184 from mikemaccana/patch-4
Markdown fixes, also add a description
2018-05-17 08:18:04 -07:00
Mike MacCana
ae5b5fb5b1 Markdown fixes, also add a description 2018-05-17 11:28:58 +01:00
Michael Niksa
3d0f15d433 Merge pull request #183 from mikemaccana/patch-3
Add link to a visual editor for .itermcolors files
2018-05-16 11:09:53 -07:00
Michael Niksa
d3678caea7 Merge pull request #182 from mikemaccana/patch-1
Add a link to colortool releases page
2018-05-16 11:09:33 -07:00
Mike MacCana
f735286a7d Add link to a visual editor for .itermcolors files 2018-05-16 18:18:56 +01:00
Mike MacCana
aed5f9eae9 Add a link to colortool releases page
For those who just want the zip / don't have a build environment set up.
2018-05-16 18:12:39 +01:00
Mike MacCana
b2ed728bf1 Also update the 'one half dark' dark theme to fix #180 2018-05-16 18:00:25 +01:00
Mike MacCana
8d75ff1bec Adjust 'ANSI 8' color to be more visible against background. Fixes #180 2018-05-16 17:47:47 +01:00
Michael Niksa
a9973139e4 Merge pull request #164 from Hsn723/master
Change listing of available schemes to use directory of executable
2018-04-30 08:26:32 -07:00
Natsumi Hoshino
78e50fcf26 Change listing of available schemes to use directory of executable 2018-04-26 23:11:28 +09:00
Michael Niksa
5885732a2d Merge branch 'yatli-master' 2018-04-25 15:57:17 -07:00
Michael Niksa
ae4e0086a9 Merge branch 'master' of https://github.com/yatli/console into yatli-master
Changes made by <miniksa> to make it fit on merging.
2018-04-25 15:56:59 -07:00
Michael Niksa
3f76c471d0 Merge branch 'hugo-vrijswijk-master' 2018-04-25 15:45:38 -07:00
Michael Niksa
d6e77edc27 Add error suppression to make listing work better. 2018-04-25 15:37:40 -07:00
Michael Niksa
1465871e98 Merge pull request #161 from twsouthwick/turn-off-attributes
Disable automatic assembly info generation
2018-04-25 15:26:29 -07:00
Taylor Southwick
198c75cf26 Disable automatic assembly info generation 2018-04-25 15:24:59 -07:00
Michael Niksa
550e197684 Merge. 2018-04-25 15:11:14 -07:00
Michael Niksa
4c3065fec7 Merge pull request #26 from twsouthwick/use-new-csproj
Use simpler csproj format
2018-04-25 14:26:22 -07:00
Michael Niksa
cad9d55e41 Merge branch 'twsouthwick-dynamic-parser-list' 2018-04-25 14:23:34 -07:00
Michael Niksa
21b618648b Merge branch 'dynamic-parser-list' of https://github.com/twsouthwick/console into tsouthwich-dynamic-parser-list 2018-04-25 14:22:15 -07:00
Michael Niksa
d66df17bcb Merge pull request #19 from Microsoft/dev/zadjii/f/output-current
Add a switch for exporting settings
2018-04-25 14:18:48 -07:00
Michael Niksa
ddb74ef5db Fix typo from PR feedback. 2018-04-25 14:17:38 -07:00
Yatao Li
49b3df3d71 weighted RGB distance works well 2018-02-14 23:01:37 +08:00
Yatao Li
22dd8a8e01 colortool: add support for fg/bg color slot designation 2018-02-14 22:40:28 +08:00
Mike Griese
e0248749eb Merge pull request #33 from bvli/master
Check for VS 2017 Professional in build.bat
2018-01-19 13:59:51 -08:00
Mike Griese
eba68a04b6 Enable setting the colors w/ VT even on windows 2018-01-19 13:51:56 -08:00
Mike Griese
85e347fc4a I guess I didn't need this after all 2018-01-19 13:32:45 -08:00
Mike Griese
9a5393d49d Fix printing the table in vt mode 2018-01-19 13:31:01 -08:00
Mike Griese
48021e1d75 fix the xterm version 2017-10-23 10:02:08 -07:00
Mike Griese
5b47a58a3a Prototype support for WSL 2017-10-13 16:09:49 -07:00
Bjarke Lindberg
889e19a05a Check for VS 2017 Professional in build.bat 2017-10-13 11:03:00 +02:00
Rich Turner
dc9cab01cd Update README.md 2017-10-08 14:34:48 -07:00
Rich Turner
f052ca5dc4 Updated readme 2017-10-08 14:30:31 -07:00
Rich Turner
e9d52f7f0f Update ISSUE_TEMPLATE.md 2017-09-11 14:49:09 -07:00
Rich Turner
a8c1100bfa Created 2017-09-11 14:46:11 -07:00
Hugo van Rijswijk
fe7a2d00fa Add option to view available color schemes 2017-09-11 20:19:33 +02:00
Taylor Southwick
7ae6ee6e00 Use simpler csproj format 2017-08-28 10:20:25 -07:00
Taylor Southwick
9bd6053664 Dynamically generate list of parsers 2017-08-28 10:06:45 -07:00
Mike Griese
cf4780a4a2 Merge pull request #21 from Nacimota/master
Fix typos in colortool help message and README
2017-08-23 13:02:29 -07:00
Lachlan Picking
c12f5a9157 Fix typos in colortool help message and README 2017-08-18 10:49:01 +10:00
Mike Griese
ce43f9af2a Merge pull request #17 from metathinker/master
ColorTool: Restore old console colors after printing the color table
2017-08-15 09:10:26 -07:00
Mike Griese
fed6302eeb Merge pull request #18 from metathinker/all.bat
ColorTool: Fix the included all.bat batch file
2017-08-15 09:08:13 -07:00
Michael Ratanapintha
f757ecaa29 ColorTool: Fix the included all.bat batch file
This batch file doesn't work if you use build.bat to build
the program, as all.bat looks for ct.exe rather than colortool.exe.
Fortunately, fixing the batch file is almost as easy as working around
its bug manually.
2017-08-14 21:49:42 -07:00
Michael Ratanapintha
4964aad780 ColorTool: Restore old console colors after printing the console table
Unfortunately, when you run `.\colortool.exe --current`,
you might notice that the color of the prompt printed after
the program finishes is slightly different from what it was
before you ran the program.

This changelist fixes the issue by restoring the old console colors
after the program finishes printing the color table.

Testing: manual
2017-08-14 21:44:53 -07:00
Mike Griese
067cf3a29b Add a switch for exporting settings
As suggested by #6
2017-08-14 13:29:39 -07:00
Michael Niksa
7899586b81 Merge pull request #16 from Microsoft/buildbadge
Add build badge for color tool master.
2017-08-14 12:36:27 -07:00
Michael Niksa
27fa917ada Add a table header and see if *that* triggers GitHub to make a markdown table. 2017-08-14 12:35:23 -07:00
Michael Niksa
dd4f1fd296 try to trigger github markdown for tables by removing end | 2017-08-14 12:34:26 -07:00
Michael Niksa
b1fb72f237 Add build badge for color tool master. 2017-08-14 12:31:11 -07:00
Gilles Khouzam
bcb46c1e8d Merge pull request #12 from csrakowski/VS-Build-Tools
Added support for using VS 2017 Build Tools
2017-08-14 10:28:26 -07:00
Gilles Khouzam
864c54f918 Merge pull request #11 from xanthalas/fix_quiet_variable_name
Fix variable name (from quite to quiet).
2017-08-14 10:27:52 -07:00
Gilles Khouzam
1a6f3f89c8 Merge pull request #9 from dpodder/use-default-msbuild
Default to msbuild.exe on PATH if found
2017-08-14 10:27:33 -07:00
Gilles Khouzam
32e5d40777 Merge pull request #8 from oising/colortool-retarget-net46
retarget csproj from net47 to net46
2017-08-14 10:25:50 -07:00
Gilles Khouzam
1b875aafb3 Merge pull request #1 from MindGirl/xmlschemaparser
Use invariant culture for parsing double values
2017-08-14 10:25:22 -07:00
Gilles Khouzam
21dea14db1 Merge pull request #3 from Nacimota/master
Thank you
2017-08-14 10:22:48 -07:00
Christiaan Rakowski
7ed7650a6b Added support for using VS 2017 Build Tools 2017-08-13 22:23:57 +02:00
Xanthalas
10d1f05c8d Fix variable name (from quite to quiet). 2017-08-13 11:13:18 +01:00
Daniel Podder
a88194e891 Default to msbuild.exe on PATH if found 2017-08-12 17:21:00 -07:00
oising
0a423074a3 retarget csproj from net47 to net46 2017-08-12 14:22:08 -04:00
Lachlan Picking
c0f5c3e149 Update README 2017-08-12 22:51:43 +10:00
Lachlan Picking
b0d43f8067 Add an option to print the colour table for the current scheme 2017-08-12 22:48:42 +10:00
Jutta Klebe
a313826830 Use invariant culture for parsing double values 2017-08-12 14:18:31 +02:00
Gilles Khouzam
1be2939f25 Population of the console repository with the ColorTool 2017-08-11 16:35:53 -07:00
159 changed files with 7768 additions and 443 deletions

39
.github/ISSUE_TEMPLATE/Bug_Report.md vendored Normal file
View File

@@ -0,0 +1,39 @@
---
name: Bug report 🐛
about: Report errors or unexpected behavior
title: "Bug Report"
labels: ''
assignees: ''
---
<!--
This bug tracker is monitored by Windows Terminal development team and other technical folks.
**Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**.
Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue.
Please use this form and describe your issue, concisely but precisely, with as much detail as possible.
-->
# Environment
```none
Windows build number: [run "ver" at a command prompt]
Windows Terminal version (if applicable):
Any other software?
```
# Steps to reproduce
<!-- A description of how to trigger this bug. -->
# Expected behavior
<!-- A description of what you're expecting, possibly containing screenshots or reference material. -->
# Actual behavior
<!-- What's actually happening? -->

View File

@@ -0,0 +1,10 @@
---
name: Documentation Issue 📚
about: Report issues in our documentation
title: "Documentation Issue"
labels: Issue-Docs
assignees: ''
---
<!-- Briefly describe which document needs to be corrected and why. -->

View File

@@ -0,0 +1,21 @@
---
name: Feature Request/Idea 🚀
about: Suggest a new feature or improvement (this does not mean you have to implement it)
title: "Feature Request"
labels: Issue-Feature
assignees: ''
---
# Summary of the new feature/enhancement
<!--
A clear and concise description of what the problem is that the new feature would solve.
Describe why and how a user would use this new functionality (if applicable).
-->
# Proposed technical implementation details (optional)
<!--
A clear and concise description of what you want to happen.
-->

View File

@@ -0,0 +1,10 @@
---
name: Community Guidance Request ✨
about: Suggest somewhere the Windows Terminal Team needs to provide community guidance through new documentation or process.
title: "Guidance"
labels: Issue-Docs
assignees: 'bitcrazed'
---
<!-- What needs to change? Who is responsible for it? Why is it an open question? -->

16
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
## References
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

61
.gitignore vendored
View File

@@ -17,10 +17,12 @@
[Rr]eleases/
x64/
x86/
ARM64/
build/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
objfre/
objchk/
@@ -79,14 +81,18 @@ _Chutzpah*
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
@@ -111,6 +117,7 @@ _TeamCity*
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
@@ -138,13 +145,16 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
## TODO: Comment the next line if you want to checkin your
## web deploy settings but do note that will include unencrypted
## passwords
#*.pubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
@@ -153,13 +163,23 @@ publish/
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Windows Azure Build Output
# Microsoft Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
@@ -211,12 +231,25 @@ FakesAssemblies/
# Visual Studio 6 workspace options file
*.opt
# LightSwitch generated files
GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
*.opendb
*.db
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
*.exe
# Windows Build System files
build*.dbb
@@ -238,7 +271,7 @@ MSG*.bin
# python
*.pyc
**Generated Files/
**/Generated Files/
**/Merged/*
**/Unmerged/*
profiles.json

4
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "dep/gsl"]
path = dep/gsl
url = https://github.com/Microsoft/gsl
url = https://github.com/microsoft/gsl
[submodule "dep/wil"]
path = dep/wil
url = https://github.com/Microsoft/wil
url = https://github.com/microsoft/wil

4
.nuget/packages.config Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="vswhere" version="2.6.7" />
</packages>

36
.vsconfig Normal file
View File

@@ -0,0 +1,36 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Component.CoreEditor",
"Microsoft.VisualStudio.Workload.CoreEditor",
"Microsoft.VisualStudio.Workload.Universal",
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
"Microsoft.VisualStudio.Component.NuGet",
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
"Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
"Microsoft.Net.Component.4.TargetingPack",
"Microsoft.Net.Component.4.5.TargetingPack",
"Microsoft.VisualStudio.Component.DiagnosticTools",
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
"Microsoft.VisualStudio.Component.Windows10SDK.18362",
"Microsoft.VisualStudio.ComponentGroup.UWP.Support",
"Microsoft.VisualStudio.Component.VC.CoreIde",
"Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
"Microsoft.VisualStudio.Component.Graphics",
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
"Microsoft.VisualStudio.Component.VC.v141.x86.x64",
"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"
]
}

8
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,8 @@
# Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments.
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
[conduct-email]: mailto:opencode@microsoft.com

View File

@@ -1,14 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
<!-- Add repositories here to the list of available repositories -->
<!-- Dependencies that we must carry because they're not on public nuget feeds right now. -->
<add key="Static Package Dependencies" value="dep\packages" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
<add key="OpenConsole - Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/OpenConsole/nuget/v3/index.json" />-->
</packageSources>
<packageSources>
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
<!-- Add repositories here to the list of available repositories -->
<!-- Dependencies that we must carry because they're not on public nuget feeds right now. -->
<add key="Static Package Dependencies" value="dep\packages" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
<add key="OpenConsole - Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/OpenConsole/nuget/v3/index.json" />-->
</packageSources>
<config>
<add key="repositorypath" value=".\packages" />
</config>
</configuration>

217
README.md
View File

@@ -1,45 +1,172 @@
# Welcome to the Console Project!
This project is currently controlled by the Windows Developer Platform Tools & Runtimes' Open Source Software team (*WDG > DEP > DART > OSS*).
Our team can be reached at `dartcon@microsoft.com`.
The code is stored at <https://microsoft.visualstudio.com/Dart/_git/OpenConsole>.
The area path within the Microsoft.VisualStudio.com database for our Work Items is `OS\CORE-OS Core\DEP-Developer Ecosystem Platform\DART-Developer Tools and Runtimes\Open Source Software\Console`.
## Jumping In
To get started, feel free to read up on some of our documentation on the way we get things done and hop in.
Make a branch off of `dev/main` for yourself of the pattern `dev/myalias/foo` and feel free to push it to the server to get automatic builds and unit test runs.
Choose a bit of code to clean up, try to add a new feature, or improve something that you try to use every day.
When you are ready, use the [web portal](https://microsoft.visualstudio.com/Dart/_git/OpenConsole/pullrequests) to send a pull request into our `dev/main` branch and we'll be happy to help you get your code in line with the rest of the console.
## Building
OpenConsole uses submodules for some of its dependencies. To make sure submodules are restored or updated:
```
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:
```
nuget.exe restore OpenConsole.sln
msbuild.exe OpenConsole.sln
```
We provide a set of convienence scripts in the /tools directory to help automate the process of building and running tests.
## Assorted Notes
Here's some assorted notes on the way we do things. If you learn something about how we do things, feel free to contribute to any of our documentation files anywhere in the repository (or make some new ones!) This is a work in progress as we try to learn what we'll need to train people on in order to be effective contributors to our project. We're pretty blind to these things after staring at this code for so long... so mind the gaps and ask us plenty of questions!
* [Coding Style](./doc/STYLE.md)
* [Code Organization](./doc/ORGANIZATION.md)
* [Exceptions in our legacy codebase](./doc/EXCEPTIONS.md)
* [Helpful smart pointers and macros for interfacing with Windows in WIL](./doc/WIL.md)
# Welcome\!
#### This repository contains the source code for:
* Windows Terminal
* The Windows console host (`conhost.exe`)
* Components shared between the two projects
* [ColorTool](https://github.com/Microsoft/Terminal/tree/master/src/tools/ColorTool)
* [Sample projects](https://github.com/Microsoft/Terminal/tree/master/samples) that show how to consume the Windows Console APIs
#### Other related repositories include:
* [Console API Documentation](https://github.com/MicrosoftDocs/Console-Docs/issues)
### Build Status
Project|Build Status
---|---
Terminal|[![Build Status](https://dev.azure.com/ms/Terminal/_apis/build/status/Terminal%20CI?branchName=master)](https://dev.azure.com/ms/Terminal/_build?definitionId=136)
ColorTool|![](https://microsoft.visualstudio.com/_apis/public/build/definitions/c93e867a-8815-43c1-92c4-e7dd5404f1e1/17023/badge)
# Terminal & Console Overview
Please take a few minutes to review the overview below before diving into the code:
## Windows Terminal
Windows Terminal is a new, modern, feature-rich, productive terminal application for command-line users. It includes many of the features most frequently requested by the Windows command-line community including support for tabs, rich text, globalization, configurability, theming & styling, and more.
The Terminal will also need to meet our goals and measures to ensure it remains fast, and efficient, and doesn't consume vast amounts of memory or power.
## The Windows console host
The Windows console host, `conhost.exe`, is Windows' original command-line user experience. It implements Windows' command-line infrastructure, and is responsible for hosting the Windows Console API, input engine, rendering engine, and user preferences. The console host code in this repository is the actual source from which the `conhost.exe` in Windows itself is built.
Console's primary goal is to remain backwards-compatible with existing console subsystem applications.
Since assuming ownership of the Windows command-line in 2014, the team has added several new features to the Console, including window transparency, line-based selection, support for [ANSI / Virtual Terminal sequences](https://en.wikipedia.org/wiki/ANSI_escape_code), [24-bit color](https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/), a [Pseudoconsole ("ConPTY")](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/), and more.
However, because the Console's primary goal is to maintain backward compatibility, we've been unable to add many of the features the community has been asking for, and which we've been wanting to add for the last several years--like tabs!
These limitations led us to create the new Windows Terminal.
## Shared Components
While overhauling the Console, we've modernized its codebase considerably. We've cleanly separated logical entities into modules and classes, introduced some key extensibility points, replaced several old, home-grown collections and containers with safer, more efficient [STL containers](https://docs.microsoft.com/en-us/cpp/standard-library/stl-containers?view=vs-2019), and made the code simpler and safer by using Microsoft's [WIL](https://github.com/Microsoft/wil) header library.
This overhaul work resulted in the creation of several key components that would be useful for any terminal implementation on Windows, including a new DirectWrite-based text layout and rendering engine, a text buffer capable of storing both UTF-16 and UTF-8, and a VT parser/emitter.
## Building a new terminal
When we started building the new terminal application, we explored and evaluated several approaches and technology stacks. We ultimately decided that our goals would be best met by sticking with C++ and sharing the aforementioned modernized components, placing them atop the modern Windows application platform and UI framework.
Further, we realized that this would allow us to build the terminal's renderer and input stack as a reusable Windows UI control that others can incorporate into their applications.
# FAQ
## Where can I download Windows Terminal?
### There are no binaries to download quite yet.
The Windows Terminal is in the _very early_ alpha stage, and not ready for the general public quite yet. If you want to jump in early, you can try building it yourself from source.
Otherwise, you'll need to wait until Mid-June for an official preview build to drop.
## 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.
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?
Firstly, make sure you're building & deploying `CascadiaPackage` in Visual Studio, _NOT_ `Host.EXE`. `OpenConsole.exe` is just `conhost.exe`, the same old console you know and love. `opencon.cmd` will launch `openconsole.exe`, and unfortunately, `openterm.cmd` is currently broken.
Secondly, try pressing <kbd>Ctrl</kbd> + <kbd>T</kbd>. The tabs are hidden when you only have one tab by default. In the future, the UI will be dramatically different, but for now, the defaults are _supposed_ to look like the console defaults.
## I tried running WindowsTerminal.exe and it crashes!
* Don't try to run it unpackaged. Make sure to build & deploy `CascadiaPackage` from Visual Studio, and run the Windows Terminal (Dev Build) app.
* Make sure you're on the right version of Windows. You'll need to be on Insider's builds, or wait for the 1903 release, as the Windows Terminal **REQUIRES** features from the latest Windows release.
# Getting Started
## 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:
- 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
* You must also [enable Developer Mode in the Windows Settings app](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) to locally install and run the Terminal app.
## Debugging
* To debug in VS, right click on CascadiaPackage (from VS Solution Explorer) and go to properties, in the Debug menu, change "Application process" and "Background task process" to "Native Only"
## Contributing
We are excited to work alongside you, our amazing community, to build and enhance Windows Terminal\!
We ask that **before you start work on a feature that you would like to contribute, <span class="underline">please file an issue</span> describing your proposed change**: We will be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort.
> 👉 **Remember\!** Your contributions may be incorporated into future versions of Windows\! Because of this, all pull requests will be subject to the same level of scrutiny for quality, coding standards, performance, globalization, accessibility, and compatibility as those of our internal contributors.
> ⚠ **Note**: The Command-Line Team is actively working out of this repository and will be periodically re-structuring the code to make it easier to comprehend, navigate, build, test, and contribute to, so **DO expect significant changes to code layout on a regular basis**.
## Documentation
All documentation is located in the `./docs` folder. If you would like to contribute to the documentation, please submit a pull request.
## Communicating with the Team
The easiest way to communicate with the team is via GitHub issues. Please file new issues, feature requests and suggestions, but **DO search for similar open/closed pre-existing issues before you do**.
Please help us keep this repository clean, inclusive, and fun\! We will not tolerate any abusive, rude, disrespectful or inappropriate behavior. Read our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/) for more details.
If you would like to ask a question that you feel doesn't warrant an issue (yet), please reach out to us via Twitter:
* Rich Turner, Program Manager: [@richturn\_ms](https://twitter.com/richturn_ms)
* Dustin Howett, Engineering Lead: [@dhowett](https://twitter.com/DHowett)
* Michael Niksa, Senior Developer: [@michaelniksa](https://twitter.com/MichaelNiksa)
* Kayla Cinnamon, Program Manager (especially for UX issues): [@cinnamon\_msft](https://twitter.com/cinnamon_msft)
# Developer Guidance
## Building the Code
This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for some of its dependencies. To make sure submodules are restored or updated, be sure to run the following prior to building:
```shell
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:
```shell
.\tools\razzle.cmd
bcz
```
We've provided a set of convenience scripts as well as [README](./tools/README.md) in the **/tools** directory to help automate the process of building and running tests.
## Coding Guidance
Please review these brief docs below relating to our coding standards etc.
> 👉 If you find something missing from these docs, feel free to contribute to any of our documentation files anywhere in the repository (or make some new ones\!)
This is a work in progress as we learn what we'll need to provide people in order to be effective contributors to our project.
- [Coding Style](https://github.com/Microsoft/Terminal/blob/master/doc/STYLE.md)
- [Code Organization](https://github.com/Microsoft/Terminal/blob/master/doc/ORGANIZATION.md)
- [Exceptions in our legacy codebase](https://github.com/Microsoft/Terminal/blob/master/doc/EXCEPTIONS.md)
- [Helpful smart pointers and macros for interfacing with Windows in WIL](https://github.com/Microsoft/Terminal/blob/master/doc/WIL.md)
# Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments.
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
[conduct-email]: mailto:opencode@microsoft.com

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Taef.TestAdapter" version="10.30.180808002" />
</packages>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="TAEF Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/Taef/nuget/v3/index.json" />
</packageSources>
<config>
<add key="repositorypath" value="..\..\packages" />
</config>
</configuration>

28
build/pipelines/ci.yml Normal file
View File

@@ -0,0 +1,28 @@
trigger:
batch: true
branches:
include:
- master
paths:
exclude:
- doc/*
- samples/*
- tools/*
pr:
branches:
include:
- master
# 0.0.yyMM.dd##
# 0.0.1904.0900
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
jobs:
- template: ./templates/build-console-ci.yml
parameters:
platform: x64
- template: ./templates/build-console-ci.yml
parameters:
platform: x86

View File

@@ -0,0 +1,34 @@
trigger: none
pr: none
variables:
baseYearForVersioning: 2019 # Used by build-console-int
versionMajor: 0
versionMinor: 1
# When we move off PackageES for Versioning, we'll need to switch
# name to this format. For now, though, we need to use DayOfYear.Rev
# to unique our builds, as mandated by PackageES's Setup task.
# name: '$(versionMajor).$(versionMinor).$(DayOfYear)$(Rev:r).0'
#
# Build name/version number above must end with .0 to make the
# store publication machinery happy.
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
jobs:
- template: ./templates/build-console-int.yml
parameters:
platform: x64
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
- template: ./templates/build-console-int.yml
parameters:
platform: x86
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
- template: ./templates/build-console-int.yml
parameters:
platform: arm64
additionalBuildArguments: /p:WindowsTerminalReleaseBuild=true
- template: ./templates/release-sign-and-bundle.yml

View File

@@ -0,0 +1,17 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}
displayName: Build ${{ parameters.platform }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
pool: { vmImage: vs2017-win2016 }
steps:
- template: build-console-steps.yml
parameters:
additionalBuildArguments: ${{ parameters.additionalBuildArguments }}

View File

@@ -0,0 +1,30 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}
displayName: Build ${{ parameters.platform }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
pool:
name: Package ES Lab E
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- template: build-console-steps.yml
parameters:
additionalBuildArguments: "/p:XesUseOneStoreVersioning=true;XesBaseYearForStoreVersion=$(baseYearForVersioning) ${{ parameters.additionalBuildArguments }}"

View File

@@ -0,0 +1,95 @@
parameters:
additionalBuildArguments: ''
steps:
- checkout: self
submodules: true
clean: true
- task: NuGetToolInstaller@0
displayName: Ensure NuGet 4.8.1
inputs:
versionSpec: 4.8.1
- task: VisualStudioTestPlatformInstaller@1
displayName: Ensure VSTest Platform
# 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
- task: VSTest@2
displayName: 'Run Unit Tests'
inputs:
testAssemblyVer2: |
$(BUILD.SOURCESDIRECTORY)\**\*unit.test*.dll
!**\obj\**
runSettingsFile: '$(BUILD.SOURCESDIRECTORY)\src\unit.tests.$(BuildPlatform).runsettings'
codeCoverageEnabled: true
runInParallel: False
testRunTitle: 'Console Unit Tests'
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
- task: VSTest@2
displayName: 'Run Feature Tests (x64 only)'
inputs:
testAssemblyVer2: |
$(BUILD.SOURCESDIRECTORY)\**\*feature.test*.dll
!**\obj\**
runSettingsFile: '$(BUILD.SOURCESDIRECTORY)\src\unit.tests.$(BuildPlatform).runsettings'
codeCoverageEnabled: true
runInParallel: False
testRunTitle: 'Console Feature Tests'
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
- task: CopyFiles@2
displayName: 'Copy *.appx/*.msix to Artifacts (Non-PR builds only)'
inputs:
Contents: |
**/*.appx
**/*.msix
**/*.appxsym
!**/Microsoft.VCLibs*.appx
TargetFolder: '$(Build.ArtifactStagingDirectory)/appx'
OverWrite: true
flattenFolders: true
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact (appx) (Non-PR builds only)'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/appx'
ArtifactName: 'appx-$(BuildConfiguration)'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

View File

@@ -0,0 +1,70 @@
parameters:
configuration: 'Release'
jobs:
- job: SignDeploy${{ parameters.configuration }}
displayName: Sign and Deploy for ${{ parameters.configuration }}
dependsOn:
- Buildx64
- Buildx86
- Buildarm64
condition: |
and
(
in(dependencies.Buildx64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx86.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildarm64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
variables:
BuildConfiguration: ${{ parameters.configuration }}
AppxProjectName: CascadiaPackage
AppxBundleName: Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle
pool:
name: Package ES Lab E
steps:
- checkout: self
clean: true
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
displayName: 'Component Detection'
- task: DownloadBuildArtifacts@0
displayName: Download AppX artifacts
inputs:
artifactName: 'appx-$(BuildConfiguration)'
itemPattern: |
**/*.appx
**/*.msix
downloadPath: '$(Build.ArtifactStagingDirectory)\appx'
- task: PowerShell@2
displayName: 'Create $(AppxBundleName)'
inputs:
targetType: filePath
filePath: '.\build\scripts\Create-AppxBundle.ps1'
arguments: |
-InputPath "$(Build.ArtifactStagingDirectory)\appx" -ProjectName $(AppxProjectName) -BundleVersion 0.0.0.0 -OutputPath "$(Build.ArtifactStagingDirectory)\$(AppxBundleName)"
- task: PkgESCodeSign@10
displayName: 'Package ES - SignConfig.WindowsTerminal.xml'
inputs:
signConfigXml: 'build\config\SignConfig.WindowsTerminal.xml'
inPathRoot: '$(Build.ArtifactStagingDirectory)'
outPathRoot: '$(Build.ArtifactStagingDirectory)\signed'
- task: PublishBuildArtifacts@1
displayName: 'Publish Signed AppX'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)\signed'
ArtifactName: 'appxbundle-signed-$(BuildConfiguration)'

View File

@@ -11,7 +11,12 @@
"/packages/",
"/ipch/",
"/dep/",
"/.vs/"
"/.vs/",
"/build/",
"/src/cascadia/",
"/.nuget/",
"/.github/",
"/samples/"
],
"SuffixFilters": [
".dbb",

View File

@@ -13,7 +13,7 @@
2. Add matching fields to Settings.hpp
- add getters, setters, the whole drill.
3. Add to the propsheet.
3. Add to the propsheet
- We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization.
- `src/propsheet/registry.cpp`
- `propsheet/registry.cpp@InitRegistryValues` should initialize the default value for the property.
@@ -28,7 +28,7 @@
6. Add the setting to `Menu::s_GetConsoleState`, and `Menu::s_PropertiesUpdate`
Now, your new setting should be stored just like all the other properties.
7. Update the feature test properties to get add the setting as well.
7. Update the feature test properties to get add the setting as well
- `ft_uia/Common/NativeMethods.cs@WinConP`:
- `Wtypes.PROPERTYKEY PKEY_Console_`
- `NT_CONSOLE_PROPS`
@@ -36,5 +36,5 @@ Now, your new setting should be stored just like all the other properties.
8. Add the default value for the setting to `win32k-settings.man`
- If the setting shouldn't default to 0 or `nullptr`, then you'll need to set the default value of the setting in `win32k-settings.man`.
9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately.
9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately

View File

@@ -2,7 +2,7 @@
## Generation
conhost requests that user32 inject a thread into the attached console application.
conhost requests that user32 injects a thread into the attached console application.
See ntuser's exitwin.c for `CreateCtrlThread`.
## Timeouts

View File

@@ -1,6 +1,6 @@
# Understanding Console Host Settings
Settings in the Windows Console Host can be a bit tricky to understand. This is mostly because the settings system evolved over the course of decades. Before we dig into the details of how settings are persisted, it's probably worth a quick look at what these settings are.
Settings in the Windows Console Host can be a bit tricky to understand. This is mostly because the settings system evolved over the course of decades. Before we dig into the details of how settings are persisted, it's probably worth taking a quick look at what these settings are.
## Settings Description

View File

@@ -6,5 +6,5 @@ This file contains notes about debugging various items in the repository.
If you want to debug code in the Cascadia package via Visual Studio, your breakpoints will not be hit by default. A tweak is required to the *CascadiaPackage* project in order to enable this.
1. Right-click on *CascadiaPackage* in Solution Explorer and select Properties
1. Right-click on *CascadiaPackage* in Solution Explorer and select Properties.
2. Change the *Application process* type from *Mixed (Managed and Native)* to *Native Only*.

View File

@@ -9,8 +9,8 @@ sometimes it's significantly simpler to use them. Given that, we have a set of r
exception use.
## Rules
1. **DO NOT** allow exceptions to leak out of new code into new code
1. **DO** use NTSTATUS or HRESULT as return values as appropriate
1. **DO NOT** allow exceptions to leak out of new code into old code
1. **DO** use NTSTATUS or HRESULT as return values as appropriate (HRESULT is preferred)
1. **DO** Encapsulate all exception behaviors within implementing classes
1. **DO NOT** introduce modern exception throwing code into old code. Instead, refactor as needed to allow encapsulation or
use non-exception based code
@@ -33,4 +33,4 @@ exception use.
### Using WIL for non-throwing modern facilities
###
TODO

View File

@@ -4,7 +4,7 @@
- **Follow the pattern of what you already see in the code**
- Try to package new ideas/components into libraries that have nicely defined interfaces
- Package new ideas into classes or refactor existing ideas into a class as you extend.
- Package new ideas into classes or refactor existing ideas into a class as you extend
- Each project should have a Unit test in a ut_ folder in its subdirectory (like `ut_host`)
- Functional tests should be in ft_ subdirectories (like `ft_api`)
- Build scripts are generally in subdirectories with their type of output (like `/dll` or `/exe`)
@@ -36,16 +36,16 @@
* `/src/host/ft_resize` Special test for resizing/reflowing the buffer window
* `/src/host/ft_uia` Currently disabled (for not being very reliable) UI Automation tests that we are looking to re-enable and expand to do UI Automation coverage of various human interactions
* `/src/host/...` - The files Ill list out below
* `/src/inc` Include files that are shared between the host and some of the other libraries. This is only some of them. The include story is kind of a mess right now, but wed like to clean it up at some point
* `/src/propslib` Library shared between console host and the OS shell “right click a shortcut file and modify console properties” page to read/write user settings to and from the registry and embedded within shortcut LNK data
* `/src/renderer` Refactored extraction of all activities related to rendering the text in the buffers onto the screen
* `/src/renderer/base` Base interface layer providing non-engine-specific rendering things like choosing the data from the console buffer, deciding how to lay out or transform that data, then dispatching commands to a specific final display engine
* `/src/renderer/gdi` The GDI implementation of rendering to the screen. Takes commands to “draw a line” or “fill the background” or “select a region” from the base and turns them into GDI calls to the screen. Extracted from original console host code.
* `/src/renderer/inc Interface definitions for all renderer communication
* `/src/terminal` Virtual terminal support for the console. This is the sequences that are found in-band with other text on STDIN/STDOUT that command the display to do things. This is the *nix way of controlling a console.
* `/src/terminal/parser` This contains a state machine and sorting engine for feeding in individual characters from STDOUT or STDIN and decoding them into the appropriate verbs that should be performed
* `/src/terminal/adapter` This converts the verbs from the interface into calls on the console API. It doesnt actually call through the API (for performance reasons since it lives inside the same binary), but it tries to remain as close to an API call as possible. There are some private extensions to the API for behaviors that didnt exist before this was written that weve not made public. We dont know if we will yet or force people to use VT to get at them.
* `/src/tsf` Text Services Foundation. This provides IME input services to the console. This was historically used for only Chinese, Japanese, and Korean IMEs specifically on OS installations with those as the primary language. It was in the summer of 2016 unrestricted to be able to be used on any OS installation with any IME (whether or not it will display correctly is a different story). It also was unrestricted to allow things like Pen and Touch input (which are routed via IME messages) to display properly inside the console from the TabTip window (the little popup that helps you insert pen/touch writing/keyboard candidates into an application)
* `/src/inc` Include files that are shared between the host and some of the other libraries. This is only some of them. The include story is kind of a mess right now, but wed like to clean it up at some point
* `/src/propslib` Library shared between console host and the OS shell “right click a shortcut file and modify console properties” page to read/write user settings to and from the registry and embedded within shortcut LNK data
* `/src/renderer` Refactored extraction of all activities related to rendering the text in the buffers onto the screen
* `/src/renderer/base` Base interface layer providing non-engine-specific rendering things like choosing the data from the console buffer, deciding how to lay out or transform that data, then dispatching commands to a specific final display engine
* `/src/renderer/gdi` The GDI implementation of rendering to the screen. Takes commands to “draw a line” or “fill the background” or “select a region” from the base and turns them into GDI calls to the screen. Extracted from original console host code.
* `/src/renderer/inc` Interface definitions for all renderer communication
* `/src/terminal` Virtual terminal support for the console. This is the sequences that are found in-band with other text on STDIN/STDOUT that command the display to do things. This is the *nix way of controlling a console.
* `/src/terminal/parser` This contains a state machine and sorting engine for feeding in individual characters from STDOUT or STDIN and decoding them into the appropriate verbs that should be performed
* `/src/terminal/adapter` This converts the verbs from the interface into calls on the console API. It doesnt actually call through the API (for performance reasons since it lives inside the same binary), but it tries to remain as close to an API call as possible. There are some private extensions to the API for behaviors that didnt exist before this was written that weve not made public. We dont know if we will yet or force people to use VT to get at them.
* `/src/tsf` Text Services Foundation. This provides IME input services to the console. This was historically used for only Chinese, Japanese, and Korean IMEs specifically on OS installations with those as the primary language. It was in the summer of 2016 unrestricted to be able to be used on any OS installation with any IME (whether or not it will display correctly is a different story). It also was unrestricted to allow things like Pen and Touch input (which are routed via IME messages) to display properly inside the console from the TabTip window (the little popup that helps you insert pen/touch writing/keyboard candidates into an application)
## Host File Overview

View File

@@ -3,5 +3,5 @@
## Philosophy
1. If it's inserting something into the existing classes/functions, try to follow the existing style as closely as possible.
1. If it's brand new code or refactoring a complete class or area of the code, please follow as Modern C++ of a style as you can and reference the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) as much as you possibly can.
1. When working with any Win32 or NT API, please try to use the [Windows Internal Library](./WIL.md) smart pointers and result handlers.
1. When working with any Win32 or NT API, please try to use the [Windows Implementation Library](./WIL.md) smart pointers and result handlers.
1. The use of NTSTATUS as a result code is discouraged, HRESULT or exceptions are preferred. Functions should not return a status code if they would always return a successful status code. Any function that returns a status code should be marked `noexcept` and have the `nodiscard` attribute.

View File

@@ -7,13 +7,13 @@ Universal Testing is the Microsoft framework for creating and deploying test pac
It involves several parts:
- TESTMD
- These define a package unit for deployment to the test device. This usually includes the test binaries and any dependent data that it will need to execute.
- There can also be a hierarchy where one package can depend on another such that packages can be re-used
- There can also be a hierarchy where one package can depend on another such that packages can be re-used.
- TESTLIST
- This defines a batch of TESTMD packages that should be executed together.
- TESTPASSES
- This defines a list of tests via a TESTLIST and a lab environment configuration on which the tests should be run
- This defines a list of tests via a TESTLIST and a lab environment configuration on which the tests should be run.
These files can either include their child element as they're supposed to (TESTMDs included in TESTLISTs) or they can often include themselves to provide chain structuring (one TESTLIST can reference another TESTLIST).

View File

@@ -1,7 +1,7 @@
# Windows Internal Library
# Windows Implementation Library
## Overview
Windows Internal Library, or WIL, is a header-only library created to help make working with the Windows API more predictable and (hopefully) bug free.
[Windows Implementation Library](https://github.com/Microsoft/wil), or WIL, is a header-only library created to help make working with the Windows API more predictable and (hopefully) bug free.
A majority of functions are in either the `wil::` or `wistd::` namespace. `wistd::` is used for things that have an equivalent in STL's `std::` namespace but have some special functionality like being exception-free. Everything else is in `wil::` namespace.
@@ -9,13 +9,13 @@ The primary usages of WIL in our code so far are...
### Smart Pointers ###
Inside [wil\resource.h](..\dep\wil\resource.h) are smart pointer like classes for many Windows OS resources like file handles, socket handles, process handles, and so on. They're of the form `wil::unique_handle` and call the appropriate/matching OS function (like `CloseHandle()` in this case) when they go out of scope.
Inside [wil\resource.h](https://github.com/microsoft/wil/blob/master/include/wil/resource.h) are smart pointer like classes for many Windows OS resources like file handles, socket handles, process handles, and so on. They're of the form `wil::unique_handle` and call the appropriate/matching OS function (like `CloseHandle()` in this case) when they go out of scope.
Another useful item is `wil::make_unique_nothrow()` which is analogous to `std::make_unique` (except without the exception which might help you integrate with existing exception-free code in the console.) This will return a `wistd::unique_ptr` (vs. a `std::unique_ptr`) which can be used in a similar manner.
### Result Handling ###
To manage the various types of result codes that come back from Windows APIs, the file [wil\result.h](..\dep\wil\result.h) provides a wealth of macros that can help.
To manage the various types of result codes that come back from Windows APIs, the file [wil\result.h](https://github.com/microsoft/wil/blob/master/include/wil/result.h) provides a wealth of macros that can help.
As an example, the method `DuplicateHandle()` returns a `BOOL` value that is `FALSE` under failure and would like you to `GetLastError()` from the operating system to find out what the actual result code is. In this circumstance, you could use the macro `RETURN_IF_WIN32_BOOL_FALSE` to wrap the call to `DuplicateHandle()` which would automatically handle this pattern for you and return the `HRESULT` equivalent on failure.

View File

@@ -16,7 +16,7 @@ The next step would be investigating one of these failures...
A quick overview of investigation... normally you can just attempt to build and reproduce the failure locally with the `OpenConsole` project and it will happen the same way as it did on the nightly build in the lab. However, sometimes the failure will be exclusive to the lab or won't happen in the same way as it does on your local dev machine. At that point, you need to move into setting up the environment as it was during the testpass and figuring out what went wrong.
You can try to do this all manually by pulling down a VM image from the release share for the nightly build, making a VM, deploying the test binaries and TAEF test runnner executables to the machine, installing the VS Remote Debugging or WinDBG tools on the VM, and then running the test and figuring out what's going wrong with the debuggers.
You can try to do this all manually by pulling down a VM image from the release share for the nightly build, making a VM, deploying the test binaries and TAEF test runner executables to the machine, installing the VS Remote Debugging or WinDBG tools on the VM, and then running the test and figuring out what's going wrong with the debuggers.
Or you can use some of the Engineering Systems tools to make this easier. I'll detail how to do that below.
@@ -24,12 +24,12 @@ Prerequisites:
- Visual Studio 2017
- Install the TDP (Test Development Platform) plug-in (see: [https://osgwiki.com/wiki/Test_Development_Platform_(TDP)]).
1. Open Visual Studio 2017 and use the TDP drop-down menu to open the `Device Manager`
1. Open Visual Studio 2017 and use the TDP drop-down menu to open the `Device Manager`.
1. In the pane that opens to the left, choose `Add` and then `Nebula VM Device`. Nebula is a cloud provider for VMs (like Azure but a more private instance for corporate work usage).
1. Name the machine and choose the build/branch/flavor/SKU from the drop downs at the bottom. It will find the VHD for you from the build shares. Hit `Add Device` to deploy to Nebula
1. Name the machine and choose the build/branch/flavor/SKU from the drop downs at the bottom. It will find the VHD for you from the build shares. Hit `Add Device` to deploy to Nebula.
1. Wait a few minutes. It took 5-10 for it to be deployed.
1. Right click the machine name in the `Device Manager` list and choose `Launch T-Shell`. You can also use `Connect via Console` to get a "remote desktop"-like session to the KVM port on the VM.
1. In T-shell, use `testd Microsoft.Console.TestLab.Desktop.testlist` or a command of that format with a different TESTLIST or TESTMD name from our project (see the [UniversalTest.md] documentation). The `testd` utility will automatically resolve the build/branch/flavor information, dig through the build shares for the matching TESTLIST/TESTMD metadata, and attempt to deploy all relevent packages and dependencies on the device. When it's successful, it will move onto running all the tests and giving you the results. On conclusion, the test results should pop up in the web browser or the `Hubble - Log Viewer` tool provided by the Engineering Systems team.
1. In T-shell, use `testd Microsoft.Console.TestLab.Desktop.testlist` or a command of that format with a different TESTLIST or TESTMD name from our project (see the [UniversalTest.md] documentation). The `testd` utility will automatically resolve the build/branch/flavor information, dig through the build shares for the matching TESTLIST/TESTMD metadata, and attempt to deploy all relevant packages and dependencies on the device. When it's successful, it will move onto running all the tests and giving you the results. On conclusion, the test results should pop up in the web browser or the `Hubble - Log Viewer` tool provided by the Engineering Systems team.
If some of the above things do not work, go to [https://osgwiki.com] and type them into the Search bar. For instance, if T-Shell isn't found or working, you can find out where to get it or download it on `OSGWiki`. The same goes for the other commands besides `testd` to use in T-shell and more information on what `Hubble` or `Nebula` are.

View File

@@ -8,18 +8,18 @@ Openconsole can be built with Visual Studio or from the command line. There are
The cmd scripts are set up to emulate a portion of the OS razzle build environment. razzle.cmd is the first script that should be run. bcz.cmd will build clean and bz.cmd should build incrementally.
There are also scripts for running the tests:
- runut.cmd - run the unit tests
- runft.cmd - run the feature tests
- runuia.cmd - run the UIA tests
- `runut.cmd` - run the unit tests
- `runft.cmd` - run the feature tests
- `runuia.cmd` - run the UIA tests
## Build with Powershell
Openconsole.psm1 should be loaded with `Import-Module`. From there `Set-MsbuildDevEnvironment` will set up environment variables required to build. There are a few exported functions (look at their documentation for further details):
- Invoke-OpenConsolebuild - builds the solution. Can be passed msbuild arguments.
- Invoke-OpenConsoleTests - runs the various tests. Will run the unit tests by default.
- Start-OpenConsole - starts Openconsole.exe from the output directory. x64 is run by default.
- Debug-OpenConsole - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
- `Invoke-OpenConsolebuild` - builds the solution. Can be passed msbuild arguments.
- `Invoke-OpenConsoleTests` - runs the various tests. Will run the unit tests by default.
- `Start-OpenConsole` - starts Openconsole.exe from the output directory. x64 is run by default.
- `Debug-OpenConsole` - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
## Configuration Types

View File

@@ -6,7 +6,7 @@
## Abstract
It should be possible to configure the terminal so that it doesn't send certain keystrokes as input to the terminal, and instead triggers certain actions. Examples of these actions could be copy/pasting text, opening a new tab, or changing the font size.
This spec describes a mechanism by which we could provide a common implementation of handling keyboard shortcuts like these. This common implementation could then be leveraged and extended by the UX implementation as to handle certain callbacks in the UX layer. For example, The TerminalCore doesn't have a concept of what a tab is, but the keymap abstraction could raise an event such that a WPF app could implement creating a new tab in it's idomatic way, and UWP could implement them in their own way.
This spec describes a mechanism by which we could provide a common implementation of handling keyboard shortcuts like these. This common implementation could then be leveraged and extended by the UX implementation as to handle certain callbacks in the UX layer. For example, The TerminalCore doesn't have a concept of what a tab is, but the keymap abstraction could raise an event such that a WPF app could implement creating a new tab in its idomatic way, and UWP could implement them in their own way.
## Terminology
* **Key Chord**: This is any possible keystroke that a user can input
@@ -28,7 +28,7 @@ This spec describes a mechanism by which we could provide a common implementatio
When the UX frontend is created, it should instantiate a `IKeyBindings` object with the keybindings mapped as it would like.
When it's creating it's platform-dependent terminal component, it can pass the `IKeyBindings` object to that component. The component will then be able to pass that object to the terminal instance.
When it's creating its platform-dependent terminal component, it can pass the `IKeyBindings` object to that component. The component will then be able to pass that object to the terminal instance.
When the terminal component calls `ITerminalInput.SendKeyEvent(uint vkey, KeyModifiers modifiers)`, the terminal will use `IKeyBindings.TryKeyChord` to see if there are any bound actions to that input. If there are, the `IKeyBindings` implementation will either handle the event by interacting with the `ITerminalInput`, or it'll invoke an event that's been registered by the frontend
@@ -66,7 +66,7 @@ partial class Terminal
```
### Project Cascadia Sample
Below is an example of how the Project Cascadia application might implement it's
Below is an example of how the Project Cascadia application might implement its
keybindings.
```csharp
@@ -105,4 +105,4 @@ class KeyBindings : IKeyBindings
How does Copy/paste play into this?
When Input is written to the terminal, and it tries the copy keybinding, what happens?
The Keybindings are global to the frontend, not local to the terminal. Copy/Paste events should also be delegates that get raised, and the frontend can then determine what to do with them. It'll probably query it's active/focused Terminal Component, then Get the `ITerminalInput` from that component, and use that to CopyText / PasteText from the Terminal as needed.
The Keybindings are global to the frontend, not local to the terminal. Copy/Paste events should also be delegates that get raised, and the frontend can then determine what to do with them. It'll probably query its active/focused Terminal Component, then Get the `ITerminalInput` from that component, and use that to CopyText / PasteText from the Terminal as needed.

View File

@@ -34,7 +34,7 @@ This spec will outline how various terminal frontends will be able to interact w
the need for a globals/profiles structure.
6. The Terminal should be able to read information from a settings structure
that's independant of how it's persisted / implemented by the Application
7. The Component should be able to have it's own settings independent of the
7. The Component should be able to have its own settings independent of the
application that's embedding it, such as font size and face, scrollbar
visibility, etc. These should be settings that are specific to the component,
and the Terminal should logically be unaffected by these settings.
@@ -57,7 +57,7 @@ VS needs to be able to persist settings just as a simple set of global settings.
When the application needs to retrieve these settings, they need to use them as a tripartite structure: frontend-component-terminal settings.
Each frontend will have it's own set of settings.
Each frontend will have its own set of settings.
Each component implementation will also ned to have some settings that control it.
The terminal also will have some settings specific to the terminal.
@@ -106,13 +106,13 @@ public interface IApplicationSettings
}
```
The Application can store whatever settings it wants in it's implementation of `IApplicationSettings`. When it instantiates a Terminal Component, it will pass it's `IComponentSettings` to it.
The Application can store whatever settings it wants in its implementation of `IApplicationSettings`. When it instantiates a Terminal Component, it will pass its `IComponentSettings` to it.
The component will retrieve whatever settings it wants from that object, and then pass the `TerminalSettings` to the Terminal it creates.
The frontend will be able to get/set it's settings from the `IApplicationSettings` implementation.
The frontend will be able to create components using the `IComponentSettings` in it's `IApplicationSettings`.
The frontend will be able to get/set its settings from the `IApplicationSettings` implementation.
The frontend will be able to create components using the `IComponentSettings` in its `IApplicationSettings`.
The Component will then create the Terminal using the `TerminalSettings`.
#### Project Cascadia Settings Details

View File

@@ -0,0 +1,36 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2026
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EchoCon", "EchoCon\EchoCon.vcxproj", "{96274800-9574-423E-892A-909FBE2AC8BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{556CAA54-33E0-4F99-95C8-0DFD6E8F6C6B}"
ProjectSection(SolutionItems) = preProject
readme.md = readme.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x64.ActiveCfg = Debug|x64
{96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x64.Build.0 = Debug|x64
{96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x86.ActiveCfg = Debug|Win32
{96274800-9574-423E-892A-909FBE2AC8BE}.Debug|x86.Build.0 = Debug|Win32
{96274800-9574-423E-892A-909FBE2AC8BE}.Release|x64.ActiveCfg = Release|x64
{96274800-9574-423E-892A-909FBE2AC8BE}.Release|x64.Build.0 = Release|x64
{96274800-9574-423E-892A-909FBE2AC8BE}.Release|x86.ActiveCfg = Release|Win32
{96274800-9574-423E-892A-909FBE2AC8BE}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B27C5007-61E2-4080-965D-8C934367BA4F}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,189 @@
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Consle sample application.
// Copyright © 2018, Microsoft
#include "stdafx.h"
#include <Windows.h>
#include <process.h>
// Forward declarations
HRESULT CreatePseudoConsoleAndPipes(HPCON*, HANDLE*, HANDLE*);
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX*, HPCON);
void __cdecl PipeListener(LPVOID);
int main()
{
wchar_t szCommand[]{ L"ping localhost" };
HRESULT hr{ E_UNEXPECTED };
HANDLE hConsole = { GetStdHandle(STD_OUTPUT_HANDLE) };
// Enable Console VT Processing
DWORD consoleMode{};
GetConsoleMode(hConsole, &consoleMode);
hr = SetConsoleMode(hConsole, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
? S_OK
: GetLastError();
if (S_OK == hr)
{
HPCON hPC{ INVALID_HANDLE_VALUE };
// Create the Pseudo Console and pipes to it
HANDLE hPipeIn{ INVALID_HANDLE_VALUE };
HANDLE hPipeOut{ INVALID_HANDLE_VALUE };
hr = CreatePseudoConsoleAndPipes(&hPC, &hPipeIn, &hPipeOut);
if (S_OK == hr)
{
// Create & start thread to listen to the incoming pipe
// Note: Using CRT-safe _beginthread() rather than CreateThread()
HANDLE hPipeListenerThread{ reinterpret_cast<HANDLE>(_beginthread(PipeListener, 0, hPipeIn)) };
// Initialize the necessary startup info struct
STARTUPINFOEX startupInfo{};
if (S_OK == InitializeStartupInfoAttachedToPseudoConsole(&startupInfo, hPC))
{
// Launch ping to emit some text back via the pipe
PROCESS_INFORMATION piClient{};
hr = CreateProcess(
NULL, // No module name - use Command Line
szCommand, // Command Line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Inherit handles
EXTENDED_STARTUPINFO_PRESENT, // Creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&startupInfo.StartupInfo, // Pointer to STARTUPINFO
&piClient) // Pointer to PROCESS_INFORMATION
? S_OK
: GetLastError();
if (S_OK == hr)
{
// Wait up to 10s for ping process to complete
WaitForSingleObject(piClient.hThread, 10 * 1000);
// Allow listening thread to catch-up with final output!
Sleep(500);
}
// --- CLOSEDOWN ---
// Now safe to clean-up client app's process-info & thread
CloseHandle(piClient.hThread);
CloseHandle(piClient.hProcess);
// Cleanup attribute list
DeleteProcThreadAttributeList(startupInfo.lpAttributeList);
free(startupInfo.lpAttributeList);
}
// Close ConPTY - this will terminate client process if running
ClosePseudoConsole(hPC);
// Clean-up the pipes
if (INVALID_HANDLE_VALUE != hPipeOut) CloseHandle(hPipeOut);
if (INVALID_HANDLE_VALUE != hPipeIn) CloseHandle(hPipeIn);
}
}
return S_OK == hr ? EXIT_SUCCESS : EXIT_FAILURE;
}
HRESULT CreatePseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut)
{
HRESULT hr{ E_UNEXPECTED };
HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE };
HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE };
// Create the pipes to which the ConPTY will connect
if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) &&
CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
{
// Determine required size of Pseudo Console
COORD consoleSize{};
CONSOLE_SCREEN_BUFFER_INFO csbi{};
HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) };
if (GetConsoleScreenBufferInfo(hConsole, &csbi))
{
consoleSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1;
consoleSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
// Create the Pseudo Console of the required size, attached to the PTY-end of the pipes
hr = CreatePseudoConsole(consoleSize, hPipePTYIn, hPipePTYOut, 0, phPC);
// Note: We can close the handles to the PTY-end of the pipes here
// because the handles are dup'ed into the ConHost and will be released
// when the ConPTY is destroyed.
if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
}
return hr;
}
// Initializes the specified startup info struct with the required properties and
// updates its thread attribute list with the specified ConPTY handle
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC)
{
HRESULT hr{ E_UNEXPECTED };
if (pStartupInfo)
{
size_t attrListSize{};
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
// Get the size of the thread attribute list.
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
// Allocate a thread attribute list of the correct size
pStartupInfo->lpAttributeList =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
// Initialize thread attribute list
if (pStartupInfo->lpAttributeList
&& InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize))
{
// Set Pseudo Console attribute
hr = UpdateProcThreadAttribute(
pStartupInfo->lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL)
? S_OK
: HRESULT_FROM_WIN32(GetLastError());
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
void __cdecl PipeListener(LPVOID pipe)
{
HANDLE hPipe{ pipe };
HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) };
const DWORD BUFF_SIZE{ 512 };
char szBuffer[BUFF_SIZE]{};
DWORD dwBytesWritten{};
DWORD dwBytesRead{};
BOOL fRead{ FALSE };
do
{
// Read from the pipe
fRead = ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwBytesRead, NULL);
// Write received text to the Console
// Note: Write to the Console using WriteFile(hConsole...), not printf()/puts() to
// prevent partially-read VT sequences from corrupting output
WriteFile(hConsole, szBuffer, dwBytesRead, &dwBytesWritten, NULL);
} while (fRead && dwBytesRead >= 0);
}

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{96274800-9574-423E-892A-909FBE2AC8BE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>EchoCon</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="EchoCon.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EchoCon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
// stdafx.cpp : source file that includes just the standard includes
// EchoCon.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file

View File

@@ -0,0 +1,13 @@
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
// TODO: reference additional headers your program requires here

View File

@@ -0,0 +1,8 @@
#pragma once
// Including SDKDDKVer.h defines the highest available Windows platform.
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
#include <SDKDDKVer.h>

View File

@@ -0,0 +1,34 @@
# "EchoCon" ConPTY Sample App
This is a very simple sample application that illustrates how to use the new Win32 Pseudo Console
(ConPTY) by:
1. Creating an input and an output pipe
1. Calling `CreatePseudoConsole()` to create a ConPTY instance attached to the other end of the pipes
1. Spawning an instance of `ping.exe` connected to the ConPTY
1. Running a thread that listens for output from `ping.exe`, writing received text to the Console
# Pre-Requirements
To build and run this sample, you must install:
* Windows 10 Insider build 17733 or later
* [Latest Windows 10 Insider SDK](https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewSDK)
# Running the sample
Once successfully built, running EchoCon should clear the screen and display the results of the
echo command:
```
Pinging Rincewind [::1] with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
```
# Resources
For more information on the new Pseudo Console infrastructure and API, please review
[this blog post](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/)

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2035
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniTerm", "MiniTerm\MiniTerm.csproj", "{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EFB13BDE-C952-4311-9FE7-35EFDAC8F021}
EndGlobalSection
EndGlobal

View File

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

View File

@@ -0,0 +1,60 @@
<?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>{121D4818-BD57-433B-8AD5-C4E1ACE7E7C0}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MiniTerm</RootNamespace>
<AssemblyName>MiniTerm</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</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.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Native\ConsoleApi.cs" />
<Compile Include="Native\ProcessApi.cs" />
<Compile Include="Native\PseudoConsoleApi.cs" />
<Compile Include="Processes\Process.cs" />
<Compile Include="Processes\ProcessFactory.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PseudoConsole.cs" />
<Compile Include="PseudoConsolePipe.cs" />
<Compile Include="Terminal.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,38 @@
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
namespace MiniTerm.Native
{
/// <summary>
/// PInvoke signatures for win32 console api
/// </summary>
static class ConsoleApi
{
internal const int STD_OUTPUT_HANDLE = -11;
internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
internal const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern SafeFileHandle GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleMode(SafeFileHandle hConsoleHandle, uint mode);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool GetConsoleMode(SafeFileHandle handle, out uint mode);
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
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Runtime.InteropServices;
namespace MiniTerm.Native
{
/// <summary>
/// PInvoke signatures for win32 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 Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 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,33 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
namespace MiniTerm.Native
{
/// <summary>
/// PInvoke signatures for win32 pseudo console 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 ResizePseudoConsole(IntPtr hPC, COORD size);
[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,74 @@
using System;
using System.Runtime.InteropServices;
using static MiniTerm.Native.ProcessApi;
namespace MiniTerm
{
/// <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()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// use the following line if the finalizer is overridden above.
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Runtime.InteropServices;
using static MiniTerm.Native.ProcessApi;
namespace MiniTerm
{
/// <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, "cmd.exe");
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 InvalidOperationException("Could not calculate the number of bytes for the attribute list. " + Marshal.GetLastWin32Error());
}
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 InvalidOperationException("Could not set up attribute list. " + Marshal.GetLastWin32Error());
}
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 InvalidOperationException("Could not set pseudoconsole thread attribute. " + Marshal.GetLastWin32Error());
}
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 InvalidOperationException("Could not create process. " + Marshal.GetLastWin32Error());
}
return pInfo;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace MiniTerm
{
/// <summary>
/// C# version of:
/// https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
///
/// System Requirements:
/// As of September 2018, requires Windows 10 with the "Windows Insider Program" installed for Redstone 5.
/// Also requires the Windows Insider Preview SDK: https://www.microsoft.com/en-us/software-download/windowsinsiderpreviewSDK
/// </summary>
/// <remarks>
/// Basic design is:
/// Terminal UI starts the PseudoConsole, and controls it using a pair of PseudoConsolePipes
/// Terminal UI will run the Process (cmd.exe) and associate it with the PseudoConsole.
/// </remarks>
static class Program
{
static void Main(string[] args)
{
try
{
var terminal = new Terminal();
terminal.Run("cmd.exe");
}
catch (InvalidOperationException e)
{
Console.Error.WriteLine(e.Message);
throw;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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("MiniPty")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MiniPty")]
[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)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("121d4818-bd57-433b-8ad5-c4e1ace7e7c0")]
// 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,39 @@
using Microsoft.Win32.SafeHandles;
using System;
using static MiniTerm.Native.PseudoConsoleApi;
namespace MiniTerm
{
/// <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 InvalidOperationException("Could not create psuedo console. Error Code " + createResult);
}
return new PseudoConsole(hPC);
}
public void Dispose()
{
ClosePseudoConsole(Handle);
}
}
}

View File

@@ -0,0 +1,46 @@
using Microsoft.Win32.SafeHandles;
using System;
using static MiniTerm.Native.PseudoConsoleApi;
namespace MiniTerm
{
/// <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 InvalidOperationException("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,145 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static MiniTerm.Native.ConsoleApi;
namespace MiniTerm
{
/// <summary>
/// The UI of the terminal. It's just a normal console window, but we're managing the input/output.
/// In a "real" project this could be some other UI.
/// </summary>
internal sealed class Terminal
{
private const string ExitCommand = "exit\r";
private const string CtrlC_Command = "\x3";
public Terminal()
{
EnableVirtualTerminalSequenceProcessing();
}
/// <summary>
/// Newer versions of the windows console support interpreting virtual terminal sequences, we just have to opt-in
/// </summary>
private static void EnableVirtualTerminalSequenceProcessing()
{
var hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (!GetConsoleMode(hStdOut, out uint outConsoleMode))
{
throw new InvalidOperationException("Could not get console mode");
}
outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
if (!SetConsoleMode(hStdOut, outConsoleMode))
{
throw new InvalidOperationException("Could not enable virtual terminal processing");
}
}
/// <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>
public void Run(string command)
{
using (var inputPipe = new PseudoConsolePipe())
using (var outputPipe = new PseudoConsolePipe())
using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, (short)Console.WindowWidth, (short)Console.WindowHeight))
using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle))
{
// copy all pseudoconsole output to stdout
Task.Run(() => CopyPipeToOutput(outputPipe.ReadSide));
// prompt for stdin input and send the result to the pseudoconsole
Task.Run(() => CopyInputToPipe(inputPipe.WriteSide));
// free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar)
OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe));
WaitForExit(process).WaitOne(Timeout.Infinite);
}
}
/// <summary>
/// Reads terminal input and copies it to the PseudoConsole
/// </summary>
/// <param name="inputWriteSide">the "write" side of the pseudo console input pipe</param>
private static void CopyInputToPipe(SafeFileHandle inputWriteSide)
{
using (var writer = new StreamWriter(new FileStream(inputWriteSide, FileAccess.Write)))
{
ForwardCtrlC(writer);
writer.AutoFlush = true;
writer.WriteLine(@"cd \");
while (true)
{
// send input character-by-character to the pipe
char key = Console.ReadKey(intercept: true).KeyChar;
writer.Write(key);
}
}
}
/// <summary>
/// Don't let ctrl-c kill the terminal, it should be sent to the process in the terminal.
/// </summary>
private static void ForwardCtrlC(StreamWriter writer)
{
Console.CancelKeyPress += (sender, e) =>
{
e.Cancel = true;
writer.Write(CtrlC_Command);
};
}
/// <summary>
/// Reads PseudoConsole output and copies it to the terminal's standard out.
/// </summary>
/// <param name="outputReadSide">the "read" side of the pseudo console output pipe</param>
private static void CopyPipeToOutput(SafeFileHandle outputReadSide)
{
using (var terminalOutput = Console.OpenStandardOutput())
using (var pseudoConsoleOutput = new FileStream(outputReadSide, FileAccess.Read))
{
pseudoConsoleOutput.CopyTo(terminalOutput);
}
}
/// <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,19 @@
# MiniTerm
Experimental terminal using the new PTY APIs from Microsoft. Written in C#, and heavily based on the native code examples.
## Status
Demonstrates the basic API calls required, but not intended for "real-world" usage.
## Resources
- [Introductory blog post from Microsoft](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/)
- [MSDN Documentation](https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session)
## System Requirements
See the Introductory blog post from Microsoft for the full setup instructions. This project has been tested with:
- OS: Windows 10 Pro Build 17744
- SDK: Windows_InsiderPreview_SDK_en-us_17749

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Samples.Terminal
{
/// <summary>
/// Implements a bounded queue that won't block on overflow; instead the oldest item is discarded.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConcurrentBoundedQueue<T> : ConcurrentQueue<T>
{
public ConcurrentBoundedQueue(int capacity)
{
Capacity = GetAlignedCapacity(capacity);
}
public ConcurrentBoundedQueue(IEnumerable<T> collection, int capacity) : base(collection)
{
Capacity = GetAlignedCapacity(capacity);
}
private int GetAlignedCapacity(int n)
{
if (n < 2)
{
throw new ArgumentException("Capacity must be at least 2");
}
var f = Math.Log(n, 2);
var p = Math.Ceiling(f);
return (int) Math.Pow(2, p);
}
public new void Enqueue(T item)
{
// if we're about to overflow, dump oldest item
if (Count >= Capacity)
{
lock (this)
{
while (Count >= Capacity)
{
TryDequeue(out _);
}
}
}
base.Enqueue(item);
}
public int Capacity
{
get; private set;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Samples.Terminal
{
internal static class NativeMethods
{
private static int MakeHRFromErrorCode(int errorCode)
{
// Don't convert it if it is already an HRESULT
if ((0xFFFF0000 & errorCode) != 0)
{
Debug.Assert(false, "errorCode is already HRESULT");
return errorCode;
}
return unchecked(((int)0x80070000) | errorCode);
}
internal static Exception GetExceptionForWin32Error(int errorCode)
{
return Marshal.GetExceptionForHR(MakeHRFromErrorCode(errorCode));
}
internal static Exception GetExceptionForLastWin32Error()
{
return GetExceptionForWin32Error(Marshal.GetLastWin32Error());
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* This is a demo that shows how we can have a stream-oriented view of characters from the console
* while also listening to console events like mouse, menu, focus, buffer/viewport(1) resize events.
*
* This has always been tricky to do because ReadConsoleW/A doesn't allow retrieving events.
* Only ReadConsoleInputW/A returns events, but isn't stream-oriented. Using both doesn't work because
* ReadConsoleW/A flushes the input queue, meaning calls to ReadConsoleInputW/A will wait forever.
*
* I do this by deriving a new Stream class which wraps ReadConsoleInputW and accepts a provider/consumer
* implementation of BlockingCollection<Kernel32.INPUT_RECORD>. This allows asynchronous monitoring of
* console events while simultaneously streaming the character input. I also use Mark Gravell's great
* System.IO.Pipelines utility classes (2) and David Hall's excellent P/Invoke wrappers (3) to make this
* demo cleaner to read; both are pulled from NuGet.
*
* (1) in versions of windows 10 prior to 1809, the buffer resize event only fires for enlarging
* the viewport, as this would cause the buffer to be enlarged too. Now it fires even when
* shrinking the viewport, which won't change the buffer size.
*
* (2) https://github.com/mgravell/Pipelines.Sockets.Unofficial
* https://www.nuget.org/packages/Pipelines.Sockets.Unofficial
*
* (3) https://github.com/dahall/Vanara
* https://www.nuget.org/packages/Vanara.Pinvoke.Kernel32
*
* Oisin Grehan - 2019/4/21
*
* https://twitter.com/oising
* https://github.com/oising
*/
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Pipelines.Sockets.Unofficial;
using Vanara.PInvoke;
namespace Samples.Terminal
{
internal class Program
{
private static async Task Main(string[] args)
{
// run for 90 seconds
var timeout = TimeSpan.FromSeconds(90);
// in reality this will likely never be reached, but it is useful to guard against
// conditions where the queue isn't drained, or not drained fast enough.
const int maxNonKeyEventRetention = 128;
var source = new CancellationTokenSource(timeout);
var token = source.Token;
var handle = Kernel32.GetStdHandle(Kernel32.StdHandleType.STD_INPUT_HANDLE);
if (!Kernel32.GetConsoleMode(handle, out Kernel32.CONSOLE_INPUT_MODE mode))
throw NativeMethods.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
mode |= Kernel32.CONSOLE_INPUT_MODE.ENABLE_WINDOW_INPUT;
mode |= Kernel32.CONSOLE_INPUT_MODE.ENABLE_VIRTUAL_TERMINAL_INPUT;
mode &= ~Kernel32.CONSOLE_INPUT_MODE.ENABLE_ECHO_INPUT;
mode &= ~Kernel32.CONSOLE_INPUT_MODE.ENABLE_LINE_INPUT;
if (!Kernel32.SetConsoleMode(handle, mode))
throw NativeMethods.GetExceptionForLastWin32Error();
// base our provider/consumer on a bounded queue to keep memory usage under control
var events = new BlockingCollection<Kernel32.INPUT_RECORD>(
new ConcurrentBoundedQueue<Kernel32.INPUT_RECORD>(maxNonKeyEventRetention));
// Task that will consume non-key events asynchronously
var consumeEvents = Task.Run(() =>
{
Console.WriteLine("consumeEvents started");
try
{
while (!events.IsCompleted)
{
// blocking call
var record = events.Take(token);
Console.WriteLine("record: {0}",
Enum.GetName(typeof(Kernel32.EVENT_TYPE), record.EventType));
}
}
catch (OperationCanceledException)
{
// timeout
}
Console.WriteLine("consumeEvents ended");
}, token);
// Task that will watch for key events while feeding non-key events into our provider/consumer collection
var readInputAndProduceEvents = Task.Run(async () =>
{
//So, this is the key point - we cannot use the following or we lose all non-key events:
// Stream stdin = Console.OpenStandardInput();
// get a unicode character stream over console input
Stream stdin = new ReadConsoleInputStream(handle, events);
// wrap in a System.IO.Pipelines.PipeReader to get clean async and span/memory usage
var reader = StreamConnection.GetReader(stdin);
while (!token.IsCancellationRequested)
{
// blocking call
var result = await reader.ReadAsync(token);
if (result.IsCanceled)
break;
var sequence = result.Buffer;
var segment = sequence.Start;
while (sequence.TryGet(ref segment, out var mem))
{
// decode back from unicode
var datum = Encoding.Unicode.GetString(mem.Span);
Console.Write(datum);
}
reader.AdvanceTo(sequence.End);
}
}, token);
Console.WriteLine("Running");
try
{
await Task.WhenAll(consumeEvents, readInputAndProduceEvents);
}
catch (OperationCanceledException)
{
// timeout
}
Console.WriteLine("press any key...");
Console.ReadKey(true);
}
}
}

View File

@@ -0,0 +1,16 @@
# ReadConsoleInputStream Demo
This is a demo that shows how we can have a stream-oriented view of characters from the console while also listening to console events like mouse, menu, focus, buffer/viewport resize events. This is particularly useful when working with VT100 streams and ConPTY.
This has always been difficult to do because ReadConsoleW/A doesn't allow retrieving events. Only ReadConsoleInputW/A returns events, but is not stream-oriented. Using both does not work because ReadConsoleW/A flushes the input queue, meaning calls to ReadConsoleInputW/A will wait forever.
A workaround has been achieved by deriving a new Stream class which wraps ReadConsoleInputW and accepts a provider/consumer implementation of BlockingCollection<Kernel32.INPUT_RECORD>. This allows asynchronous monitoring of console events while simultaneously streaming the character input. Mark Gravell's great System.IO.Pipelines utility classes and David Hall's excellent P/Invoke wrappers are also used to make this demo cleaner to read; both are pulled from NuGet.
**Note:**
In versions of Windows 10 prior to 1809, the buffer resize event only fires for enlarging the viewport, as this would cause the buffer to be enlarged too. Now it fires even when shrinking the viewport, which won't change the buffer size.
NuGet packages used (GitHub links):
* [Pipelines.Sockets.Unofficial](https://github.com/mgravell/Pipelines.Sockets.Unofficial)
* [Vanara P/Invoke](https://github.com/dahall/Vanara)

View File

@@ -0,0 +1,198 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using Vanara.PInvoke;
namespace Samples.Terminal
{
/// <summary>
/// Provides a Stream-oriented view over the console's input buffer key events
/// while also collecting out of band events like buffer resize, menu etc in
/// a caller-provided BlockingCollection.
/// </summary>
/// <remarks>The buffer contains unicode chars, not 8 bit CP encoded chars as we rely on ReadConsoleInputW.</remarks>
public sealed class ReadConsoleInputStream : Stream
{
private const int BufferSize = 256;
private const int BytesPerWChar = 2;
private readonly BlockingCollection<Kernel32.INPUT_RECORD> _nonKeyEvents;
private IntPtr _handle;
/// <summary>
/// Creates an instance of ReadConsoleInputStream over the standard handle for StdIn.
/// </summary>
/// <param name="nonKeyEvents">A BlockingCollection provider/consumer collection for collecting non key events.</param>
public ReadConsoleInputStream(BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents) :
this(Kernel32.GetStdHandle(Kernel32.StdHandleType.STD_INPUT_HANDLE), nonKeyEvents)
{
}
/// <summary>
/// Creates an instance of ReadConsoleInputStream over a caller-provided standard handle for stdin.
/// </summary>
/// <param name="handle">A HFILE handle representing StdIn</param>
/// <param name="nonKeyEvents">A BlockingCollection provider/consumer collection for collecting non key events.</param>
internal ReadConsoleInputStream(HFILE handle,
BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents)
{
Debug.Assert(handle.IsInvalid == false, "handle.IsInvalid == false");
_handle = handle.DangerousGetHandle();
_nonKeyEvents = nonKeyEvents;
}
public override bool CanRead { get; } = true;
public override bool CanWrite => false;
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException("Seek not supported.");
public override long Position
{
get => throw new NotSupportedException("Seek not supported.");
set => throw new NotSupportedException("Seek not supported.");
}
protected override void Dispose(bool disposing)
{
_handle = IntPtr.Zero;
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
{
ValidateRead(buffer, offset, count);
Debug.Assert(offset >= 0, "offset >= 0");
Debug.Assert(count >= 0, "count >= 0");
Debug.Assert(buffer != null, "bytes != null");
// Don't corrupt memory when multiple threads are erroneously writing
// to this stream simultaneously.
if (buffer.Length - offset < count)
throw new IndexOutOfRangeException("IndexOutOfRange_IORaceCondition");
int bytesRead;
int ret;
if (buffer.Length == 0)
{
bytesRead = 0;
ret = Win32Error.ERROR_SUCCESS;
}
else
{
var charsRead = 0;
bytesRead = 0;
var records = new Kernel32.INPUT_RECORD[BufferSize];
// begin input loop
do
{
var readSuccess = Kernel32.ReadConsoleInput(_handle, records, BufferSize, out var recordsRead);
Debug.WriteLine("Read {0} input record(s)", recordsRead);
// some of the arithmetic here is deliberately more explicit than it needs to be
// in order to show how 16-bit unicode WCHARs are packed into the buffer. The console
// subsystem is one of the last bastions of UCS-2, so until UTF-16 is fully adopted
// the two-byte character assumptions below will hold.
if (readSuccess && recordsRead > 0)
{
for (var index = 0; index < recordsRead; index++)
{
var record = records[index];
if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT)
{
// skip key up events - if not, every key will be duped in the stream
if (record.Event.KeyEvent.bKeyDown == false) continue;
// pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer.
var glyph = (ushort) record.Event.KeyEvent.uChar;
var lsb = (byte) (glyph & 0xFFu);
var msb = (byte) ((glyph >> 8) & 0xFFu);
// ensure we accommodate key repeat counts
for (var n = 0; n < record.Event.KeyEvent.wRepeatCount; n++)
{
buffer[offset + charsRead * BytesPerWChar] = lsb;
buffer[offset + charsRead * BytesPerWChar + 1] = msb;
charsRead++;
}
}
else
{
// ignore focus events; not doing so makes debugging absolutely hilarious
// when breakpoints repeatedly cause focus events to occur as your view toggles
// between IDE and console.
if (record.EventType != Kernel32.EVENT_TYPE.FOCUS_EVENT)
{
// I assume success adding records - this is not so critical
// if it is critical to you, loop on this with a miniscule delay
_nonKeyEvents.TryAdd(record);
}
}
}
bytesRead = charsRead * BytesPerWChar;
}
else
{
Debug.Assert(bytesRead == 0, "bytesRead == 0");
}
} while (bytesRead == 0);
Debug.WriteLine("Read {0} character(s)", charsRead);
ret = Win32Error.ERROR_SUCCESS;
}
var errCode = ret;
if (Win32Error.ERROR_SUCCESS != errCode)
throw NativeMethods.GetExceptionForWin32Error(errCode);
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException("Write operations not implemented.");
}
public override void Flush()
{
throw new NotSupportedException("Flush/Write not supported.");
}
public override void SetLength(long value)
{
throw new NotSupportedException("Seek not supported.");
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException("Seek not supported.");
}
private void ValidateRead(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (offset < 0 || count < 0)
throw new ArgumentOutOfRangeException(offset < 0 ? nameof(offset) : nameof(count),
"offset or count cannot be negative numbers.");
if (buffer.Length - offset < count)
throw new ArgumentException("invalid offset length.");
if (!CanRead) throw new NotSupportedException("Get read not supported.");
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
<RootNamespace>Samples.Terminal</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Pipelines.Sockets.Unofficial" Version="2.0.22" />
<PackageReference Include="Vanara.PInvoke.Kernel32" Version="2.3.6" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadConsoleInputStreamDemo", "ReadConsoleInputStreamDemo.csproj", "{62F500DE-4F06-4B46-B7AF-02AF21296F00}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{62F500DE-4F06-4B46-B7AF-02AF21296F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62F500DE-4F06-4B46-B7AF-02AF21296F00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62F500DE-4F06-4B46-B7AF-02AF21296F00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62F500DE-4F06-4B46-B7AF-02AF21296F00}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55A9793B-D717-4A6E-A8FE-ABC6CD3B17BA}
EndGlobalSection
EndGlobal

View File

@@ -1,32 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<InstrumentationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Instrumentation>
<Regions>
<Instrumentation>
<Regions>
<!-- The RegionRoot and Region GUID are made up. Make some up. -->
<RegionRoot Guid="{ff1020e3-3f3a-3656-44a0-096b0b9b575a}" Name="TAEF">
<Region Guid="{e1f18b03-d49c-6f10-3753-2dc36fa663f1}" Name="TestRun">
<Start>
<RegionRoot Guid="{ff1020e3-3f3a-3656-44a0-096b0b9b575a}" Name="TAEF">
<Region Guid="{e1f18b03-d49c-6f10-3753-2dc36fa663f1}" Name="TestRun">
<Start>
<!-- This GUID corresponds to one of the TAEF providers that will send this start/stop event pair. -->
<Event Provider="{70d27130-f2f3-4365-b790-d31223254ef4}" Name="RunTest_TestRunScope" Opcode="1"/>
</Start>
<Stop>
<Event Provider="{70d27130-f2f3-4365-b790-d31223254ef4}" Name="RunTest_TestRunScope" Opcode="2"/>
</Stop>
<Match>
<Event>
<Payload FieldName="ScopeName"/>
</Event>
</Match>
<Naming>
<PayloadBased NameField="ScopeName"/>
</Naming>
<Event Provider="{70d27130-f2f3-4365-b790-d31223254ef4}" Name="RunTest_TestRunScope" Opcode="1"/>
</Start>
<Stop>
<Event Provider="{70d27130-f2f3-4365-b790-d31223254ef4}" Name="RunTest_TestRunScope" Opcode="2"/>
</Stop>
<Match>
<Event>
<Payload FieldName="ScopeName"/>
</Event>
</Match>
<Naming>
<PayloadBased NameField="ScopeName"/>
</Naming>
<Metadata>
<!-- At a bare minimum, you must collect something to get the wall time measurement. -->
<!-- The WinPerf team recommended collecting CPU if you don't care about anything else. -->
<WinperfWPAPreset.1>CPU</WinperfWPAPreset.1>
<WinperfWPAPreset.1.ProcessName>te.processhost.exe</WinperfWPAPreset.1.ProcessName>
</Metadata>
</Region>
</Region>
<!-- Again, this GUID is just straight up made up. -->
<!-- Also, just keep making regions. There's also a provision to stack a region inside a region. -->
<!-- Go look up "Region of Interest" files in respect to WPA to learn more. -->
@@ -56,9 +56,9 @@
<!-- You must also go change the .wprp to COLLECT the relevant data in the first place. -->
<WinperfWPAPreset.2>Commit</WinperfWPAPreset.2>
<WinperfWPAPreset.2.ProcessName>conhost.exe;openconsole.exe</WinperfWPAPreset.2.ProcessName>
</Metadata>
</Metadata>
</Region>
</RegionRoot>
</Regions>
</Instrumentation>
</RegionRoot>
</Regions>
</Instrumentation>
</InstrumentationManifest>

View File

@@ -1,9 +1,9 @@
<WindowsPerformanceRecorder Version="1.0" Comments="Test" Company="Microsoft Corporation" Copyright="Microsoft Corporation">
<Profiles>
<SystemCollector Id="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
<BufferSize Value="1024"/>
<Buffers Value="100"/>
</SystemCollector>
<Profiles>
<SystemCollector Id="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
<BufferSize Value="1024"/>
<Buffers Value="100"/>
</SystemCollector>
<!-- Leave this system provider alone. It's for environmental statistics by the plugin only -->
<SystemProvider Id="SystemProvider_Winperf_Environment_Light">
<Keywords Operation="Add">
@@ -11,21 +11,21 @@
</Keywords>
</SystemProvider>
<!-- This one is our system provider to customize as we see fit. -->
<SystemProvider Id="SystemProvider_ConsolePerf_Verbose" Base="SystemProvider_Base">
<Keywords Operation="Add">
<SystemProvider Id="SystemProvider_ConsolePerf_Verbose" Base="SystemProvider_Base">
<Keywords Operation="Add">
<!-- Add additional kernel system collection here if we decide we need it for the regions of interest file -->
<Keyword Value="CSwitch"/>
<Keyword Value="CSwitch"/>
<Keyword Value="MemoryInfo"/>
<Keyword Value="MemoryInfoWS"/>
<Keyword Value="VirtualAllocation"/>
</Keywords>
</SystemProvider>
</Keywords>
</SystemProvider>
<!-- Then add all the event providers we might possibly need. The first batch up here is boilerplate ones -->
<EventProvider Id="EventProvider-Microsoft.OSGENG.Testing.TaefEngine" Name="70d27130-f2f3-4365-b790-d31223254ef4"/>
<EventProvider Id="EventProvider-Microsoft-Windows-TestExecution" Name="Microsoft-Windows-TestExecution"/>
<EventProvider Id="EventProvider-Microsoft.Windows.TestExecution.WexLogger" Name="40c4df8b-00a9-5159-62bc-9bbc5ee78a29"/>
<EventProvider Id="EventProvider-Microsoft-Windows-WinPerf" Name="7dba59f2-a1ff-40f0-b9f1-34b7531e9580"/>
<EventProvider Id="EventProvider-Microsoft.Windows.WinPerf" Name="BE6F04EA-3488-4543-8082-24843EAEC303"/>
<EventProvider Id="EventProvider-Microsoft.OSGENG.Testing.TaefEngine" Name="70d27130-f2f3-4365-b790-d31223254ef4"/>
<EventProvider Id="EventProvider-Microsoft-Windows-TestExecution" Name="Microsoft-Windows-TestExecution"/>
<EventProvider Id="EventProvider-Microsoft.Windows.TestExecution.WexLogger" Name="40c4df8b-00a9-5159-62bc-9bbc5ee78a29"/>
<EventProvider Id="EventProvider-Microsoft-Windows-WinPerf" Name="7dba59f2-a1ff-40f0-b9f1-34b7531e9580"/>
<EventProvider Id="EventProvider-Microsoft.Windows.WinPerf" Name="BE6F04EA-3488-4543-8082-24843EAEC303"/>
<EventProvider Id="EventProvider-Census" Name="262CDE7A-5C84-46CF-9420-94963791EF69"/>
<!-- These providers are relevant to specific Regions of Interest -->
<EventProvider Id="EventProvider-Microsoft-Windows-Kernel-Memory" Name="Microsoft-Windows-Kernel-Memory"/>
@@ -45,31 +45,31 @@
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
<!-- Now define some profiles. We'll call them by ID when collecting. Also, the Base is where it is inheriting from and is a .wprpi file built... -->
<!-- ... into WPR automatically. Go look in the WPR install directory or in the documentation to find it. -->
<Profile Id="ConsolePerf.Verbose.File" Base="GeneralProfile.Light.File" LoggingMode="File" Name="ConsolePerfProfile" DetailLevel="Verbose" Description="Console Performance default profile">
<Collectors Operation="Add">
<SystemCollectorId Value="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
<Profile Id="ConsolePerf.Verbose.File" Base="GeneralProfile.Light.File" LoggingMode="File" Name="ConsolePerfProfile" DetailLevel="Verbose" Description="Console Performance default profile">
<Collectors Operation="Add">
<SystemCollectorId Value="SystemCollector_ConsolePerfSystemCollectorInFileLarge">
<!-- You don't have to change anything here when system keywords are added above. -->
<!-- Kernel collection is a bit more unified than user collection of events. -->
<SystemProviderId Value="SystemProvider_ConsolePerf_Verbose"/>
</SystemCollectorId>
<EventCollectorId Value="EventCollector_WPREventCollectorInFile">
<EventProviders Operation="Add">
<SystemProviderId Value="SystemProvider_ConsolePerf_Verbose"/>
</SystemCollectorId>
<EventCollectorId Value="EventCollector_WPREventCollectorInFile">
<EventProviders Operation="Add">
<!-- Anything we need to collect for our perf test has to be referenced again here. -->
<EventProviderId Value="EventProvider-Microsoft.OSGENG.Testing.TaefEngine"/>
<EventProviderId Value="EventProvider-Microsoft-Windows-TestExecution"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.TestExecution.WexLogger"/>
<EventProviderId Value="EventProvider-Microsoft-Windows-WinPerf"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.WinPerf"/>
<EventProviderId Value="EventProvider-Microsoft.OSGENG.Testing.TaefEngine"/>
<EventProviderId Value="EventProvider-Microsoft-Windows-TestExecution"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.TestExecution.WexLogger"/>
<EventProviderId Value="EventProvider-Microsoft-Windows-WinPerf"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.WinPerf"/>
<EventProviderId Value="EventProvider-Microsoft-Windows-Kernel-Memory"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Launcher"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Host"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Render.VtEngine"/>
</EventProviders>
</EventCollectorId>
</Collectors>
</Profile>
</EventProviders>
</EventCollectorId>
</Collectors>
</Profile>
<!-- Leave everything below here alone. They're needed for WinPerf to collect environmental census information -->
<Profile Id="WinPerfEnvironment.Light.File" Base="" LoggingMode="File" Name="WinPerfEnvironment" DetailLevel="Light" Description="WinPerf environment profile">
<Collectors Operation="Add">

View File

@@ -1024,9 +1024,9 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe
}
}
data.text.emplace_back(selectionText);
data.FgAttr.emplace_back(selectionFgAttr);
data.BkAttr.emplace_back(selectionBkAttr);
data.text.emplace_back(std::move(selectionText));
data.FgAttr.emplace_back(std::move(selectionFgAttr));
data.BkAttr.emplace_back(std::move(selectionBkAttr));
}
return data;

View File

@@ -44,7 +44,10 @@
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<AppxManifest Include="Package.appxmanifest" Condition="'$(WindowsTerminalReleaseBuild)'=='true'">
<SubType>Designer</SubType>
</AppxManifest>
<AppxManifest Include="Package-Dev.appxmanifest" Condition="'$(WindowsTerminalReleaseBuild)'!='true'">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
<Identity
Name="WindowsTerminalDev"
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
Version="0.0.1.0" />
<Properties>
<DisplayName>ms-resource:AppNameDev</DisplayName>
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="ms-resource:AppNameDev"
Description="ms-resource:AppDescriptionDev"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile
Wide310x150Logo="Images\Wide310x150Logo.png"
Square71x71Logo="Images\SmallTile.png"
Square310x310Logo="Images\LargeTile.png"/>
<uap:SplashScreen Image="Images\SplashScreen.png"/>
</uap:VisualElements>
<Extensions>
<uap3:Extension Category="windows.appExecutionAlias" Executable="WindowsTerminal.exe" EntryPoint="Windows.FullTrustApplication">
<uap3:AppExecutionAlias>
<desktop:ExecutionAlias Alias="wtd.exe" />
</uap3:AppExecutionAlias>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -118,9 +118,15 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppDescription" xml:space="preserve">
<value>Codename Cascadia</value>
<value>The New Windows Terminal</value>
</data>
<data name="AppName" xml:space="preserve">
<value>Windows Terminal (Preview)</value>
</data>
</root>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
</data>
<data name="AppNameDev" xml:space="preserve">
<value>Windows Terminal (Dev Build)</value>
</data>
</root>

View File

@@ -300,14 +300,12 @@ namespace winrt::TerminalApp::implementation
}
// Method Description:
// - Called when the feedback button is clicked. Launches the feedback hub
// to the list of all feedback for the Terminal app.
// - Called when the feedback button is clicked. Launches github in your
// default browser, navigated to the "issues" page of the Terminal repo.
void App::_FeedbackButtonOnClick(const IInspectable&,
const RoutedEventArgs&)
{
// If you want this to go to the new feedback page automatically, use &newFeedback=true
winrt::Windows::System::Launcher::LaunchUriAsync({ L"feedback-hub://?tabid=2&appid=Microsoft.WindowsTerminal_8wekyb3d8bbwe!App" });
winrt::Windows::System::Launcher::LaunchUriAsync({ L"https://github.com/microsoft/Terminal/issues" });
}
// Method Description:
@@ -325,10 +323,14 @@ namespace winrt::TerminalApp::implementation
bindings.NewTab([this]() { _OpenNewTab(std::nullopt); });
bindings.CloseTab([this]() { _CloseFocusedTab(); });
bindings.NewTabWithProfile([this](const auto index) { _OpenNewTab({ index }); });
bindings.ScrollUp([this]() { _DoScroll(-1); });
bindings.ScrollDown([this]() { _DoScroll(1); });
bindings.ScrollUp([this]() { _Scroll(-1); });
bindings.ScrollDown([this]() { _Scroll(1); });
bindings.NextTab([this]() { _SelectNextTab(true); });
bindings.PrevTab([this]() { _SelectNextTab(false); });
bindings.ScrollUpPage([this]() { _ScrollPage(-1); });
bindings.ScrollDownPage([this]() { _ScrollPage(1); });
bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); });
bindings.OpenSettings([this]() { _OpenSettings(); });
}
// Method Description:
@@ -371,7 +373,7 @@ namespace winrt::TerminalApp::implementation
[this](wil::FolderChangeEvent event, PCWSTR fileModified)
{
// We want file modifications, AND when files are renamed to be
// profiles.json. This second case will ofentimes happen with text
// profiles.json. This second case will oftentimes happen with text
// editors, who will write a temp file, then rename it to be the
// actual file you wrote. So listen for that too.
if (!(event == wil::FolderChangeEvent::Modified ||
@@ -629,8 +631,7 @@ namespace winrt::TerminalApp::implementation
// reloading settings)
const auto* const p = _settings->FindProfile(tabProfile);
// TODO: MSFT:21268795: Need a better story for what should happen when the last tab is closed.
if (p != nullptr && p->GetCloseOnExit() && _tabs.size() > 1)
if (p != nullptr && p->GetCloseOnExit())
{
_RemoveTabViewItem(tabViewItem);
}
@@ -657,21 +658,18 @@ namespace winrt::TerminalApp::implementation
return _tabView.SelectedIndex();
}
void App::_OpenSettings()
{
LaunchSettings();
}
// Method Description:
// - Close the currently focused tab. Focus will move to the left, if possible.
void App::_CloseFocusedTab()
{
if (_tabs.size() > 1)
{
int focusedTabIndex = _GetFocusedTabIndex();
std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
// We're not calling _FocusTab here because it makes an async dispatch
// that is practically guaranteed to not happen before we delete the tab.
_tabView.SelectedIndex((focusedTabIndex > 0) ? focusedTabIndex - 1 : 1);
_tabView.Items().RemoveAt(focusedTabIndex);
_tabs.erase(_tabs.begin() + focusedTabIndex);
}
int focusedTabIndex = _GetFocusedTabIndex();
std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
_RemoveTabViewItem(focusedTab->GetTabViewItem());
}
// Method Description:
@@ -680,12 +678,29 @@ namespace winrt::TerminalApp::implementation
// view up, and positive values will move the viewport down.
// Arguments:
// - delta: a number of lines to move the viewport relative to the current viewport.
void App::_DoScroll(int delta)
void App::_Scroll(int delta)
{
int focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->Scroll(delta);
}
// Method Description:
// - Move the viewport of the terminal of the currently focused tab up or
// down a page. The page length will be dependent on the terminal view height.
// 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
// is clamped between -1 and 1)
void App::_ScrollPage(int delta)
{
delta = std::clamp(delta, -1, 1);
const auto focusedTabIndex = _GetFocusedTabIndex();
const auto control = _tabs[focusedTabIndex]->GetTerminalControl();
const auto termHeight = control.GetViewHeight();
_tabs[focusedTabIndex]->Scroll(termHeight * delta);
}
// Method Description:
// - Copy text from the focused terminal to the Windows Clipboard
// Arguments:
@@ -710,10 +725,20 @@ namespace winrt::TerminalApp::implementation
// we clamp the values to the range [0, tabCount) while still supporting moving
// leftward from 0 to tabCount - 1.
_SetFocusedTabIndex(
(tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount
static_cast<int>((tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount)
);
}
// Method Description:
// - Sets focus to the desired tab.
void App::_SelectTab(const int tabIndex)
{
if (tabIndex >= 0 && tabIndex < _tabs.size())
{
_SetFocusedTabIndex(tabIndex);
}
}
// Method Description:
// - Responds to the TabView control's Selection Changed event (to move a
// new terminal control into focus.)
@@ -758,13 +783,9 @@ namespace winrt::TerminalApp::implementation
// - eventArgs: the event's constituent arguments
void App::_OnTabClosing(const IInspectable& sender, const MUX::Controls::TabViewTabClosingEventArgs& eventArgs)
{
// Don't allow the user to close the last tab ..
// .. yet.
if (_tabs.size() > 1)
{
const auto tabViewItem = eventArgs.Item();
_RemoveTabViewItem(tabViewItem);
}
const auto tabViewItem = eventArgs.Item();
_RemoveTabViewItem(tabViewItem);
// If we don't cancel the event, the TabView will remove the item itself.
eventArgs.Cancel(true);
}
@@ -825,6 +846,11 @@ namespace winrt::TerminalApp::implementation
// - tabViewItem: the TabViewItem in the TabView that is being removed.
void App::_RemoveTabViewItem(const IInspectable& tabViewItem)
{
// To close the window here, we need to close the hosting window.
if (_tabs.size() == 1)
{
_lastTabClosedHandlers();
}
uint32_t tabIndexFromControl = 0;
_tabView.Items().IndexOf(tabViewItem, tabIndexFromControl);
@@ -836,6 +862,9 @@ namespace winrt::TerminalApp::implementation
// 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);
}
// Method Description:
@@ -874,4 +903,5 @@ namespace winrt::TerminalApp::implementation
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.
DEFINE_EVENT(App, TitleChanged, _titleChangeHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(App, LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs);
}

View File

@@ -44,6 +44,7 @@ namespace winrt::TerminalApp::implementation
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT(TitleChanged, _titleChangeHandlers, winrt::Microsoft::Terminal::TerminalControl::TitleChangedEventArgs);
DECLARE_EVENT(LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs);
private:
App(Windows::UI::Xaml::Markup::IXamlMetadataProvider const& parentProvider);
@@ -72,6 +73,8 @@ namespace winrt::TerminalApp::implementation
void _CreateNewTabFlyout();
void _LoadSettings();
void _OpenSettings();
void _HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept;
void _RegisterSettingsChange();
@@ -87,14 +90,16 @@ namespace winrt::TerminalApp::implementation
void _OpenNewTab(std::optional<int> profileIndex);
void _CloseFocusedTab();
void _SelectNextTab(const bool bMoveRight);
void _SelectTab(const int tabIndex);
void _SetFocusedTabIndex(int tabIndex);
int _GetFocusedTabIndex() const;
void _DoScroll(int delta);
void _Scroll(int delta);
void _CopyText(const bool trimTrailingWhitespace);
// Todo: add more event implementations here
// MSFT:20641986: Add keybindings for New Window
void _ScrollPage(int delta);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
void _OnTabClosing(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabClosingEventArgs& eventArgs);

View File

@@ -3,6 +3,7 @@
namespace TerminalApp
{
delegate void LastTabClosedEventArgs();
[default_interface]
runtimeclass App : Microsoft.UI.Xaml.Markup.XamlApplication
{
@@ -24,6 +25,7 @@ namespace TerminalApp
Boolean GetShowTabsInTitlebar();
event Microsoft.Terminal.TerminalControl.TitleChangedEventArgs TitleChanged;
event LastTabClosedEventArgs LastTabClosed;
String GetTitle();

View File

@@ -38,6 +38,9 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::NewTab:
_NewTabHandlers();
return true;
case ShortcutAction::OpenSettings:
_OpenSettingsHandlers();
return true;
case ShortcutAction::NewTabProfile0:
_NewTabWithProfileHandlers(0);
@@ -86,13 +89,48 @@ namespace winrt::TerminalApp::implementation
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;
}
@@ -112,6 +150,9 @@ namespace winrt::TerminalApp::implementation
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);
}

View File

@@ -49,6 +49,9 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
DECLARE_EVENT(ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
DECLARE_EVENT(ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
DECLARE_EVENT(ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
DECLARE_EVENT(ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DECLARE_EVENT(OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
private:
std::unordered_map<winrt::Microsoft::Terminal::Settings::KeyChord, TerminalApp::ShortcutAction, KeyChordHash, KeyChordEquality> _keyShortcuts;

View File

@@ -21,13 +21,25 @@ namespace TerminalApp
NewWindow,
CloseWindow,
CloseTab,
SwitchToTab,
NextTab,
PrevTab,
SwitchToTab0,
SwitchToTab1,
SwitchToTab2,
SwitchToTab3,
SwitchToTab4,
SwitchToTab5,
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
SwitchToTab9,
IncreaseFontSize,
DecreaseFontSize,
ScrollUp,
ScrollDown
ScrollDown,
ScrollUpPage,
ScrollDownPage,
OpenSettings
};
delegate void CopyTextEventArgs();
@@ -37,13 +49,16 @@ namespace TerminalApp
delegate void NewWindowEventArgs();
delegate void CloseWindowEventArgs();
delegate void CloseTabEventArgs();
delegate void SwitchToTabEventArgs();
delegate void NextTabEventArgs();
delegate void PrevTabEventArgs();
delegate void SwitchToTabEventArgs(Int32 profileIndex);
delegate void IncreaseFontSizeEventArgs();
delegate void DecreaseFontSizeEventArgs();
delegate void ScrollUpEventArgs();
delegate void ScrollDownEventArgs();
delegate void ScrollUpPageEventArgs();
delegate void ScrollDownPageEventArgs();
delegate void OpenSettingsEventArgs();
[default_interface]
runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
@@ -66,5 +81,8 @@ namespace TerminalApp
event DecreaseFontSizeEventArgs DecreaseFontSize;
event ScrollUpEventArgs ScrollUp;
event ScrollDownEventArgs ScrollDown;
event ScrollUpPageEventArgs ScrollUpPage;
event ScrollDownPageEventArgs ScrollDownPage;
event OpenSettingsEventArgs OpenSettings;
}
}

View File

@@ -6,6 +6,7 @@
#include <conattrs.hpp>
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
using namespace winrt::Microsoft::Terminal::Settings;
using namespace ::TerminalApp;
@@ -37,9 +38,69 @@ ColorScheme _CreateCampbellScheme()
return campbellScheme;
}
ColorScheme _CreateOneHalfDarkScheme()
{
// First 8 dark colors per: https://github.com/sonph/onehalf/blob/master/putty/onehalf-dark.reg
// Dark gray is per colortool scheme, the other 7 of the last 8 colors from the colortool
// scheme are the same as their dark color equivalents.
ColorScheme oneHalfDarkScheme { L"One Half Dark",
RGB(220, 223, 228),
RGB( 40, 44, 52) };
auto& oneHalfDarkTable = oneHalfDarkScheme.GetTable();
auto oneHalfDarkSpan = gsl::span<COLORREF>(&oneHalfDarkTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
oneHalfDarkTable[0] = RGB( 40, 44, 52); // black
oneHalfDarkTable[1] = RGB(224, 108, 117); // dark red
oneHalfDarkTable[2] = RGB(152, 195, 121); // dark green
oneHalfDarkTable[3] = RGB(229, 192, 123); // dark yellow
oneHalfDarkTable[4] = RGB( 97, 175, 239); // dark blue
oneHalfDarkTable[5] = RGB(198, 120, 221); // dark magenta
oneHalfDarkTable[6] = RGB( 86, 182, 194); // dark cyan
oneHalfDarkTable[7] = RGB(220, 223, 228); // gray
oneHalfDarkTable[8] = RGB( 90, 99, 116); // dark gray
oneHalfDarkTable[9] = RGB(224, 108, 117); // red
oneHalfDarkTable[10] = RGB(152, 195, 121); // green
oneHalfDarkTable[11] = RGB(229, 192, 123); // yellow
oneHalfDarkTable[12] = RGB( 97, 175, 239); // blue
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);
return oneHalfDarkScheme;
}
ColorScheme _CreateOneHalfLightScheme()
{
// First 8 dark colors per: https://github.com/sonph/onehalf/blob/master/putty/onehalf-light.reg
// Last 8 colors per colortool scheme.
ColorScheme oneHalfLightScheme { L"One Half Light",
RGB(56, 58, 66),
RGB(250, 250, 250) };
auto& oneHalfLightTable = oneHalfLightScheme.GetTable();
auto oneHalfLightSpan = gsl::span<COLORREF>(&oneHalfLightTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
oneHalfLightTable[0] = RGB( 56, 58, 66); // black
oneHalfLightTable[1] = RGB(228, 86, 73); // dark red
oneHalfLightTable[2] = RGB( 80, 161, 79); // dark green
oneHalfLightTable[3] = RGB(193, 131, 1); // dark yellow
oneHalfLightTable[4] = RGB( 1, 132, 188); // dark blue
oneHalfLightTable[5] = RGB(166, 38, 164); // dark magenta
oneHalfLightTable[6] = RGB( 9, 151, 179); // dark cyan
oneHalfLightTable[7] = RGB(250, 250, 250); // gray
oneHalfLightTable[8] = RGB( 79, 82, 93); // dark gray
oneHalfLightTable[9] = RGB(223, 108, 117); // red
oneHalfLightTable[10] = RGB(152, 195, 121); // green
oneHalfLightTable[11] = RGB(228, 192, 122); // yellow
oneHalfLightTable[12] = RGB( 97, 175, 239); // blue
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);
return oneHalfLightScheme;
}
ColorScheme _CreateSolarizedDarkScheme()
{
ColorScheme solarizedDarkScheme { L"Solarized Dark",
RGB(253, 246, 227),
RGB( 7, 54, 66) };
@@ -96,8 +157,8 @@ ColorScheme _CreateSolarizedLightScheme()
// Method Description:
// - Create the set of schemes to use as the default schemes. Currently creates
// three default color schemes - Campbell (the new cmd color scheme),
// Solarized Dark and Solarized Light.
// five default color schemes - Campbell (the new cmd color scheme),
// One Half Dark, One Half Light, Solarized Dark, and Solarized Light.
// Arguments:
// - <none>
// Return Value:
@@ -105,9 +166,10 @@ ColorScheme _CreateSolarizedLightScheme()
void CascadiaSettings::_CreateDefaultSchemes()
{
_globals.GetColorSchemes().emplace_back(_CreateCampbellScheme());
_globals.GetColorSchemes().emplace_back(_CreateOneHalfDarkScheme());
_globals.GetColorSchemes().emplace_back(_CreateOneHalfLightScheme());
_globals.GetColorSchemes().emplace_back(_CreateSolarizedDarkScheme());
_globals.GetColorSchemes().emplace_back(_CreateSolarizedLightScheme());
}
// Method Description:
@@ -120,15 +182,14 @@ void CascadiaSettings::_CreateDefaultSchemes()
// - <none>
void CascadiaSettings::_CreateDefaultProfiles()
{
Profile defaultProfile{};
defaultProfile.SetFontFace(L"Consolas");
defaultProfile.SetCommandline(L"cmd.exe");
defaultProfile.SetColorScheme({ L"Campbell" });
defaultProfile.SetAcrylicOpacity(0.75);
defaultProfile.SetUseAcrylic(true);
defaultProfile.SetName(L"cmd");
_globals.SetDefaultProfile(defaultProfile.GetGuid());
Profile cmdProfile{};
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.
@@ -145,13 +206,16 @@ void CascadiaSettings::_CreateDefaultProfiles()
}
powershellProfile.SetFontFace(L"Courier New");
powershellProfile.SetCommandline(psCmdline);
powershellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
powershellProfile.SetColorScheme({ L"Campbell" });
powershellProfile.SetDefaultBackground(RGB(1, 36, 86));
powershellProfile.SetUseAcrylic(false);
powershellProfile.SetName(L"PowerShell");
_profiles.emplace_back(defaultProfile);
_profiles.emplace_back(powershellProfile);
_profiles.emplace_back(cmdProfile);
_globals.SetDefaultProfile(powershellProfile.GetGuid());
}
// Method Description:
@@ -173,6 +237,9 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::CloseTab,
KeyChord{ KeyModifiers::Ctrl,
static_cast<int>('W') });
keyBindings.SetKeyBinding(ShortcutAction::OpenSettings,
KeyChord{ KeyModifiers::Ctrl,
VK_OEM_COMMA });
keyBindings.SetKeyBinding(ShortcutAction::NextTab,
KeyChord{ KeyModifiers::Ctrl,
@@ -217,10 +284,46 @@ void CascadiaSettings::_CreateDefaultKeybindings()
keyBindings.SetKeyBinding(ShortcutAction::ScrollUp,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_PRIOR });
VK_UP });
keyBindings.SetKeyBinding(ShortcutAction::ScrollDown,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_DOWN });
keyBindings.SetKeyBinding(ShortcutAction::ScrollDownPage,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_NEXT });
keyBindings.SetKeyBinding(ShortcutAction::ScrollUpPage,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
VK_PRIOR });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab0,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('1') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab1,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('2') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab2,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('3') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab3,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('4') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab4,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('5') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab5,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('6') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab6,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('7') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab7,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('8') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('9') });
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab9,
KeyChord{ KeyModifiers::Alt,
static_cast<int>('0') });
}
// Method Description:
@@ -358,7 +461,7 @@ std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view
do
{
result.resize(requiredSize);
requiredSize = ::ExpandEnvironmentStringsW(source.data(), result.data(), result.size());
requiredSize = ::ExpandEnvironmentStringsW(source.data(), result.data(), gsl::narrow<DWORD>(result.size()));
} while (requiredSize != result.size());
// Trim the terminating null character

View File

@@ -16,6 +16,25 @@ 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 const std::array<std::wstring, 16> TABLE_COLORS =
{
L"black",
L"red",
L"green",
L"yellow",
L"blue",
L"purple",
L"cyan",
L"white",
L"brightBlack",
L"brightRed",
L"brightGreen",
L"brightYellow",
L"brightBlue",
L"brightPurple",
L"brightCyan",
L"brightWhite"
};
ColorScheme::ColorScheme() :
_schemeName{ L"" },
@@ -71,17 +90,20 @@ JsonObject ColorScheme::ToJson() const
auto fg = JsonValue::CreateStringValue(Utils::ColorToHexString(_defaultForeground));
auto bg = JsonValue::CreateStringValue(Utils::ColorToHexString(_defaultBackground));
auto name = JsonValue::CreateStringValue(_schemeName);
JsonArray tableArray{};
for (auto& color : _table)
{
auto s = Utils::ColorToHexString(color);
tableArray.Append(JsonValue::CreateStringValue(s));
}
jsonObject.Insert(NAME_KEY, name);
jsonObject.Insert(FOREGROUND_KEY, fg);
jsonObject.Insert(BACKGROUND_KEY, bg);
jsonObject.Insert(TABLE_KEY, tableArray);
int i = 0;
for (const auto& current : TABLE_COLORS)
{
auto& color = _table.at(i);
auto s = JsonValue::CreateStringValue(Utils::ColorToHexString(color));
jsonObject.Insert(current, s);
i++;
}
return jsonObject;
}
@@ -112,6 +134,8 @@ ColorScheme ColorScheme::FromJson(winrt::Windows::Data::Json::JsonObject json)
const auto color = Utils::ColorFromHexString(bgString.c_str());
result._defaultBackground = color;
}
// Legacy Deserialization. Leave in place to allow forward compatibility
if (json.HasKey(TABLE_KEY))
{
const auto table = json.GetNamedArray(TABLE_KEY);
@@ -123,12 +147,24 @@ ColorScheme ColorScheme::FromJson(winrt::Windows::Data::Json::JsonObject json)
{
auto str = v.GetString();
auto color = Utils::ColorFromHexString(str.c_str());
result._table[i] = color;
result._table.at(i) = color;
}
i++;
}
}
int i = 0;
for (const auto& current : TABLE_COLORS)
{
if (json.HasKey(current))
{
const auto str = json.GetNamedString(current);
const auto color = Utils::ColorFromHexString(str.c_str());
result._table.at(i) = color;
}
i++;
}
return result;
}

View File

@@ -69,7 +69,7 @@ Profile::Profile() :
_acrylicTransparency{ 0.5 },
_useAcrylic{ false },
_scrollbarState{ },
_closeOnExit{ false },
_closeOnExit{ true },
_padding{ DEFAULT_PADDING },
_icon{ }
{
@@ -352,7 +352,7 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json)
}
if (json.HasKey(CURSORHEIGHT_KEY))
{
result._cursorHeight = json.GetNamedNumber(CURSORHEIGHT_KEY);
result._cursorHeight = static_cast<uint32_t>(json.GetNamedNumber(CURSORHEIGHT_KEY));
}
if (json.HasKey(CURSORSHAPE_KEY))
{
@@ -427,6 +427,11 @@ void Profile::SetCommandline(std::wstring cmdline) noexcept
_commandline = cmdline;
}
void Profile::SetStartingDirectory(std::wstring startingDirectory) noexcept
{
_startingDirectory = startingDirectory;
}
void Profile::SetName(std::wstring name) noexcept
{
_name = name;

View File

@@ -40,6 +40,7 @@ public:
void SetColorScheme(std::optional<std::wstring> schemeName) noexcept;
void SetAcrylicOpacity(double opacity) noexcept;
void SetCommandline(std::wstring cmdline) noexcept;
void SetStartingDirectory(std::wstring startingDirectory) noexcept;
void SetName(std::wstring name) noexcept;
void SetUseAcrylic(bool useAcrylic) noexcept;
void SetDefaultForeground(COLORREF defaultForeground) noexcept;

View File

@@ -7,6 +7,8 @@
#include <DefaultSettings.h>
#include <unicode.hpp>
#include <Utf16Parser.hpp>
#include <WinUser.h>
#include "..\..\types\inc\GlyphWidth.hpp"
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Terminal::Core;
@@ -35,7 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_desiredFont{ DEFAULT_FONT_FACE.c_str(), 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 },
_actualFont{ DEFAULT_FONT_FACE.c_str(), 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
_touchAnchor{ std::nullopt },
_leadingSurrogate{}
_leadingSurrogate{},
_cursorTimer{}
{
_Create();
}
@@ -74,11 +77,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Create the SwapChainPanel that will display our content
Controls::SwapChainPanel swapChainPanel;
swapChainPanel.SetValue(FrameworkElement::HorizontalAlignmentProperty(),
box_value(HorizontalAlignment::Stretch));
swapChainPanel.SetValue(FrameworkElement::HorizontalAlignmentProperty(),
box_value(HorizontalAlignment::Stretch));
swapChainPanel.SizeChanged({ this, &TermControl::_SwapChainSizeChanged });
swapChainPanel.CompositionScaleChanged({ this, &TermControl::_SwapChainScaleChanged });
@@ -294,6 +292,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
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.
auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
SetGlyphWidthFallback(pfn);
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
@@ -369,7 +372,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// In the scenario where the user has turned off the OS setting to automatically hide scollbars, the
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
// achieve the intended effect.
_scrollBar.Visibility(Visibility::Collapsed);
_scrollBar.Visibility(Visibility::Collapsed);
}
else
{
@@ -404,6 +407,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
// Set up blinking cursor
int blinkTime = GetCaretBlinkTime();
if (blinkTime != INFINITE)
{
// Create a timer
_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 });
}
else
{
// The user has disabled cursor blinking
_cursorTimer = std::nullopt;
}
// 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
@@ -502,6 +523,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
WI_IsFlagSet(modifiers, KeyModifiers::Ctrl),
WI_IsFlagSet(modifiers, KeyModifiers::Alt),
WI_IsFlagSet(modifiers, KeyModifiers::Shift));
if (_cursorTimer.has_value())
{
// Manually show the cursor when a key is pressed. Restarting
// the timer prevents flickering.
_terminal->SetCursorVisible(true);
_cursorTimer.value().Start();
}
}
e.Handled(handled);
@@ -793,6 +822,29 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
// Method Description:
// - Event handler for the GotFocus event. This is used to start
// blinking the cursor when the window is focused.
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
if (_cursorTimer.has_value())
_cursorTimer.value().Start();
}
// Method Description:
// - Event handler for the LostFocus event. This is used to hide
// and stop blinking the cursor when the window loses focus.
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
if (_cursorTimer.has_value())
{
_cursorTimer.value().Stop();
_terminal->SetCursorVisible(false);
}
}
void TermControl::_SendInputToConnection(const std::wstring& wstr)
{
_connection.WriteInput(wstr);
@@ -847,6 +899,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_renderer->TriggerRedrawAll();
}
// Method Description:
// - Toggle the cursor on and off when called by the cursor blink timer.
// Arguments:
// - sender: not used
// - e: not used
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */)
{
_terminal->SetCursorVisible(!_terminal->IsCursorVisible());
}
// Method Description:
// - Process a resize event that was initiated by the user. This can either be due to the user resizing the window (causing the swapchain to resize) or due to the DPI changing (causing us to need to resize the buffer to match)
// Arguments:
@@ -954,7 +1017,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto copiedData = _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace);
_terminal->ClearSelection();
_renderer->TriggerSelection();
// send data up for clipboard
_clipboardCopyHandlers(copiedData);
@@ -978,6 +1040,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _terminal->GetScrollOffset();
}
// Function Description:
// - Gets the height of the terminal in lines of text
// Return Value:
// - The height of the terminal in lines of text
int TermControl::GetViewHeight() const
{
const auto viewPort = _terminal->GetViewport();
return viewPort.Height();
}
// Function Description:
// - Determines how much space (in pixels) an app would need to reserve to
// create a control with the settings stored in the settings param. This
@@ -1036,12 +1108,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
float width = gsl::narrow<float>(cols * fFontWidth);
// Reserve additional space if scrollbar is intended to be visible
if (settings.ScrollState() == ScrollbarState::Visible)
{
width += scrollbarSize;
}
if (settings.ScrollState() == ScrollbarState::Visible)
{
width += scrollbarSize;
}
const float height = gsl::narrow<float>(rows * fFontHeight);
const float height = gsl::narrow<float>(rows * fFontHeight);
return { width, height };
}

View File

@@ -44,6 +44,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void ScrollViewport(int viewTop);
int GetScrollOffset();
int GetViewHeight() const;
void SwapChainChanged();
~TermControl();
@@ -84,6 +85,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate;
std::optional<Windows::UI::Xaml::DispatcherTimer> _cursorTimer;
// If this is set, then we assume we are in the middle of panning the
// viewport via touch input.
std::optional<winrt::Windows::Foundation::Point> _touchAnchor;
@@ -100,7 +103,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _MouseWheelHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _ScrollbarChangeHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e);
void _GotFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
void _BlinkCursor(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
void _SendInputToConnection(const std::wstring& wstr);
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);

View File

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

View File

@@ -560,6 +560,8 @@ void Terminal::ClearSelection() noexcept
_endSelectionPosition = {0, 0};
_selectionAnchor_YOffset = 0;
_endSelectionPosition_YOffset = 0;
_buffer->GetRenderTarget().TriggerSelection();
}
// Method Description:
@@ -588,3 +590,13 @@ const std::wstring Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhi
return result;
}
// Method Description:
// - Sets the visibility of the text cursor.
// Arguments:
// - isVisible: whether the cursor should be visible
void Terminal::SetCursorVisible(const bool isVisible) noexcept
{
auto& cursor = _buffer->GetCursor();
cursor.SetIsVisible(isVisible);
}

View File

@@ -113,6 +113,8 @@ public:
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 SetCursorVisible(const bool isVisible) noexcept;
#pragma region TextSelection
const bool IsSelectionActive() const noexcept;
void SetSelectionAnchor(const COORD position);

View File

@@ -19,7 +19,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_cursorShape{ CursorStyle::Vintage },
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
_useAcrylic{ false },
_closeOnExit{ false },
_closeOnExit{ true },
_tintOpacity{ 0.5 },
_padding{ DEFAULT_PADDING },
_fontFace{ DEFAULT_FONT_FACE },

View File

@@ -70,6 +70,7 @@ void AppHost::Initialize()
_app.Create();
_app.TitleChanged({ this, &AppHost::AppTitleChanged });
_app.LastTabClosed({ this, &AppHost::LastTabClosed });
AppTitleChanged(_app.GetTitle());
@@ -93,6 +94,16 @@ void AppHost::AppTitleChanged(winrt::hstring newTitle)
_window->UpdateTitle(newTitle.c_str());
}
// Method Description:
// - Called when no tab is remaining to close the window.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppHost::LastTabClosed() {
_window->Close();
}
// Method Description:
// - Resize the window we're about to create to the appropriate dimensions, as
// specified in the settings. This will be called during the handling of

View File

@@ -15,7 +15,7 @@ public:
virtual ~AppHost();
void AppTitleChanged(winrt::hstring newTitle);
void LastTabClosed();
void Initialize();
private:

View File

@@ -61,6 +61,17 @@ void IslandWindow::MakeWindow() noexcept
}
// Method Description:
// - Called when no tab is remaining to close the window.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::Close()
{
PostQuitMessage(0);
}
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the propoer size.

View File

@@ -13,7 +13,7 @@ public:
virtual ~IslandWindow() override;
void MakeWindow() noexcept;
void Close();
virtual void OnSize();
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;

View File

@@ -27,8 +27,9 @@
<activatableClass name="Microsoft.Terminal.Settings.TerminalSettings" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
<file name="TerminalApp.dll" hashalg="SHA1">
<activatableClass name="Microsoft.Terminal.TerminalApp.AppKeyBindings" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
<activatableClass name="Microsoft.Terminal.TerminalApp.TermApp" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
<activatableClass name="TerminalApp.App" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
<activatableClass name="TerminalApp.AppKeyBindings" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
<activatableClass name="TerminalApp.XamlmetaDataProvider" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
<file name="TerminalConnection.dll" hashalg="SHA1">
<activatableClass name="Microsoft.Terminal.TerminalConnection.ConhostConnection" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
@@ -126,4 +127,7 @@
<activatableClass name="Microsoft.UI.Xaml.Media.RevealBorderBrush" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
<activatableClass name="Microsoft.UI.Xaml.XamlTypeInfo.XamlControlsXamlMetaDataProvider" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
<file name="Microsoft.UI.Xaml.Markup.dll" hashalg="SHA1">
<activatableClass name="Microsoft.UI.Xaml.Markup.XamlApplication" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
</assembly>

View File

@@ -26,15 +26,15 @@
<PostBuildEvent Condition="'$(Platform)'!='Win32'">
<Command>
echo OutDir=$(OutDir)
(echo f | xcopy /y $(OutDir)$(ProjectName).dll $(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll )
(echo f | xcopy /y $(OutDir)$(ProjectName).pdb $(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).dll&quot; )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName).pdb&quot; )
</Command>
</PostBuildEvent>
<PostBuildEvent Condition="'$(Platform)'=='Win32'">
<Command>
echo OutDir=$(OutDir)
(echo f | xcopy /y $(OutDir)$(ProjectName).dll $(OpenConsoleDir)$(Configuration)\$(ProjectName).dll )
(echo f | xcopy /y $(OutDir)$(ProjectName).pdb $(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).dll&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).dll&quot; )
(echo f | xcopy /y &quot;$(OutDir)$(ProjectName).pdb&quot; &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName).pdb&quot; )
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
@@ -51,14 +51,14 @@
<ItemDefinitionGroup Condition="'$(Platform)'!='Win32'">
<PreBuildEvent>
<Command>
if not exist $(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName) mkdir $(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName)
if not exist &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName)&quot; mkdir &quot;$(OpenConsoleDir)$(Platform)\$(Configuration)\$(ProjectName)&quot;
</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
<PreBuildEvent>
<Command>
if not exist $(OpenConsoleDir)$(Configuration)\$(ProjectName) mkdir $(OpenConsoleDir)\$(Configuration)\$(ProjectName)
if not exist &quot;$(OpenConsoleDir)$(Configuration)\$(ProjectName)&quot; mkdir &quot;$(OpenConsoleDir)\$(Configuration)\$(ProjectName)&quot;
</Command>
</PreBuildEvent>
</ItemDefinitionGroup>

View File

@@ -3,6 +3,8 @@
#include "precomp.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
// This class is intended to test boundary conditions for:
// SetConsoleActiveScreenBuffer
class BufferTests
@@ -23,6 +25,8 @@ class BufferTests
END_TEST_METHOD()
TEST_METHOD(ScrollLargeBufferPerformance);
TEST_METHOD(ChafaGifPerformance);
};
void BufferTests::TestSetConsoleActiveScreenBufferInvalid()
@@ -144,3 +148,66 @@ void BufferTests::ScrollLargeBufferPerformance()
Log::Comment(String().Format(L"%d calls took %d ms. Avg %d ms per call", count, delta, delta/count));
}
void BufferTests::ChafaGifPerformance()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsPerfTest", L"true")
END_TEST_METHOD_PROPERTIES()
const auto Out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO Info;
GetConsoleScreenBufferInfo(Out, &Info);
// We need a large buffer
Info.dwSize.Y = 9999;
SetConsoleScreenBufferSize(Out, Info.dwSize);
SetConsoleCursorPosition(Out, { 0});
DWORD Mode = 0;
GetConsoleMode(Out, &Mode);
Mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(Out, Mode);
SetConsoleOutputCP(CP_UTF8);
// Taken from: https://blog.kowalczyk.info/article/zy/Embedding-binary-resources-on-Windows.html
HGLOBAL res_handle = NULL;
HRSRC res;
char * res_data;
DWORD res_size;
// NOTE: providing g_hInstance is important, NULL might not work
HMODULE hModule = (HMODULE)&__ImageBase;
res = FindResource(hModule, MAKEINTRESOURCE(CHAFA_CONTENT), RT_RCDATA);
if (!res)
{
VERIFY_FAIL(L"Couldn't find resource.");
return;
}
res_handle = LoadResource(hModule, res);
if (!res_handle)
{
VERIFY_FAIL(L"Couldn't load resource.");
return;
}
res_data = (char*)LockResource(res_handle);
res_size = SizeofResource(hModule, res);
/* you can now use the resource data */
Log::Comment(L"Working. Please wait...");
const auto now = std::chrono::steady_clock::now();
DWORD count = 0;
for (DWORD pos = 0; pos < res_size; pos += 1000)
{
DWORD written = 0;
WriteConsoleA(Out, res_data + pos, min(1000, res_size-pos), &written, nullptr);
count++;
}
const auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - now).count();
Log::Comment(String().Format(L"%d calls took %d ms. Avg %d ms per call", count, delta, delta / count));
}

View File

@@ -30,12 +30,16 @@
<ClInclude Include="Common.hpp" />
<ClInclude Include="OneCoreDelay.hpp" />
<ClInclude Include="precomp.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Host.Tests.Feature.rc" />
</ItemGroup>
<PropertyGroup>
<ProjectGuid>{8CDB8850-7484-4EC7-B45B-181F85B2EE54}</ProjectGuid>
<Keyword>Win32Proj</Keyword>

View File

@@ -98,8 +98,16 @@
<ClInclude Include="OneCoreDelay.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Host.Tests.Feature.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

Binary file not shown.

482
src/host/ft_host/chafa.txt Normal file
View File

@@ -0,0 +1,482 @@
[?25l
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂╴▂┈▪⎽▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▘⎺▆▅▍▗╾⎼▃▊▅╾┚┶╲⎽▄▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅┳▃▂⎽⎽▃▃▅▅▄▄▗▖⎺▆▆▅▎▍▄▃▂▊▗▆⎼▃▂▝▖⎺◆⎺▅▅▃▂▄▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▋▃▂⎼▊▗▊▅▄▄▃▅▋▆▆▅▅▮▎▂▂⎽▊▍▍▂⎺⎺▍▋⎺▆▆▂▲▄▃▃▆▄▂╴│╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅━╾⎼▃▍▄▌▄▃━▋▃▍▅▄▄▊▃▃⎺▆▆▋▍▍⎺▆▆▍▊▅▅⎻▌┈▆▆▄▂▅▃⎺▆▄▃▂⎽⎽⎽▂▃▖▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▄▃▂▊⎽▉▃▅▅▅▍▂▄▄▃▂▌▊▗▆▅▅▄▎▅▅▄▄▍▊▄━━▋▖┈▪▝▄⎺▌▅▂▪▃▂⎽⎽╲  ▝▗▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▊▂⎽⎺⎺▍▲▌⎼▄▃▖●▊━▅▄▄▍▋▗▃━━▌▎▃▃▂▂▖▉⎼⎼⎼▃▆▅▄▂⎽⎺▍┈▎⎺⎺▆▆▆▎▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▘⎺⎺⎺┳▆▆▃▂▂▂▋▅▌▃▂▂▂▎▌▝▄▄▃▌▘▝▃▃▂▋▉■┈⎽▉▏⎺▆▅▃▆▘┈▌▎▋ ╵▌▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗⎻⎻▆━▘▅▋⎺╴⎺⎻▍▆▘┈⎺▆▆⎺▍▂⎽▂⎽▌▘▎⎺⎺⎺▋▉⎺▖┈⎺▎▎▊▍▖┈▆▗▝▅▃▂└⎽▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂▂▃▃▃▝▄▘▅▄⎺▄▲▲▄▄▃▃▘▅▅▄▄▄▄⎺▄▄▃▃▋ ━━━━▎▂⎽▉▝┈▘▎◀▎▆▄▃▂▂▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▋⎼⎽▂▂▌▆╺━▲⎼⎼▋▅▅▅▄▃▲▅▅▅▄▄▅▄▖▏▄▄▃▃▋ ▂▂┈⎽▌▎▊⎺▅▃▂▌▏▌▉▘▌▎ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎺⎺┑▂▂▂▄▄▄▂▂▄▄▃▃▃▃▂▄▃▃▃◆⎺▆▆▆▆▝▅▄▄▄▃▂⎽▆▅▄▂▘┥▊▂▘⎽▗▗▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▄▅┒▅⎺▅▃⎽┈▅▃▃ ▆▅▄▃▂▋▂▪▉▋▌▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲●▝⎼▆▄▂┈⎺▅▃▂◀▋   ▂▃▘▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄⎺⎺▆▅▄▄▄▄▅▆⎺┯▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚┆ ╵╴┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂▂▂┈│▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▗▆▅▅▖▌▂▂⎼▊▄▃▃▃▂⎺▄▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▆▄▃▃▂▃▃▅▄▄▃▖▖▆▅▅▄▎▍▄▄▃▊▌▃▃▂▂▖⎺⎺▆▄▄▂▃▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▋▄▃▃▃▅▖▄▃▃▃▅▋▅▄▄▼▮▎▃▃▂▊▍▌▅▄▃▍▋⎺▆▅▂▗▃▂▆▄▂┈▍│╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▃─━▃━▍▄▌━━━▋▃▍▄▃▃▊▃▃▂⎽⎺▋▍▍▃▂▂▌▋▆▅⎻▌┈▆▄▂▲▃⎺▆▅▃▂⎽⎽⎽▂▃▄▖▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▌▃▂⎼▂⎽▗▅▅▄▄▍▃▂▂▲━▌▊▘▆▆▆▌▍▖⎺⎺▆▍▊▄━━▋▖┈▉▄▍▆▅▄▲▄▂▂▪▲▪  ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▊⎼⎼▃▂▍⎽▗▃▃▂▂▝▊▄▃▃▃▍▋▗▄▄▃▌▃▆▅▅▄▌▉─⎼▃▅▅▄▂⎽▖▝▍┈▎▆▆▆▆⎺▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂◀⎽⎽━▋▆▆▂⎽⎺⎺⎺▅▍⎽⎽▲⎺▎▌▂▅▅▄▌●▍━━━▌▉⎽⎽⎽▉▏⎺▅▃⎽▄▘┊▌▏▎▮┕▉▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▆▆▼⎻▆▅▋┳▆▆▅▍▆▅▆▅▅▅⎺▍▃▂▂⎽▍▘▝▃▂▂▋▉⎺▖⎺┳▎┫▉▏▆▄▏▝▄▂⎽┊⎽▊▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▄▃━▃▄▝▄▍▃▃━━▮▉▃▂▂▲▗▄▆▅▅▅▅●▄▎⎺▆▆▋ ▏━━━▎▘▏▋▌┈▋▎▎▆▅▄▃▃▃▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅╺▃▂▂▂▌▆▗▄▃▃▂▋▅▅▅▄▃▲▅▅▅▄▄▅▄▖▂▂⎽⎽●▖ ▂▂┈⎽▌▎⎺▅▃▂▂▌╲▌▗▘▌▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺┑▂▂▂▼▼▼▂▂▄▄▃▃▃▃▂▄▃▃▃▃━▅▄▅⎼▝▅▄▄▃▂⎽▆▅▃▂╵⎺▅▘▊▃▘▗┄▗▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▄▄▝⎺▅▃⎽┈▅▃▂└⎺▅▄▃▂▂▘▃┈▎▋▍▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲●⎼▆▄▃┈⎺▅▃▂⎽▌▊ ┈⎽▃▅▚⎻─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄╹⎺▅▄▄▄▄▅▆⎺⎺▂▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╷·╶ ┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂▂⎽┈│▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▗▄▄▃▖▌▂▂⎼▊▖▅▅▄▄▖▄▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▆▅▄▄▃▗▃▃▃▃▂▂▖▄▄▃▃▎▍⎼▄▃▊▍▄▄▄▃▃▖⎺⎺▆▃▃⎽▄▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▋▅▅▄▗▅▊▃▂▂▂▅▋▃━━┑▮▎▃▃▂▊▍▌━━━▍▋⎺▆▅▖▃▂⎺▅▃⎽┊▍│╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄━▅▄─▘▄▍━▅▄▋▃▍▅▄▄▊▃▃▂▂⎺▋▍▂▃▂▃▍▋▆⎻▄▌▆▅▃▲▄▂⎺▅▄▃▂▂▂▂▃▅▆▖⎺╎▆▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▌▄▄▃╸▂▗▄▃▃▃▍▃▄▄▃▃▌▊▘⎺▆▆▌▍▝▃▂▂▌▊▄━╾▋▖┈┃▃▆▅▃━╾▃▂▂■⎽╴  ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▊▃▂▂▂▍▲▗▂▂⎼⎽▃▊▂▂▂⎺▝▋▗▄▄▄▌▝▍⎺⎺⎺▌▉⎼⎼▂▝▄▃⎽┈▝▆▍┈▎⎺⎺⎺╴⎺▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂⎽⎼▃▂▂▆▆⎺⎺▆▆▆▅▍▆▆▆▅▎▌▂▅▅▄▗▖▗▅▄▄▋▉⎽┈▝▉⎺▆▄▂▅▂▋┊▌╻┚▼╲▋▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▂⎺⎺⎺▍▅▋▅▅▄▄▎▆▝▄▃▃▃▏▍▃▂▂▂▍▏⎽▅▅▄▋▉⎺▖▆▼▎▋╴▌⎺▅▃▚▝▃⎽┈└⎽▊▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▅▅⎻⎻⎻▝▄▎━▅▅┰▮▪▄▄▄▃▋▅▆▅▅▅▅▝▘▎⎽▂┈▊ ▏━━⎼▎▋┦┊▊▉▋▎▌▅▄▃▃▃▄▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▝▄▄▄▃▘▆▗▂▂▂▂▋▅▅▄▃⎺⎺▅▅▅▄▄▅▄▖▄▄▄▃▄▋ ▂▂┈⎽▗▖▅▄▂⎽╴▌▪▌▋▘▍▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺▪▲⎺⎺┯⎺▘▂▂▄▄▃▃▃▃▂▄▃▃▃▃━▅▄▅⎼▝▅▄▃▂▖⎺▝▃▂⎽⎺▅▘▎▊▏▌▏╴▗▉▍▊▝▝▊▎▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▄╵⎺▅▃⎽┈▆▃▂┓┡▅▄▃▃▂▃▘▅◀▎▊▎▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲⎺▆▅▃⎽⎺▅▃▂▘··▊┈⎽▃▅┯▅▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▘⎺▆▅▄▄▄▅▆⎺╴ ┄▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿╌▅┚╴▘╶ ┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽╵┊▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▃▂▲⎽▗▗▲⎼⎼▊▖▅▆▆▅▖▅▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▖▆▆▅▅▗▃▂▂▂▲▗▖▂▲▪⎼▎▍▄▃▃▊▌▆▆▅▅▄▖⎺▆▅▂▂▆▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▆▆▅▅▅▊▲⎽⎼▪▅▋▄▄▃▖▮▎▃▂▂▊▍▌▄▄▄▍▋▆▅▄▍▂┊▆▄▂▊·▍│╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅⎻⎻▆▅▘▄▍▄▃▃▋▃▍▃▃▂▊▂▃⎽⎺⎺▋▍▃▃▃▂▍▋▅⎻╾▌▅▃▂▄▂⎽▆▄▃▂▂▂▃▃▅▆⎺▖▂╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▅▅▅▗▂▗▃▂▂▂▍▃▂┈▂⎺▌▊▗▆▆▅▘▍▍⎼▄▄▍▊━╾⎼▋▖ ▊⎺▅▃▃╾▄▃▂▂⎽▂   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▋▄▄▃▃▘▮▌▲⎽⎺▆▄▊▆▆▆▅▍▋▗▄▃▃▌▎▍▂▂⎽▌▉⎼▃▂▝▃▂▎┈⎺▝▍╴▎▆┱╷▍▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂━━▲▂▄▆▆▆▆▅▅▘▅▌▄▃▃━▎▌▝▅▄▄▌▖▍▆▆▆▌▉▮┈▄⎺▆▄▂▅▃┙▋┊▌▎▋▖┝▌▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋▃▂▂▂▍▅▋▄▃━━▎▆▎▅▄▄▄▏▍▂▂⎽⎽▌▃▃▃▃▂▋▉⎺▆⎻◆▎▋╏⎺▆▃▆┻▊▂┈┈╴⎽▂▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗⎺⎺▆▆▆▃▄▗▄▄▃▃▮▍⎽⎽⎽▂▊▅▅▅▅▄▄▉▘▃▃▃▂▋ ━━⎼▃▍▍▍▋▊┈▋▎▌▗▄▃▃▄▅▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▘▆▆▅▅▘▆▊▂▂▆▆▋▅▘▆▆▅▅▘▅▅▄▄▅▄▖▆▆▅▅▆▌ ▎⎽┈▉▅▅▄▂⎽▏▋▌·▌▉▎▘▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺▪▝▆▆▆▅▘▂▂▄▄▃▃▃▃▂▄▃▃▃▃━▅▄▅⎼▝▄▃▂⎽⎽▅▝▂┈·▅▃▊▎▊▶▘┆┈▗▊▍▊▝▝▊▗▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▄▆▅▃⎽⎺▆▄▂└╷▄▃▃▃▃▄▘┈┕⎽▂▎▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲▄▅▃⎽⎺▅▄▂┈▘ ·▝▂▃▅⎺⎺▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃┑▆▅▄▄▅▅▆⎺┆┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿⎽▅⎻╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂⎽┊·╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▆▅▅▄▄▗▃▃▂⎺▗▆⎺▆▆▎▝▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅┱⎺⎺▆▆▗▃▂▂⎽▄▄▃▃▂▂⎺▎▍▂⎺▲▊▌▆▆▆▆▃▖▆▅▄▂▅╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎺⎺⎺▗┎▊⎼▄▃▖▏▋⎺⎺⎺▉▪▎▲▮⎺▊▍▌▅▅▅▍▋▅▄━▌·▆▄▂┈▊ ▍│╵·▆▅▄▄▃▂▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄⎽⎺▼⎺▍▄▍▃▂▂▋▃▍▆▆▆▊▂▃▆▆▅▋▎▃▄▃▃▍▊▄━⎼▗▄▂▅▃⎽▆▅▄▃▂▂▃▄▅▆┈┈▖▆▂╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘⎺⎺⎻▊▂▗▂▂⎽⎺▍▂▅▅▅▄▌▊▗▄▄▄▘▍▂▲▲▪▌▊━⎼▂▋▖┈⎺▆▄▂┳▄▃▂▖┷▂▃   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▅▅▅▄▘▲▌▆▆▆▅▄▊▃▃▃▃▘▋▂━━━▌▎▍▃▂▂▌▉▃▂▝▝▂⎽▗┈▉▝▍┈▎⎺┱▏▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝⎼━━┷▆▆▅▅▄▄▘▅▌▄▄▄▄▎▌▝▃▃▃▌▎▍⎺⎺▆▋▉▪┊▆▅▅▃▆▄▂▏▋┊▌┧▌╲▋▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋▂▂▂⎼▍▅▋━━━━▎▆▖▂▂⎽▂▏▗▏⎺▃⎺▍▖▄▄▄▃▌▉▆⎻━─▎▋▌▆▄▂▅▘▊⎽┈┈⎽▂▃▖▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▘⎽⎽⎽▊▃▄▃▃▂▂▂▮▍▆▆▆▅▅▅▃▃▃▃▃▘▘▄▄▄▄▋ ╾⎼▂⎽▍╷▅⎽┩▋⎺▎▊▗▄▄▄▅⎺▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▼⎺⎺⎺▖▆▗⎺⎺▆▆▋▅▋▃▃▂▂▘▅▅▄▄▅▄▖▆▆▆▆▆▌ ▎▊┈⎺▝▄▂▅▗┒▊▌▏▌▪┕┙▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺▪▝▅▅▄▄▘▂▂▄▄▃▃▃▃▂▄▃▃▃▃━▅▄▅⎼▝▃▂⎽▆▅▘▍▪┈▅▃▂▊▎▊⎽·╎┈▗▊▍▊▝▝▉▎▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▆▝▃▂⎺▅▄▂▍▅▅▅▄▃▃▃▄▅┈┈▋▂▄▂▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅▮▆▅▃⎽⎺▅▄▂┈┈▍▉⎽▂▃▅■▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃⎺▆▅▅▄▅▆⎺⎺▉▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿╌▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▘━■▆▊▗▄▅▆▆▅▅▅▅▄▄▗▆▆▆▅▖▆⎺⎺▆▎▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▘▃▂▂▎▃⎽⎽▄▄▄▃▅▅▅▅▎▍▆▆▆▅▌▗▆▆▆▗▆▅▄▃┊▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂▂▂▋▅▊▃▃▂▊▮▋▅▅▄▄▆▎▅▅▅▊▍▌▅▅▅▍▋▄━⎼▅▆▅▃⎽ ▊ ▍│╵·▆▅▄▂▂▂▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗▂▂⎽⎽▍▄▍▂▂⎺▋▃▍▄▃▃▊▃▗▄▄▃▋▎▃▄▄▃▍▋━⎼▂▗▂■▃┈⎺▅▄▃▃▃▃▄▅▆┈ ┈▖╹▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▆▆▆▊┶▉⎺⎺⎺▆▍▃▏▅▅▅▌▊▂▂▂▲▋▍▂▂▂▲▍▊⎼▂▪▋▖┈▆▅▃▂┱▄▃⎽┈▂▃▖   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅┙▆▆▆▆▘▲▋▅▅▅▅▄▊▄▄▃▃▍▋▝▃▃▃▋▎▝▃▃▂▌▉⎽▝⎺▊⎽▝▌┊▌▝▍┈▎⎺⎺⎺ ▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▅┯⎻⎻▆▆▃▄▃▃┛▅▍▂▂⎽⎺▎▌▎⎽⎽⎺▌▎▍▆⎺▖▋▉▅▆▄▼▃▆▄▂▎▌▋┊▌╏▎▂╵▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋⎼━━─▘▅▋▅▅▄▄▍▆▆▆▆▅▅▏▍▅▅▅▅▖▖▄▄▄▄▌▉⎻━─▃▎▋▆▄▂▆▄▘▊⎽┈⎽▂▄▘▎▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▘⎽▃▂▖▝▄▘▂▃▂▖▮▊▂▆▆▆▖▖▎━━▅━▘▘▄▄▄▄▋ ▃▂┈▋▍▍╹▋▌⎺▆▎▝▗▄▄▅⎺┈▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▅▆▆▆▄▋▅▋▂▃▃▂▌▅▅▄▄▅▅▖▆┈⎺▆◀▌ ▎▍┈▅▍▂▂▍▗┱▎▌▏▌┹▃▋▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▝▃▃▃▃▘▂▄◆⎺⎺⎺┻▃▂▄▃▃▃▃━▅▄▅⎼▝▂⎽▆▅▃▋▍⎺▅▄▂▘▊▎▊▂▂⎽╴▗⎽▎▘▝▝▊▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃▃⎺▅▅⎽⎺▅▃▂╴⎺▆▏▗▃▃▃▄▅⎺ ┈▂▄▆▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄┄⎺▅▃⎽⎺▅▄▂▃⎽┈⎽⎽▂▃▅▮▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃━▆▅▅▅▅▆⎺⎺┋┆▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴▘╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂╸⎽▅▆▪▊▗▄▅▆▆▅▅▅▅▄▄▖▄▄▄▃■▆▆▆▆▎▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅⎽⎼⎼▃▎▃▄▆▄▄▄┲▃▃▂▂┓▍▄▄▃▃▌▆▆▆▅▍▅▄▃▂┈▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎼⎼▃▋⎼▊▂▂┵⎺▅▋▂▂▲⎽▄▎━━━▊▍▌▅▅▅▍▋━⎽▂▅▅▃▂⎽·▊ ▍│╵·▆▅▂▂▃▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄⎽▃▂▂▎▄▍▲⎺⎺▋▃▍▄▄▄▊▃▘▄▂▄▋▍▃▃▃▃▍▋⎼▂⎺▃▲▄▂⎺▆▅▄▃▃▃▄▅⎺⎺  ┈▖╏▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▂⎽⎽▊⎽▉▆▆▆▆▍▃▃▂▂▂▋▊▃▃▃▂▋▍▂▲▲▪▍▊▂▲▅▋▃⎺▅▃▂▲▄⎼⎽⎽▂▃▅▖   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅┙⎺⎺⎺⎺▘▲▋▅▅▄▄▄▊⎺⎺⎺⎺▍▋▍⎺⎺⎺▌▎▍▂▂▂▌▉▮⎺▅▊▄▝▌ ▖▝▍┈▎·▌▉▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▆▅▆▅▆▆┻▃▃▂┛▅▝▅▅▅▅▎▌▅▅▅▅▌▖▍⎺⎺⎺▋▉▆⎻━⎼▂▄▂⎽▍▅▋┊▌╏▋▖╵▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋▂▃▂▂▘▅▗▄▄▄▃▍▆▲━■━━▏▍▅▅▅▅▍▃▃▄▄▃▌┊━⎼▂⎽▎▆▅▃▆▄▃▂▊⎽▂▃▄▆▃▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━▲▂▂▂⎼●▄▘▂⎽▂▉▮▊▃▃▃▂▃▖▘▂▂▂▂▍▘▃▄▄▃▋ ▂▮┈▆▍▍▊▋▋⎺▆▎▝▗▅▆⎺⎺ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▅▅▅▃▆▋▅▗⎺⎺⎺⎺▋▅▅▆▆▅▅▖▆▆▆▆◀▌ ▎▆▅━▌▖▌▉▌▍▊▌╿▌▃┬▍▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▪▂▃▂━▍▂▝▃▄▄▃▘▃▂▄▃▃▃▃━▅▄▅⎼▋┈▅▄▃⎽▗▍▅▄▂⎽╼▊▎▊▂▎▎▃▗▂▎▘▝▝▊▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▝▆▅▃▖⎺▝▃⎽▋⎺▆▅▘▄▃▄▄▆⎺▊┈▂▄▆▼▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▅⎺▅▃▂⎽▅▄▃▂▮┈┈▝▂▄▅▃▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃━▆▅▅▅▅▆⎺▼⎺·╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂━◀▅▅▖▊▗▄▅▆▆▅▅▅▅▄▄▅▂▲▲▪▖▅▆▅▅▖▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▂▂⎽⎼▎▃▃▄▃▄▄▄▪▪⎼▅▅▍⎼⎼⎼▊▍▅▅▅▄▍▃▃⎽⎽╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂▂⎼▊▄▊▂▂⎺⎺▏▗▂▂▂⎺▎▎▃▃▂▊▍▌▄▄▄▍▋⎼▂⎺▄▄▂┊▏·▊ ▍│╵··▃▂▃▄▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅⎼⎽▃▃▍▄▌⎺⎺▆▋▃▍⎺⎺⎺▊▂╍▂▂⎽▋▍▂▂▂▃▍▋┹ ▆▂▄▂▼▆▅▄▄▄▄▄▆⎺⎺┊  ┈▖╏▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▂▂▂▊⎽▉▅▆▅▅▍▂▆▆▆▅▌▊▄▆▆▆▋▘▝▄▄▃▌▊▮▆▅▋▖▅▄▃▂━▄▄⎽▂▄▅⎺▋   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▲⎽▮⎺▌▲▋▄▄▄▃▄▊▄▄▄▄▍▋▄▄▄▄▘▅▍▂▂⎺▌▉▆▅━▊▄▝▋╵⎺▝▍┈▎┃▋▖▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▅⎺⎻▗▆▆▲▂━━▌▅▌▲▲▲━▎▌▝▅▅▅▌▖▗▆▆▆▌▉⎻╾⎼▂▅▃⎽⎽▍▝▋┊▌╏▎ ┕▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗━━━━▘▅▗▃▃▃▃▍▆▂▂▃▂▂▮▍▂▂▂▂▌▮▂▅▄▅▋┊⎼▂┈▏▅▃⎽▅▃▂▂▊▂▃▄▆▃▅┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▆▂▃▃▂▂▝▄▘■⎽⎺⎺▮▊▆⎺■⎺▋┒▃▆▆▆▆▍▘▂▂▂▂▋ ▏▊▆⎻▍▍▄▊▏▆▅▖▝▅▆⎺▏⎺ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▗▄▅▅▅▋▅▋▃▃▃▃▌▪▂▂▂▂▂▘▅▅▅▅▅▌ ▎⎻╾▃▌▊┣▌┌▍▊▌▊▌⎽▌▋▌ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▍▲▂▲▲▍▂▎▃▃▃▃▍▃▂▄▃▃▃▃━▅▄▅⎼▋ ▃▂┈▊▝▅▄▂⎽▂▘▊▎▊▖▄▍▆▂▄┊┈▋▊▋▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▅▃▂▃▆▂┱▼⎺⎺▃━━▆▅▅⎽▆━━▆━━▅▆▄▃⎽⎽▅▝▼┈┈▆▅▄▏▗▄▅▆┳▍▂▂▄▆▼▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅▃▂⎽▅▄▃▂⎽●┈⎽▂▄▆●▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄╶◆⎺▆▅▅▅▆⎺╲▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂▂▄▖▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▅▄▄▄▄⎺▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▂▂▂╾▎▃▃▄▃▄▄▄▃▃▃▅▅▗▆▆▆▆▌▗▄▃▃▗▂⎽⎽·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌━━▂▊▆▊⎺⎺⎺▆▪▗▅▅▅▅▪▎▆▆▅▊▍▌━━━▗▋▂⎺▆▃▂⎽⎺▏·▊ ▍·┄·⎽▂▃▄▅▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▂⎼⎼⎼▍▄▌▆▆▆▋▃▍▄▄▄▊▃▏▄▄▄▋▍▍▄▂▃▍▋▪▆▄▌▃⎽⎺▆▅▄▄▄▅▆━▍⎽┈  ┈▖╏▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▃▃▃▊⎽▉▅▅▅▅▍▃●━━━▌▊▝▃▃▄▋▍▂▂▂▂▌▊▆▅━▋▆▄▃▂⎽⎽┰▂▃▄▅⎺▏▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▂▂▂⎽▍⎽▋▄▄▃▅▄▊▄▄▄▄▍▋▝▄▄▄▌▝▆▆▆▆▌▉⎻━⎼▊▆▝▋▝▌▝▍┈▎╷▋▎╷▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅⎺⎺⎺▖▆▆▲━━▲▌▅▌▂▂▂▂▎▌▎▂⎽⎽▌▏▗▄▄▄▌▉╾▃⎽▚▃⎽▖▗▍▅▋┊▌▂▋▪·▂▍▝▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▃▄▄▄▍▅▗▂▃▃▂▍▆▆▆⎺⎺▆▆▍▅▆▆▅▍▘▝▄▄▄▋┊⎽▪┈▅▆▄▂▅▄▃▂▂▊▃▄▆▃▅▆┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━┻▃▃▃▃▝▄▘⎺⎺⎺▆▲▊▃▃▃▃▘▅▏▅▆▆▅▍▄▎⎽⎽┊▋ ▏▆⎻╾▎▍⎺▆▅▅▖▖▆⎺◀▗┆ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▗▄▄▅▄▋▅▋▃▄▄▄▌▖▅▂▂▂▂▎▃▃▃▃▄▋ ▎╾▃▂▌▎┄╎ ▎▊▌▏▌▎▗▌▍ ▊▏▎▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▍⎼▂⎽⎼▍⎽▘▆▆▆▆▍┹▅▆▆▆▆▃━▅▄▅⎼▋ ▎⎽┈⎺▝▃▂⎽▎▝▃▊▎▊▖▉▘▂▄▆┊┈▝▝▋▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃┅▄▄▅▄▄▘━━▆▅▅⎽▆━━▆━━▝▄▂⎽▆▄▃▌▎┈▆▅▅▄▏▗▅▆▍▋⎽▃▄▆┯▃▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▅▆▆▃▂⎽▅▄▃▂⎽⎽╲⎽▃▄▆◀▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▮⎺▆▅▅▆▆⎺⎺▊▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂▂▂▖▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▂▂▂▂⎽▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▂▂▂▂▎▃▃▄▃▄▄▄▃▃▃▅▅▌▃▃▃▂▗▲▂▂⎼▍▃┊▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▃━▲▊▅▊⎺⎺⎺▆▪▶▃▃▃▂┱▎▂▂▂▊▍▍▃▃▃▗┫╺▆▄▃⎽▪ ··▊ ▍│·▂▂▄▅▅▅▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▂▄⎼⎼▍▄▌▅▅▆▘▃▍▂▂▲▊▃▋▄▄▄▋▍▍▂▂⎽▌▋▆⎻━▌⎽⎺▆▅▅▄▅▅▆┈▎▝▂▖  ┈▖▆▂╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍⎼⎼▃▊⎽▉▅▅▅▅▍▃▄▄▄▃▌▊▌▂▂▂▋▍▆▆▆▆▍▊▅━⎼▗▅▃▂▂⎽▂┳▃▄▅⎺╴┊▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▂▂▂⎽▍⎽▋▄▄▃▅▄▊▂▂▂⎽▍▋▆⎺⎺▆▌▎▄▄▄▄▖▉╾⎼▂▊▍▝▋▘└▝▍┈▎·┈·▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▆▆⎺▊⎺▆▲▲▲▲▌▅▌▆▆▆▆▎▌▄▄▄▄▍▎▝▅▅▅▌▉▂┈▝▊▂┄▍▗▍▅▋┊▌▅▍┊▂▃▘ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▄⎻⎻▄▍▅▗▂▃▂▂▍▆▝▄▄▄▃⎺▍▅▅▅▅▌▘▎▂▂▂▋▉▏▖▅◆▄▂▆▄▃▃▂▃▝▄■▃▅⎺▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▆▃▃▄▃▃▝▄▘▆⎺⎺⎺▮▊▝▄▄▄▖▖▪⎽▂▂┈▍▗▅▆▆▆▋ ▅╺╾▃▍╏⎺⎺▆▅▅▎▋▎▋┕▏▅ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▗▃▄▄▄▋▅▗⎽⎽⎽⎽▋┱▃▅▆▆▅▉⎽⎽■▆■▖ ▎▂┈▊▌▉▉▌▉┱·▌▌▌╸▄▍▎ ▊▏▎▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▍⎼┰▪⎼▍⎽▍▅▅▅▅▍▊⎽▲▲━━▪━▅▄▆⎼▋ ▎▌▆▅▋⎽⎽▗▆▉▉▊▎▊┝▂▂▄▆⎺ ┈╲▂▘▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▃▆▃▃▂▃▅▮⎼━▪┰▎━━▆▅▅⎽▆━━▆━━▝▂⎽▅▄▂▊▌▆⎺▆▅▄▄╲▗▆┈▍⎽▃▅▆▆━▃▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅━▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▄▆▅▄▂⎽▅▄▃▂⎽⎽▂⎽▃▄▆▆▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─⎺⎺▆▆▅▆⎺▅▼╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂▂▂▖▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▖▃⎽┈·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▂▂▂▂▎▃▃▄▃▄▄▄▃▃▃▅▅▃▂▃▃▄▗▅▂▂▂▍▃╵▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▃━▲▊▅▊⎺⎺⎺▆▮▲⎽▄━⎽▆▘▂▂▆▊▍▍⎺⎺⎺▃▆▆▅▃▂▄┊ ··▊ ▍ ▂▂▄▅▆▆▅▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▂⎽▄⎼▍▄▌▆▆▆▋▃▍▃▃▃▊▂▄▆⎺▆▋▍▌▆▆▆▍▊▄━⎼▗▎⎺▆▅▅▅▆▆⎺┈⎽▂▃┊  ┈▖╏▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍⎼⎼▃▊⎽▉▅▅▅▅▍▃▂▂▂⎺▌▊▗▅▅▅▋▎▃▄▄▃▍▊━⎼▂▗▄▃▂▂▂▂╈▅▆▖▄▉ ▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▂▂▂⎽▍▲▋▄▄▄▅▄▊▆▆▆▆▍▋▂▃▃▃▌▎⎽▲▲▲▌▉▃▂▊▝▝▗┊⎺▝▍┈▎╏▎▝▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▆▆▆⎺▖⎺▆▲━━▲▌▅▌▄▄▄▄▎▌▃▄▄▃▌▎▍▂▂▂▋▉▪┈▆▉⎽▝▍▉▋▝▋┊▌▗▌▂▃▅⎽ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▄⎻⎻▄▍▅▗▂▃▃▂▍▆▮▅▲━▪⎺▍⎺⎽▮┈▌▖▎▆▆▆▋▉▆▅━▲▃▆▅▄▃▃▃▄▝■▃▅⎺▖▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━▃▃▄▃▃▝▄▘⎺⎺⎺▉▮▊⎽▂▂▂▊╶▎▅▅▅▅▍⎽▅▃▃▃▋ ━⎼▂⎽▍▍⎺▆▆▅▆▎◀▎┏▌▗▃ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▗▄▄▄▅▋▅▋▆▆▆▆▌▖▋▅▅▅▅▘▂▂▃▂▊▉▎⎽┈⎺▌▌▍▗▎▊▌▏▌▗▆▋▎┈▂▃▘▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▍⎼▂⎽⎼▍▂▍▆▆▆▆▍▗▖▂▂⎽▅▎▅▅▅▅▆▋┈▎▆⎻━▋ ▉▉┊▶▪▊▎▊⎽▂▄▆⎺╴┈▂▘⎽▘▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▃▃▂▃▆⎽▂▃⎽┈▎▝▄▅▅▄▅⎺━━▆━━▋ ▝▃▂⎽▊▋⎺▆▅▅▅▅▏▌┈ ▃▃▅▆▆▆━▃▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▉▆▆▆▆▆▃▃▄▄▅▅▅▄▃┳⎽▃▂▂▃▆▄▃▃⎽▝▄▃▂▂⎽▪▂▃▅▆▌┭▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄⎺⎺▆▆▆▆⎺⎺▆⎺╶▊▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂┷▂▂⎽▖▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▂▂▂⎼▎▃▃▄▃▄▄▄▃▃▃▅▅▃▂▃▃▄▊▅▆▅▅▖▃╵▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▲▂▂▊▘▊▂▂▆⎺▏▲╸⎽╺▅▅▗▄▄▄▄▍▗▅▄▄▍▅▅▃▂┉⎽  ··▊ ╴⎽▃▄▅▆▆▆▅▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▂⎼▃▃▍▄▌⎺⎺▆▋▃▍⎺⎽⎺▊▃●▃▃▃▌▍▌━━■▍▋━⎼▂▗┇▆▆▅▅▆⎺⎺┈⎼▂▃▄▖  ┈▖╹▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▃▃▂▊⎽▉▅▆▅▅▍▂▅▆▆▆▌▊▌▅▅▄▋▎▝▄▂▄▌▊⎼▂▪▗▃▃▂▂▃▃▼▉⎺⎺▊▝┊▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎽▂⎽⎽▌▲▋▄▄▄▆▄▊▄▄▄▄▍▋▍▂▂▂▌▗▍▂▃▂▌▉⎽▝⎺▊▖┓▋┈▊▝▍┈▎▊▉▊▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅⎺⎺⎺▖▆▆▲▂▂━▌▅▌▅▆▅▂▎▌▆⎺⎺⎺▌▄▆▆▆▆▋▉▝▆⎻▊▖▊▍▗▍▝▋┊▌▃▂▄▅▂▂ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▄▃▃▃▘▅▗▃▃▃▃▍▆▃▃▃▃▃▆▝▄▄▄▄▍▂▍▃▃▼▋▉⎻━⎼▃⎽▅▄▃▃▃▄▄▊▃▅⎺┈▪▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━┻▃▃▃▃▝▄▘⎽⎽▆⎺▮▊⎺⎺⎺⎺▆▖▎▄▄▄▄▍▘▝▃▃▃▋ ▃▂┈▊▍▋⎺▆▆▆⎺▎◀▎▉▍·▋ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▗▄▅▅▅▋▅▋▄▄▄▄▌▪▖▮╍⎽▂┣▃▆⎺⎺⎺▋ ▎▌⎺▅▌▍▌▌┡▍·▌▝▌▘▖▌▌▂▘▅▘▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▍▲▂▲▲▍▂▎▄▄▄▄▍▋▂▄▄▄▄▉▂▂▂▂▂▖ ▍⎻╾⎼▋▍╴┊⎺┩▪▊▎▊▃▅▆▘┅▎▂▃▃▚▋▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▆▅⎺⎺⎺▎▋▝▄▄▄▖▎━━▆━━▋ ▍▘┈▌▝▆▆▅▅▅▅▆⎺▌ ⎽▃▅▆▂▆▆■▼▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅╴▄▄▄▄▄▃▼⎺▼▼▅▝▄▃┳⎽▃▂▃▅▄▃⎽▂▅▝▃▂▂▂▃▂▃▅⎺▼▘┭▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺▆▝⎺▆▆▆▆⎺▄▄⎺┘╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂╍◀▅▅▖▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▲⎽⎼⎼▎▃▄▆▄▄▄▄▃▃▃▅▅▃▂▃▃▄▃▂▃▃▂┳▃╵▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂⎼⎼▋▘▊▂▂▂⎺▏▲╸⎺·▅▅▅▂⎽▂▮▗▂▂▂⎽▍▄▃▂⎽┉ ▉┊ ⎺┈▂▃▄▅▆⎺▆▆▅▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▄▄▃▃▍▄▍▲⎺⎺▋▃▗▆▆▆▆▃▅▃▃▃▋▍▍▃▃▃▌▋⎼▂⎽▄⎺▆▆▆▆┒▌▪⎽▂▃▄▄▖  ┈▖▆▂╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▂▂▂▊⎽▉▆▆▆▆▍▂▅▅▅▅▘▊▌⎺⎺⎺▋▎▍▂▂⎽▌▊▂▪▅▄▃▃▃▃▄▅▌▊│┊·▉┊▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎺⎺⎺⎺▌▲▋▅▅▄▄▄▊▃▃▃━▍▋▍▆▆▅▌▎▅▆▆▅▍▉⎺▅▅▊▄▌▌┊▊▝▍┈▎┢▎▉▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▅▅⎻▆▆▆┻▃▃━┛▅▌▄▄▄▄▎▌▃▃▃▃▌▏▃▃▃▃▖▉▆⎻━▉▏▍▍▉▍▋▋┊▌▂▄▅▂▃▖ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗━━━▃▘▅▗▄▄▄▃▍▆▂▂▂⎽▂╏▍▄▄▄▄▍▘▝▃▃▃▋┈━⎼▃▂▅▄▄▃▃▄▅┩╌▅⎺▖▆▋▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━▂▂▂▂▂▝▄▘⎽⎽▆▉▮▊▆▆▆▆▆▖▎⎺▼◀⎺▍╸▎┐⎺▊ ⎽▘┈▝▍▆▆▆▆⎺⎺▎·▎▉▍·▋ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▅▅⎺▆▆▋▅▋━━━━▌▄▄▄▄▄▅┃▂▃▃▃▃┛ ▎▆▅⎻▌╌▎▍▗▎▊▌┊▌┎▄▶▂▄▘▅┈▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▪▂▃▃━▍▂╽▂▂▂▂▌▊▝▄▄▄▖┃⎺▂▃▃▃▊ ▎─▃▂▋┒▎┻▉┨▏▊▎▝▅▆▍▌⎽▂▃▃▃▖▊▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆▅▆▆▆▆▎▗▖▅▆┈⎺▏▆▆▆▆▆▋ ▍▘ ▝▝▆▅▅▅▆▆⎺▌⎽▂▃▅▆⎺▂▆▅▆▼▃▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▊▂▃▃▃▃▚▝▃▄▄▃▄▝▃┳⎽▃▂▝▃▂⎽▆▅▄▝▂▂▂▃▂▄▅┳━▄▘┭▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯╶▅╴▆▆▆⎺━▝▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂╶▪▅▆▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅⎽⎼⎽▂▎▃▄⎽▄▗▄▄▃▃▃▅▅▃▂▃▃▄▃▄▃⎺▂▂▃╵▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎽⎽▂▋▄▊▃▃▂▊▘▲╸⎺·▅▅▅▅▄▅▆▗▄▂▂▄▍▂▂⎽┊╴  ··┅▂▃▄▆▆⎺⎺⎺▆▄▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄▂▂▂▂▎▄▍▂▂⎺▋▃▗▆▆▆▅▂▄▆▆▆▆▍▗⎺▆▆▌▋┹⎽▝▆⎺▆▆⎺┈▍▂▂▃▄▄▅▄▖  ┈▖╹▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘⎽⎽⎽▊▂▉⎺⎺⎺▆▍▂▄▅▄▄▘▊▅▅▅▅▋▎▗▅▅▅▌▊⎽▅▅▄▃▃▃▄▅▆▏▊┇╵▊▎ ▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅╶⎺⎺⎺⎺▘▲▋▅▅▅▅▄▊━━━━▍▋▌━━━▌▎▂▄▃▄▌▉⎺▆⎻▊▏┓▋┈▝▆▍┈▎·▎╻▏▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▄▆▆▅━▆▆▃▄▄▃┛▅▌▄▄▄▃▎▌▝▄▄▃▌▘▝▃▃▃▋▉⎻━━▉▏▍▍▗▌▅▋┊▝▄▅▂▃▃▖ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋▂▂▂▃▘▅▋▅▅▅▄▎▆▂┈┈▂▉⎺▍●┹▂⎽▌▅⎺⎺⎺▆▋┊⎼▂⎽⎽▅▄▄▄▄▅▂▏▉⎺┈▌▘▋▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━⎽⎽⎼⎼⎽▝▄▘▂▂⎽▖▮▊▅▅▅▅▘▄▄▅▅▅▅▎▃▗▄▄▄▋ ▏▊┊⎺▍⎺▆▆╴⎺▊▎┕▎▉▍·▋ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▄▂▂▂▗▆▅▆▆▆⎺▋▅▋━━━━▌▪▊▅▅▅▅▊▘▝▄▄▃▊ ▎▅⎻━▌▘▍▍▝▗┫▌▋▌▘⎽▃▄▅▅▅╲▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺⎽▝▃▄▃▃▘⎽╽▂▂▂⎽▌▗▖⎽⎽▂▃┣▄⎺▆⎺⎺▊ ▎▃▂▂▋┄·╷┹▋▄▊▏▆⎺┊▎⎽▃▃▄▃▃▂▊▂▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▄▅▅▅▅▎▋▃▄▄▄▄▊▂▂▂▂▃▖ ▍▘┈▆▝▅▅▆▆⎺⎺◀▊▂▄▆⎺■⎺⎺▅▄▄▆▼▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▊▲▂▂▲▪▚▌▃▃▃▃▖▎▃┳⎽▃▂▋▂⎽▆▅▄▃▋▂▂⎼▂▄▆⎺⎽━▄▘┭▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▄▃⎺⎺▼▼▄▄▄▄▃⎺▆⎺┳⎻⎺⎺▆▆·⎺▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▖▘▂▂⎺▎▃▂▂⎽▄▄▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▅▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂▂⎺▊▗▊⎼▄▃▖▅▲╸⎺·▅▅▅▅▄▅▆▊▅▅▅▅▖▂▘▘⎽╴ ▉┊⎽▂▃▅▆⎺▪■⎺▆▅▄▖▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗▂▆▂⎺▍▄▍▃▃▂▋▃▗▆▆▆▅▃▄▄▄▄▖▍▄▄▄▃▚┫▲┊╹▆⎺⎺■⎺⎽⎽▆▗▄▅▅▅▄▖  ┈▖╏▆╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▆⎺▅▊▂▗▂▂⎽⎺▍▂▅▅▅▄▄▊▃▃▂▂▋▎▍━━▅▌▊▆⎺▆▄▄▄▄▅▆⎺▌▊▪┍▎▉ ▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅┙▆▆▆▅▘▲▌▆▆▆▆▄▊▃━━━▍▋▝▄▄▃▌▎▝▃▃▂▌▉▆▅⎻▊╎▝▋┈▊▝▍┈▎▌▎╴╵▮▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝⎻⎻⎻▄▆▆▅▅▄▄▘▅▌▄▄▄▃▎▌▎⎽▂■▌▗▖⎺⎺⎺▌▉━╾⎼▉▏▍▍▗▌▋▋┈▝▆▂▃▄▄▖ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋▃▃▂▂▌▅▋━━━━▎▆▂┈┈▂▉⎺▍▅▅▅▅▍▂▄▄▄▄▖┊▂▪┈╸▄▄▄▅▆▂▃┓▊┊ ▌▆╎▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂▂▂▂▊▃▄▃▃▃▂▂▮▊▅▅▅▅▘▃⎽▂▂▂━▘⎺▝▄▄▄▋ ▏▮┈▆▍▆⎺┐▎┈▘▎◀▎╷▍▏┊ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▅▃▂▂▲▪▆▗⎺⎺⎺▆▋▅▋━━━━▌▪▊▂▂▂▂▎▘▎⎺⎺▉▊ ▎━━━▌▎▍▄▊▎▊▌▏▌⎽▃▄▅▆▆▅▘▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺▪▝▅▅▅▄▘⎺╽▂▂▂⎽▌▊▆▆▆▆▆▉▖▃▄▄▃▘ ▎▘┈⎽▋▌▘▉▘▂▂▘▘⎺ ┊▂▃▄▄▄▃▂▝▊┗▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▄▅▅▅▅▎▋▂▅▅▅━▊┈▃▃▃▃▊┊▍▌┈▆▝▅▆▆⎺⎺▍▚▂▄▆⎺⎺⎺⎺▆▅▃▃▄▆▃▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▊▂▂▂▲▂▚▌▎▘┈⎽⎽▆▆┳▆▆▆▊┈▝▄▄▃▃▋▃⎼▃▄▆▆▅⎽━▄▘┭▅▅▅▅▅▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▝▅▅▅▄▅▝▄▄▄▃⎺▆▗⎺⎺⎺⎺⎺⎺▅▝▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▖▆▆▆▆▗▃▂▂▂▲┱▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▅▅▅▆▆▂▂▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎺⎺▆▖▅▊▂▲⎼▪▅▲╸⎺·▅▅▅▅▄▅▆▆▃▃▂▂▂▌▲▘⎽┊⎽▪┈⎽▃▄▅▆▪⎺⎺⎺⎺▆▄▃▂▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄▆▅⎺⎻▍▄▍▄▄▃▋▃▗⎺▆▆▆▃▃▃▃▂▂▍▍⎼⎼⎼▝┫▲┊▼▆⎺⎺▍┈▶▂━▗▅▅▅▅▄┊  ▝▖⎺┒╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▆⎻▆▅⎺▗▃▂▂▂▍▂▅▅▅▅▘▊▝⎼▄▄▋▎▍▂▂▂▌▊⎺▆▆▄▄▅▆⎺├▃▏▊┇╸▋▝┊▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▅▅▄▄▘⎽▌▲⎼⎺▆▄▊▄▃▃━▍▋▍▂▂⎽▌▎▍▅▆▆▌▉▅⎻▄▊▝▝▌┈▋▋▍┈▎▊⎽╷▲▮▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂┗━━▃▂▆▆▆▆▅▅▅▅▌▅▅▄▄▎▌▎▆▆▆▌▏▄▄▄▄▌▉━─⎼▉▏▝▎▌▍┈▋┈▆▂▃▄▄▃▖ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▋⎼⎼⎼▃▍▅▋▄▄▃━▎▆▂▂▂▂▂▏▍▄▃▃▃▍▏⎽▅▄▄▋ ⎽╸┈▶▄▄▅▆▂▄▅▶▊┊┈▎╴▪▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▘▂▆⎺⎺▃▄▪▄▄▄▃▮▊▆▆▆▆▅▖▎▄▄▄▃▍▘▎⎽▂⎻▊ ▏▖⎺▆▍⎺▖▉▌┈▋▎◀▎▗▎▏┊ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▘▆⎺⎺▆▆▆▊▂▂▂▆▋▅▋▃▃━━▌▪▊⎺⎺⎺■▎▖▗▅▅▅▋ ▎━━━▌▌▪┊▃╴▊▌┊▝▃▄▅▆▆▅▅▘▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺▪▝▆▆▆▅▘▂┃▃▃▂▂▌▋▗▄▄▄▘▊▂▝▄▄▄▋ ▎▂┈⎽▋▋▌⎽⎽▂▄▘┝▍┈▂▃▄▄▄▃▂▪▝▊▎▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▅▆▆▆▆▎▋▝▃▃▃▃▗▅▎⎺┊▗▋╴▍▖▆▆▝▆⎺┈▋▎⎽▃▄▆⎺▂⎺⎺▆▅▃▂⎽▂▄▎▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▊▃▃▃▃▃▚▗▆▆▆▆▆▊▃▃▃▂▃▖┈▝▃▃▃▃⎽▂▃▅▆▆▄▅⎽━▄▘┭▅▅▅▅▄▄▅▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▝▂▂▂▂▃▝▄▄▄▃⎺▂⎺⎺⎺⎺⎺⎺▄▘▃▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▖▅▅▅▄▗▃▃▃▃▂▂▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▅▅▅▆▅▃⎽▄▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▆▅▅▗▅▊▃▂▂▂▘▲▶⎽━▅▅▅▅▄▅▆▅▲▮╸⎻▃▌▲▘⎽┊┈⎽▂▃▄▅▆┈⎺⎽▂■▆▅▃▂▄▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅⎻▆▅▅▘▄▍━▅▄▋▃▍▂▂⎽▊▃▂▂▂⎽⎽▗▃▂⎺⎽▄▆▪┊⎽▗▎⎽■▗▂▃▅▗▆▆▅▄▃▖  ▝▖▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▅▅▄▄┷▗▄▃▃▃▍▃▎⎺▆▆▌▊▄▃▃▃▋▎▍⎺▆▆▌▊▆▆▅▗▅▆⎺ ┃┫▶▊▶▍▝▉ ▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▝▃▃▃▂▍▲▗▂▂⎼⎽▃▊▅▅▄▄▍▋▍▂⎺⎺▌▎▗▄▄▄▌▉⎻▄▄▊▆▝┪╴▝▊▍┈▎▗┛⎽╹▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▲▂⎼⎼⎼▆▆⎺⎺⎺▆▆▅▌▃▆▆▂▎▌▆▆▅▅▌▏⎽▅▅▄▌▉─⎼⎼▉▏▎▍⎽⎽▃▘┊▗▄▄▄▄▃▂ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▂▂⎽⎽▍▅▋▅▅▄▄▎▆▎▃▃▃▖▏▍▃▄▆▂▘▘▎▂▘┈▋ ⎽▪┈⎽▅▅▆▂▄▆⎺▍▊┊ ▌╷▶▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▆▆▆▅▅▝▄▎━━▂┱▮▊▏⎺⎺⎺▋▖▎▃▃▂▂▍▄▆▆▆▅▋ ▏▖┈⎺▍▍▂▉▌▋▘▎▎╷▍▏┊ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▘▅▅▅▅▘▆▊▂▂▂▂▋▅▋▅▄▄▄▌┳▊⎺⎺⎺▆▊▂■■▆━▋ ▎━━━▌▊┋▖▍▎▊▌▏▝▅▆▆▆▆▅▄▎▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺┑▲⎺⎺┯⎺▌▂▎▄▄▄▄▌▋▃▃▂■▪┫▆▎⎽▘┈▊ ▎⎽┈⎽▋▗▂▂▃▄▆▍▊▍┊⎼▄▅▄▄▃⎽▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▖▅▆⎺⎺▎▋▍▂▂▂▂┠▂▅▅▄▄▘╴▍▖⎺⎺▆▖▎ ▪⎽▃▅▆▼⎺▼⎺⎺▆▄▂┈ ⎽▂▘▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅▉▅▅▄▄▄▃▗▅▅▅▅▅▊ ▄▃▃▃▊┈▝▃▃⎼⎼▂▃▅■⎺▅▄▅⎽━▄▘┭▅▅▅▼■▼▄▆▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▎╾┯⎼⎼▂▪▅▄◆┯●▼⎺⎺⎺▆⎺┬▄▘▃▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▖▄▄▃▃▗▃▅▄▄▃▖▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▅▅▅▆▄▂⎽▄▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▋▅▄▄▃▘▖▄▃▃▃▅■▂▂━▂▅▅▅▄▅▆▅▅▋▝⎺▃▌▲▘┈┉▂▂▃▄▆⎺⎺⎽■▂▂⎺▆▄▂▃▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄━▄▄━▘▄▌━━━▋▃▍▄▃▃▊▃▂▂▂⎽⎽▗▆▆▆▅▅▆▪┊⎽▋▂▂⎽▂▄▄▆▌▆▆▅▄▂┈ ┈▆▖·▆▆▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▝▄▃▃▂▂▗▅▅▄▄▍▃▃▂▂⎽▌▊▄▃▃▂▋▎▅▅▄▄▖▊▆▅▅▋▆⎺⎺▝▖▍▌▊▏╹▊⎼┊▌   ▝▏▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▊▂▂⎼⎼▍⎽▗▃▃▂▂▝▊⎺⎺▆▆▍▋▍⎽⎺⎺▌▎▍━━┳▌▉▅▅⎻▊▆▝▊┈▌▝▍┈▎⎽▂⎽·▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂⎽▂▂▂▂▆▆▂⎽⎺⎺⎺▅▌▅▄▄▄▎▌▆▅▅▅▌▘▝▃▂▂▋▉─━─▊▎▅⎽⎽▃▄⎽┈▗▅▅▄▄▂⎽ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗⎺⎺▆▆▍▅▋▆▆▆▅▍▆●▂▲▲▪⎺▍▃▆▅▂▘▃▎▗⎺▆▌┊▂▪⎽⎽▆▆▃▄▆⎺▉▍▊┊┈▎ ▶▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▅⎻⎻▄▃▝▄▍▄▃▃▃▮▊▝▂▂▂▘▖▎▃▃▂▂▍▂▄▄▃▃▖┈▏▅┈▖▍▎▍▋▋┈▋▎╷▎▉▍·▃ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▝▄▃▃▃┛▆▊▄▃▃▃▋▅▗⎺▆▆▆▌┳▊┓⎺▆▆┃●▝▃▃▃▊ ▎━━┙▌▌▗▍▋╸⎽▌▎▝▆▆▆▆▅▘▃▎▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺┑▂▂▃▼▼▼▂▍▃▃▆▆▍▋▃▂■■▪╋▘▎⎺╴┊▊ ▎▂┈⎽▋⎽▂▃▅▆▍▍▂⎽▪▪▅▅▄▃▂┈▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▅▂▂▂▂┈▎▋▍▂▂▂⎽▊▖▂▆▆▆▊╴▍▪┈┝▊▌▋┈▂▃▅▆━▼▅▼┈▆▄▃┈ ▆▄┛▌▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅⎼⎻⎺·▅▅▃▗▅▅▅▅▅▊┈⎽┈⎽▘▊▏▝▄⎼▂▃▄▆▲▝▆▅▄▅⎽━▄▘┭▅▅⎺▆▅▆┑⎺▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▍┭⎼▄▄▂▝▅▅▅▄▅■⎺⎺⎻▪▄─▄▘▃▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▆▃▃▂▃▃▃▆▅▅▄▖▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▅▅▄⎽▂⎽⎽▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▃▃▂▂▄▊▅▅▄▄▅▗▄▄▃▃▆▅▅▄▅▆▅▅▋▝⎺▃▌▲▘▂▂▃▄▅▆⎺▏┈▂■▂⎺▆▅▃▂▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅━━─▂▍▄▌▄▃━▋▃▍▃▂━▊▃▃▃▂▂⎽▋▅▅▄▄▄┫▲⎽⎽▌▃⎼▂▄▅▆▎▌▆▆▅▃▂┈ ▆▅▖▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▌▂⎼⎽▊⎽▗▃▆▅▅▎▃▲▅▄▃▌▊▝▄▃▃▋▎▄▃▃▂▖▊▅▅▄▋▖┈┃▝▝╷▏▊┃┆▋▆╷▌   ▝▗▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▊⎼▃▂⎺▍▲▌▪▄▄▖▝▊▃▂▂▂▍▋▍▂⎽⎺▌▎▍▄▄▃▌▉▅▅▆▊▖▝▋┈▊▆▍┈▂▂▂⎽▶▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂◀⎽⎽⎺▆⎺▆▃▂▂⎽▋▅▌┈⎺▆▆▎▌▎▆▆▅▌▘▍⎽⎽⎽▋▉━━━▊▏⎽▂▃▅▂▃┊▗▅▅▄▃■▘▝▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▅▼⎻⎻▆▅▋⎺╴·▆▍▆▅▅▄▄▄▆▍▃▃▃▂▍▖▆▅▅▅▋ ▂▂▂▃⎺▃▄▆╴┈▉▍▊┊ ▌╷▋▝┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▗▃▃▂▂▂▝▄▘▅▅▄▄▲▊━▅▅▄▖▖▎▄▃▃▃▍⎽▃▂▲━▖ ▎▊┈▋▍▌┌╹▊┈▍▎╷▎⎽▍▏┊ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅╺▂▂⎼⎼▌▆▉━▲⎼⎼▋▅▋▂▃⎽▂▋▪▊┊⎺⎺⎺┃▘▎▂⎽▘▊ ▎▃⎻▘▌▋▊▉▍⎽▃▘▌▆⎺⎺▆▆▄▃▖▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺■⎼⎺┑▂▂▂▄▄▄▂▘▆▅▅▅▍▊▗▃▃▃▼╋▂▎▅▅▅▋ ▎▄▂▃▋▃▄▅⎺▎▎▂⎼⎼▪▅▅▄▃▂┈┈▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃┅▂━▲━━▘▋▝▃▂▂▂▊⎽▅▄▄▄▊╴▍▖┈▋▊▌▋▂▄▅⎺▼▼▄⎺⎺▆▅▃⎽ ▆▅▃▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▗▆▆▆▆▅▗▆┖┈▃⎺▊┊⎽⎼▂▃▄▆⎺■▝▆▅▄▅⎽━▄▘┭▅▅▆▄▃▃▅▘▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▝▂▲▲▲▂▝▃▃▂▂▃▅■┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄⎺ ╴▪┏╸┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▆▂▲⎽▪▃▃▆▆▅▅▎▄▃▃▃▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▅▄▄▃▂▂⎽⎽▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂⎼⎼▋▅▊▆▅▅▄▘▗⎺▆▅▅▗▅▅▄▅▆▅▅▋▝⎺▃▶▂▂▃▃▄▅▆◀▍▲▂▂━▂⎺▆▄▂▂▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▲⎼▃▃▍▄▌▅▄▄▋▃▍▆▅▄▊▃▄▄▃▃▂▊▅▄▄▃▄┫━▂▃▌━▂▄▅▆▊▎▌▆▅▄▂⎽ ┈▅▄■▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▃▂▄▊⎽▗▄▄▃▄▎▂▝▄▃━▌▊▃━▲⎼▋▎▃▂▂▲▌▊▌▮▪▋▃┈▊▉│▊▏▊●┣▝ ┈▋   ▝▗▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▂┊⎺▆▍⎽╺━▲━▪▝▊▂▅▅▄▍▋▝▃▃▂▌▎▝▃▃▂▌▉▆▆⎺▊▍▝▋┈╵┈▍┈▂▂▂╸▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▆⎺⎺▆▅▆▆▄▃▃▂▌⎽▌▃▃▂▂▎▌▎⎽⎺▖▌▎▍⎽⎺┳▋▉┅⎻⎻▊⎽▂▃▅▆▃▖┊▗▅▄▃▂▅▘ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗⎻━━⎻▘▅▗┈⎽⎺⎺▍▆▖⎺⎺⎺▆▏▍▅▅▄▄▍▖▅▄▄▄▌┊▃▃⎼╼▃▄▆⎺▖┈▉▍▊┊ ▌▘⎺▆▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▆▂▃━▂▂▝▄▘▆▆▅▅┗▊▅▄▄▃▘▃▎■━▅▄▘▏▂▅▄▄▋ ▏▊┈⎽▎▍▆▊▍┈▍▎╷▎⎽▍▏┊ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌▃▂▃⎽▌▆╺▃━▂━▋▅▋▅▅▄▄▌▪▊▂▃⎽⎽▎▘▎⎽⎺▪▊ ▎▅▆▊▌▊·⎽▂▃▅▘▋⎺⎺⎺▆▅▃▂┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆⎺⎺⎺⎺▆┑▂▂▂▄▄▄⎽▎▂▂⎽▅▍▊▌▅▅▄▘▊▲▅▄▄▄▋ ▎▃⎼╼▝▅▆┈▊▎┻▃▲━┯▅▅▄▂╴┈▆▋▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆▆▅▅▅▅▘▋▝▅▄▄▖▊▂▄▃▃▃▊┊▍▏ ▌▊▌▂▄▆⎺▼▄▃▄⎺⎺▅▃▂ ┈▅▃▂▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▌▎▅┊▗⎺▗▅⎺⎺▆▆▘⎽▂▃▄▅⎺┈▲■▝▆▅▄▅⎽━▄▘┭▅■▅▃⎽⎽▃▘⎻─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▝▄▃▃▃▄▋▂▂▲━━▃⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄⎺ ╴▪┣⎺┈▎⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅━▄▃▃▃▃▘⎺▆▆▎▖▂━▮▅▅▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▆╸▃▄▂▂▂▂⎽⎽▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎼▃▂▋⎼▊⎺▆▆▅▆▋▄▃▂⎺▗▅▅▄▅▆▅▅▋▝⎺▃▃▃▄▄▄▆⎺⎽▲▂▃▃▃▲⎺▆▅▃▂▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▃▂▂⎽▍▄▍▆▅▅▋▃▍▂⎺⎺▊▂▅▆▅▅▖▗▄▄▃▃▘▋⎼⎼╼▂▃▄▅⎺⎺▊▎▌▆▄▃⎽▘┈▆▄▂▮▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍⎺⎽▆▊▂▗▅▄▄▃▍▃▌⎺▆▆▌▊▗▄▄▃▋▎▃▂▲▪▌▊▂▂▂▋▃┈▉▌▌▍▏▊▍▝▍╴▆▖   ▝▂▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎺▆▼⎻▍⎽▝▃▃▂━▝▊▅▅▄▄▍▋▍━▅▄▌▎▝▃▂▂▌▉┊▄▆▊▊▝┫▉⎽▂▎┈▃▂⎽·▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▆▆▆▅▅▆▆▲▄▄▃▌▅▝▃━▲━▎▌▝▃▂▘▌▎▍⎺▅╍▋▉▅▅▆▉▂▄▅▂▃▅▖┊▗▅▄▂▆▄▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▅─⎼▃▘▅▗▂▂▼▂▍▆▏▄▃▃▂▮▍▏⎺▆┈▍▖▅▄▄▃▍┈⎼╼━⎻▅▆⎺┑·┈▉▍▊┊ ▌▎▆▄▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅━⎼⎼▃▄▝▄▘┈┈▆▄▮▊▎▮⎺⎺▊▅▄▄▄▃▃▘▏▲▅▄▄▋ ▎▶⎽▖▍▌▝▍▌▊┃▎╷▎⎽▌┊▎ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌┈┊▆⎺▌▆▆▅▄▃▃┛▅▋▅▄▄▃▌▖▊▄▄▃▃▎▘▎⎽⎺┅▊┊▎▆┈▉▌▎⎽▂▄▅▆▍╿⎺⎺▆▅▄▂▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺┳╴▆▆▆▆▪▂▂▂▄▄▄⎽▪▅▄▃▃▍▗▖⎺⎺⎺⎺┠▲▅▄▃▄▋ ▎╼━▘▅▖▍┈⎽▪▲▲▲▮▅▅▄▃⎽⎺┊▘▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▅⎺▆▃▋▄▃▃▂▂▊■▄▃▃▂▊┊▍▘┈▂▝▃▄▆■▼▅▄▃▼⎺▆▄▂┈┊▅▃⎽▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▌▃▂▂⎽▖▗▅⎺▆▆▆▘▃▄▅▆▅▘⎽▲●▝▆▅▄▅⎽━▄▘┭■▅▃⎽ ⎺⎽▂▝─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▅▄▆▆▆⎺▋▂▂━▪━▃⎺┳⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄⎺▉╴▪▍⎺▆▲⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴▘╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▄▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▄▄▃▃▃▃▎▂⎺▆▎⎽▄▃▃▂┱▃▂▃▃▄▃▄▂⎺▂▂▃╵▏·╸▗▅▂▂▃▃▂▂▃▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂▂⎺▋▄▊▂⎺▆▆▘▊▃▂▲▂▎▄▲⎽▅▆▅▅▋▝⎺▗▄▄▅▆▆⎺┈▂▃⎽▃▃▃▲▆▅▃▂▃▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄▂⎽⎺⎺▍▄▍⎺▆▅▋▃▍━▄▄▊▃▅▂⎺⎺▋▋▅▅▄▃▘▋┷━▄▃▄▆⎺▆╷▊▎▌▅▃▂⎽▃⎺▅▃▂┳▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▆┯⎻▊▂▗▆▅▅▄▍▃▄▃▃▂▌▊▌⎺▆▆▋▎▃▃▂━▌▊▃⎼─▋▖┈▊┝▝▖▏▊┃▎▋┮▅▖   ▝▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▅⎻▆▅▘⎽▝▄▃▃▃▝▊▂▂┭⎺▍▋▍▅▄▄▌▎▝▄▃▂▌▉⎽⎽▂▊▃▝▊⎽▂▃▎┈▂▂┛▉▝▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▅▄▄▄▆▆■━▅▄▌▅▌▆▆▅▅▝▌▃▆▅▄▘▎▍⎽▪⎺▋▉⎺┈▆╲▄▅▂▄▅▆▋┊▗▄▃■▄▂▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▄▃▂▂▌▅▗▃▂▂▂▍▆▝▃━■■▏▍▃▃▂▂▍▖▅▅▄▄▌┊━⎻▅▆⎺⎺▎▉▏┈▉▍▊┊ ▎⎺▅▃▎▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅⎼▃▂▂▖▝▄▘┊▆▆▆▎▊▝▄▃▃▖▖▘⎺⎺▆▆╿⎽■━▄▄▋ ▂▃⎼┘▍▌▌▊▌┈⎽▎╷▎⎽▌▗▘ ▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌┈▆▆▆▍▆▅▆▅▄▄▘▅▋⎽⎽⎺⎺▋▅▄▄▃■▪┨▘▎⎽⎽┉▊┊▎▌ ⎽▌⎽▃▄▅▆⎺▖▊⎺⎺▆▄▃┈▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▃▆▅▅▄▘┑▂▂▂▄▄▄▂▝▅▄▄▄▘▊▝▃▃▂▂┣┙▎▅▄▄▋┈▎⎻▅▚▋▍▊┈⎼▲▂▂▮▆▅▄▃▂▉▆▄▂▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃▗▍⎺▆▆▅▊⎽▅▄▃▃▊┈▍▘▖▂▃▅▆▼▃▆▅▄▼▼▆▄▂┈┊▅▃┈╶▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▝▃▂▲▲▂▗▆▝⎺⎺▆▘▅▆⎺▄▄▅⎽▲●▝▆▅▄▅⎽━▄▘▆▄▂ ┈▅▃▉▎─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎┚▄▝▃▂▂▲━▄⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄⎺·┆⎺▄▅▅⎺▲▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴▘╵╴┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▗▅▆▆▅▅▅▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▃▄▃▃▃▃▎▂▂▆▎▄▆▆▅▅▗▃▂▃▃▄▃▄▂⎺▂▂▃╵▏▂┈⎽▂▃▃▃▃▂⎽▃▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂▆▆▖▄▊▂⎺⎺▉▘▋▆▅▄▄▆▗▄▃▂▂▅▅▋▝⎺▃▅▆▆▪▗⎽▂▃⎼▄▄▃▂⎺▆▄▂▃▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄⎺⎺⎻▆▘▄▍⎽▆▆▋▃▍▅▄▃▊▃▗▂▲⎼▋▗▆▆▅▄▍▋⎻▅▆▗▆⎺▄▪⎺▊┏▗▄▂▲▄▃▆▄▂⎽▆▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍┯┱⎻▗▂▗⎺▆▅▅▍▂▝▃▅▅▌▊▌▄▃▂▋▎▅▄▃▃▖▊┶━▄▋▖┈▊▉▊▊▏▊▶├╴▆▄▂  ┈▝▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎻▆▅▅▘▮▗▅▄▃▃▝▊━▄▄▃▍▋▍⎽⎺⎺▌▎╏▅▄▄▌▉▂▃⎼▊▄▂┹▂▃▃▘┈▂⎽▎⎺▉▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▄▃▃▃▆▆▪━━▅▌▅▌▃▂▂⎺▎▌▆▆▅▄▌▘▝▂⎽⎽▋▉▪┈▮▂▆▂▄▅▆▆▋┊▗▄▂▅▃⎽▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▃▂⎼⎼▍▅▗▄▃▂▂▍▆▖⎺▆▅▅▏▍▄▃▂━▘▗▎▆▅▅▌▉▅▆⎺▃▎▌╴▋╴┈▉▍▊┊ ⎺▆▄⎽▖▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃━▃▂▂⎽⎺▃▄▘┊┊▆▆▏▪▄▃▃▆▖▖▏▄▃▂▂▍▂▃▂■━▖ ⎼╼━▘▍·▄▊╴⎽⎽▎╷▎╶▌·▋┈▍┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▗▆▅▅▄▍▆▆▆▆▅▅▘▅▋▄▃▃▂▌▪▖⎺⎺▆▆▊▏▝▂▂▃▊┊▎▊⎽▖▝▃▅▆⎺⎺▌●╿⎺▆▅▃⎽┈▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▅▅▄▄▃▘┑▂▂▂▄▄▄▂▂⎺▼⎺⎺┻▊▋▃▂▆▆●▃▎▆▅▆▋┊▍▖┊▊▋▍▪⎼▂▂▂▂▆▅▅▃▂⎺▆▄▎▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃▋▝▂▂┈⎽┃▃▂▅▅▄▊ ▍⎼▂▄▅⎺▼▄▃▆▅▃▼▆▅▃┈┊▆▄┈┈╶▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▼⎺▆▆▅▆▊┈▏┈▂⎺▉⎺▅▄▄▄▅⎽▲●▝▆▅▄▅⎽━▄⎺▆▄▂┈┊▅▃▘▉▎─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▄┹▄▃▃▃▃▅⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄◀▉╸⎺▆▄▄▅▆▮▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶ ┚│▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▗▅▆▆▅▂⎽▅▄▄▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▃▄▃▃▃▃▎▃▂⎺▎▖▄▃▂▂▎▃▂▃▃▄▃▄▂⎺▂▂▃┊⎽⎽▂▃▃▄▄▄▃▂▂▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎺▆▅▖▄▊▃▂⎺⎺▅▋▃▂⎺▉▪▘⎺▆▅▅▆■▋▝⎺▗▆⎻▎⎽▍▃⎼▄▃▄▄▃▂⎺▅▃⎽▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗⎺▆▆▆▘▄▍⎽⎺▆▋▃▍⎽⎺▆▊▂▆▆▅▄▋▗▖▆▆▆▍▋▅╴⎺▌┈⎺┒▅┍▊▮▗▃▂▅▄▆▅▃⎽┵╸▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▅▅▅▗▂▗⎽▆▅▅▍▃▆▆▅▅▌▊▗▄▃━▋▎▗▅▅▄▖▊⎻▅▆▋▖┈·│┕▉▏▊┠▍▆▅▃▂  ┈▄▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎻▅▅▄▘▮▋▅▄▄▃▝▊▅▄▃▃▘▋▂▅▄▄▌▎▗▃━━▌▉─━━▊╵⎽▂▃▄▄▘┈▎┟▋╵⎺▉▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝▄▃▂▂▆▆▪━━━▌▅▌━▅▄▄▎▌▃▂▂⎺▌▎▝▄▃▂▋▉⎽▂▃⎼▃▄▅▆⎺⎺▋┊▗▂▆▄▂┑▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▂⎼▃▂▍▅▋▄▃▃▂▍▆▄▃▂▂⎽▏▍⎺⎺▆▆▌▘▎▂▗⎺▌▉⎺▖┈╻▎▋ ▍▏┈▉▍▊┊┈▆▄▂▅▖▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▘⎽⎽⎺⎺▄▄▘┈┊⎺⎺▏▍▖⎺▆▆▅▄▎▄▃■■▍▄▅▅▄▃▖ ▏⎻▅▚▍▎╴⎽╴⎽▂▎╷▎╶▋┊│┈▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▗▆▅⎻▄▍▆▆━▆▅▅▘▅▘▄▃▂▂▘▖▊▄▃▂▂┃⎽▎▄▄▃▋ ▂▃⎼╸▄▅▆┈▋▌▎▋╿▆▅▃▂▖ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▅▄▃▃▃▎┑▂▂▂▄▄▄▂▂▄▄▃▃▃▗▖⎺⎺▆▆▎▘▎⎺┊┊▊╴▎▍ ▊▋▍⎼▂▂▂⎽⎽▆▅▄▂▶⎺▅▃▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃▝▃▃▂▂▃▄▂▄▃▂▆▋⎽▂▃▄▆⎺▝▄▄▃▆▅▼▆▅▃⎽┊▆▄·┈┈▖ ▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▊ ▃▂⎽⎽▉●▃▄▄▄▅⎽▲●▝▆▅▄▅⎽━▼▆▄▂┈┊▅▃▘▋▊▏─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▅▆▆▅▅▅▮⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄◀·⎺▆▅▃▃▄▆▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚╴╄╶ ┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽⎼▅▆▆▅▗▃▂▂▗▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▃▄▃▃▃▃▎▃▂⎺▎▊▃▂⎼⎼▎▌▃▂▂▪▃▄▂⎺▂▂▃▂▂▂▃▄▄▄▄▄▃⎽▂▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎺▆▅▖▄▊▃▂⎺⎺▅▋━▄▃▖▄▎╾▄▃▊▆▂⎽▪⎽▗▖▂▂⎼▍▄⎽⎽┷▄▃▂┊▆▄▂⎽▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗⎺⎻▆▅▘▄▍⎽⎺▆▋▃▍▄▃▂▊▃▋▃▂⎺▋▍▝▃▂▂▌▋▅┈▂▌┈▝▝⎽▝▊⎺▗▂▗▃▅▅▃▂▮▮▆▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘┱▆▅▗┺▗⎽▆▅▅▍▃▃▂⎽▆▌▊▌⎽⎺▆▋▎▍⎺⎺▆▌▊▆▆▅▋▖ ▊▉▝⎽▏▊┇⎺▅▃▂▗╸┈⎺▄▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗╺▅▄▃▘⎽▋▅▄▄▄▝▊▮⎺▆▅▍▋▆▅▅▄▌▎▍▅▄▄▌▉▄▄▅▊▂▃▄▄▄▄▍┈▎▉▋▝╵⎺▏▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝▃▃▂▂▆▆▄━━━▌▅▌▅▄▃▃▎▌▄▃▂▲▘▏▃▃▄▃▌▉⎼⎼━▄▄▅▆⎺┈⎺▋┊▗■▄▂⎽▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅╺⎼⎼▃▂▍▅▋▄▄▃▃▍▆▎▆▅▄▄▪▍▅▃▃▂▌▎▝▃▂▂▋▉▏▪▂▗▎▋╶▉⎽┈▉▍▊┊⎺▅▃▆▃▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂⎽⎺⎺▆▄▄▘⎽┊⎺⎺▲▪▃▂⎽▂▊▉▪⎽▼┱▆▌╸▎⎺▆▆▋┈▆■┈▌▍▍⎽▂⎽⎽⎽▎╷▎╷▌▂⎺▆▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▗▅⎻⎻▄▍▆▆▼▆▅▅▅▅▄■▆▆▅▅┱▃▅▄▃▃┫▖▗▃■■▋ ▎━━▘▆▖⎺ ▋▊▪▍▍▝▄▂▍▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▅▄▃▃▂▆┑▂▂▂▄▄▄▂▂▄▄▃▃▃▊▝▄▃▃▃┃▮▝▂▃┈▊┈▎▊┈▃▝▂▂▂▂▂⎽▆▅▄▂┈▆▅▃⎽▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━╍▆▆▆⎺▊▄▎▆▅▅▘▃▄▅▆·┒▆▄▄▃▆▆■▅▃▂┈▆▄▂▊┈▅╶▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▊⎽⎽▅▄▖▂■▃▄▄▄▅⎽▲●▝▆▅▄▅⎽━▆▅▃┈┊▅▃▂▌▋▊▎─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▼▼▼▼▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶▚▄◀⎺▆▅▄▃▃▄▅▲▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚┤└╶ ┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▗▅▆▆▅▗▅▄▃▖▅▃▃▄▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▃▄▃▃▃▃▎▃▂⎺▎▖▅▄▃▃▎▗▆▅▄▄▃▄▂⎺▂▂▂▃▃▄▄▅▅▅▄▃▂⎽▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎺▆▅▖▗▊▃▂⎺⎺▪▋▄▃━┑▅▎▅▄▃▘■▄▄▃▂▘▋⎼⎼╼▂▂▂⎽━▄▃┊▆▄▂▝┊▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗⎺⎻▆▅▘▄▍⎽⎺▆▋▃▍▃▅▄▊▃▏▃━━▋▍▃▂▲⎼▍▋▲▂⎼▌┈▅▝⎺⎺▆▆▂▲▄⎺▆▄▂⎽⎽▄╸▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▘▅▆▅━┷▗⎽▆▅▅▍▃▲▄▃▃▌▊▂▅▄▃▋▎▝▄▃▂▌▊▄▋⎼▋▖┈·▉▝▖▏▊⎺▆▄▂▲▄▪┊⎺▄▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎻━▄▄▘▮▋▅▄▄▃▝▊▃▂⎽⎺▍▋▝▃▂⎽▋▎▍⎽⎺▌▉▆▆⎺▊▃▄▄▅▄▃▎┈▎┆▎┕⎺▆▎▍▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝▃▃▂▂▆▆▪━━━▌▅▍⎺▆▆▅▎▌▎⎺▆▆▌▃▆▅▄▄▌▉━⎻▄▅▆⎺⎺⎺┈▆▖┊▆▅▃⎽▪▋▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▂⎼▃▂▍▅▋▄▃▃▂▍▆╏▄▄▃▃▏▍▅▄▃▃▍⎽▃■▲▪▌▉▂▃⎼▗▎▋╴▎▖┈▉▍▊⎺▅▃■▄▂▏▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂⎽⎺⎺▆▄▄▘┈┊⎺⎺▏▉▂▅▄▃▋▃▎▅▄▄▃▍▘▝▂⎽┹▊┊▎▊┈⎽▎⎽⎽▂⎽⎽⎽▎╷▎╷▍⎺⎺▅▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▗▅⎻▄▃▍▆▆━▆▅▅▘▅▅▅▄⎺⎺▅▖┫▂⎺⎺▆▎▘▎▆▅▆▋┈▎▅▆▊▌▎▎ ▌▊⎺▍▍▝▂⎽▘▎ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▅▄▃▃▃▎┑▂▂▂▄▄▄▂▂▄▄▃▃▃┻▅▄▃▃▄▉▖▂▅▅▅▋ ▎▂⎼▂▂▃▃▂▂⎽▆▅▄▂┈┈▅▃▂⎽▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅▆▊┈⎽▃┈▗▗▅▆⎺▘▄┒▆▄▄▃▘⎺▆▄▂┈▆▄▂┡⎺▅▄▂▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅╇▅▄▃▃▄▆■▃▄▄▄▅⎽▲●▝▆▅▄▅⎽┯▅▃⎽ ▆▄▂⎽▌▋▘▏─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶╻▅·⎺▅▄▃▃▃▄▅▲▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿⎽▅⎻╷·┘╴┚│▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▗▅▆▆▅▘▆▅▅▖▅⎽▮▅▅▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▃▄▃▃▃▃▎▂⎺▆▎▖▆▆▅▄▎▍⎼▂⎺▊▃▄▂⎺▂━▃▄▅▅▅▅▅▅▄▃▃▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎽▆▆▖▄▊▂⎺⎺⎺▆▋▆▅▄▼▮▎▂⎺⎺▊▗▖▆▆▅▌▋━┷▄▂▂▂▲╾▃■⎺▅▃⎽┈▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▗⎺⎺▆▆▘▄▍⎽▆▆▋▃▍▄▄▃▋▃▏⎺▆▅▋▍▆▅▄▄▖▋⎼⎼╼▌┈▊▋╴⎺▅▅▃▄▃▆▅▃▂⎽⎽▮╸▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▅⎺▅▗▂▗⎺▆▅▅▍▃▃▃━━▌▊▅▅▄▄▋▎▗▃━━▌▊⎽▂▂▋▖ ▊▉▝┪▶▊⎺▅▃▲▄▃▂⎺⎺▝▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎻▅╾▄▘▲▗▅▄▃▃▝▊┭▄▃▃▍▋▄▃▂▲▘▎▂▄▃▃▌▉┈▅▍▝▄▅▅▅▄▃▍┈▎┇▎▪⎺▆▎▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▝▄▃▃▂▆▆▪━━▅▌▅▌▂⎽⎽⎺▎▌▝▄▃▃▌▘▍⎽┊▗▌▉▅▆▆⎺⎺▆⎺⎺⎺▆▖┊▝▃⎽▂╵▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▂⎼⎼▄▍▅▗▄▃▂▂▍▆▏▆▆▅▅╍▍▂▪⎺⎺▌▄▆▅▅▄▋ ⎼╼━┛▎▋▉▊▖┈▉▍▊▆▄▂▅▃⎽┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂▂⎺▄⎺▃▄▘┊┊▆▆▮▉▄▃▂▲▗▅▃▆▅▄▄▖⎽▶■▅▅▋ ▏▶▂▖▎▂▂▂⎽⎽⎽▎┊▎╶◀⎺▅▄▖┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▗▆▅⎻▄▍▆▆▆▆▅▄▘▅▅▅▄▃▲▅▖▂▆▅▄▖▘▘▎▂▃▘▊╴▎▪┈▊▌▎▊ ▌⎺⎺▍┥▝⎽│▍▎ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▅▄▃▃▃▘┑▂▂▂▄▄▄▂▂▄▄▃▃▃▃▂⎽▼▼▼▂▃▆▅▄▄▘ ▝⎼▂▃▃▃▃▂⎽⎺▆▄▃▂┈▆▄▂⎽▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅▆▊⎽▅▄▄▃▂▅▃▆▄▅┒▆▄▄▃⎺▆▄▂┈⎺▅▃╸⎺▅▘▃⎽▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃━⎺▃▃■■▃▄▄▄▅⎽▲●▝▆▅▄▅■▅▃⎽┈▆▄▂┈┈▌▋▊▎─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶╻⎺▅▄▃▃▃▄▅▆▮▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿⎽▅⎻┆ ╵╴┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊⎽▄▅▆▆▅▍▆▆▆▍▗▄▃▂▂▄▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▄▄▃▃▃▃▎▂⎺▆▎▖▪⎺▆▅▎▍▃▂▂▊▄▂▲▘▂┷▅▅▆▆▅▆▅▄▃▂▄▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▂⎺▆▖▗▊▂⎺▆▆▅▋⎺▆▅▅▮▎━▄▄▊▗▌▃▂▂▌▋▄▄▅▃▂▂━▄▂⎺▆▄▂┊·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄▂⎺⎺▅▘▄▍⎺▆▅▋▃▍▅▅▄▊▃▲▄▃▂▋▍▍▂⎺⎺▌▋┷━━▌┈▊⎺⎺▆▄▃╾▃┊▅▄▂⎽⎽⎽▮▆▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▆┱⎺▗▂▗▆▅▄▄▍▂▅▄▃▂▘▊▃▂⎽⎺▋▍▍▆▅▅▌▊▃⎼⎼▋▖ ▪▖│▉▌▉▅▃▂▪▃▂⎽⎺┈▝▆▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎻▆▅▅▘⎽▗▄▃▃▂▝▊━━▄▄▍▋▍▆▆▅▌▎▄▄▃▃▌▉⎽■⎽▅▅▅▅▄▃▂▍┈▎▉▎┊▆▅▖▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▄▄▃▃▆▆■━▅▄▌▅▌▃▃▂▂▎▌▅▄▃━▌▏▂▪▄▃▋▉▆┈▆▉▏▖▍⎺▆▅▖┊▝▂┫▎┋▌▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊▃▂▂⎼▍▅▗▃▂▂▂▍▆╽┊⎺▆▆⎺▍▅▄▄▃▌▘▎⎽⎽▼▋┊┅⎻⎻▘▎▋▎▌╴┈▉▌▉▄▂▅▃▂▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▘▂⎽⎽▊▃▄▘┊▆▆▅▮─▅▄▃▃▘▖▎▂⎽⎽▅▍╸▎▅▅▅▋ ▂▃▃▖⎽▂▂▂⎽⎽▍▎·▎┍▋▆▄▃▂┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌⎺▆▅▅▍▆▅▆▅▄▄▘▅▅▅▄▃▲▅▅▅▄▄▃▄▉▂▂▅▄▄▋┊▎▏ ▉▌▎▌┈▊⎺▆▘┑▌▂▄▏▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺▆▅▄▄▄▎┑▂▂▂▄▄▄▂▂▄▄▃▃▃▃▂▄▃▃▃▘▆⎽▘⎺┊▊⎽▂▂▃▄▃▃▂⎽⎽▆▅▃⎽▋▆▄▂╸▗▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▝▄▄▃▃▃▆▄━▆▄▅┒▆▄▄⎺▆▄▂┈⎺▅▃▘⎺▆▄▘▂⎽▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲●▝▆▅▄▝▆▄▂ ▆▄▃┈┈┈▌▋▉▝─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺╶┝⎺▆▄▃▃▃▄▅▆⎺▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚┆ ╶╶┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂▂⎽⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▍▂⎺▆▍▗▆▆▅▄▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▅▄▄▃▃▃▃▘▆▆▆▎▖▂⎺▆▆▎▍▆▅▄▄▖▄▄▃▃▂▆▆▆▆▅▆▅▄▂▄▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌▃▂▆▋▄▊⎺▆▆▅▆▋▮▆▆▆▪▎▅▄▃▊▍▄▃▂▂▖▋▅▅▅▃▂▲▅▃▂▆▅▃╴╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▄▃▂⎺⎺▍▄▍▆▅▅▋▃▍▆▅▅▊▃▃▃▃▃▋▍▍▅▄▃▍▋▄⎻⎻▌┈▄▃▆▄▃━▃┈▆▄▃▂⎽⎽⎽▂┳▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍▪⎺┳▊⎽▗▅▄▄▃▍▂▅▄▄▃▘▊▂▲⎼▄▋▍▝▃▂⎽▌▊╼━━▋▖┈▉▌▅▉▮▊▄▂■▄▂▂⎺┈ ▝▗▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗▆▼⎻▆▘▲▝▃▃━━▝▊▃━━▅▍▋▝▃▂⎽▋▗▍⎺▆▆▍▉▂▂▂▄▆▆▅▄▃⎽▍┈▎┣▋⎺▆▅▖▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂▅▅▅▄▄▆▆▲▄▄▃▌▅▌▄▃▃▂▎▌▎⎺▆▆▌▃▅▄▄▃▖▉▪┈▖▉▏▝⎺⎺▅▄▂╴▌⎽▌▖▊▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▊⎼▃▃▂▘▅▗▂▂⎽⎽▍▆▂┈┊▆⎺⎺▍▅▄▃▃▍▏▶▅▄▄▋▉▅▅▆▎▋▍▉╵┈▉▍▗▃▆▄▂▂▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▅▂⎼▂▂▂▝▄▘┈┈▆▄▮─▅▅▄▄▘▃▎▅▄▄▃▍▘▎⎽⎽▪▊ ⎼⎼╼━▂▂▂▂▏┈▘▎▉▎▝▆▅▃▂▂┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌┊⎺▆▆▍▆▆▅▄▃▃┛▅▅▅▄▃▲▅▅▅▅▅▆▆▖▃▅▅▄▄▘ ▎▉┈⎽▌▎▪ ▪▆▅▘▏▌┏▍▍▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺┊▆▆▅▅▘┑▂▂▂▄▄▄▂▂▄▄▃▃▃▃▂▄▃▃▃▊⎽▅▄▄▄▂▃▃▃▄▄▃▃▂⎽▆▅▃▂▌▝▄▂⎽╴▗▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆▄▄▆▆▄▄▄━▆▄▅┒▆▄▄▆▄▃┈┈▅▃┈▌▆▄▃▘▂⎽▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲●▝▆▅▆▆▄▂┈▆▅▃⎽┈ ┈▌╿▊▄─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆⎺▪⎺▆▅▄▃▃▄▅▆⎺⎺▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╿⎽▅⎻╴▘╶ ┸┅▆▄▄▄▅▅▅▆▅▆▆▅
▄▄▄▃▃⎽⎽⎺╵╺━━⎽▃▃▃━⎺▃▃▃▂▃▃▃▄▃▃▄▃▂▂▂⎽▂⎽╴▏▪┋└·▝▆■▆▃▝▃▃▃▂▃▃▄▄▃▄▃▂▂┺ ▃▃▃▄·⎽▗▃▂▉╺■⎺▆▆▄▄
▄▄▅▆▅▅▅▅▄▅▆▄▃▃▂▂▂■■▆▊▗▄▅▆▆▅▍▂⎺▆▍▗▃▂⎺▆▃▄▼┖⎽·▆▅▆▆▂▆▆▆▆▄▂▃▄▄▃▃▆▆▄▃▃▃▃▃▃▂▆▃▂▃▄▃▃▃▂▂▪
▲▅▅▆▂┷▃▄▅▆▆▅▅▅▆▲▪▄▃▃▃▆▆▅▅▎▖▂⎺▆▆▎▍●⎺▆▊▗▄⎺▆▅▄▖⎺⎺⎺▅▅▄▃▃▅▅▆▆▂▅▅▆▆▆▆▆▃▅▄▄▅▆▅▃▄▄▄▅▅▄▃▂
▂▄▄▄▄▄▄▄▄▃▅▅▅▃▌⎼⎼▃▋▗▊▆▅▅▄▆▋▮▆▆▆▮▎⎽▆▆▊▍▆▆▅▅▖▋▆▆▆▃▂━▄▂▪▅▃⎽╴╵·▆▅▄▄▅▅▅▅▅▄▄▅▃╷╵·⎺·⎻┄▆
▅▃▆▅▄▆▅▄▄▄▄▃▆▅▃▃▂▂▎▄▌▅▄▄▋▃▍▆▅▅▊▂▄▆▅▄▋▍▗▄▃■▍▋▅▅▅▌┈▄⎺▅▃●▄╾⎺▅▃▂┷⎽⎽⎽▂▖▆┗╷▆·┈⎺ ┈▉┺┎╲╎
▅▄▅▅▆▆▃▎⎺╲▄▃▄▍⎽⎽⎺▋⎽▉▝▃▃▅▍▂▅▄▄▃▘▊▗▄▃▃▘▎▃▂▃▃▌▊━━▃▋▖┈▪▝▝╷▏▄▃▲▗▃╾⎺╺  ▝▗▃▅▄▃▂▏╲▉╴╽▪▉╴
⎽┈▅▃▃▄▃▂▗▲▼▅▗⎺▆▆▼▍⎽╺━▲▅▪▝▊▃━▅▅▍▋▍━▅▄▌▘▝▃▃▂▌▉▃⎼▃⎽▆▅▄▃▂┅▍┈▎▝┓▆▆▅▎▌▖ ▍▄▄▄▃▂▃▂╌╶▪▌⎼┊
▃▂▂▃▄▃▃▅▆⎺▃▂⎺⎺▆▆▅▆▆▄▃▃▂▋⎽▌▄▃▃▂▎▌▝▂▂⎽▌▅▍⎺⎺⎺▋▉▮┈▮▉▏⎺⎺▆▄▂⎽ ▌▊▋·▊▊▌ ▖▄▝■▌▖▂▂▂▂▂⎽▂▄▄▄
⎺▆▄▄▅▄▅▅▄▅▅▗▅╾─▄▘▅▗⎽⎽⎺⎺▍▆⎽┊┊▅⎺⎺▍▏⎺▆▆▍▖▗▄▄▄▋▉▆▖▆▖▎▋▎▋╴┈▉▪⎺⎽▄▃⎽⎽▊┊▍▅▝▌▅▅▄▄▃●▆▆▆▅▆▅
▃▃▄▄▃▄▖▅▄▃▆┻▂▂⎼▪▝▄▍┈▆▅▅▲─▅▅▄▄▘╻▃▃▂▂■▘▏▝▅▄▄▋ ━━━┷▂▂▂⎽▊┈▋▎▮▎⎺▅▄▃▂▂┊▏▖▝▄▅▅▅▅▆▆▅▅▅▅▄
▅▄▃▃▂▃▃▃▂▅▌⎽▅▂▄▌▆▉▃━▂━▋▅▅▅▄▃▲▅▅▅▄▄▅▄▖▘▎▘▅┈▊ ▎▂┈⎽▌▎▎┊▆▅▃▌▏▌┅▍▌▍ ▊┊▍▉▌▌▄▄▗▅▅▅▅▃▃▄▂
▅▅▅▆▅▅▄▮⎺⎺⎺⎺⎺⎺▆▆┑▂▂▂▄▄▄▂▂▄▄▃▃▃▃▂▄▃▃▃┹▄▃▃▂▃▄▄▄▄▄▄▃▂⎽▆▅▃▂▎▶▝▃⎽▅┊▗▊▍▊▝▝▊▖▄▄▊·▎▘▅▅▘▉
▂▅▃▂▃▂▌▄▂▅▆▄⎽▆▆▅▅▅▆▆▃▂▃▆■▆▃▆▄▃━━▆▅▅⎽▆━━▆━━▄▃━▆▃▄┒▆▼▆▅▃┈┈▅▃┈┈▆▅▃▂▋▂⎽▎▋▋▂■■▅▄▅▅┓⎽▃
▄▃▄▅▅▅▘▂▂▄▅▂▂▂▂▂▄▄▃▃▄▅▅┷▅⎺ ▄⎺▃▃▄▄▅▅▅▄▃┳⎽▃▂■■▃▄▄▄▅⎽▲●▝▆⎺▆▄▂┈▆▅▃▂▊  ┈▝⎽▋▂─■▲▲⎽▂▄▄▅
▃▄▄▄▄▃▝▅▄━▆━━▆━━▆▆▆⎺▆▆▅▄▄▃▃▃▃▂▃▃▂▎▃▃▄▄▄▄▃⎺▆⎺┯⎺◆▄─▄▘▃▃▃▃▄▆┘▪▆▅▄▃▃▄▅▆┯◆⎽▪▍▝▄▄▃▂⎽▋▗
╁⎺▆▅▅▄▅▅▃▅▝▅▄▃▄▄▅▆━▌▂▂▅▍▅╸▅▅▅▆▆▆▆▅▅▄▂▄▅▖━▃▃▂·╎■╷▆▅▄▅╵┒┕▄▪⎽╷·▅┚┤└╶·┸┅▆▄▄▄▅▅▅▆▅▆▆▅
[?25h

View File

@@ -32,3 +32,5 @@ using namespace WEX::TestExecution;
// Include our common helpers
#include "common.hpp"
#include "resource.h"

View File

@@ -0,0 +1 @@
#define CHAFA_CONTENT 300

View File

@@ -1,5 +1,5 @@
{
"$schema": "http://universaltest/schema/testmddefinition-2.json",
"$schema": "http://universaltest/schema/testmddefinition-5.json",
"Package": {
"ComponentName": "Console",
"SubComponentName": "Host-FeatureTests"
@@ -30,6 +30,7 @@
"AdditionalPlugins": [
{
"Type": "Microsoft.TestInfrastructure.UniversalTest.TestMD.Plugins.Winperf.WinperfPlugin",
"Role": "Default",
"Parameters": {
"MachineConfigurationParameter": "none",
"LocalReproUpload": "true"

View File

@@ -545,6 +545,8 @@ void SCREEN_INFORMATION::RefreshFontWithRenderer()
->TriggerFontChange(ServiceLocator::LocateGlobals().dpi,
GetDesiredFont(),
GetCurrentFont());
NotifyGlyphWidthFontChanged();
}
}
}

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