Compare commits

..

47 Commits

Author SHA1 Message Date
Dustin L. Howett
e33bc3d137 build: separate vpack creation from vpack publication (#19380)
This will allow us to publish vpacks without making the build fail
waiting for us to *merge* those vpacks into Windows. It also gives us
better control over when and where the vpack update gets merged.

(cherry picked from commit 6b428577b9)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfLAks
Service-Version: 1.24
2025-09-25 13:37:20 -05:00
Dustin L. Howett
6aae2d0b72 Move newTabMenu creation to Settings fixups (#19353)
Some of the other settings fixups require there to be a valid
NewTabMenu, rather than just a temporary object. Since the resolving all
the menu entries after loading already forces the user to have a
`newTabMenu`, let's just codify it as a real fixup.

I've moved the SSH folder fixup after the settings fixup because it
relies on there being a NTM.

I decided not to make this fixup write back to the user's settings.
There are a couple reasons for this, all of which are flimsy.

- There are a number of tests that test fixup behavior, especially those
around actions, which would need to be updated for this new mandatory
key. I did not think it proper to add `newTabMenu` to ten unrelated
tests that only contain actions (for example.)
- We actually don't currently have mandatory keys. But this one was
always being added anyway, in a later phase...
- It's consistent with the existing behavior.

Closes #19356

(cherry picked from commit e80aadd98b)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0HjU
Service-Version: 1.24
2025-09-16 18:31:42 -05:00
Dustin L. Howett
148a86c81c VsDev: reject VS instances which do not actually contain devshell/devcmd (#19352)
Closes #19169

(cherry picked from commit 1926c4601c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0Hto
Service-Version: 1.24
2025-09-16 18:31:40 -05:00
Dustin L. Howett
94e96ca048 Avoid generating SSH profiles using stale memory (#19354)
You can't return a `string_view` to a temporary. It's a miracle this
ever worked.

Broken since inception in a5f9c85c39

Closes #19355

(cherry picked from commit 46b9572e60)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzge0HmE
Service-Version: 1.24
2025-09-16 18:31:39 -05:00
Ayman Bagabas
b53d7df066 Add support for VT horizontal mouse wheel events (#19248)
This adds support for horizontal mouse wheel events (`WM_MOUSEHWHEEL`).
With this change, applications running in the terminal can now receive
and respond to horizontal scroll inputs from the mouse/trackpad.

Closes #19245
Closes #10329

(cherry picked from commit 814f78ed2c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgep1sM
Service-Version: 1.24
2025-09-16 18:31:37 -05:00
Leonard Hecker
1194ad0343 Fix behavior of split-pane for existing windows (#19347)
Closes #18815

## Validation Steps Performed
* `wt -w 0 sp` splits the current tab 

(cherry picked from commit 0aee174e68)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeV1i4
Service-Version: 1.24
2025-09-16 14:38:44 -05:00
John Cavanaugh
04e1290102 Fix terminal profile schema to allow null in keybinding id (#19332)
Fixes the terminal profile jsonschema to allow for null in the id. This
is to match the current implementation when disabling a built in default
keybind.

(cherry picked from commit eb16eb26ab)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgemwL0
Service-Version: 1.24
2025-09-16 14:38:43 -05:00
Leonard Hecker
f5083714c1 Fix a crash in _makeCursorVisible (#19329)
Fixes the crash and also makes `SnapOnOutput` a bit nicer.

Closes #19325

## Validation Steps Performed
* Launch vim in WSL
* Exit
* No crash 

(cherry picked from commit 384932183f)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgegVZ8
Service-Version: 1.24
2025-09-16 14:38:41 -05:00
Leonard Hecker
7642eec206 Fix right click on tabs closing them (#19273)
I do not like this.

## Validation Steps Performed
* Enable close buttons on tabs
* Open a tab
* Close the tab with middle click
* Open a tab
* Right click the tab
* Tab doesn't close, Menu opens 

(cherry picked from commit 4a34a76504)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeAlgM
Service-Version: 1.24
2025-09-16 14:38:40 -05:00
Myungchul Keum
37c674dd6e Adjust "Dimidium" color scheme (#19303)
- Add Selection BG color
- Make Bright white brighter

## Summary of the Pull Request
Final tune for Dimidium color scheme before its release.

## References and Relevant Issues
#18563

## Detailed Description of the Pull Request / Additional comments
I made little change to Dimidium color scheme.

<img width="640" height="174" alt="cmp-lightness1c"
src="https://github.com/user-attachments/assets/2e4aa6ca-5864-4901-b323-2e2bb2bf00e8"
/>

![preview-terminal](https://github.com/user-attachments/assets/8a53c54d-942a-44a2-9ee7-9ff8a6d2dfab)

<img width="584" height="207" alt="image"
src="https://github.com/user-attachments/assets/b70b0759-7961-4f8f-aaa7-762fc48e425b"
/>

- Adjusted "Bright white" slightly brighter, hoping it can be
distinguished better from "White".
- Defined "Selection Background" color.

This will be the final tune for Dimidum color scheme.

(cherry picked from commit 8011f3e28c)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeMuoQ
Service-Version: 1.24
2025-09-05 14:42:10 -05:00
Leonard Hecker
3a57ba54a0 Avoid reentrancy issues when dropping AppHost (#19296)
tl;dr: ~Apphost() may pump the message loop.
That's no bueno. See comments in the diff.

Additionally, this PR enables `_assertIsMainThread` in
release to trace down mysterious crashes in those builds.

(cherry picked from commit 8d41ace320)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeJeYA
Service-Version: 1.24
2025-09-05 14:42:09 -05:00
Leonard Hecker
fc465bdae6 Fix CoreWindow being destroyed after handoff (#19298)
As per: https://github.com/microsoft/terminal/discussions/19280#discussioncomment-14237148

## Validation Steps Performed
* Launch wtd via handoff (spawn cmd, etc.)
* Shift+Click the tab bar + button to create a new window
* Close the initial window
* UI doesn't lock up 

(cherry picked from commit 7849b00cbd)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeLN9s
Service-Version: 1.24
2025-09-05 14:42:08 -05:00
Leonard Hecker
04a5dffdcd Fix a race condition around Open/CloseClipboard (#19297)
tl;dr: Open/CloseClipboard are surprisingly not thread-safe.

## Validation Steps Performed
* Copy a large amount of text (>1MB)
* Run `edit.exe`
* Press and hold Ctrl+Shift+V
* Doesn't crash 

(cherry picked from commit 5899343237)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeLIjw
Service-Version: 1.24
2025-09-05 14:42:07 -05:00
Dustin L. Howett
894e57769b Include Profile.BellSound as a media resource (#19289)
I legitimately cannot figure out how I forgot this. Bell should support
all the same validation as other media resources! Technically this means
you can set `bellSound` to `desktopWallpaper`, but... we'll pretend that
makes sense.

I reworked the viewmodel to be a little more sensible. It no longer
requires somebody else to check that its files exist. The settings UI
now also displays `File not found` in the _preview_ for the bell if it
is a single file which failed validation!

(cherry picked from commit 4272151adc)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeF9qU
Service-Version: 1.24
2025-09-05 14:42:06 -05:00
Dustin L. Howett
eb206177a4 sb: add appId to the StoreBroker blobs (new AERO requirement) (#19290)
> _I am altering the deal. Pray I do not alter it further._
> -the AERO team, maybe

(cherry picked from commit bd14f69080)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgeF9fQ
Service-Version: 1.24
2025-09-05 14:42:05 -05:00
Dustin L. Howett
ad48162f02 Reflect inbox changes from RS (#19258)
Reflect inbox changes to `onecore/windows/core/console/open`.

* eed3a6fa5 Merged PR 13076689: Update managed TAEF tests that exist in
GE branches to use the new publishing locations of TAEF's managed
reference binaries.
* 718d7d02d Merged PR 12483430: build console* with clang

Somebody internal is trying to build the console with Clang (which is
cool).

---------

Co-authored-by: Dragos Sambotin <dragoss@microsoft.com>
Co-authored-by: Phil Deets <pdeets@microsoft.com>
2025-08-25 19:14:25 +00:00
Dustin L. Howett
6771470c8b PDPs: Preview->Stable, new Preview notes (#19272) 2025-08-25 17:58:06 +00:00
Dustin L. Howett
21cfbf170c Move to CppWinRT 2.0.250303.1 (#19268)
Interesting changes in this update:
- better support for `REFIID,IUnknown**` in `capture`
- `LOAD_LIBRARY_SEARCH_DEFAULT_DIRS` for all SxS DLL loading
- `get_self` reading from classic COM interfaces (rather than WinRT
  ones)
- better incremental builds by ignoring stale winmd files (see
  microsoft/cppwinrt#1404)
- some ability to mix c++17 and c++20 static libraries
- better codegen for `consume` methods

This version of C++/WinRT is better about propagating `protected`
fields from the metadata into the C++ projections. This required
us to switch to the `I...Protected` interfaces for some things
we are _technically_ not allowed access to. We also had some
`overridable` (protected!) members of our own that needed undec-
oration.
2025-08-25 12:46:47 -05:00
Dustin L. Howett
4f391c5e42 build: remove the forced vpack submission; let us do it on our own time (#19271) 2025-08-25 19:44:11 +02:00
Carlos Zamora
d61ad2d9cd Fix unfocused appearance UI in settings (#19263)
The unfocused appearance section in the settings UI looks a little off.
Specifically, the header was too large (larger than the breadcrumbs!)
and the button was weirdly aligned.

This PR reduces the size of the header and creates a style in
CommonResources that manages it. This is the only place it's used, for
now. A vertical alignment was added to the "create appearance" and
"delete appearance" buttons to make them look better. The top margin of
the "Text" header was also removed so that there isn't an awkward gap in
the unfocused appearance section (the 32 that was there was moved to the
bottom of the control preview so that that area remains unaffected.)

Follow-up from #19001
2025-08-25 10:32:49 -07:00
Carlos Zamora
7578209be5 Add telemetry for settings UI traffic (#19156)
## Summary of the Pull Request
Adds a telemetry provider to the Terminal.Settings.Editor project as
well as new telemetry events to track traffic through the settings UI.
Specifically, the following events were added:
- `NavigatedToPage`: Event emitted when the user navigates to a page in
the settings UI
- Has a `PageId` parameter that includes the identifier of the page that
was navigated to
- (conditionally added when PageId = `page.editColorScheme`)
`SchemeName` parameter tracks the name of the color scheme that's being
edited
   - conditionally added when PageId = `page.extensions`:
- `ExtensionPackageCount`: The number of extension packages displayed
- `ProfilesModifiedCount`: The number of profiles modified by enabled
extensions
- `ProfilesAddedCount`: The number of profiles added by enabled
extensions
- `ColorSchemesAddedCount`: The number of color schemes added by enabled
extensions
   - conditionally added when PageId = `page.extensions.extensionView`:
- `FragmentSource`: The source of the fragment included in this
extension package
- `FragmentCount`: The number of fragments included in this extension
package
      - `Enabled`: The enabled status of the extension
- (conditionally added when PageID = `page.newTabMenu`) if the page is
representing a folder view
   - conditionally added when PageID = `page.profile.*`:
- `IsProfileDefaults`: if the modified profile is the profile.defaults
object
      - `ProfileGuid`: the guid of the profile that was navigated to
      - `ProfileSource`: the source of the profile that was navigated to
- conditionally added when PageID = `page.profile` (aka the base profile
page):
         - `Orphaned`: tracks if the profile was orphaned
         - `Hidden`: tracks if the profile is hidden
- (conditionally added when PageID = `page.profile.appearance`)
`HasBackgroundImage`: `if the profile has a background image defined`
- (conditionally added when PageID = `page.profile.appearance`)
`HasUnfocusedAppearance`: `if the profile has an unfocused appearance
defined`
- `AddNewProfile`: Event emitted when the user adds a new profile
`IsExtensionView` parameter tracks if the page is representing a view of
an extension
- Has a `Type` parameter that represents the type of the creation method
(i.e. empty profile, duplicate)
- `ResetApplicationState`: Event emitted when the user resets their
application state (via the UI)
- `ResetToDefaultSettings`: Event emitted when the user resets their
settings to their default value (via the UI)
- `OpenJson`: Event emitted when the user clicks the Open JSON button in
the settings UI
- Has a `SettingsTarget` parameter that represents the target settings
file (i.e. settings.json vs defaults.json)
- `CreateUnfocusedAppearance`: Event emitted when the user creates an
unfocused appearance for a profile
- `IsProfileDefaults`: if the modified profile is the profile.defaults
object
   - `ProfileGuid`: the guid of the profile that was navigated to
   - `ProfileSource`: the source of the profile that was navigated to
- `DeleteProfile`: Event emitted when the user deletes a profile
- also includes `ProfileGuid`, `ProfileSource`, `Orphaned` from the
`NavigatedToPage` section above

The page ids can be reused later as a serialized reference to the page.
We already use the one for the extensions page for the "new" badge.
2025-08-21 19:06:20 -05:00
Carlos Zamora
2c666aa292 Clear Name, Source, and Commandline from Profiles.Defaults (#19225)
The Name, Source, and Commandline profile settings should not be allowed
to be set on the Profiles.Defaults object. This just enforces that by
clearing them (as is done with Guid).

These profile settings are omitted from the settings UI's profile
defaults page.

Closes #19202
2025-08-21 19:05:27 -05:00
Dustin L. Howett
68b723c16c Remove ProfileName from the surface of CoreSettings (#19261)
You know how much I hate squirreling away information on objects we have
to pass halfway across the universe just to get back.

In this case, `StartingTitle` will always be the name of the profile. We
only used ProfileName in places where we _needed a Title_, so this makes
it much more obvious what we're doing.
2025-08-20 17:03:47 -05:00
Carlos Zamora
642a2aa41e [Conhost] Fix off-by-1 error when copying and coloring selections (#19259)
## Summary of the Pull Request
Fixes a bug where copying and coloring selected text would be off by
one. This was introduced in #18106 when selection was updated to be
stored as an exclusive range. `Selection::_RegenerateSelectionSpans()`
was updated then, but copying text and coloring selection didn't rely on
selection spans.

Copying text relies on `GetSelectionAnchors()`. This function has now
been updated to increment the bottom-right point of the selection. This
way, `GetTextSpans()` operates on the expected _exclusive_ range.

Coloring selection relies on `TextBuffer::SearchText()`,
`TextBuffer::GetTextRects` and `GetSelectionSpans()`. Both
`Selection::ColorSelection()` were updated to use `rect` over
`inclusive_rect` to emphasize that they are exclusive ranges. Converting
between the two improves clarity and fixes the bug.

## References and Relevant Issues
Introduced in #18106 

## Validation Steps Performed
Copying text works in the following scenarios:
 single line, left-to-right and right-to-left
 multi-line, diagonal directions
 block selection

Coloring text works in the following scenarios:
ctrl+# --> color instance
ctrl+shift+# --> color all instances

Closes #19053
2025-08-20 10:43:26 -07:00
Carlos Zamora
7055b99acc Fix names and types of a few telemetry events (#19257)
Fixes a few issues with some telemetry events:
- The macro is organized as such: `TraceLoggingX(value, argName,
[argDescription])`. A few args had a description set on the spot where
the name should be. I added a name for a few of these.
- `TraceLoggingBool` --> `TraceLoggingInt32` for `themeChoice` (we
shouldn't be casting the evaluated int as a bool; it loses some of the
data we care about)
- improves the description for `themeChoice` to include information
about the legacy values

Checked through all our telemetry events and all of the args have a
proper name set. We tend to use `TraceLoggingValue` too which
automatically figures out the type that's being used, so that's also
handled.
2025-08-19 19:13:58 +00:00
Dustin L. Howett
837e86c18c Bust TerminalSettingsCache down to a plain old C++ class (#19254) 2025-08-19 09:35:27 -05:00
Leonard Hecker
e97388cb27 Fix SnapOnOutput not always snapping (#19247)
`IsOn` is the blinker on/off state, which `IsVisible`
is the actual cursor visibility on/off state.

## Validation Steps Performed
* Run bash/zsh in WSL
* (Repeatedly) Quickly scroll right and press A-Z
* Scrolls to the left 
2025-08-15 20:34:29 +00:00
Carlos Zamora
1b2aad6504 Add SSH folder to NTM for dynamic SSH profiles (#19239)
Automatically generates an "SSH" folder in the new tab menu that
contains all profiles generated by the SSH profile generator. This
folder is created if the SSH generator created some profiles and the
folder hasn't been created before. Detecting if the folder was generated
is done via the new `bool ApplicationState::SSHFolderGenerated`. The
logic is similar to `SettingsLoader::DisableDeletedProfiles()`.

Found a bug on new tab menu's folder inlining feature where we were
counting the number of raw entries to determine whether to inline or
not. Since the folder only contained the match profiles entry, this bug
made it so that the profile entries would always be inlined. The fix was
very simple: count the number of _resolved_ entries instead of the raw
entries. This can be pulled into its own PR and serviced, if desired.

## References and Relevant Issues
#18814 
#14042 

## Validation Steps Performed
 Existing users get an SSH folder if profiles were generated

## PR Checklist
Closes #19043
2025-08-13 15:43:27 -07:00
Dustin L. Howett
abaa9488d9 Switch to the @Local view on TerminalDependencies (#19243)
Due to an unexpected decision on behalf of the Azure Artifacts folks,
the default view for a feed with upstream sources reports all packages,
even if they are not actually populated into the feed.

This results in (uncontrolled) 401 errors whenever a new package appears
upstream, because the feed tells our users and our build system that it
is available, but fails when the download actually begins because it is
not allowed to "write" the upstream version to the feed.
2025-08-13 16:27:01 +00:00
Leonard Hecker
6b19d21845 Fix a output marks performance regression (#19242)
An alternative approach for #18291. Improves perf by ~7%.
2025-08-13 18:10:18 +02:00
Dustin L. Howett
0d23624fa9 Use a new API to propagate foreground state to child processes (#19192)
Windows 11 uses some additional signals to determine what the user cares
about and give it a bit of a QoS boost. One of those signals is whether
it is associated with a window that is in the foreground or which has
input focus.

Association today takes two forms:
- Process has a window which is in the foreground or which has input
  focus
- Process has a *parent* that meets the above criterion.

Console applications that are spawned "inside" terminal by handoff do
not fall into either bucket. They don't have a window. Their parent is
`dllhost` or `explorer`, who is definitely not in focus.

We are piloting a new API that allows us to associate those processes
with Terminal's window.

When Terminal is in focus, it will attach every process from the active
tab to its QoS group. This means that whatever is running in that tab
is put into the "foreground" bucket, and everything running in other
background tabs is not.

When Terminal is out of focus, it attaches every process to its QoS
group. This ensures that they all go into the "background" bucket
together, following the window.
2025-08-13 00:09:50 +00:00
Leonard Hecker
8a05910e3c Implement snap-on-input/output for conhost (#17453)
This extends our current behavior in conhost to scroll to the
cursor position when typing. This is especially relevant in WSL,
where this won't happen at all, otherwise.

Closes #18073
Closes MSFT:49027268
2025-08-12 12:22:33 -07:00
Windows Console Service Bot
a0f7b332fa Localization Updates - multi line paste - 08/09/2025 03:04:06 (#19230) 2025-08-12 10:39:45 -05:00
Leonard Hecker
0c064905b3 Revert "ConPTY: Emit DSR CPR on resize (#19089)" (#19237)
This reverts commit c55aca508b
because it is unaware of the VT state and may inject the DSR CPR
while e.g. a DCS is going on.

Reopens #18725
2025-08-11 17:48:35 +00:00
Leonard Hecker
e2f3e53064 Don't trim bracketed pastes (#19067)
* Moves clipboard writing from `ControlCore` to `TerminalPage`.
  This requires adding a bunch of event types and logic.
  This is technically not needed anymore after changing the
  direction of this PR, but I kept it because it's better.
* Add a `WarnAboutMultiLinePaste` enum to differentiate between
  "paste without warning always/never/if-bracketed-paste-disabled".

Closes #13014
Closes https://github.com/microsoft/edit/issues/279

## Validation Steps Performed
* Launch Microsoft Edit and copy text with a trailing newline
* Paste it with Ctrl+Shift+V
* It's pasted as it was copied 
* Changing the setting to "always" always warns 

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
2025-08-08 20:53:42 +00:00
Leonard Hecker
5b41f14660 Disable WIN32IM on shutdown (#19229)
Closes #19153

## Validation Steps Performed
I tried reproducing the issue on Windows 10 and couldn't.
I'm not sure what I did wrong. But I tested it under a
debugger with VtPipeTerm and it wrote the sequences to stdout.
2025-08-08 19:48:52 +02:00
Windows Console Service Bot
514da89b63 Localization Updates - main - 08/06/2025 03:04:16 (#19219) 2025-08-07 07:30:29 -05:00
Dustin L. Howett
6c2f38c732 Profiles Editor: make sure we notify the IconPath property (#19223)
There were instances where changing the icon or resetting it did not
result in the text box changing.

Regressed in #19143
2025-08-06 16:09:47 -07:00
Leonard Hecker
6fb70eb510 Throttle incoming handoffs so WinUI can layout them (#19220)
As explained in the diff itself.

Closes #13136

## Validation Steps Performed
* In conhost, with `wtd` not running, execute:
  ```pwsh
1..10 | % { wt -w 1 nt --title=$_ pwsh -NoExit -Command "Get-Date
-Format HH:mm:ss.ffff" }
  ```
* Every tab is up and running immediately 
2025-08-06 18:55:29 +00:00
Leonard Hecker
c55aca508b ConPTY: Emit DSR CPR on resize (#19089)
This will help terminals with a reflow behavior unlike
the one implemented in ConPTY, such as VS Code.

Closes #18725

## Validation Steps Performed
* Tested under a debugger 
* Use PowerShell 5 and reflow lines in the ConPTY buffer until they're
  pushed outside the top viewport. Then type something in the prompt.
  Cursor is in a consistent position, even if slightly off. 
2025-08-06 13:06:44 +02:00
Dustin L. Howett
666a75bc70 Fix hot reload for icon, bell, close on exit; regressed in #16172 (#19217)
In #16172, we removed the propagation of the profile down into the
Terminal Pane Content.

It was holding on to the profile from the _old_ settings model (😱).

This also broke background image hot reload.
2025-08-05 21:18:43 +00:00
Dustin L. Howett
c0f9a198c6 Rewrite media resource handling (relative path icons, web URLs) (#19143)
This pull request broadly rewrites how we handle all media resources in
the Terminal settings model.

## What is a media resource?

A media resource is any JSON property that refers to a file on disk,
including:

- `icon` on profile
- `backgroundImage` on profile (appearance)
- `pixelShaderPath` and `pixelShaderImagePath` on profile (appearance)
- `icon` on command and the new tab menu entries

The last two bullet points were newly discovered during the course of
this work.

## Description of Changes

In every place the settings model used to store a string for a media
path, it now stores an `IMediaResource`.

A media resource must be _resolved_ before it's used. When resolved, it
can report whether it is `Ok` (found, valid) and what the final
normalized path was.

This allows the settings model to apply some new behaviors.

One of those new behaviors is resolving media paths _relative to the
JSON file that referred to them._ This means fragments and user settings
can now contain _local_ images, pixel shaders and more and refer to them
by filename.

Relative path support requires us to track the path from which every
media resource "container" was read[^2]. For "big" objects like Profile,
we track it directly in the object and for each layer. This means that
fragments **updating** a profile pass their relative base path into the
mix. For some of the entries such as those in `newTabMenu`, we just wing
it (#19191). For everything that is recursively owned by a parent that
has a path (say each Command inside an ActionMap), we pass it in from
the parent during media resolution.

During resolution, we now track _exactly which layer_ an icon,
background image, or pixel shader path came from and read the "base
path" from only that layer. The base path is not inherited.

Another new behavior is in the handling of web and other URLs.

Canonical and a few other WSL distributors had to resort to web URLs for
icons because we did not support loading them from the package. Julia
tried to use `ms-appx://JuliaPackageNameHere/path/to/icon` for the same
reason. Neither was intended, and of the two the second _should_ have
worked but never could[^1].

For both `http(s?)` URLs and `ms-appx://` URLs which specify a package
name, we now strip everything except the filename. As an example...

If my fragment specifies `https://example.net/assets/foo.ico`, and my
fragment was loaded from `C:\Fragments`, Terminal will look *only* at
`C:\Fragments\foo.ico`.

This works today for Julia (they put their icon in the fragment folder
hoping that one day we would support this.) It will require some work
from existing WSL distributors.

I'm told that this is similar to how XML schema documents work.

Now, icons are special. They support _Emoji_ and _Segoe Icons_. This PR
adds an early pass to avoid resolving anything that looks like an
emoji.

This PR intentionally expands the heuristic definition of an emoji. It
used to only cover 1-2 code unit emoji, which prevented the use of any
emoji more complicated than "man in business suite levitating."

An icon path will now be considered an emoji or symbol icon if it is
composed of a single grapheme cluster (as measured by ICU.)

This is not perfect, as it errs on the side of allowing too many
things... but each of those things is technically a single grapheme
cluster and is a perfectly legal FontIcon ;)

Profile icons are _even more special_ than icons. They have an
additional fallback behavior which we had to preserve. When a profile
icon fails validation, or is expressly set to `null`, we fall back to
the EXE specified in the command line.

Because we do this fallback during resolution, _and the icon may be
inherited by any higher profile,_ we can only resolve it against the
commandline at the same level as the failed or nulled icon.

Therefore, if you specify `icon: null` in your `defaults` profile, it
will only ever resolve to `cmd.exe` for any profile that inherits it
(unless you change `defaults.commandline`).

This change expands support for the magic keywords `desktopWallpaper`
and `none` to all media paths (yes, even `pixelShaderPath`... but also,
`pixelShaderImagePath`!) It also expands support for _environment
variables_ to all of those places. Yes, we had like forty different
handlers for different types of string path. They are now uniform.

## Resource Validation

Media resources which are not found are "rejected". If a rejected
resource lives in _user_ settings, we will generate a warning and
display it.

In the future, we could detect this in the Settings UI and display a
warning inline.

## Surprises

I learned that `Windows.Foundation.Uri` parses file paths into `file://`
URIs, but does not offer you a way to get the original file path back
out. If you pass `C:\hello world`, _`Uri.Path`_ will return
`/C:/hello%20world`. I kid you not.

As a workaround, we bail out of URL handling if the `:` is too close to
the start (indicating an absolute file path).

## Testing

I added a narow test hook in the media resource resolver, which is
removed completely by link-time code generation. It is a real joy.

The test cases are all new and hopefully comprehensive.

Closes #19075
Closes #16295
Closes #10359 (except it doesn't support fonts)
Supersedes #16949 somewhat (`WT_SETTINGS_DIR`)
Refs #18679

Refs #19215 (future work)
Refs #19201 (future work)
Refs #19191 (future work)

[^1]: Handling a `ms-appx` path requires us to _add their package to our
dependency graph_ for the entire duration during which the resource will
be used. For us, that could be any time (like opening the command
palette for the first time!)

[^2]: We don't bother tracking where the defaults came from, because we
control everything about them.
2025-08-05 20:47:50 +00:00
Dustin L. Howett
a258d7d3df Fix title being stuck after toggling "showTerminalTitleInTitlebar" (#19212)
We already have the logic to fall back when the setting is disabled, we
just _also_ had a terrible eventing architecture and never used the
`Title` getter that was smart enough to figure it out.

This pull request somewhat disentangles the mess that is title handling.
For example, TerminalPage had a facility to get its current title! It
was never used. Once we started using it, it wouldn't work for the
Settings page... because it only read the title out of the active
control.

Closes #12871
Closes #19139 (superseded)

---------

Co-authored-by: techypanda <jonathan_wright@hotmail.com>
2025-08-04 17:46:55 -07:00
Leonard Hecker
0a6394270e Fix WSL PATH corruption & potential use-after-free (#19211)
Closes #19152

## Validation Steps Performed
* Set `PATH` on a linux profile
* `PATH` isn't messed up inside WSL 
2025-08-04 22:42:26 +00:00
MQY
88ab154f22 Prevent cursor repositioning during mouse selection (#19182)
- Modify the cursor repositioning logic to check if a selection is in
progress
- Only reposition the cursor when the mouse is used for positioning, not
during selection operations

Closes #19181
2025-08-04 21:09:08 +00:00
Leonard Hecker
dbf740cf2c Extend lead/trail edge support for throttled_func (#19210)
You can now create throttled functions which trigger both on the leading
and trailing edge. This was then also ported to `ThrottledFunc` for
`DispatcherQueue`s and used for title/taskbar updates.

Closes #19188

## Validation Steps Performed
* In CMD run:
  ```batch
  FOR /L %N IN () DO @echo %time%
  ```
* Doesn't hang the UI 
2025-08-04 20:25:37 +02:00
Leonard Hecker
9ab7cf312f Remove dependency on .NET 6 and Framework 4.8 (#19199)
* .NET 6 is EOL
* Framework 4.7.2 comes by default with VS, but 4.8 doesn't
2025-08-04 13:09:22 -05:00
249 changed files with 4426 additions and 1742 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"XamlStyler.Console": {
"version": "3.2311.2",
"version": "3.2501.8",
"commands": [
"xstyler"
]

View File

@@ -174,6 +174,7 @@ tokeninfo
tolower
toupper
TRACKMOUSEEVENT
ubrk
UChar
UFIELD
ULARGE

View File

@@ -34,6 +34,7 @@ issecret
libucrt
libucrtd
LOCKFILE
LTCG
Lxss
makepri
microsoft

View File

@@ -28,6 +28,7 @@ jerrysh
Kaiyu
leonardder
lhecker
Lovecraft
masserano
menger
migrie

View File

@@ -822,6 +822,7 @@ INPUTSCOPE
INSERTMODE
INTERACTIVITYBASE
INTERCEPTCOPYPASTE
internalevent
INTERNALNAME
intsafe
INVALIDARG
@@ -960,6 +961,7 @@ lstatus
lstrcmp
lstrcmpi
LTEXT
lto
ltsc
LUID
luma
@@ -1708,6 +1710,7 @@ titlebars
TITLEISLINKNAME
TLDP
TLEN
Tlgg
TMAE
TMPF
tmultiple
@@ -1970,6 +1973,7 @@ WRITECONSOLEOUTPUT
WRITECONSOLEOUTPUTSTRING
wrkstr
WRL
wrl
wrp
WRunoff
WSLENV

View File

@@ -17,7 +17,6 @@
"Microsoft.VisualStudio.Component.AppInsights.Tools",
"Microsoft.Net.Component.4.8.TargetingPack",
"Microsoft.VisualStudio.Component.DiagnosticTools",
"Microsoft.NetCore.Component.Runtime.6.0",
"Microsoft.VisualStudio.Component.ClassDesigner",
"Microsoft.VisualStudio.Component.GraphDocument",
"Microsoft.VisualStudio.Component.CodeMap",

View File

@@ -4,7 +4,7 @@
<clear />
<!-- Dependencies that we can turn on to force override for testing purposes before uploading. -->
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies%40Local/nuget/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />

View File

@@ -56,9 +56,9 @@ This is an open source project and we welcome community participation. To partic
<ReleaseNotes _locID="App_ReleaseNotes">
<!-- _locComment_text="{MaxLength=1500} {Locked=__VERSION_NUMBER__}{Locked=wt.exe} App Release Note" -->Version __VERSION_NUMBER__
- We've added dozens of settings to the UI that once only existed in the JSON file, including a new page for customizing the layout of your New Tab menu!
- We have rearchitected window management to improve reliability; please file any bugs you encounter with the wt.exe alias
- Profiles now show an icon if they've been hidden or refer to programs which were uninstalled.
- A whole new Extensions page that shows what has been installed into your Terminal
- Command Palette now shows up in your native language as well as English
- New VT features such as synchronized rendering, new color schemes, configuration for quick mouse actions like zooming, and more
Please see our GitHub releases page for additional details.
</ReleaseNotes>

View File

@@ -20,6 +20,7 @@
"DisableAutoPackageNameFormatting": false
},
"appSubmission": {
"appId": "9N8G5RFZ9XK3",
"productId": "00014050269303149694",
"targetPublishMode": "NotSet",
"targetPublishDate": null,

View File

@@ -54,14 +54,11 @@ This is an open source project and we welcome community participation. To partic
<!-- _locComment_text="{MaxLength=255} App DevStudio" -->
</DevStudio>
<ReleaseNotes _locID="App_ReleaseNotes">
<!-- _locComment_text="{MaxLength=1500} {Locked=__VERSION_NUMBER__} App Release Note" -->Version __VERSION_NUMBER__
<!-- _locComment_text="{MaxLength=1500} {Locked=__VERSION_NUMBER__}{Locked=wt.exe} App Release Note" -->Version __VERSION_NUMBER__
- We've rewritten how console applications are hosted inside Terminal! Please report any bugs you encounter.
- Terminal now supports Sixels!
- You can now open a docked panel containing snippets of commands you have saved to use later
- Command Prompt users on the latest Windows 11 release may see a "quick tip" icon that suggests installable software from WinGet
- Selected text will now be much more visible (and customizable!)
- A number of reliabilty bugs, convenience issues and annoyances have been fixed.
- We've added dozens of settings to the UI that once only existed in the JSON file, including a new page for customizing the layout of your New Tab menu!
- We have rearchitected window management to improve reliability; please file any bugs you encounter with the wt.exe alias
- Profiles now show an icon if they've been hidden or refer to programs which were uninstalled.
Please see our GitHub releases page for additional details.
</ReleaseNotes>

View File

@@ -20,6 +20,7 @@
"DisableAutoPackageNameFormatting": false
},
"appSubmission": {
"appId": "9N0DX20HK701",
"productId": "00013926773940052066",
"targetPublishMode": "NotSet",
"targetPublishDate": null,

View File

@@ -4,7 +4,7 @@
"collection": "microsoft",
"project": "OS",
"repo": "os.2020",
"name": "official/rs_we_adept_e4d2",
"name": "official/ge_current_directwinpd_deep",
"workitem": "38106206",
"CheckinFiles": [
{

View File

@@ -2,7 +2,7 @@
{
"MatchedPath": [
"WpfTerminalControl/net472/Microsoft.Terminal.Wpf.dll",
"WpfTerminalControl/net6.0-windows/Microsoft.Terminal.Wpf.dll"
"WpfTerminalControl/net8.0-windows/Microsoft.Terminal.Wpf.dll"
],
"SigningInfo": {
"Operations": [

View File

@@ -53,8 +53,12 @@ parameters:
displayName: "Publish Symbols to MSDL"
type: boolean
default: true
- name: createVpack
displayName: "Create a VPack for Windows"
type: boolean
default: false
- name: publishVpackToWindows
displayName: "Publish VPack to Windows"
displayName: "Publish above VPack to Windows"
type: boolean
default: false
@@ -89,6 +93,7 @@ extends:
clientId: $(SigningOriginalClientId)
terminalInternalPackageVersion: ${{ parameters.terminalInternalPackageVersion }}
publishSymbolsToPublic: ${{ parameters.publishSymbolsToPublic }}
createVpack: ${{ parameters.createVpack }}
publishVpackToWindows: ${{ parameters.publishVpackToWindows }}
symbolPublishingSubscription: $(SymbolPublishingServiceConnection)
symbolPublishingProject: $(SymbolPublishingProject)

View File

@@ -86,7 +86,7 @@ jobs:
$MachineToken = $env:SYSTEM_ACCESSTOKEN | ConvertTo-SecureString -AsPlainText -Force
$Credential = [PSCredential]::new("ONEBRANCH_TOKEN", $MachineToken)
$MachineToken = $null
$Feed = "https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies/nuget/v3/index.json"
$Feed = "https://pkgs.dev.azure.com/shine-oss/terminal/_packaging/TerminalDependencies%40Local/nuget/v3/index.json"
Register-PSResourceRepository -Name "PSGalleryUpstream" -Uri $Feed -Trusted
Get-PSResourceRepository

View File

@@ -69,10 +69,3 @@ jobs:
artifact: $(JobOutputArtifactName)
displayName: 'Publish VPack Manifest to Drop'
- task: PkgESFCIBGit@12
displayName: 'Submit VPack Manifest to Windows'
inputs:
configPath: '$(Build.SourcesDirectory)\build\config\GitCheckin.json'
artifactsDirectory: $(XES_VPACKMANIFESTDIRECTORY)
prTimeOut: 5

View File

@@ -49,6 +49,9 @@ parameters:
- name: symbolExpiryTime
type: string
default: 36530 # This is the default from PublishSymbols@2
- name: createVpack
type: boolean
default: false
- name: publishVpackToWindows
type: boolean
default: false
@@ -192,8 +195,8 @@ extends:
ob_outputDirectory: $(JobOutputDirectory)
ob_artifactBaseName: $(JobOutputArtifactName)
### This job is also in charge of submitting the vpack to Windows if it's enabled
ob_createvpack_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }}
ob_updateOSManifest_enabled: ${{ and(parameters.buildTerminal, parameters.publishVpackToWindows) }}
ob_createvpack_enabled: ${{ and(parameters.buildTerminal, parameters.createVpack) }}
ob_updateOSManifest_enabled: ${{ and(parameters.buildTerminal, parameters.createVpack, parameters.publishVpackToWindows) }}
### If enabled above, these options are in play.
ob_createvpack_packagename: 'WindowsTerminal.app'
ob_createvpack_owneralias: 'conhost@microsoft.com'
@@ -229,7 +232,7 @@ extends:
New-Item "$(JobOutputDirectory)/vpack" -Type Directory
displayName: Make sure the vpack directory exists
- ${{ if parameters.publishVpackToWindows }}:
- ${{ if parameters.createVpack }}:
- pwsh: |-
Copy-Item -Verbose -Path "$(MsixBundlePath)" -Destination (Join-Path "$(JobOutputDirectory)/vpack" 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle')
displayName: Stage msixbundle for vpack

View File

@@ -4,8 +4,8 @@
<!-- Native packages -->
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
<package id="Microsoft.Taef" version="10.93.240607003" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.7.230706001" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Internal.Windows.Terminal.ThemeHelpers" version="0.8.250811004" targetFramework="native" />
<package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="2.3.2262" targetFramework="native" developmentDependency="true" />
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />

View File

@@ -2302,8 +2302,15 @@
"additionalProperties": false,
"properties": {
"id": {
"description": "The ID of the command this keybinding should execute.",
"type": "string"
"description": "The ID of the command this keybinding should execute (or null to disable a default).",
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"keys": {
"description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key",

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
</packages>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.230207.1" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.8.4" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.1661.34" targetFramework="native" />
</packages>

View File

@@ -942,12 +942,12 @@ void ROW::_resizeChars(uint16_t colEndDirty, uint16_t chBegDirty, size_t chEndDi
}
}
til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() noexcept
RowAttributes& ROW::Attributes() noexcept
{
return _attr;
}
const til::small_rle<TextAttribute, uint16_t, 1>& ROW::Attributes() const noexcept
const RowAttributes& ROW::Attributes() const noexcept
{
return _attr;
}

View File

@@ -14,6 +14,11 @@
class ROW;
class TextBuffer;
// Because MarkKind::Output gets set only on the actually written text,
// most rows will end up having at least 2 runs: The start of the line
// with MarkKind::Output and the rest of the line with MarkKind::None.
using RowAttributes = til::small_rle<TextAttribute, uint16_t, 2>;
enum class DelimiterClass
{
ControlChar,
@@ -149,8 +154,8 @@ public:
void ReplaceText(RowWriteState& state);
void CopyTextFrom(RowCopyTextFromState& state);
til::small_rle<TextAttribute, uint16_t, 1>& Attributes() noexcept;
const til::small_rle<TextAttribute, uint16_t, 1>& Attributes() const noexcept;
RowAttributes& Attributes() noexcept;
const RowAttributes& Attributes() const noexcept;
TextAttribute GetAttrByColumn(til::CoordType column) const;
std::vector<uint16_t> GetHyperlinks() const;
ImageSlice* SetImageSlice(ImageSlice::Pointer imageSlice) noexcept;
@@ -298,7 +303,7 @@ private:
std::span<uint16_t> _charOffsets;
// _attr is a run-length-encoded vector of TextAttribute with a decompressed
// length equal to _columnCount (= 1 TextAttribute per column).
til::small_rle<TextAttribute, uint16_t, 1> _attr;
RowAttributes _attr;
// The width of the row in visual columns.
uint16_t _columnCount = 0;
// Stores double-width/height (DECSWL/DECDWL/DECDHL) attributes.

View File

@@ -24,6 +24,7 @@
<PropertyGroup Label="NuGet Dependencies">
<!-- TerminalCppWinrt is intentionally not set -->
<TerminalMUX>true</TerminalMUX>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

View File

@@ -548,7 +548,9 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
{
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting());
const auto copyFormatting = realArgs.CopyFormatting();
const auto format = copyFormatting ? copyFormatting.Value() : _settings.GlobalSettings().CopyFormatting();
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), format);
args.Handled(handled);
}
}

View File

@@ -135,12 +135,19 @@ namespace winrt::TerminalApp::implementation
_isElevated = ::Microsoft::Console::Utils::IsRunningElevated();
_canDragDrop = ::Microsoft::Console::Utils::CanUwpDragDrop();
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->ReloadSettings();
}
});
_reloadSettings = std::make_shared<ThrottledFunc<>>(
DispatcherQueue::GetForCurrentThread(),
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 100 },
.debounce = true,
.trailing = true,
},
[weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->ReloadSettings();
}
});
_languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
_reloadSettings->Run();

View File

@@ -64,7 +64,7 @@ namespace winrt::TerminalApp::implementation
bool _hasSettingsStartupActions{ false };
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
std::shared_ptr<ThrottledFunc<>> _reloadSettings;
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};

View File

@@ -61,7 +61,7 @@ namespace winrt::TerminalApp::implementation
winrt::hstring Icon()
{
return _Command.IconPath();
return _Command.Icon().Resolved();
}
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);

View File

@@ -19,41 +19,6 @@ DEFINE_PROPERTYKEY(PKEY_AppUserModel_DestListLogoUri, 0x9F4C2855, 0x9F79, 0x4B39
{ 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 29 \
}
// Function Description:
// - This function guesses whether a string is a file path.
static constexpr bool _isProbableFilePath(std::wstring_view path)
{
// "C:X", "C:\X", "\\?", "\\."
// _this function rejects \??\ as a path_
if (path.size() >= 3)
{
const auto firstColon{ path.find(L':') };
if (firstColon == 1)
{
return true;
}
const auto prefix{ path.substr(0, 2) };
return prefix == LR"(//)" || prefix == LR"(\\)";
}
return false;
}
// Function Description:
// - DestListLogoUri cannot take paths that are separated by / unless they're URLs.
// This function uses std::filesystem to normalize strings that appear to be file
// paths to have the "correct" slash direction.
static std::wstring _normalizeIconPath(std::wstring_view path)
{
const auto fullPath{ wil::ExpandEnvironmentStringsW<std::wstring>(path.data()) };
if (_isProbableFilePath(fullPath))
{
std::filesystem::path asPath{ fullPath };
return asPath.make_preferred().wstring();
}
return std::wstring{ fullPath };
}
// Method Description:
// - Updates the items of the Jumplist based on the given settings.
// Arguments:
@@ -124,7 +89,7 @@ void Jumplist::_updateProfiles(IObjectCollection* jumplistItems, winrt::Windows:
auto args = fmt::format(FMT_COMPILE(L"-p {}"), to_hstring(profile.Guid()));
// Create the shell link object for the profile
const auto normalizedIconPath{ _normalizeIconPath(profile.Icon()) };
const auto normalizedIconPath{ profile.Icon().Resolved() };
const auto shLink = _createShellLink(profile.Name(), normalizedIconPath, args);
THROW_IF_FAILED(jumplistItems->AddObject(shLink.get()));
}

View File

@@ -35,17 +35,23 @@ namespace winrt::TerminalApp::implementation
// (which should be the default, see:
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-trackmouseevent#remarks)
unsigned int hoverTimeoutMillis{ 400 };
LOG_IF_WIN32_BOOL_FALSE(SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hoverTimeoutMillis, 0));
const auto toolTipInterval = std::chrono::milliseconds(hoverTimeoutMillis);
if (FAILED(SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hoverTimeoutMillis, 0)))
{
hoverTimeoutMillis = 400;
}
// Create a ThrottledFunc for opening the tooltip after the hover
// timeout. If we hover another button, we should make sure to call
// Run() with the new button. Calling `_displayToolTip.Run(nullptr)`,
// which will cause us to not display a tooltip, which is used when we
// leave the control entirely.
_displayToolTip = std::make_shared<ThrottledFuncTrailing<Controls::Button>>(
_displayToolTip = std::make_shared<ThrottledFunc<Controls::Button>>(
dispatcher,
toolTipInterval,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ hoverTimeoutMillis },
.debounce = true,
.trailing = true,
},
[weakThis = get_weak()](Controls::Button button) {
// If we provide a button, then open the tooltip on that button.
// We can "dismiss" this throttled func by calling it with null,

View File

@@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> MaximizeClick;
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> CloseClick;
std::shared_ptr<ThrottledFuncTrailing<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
std::shared_ptr<ThrottledFunc<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
std::optional<CaptionButton> _lastPressedButton{ std::nullopt };
};
}

View File

@@ -1161,10 +1161,12 @@ void Pane::SetActive()
// focused, else the profile of the last control to be focused
Profile Pane::GetFocusedProfile()
{
auto lastFocused = GetActivePane();
if (const auto& terminalPane{ lastFocused->_getTerminalContent() })
if (auto lastFocused{ GetActivePane() })
{
return terminalPane.GetProfile();
if (const auto& terminalPane{ lastFocused->_getTerminalContent() })
{
return terminalPane.GetProfile();
}
}
return nullptr;
}

View File

@@ -44,10 +44,6 @@ namespace winrt::TerminalApp::implementation
{
_args = { value.begin(), value.end() };
_parseResult = _parsed.ParseArgs(_args);
if (_parseResult == 0)
{
_parsed.ValidateStartupCommands();
}
}
winrt::com_array<winrt::hstring> CommandlineArgs::Commandline()

View File

@@ -270,13 +270,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Registerkarte kopieren</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Profil mit einem ungültigen "backgroundImage" gefunden. Dieses Profil hat standardmäßig kein Hintergrundbild. Stellen Sie sicher, dass beim Festlegen eines "backgroundImage" der Wert ein gültiger Dateipfad zu einem Bild ist.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Profil mit einem ungültigen "icon" gefunden. Dieses Profil hat standardmäßig kein Symbol. Stellen Sie sicher, dass beim Festlegen eines "icon" der Wert ein gültiger Dateipfad zu einem Bild ist.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Mindestens eine in den Einstellungen angegebene Ressource (z. B. icon oder backgroundImage) wurde nicht gefunden.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Beim Analysieren Ihrer Tastenzuordnungen wurden Warnungen gefunden:</value>

View File

@@ -271,13 +271,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Duplicate tab</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Found a profile with an invalid "icon". Defaulting that profile to have no icon. Make sure that when setting an "icon", the value is a valid file path to an image.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>One or more resources (such as icon or backgroundImage) specified in your settings could not be found.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Warnings were found while parsing your keybindings:</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Duplicar pestaña</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Se encontró un perfil con un "backgroundImage" no válido. Si se predetermina que ese perfil no tiene imagen de fondo. Asegúrese de que al establecer "backgroundImage", el valor sea una ruta de acceso de archivo válida a una imagen.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Se encontró un perfil con un "icon" no válido. Estableciendo ese perfil para no tener icono. Asegúrese de que, al establecer un "icon", el valor es una ruta de acceso de archivo válida a una imagen.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>No se encontraron uno o varios recursos (como icon o backgroundImage) especificados en la configuración.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Se encontraron advertencias al analizar los enlaces de teclado:</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Dupliquer longlet</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Profil détecté avec une "backgroundImage" non valide. Par défaut, ce profil ne possède pas dimage darrière-plan. Assurez-vous que lorsque vous définissez une "backgroundImage", la valeur est un chemin daccès de fichier valide vers une image.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Profil détecté avec une "icon" non valide. Par défaut, ce profil ne possède pas dicône. Assurez-vous que lorsque vous définissez une "icon", la valeur est un chemin daccès de fichier valide vers une image.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Une ou plusieurs ressources (telles que icon ou backgroundImage) spécifiées dans vos paramètres sont introuvables.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Des avertissements ont été détectés lors de lanalyse de vos combinaisons de touches :</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Duplica scheda</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>È stato trovato un profilo con un "backgroundImage" non valido. Impostazione predefinita per il profilo non è disponibile un'immagine di sfondo. Accertarsi che quando si imposta un "backgroundImage", il valore è un percorso di file valido per un'immagine.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Trovato un profilo con "icon" non valida. Impostare il profilo senza icon. Assicurarsi che, quando si imposta una "icon", il valore abbia un percorso file valido per un'immagine.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Impossibile trovare una o più risorse, ad esempio icon o backgroundImage, specificate nelle impostazioni.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Sono stati trovati avvisi durante l'analisi delle associazioni di tasti:</value>

View File

@@ -268,13 +268,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>タブを複製する</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>無効な "backgroundImage" を持つプロファイルが見つかりました。既定では、そのプロファイルに背景画像はありません。"backgroundImage" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>無効な "icon" を持つプロファイルが見つかりました。既定では、そのプロファイルにアイコンはありません。"icon" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>設定で指定された 1 つ以上のリソース (icon や backgroundImage など) が見つかりませんでした。</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>キー バインドの解析中に警告が検出されました:</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>탭 복제</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>잘못된 "backgroundImage" 프로필을 찾았습니다. 해당 프로필을 배경 이미지가 없는 기본값으로 설정합니다. "backgroundImage"설정할 때 값이 이미지에 대한 유효한 파일 경로인지 확인합니다.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>잘못된 "icon"이 있는 프로필을 발견했습니다. 해당 프로필에 아이콘이 없도록 기본값을 설정합니다. "icon" 설정 시 값이 이미지에 대한 올바른 파일 경로인지 확인합니다.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>설정에 지정된 하나 이상의 리소스(예: icon 또는 backgroundImage)찾을 수 없습니다.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>키 바인딩 구문을 분석하는 동안 경고를 발견했습니다.</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Duplicar guia</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Foi encontrado um perfil com um "backgroundImage" inválido. O perfil deve ser o padrão para que não haja nenhuma imagem de tela de fundo. Certifique-se de que, ao definir um "backgroundImage", o valor é um caminho de arquivo válido para uma imagem.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Foi encontrado um perfil com um "icon" inválido. Padronize esse perfil para ele não ter ícone. Certifique-se de que, ao definir um "icon", o valor seja um caminho de arquivo válido para uma imagem.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Um ou mais recursos (como icon ou backgroundImage) especificados em suas configurações não foram encontrados.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Os avisos foram encontrados durante a análise das suas ligações de teclas:</value>

View File

@@ -271,13 +271,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Ďϋφľіčάтέ τàв !!! </value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Ωňę бггě яėŝǿüґсėş (šυćн âş icon ōя backgroundImage) ŝрěçìƒįєð ίπ ýőūŕ ŝėтťīлġš ċòŭĺð йöŧ вέ ƒòúпď. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>

View File

@@ -271,13 +271,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Ďϋφľіčάтέ τàв !!! </value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Ωňę бггě яėŝǿüґсėş (šυćн âş icon ōя backgroundImage) ŝрěçìƒįєð ίπ ýőūŕ ŝėтťīлġš ċòŭĺð йöŧ вέ ƒòúпď. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>

View File

@@ -271,13 +271,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Ďϋφľіčάтέ τàв !!! </value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Ωňę бггě яėŝǿüґсėş (šυćн âş icon ōя backgroundImage) ŝрěçìƒįєð ίπ ýőūŕ ŝėтťīлġš ċòŭĺð йöŧ вέ ƒòúпď. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>Дублировать вкладку</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Найден профиль с недопустимым объектом "backgroundImage". По умолчанию для этого профиля не используется фоновое изображение. Убедитесь, что значение, заданное для "backgroundImage", является допустимым путем файла к изображению.</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>Найден профиль с недопустимым объектом "icon". По умолчанию для этого профиля не используется значок. Убедитесь, что значение, заданное для "icon", является допустимым путем файла к изображению.</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>Не удалось найти один или несколько ресурсов (icon или backgroundImage), указанных в параметрах.</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>При анализе настраиваемых сочетаний клавиш найдены предупреждения:</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>复制标签页</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>找到一个具有无效 "backgroundImage" 的配置文件。将该配置文件设置为默认设置为不包含背景图像。请确保在设置 "backgroundImage" 时,该值是指向图像的有效文件路径。</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>找到一个带有无效 "icon" 的配置文件。将该配置文件默认为无图标。确保设置 "icon" 时,该值是图像的有效文件路径。</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>找不到设置中指定的一个或多个资源 (,如 icon 或 backgroundImage)。</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>分析键绑定时发现警告:</value>

View File

@@ -267,13 +267,9 @@
<data name="DuplicateTabText" xml:space="preserve">
<value>複製索引標籤</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>找到具有無效 "backgroundImage" 的設定檔。將該設定檔的預設值設為沒有背景影像。請確定設定 "backgroundImage" 時,該值是影像的有效檔案路徑。</value>
<comment>{Locked="\"backgroundImage\""}</comment>
</data>
<data name="InvalidIcon" xml:space="preserve">
<value>已發現具有無效 "icon" 的設定檔。將該設定檔預設為無圖示。設定 "icon" 時,請確認值是有效的影像檔案路徑。</value>
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
<data name="InvalidMediaResource" xml:space="preserve">
<value>找不到一或多個資源 (,例如在您的設定中指定的 icon 或 backgroundImage)。</value>
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
</data>
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
<value>剖析金鑰繫結時發現警告:</value>

View File

@@ -1035,27 +1035,49 @@ namespace winrt::TerminalApp::implementation
void Tab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content)
{
auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher();
auto dispatcher = DispatcherQueue::GetForCurrentThread();
ContentEventTokens events{};
auto throttledTitleChanged = std::make_shared<ThrottledFunc<>>(
dispatcher,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 200 },
.leading = true,
.trailing = true,
},
[weakThis]() {
if (const auto tab = weakThis.get())
{
tab->UpdateTitle();
}
});
events.TitleChanged = content.TitleChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
// The lambda lives in the `std::function`-style container owned by `control`. That is, when the
// `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to
// copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines.
// See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
const auto weakThisCopy = weakThis;
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThisCopy.get() })
[func = std::move(throttledTitleChanged)](auto&&, auto&&) {
func->Run();
});
auto throttledTaskbarProgressChanged = std::make_shared<ThrottledFunc<>>(
dispatcher,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 200 },
.leading = true,
.trailing = true,
},
[weakThis]() {
if (const auto tab = weakThis.get())
{
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->UpdateTitle();
tab->_UpdateProgressState();
}
});
events.TaskbarProgressChanged = content.TaskbarProgressChanged(
winrt::auto_revoke,
[func = std::move(throttledTaskbarProgressChanged)](auto&&, auto&&) {
func->Run();
});
events.TabColorChanged = content.TabColorChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
@@ -1071,18 +1093,6 @@ namespace winrt::TerminalApp::implementation
}
});
events.TaskbarProgressChanged = content.TaskbarProgressChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
const auto weakThisCopy = weakThis;
co_await wil::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired
if (auto tab{ weakThisCopy.get() })
{
tab->_UpdateProgressState();
}
});
events.ConnectionStateChanged = content.ConnectionStateChanged(
winrt::auto_revoke,
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
@@ -2117,9 +2127,9 @@ namespace winrt::TerminalApp::implementation
// - The value to populate in the title run of the tool tip
winrt::hstring Tab::_CreateToolTipTitle()
{
if (const auto& control{ GetActiveTerminalControl() })
if (const auto profile{ GetFocusedProfile() })
{
const auto profileName{ control.Settings().ProfileName() };
const auto profileName{ profile.Name() };
if (profileName != Title())
{
return winrt::hstring{ fmt::format(FMT_COMPILE(L"{}: {}"), profileName, Title()) };

View File

@@ -21,8 +21,8 @@ namespace TerminalApp
UInt32 TabViewIndex;
UInt32 TabViewNumTabs;
overridable void Focus(Windows.UI.Xaml.FocusState focusState);
overridable void Shutdown();
void Focus(Windows.UI.Xaml.FocusState focusState);
void Shutdown();
void SetDispatch(ShortcutActionDispatch dispatch);
}

View File

@@ -157,12 +157,7 @@ namespace winrt::TerminalApp::implementation
// Set this tab's icon to the icon from the content
_UpdateTabIcon(*newTabImpl);
// This is necessary, because WinUI does not have support for middle clicks.
// Its Tapped event doesn't provide the information what button was used either.
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabPointerPressed });
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabPointerReleased });
tabViewItem.PointerExited({ this, &TerminalPage::_OnTabPointerExited });
tabViewItem.PointerEntered({ this, &TerminalPage::_OnTabPointerEntered });
// When the tab requests close, try to close it (prompt for approval, if required)
newTabImpl->CloseRequested([weakTab, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
@@ -664,7 +659,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - returns a tab corresponding to a view item. This might return null,
// so make sure to check the result!
winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept
winrt::TerminalApp::Tab TerminalPage::_GetTabByTabViewItem(const IInspectable& tabViewItem) const noexcept
{
uint32_t tabIndexFromControl{};
const auto items{ _tabView.TabItems() };
@@ -876,60 +871,64 @@ namespace winrt::TerminalApp::implementation
_UpdateTabView();
}
// Method Description:
// - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
void TerminalPage::_OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
if (!_tabItemMiddleClickHookEnabled || !e.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
if (const auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
{
_tabPointerMiddleButtonPressed = tabViewItem.CapturePointer(eventArgs.Pointer());
_tabPointerMiddleButtonExited = false;
}
eventArgs.Handled(true);
return;
}
const auto tabViewItem = sender.try_as<MUX::Controls::TabViewItem>();
if (!tabViewItem || !tabViewItem.CapturePointer(e.Pointer()))
{
return;
}
_tabItemMiddleClickExited = false;
_tabItemMiddleClickPointerEntered = tabViewItem.PointerEntered(winrt::auto_revoke, [this](auto&&, auto&& e) {
_tabItemMiddleClickExited = false;
e.Handled(true);
});
_tabItemMiddleClickPointerExited = tabViewItem.PointerExited(winrt::auto_revoke, [this](auto&&, auto&& e) {
_tabItemMiddleClickExited = true;
e.Handled(true);
});
_tabItemMiddleClickPointerCaptureLost = tabViewItem.PointerCaptureLost(winrt::auto_revoke, [this](auto&& sender, auto&& e) {
// The WinUI TabView calls CapturePointer() internally and it's not reference counted,
// so when it calls ReleasePointerCapture() in its PointerReleased handler,
// we get a PointerCaptureLost before we receive the PointerReleased event.
// This makes typical handling of PointerReleased events on our side difficult.
// Well, whatever, now we just hook PointerCaptureLost because we know WinUI will trigger it.
_tabItemMiddleClickPointerEntered.revoke();
_tabItemMiddleClickPointerExited.revoke();
_tabItemMiddleClickPointerCaptureLost.revoke();
if (!_tabItemMiddleClickExited && !e.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_OnTabPointerReleasedCloseTab(std::move(sender));
}
e.Handled(true);
});
e.Handled(true);
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerReleased(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
safe_void_coroutine TerminalPage::_OnTabPointerReleasedCloseTab(IInspectable sender)
{
if (_tabPointerMiddleButtonPressed && !eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonPressed = false;
if (auto tabViewItem{ sender.try_as<MUX::Controls::TabViewItem>() })
{
tabViewItem.ReleasePointerCapture(eventArgs.Pointer());
if (!_tabPointerMiddleButtonExited)
{
_OnTabPointerReleasedCloseTab(std::move(tabViewItem));
}
}
eventArgs.Handled(true);
}
}
safe_void_coroutine TerminalPage::_OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender)
{
const auto tab = _GetTabByTabViewItem(sender);
if (!tab)
{
co_return;
}
// WinUI asynchronously updates its tab view items, so it may happen that we're given a
// `TabViewItem` that still contains a `Tab` which has actually already been removed.
// First we must yield once, to flush out whatever TabView is currently doing.
const auto strong = get_strong();
co_await wil::resume_foreground(Dispatcher());
const auto tab = _GetTabByTabViewItem(sender);
if (!tab)
{
co_return;
}
// `tab.Shutdown()` in `_RemoveTab()` sets the content to null = This checks if the tab is closed.
if (tab.Content())
{
@@ -937,34 +936,6 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerEntered(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonExited = false;
eventArgs.Handled(true);
}
}
// Method Description:
// - Tracking pointer state for tab remove
// Arguments:
// - sender: the control that originated this event (TabViewItem)
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabPointerExited(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
{
if (eventArgs.GetCurrentPoint(nullptr).Properties().IsMiddleButtonPressed())
{
_tabPointerMiddleButtonExited = true;
eventArgs.Handled(true);
}
}
void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab)
{
// Unfocus all the tabs.
@@ -999,10 +970,7 @@ namespace winrt::TerminalApp::implementation
tab.TabViewItem().StartBringIntoView();
// Raise an event that our title changed
if (_settings.GlobalSettings().ShowTitleInTitlebar())
{
TitleChanged.raise(*this, tab.Title());
}
TitleChanged.raise(*this, nullptr);
_updateThemeColors();
@@ -1012,6 +980,8 @@ namespace winrt::TerminalApp::implementation
auto profile = tabImpl->GetFocusedProfile();
_UpdateBackground(profile);
}
_adjustProcessPriorityThrottled->Run();
}
CATCH_LOG();
}

View File

@@ -26,6 +26,7 @@
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalMUX>true</TerminalMUX>
<TerminalWinGetInterop>true</TerminalWinGetInterop>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -173,9 +174,7 @@
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="TerminalSettingsCache.h">
<DependentUpon>TerminalSettingsCache.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalSettingsCache.h" />
<ClInclude Include="SuggestionsControl.h">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
</ClInclude>
@@ -288,9 +287,7 @@
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="TerminalSettingsCache.cpp">
<DependentUpon>TerminalSettingsCache.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalSettingsCache.cpp" />
<ClCompile Include="SuggestionsControl.cpp">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
</ClCompile>
@@ -365,7 +362,6 @@
<DependentUpon>TaskPaneContent.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="TerminalSettingsCache.idl" />
<Midl Include="MarkdownPaneContent.idl">
<DependentUpon>MarkdownPaneContent.xaml</DependentUpon>
<SubType>Code</SubType>

View File

@@ -90,7 +90,6 @@
<Midl Include="TerminalWindow.idl" />
<Midl Include="TaskbarState.idl" />
<Midl Include="IPaneContent.idl" />
<Midl Include="TerminalSettingsCache.idl" />
<Midl Include="Monarch.idl" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,6 +6,7 @@
#include "TerminalPage.h"
#include <LibraryResources.h>
#include <TerminalThemeHelpers.h>
#include <Utils.h>
#include <TerminalCore/ControlKeyStates.hpp>
@@ -17,6 +18,7 @@
#include "SettingsPaneContent.h"
#include "SnippetsPaneContent.h"
#include "TabRowControl.h"
#include "TerminalSettingsCache.h"
#include "../../types/inc/ColorFix.hpp"
#include "../../types/inc/utils.hpp"
@@ -56,6 +58,160 @@ namespace winrt
using VirtualKeyModifiers = Windows::System::VirtualKeyModifiers;
}
namespace clipboard
{
static SRWLOCK lock = SRWLOCK_INIT;
struct ClipboardHandle
{
explicit ClipboardHandle(bool open) :
_open{ open }
{
}
~ClipboardHandle()
{
if (_open)
{
ReleaseSRWLockExclusive(&lock);
CloseClipboard();
}
}
explicit operator bool() const noexcept
{
return _open;
}
private:
bool _open = false;
};
ClipboardHandle open(HWND hwnd)
{
// Turns out, OpenClipboard/CloseClipboard are not thread-safe whatsoever,
// and on CloseClipboard, the GetClipboardData handle may get freed.
// The problem is that WinUI also uses OpenClipboard (through WinRT which uses OLE),
// and so even with this mutex we can still crash randomly if you copy something via WinUI.
// Makes you wonder how many Windows apps are subtly broken, huh.
AcquireSRWLockExclusive(&lock);
bool success = false;
// OpenClipboard may fail to acquire the internal lock --> retry.
for (DWORD sleep = 10;; sleep *= 2)
{
if (OpenClipboard(hwnd))
{
success = true;
break;
}
// 10 iterations
if (sleep > 10000)
{
break;
}
Sleep(sleep);
}
if (!success)
{
ReleaseSRWLockExclusive(&lock);
}
return ClipboardHandle{ success };
}
void write(wil::zwstring_view text, std::string_view html, std::string_view rtf)
{
static const auto regular = [](const UINT format, const void* src, const size_t bytes) {
wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) };
const auto locked = GlobalLock(handle.get());
memcpy(locked, src, bytes);
GlobalUnlock(handle.get());
THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get()));
handle.release();
};
static const auto registered = [](const wchar_t* format, const void* src, size_t bytes) {
const auto id = RegisterClipboardFormatW(format);
if (!id)
{
LOG_LAST_ERROR();
return;
}
regular(id, src, bytes);
};
EmptyClipboard();
if (!text.empty())
{
// As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
// CF_UNICODETEXT: [...] A null character signals the end of the data.
// --> We add +1 to the length. This works because .c_str() is null-terminated.
regular(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
}
if (!html.empty())
{
registered(L"HTML Format", html.data(), html.size());
}
if (!rtf.empty())
{
registered(L"Rich Text Format", rtf.data(), rtf.size());
}
}
winrt::hstring read()
{
// This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
if (const auto handle = GetClipboardData(CF_UNICODETEXT))
{
const wil::unique_hglobal_locked lock{ handle };
const auto str = static_cast<const wchar_t*>(lock.get());
if (!str)
{
return {};
}
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
const auto len = wcsnlen(str, maxLen);
return winrt::hstring{ str, gsl::narrow_cast<uint32_t>(len) };
}
// We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
if (const auto handle = GetClipboardData(CF_HDROP))
{
const wil::unique_hglobal_locked lock{ handle };
const auto drop = static_cast<HDROP>(lock.get());
if (!drop)
{
return {};
}
const auto cap = DragQueryFileW(drop, 0, nullptr, 0);
if (cap == 0)
{
return {};
}
auto buffer = winrt::impl::hstring_builder{ cap };
const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1);
if (len == 0)
{
return {};
}
return buffer.to_hstring();
}
return {};
}
} // namespace clipboard
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) :
@@ -102,6 +258,17 @@ namespace winrt::TerminalApp::implementation
// before restoring previous tabs in that scenario.
}
}
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
DispatcherQueue::GetForCurrentThread(),
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 100 },
.debounce = true,
.trailing = true,
},
[=]() {
_adjustProcessPriority();
});
_hostingHwnd = hwnd;
return S_OK;
}
@@ -113,7 +280,7 @@ namespace winrt::TerminalApp::implementation
if (_settings == nullptr)
{
// Create this only on the first time we load the settings.
_terminalSettingsCache = TerminalApp::TerminalSettingsCache{ settings, *_bindings };
_terminalSettingsCache = std::make_shared<TerminalSettingsCache>(settings, *_bindings);
}
_settings = settings;
@@ -216,6 +383,8 @@ namespace winrt::TerminalApp::implementation
{
const auto visibility = theme.Tab() ? theme.Tab().ShowCloseButton() : Settings::Model::TabCloseButtonVisibility::Always;
_tabItemMiddleClickHookEnabled = visibility == Settings::Model::TabCloseButtonVisibility::Never;
switch (visibility)
{
case Settings::Model::TabCloseButtonVisibility::Never:
@@ -545,22 +714,32 @@ namespace winrt::TerminalApp::implementation
_WindowProperties.VirtualEnvVars(env);
}
// The current TerminalWindow & TerminalPage architecture is rather instable
// and fails to start up if the first tab isn't created synchronously.
//
// While that's a fair assumption in on itself, simultaneously WinUI will
// not assign tab contents a size if they're not shown at least once,
// which we need however in order to initialize ControlCore with a size.
//
// So, we do two things here:
// * DO NOT suspend if this is the first tab.
// * DO suspend between the creation of panes (or tabs) in order to allow
// WinUI to layout the new controls and for ControlCore to get a size.
//
// This same logic is also applied to CreateTabFromConnection.
//
// See GH#13136.
auto suspend = _tabs.Size() > 0;
for (size_t i = 0; i < actions.size(); ++i)
{
if (i != 0)
if (suspend)
{
// Each action may rely on the XAML layout of a preceding action.
// Most importantly, this is the case for the combination of NewTab + SplitPane,
// as the former appears to only have a layout size after at least 1 resume_foreground,
// while the latter relies on that information. This is also why it uses Low priority.
//
// Curiously, this does not seem to be required when using startupActions, but only when
// tearing out a tab (this currently creates a new window with injected startup actions).
// This indicates that this is really more of an architectural issue and not a fundamental one.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
}
_actionDispatch->DoAction(actions[i]);
suspend = true;
}
// GH#6586: now that we're done processing all startup commands,
@@ -575,8 +754,16 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::CreateTabFromConnection(ITerminalConnection connection)
safe_void_coroutine TerminalPage::CreateTabFromConnection(ITerminalConnection connection)
{
const auto strong = get_strong();
// This is the exact same logic as in ProcessStartupActions.
if (_tabs.Size() > 0)
{
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
}
NewTerminalArgs newTerminalArgs;
if (const auto conpty = connection.try_as<ConptyConnection>())
@@ -591,6 +778,7 @@ namespace winrt::TerminalApp::implementation
// elevated version of the Terminal with that profile... that's a
// recipe for disaster. We won't ever open up a tab in this window.
newTerminalArgs.Elevate(false);
const auto newPane = _MakePane(newTerminalArgs, nullptr, std::move(connection));
newPane->WalkTree([](const auto& pane) {
pane->FinalizeConfigurationGivenDefault();
@@ -923,7 +1111,7 @@ namespace winrt::TerminalApp::implementation
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);
// If the folder should auto-inline and there is only one item, do so.
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntryItems.size() == 1)
{
for (auto const& folderEntryItem : folderEntryItems)
{
@@ -937,7 +1125,7 @@ namespace winrt::TerminalApp::implementation
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
folderItem.Text(folderEntry.Name());
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon().Resolved());
folderItem.Icon(icon);
for (const auto& folderEntryItem : folderEntryItems)
@@ -987,7 +1175,7 @@ namespace winrt::TerminalApp::implementation
break;
}
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex(), profileEntry.Icon());
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex(), profileEntry.Icon().Resolved());
items.push_back(profileItem);
break;
}
@@ -997,7 +1185,7 @@ namespace winrt::TerminalApp::implementation
const auto actionId = actionEntry.ActionId();
if (_settings.ActionMap().GetActionByID(actionId))
{
auto actionItem = _CreateNewTabFlyoutAction(actionId, actionEntry.Icon());
auto actionItem = _CreateNewTabFlyoutAction(actionId, actionEntry.Icon().Resolved());
items.push_back(actionItem);
}
@@ -1036,7 +1224,7 @@ namespace winrt::TerminalApp::implementation
// If a custom icon path has been specified, set it as the icon for
// this flyout item. Otherwise, if an icon is set for this profile, set that icon
// for this flyout item.
const auto& iconPath = iconPathOverride.empty() ? profile.EvaluatedIcon() : iconPathOverride;
const auto& iconPath = iconPathOverride.empty() ? profile.Icon().Resolved() : iconPathOverride;
if (!iconPath.empty())
{
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
@@ -1122,7 +1310,7 @@ namespace winrt::TerminalApp::implementation
// If a custom icon path has been specified, set it as the icon for
// this flyout item. Otherwise, if an icon is set for this action, set that icon
// for this flyout item.
const auto& iconPath = iconPathOverride.empty() ? action.IconPath() : iconPathOverride;
const auto& iconPath = iconPathOverride.empty() ? action.Icon().Resolved() : iconPathOverride;
if (!iconPath.empty())
{
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
@@ -1757,11 +1945,9 @@ namespace winrt::TerminalApp::implementation
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTitle(const Tab& tab)
{
auto newTabTitle = tab.Title();
if (tab == _GetFocusedTab())
{
TitleChanged.raise(*this, newTabTitle);
TitleChanged.raise(*this, nullptr);
}
}
@@ -1776,7 +1962,7 @@ namespace winrt::TerminalApp::implementation
{
term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler });
// Add an event handler when the terminal wants to paste data from the Clipboard.
term.WriteToClipboard({ get_weak(), &TerminalPage::_copyToClipboard });
term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler });
term.OpenHyperlink({ this, &TerminalPage::_OpenHyperlinkHandler });
@@ -1798,9 +1984,7 @@ namespace winrt::TerminalApp::implementation
});
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler });
term.WindowSizeChanged({ get_weak(), &TerminalPage::_WindowSizeChanged });
// Don't even register for the event if the feature is compiled off.
@@ -1946,7 +2130,7 @@ namespace winrt::TerminalApp::implementation
return false;
}
TermControl TerminalPage::_GetActiveControl()
TermControl TerminalPage::_GetActiveControl() const
{
if (const auto tabImpl{ _GetFocusedTabImpl() })
{
@@ -2407,6 +2591,8 @@ namespace winrt::TerminalApp::implementation
auto profile = tab->GetFocusedProfile();
_UpdateBackground(profile);
}
_adjustProcessPriorityThrottled->Run();
}
uint32_t TerminalPage::NumberOfTabs() const
@@ -2625,17 +2811,9 @@ namespace winrt::TerminalApp::implementation
{
if (_settings.GlobalSettings().ShowTitleInTitlebar())
{
auto selectedIndex = _tabView.SelectedIndex();
if (selectedIndex >= 0)
if (const auto tab{ _GetFocusedTab() })
{
try
{
if (auto focusedControl{ _GetActiveControl() })
{
return focusedControl.Title();
}
}
CATCH_LOG();
return tab.Title();
}
}
return { L"Terminal" };
@@ -2730,75 +2908,6 @@ namespace winrt::TerminalApp::implementation
return dimension;
}
static wil::unique_close_clipboard_call _openClipboard(HWND hwnd)
{
bool success = false;
// OpenClipboard may fail to acquire the internal lock --> retry.
for (DWORD sleep = 10;; sleep *= 2)
{
if (OpenClipboard(hwnd))
{
success = true;
break;
}
// 10 iterations
if (sleep > 10000)
{
break;
}
Sleep(sleep);
}
return wil::unique_close_clipboard_call{ success };
}
static winrt::hstring _extractClipboard()
{
// This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
if (const auto handle = GetClipboardData(CF_UNICODETEXT))
{
const wil::unique_hglobal_locked lock{ handle };
const auto str = static_cast<const wchar_t*>(lock.get());
if (!str)
{
return {};
}
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
const auto len = wcsnlen(str, maxLen);
return winrt::hstring{ str, gsl::narrow_cast<uint32_t>(len) };
}
// We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
if (const auto handle = GetClipboardData(CF_HDROP))
{
const wil::unique_hglobal_locked lock{ handle };
const auto drop = static_cast<HDROP>(lock.get());
if (!drop)
{
return {};
}
const auto cap = DragQueryFileW(drop, 0, nullptr, 0);
if (cap == 0)
{
return {};
}
auto buffer = winrt::impl::hstring_builder{ cap };
const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1);
if (len == 0)
{
return {};
}
return buffer.to_hstring();
}
return {};
}
// Function Description:
// - This function is called when the `TermControl` requests that we send
// it the clipboard's content.
@@ -2818,36 +2927,51 @@ namespace winrt::TerminalApp::implementation
const auto weakThis = get_weak();
const auto dispatcher = Dispatcher();
const auto globalSettings = _settings.GlobalSettings();
const auto bracketedPaste = eventArgs.BracketedPasteEnabled();
// GetClipboardData might block for up to 30s for delay-rendered contents.
co_await winrt::resume_background();
winrt::hstring text;
if (const auto clipboard = _openClipboard(nullptr))
if (const auto clipboard = clipboard::open(nullptr))
{
text = _extractClipboard();
text = clipboard::read();
}
if (globalSettings.TrimPaste())
if (!bracketedPaste && globalSettings.TrimPaste())
{
text = { Utils::TrimPaste(text) };
if (text.empty())
{
// Text is all white space, nothing to paste
co_return;
}
text = winrt::hstring{ Utils::TrimPaste(text) };
}
if (text.empty())
{
co_return;
}
bool warnMultiLine = false;
switch (globalSettings.WarnAboutMultiLinePaste())
{
case WarnAboutMultiLinePaste::Automatic:
// NOTE that this is unsafe, because a shell that doesn't support bracketed paste
// will allow an attacker to enable the mode, not realize that, and then accept
// the paste as if it was a series of legitimate commands. See GH#13014.
warnMultiLine = !bracketedPaste;
break;
case WarnAboutMultiLinePaste::Always:
warnMultiLine = true;
break;
default:
warnMultiLine = false;
break;
}
// If the requesting terminal is in bracketed paste mode, then we don't need to warn about a multi-line paste.
auto warnMultiLine = globalSettings.WarnAboutMultiLinePaste() && !eventArgs.BracketedPasteEnabled();
if (warnMultiLine)
{
const auto isNewLineLambda = [](auto c) { return c == L'\n' || c == L'\r'; };
const auto hasNewLine = std::find_if(text.cbegin(), text.cend(), isNewLineLambda) != text.cend();
warnMultiLine = hasNewLine;
const std::wstring_view view{ text };
warnMultiLine = view.find_first_of(L"\r\n") != std::wstring_view::npos;
}
constexpr const std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB
constexpr std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB
const auto warnLargeText = text.size() > minimumSizeForWarning && globalSettings.WarnAboutLargePaste();
if (warnMultiLine || warnLargeText)
@@ -2857,7 +2981,7 @@ namespace winrt::TerminalApp::implementation
if (const auto strongThis = weakThis.get())
{
// We have to initialize the dialog here to be able to change the text of the text block within it
FindName(L"MultiLinePasteDialog").try_as<WUX::Controls::ContentDialog>();
std::ignore = FindName(L"MultiLinePasteDialog");
ClipboardText().Text(text);
// The vertical offset on the scrollbar does not reset automatically, so reset it manually
@@ -3038,7 +3162,7 @@ namespace winrt::TerminalApp::implementation
// - formats: dictate which formats need to be copied
// Return Value:
// - true iff we we able to copy text (if a selection was active)
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats)
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const CopyFormat formats)
{
if (const auto& control{ _GetActiveControl() })
{
@@ -3181,6 +3305,21 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_copyToClipboard(const IInspectable, const WriteToClipboardEventArgs args) const
{
if (const auto clipboard = clipboard::open(_hostingHwnd.value_or(nullptr)))
{
const auto plain = args.Plain();
const auto html = args.Html();
const auto rtf = args.Rtf();
clipboard::write(
{ plain.data(), plain.size() },
{ reinterpret_cast<const char*>(html.data()), html.size() },
{ reinterpret_cast<const char*>(rtf.data()), rtf.size() });
}
}
// Method Description:
// - Paste text from the Windows Clipboard to the focused terminal
void TerminalPage::_PasteText()
@@ -3571,7 +3710,7 @@ namespace winrt::TerminalApp::implementation
return;
}
const auto path = newAppearance.ExpandedBackgroundImagePath();
const auto path = newAppearance.BackgroundImagePath().Resolved();
if (path.empty())
{
_tabContent.Background(nullptr);
@@ -3636,7 +3775,7 @@ namespace winrt::TerminalApp::implementation
// updating terminal panes, so that we don't have to build a _new_
// TerminalSettings for every profile we update - we can just look them
// up the previous ones we built.
_terminalSettingsCache.Reset(_settings, *_bindings);
_terminalSettingsCache->Reset(_settings, *_bindings);
for (const auto& tab : _tabs)
{
@@ -3694,6 +3833,9 @@ namespace winrt::TerminalApp::implementation
_updateThemeColors();
_updateAllTabCloseButtons();
// The user may have changed the "show title in titlebar" setting.
TitleChanged.raise(*this, nullptr);
}
void TerminalPage::_updateAllTabCloseButtons()
@@ -3707,6 +3849,8 @@ namespace winrt::TerminalApp::implementation
theme.Tab().ShowCloseButton() :
Settings::Model::TabCloseButtonVisibility::Always;
_tabItemMiddleClickHookEnabled = visibility == Settings::Model::TabCloseButtonVisibility::Never;
for (const auto& tab : _tabs)
{
tab.CloseButtonVisibility(visibility);
@@ -4583,9 +4727,12 @@ namespace winrt::TerminalApp::implementation
if (const auto coreState{ sender.try_as<winrt::Microsoft::Terminal::Control::ICoreState>() })
{
const auto newConnectionState = coreState.ConnectionState();
co_await wil::resume_foreground(Dispatcher());
_adjustProcessPriorityThrottled->Run();
if (newConnectionState == ConnectionState::Failed && !_IsMessageDismissed(InfoBarMessage::CloseOnExitInfo))
{
co_await wil::resume_foreground(Dispatcher());
if (const auto infoBar = FindName(L"CloseOnExitInfoBar").try_as<MUX::Controls::InfoBar>())
{
infoBar.IsOpen(true);
@@ -4850,6 +4997,94 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_adjustProcessPriority() const
{
// Windowing is single-threaded, so this will not cause a race condition.
static bool supported{ true };
if (!supported || !_hostingHwnd.has_value())
{
return;
}
std::array<HANDLE, 32> processes;
auto it = processes.begin();
const auto end = processes.end();
auto&& appendFromControl = [&](auto&& control) {
if (it == end)
{
return;
}
if (control)
{
if (const auto conn{ control.Connection() })
{
if (const auto pty{ conn.try_as<winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection>() })
{
if (const uint64_t process{ pty.RootProcessHandle() }; process != 0)
{
*it++ = reinterpret_cast<HANDLE>(process);
}
}
}
}
};
auto&& appendFromTab = [&](auto&& tabImpl) {
if (const auto pane{ tabImpl->GetRootPane() })
{
pane->WalkTree([&](auto&& child) {
if (const auto& control{ child->GetTerminalControl() })
{
appendFromControl(control);
}
});
}
};
if (!_activated)
{
// When a window is out of focus, we want to attach all of the processes
// under it to the window so they all go into the background at the same time.
for (auto&& tab : _tabs)
{
if (auto tabImpl{ _GetTabImpl(tab) })
{
appendFromTab(tabImpl);
}
}
}
else
{
// When a window is in focus, propagate our foreground boost (if we have one)
// to current all panes in the current tab.
if (auto tabImpl{ _GetFocusedTabImpl() })
{
appendFromTab(tabImpl);
}
}
const auto count{ gsl::narrow_cast<DWORD>(it - processes.begin()) };
const auto hr = TerminalTrySetWindowAssociatedProcesses(_hostingHwnd.value(), count, count ? processes.data() : nullptr);
if (S_FALSE == hr)
{
// Don't bother trying again or logging. The wrapper tells us it's unsupported.
supported = false;
return;
}
TraceLoggingWrite(
g_hTerminalAppProvider,
"CalledNewQoSAPI",
TraceLoggingValue(reinterpret_cast<uintptr_t>(_hostingHwnd.value()), "hwnd"),
TraceLoggingValue(count),
TraceLoggingHResult(hr));
#ifdef _DEBUG
OutputDebugStringW(fmt::format(FMT_COMPILE(L"Submitted {} processes to TerminalTrySetWindowAssociatedProcesses; return=0x{:08x}\n"), count, hr).c_str());
#endif
}
void TerminalPage::WindowActivated(const bool activated)
{
// Stash if we're activated. Use that when we reload
@@ -4857,6 +5092,8 @@ namespace winrt::TerminalApp::implementation
_activated = activated;
_updateThemeColors();
_adjustProcessPriorityThrottled->Run();
if (const auto& tab{ _GetFocusedTabImpl() })
{
if (tab->TabStatus().IsInputBroadcastActive())
@@ -4876,8 +5113,6 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine TerminalPage::_ControlCompletionsChangedHandler(const IInspectable sender,
const CompletionsChangedEventArgs args)
{
// This will come in on a background (not-UI, not output) thread.
// This won't even get hit if the velocity flag is disabled - we gate
// registering for the event based off of
// Feature_ShellCompletions::IsEnabled back in _RegisterTerminalEvents
@@ -5051,7 +5286,7 @@ namespace winrt::TerminalApp::implementation
makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr }, menu);
const auto focusedProfileName = focusedProfile.Name();
const auto focusedProfileIcon = focusedProfile.Icon();
const auto focusedProfileIcon = focusedProfile.Icon().Resolved();
const auto splitPaneDuplicateText = RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName; // SplitPaneDuplicateText
const auto splitPaneRightText = RS_(L"SplitPaneRightText");
@@ -5077,7 +5312,7 @@ namespace winrt::TerminalApp::implementation
{
const auto profile = activeProfiles.GetAt(profileIndex);
const auto profileName = profile.Name();
const auto profileIcon = profile.Icon();
const auto profileIcon = profile.Icon().Resolved();
NewTerminalArgs args{};
args.Profile(profileName);
@@ -5112,22 +5347,22 @@ namespace winrt::TerminalApp::implementation
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Down, mruPanes))
{
makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu);
makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu);
}
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Right, mruPanes))
{
makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu);
makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu);
}
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Up, mruPanes))
{
makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu);
makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu);
}
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Left, mruPanes))
{
makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu);
makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu);
}
makeMenuItem(RS_(L"SwapPaneText"), L"\xF1CB", swapPaneMenu, menu);

View File

@@ -3,6 +3,8 @@
#pragma once
#include <ThrottledFunc.h>
#include "TerminalPage.g.h"
#include "Tab.h"
#include "AppKeyBindings.h"
@@ -29,6 +31,8 @@ namespace Microsoft::Terminal::Core
namespace winrt::TerminalApp::implementation
{
struct TerminalSettingsCache;
inline constexpr uint32_t DefaultRowsToScroll{ 3 };
inline constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
@@ -149,7 +153,7 @@ namespace winrt::TerminalApp::implementation
safe_void_coroutine ProcessStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> actions,
const winrt::hstring cwd = winrt::hstring{},
const winrt::hstring env = winrt::hstring{});
void CreateTabFromConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
safe_void_coroutine CreateTabFromConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
TerminalApp::WindowProperties WindowProperties() const noexcept { return _WindowProperties; };
@@ -169,7 +173,7 @@ namespace winrt::TerminalApp::implementation
til::property_changed_event PropertyChanged;
// -------------------------------- WinRT Events ---------------------------------
til::typed_event<IInspectable, winrt::hstring> TitleChanged;
til::typed_event<IInspectable, IInspectable> TitleChanged;
til::typed_event<IInspectable, IInspectable> CloseWindowRequested;
til::typed_event<IInspectable, winrt::Windows::UI::Xaml::UIElement> SetTitleBarContent;
til::typed_event<IInspectable, IInspectable> FocusModeChanged;
@@ -273,7 +277,7 @@ namespace winrt::TerminalApp::implementation
TerminalApp::ContentManager _manager{ nullptr };
TerminalApp::TerminalSettingsCache _terminalSettingsCache{ nullptr };
std::shared_ptr<TerminalSettingsCache> _terminalSettingsCache{};
struct StashedDragData
{
@@ -359,8 +363,11 @@ namespace winrt::TerminalApp::implementation
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
bool _MoveTab(winrt::com_ptr<Tab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
std::shared_ptr<ThrottledFunc<>> _adjustProcessPriorityThrottled;
void _adjustProcessPriority() const;
template<typename F>
bool _ApplyToActiveControls(F f)
bool _ApplyToActiveControls(F f) const
{
if (const auto tab{ _GetFocusedTabImpl() })
{
@@ -379,12 +386,12 @@ namespace winrt::TerminalApp::implementation
return false;
}
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl() const;
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
std::optional<uint32_t> _GetTabIndex(const TerminalApp::Tab& tab) const noexcept;
TerminalApp::Tab _GetFocusedTab() const noexcept;
winrt::com_ptr<Tab> _GetFocusedTabImpl() const noexcept;
TerminalApp::Tab _GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept;
TerminalApp::Tab _GetTabByTabViewItem(const IInspectable& tabViewItem) const noexcept;
void _HandleClosePaneRequested(std::shared_ptr<Pane> pane);
safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::Tab tab);
@@ -413,10 +420,11 @@ namespace winrt::TerminalApp::implementation
bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats);
safe_void_coroutine _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
void _copyToClipboard(IInspectable, Microsoft::Terminal::Control::WriteToClipboardEventArgs args) const;
void _PasteText();
safe_void_coroutine _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);
@@ -427,13 +435,18 @@ namespace winrt::TerminalApp::implementation
void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs);
void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs);
bool _tabPointerMiddleButtonPressed{ false };
bool _tabPointerMiddleButtonExited{ false };
// BODGY: WinUI's TabView has a broken close event handler:
// If the close button is disabled, middle-clicking the tab raises no close
// event. Because that's dumb, we implement our own middle-click handling.
// `_tabItemMiddleClickHookEnabled` is true whenever the close button is hidden,
// and that enables all of the rest of this machinery (and this workaround).
bool _tabItemMiddleClickHookEnabled = false;
bool _tabItemMiddleClickExited = false;
PointerEntered_revoker _tabItemMiddleClickPointerEntered;
PointerExited_revoker _tabItemMiddleClickPointerExited;
PointerCaptureLost_revoker _tabItemMiddleClickPointerCaptureLost;
void _OnTabPointerPressed(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
void _OnTabPointerReleased(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
safe_void_coroutine _OnTabPointerReleasedCloseTab(winrt::Microsoft::UI::Xaml::Controls::TabViewItem sender);
void _OnTabPointerEntered(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
void _OnTabPointerExited(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs);
safe_void_coroutine _OnTabPointerReleasedCloseTab(IInspectable sender);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);

View File

@@ -82,7 +82,7 @@ namespace TerminalApp
void WindowActivated(Boolean activated);
void SendContentToOther(RequestReceiveContentArgs args);
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;

View File

@@ -6,6 +6,7 @@
#include <mmsystem.h>
#include "TerminalSettingsCache.h"
#include "../../types/inc/utils.hpp"
#include "BellEventArgs.g.cpp"
@@ -20,7 +21,7 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection;
namespace winrt::TerminalApp::implementation
{
TerminalPaneContent::TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const TerminalApp::TerminalSettingsCache& cache,
const std::shared_ptr<TerminalSettingsCache>& cache,
const winrt::Microsoft::Terminal::Control::TermControl& control) :
_control{ control },
_cache{ cache },
@@ -82,7 +83,7 @@ namespace winrt::TerminalApp::implementation
winrt::hstring TerminalPaneContent::Icon() const
{
return _profile.EvaluatedIcon();
return _profile.Icon().Resolved();
}
Windows::Foundation::IReference<winrt::Windows::UI::Color> TerminalPaneContent::TabColor() const noexcept
@@ -278,7 +279,7 @@ namespace winrt::TerminalApp::implementation
auto sounds{ _profile.BellSound() };
if (sounds && sounds.Size() > 0)
{
winrt::hstring soundPath{ wil::ExpandEnvironmentStringsW<std::wstring>(sounds.GetAt(rand() % sounds.Size()).c_str()) };
winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
winrt::Windows::Foundation::Uri uri{ soundPath };
_playBellSound(uri);
}
@@ -340,9 +341,13 @@ namespace winrt::TerminalApp::implementation
RestartTerminalRequested.raise(*this, nullptr);
}
void TerminalPaneContent::UpdateSettings(const CascadiaSettings& /*settings*/)
void TerminalPaneContent::UpdateSettings(const CascadiaSettings& settings)
{
if (const auto& settings{ _cache.TryLookup(_profile) })
// Reload our profile from the settings model to propagate bell mode, icon, and close on exit mode (anything that uses _profile).
const auto profile{ settings.FindProfile(_profile.Guid()) };
_profile = profile ? profile : settings.ProfileDefaults();
if (const auto& settings{ _cache->TryLookup(_profile) })
{
_control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings());
}

View File

@@ -8,6 +8,8 @@
namespace winrt::TerminalApp::implementation
{
struct TerminalSettingsCache;
struct BellEventArgs : public BellEventArgsT<BellEventArgs>
{
public:
@@ -20,7 +22,7 @@ namespace winrt::TerminalApp::implementation
struct TerminalPaneContent : TerminalPaneContentT<TerminalPaneContent>, BasicPaneEvents
{
TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const TerminalApp::TerminalSettingsCache& cache,
const std::shared_ptr<TerminalSettingsCache>& cache,
const winrt::Microsoft::Terminal::Control::TermControl& control);
winrt::Windows::UI::Xaml::FrameworkElement GetRoot();
@@ -59,7 +61,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
TerminalApp::TerminalSettingsCache _cache{ nullptr };
std::shared_ptr<TerminalSettingsCache> _cache{};
bool _isDefTermSession{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
import "IPaneContent.idl";
import "TerminalSettingsCache.idl";
import "FilteredCommand.idl";
namespace TerminalApp

View File

@@ -3,7 +3,6 @@
#include "pch.h"
#include "TerminalSettingsCache.h"
#include "TerminalSettingsCache.g.cpp"
namespace winrt
{

View File

@@ -13,14 +13,13 @@ Abstract:
--*/
#pragma once
#include "TerminalSettingsCache.g.h"
#include <inc/cppwinrt_utils.h>
#include "winrt/Microsoft.Terminal.Settings.Model.h"
#include "winrt/TerminalApp.h"
namespace winrt::TerminalApp::implementation
{
class TerminalSettingsCache : public TerminalSettingsCacheT<TerminalSettingsCache>
struct TerminalSettingsCache
{
public:
TerminalSettingsCache(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings);
Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult TryLookup(const Microsoft::Terminal::Settings::Model::Profile& profile);
void Reset(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings);
@@ -31,8 +30,3 @@ namespace winrt::TerminalApp::implementation
std::unordered_map<winrt::guid, std::pair<Microsoft::Terminal::Settings::Model::Profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult>> profileGuidSettingsMap;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TerminalSettingsCache);
}

View File

@@ -1,13 +0,0 @@
import "AppKeyBindings.idl";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass TerminalSettingsCache
{
TerminalSettingsCache(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, AppKeyBindings bindings);
Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult TryLookup(Microsoft.Terminal.Settings.Model.Profile profile);
void Reset(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, AppKeyBindings bindings);
}
}

View File

@@ -40,8 +40,7 @@ static const std::array settingsLoadWarningsLabels{
USES_RESOURCE(L"MissingDefaultProfileText"),
USES_RESOURCE(L"DuplicateProfileText"),
USES_RESOURCE(L"UnknownColorSchemeText"),
USES_RESOURCE(L"InvalidBackgroundImage"),
USES_RESOURCE(L"InvalidIcon"),
USES_RESOURCE(L"InvalidMediaResource"),
USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
USES_RESOURCE(L"TooManyKeysForChord"),
USES_RESOURCE(L"MissingRequiredParameter"),
@@ -1049,6 +1048,11 @@ namespace winrt::TerminalApp::implementation
// (or called TerminalWindow::Initialize)
if (_appArgs->ExitCode() == 0)
{
// The existing logic (before this commit) strictly relied on
// ValidateStartupCommands() only to be called for new windows.
// It modifies the actions it stores.
parsedArgs.ValidateStartupCommands();
// If the size of the arguments list is 1,
// then it contains only the executable name and no other arguments.
_hasCommandLineArguments = _appArgs->CommandlineRef().size() > 1;

View File

@@ -210,7 +210,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, Initialized);
FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent);
FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged);
FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, TitleChanged);
FORWARDED_TYPED_EVENT(CloseWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, CloseWindowRequested);
FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged);
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);

View File

@@ -112,7 +112,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> Initialized;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.Theme> RequestedThemeChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;

View File

@@ -16,6 +16,7 @@
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalMUX>true</TerminalMUX>
<TerminalThemeHelpers>true</TerminalThemeHelpers>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />

View File

@@ -30,8 +30,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
// Function Description:
// - launches the client application attached to the new pseudoconsole
HRESULT ConptyConnection::_LaunchAttachedClient() noexcept
try
void ConptyConnection::_LaunchAttachedClient()
{
STARTUPINFOEX siEx{ 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
@@ -43,15 +42,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto attrList{ std::make_unique<std::byte[]>(size) };
#pragma warning(suppress : 26490) // We have to use reinterpret_cast because we allocated a byte array as a proxy for the adjustable size list.
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList.get());
RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size));
THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size));
RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
_hPC.get(),
sizeof(HPCON),
nullptr,
nullptr));
THROW_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(
siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
_hPC.get(),
sizeof(HPCON),
nullptr,
nullptr));
auto cmdline{ wil::ExpandEnvironmentStringsW<std::wstring>(_commandline.c_str()) }; // mutable copy -- required for CreateProcessW
auto environment = _initialEnv;
@@ -72,7 +72,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
std::wstring additionalWslEnv;
// WSLENV.2: Figure out what variables are already in WSLENV.
std::unordered_set<std::wstring_view> wslEnvVars;
std::unordered_set<std::wstring> wslEnvVars{
// We never want to put a custom Windows PATH variable into WSLENV,
// because that would override WSL's computation of the NIX PATH.
L"PATH",
};
for (const auto& part : til::split_iterator{ std::wstring_view{ wslEnv }, L':' })
{
// Each part may contain a variable name and flags (e.g., /p, /l, etc.)
@@ -97,25 +101,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
// add additional env vars
if (_environment)
{
// Order the environment variable names so that resolution order is consistent
// NOTE(lhecker): I'm like 99% sure that this is unnecessary.
std::set<std::wstring, til::env_key_sorter> keys{};
for (const auto item : _environment)
{
keys.insert(std::wstring{ item.Key() });
}
// add additional env vars
for (const auto& key : keys)
{
try
{
const auto key = item.Key();
// This will throw if the value isn't a string. If that
// happens, then just skip this entry.
const auto value = winrt::unbox_value<hstring>(_environment.Lookup(key));
environment.set_user_environment_var(key.c_str(), value.c_str());
environment.set_user_environment_var(key, value);
// WSLENV.4: Add custom user environment variables to WSLENV.
if (wslEnvVars.emplace(key).second)
@@ -166,7 +164,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto [newCommandLine, newStartingDirectory] = Utils::MangleStartingDirectoryForWSL(cmdline, _startingDirectory);
const auto startingDirectory = newStartingDirectory.size() > 0 ? newStartingDirectory.c_str() : nullptr;
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(
nullptr,
newCommandLine.data(),
nullptr, // lpProcessAttributes
@@ -193,10 +191,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
return S_OK;
}
CATCH_RETURN();
// Who decided that?
#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6).
@@ -345,7 +340,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
auto ownedSignal = duplicateHandle(signal);
auto ownedReference = duplicateHandle(reference);
auto ownedServer = duplicateHandle(server);
auto ownedClient = duplicateHandle(client);
wil::unique_hfile ownedClient;
LOG_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), client, GetCurrentProcess(), ownedClient.addressof(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_SET_INFORMATION | SYNCHRONIZE, FALSE, 0));
if (!ownedClient)
{
// If we couldn't reopen the handle with SET_INFORMATION, which may be required to do things like QoS management, fall back.
ownedClient = duplicateHandle(client);
}
THROW_IF_FAILED(ConptyPackPseudoConsole(ownedServer.get(), ownedReference.get(), ownedSignal.get(), &_hPC));
ownedServer.release();
@@ -418,7 +419,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
THROW_IF_FAILED(ConptyShowHidePseudoConsole(_hPC.get(), _initialVisibility));
}
THROW_IF_FAILED(_LaunchAttachedClient());
_LaunchAttachedClient();
}
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
// window is expecting it to be on the first layout.
@@ -538,6 +539,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
DWORD exitCode{ 0 };
GetExitCodeProcess(_piClient.hProcess, &exitCode);
_piClient.reset();
// Signal the closing or failure of the process.
// exitCode might be STILL_ACTIVE if a client has called FreeConsole() and
// thus caused the tab to close, even though the CLI app is still running.
@@ -654,6 +657,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
uint64_t ConptyConnection::RootProcessHandle() noexcept
{
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
return reinterpret_cast<uint64_t>(_piClient.hProcess);
}
void ConptyConnection::Close() noexcept
try
{

View File

@@ -30,6 +30,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ShowHide(const bool show);
void ReparentWindow(const uint64_t newParent);
uint64_t RootProcessHandle() noexcept;
winrt::hstring Commandline() const;
winrt::hstring StartingTitle() const;
@@ -58,7 +59,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
static HRESULT NewHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) noexcept;
static winrt::hstring _commandlineFromProcess(HANDLE process);
HRESULT _LaunchAttachedClient() noexcept;
void _LaunchAttachedClient();
void _indicateExitWithStatus(unsigned int status) noexcept;
static std::wstring _formatStatus(uint32_t status);
void _LastConPtyClientDisconnected() noexcept;

View File

@@ -21,6 +21,8 @@ namespace Microsoft.Terminal.TerminalConnection
void ReparentWindow(UInt64 newParent);
UInt64 RootProcessHandle();
static event NewConnectionHandler NewConnection;
static void StartInboundListener();

View File

@@ -104,8 +104,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// GH#8969: pre-seed working directory to prevent potential races
_terminal->SetWorkingDirectory(_settings->StartingDirectory());
auto pfnCopyToClipboard = [this](auto&& PH1) { _terminalCopyToClipboard(std::forward<decltype(PH1)>(PH1)); };
_terminal->SetCopyToClipboardCallback(pfnCopyToClipboard);
_terminal->SetCopyToClipboardCallback([this](wil::zwstring_view wstr) {
WriteToClipboard.raise(*this, winrt::make<WriteToClipboardEventArgs>(winrt::hstring{ std::wstring_view{ wstr } }, std::string{}, std::string{}));
});
auto pfnWarningBell = [this] { _terminalWarningBell(); };
_terminal->SetWarningBellCallback(pfnWarningBell);
@@ -174,8 +175,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
//
// NOTE: Calling UpdatePatternLocations from a background
// thread is a workaround for us to hit GH#12607 less often.
shared->outputIdle = std::make_unique<til::debounced_func_trailing<>>(
std::chrono::milliseconds{ 100 },
shared->outputIdle = std::make_unique<til::throttled_func<>>(
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 100 },
.debounce = true,
.trailing = true,
},
[this, weakThis = get_weak(), dispatcher = _dispatcher]() {
dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() {
if (const auto self = weakThis.get(); self && !self->_IsClosing())
@@ -195,8 +200,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// If you rapidly show/hide Windows Terminal, something about GotFocus()/LostFocus() gets broken.
// We'll then receive easily 10+ such calls from WinUI the next time the application is shown.
shared->focusChanged = std::make_unique<til::debounced_func_trailing<bool>>(
std::chrono::milliseconds{ 25 },
shared->focusChanged = std::make_unique<til::throttled_func<bool>>(
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 25 },
.debounce = true,
.trailing = true,
},
[this](const bool focused) {
// Theoretically `debounced_func_trailing` should call `WaitForThreadpoolTimerCallbacks()`
// with cancel=true on destruction, which should ensure that our use of `this` here is safe.
@@ -204,9 +213,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
});
// Scrollbar updates are also expensive (XAML), so we'll throttle them as well.
shared->updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
shared->updateScrollBar = std::make_shared<ThrottledFunc<Control::ScrollPositionChangedArgs>>(
_dispatcher,
std::chrono::milliseconds{ 8 },
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 8 },
.trailing = true,
},
[weakThis = get_weak()](const auto& update) {
if (auto core{ weakThis.get() }; core && !core->_IsClosing())
{
@@ -588,7 +600,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else if (vkey == VK_RETURN && !mods.IsCtrlPressed() && !mods.IsAltPressed())
{
// [Shift +] Enter --> copy text
CopySelectionToClipboard(mods.IsShiftPressed(), false, nullptr);
CopySelectionToClipboard(mods.IsShiftPressed(), false, _settings->CopyFormatting());
_terminal->ClearSelection();
_updateSelectionUI();
return true;
@@ -1252,89 +1264,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updateSelectionUI();
}
static wil::unique_close_clipboard_call _openClipboard(HWND hwnd)
{
bool success = false;
// OpenClipboard may fail to acquire the internal lock --> retry.
for (DWORD sleep = 10;; sleep *= 2)
{
if (OpenClipboard(hwnd))
{
success = true;
break;
}
// 10 iterations
if (sleep > 10000)
{
break;
}
Sleep(sleep);
}
return wil::unique_close_clipboard_call{ success };
}
static void _copyToClipboard(const UINT format, const void* src, const size_t bytes)
{
wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) };
const auto locked = GlobalLock(handle.get());
memcpy(locked, src, bytes);
GlobalUnlock(handle.get());
THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get()));
handle.release();
}
static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes)
{
const auto id = RegisterClipboardFormatW(format);
if (!id)
{
LOG_LAST_ERROR();
return;
}
_copyToClipboard(id, src, bytes);
}
static void copyToClipboard(wil::zwstring_view text, std::string_view html, std::string_view rtf)
{
const auto clipboard = _openClipboard(nullptr);
if (!clipboard)
{
LOG_LAST_ERROR();
return;
}
EmptyClipboard();
if (!text.empty())
{
// As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
// CF_UNICODETEXT: [...] A null character signals the end of the data.
// --> We add +1 to the length. This works because .c_str() is null-terminated.
_copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
}
if (!html.empty())
{
_copyToClipboardRegisteredFormat(L"HTML Format", html.data(), html.size());
}
if (!rtf.empty())
{
_copyToClipboardRegisteredFormat(L"Rich Text Format", rtf.data(), rtf.size());
}
}
// Called when the Terminal wants to set something to the clipboard, i.e.
// when an OSC 52 is emitted.
void ControlCore::_terminalCopyToClipboard(wil::zwstring_view wstr)
{
copyToClipboard(wstr, {}, {});
}
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
@@ -1345,7 +1274,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// if we should defer which formats are copied to the global setting
bool ControlCore::CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats)
const CopyFormat formats)
{
::Microsoft::Terminal::Core::Terminal::TextCopyData payload;
{
@@ -1357,19 +1286,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return false;
}
// use action's copyFormatting if it's present, else fall back to globally
// set copyFormatting.
const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting();
const auto copyHtml = WI_IsFlagSet(copyFormats, CopyFormat::HTML);
const auto copyRtf = WI_IsFlagSet(copyFormats, CopyFormat::RTF);
const auto copyHtml = WI_IsFlagSet(formats, CopyFormat::HTML);
const auto copyRtf = WI_IsFlagSet(formats, CopyFormat::RTF);
// extract text from buffer
// RetrieveSelectedTextFromBuffer will lock while it's reading
payload = _terminal->RetrieveSelectedTextFromBuffer(singleLine, withControlSequences, copyHtml, copyRtf);
}
copyToClipboard(payload.plainText, payload.html, payload.rtf);
WriteToClipboard.raise(
*this,
winrt::make<WriteToClipboardEventArgs>(
winrt::hstring{ payload.plainText },
std::move(payload.html),
std::move(payload.rtf)));
return true;
}
@@ -2069,7 +1999,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->MultiClickSelection(terminalPosition, mode);
selectionNeedsToBeCopied = true;
}
else if (_settings->RepositionCursorWithMouse()) // This is also mode==Char && !shiftEnabled
else if (_settings->RepositionCursorWithMouse() && !selectionNeedsToBeCopied) // Don't reposition cursor if this is part of a selection operation
{
_repositionCursorWithMouse(terminalPosition);
}
@@ -2757,15 +2687,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
safe_void_coroutine ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson,
unsigned int replaceLength)
void ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength)
{
auto args = winrt::make_self<CompletionsChangedEventArgs>(winrt::hstring{ menuJson },
replaceLength);
co_await winrt::resume_background();
CompletionsChanged.raise(*this, *args);
CompletionsChanged.raise(*this, winrt::make<CompletionsChangedEventArgs>(winrt::hstring{ menuJson }, replaceLength));
}
// Select the region of text between [s.start, s.end), in buffer space

View File

@@ -124,7 +124,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SendInput(std::wstring_view wstr);
void PasteText(const winrt::hstring& hstr);
bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats);
bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const CopyFormat formats);
void SelectAll();
void ClearSelection();
bool ToggleBlockSelection();
@@ -277,6 +277,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::typed_event<IInspectable, Control::FontSizeChangedArgs> FontSizeChanged;
til::typed_event<IInspectable, Control::TitleChangedEventArgs> TitleChanged;
til::typed_event<IInspectable, Control::WriteToClipboardEventArgs> WriteToClipboard;
til::typed_event<> WarningBell;
til::typed_event<> TabColorChanged;
til::typed_event<> BackgroundColorChanged;
@@ -307,9 +308,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
private:
struct SharedState
{
std::unique_ptr<til::debounced_func_trailing<>> outputIdle;
std::unique_ptr<til::debounced_func_trailing<bool>> focusChanged;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
std::unique_ptr<til::throttled_func<>> outputIdle;
std::unique_ptr<til::throttled_func<bool>> focusChanged;
std::shared_ptr<ThrottledFunc<Control::ScrollPositionChangedArgs>> updateScrollBar;
};
void _setupDispatcherAndCallbacks();
@@ -325,7 +326,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _sendInputToConnection(std::wstring_view wstr);
#pragma region TerminalCoreCallbacks
void _terminalCopyToClipboard(wil::zwstring_view wstr);
void _terminalWarningBell();
void _terminalTitleChanged(std::wstring_view wstr);
void _terminalScrollPositionChanged(const int viewTop,
@@ -339,7 +339,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow);
void _terminalWindowSizeChanged(int32_t width, int32_t height);
safe_void_coroutine _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);
void _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);
#pragma endregion
#pragma region RendererCallbacks

View File

@@ -185,6 +185,7 @@ namespace Microsoft.Terminal.Control
// These events are called from some background thread
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, WriteToClipboardEventArgs> WriteToClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;

View File

@@ -199,7 +199,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// if we should defer which formats are copied to the global setting
bool ControlInteractivity::CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats)
const CopyFormat formats)
{
if (_core)
{
@@ -316,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else
{
// Try to copy the text and clear the selection
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, false, nullptr);
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, false, _core->Settings().CopyFormatting());
_core->ClearSelection();
if (_core->CopyOnSelect() || !successfulCopy)
{
@@ -461,7 +461,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// IMPORTANT!
// DO NOT clear the selection here!
// Otherwise, the selection will be cleared immediately after you make it.
CopySelectionToClipboard(false, false, nullptr);
CopySelectionToClipboard(false, false, _core->Settings().CopyFormatting());
}
_singleClickTouchdownPos = std::nullopt;
@@ -485,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event.
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const Core::Point delta,
const Core::Point pixelPosition,
const Control::MouseButtonState buttonState)
{
@@ -506,9 +506,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _sendMouseEventHelper(terminalPosition,
WM_MOUSEWHEEL,
delta.Y != 0 ? WM_MOUSEWHEEL : WM_MOUSEHWHEEL,
modifiers,
::base::saturated_cast<short>(delta),
::base::saturated_cast<short>(delta.Y != 0 ? delta.Y : delta.X),
buttonState);
}
@@ -517,15 +517,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity())
{
_mouseTransparencyHandler(delta);
_mouseTransparencyHandler(delta.Y);
}
else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom())
{
_mouseZoomHandler(delta);
_mouseZoomHandler(delta.Y);
}
else
{
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
_mouseScrollHandler(delta.Y, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
}
return false;
}
@@ -659,7 +659,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core->IsVtMouseModeEnabled();
}
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta)
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta)
{
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
@@ -667,7 +667,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return false;
}
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta);
if (delta.Y != 0)
{
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta.Y);
}
else
{
return _core->ShouldSendAlternateScroll(WM_MOUSEHWHEEL, delta.X);
}
}
// Method Description:

View File

@@ -74,7 +74,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TouchReleased();
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta,
const Core::Point delta,
const Core::Point pixelPosition,
const Control::MouseButtonState state);
@@ -84,7 +84,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats);
const CopyFormat formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(const Core::Point pixelPosition);
@@ -153,7 +153,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _hyperlinkHandler(const std::wstring_view uri);
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta);
til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell);

View File

@@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Control
InteractivityAutomationPeer OnCreateAutomationPeer();
Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference<CopyFormat> formats);
Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, CopyFormat formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point);
@@ -60,7 +60,7 @@ namespace Microsoft.Terminal.Control
void TouchReleased();
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
Int32 delta,
Microsoft.Terminal.Core.Point delta,
Microsoft.Terminal.Core.Point pixelPosition,
MouseButtonState state);

View File

@@ -6,6 +6,7 @@
#include "FontSizeChangedArgs.g.cpp"
#include "TitleChangedEventArgs.g.cpp"
#include "ContextMenuRequestedEventArgs.g.cpp"
#include "WriteToClipboardEventArgs.g.cpp"
#include "PasteFromClipboardEventArgs.g.cpp"
#include "OpenHyperlinkEventArgs.g.cpp"
#include "NoticeEventArgs.g.cpp"

View File

@@ -6,6 +6,7 @@
#include "FontSizeChangedArgs.g.h"
#include "TitleChangedEventArgs.g.h"
#include "ContextMenuRequestedEventArgs.g.h"
#include "WriteToClipboardEventArgs.g.h"
#include "PasteFromClipboardEventArgs.g.h"
#include "OpenHyperlinkEventArgs.g.h"
#include "NoticeEventArgs.g.h"
@@ -56,6 +57,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(winrt::Windows::Foundation::Point, Position);
};
struct WriteToClipboardEventArgs : WriteToClipboardEventArgsT<WriteToClipboardEventArgs>
{
WriteToClipboardEventArgs(winrt::hstring&& plain, std::string&& html, std::string&& rtf) :
_plain(std::move(plain)),
_html(std::move(html)),
_rtf(std::move(rtf))
{
}
winrt::hstring Plain() const noexcept { return _plain; }
winrt::com_array<uint8_t> Html() noexcept { return _cast(_html); }
winrt::com_array<uint8_t> Rtf() noexcept { return _cast(_rtf); }
private:
static winrt::com_array<uint8_t> _cast(const std::string& str)
{
const auto beg = reinterpret_cast<const uint8_t*>(str.data());
const auto len = str.size();
return { beg, beg + len };
}
winrt::hstring _plain;
std::string _html;
std::string _rtf;
};
struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
{
public:

View File

@@ -6,6 +6,7 @@ namespace Microsoft.Terminal.Control
[flags]
enum CopyFormat
{
None = 0x0,
HTML = 0x1,
RTF = 0x2,
All = 0xffffffff
@@ -31,6 +32,13 @@ namespace Microsoft.Terminal.Control
AlphanumericHalfWidth,
};
enum WarnAboutMultiLinePaste
{
Automatic,
Always,
Never,
};
runtimeclass FontSizeChangedArgs
{
Int32 Width { get; };
@@ -47,6 +55,13 @@ namespace Microsoft.Terminal.Control
String Title;
}
runtimeclass WriteToClipboardEventArgs
{
String Plain { get; }; // UTF-16, as required by CF_UNICODETEXT
byte[] Html { get; }; // UTF-8, as required by "HTML Format"
byte[] Rtf { get; }; // UTF-8, as required by "Rich Text Format"
}
runtimeclass PasteFromClipboardEventArgs
{
void HandleClipboardData(String data);

View File

@@ -38,8 +38,6 @@ namespace Microsoft.Terminal.Control
interface IControlSettings requires Microsoft.Terminal.Core.ICoreSettings,
Microsoft.Terminal.Control.IControlAppearance
{
String ProfileName;
Boolean EnableUnfocusedAcrylic { get; };
Guid SessionId { get; };
ScrollbarState ScrollState { get; };

View File

@@ -11,6 +11,6 @@ namespace Microsoft.Terminal.Control
[uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")]
interface IMouseWheelListener
{
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
Boolean OnMouseWheel(Windows.Foundation.Point coord, Microsoft.Terminal.Core.Point delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
}
}

View File

@@ -190,7 +190,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return nullptr;
}
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parent.ProviderFromPeer(parent));
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parent.as<IAutomationPeerProtected>().ProviderFromPeer(parent));
return xutr.as<XamlAutomation::ITextRangeProvider>();
};

View File

@@ -330,6 +330,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested });
_revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand });
_revokers.WindowSizeChanged = _core.WindowSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWindowSizeChanged });
_revokers.WriteToClipboard = _core.WriteToClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWriteToClipboard });
_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });
@@ -358,9 +359,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// These three throttled functions are triggered by terminal output and interact with the UI.
// Since Close() is the point after which we are removed from the UI, but before the
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
_playWarningBell = std::make_shared<ThrottledFuncLeading>(
_playWarningBell = std::make_shared<ThrottledFunc<>>(
dispatcher,
TerminalWarningBellInterval,
til::throttled_func_options{
.delay = TerminalWarningBellInterval,
.leading = true,
},
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
{
@@ -368,9 +372,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
dispatcher,
ScrollBarUpdateInterval,
til::throttled_func_options{
.delay = ScrollBarUpdateInterval,
.trailing = true,
},
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
{
@@ -2157,15 +2164,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
RestorePointerCursor.raise(*this, nullptr);
const auto point = args.GetCurrentPoint(*this);
// GH#10329 - we don't need to handle horizontal scrolls. Only vertical ones.
// So filter out the horizontal ones.
if (point.Properties().IsHorizontalMouseWheel())
{
return;
}
auto delta = point.Properties().MouseWheelDelta();
auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(),
point.Properties().IsHorizontalMouseWheel() ?
Core::Point{ delta, 0 } :
Core::Point{ 0, delta },
_toTerminalOrigin(point.Position()),
TermControl::GetPressedMouseButtons(point));
if (result)
@@ -2185,7 +2188,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - delta: the mouse wheel delta that triggered this event.
// - state: the state for each of the mouse buttons individually (pressed/unpressed)
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
const int32_t delta,
const Core::Point delta,
const bool leftButtonDown,
const bool midButtonDown,
const bool rightButtonDown)
@@ -2603,9 +2606,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.Title();
}
hstring TermControl::GetProfileName() const
hstring TermControl::GetStartingTitle() const
{
return _core.Settings().ProfileName();
return _core.Settings().StartingTitle();
}
hstring TermControl::WorkingDirectory() const
@@ -2628,7 +2631,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats)
bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const CopyFormat formats)
{
if (_IsClosing())
{
@@ -3451,13 +3454,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Switch on the light and animate the intensity to fade out
VisualBellLight::SetIsTarget(RootGrid(), true);
auto compositionLight{ BellLight().as<Windows::UI::Xaml::Media::IXamlLightProtected>().CompositionLight() };
if (_isBackgroundLight)
{
BellLight().CompositionLight().StartAnimation(L"Intensity", _bellDarkAnimation);
compositionLight.StartAnimation(L"Intensity", _bellDarkAnimation);
}
else
{
BellLight().CompositionLight().StartAnimation(L"Intensity", _bellLightAnimation);
compositionLight.StartAnimation(L"Intensity", _bellLightAnimation);
}
}
}
@@ -4147,7 +4151,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const IInspectable& /*args*/)
{
// formats = nullptr -> copy all formats
_interactivity.CopySelectionToClipboard(false, false, nullptr);
_interactivity.CopySelectionToClipboard(false, false, _core.Settings().CopyFormatting());
ContextMenu().Hide();
SelectionContextMenu().Hide();
}

View File

@@ -58,9 +58,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
uint64_t ContentId() const;
hstring GetProfileName() const;
hstring GetStartingTitle() const;
bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats);
bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const CopyFormat formats);
void PasteTextFromClipboard();
void SelectAll();
bool ToggleBlockSelection();
@@ -147,7 +147,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
bool OnMouseWheel(const Windows::Foundation::Point location, const winrt::Microsoft::Terminal::Core::Point delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
~TermControl();
@@ -227,8 +227,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable);
BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(WriteToClipboard, IInspectable, Control::WriteToClipboardEventArgs);
BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
// clang-format on
@@ -284,7 +284,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _quickFixesAvailable{ false };
til::CoordType _quickFixBufferPos{};
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
struct ScrollBarUpdate
{
@@ -294,7 +294,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
double newViewportSize;
};
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
bool _isInternalScrollBarUpdate;
@@ -463,6 +463,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::ControlCore::UpdateSelectionMarkers_revoker UpdateSelectionMarkers;
Control::ControlCore::OpenHyperlink_revoker coreOpenHyperlink;
Control::ControlCore::TitleChanged_revoker TitleChanged;
Control::ControlCore::WriteToClipboard_revoker WriteToClipboard;
Control::ControlCore::TabColorChanged_revoker TabColorChanged;
Control::ControlCore::TaskbarProgressChanged_revoker TaskbarProgressChanged;
Control::ControlCore::ConnectionStateChanged_revoker ConnectionStateChanged;

View File

@@ -53,6 +53,7 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.Control.IControlSettings Settings { get; };
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, WriteToClipboardEventArgs> WriteToClipboard;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
@@ -87,7 +88,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> RestartTerminalRequested;
Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference<CopyFormat> formats);
Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, CopyFormat formats);
void PasteTextFromClipboard();
void SelectAll();
Boolean ToggleBlockSelection();

View File

@@ -308,14 +308,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// fall back to title if profile name is empty
if (auto control{ _termControl.get() })
{
const auto profileName = control->GetProfileName();
if (profileName.empty())
const auto originalName = control->GetStartingTitle();
if (originalName.empty())
{
return control->Title();
}
else
{
return profileName;
return originalName;
}
}

View File

@@ -66,6 +66,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
});
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("actions", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/)

View File

@@ -29,11 +29,27 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AddProfile::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::AddProfilePageNavigationState>();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("addProfile", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
void AddProfile::AddNewClick(const IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs& /*eventArgs*/)
{
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"AddNewProfile",
TraceLoggingDescription("Event emitted when the user adds a new profile"),
TraceLoggingValue("EmptyProfile", "Type", "The type of the creation method (i.e. empty profile, duplicate)"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
_State.RequestAddNew();
}
@@ -42,7 +58,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (const auto selected = Profiles().SelectedItem())
{
_State.RequestDuplicate(selected.try_as<Model::Profile>().Guid());
const auto selectedProfile = selected.as<Model::Profile>();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"AddNewProfile",
TraceLoggingDescription("Event emitted when the user adds a new profile"),
TraceLoggingValue("Duplicate", "Type", "The type of the creation method (i.e. empty profile, duplicate)"),
TraceLoggingValue(!selectedProfile.Source().empty(), "SourceProfileHasSource", "True, if the source profile has a source (i.e. dynamic profile generator namespace, fragment). Otherwise, False, indicating it's based on a custom profile."),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
_State.RequestDuplicate(selectedProfile.Guid());
}
}

View File

@@ -59,7 +59,7 @@
<IconSourceElement Grid.Column="0"
Width="16"
Height="16"
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon.Resolved), Mode=OneTime}" />
<TextBlock Grid.Column="1"
Text="{x:Bind Name}" />

View File

@@ -254,9 +254,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// Cache the original BG image path. If the user clicks "Use desktop
// wallpaper", then un-checks it, this is the string we'll restore to
// them.
if (BackgroundImagePath() != L"desktopWallpaper")
if (BackgroundImagePath().Path() != L"desktopWallpaper")
{
_lastBgImagePath = BackgroundImagePath();
_lastBgImagePath = BackgroundImagePath().Path();
}
}
@@ -913,7 +913,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path)
{
BackgroundImagePath(path);
_appearance.BackgroundImagePath(Model::MediaResourceHelper::FromString(path));
_NotifyChanges(L"BackgroundImagePath");
}
hstring AppearanceViewModel::BackgroundImageAlignmentCurrentValue() const
@@ -956,7 +957,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring AppearanceViewModel::CurrentBackgroundImagePath() const
{
const auto bgImagePath = BackgroundImagePath();
const auto bgImagePath = BackgroundImagePath().Path();
if (bgImagePath.empty())
{
return RS_(L"Appearance_BackgroundImageNone");
@@ -970,7 +971,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool AppearanceViewModel::UseDesktopBGImage() const
{
return BackgroundImagePath() == L"desktopWallpaper";
return BackgroundImagePath().Path() == L"desktopWallpaper";
}
void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop)
@@ -983,23 +984,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
//
// Only stash this value if it's not the special "desktopWallpaper"
// value.
if (BackgroundImagePath() != L"desktopWallpaper")
if (BackgroundImagePath().Path() != L"desktopWallpaper")
{
_lastBgImagePath = BackgroundImagePath();
_lastBgImagePath = BackgroundImagePath().Path();
}
BackgroundImagePath(L"desktopWallpaper");
SetBackgroundImagePath(L"desktopWallpaper");
}
else
{
// Restore the path we had previously cached. This might be the
// empty string.
BackgroundImagePath(_lastBgImagePath);
SetBackgroundImagePath(_lastBgImagePath);
}
}
bool AppearanceViewModel::BackgroundImageSettingsVisible() const
{
return !BackgroundImagePath().empty();
return !BackgroundImagePath().Path().empty();
}
void AppearanceViewModel::ClearColorScheme()
@@ -1424,7 +1425,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
auto file = co_await OpenImagePicker(parentHwnd);
if (!file.empty())
{
Appearance().BackgroundImagePath(file);
Appearance().SetBackgroundImagePath(file);
}
}

View File

@@ -79,7 +79,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, RetroTerminalEffect);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Core.CursorStyle, CursorShape);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(UInt32, CursorHeight);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, BackgroundImagePath);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.IMediaResource, BackgroundImagePath);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, BackgroundImageOpacity);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Xaml.Media.Stretch, BackgroundImageStretchMode);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.ConvergedAlignment, BackgroundImageAlignment);

View File

@@ -68,6 +68,7 @@
<StackPanel Style="{StaticResource PivotStackStyle}">
<!-- Grouping: Text -->
<TextBlock x:Uid="Profile_TextHeader"
Margin="0,0,0,4"
Style="{StaticResource TextBlockSubHeaderStyle}" />
<!-- Color Scheme -->
<!-- This currently only display the Dark color scheme, even if the user has a pair of schemes set. -->
@@ -542,7 +543,7 @@
IsEnabled="{x:Bind mtu:Converters.InvertBoolean(Appearance.UseDesktopBGImage), Mode=OneWay}"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind mtu:Converters.StringOrEmptyIfPlaceholder('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
Text="{x:Bind mtu:Converters.StringOrEmptyIfPlaceholder('desktopWallpaper', Appearance.BackgroundImagePath.Path), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
<StackPanel Orientation="Horizontal">
<Button x:Uid="Profile_BackgroundImageBrowse"
Margin="0,10,10,0"

View File

@@ -44,6 +44,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
ColorSchemeListView().Focus(FocusState::Programmatic);
});
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("colorSchemes", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
void ColorSchemes::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)

View File

@@ -160,6 +160,13 @@
<Setter Property="Margin" Value="0,32,0,4" />
</Style>
<Style x:Key="TextBlockSubtitleStyle"
BasedOn="{StaticResource SubtitleTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="MaxWidth" Value="{StaticResource StandardControlMaxWidth}" />
<Setter Property="Margin" Value="0,32,0,4" />
</Style>
<!-- Used for disclaimers -->
<Style x:Key="DisclaimerStyle"
TargetType="TextBlock">

View File

@@ -25,11 +25,25 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void CompatibilityViewModel::ResetApplicationState()
{
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"ResetApplicationState",
TraceLoggingDescription("Event emitted when the user resets their application state"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
_settings.ResetApplicationState();
}
void CompatibilityViewModel::ResetToDefaultSettings()
{
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"ResetToDefaultSettings",
TraceLoggingDescription("Event emitted when the user resets their settings to their default value"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
_settings.ResetToDefaultSettings();
}
@@ -41,6 +55,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void Compatibility::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::CompatibilityViewModel>();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("compatibility", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
void Compatibility::ResetApplicationStateButton_Click(const Windows::Foundation::IInspectable& /*sender*/, const Windows::UI::Xaml::RoutedEventArgs& /*e*/)

View File

@@ -42,7 +42,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_ViewModel = e.Parameter().as<Editor::ColorSchemeViewModel>();
NameBox().Text(_ViewModel.Name());
const auto schemeName = _ViewModel.Name();
NameBox().Text(schemeName);
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("colorSchemes.editColorScheme", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingValue(schemeName.data(), "SchemeName", "The name of the color scheme that's being edited"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
void EditColorScheme::ColorPickerChanged(const IInspectable& sender,

View File

@@ -20,8 +20,6 @@ using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
static constexpr std::wstring_view ExtensionPageId{ L"page.extensions" };
Extensions::Extensions()
{
InitializeComponent();
@@ -41,6 +39,36 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
vmImpl->ExtensionPackageIdentifierTemplateSelector(_extensionPackageIdentifierTemplateSelector);
vmImpl->LazyLoadExtensions();
vmImpl->MarkAsVisited();
if (vmImpl->IsExtensionView())
{
const auto currentPkgVM = vmImpl->CurrentExtensionPackage();
const auto currentPkg = currentPkgVM.Package();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("extensions.extensionView", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingValue(currentPkg.Source().c_str(), "FragmentSource", "The source of the fragment included in this extension package"),
TraceLoggingValue(currentPkgVM.FragmentExtensions().Size(), "FragmentCount", "The number of fragments included in this extension package"),
TraceLoggingValue(currentPkgVM.Enabled(), "Enabled", "The enabled status of the extension"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
else
{
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("extensions", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingValue(vmImpl->ExtensionPackages().Size(), "ExtensionPackageCount", "The number of extension packages displayed"),
TraceLoggingValue(vmImpl->ProfilesModified().Size(), "ProfilesModifiedCount", "The number of profiles modified by enabled extensions"),
TraceLoggingValue(vmImpl->ProfilesAdded().Size(), "ProfilesAddedCount", "The number of profiles added by enabled extensions"),
TraceLoggingValue(vmImpl->ColorSchemesAdded().Size(), "ColorSchemesAddedCount", "The number of color schemes added by enabled extensions"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
}
void Extensions::ExtensionNavigator_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/)
@@ -338,7 +366,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool ExtensionsViewModel::DisplayBadge() const noexcept
{
return !Model::ApplicationState::SharedInstance().BadgeDismissed(ExtensionPageId);
return !Model::ApplicationState::SharedInstance().BadgeDismissed(L"page.extensions");
}
// Returns true if the extension is enabled, false otherwise
@@ -411,7 +439,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void ExtensionsViewModel::MarkAsVisited()
{
Model::ApplicationState::SharedInstance().DismissBadge(ExtensionPageId);
Model::ApplicationState::SharedInstance().DismissBadge(L"page.extensions");
_NotifyChanges(L"DisplayBadge");
}

View File

@@ -217,7 +217,7 @@
<IconSourceElement Width="16"
Height="16"
Margin="0,0,12,0"
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.EvaluatedIcon), Mode=OneWay}" />
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.Icon.Resolved), Mode=OneWay}" />
<TextBlock Text="{x:Bind Profile.Name, Mode=OneWay}" />

View File

@@ -25,5 +25,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void GlobalAppearance::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::GlobalAppearanceViewModel>();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("globalAppearance", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
}

View File

@@ -5,6 +5,8 @@
#include "Interaction.h"
#include "Interaction.g.cpp"
#include "EnumEntry.h"
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Microsoft::Terminal::Settings::Model;
@@ -13,10 +15,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Interaction::Interaction()
{
InitializeComponent();
INITIALIZE_BINDABLE_ENUM_SETTING(WarnAboutMultiLinePaste, WarnAboutMultiLinePaste, winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste, L"Globals_WarnAboutMultiLinePaste", L"Content");
}
void Interaction::OnNavigatedTo(const NavigationEventArgs& e)
{
_ViewModel = e.Parameter().as<Editor::InteractionViewModel>();
TraceLoggingWrite(
g_hTerminalSettingsEditorProvider,
"NavigatedToPage",
TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"),
TraceLoggingValue("interaction", "PageId", "The identifier of the page that was navigated to"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
}
}

View File

@@ -16,6 +16,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
til::property_changed_event PropertyChanged;
WINRT_OBSERVABLE_PROPERTY(Editor::InteractionViewModel, ViewModel, PropertyChanged.raise, nullptr);
GETSET_BINDABLE_ENUM_SETTING(WarnAboutMultiLinePaste, winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste, ViewModel().WarnAboutMultiLinePaste);
};
}

View File

@@ -9,5 +9,8 @@ namespace Microsoft.Terminal.Settings.Editor
{
Interaction();
InteractionViewModel ViewModel { get; };
IInspectable CurrentWarnAboutMultiLinePaste;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> WarnAboutMultiLinePasteList { get; };
}
}

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