Compare commits

...

37 Commits

Author SHA1 Message Date
Dustin L. Howett
6115cf31f5 Fix build after backport of 1ca0c76bc7 2025-12-16 18:32:35 -06:00
Anastasia Smigaliova
b277e4b515 Fix cursor being hidden when pressing modifier keys (#19473)
Closes #19445

(cherry picked from commit b357de9897)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgirMuw
Service-Version: 1.24
2025-12-16 18:16:06 -06:00
Samuel D. Leslie
8a8d02f7e1 Minor fixes to the settings schema (#19387)
## Summary of the Pull Request
Fixes a couple of minor issues in the settings schema which can result
in erroneous settings validation failures.

## References and Relevant Issues
None

## Detailed Description of the Pull Request / Additional comments
- `answerbackMessage`
  Permit `null` type (corresponds to the default value).
- `compatibility.input.forceVT`
  Add missing setting (previously was `experimental.input.forceVT`).
- `rendering.graphicsAPI`
  Add missing `automatic` enumeration value.
- Mark several settings as deprecated using the same format and direct
the user to the updated settings to use.

## Validation Steps Performed
Tested updated schema against configuration with above settings present.

## PR Checklist
- [X] Schema updated (if necessary)

---------

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
(cherry picked from commit fc2d107897)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgirMyU
Service-Version: 1.24
2025-12-16 18:16:04 -06:00
Dustin L. Howett
1ba28b298f env: don't explode when GetShortPathNameW fails (#19284)
It fails inside app containers (!) such as the one used by LocalTests.

(cherry picked from commit 91c9a14a71)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgirMr8
Service-Version: 1.24
2025-12-16 18:16:03 -06:00
Dustin L. Howett
ba6cd3ebd5 When the renderer fails, try to fall back to D2D + WARP; retry changes (#19636)
This commit also ups the number of render failures that are permissible
to 6 (one try plus 5 retries), and moves us to use an exponential
backoff rather than a simple geometric one.

It also suppresses the dialog box in case of present failures for Stable
users. I feel like the warning dialog should be used for something that
the user can actually do something about...

Closes #15601
Closes #18198

(cherry picked from commit 45c5370271)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgiXh2E
Service-Version: 1.24
2025-12-09 17:07:00 -06:00
SEt
5c543bb2be Gracefully handle unavailable TSF from SYSTEM account (#19635)
When run from SYSTEM account TSF seems to be unavailable. The only
missing step to handle that is check during initialization.

Not sure if fail after partial success in `Implementation::Initialize`
should also be gracefully handled.

Closes #19634

(cherry picked from commit 8bb831f628)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgiXm14
Service-Version: 1.24
2025-12-09 17:04:58 -06:00
Dustin L. Howett
8b07837fdd Disallow fragments from containing UNC media paths (#19615)
Fragments are not allowed to declare web-source icons; this is equally
true for UNC paths in the local network (or WebDAV paths!)

(cherry picked from commit 7a7cdec91f)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgiGQDM
Service-Version: 1.24
2025-12-09 11:39:35 -06:00
Windows Console Service Bot
4fd7751d83 Localization Updates - fix ja-jp and fr-fr loc bugs - 12/04/2025 00:46:52 (#19616)
Closes #17191
Closes #18795
Closes #17827

(cherry picked from commit c4668d99b7)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgiGa_k
Service-Version: 1.24
2025-12-09 11:39:33 -06:00
Dustin L. Howett
47da5a11b2 Enable DECRQCRA at compile time for all Terminal builds (#19606)
Support for DECRQCRA Request Checksum of Rectangular Area was added in
#14989, but left disabled at build time because it could be considered a
security risk.

In #17895, we unconditionally added a toggle for it to Terminal's
settings UI and settings schema (`compatibility.allowDECRQCRA`). For
users on Stable and Preview, it didn't actually enable anything. Whoops.

Since we have a way to turn it off (and in so doing, mitigate the risk)
in Terminal, it's high time for us to remove the feature gating.

Conhost doesn't support turning it off for now and so conhost can still
have it compiled out, as a treat.

(cherry picked from commit 3e29d2a316)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgiA9JY
Service-Version: 1.24
2025-12-09 11:29:20 -06:00
penguin-sophist
08a981d7b7 Fix negative delta scroll (#19573)
This fixes the sign extension from 16 to 32 bit by casting
from the unsigned to the signed type first.

Closes #19391
Closes #19484

(cherry picked from commit fb75fb56c0)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzghttY4
Service-Version: 1.24
2025-12-09 11:29:18 -06:00
Leonard Hecker
7e69a2e96f Fix a WPF<>TSF crash by avoiding TF_TMAE_CONSOLE (#19584)
As explained in detail in the diff.

Closes #19562

(cherry picked from commit 1ca0c76bc7)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzghp8js
Service-Version: 1.24
2025-11-24 12:50:58 -06:00
Yash kumar kasaudhan
5205575d4d Fix #19390: Make snap-on-input conditional to prevent blocking programmatic scroll (#19414)
## Summary of the Pull Request
This PR fixes a bug where programmatic scrolling would get stuck. The
fix makes the "snap-on-input" feature conditional, activating it only
for modern applications that use Virtual Terminal (VT) processing. This
restores correct scrolling behavior for legacy applications without
removing the feature for new ones.

## References and Relevant Issues
Fixes #19390: OpenConsole: Cursor visibility prevents programmatic
scrolling

## Detailed Description of the Pull Request / Additional comments
The "snap-on-input" feature introduced in a previous PR caused an
unintended side effect for older console programs that use the
SetConsoleWindowInfo API to manage their own viewport. When such a
program tried to scroll using a key press, the snap feature would
immediately pull the view back to the cursor's position, causing the
screen to flicker and get stuck.

This fix makes the snap-on-input feature smarter by checking the
application's mode first.

## Validation Steps Performed

Compiled the minimal C++ reproduction case from issue #19390.

Ran the test executable inside the newly built OpenConsole.exe.

Confirmed that scrolling with the Up/Down arrow keys now works
correctly, even with a visible cursor. The view no longer flickers or
gets stuck when the cursor moves outside the viewport.

Closes #19390

(cherry picked from commit c28610d016)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfZNLg
Service-Version: 1.24
2025-11-24 12:50:57 -06:00
Windows Console Service Bot
7a34181fc7 Localization Updates - main - 11/07/2025 03:05:02 (#19532)
It said the equivalent of "Nickname" rather than "aliased".

Co-authored-by: Console Service Bot <consvc@microsoft.com>
(cherry picked from commit 965a121a38)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzghDyBA
Service-Version: 1.24
2025-11-24 12:50:55 -06:00
Leonard Hecker
b38489b67b Allow creating new windows on another virtual desktop (#19458)
Whoops. Closes #18652

<DHowett> I chatted with Leonard to figure out why I kept
misunderstanding this PR. The key is that **this function should not
always return an existing window.** It's supposed to find an existing
window on the current virtual desktop, not literally any window
anywhere.

(cherry picked from commit 5ae95d7df0)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgf7tqY
Service-Version: 1.24
2025-11-24 12:50:54 -06:00
Dustin L. Howett
d25b84d4c1 Make sure UIHelpers' resources are included in the final PRI file (#19461)
OCWildcardResource requires the inclusion of a magic MSBuild target that
we wrote. We forgot to include it here. Oops.

Closes #19444

(cherry picked from commit 0e3d136dad)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgf7udM
Service-Version: 1.24
2025-11-24 12:50:53 -06:00
Windows Console Service Bot
9ab1b7ba3b Localization Updates - main - 10/15/2025 02:48:19 (#19451)
Closes #18849
Closes #19412

Co-authored-by: Console Service Bot <consvc@microsoft.com>
(cherry picked from commit 7f6ab5520e)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgf5KOs
Service-Version: 1.24
2025-11-24 12:50:51 -06:00
Carlos Zamora
d35bc4cee6 Update WinGet CNF for exact command matching (#19432)
## Summary of the Pull Request
Update the WinGet CNF package search to match that of the updated
PowerShell WinGet CNF module. Now, we'll only search for matching
commands instead of by name and moniker.

## References and Relevant Issues
https://github.com/microsoft/winget-command-not-found/pull/29

## Validation Steps Performed
 In CMD, type "vim" and vim packages are suggested

(cherry picked from commit 819987c90e)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfuACY
Service-Version: 1.24
2025-11-24 12:50:50 -06:00
Carlos Zamora
77ee52b872 Fix unfocusedBackground being used as active tab color (#19424)
## Summary of the Pull Request
Turns out that the `"TabViewItemHeaderBackground"` resource should be
set to the _selected_ color instead of the _deselected_ color.

In 1.22, (pre-#18109) we actually didn't set this resource. But we do
actually need it for high contrast mode! (verified)

## Validation Steps Performed
 High contrast mode looks right
 "Snazzy" theme from bug report looks right

## PR Checklist
 Closes #19343

(cherry picked from commit b62cad640b)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfrGvI
Service-Version: 1.24
2025-11-24 12:50:48 -06:00
Dustin L. Howett
671552671d Use the actual process image instead of module filename to dedup session (#19415)
Apparently, `GetModuleFileNameW` returns exactly the path (or prefix, in
case of a DLL) passed to `CreateProcess` casing and all. Since we were
using it to generate the uniquing hash for Portable and Unpackaged
instances, this meant that `C:\Terminal\wt` and `C:\TeRmInAl\wt` were
considered different instances. Whoops.

Using `QueryFullProcessImageNameW` instead results in canonicalization.
Maybe the kernel does it. I don't know. What I do know is that it works
more correctly.

(`Query...` goes through the kernel, while `GetModule...` goes through
the loader. Interesting!)

Closes #19253

(cherry picked from commit 9d7ea77cc8)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfkTuo
Service-Version: 1.24
2025-10-08 17:55:14 -05:00
PankajBhojwani
58a5088735 Only do "keys" fixups for non-nested, non-iterable commands (#19408)
## Summary of the Pull Request
When we introduced action IDs, we separated "commands" from
"keybindings", and introduced fixup logic to rewrite the legacy-style
command blocks into the new version. However we don't do any ID logic
for nested and iterable commands, so make sure we don't inform the
loader for fixups in those cases.

## Validation Steps Performed
We no longer repeatedly attempt to fixup the settings file when we see a
`"keys"` entry in a nested/iterable command block

## PR Checklist
- [x] Closes #18736

(cherry picked from commit 04676bd31a)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfjzjQ
Service-Version: 1.24
2025-10-08 17:55:13 -05:00
Dustin L. Howett
c5f99b69d5 Remove the leading fire from the taskbar progress handler (#19403)
If the progress state hasn't been set for more than 200ms, we shouldn't
even bother flickering the old state.

This prevents applications from making the tab (and the taskbar icon)
flicker.

We were reviewing #19394 and decided that the _original_ behavior before
Leonard's throttling fix was somewhat unfortunate as well. An
application that sets an indeterminate state for 10ms and then clears it
shouldn't be able to make any part of the application flicker, fast _or_
slow.

Removing the leading fire time from the throttled function ensures that
it will only fire once every 200ms, and only with the state most
recently set. It will not debounce (so setting the progress every 150ms
will not prevent it from updating.)

Closes #19394

(cherry picked from commit 998ab586e1)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfZOsU PVTI_lADOAF3p4s4BBcTlzgfb1NE
Service-Version: 1.24
2025-10-08 17:55:11 -05:00
Dustin L. Howett
6f5b293a56 Avoid reentrancy issues when dropping AppHost, even harder (#19395)
The previous fix in #19296 moved the _destruction_ of AppHost into the
tail end after we manipulate the `_windows` vector; however, it kept the
part which calls into XAML (`Close`) before the `erase`. I suspect that
we still had some reentrancy issues, where we cached an iterator before
the list was modified by another window close event.

That is:

```mermaid
sequenceDiagram
		Emperor->>Emperor: Close Window
		Emperor->>+AppHost: Close (a)
		AppHost->>XAML: Close
		XAML-->>Emperor: pump loop
		Emperor->>Emperor: Close Window
		Emperor->>+AppHost: Close (b)
		AppHost->>XAML: Close
		XAML-->>Emperor: pump loop
		AppHost->>-Emperor: Closed
		Emperor->>Emperor: erase(b)
		AppHost->>-Emperor: Closed
		Emperor->>Emperor: erase(a)
```

Moving the `Close()` to after the `erase` ensures that there are no
cached iterators that survive beyond XAML pumping the message loop.

Fixes 8d41ace3

(cherry picked from commit 5976de1600)
Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgfScpM
Service-Version: 1.24
2025-10-08 17:55:10 -05:00
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
75 changed files with 901 additions and 498 deletions

View File

@@ -178,6 +178,7 @@ ubrk
UChar
UFIELD
ULARGE
UNCEx
UOI
UPDATEINIFILE
urlmon

View File

@@ -1091,6 +1091,8 @@ NEXTLINE
nfe
NLSMODE
NOACTIVATE
NOACTIVATEKEYBOARDLAYOUT
NOACTIVATETIP
NOAPPLYNOW
NOCLIP
NOCOMM
@@ -1505,7 +1507,6 @@ scrolllock
scrolloffset
SCROLLSCALE
SCROLLSCREENBUFFER
scursor
sddl
SDKDDK
segfault
@@ -1703,9 +1704,11 @@ TEXTMETRIC
TEXTMETRICW
textmode
texttests
TFCAT
THUMBPOSITION
THUMBTRACK
tilunittests
TIPCAP
titlebars
TITLEISLINKNAME
TLDP
@@ -1757,6 +1760,8 @@ UIACCESS
uiacore
uiautomationcore
uielem
UIELEMENTENABLED
UIELEMENTENABLEDONLY
UINTs
uld
uldash

View File

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

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

@@ -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

@@ -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

@@ -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",
@@ -2459,6 +2466,7 @@
"description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.",
"type": "string",
"enum": [
"automatic",
"direct2d",
"direct3d11"
]
@@ -2472,8 +2480,9 @@
"type": "boolean"
},
"experimental.input.forceVT": {
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
"type": "boolean"
"type": "boolean",
"description": "[Deprecated] Replaced with the \"compatibility.input.forceVT\" profile setting.",
"deprecated": true
},
"experimental.useBackgroundImageForWindow": {
"default": false,
@@ -2520,14 +2529,14 @@
"type": "string"
},
"rowsToScroll": {
"default": "system",
"description": "This parameter once allowed you to override the systemwide \"choose how many lines to scroll at one time\" setting. It no longer does so. However, you can customize the number of lines to scroll in \"scrollUp\" and \"scrollDown\" bindings.",
"maximum": 999,
"minimum": 0,
"type": [
"integer",
"string"
],
"description": "[Deprecated] This setting no longer has any effect. However, you can customize the number of lines to scroll using the \"rowsToScroll\" argument on the \"scrollUp\" and \"scrollDown\" actions.",
"default": "system",
"minimum": 0,
"maximum": 999,
"deprecated": true
},
"minimizeToNotificationArea": {
@@ -2573,9 +2582,12 @@
"$ref": "#/$defs/NewTabMenu"
},
"language": {
"default": "",
"type": [
"string",
"null"
],
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
"type": "string"
"default": null
},
"theme": {
"default": "dark",
@@ -2639,19 +2651,19 @@
"type": "boolean"
},
"useTabSwitcher": {
"description": "[Deprecated] Replaced with the \"tabSwitcherMode\" setting.",
"default": true,
"description": "Deprecated. Please use \"tabSwitcherMode\" instead.",
"oneOf": [
{
"type": "boolean"
},
{
"type": "string",
"enum": [
"mru",
"inOrder",
"disabled"
],
"type": "string"
]
}
],
"deprecated": true
@@ -2708,12 +2720,12 @@
"type": "object",
"properties": {
"acrylicOpacity": {
"type": "number",
"description": "[Deprecated] Replaced with the \"opacity\" setting.",
"default": 0.5,
"description": "[deprecated] Please use `opacity` instead.",
"deprecated": true,
"maximum": 1,
"minimum": 0,
"type": "number"
"maximum": 1,
"deprecated": true
},
"antialiasingMode": {
"default": "grayscale",
@@ -2739,6 +2751,11 @@
"description": "When set to true, when opening a new tab or pane it will get reloaded environment variables.",
"type": "boolean"
},
"compatibility.input.forceVT": {
"default": false,
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
"type": "boolean"
},
"compatibility.allowDECRQCRA": {
"default": false,
"description": "When set to true, the terminal will support the DECRQCRA (Request Checksum of Rectangular Area) escape sequence.",
@@ -2910,24 +2927,29 @@
"type": "boolean"
},
"experimental.autoMarkPrompts": {
"deprecated": true,
"description": "This has been replaced by autoMarkPrompts in 1.21",
"type": "boolean"
"type": "boolean",
"description": "[Deprecated] Replaced with the \"autoMarkPrompts\" setting.",
"deprecated": true
},
"experimental.retroTerminalEffect": {
"description": "When set to true, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "boolean"
},
"experimental.showMarksOnScrollbar": {
"deprecated": true,
"description": "This has been replaced by showMarksOnScrollbar in 1.21",
"type": "boolean"
"type": "boolean",
"description": "[Deprecated] Replaced with the \"showMarksOnScrollbar\" setting.",
"deprecated": true
},
"showMarksOnScrollbar": {
"default": false,
"description": "When set to true, marks added to the buffer via the addMark action will appear on the scrollbar.",
"type": "boolean"
},
"experimental.rainbowSuggestions": {
"type": "boolean",
"description": "Enables displaying command suggestions in the terminal in RGB (all the colors of the rainbow!).",
"default": false
},
"experimental.rightClickContextMenu": {
"default": false,
"description": "When true, right-click shows a context menu; otherwise, it pastes from the clipboard or copies selection.",
@@ -2943,23 +2965,29 @@
"type": "string"
},
"fontFace": {
"default": "Cascadia Mono",
"description": "[deprecated] Define 'face' within the 'font' object instead.",
"type": "string",
"description": "[Deprecated] Replaced with the \"face\" setting within the \"font\" object.",
"default": "Cascadia Mono",
"deprecated": true
},
"fontSize": {
"default": 12,
"description": "[deprecated] Define 'size' within the 'font' object instead.",
"minimum": 1,
"type": "number",
"description": "[Deprecated] Replaced with the \"size\" setting within the \"font\" object.",
"default": 12,
"minimum": 1,
"deprecated": true
},
"fontWeight": {
"description": "[Deprecated] Replaced with the \"weight\" setting within the \"font\" object.",
"default": "normal",
"description": "[deprecated] Define 'weight' within the 'font' object instead.",
"oneOf": [
{
"type": "integer",
"minimum": 100,
"maximum": 990
},
{
"type": "string",
"enum": [
"thin",
"extra-light",
@@ -2972,13 +3000,7 @@
"extra-bold",
"black",
"extra-black"
],
"type": "string"
},
{
"maximum": 990,
"minimum": 100,
"type": "integer"
]
}
],
"deprecated": true
@@ -3089,7 +3111,10 @@
},
"answerbackMessage": {
"description": "The response that is sent when an ENQ control character is received.",
"type": "string"
"type": [
"string",
"null"
]
},
"source": {
"description": "Stores the name of the profile generator that originated this profile.",

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

@@ -887,7 +887,7 @@
<value>Déplacer l'onglet vers une nouvelle fenêtre </value>
</data>
<data name="RunAsAdminFlyout.Text" xml:space="preserve">
<value>Exécuter en temps qu'administrateur (restreint)</value>
<value>Exécuter en tant qu'administrateur (restreint)</value>
<comment>This text is displayed on context menu for profile entries in add new tab button.</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_ExistingTab" xml:space="preserve">

View File

@@ -185,7 +185,7 @@
<value>終了</value>
</data>
<data name="MultiplePanes" xml:space="preserve">
<value>複数ウィンドウ</value>
<value>複数ペイン</value>
</data>
<data name="TabCloseSubMenu" xml:space="preserve">
<value>閉じる</value>
@@ -203,13 +203,13 @@
<value>タブを閉じる</value>
</data>
<data name="PaneClose" xml:space="preserve">
<value>ウィンドウを閉じる</value>
<value>ペインを閉じる</value>
</data>
<data name="SplitTabText" xml:space="preserve">
<value>タブを分割</value>
</data>
<data name="SplitPaneText" xml:space="preserve">
<value>ウィンドウを分割する</value>
<value>ペインを分割する</value>
</data>
<data name="SplitPaneToolTipText" xml:space="preserve">
<value>分割する方向を右クリック - 右/下/上/左</value>
@@ -230,7 +230,7 @@
<value>複製</value>
</data>
<data name="SwapPaneText" xml:space="preserve">
<value>ウィンドウを入れ替える</value>
<value>ペインを入れ替える</value>
</data>
<data name="SwapPaneDownText" xml:space="preserve">
<value>下とペインを入れ替え</value>
@@ -245,10 +245,10 @@
<value>左とペインを入れ替え</value>
</data>
<data name="TogglePaneZoomText" xml:space="preserve">
<value>ウィンドウのズームの切り替え</value>
<value>ペインのズームの切り替え</value>
</data>
<data name="CloseOtherPanesText" xml:space="preserve">
<value>他のウィンドウを閉じる</value>
<value>他のペインを閉じる</value>
</data>
<data name="SearchWebText" xml:space="preserve">
<value>Web 検索</value>
@@ -317,7 +317,7 @@
<value>同じ名前ですが大文字と小文字が異なる環境変数が複数見つかりました。使用される値は 1 つだけです。</value>
</data>
<data name="CmdCommandArgDesc" xml:space="preserve">
<value>新しいタブまたはウィンドウで生成されるオプションのコマンドと引数</value>
<value>新しいタブまたはペインで生成されるオプションのコマンドと引数</value>
</data>
<data name="SaveSnippetDesc" xml:space="preserve">
<value>コマンド ラインを入力アクションとして保存する</value>
@@ -345,17 +345,17 @@
<value>指定されたインデックスのタブにフォーカスを移動する</value>
</data>
<data name="CmdMovePaneTabArgDesc" xml:space="preserve">
<value>フォーカスされたウィンドウを指定されたインデックスのタブに移動する</value>
<value>フォーカスされたペインを指定されたインデックスのタブに移動する</value>
</data>
<data name="CmdMovePaneDesc" xml:space="preserve">
<value>フォーカスされたウィンドウを別のタブに移動する</value>
<value>フォーカスされたペインを別のタブに移動する</value>
</data>
<data name="CmdMPDesc" xml:space="preserve">
<value>"move-pane" サブコマンドのエイリアス。</value>
<comment>{Locked="\"move-pane\""}</comment>
</data>
<data name="CmdSplitPaneSizeArgDesc" xml:space="preserve">
<value>サイズを親ウィンドウに対する割合として指定します。有効な値は (0,1) の間で、排他的です。</value>
<value>サイズを親ペインに対する割合として指定します。有効な値は (0,1) の間で、排他的です。</value>
</data>
<data name="CmdNewTabDesc" xml:space="preserve">
<value>新しいタブの作成</value>
@@ -365,14 +365,14 @@
<comment>{Locked="\"new-tab\""}</comment>
</data>
<data name="CmdFocusPaneDesc" xml:space="preserve">
<value>別のウィンドウにフォーカスを移動する</value>
<value>別のペインにフォーカスを移動する</value>
</data>
<data name="CmdFPDesc" xml:space="preserve">
<value>"focus-pane" サブコマンドのエイリアス。</value>
<comment>{Locked="\"focus-pane\""}</comment>
</data>
<data name="CmdFocusPaneTargetArgDesc" xml:space="preserve">
<value>指定されたインデックスのウィンドウをフォーカスする</value>
<value>指定されたインデックスのペインをフォーカスする</value>
</data>
<data name="CmdProfileArgDesc" xml:space="preserve">
<value>指定されたプロファイルで開きます。プロファイルの名前または GUID を指定できます</value>
@@ -381,20 +381,20 @@
<value>WT_SESSION 変数を設定します; GUID である必要があります</value>
</data>
<data name="CmdSplitPaneDesc" xml:space="preserve">
<value>新しい分割ウィンドウの作成</value>
<value>新しい分割ペインの作成</value>
</data>
<data name="CmdSPDesc" xml:space="preserve">
<value>"split-pane" サブコマンドのエイリアス。</value>
<comment>{Locked="\"split-pane\""}</comment>
</data>
<data name="CmdSplitPaneHorizontalArgDesc" xml:space="preserve">
<value>新しいウィンドウを水平方向に分割して作成します ([-]と考えてください)</value>
<value>新しいペインを水平方向に分割して作成します ([-]と考えてください)</value>
</data>
<data name="CmdSplitPaneVerticalArgDesc" xml:space="preserve">
<value>新しいウィンドウを垂直方向に分割して作成します ([|]と考えてください)</value>
<value>新しいペインを垂直方向に分割して作成します ([|]と考えてください)</value>
</data>
<data name="CmdSplitPaneDuplicateArgDesc" xml:space="preserve">
<value>フォーカスされたウィンドウのプロファイルを複製して、新しいウィンドウを作成します</value>
<value>フォーカスされたペインのプロファイルを複製して、新しいペインを作成します</value>
</data>
<data name="CmdStartingDirArgDesc" xml:space="preserve">
<value>プロファイルの "startingDirectory" の設定ではなく、指定されたディレクトリで開きます</value>
@@ -412,7 +412,7 @@
<comment>{Locked="\"tabTitle\""}</comment>
</data>
<data name="CmdInheritEnvDesc" xml:space="preserve">
<value>新しい環境ブロックを作成するのではなく、新しいタブまたはウィンドウを作成するときに、ターミナル独自の環境変数を継承します。これは、"command" が渡されたときに既定で設定されます。 </value>
<value>新しい環境ブロックを作成するのではなく、新しいタブまたはペインを作成するときに、ターミナル独自の環境変数を継承します。これは、"command" が渡されたときに既定で設定されます。 </value>
<comment>{Locked="\"command\""}</comment>
</data>
<data name="CmdColorSchemeArgDesc" xml:space="preserve">
@@ -429,7 +429,7 @@
<value>ウィンドウを全画面表示モードで起動する</value>
</data>
<data name="CmdMoveFocusDesc" xml:space="preserve">
<value>指定した方向にある隣のウィンドウにフォーカスを移動する</value>
<value>指定した方向にある隣のペインにフォーカスを移動する</value>
</data>
<data name="CmdMFDesc" xml:space="preserve">
<value>"move-focus" サブコマンドのエイリアス。</value>
@@ -439,10 +439,10 @@
<value>フォーカスを移動する方向</value>
</data>
<data name="CmdSwapPaneDesc" xml:space="preserve">
<value>フォーカスされたウィンドウを、指定された方向の隣接するウィンドウと入れ替える</value>
<value>フォーカスされたペインを、指定された方向の隣接するペインと入れ替える</value>
</data>
<data name="CmdSwapPaneDirectionArgDesc" xml:space="preserve">
<value>フォーカスされたウィンドウを移動する方向</value>
<value>フォーカスされたペインを移動する方向</value>
</data>
<data name="CmdFocusDesc" xml:space="preserve">
<value>ウィンドウをフォーカス モードで起動</value>
@@ -857,7 +857,7 @@
<value>設定ページを開きます</value>
</data>
<data name="SplitTabToolTip" xml:space="preserve">
<value>現在のディレクトリのアクティブなプロファイルを使用して新しいウィンドウを開きます</value>
<value>現在のディレクトリのアクティブなプロファイルを使用して新しいペインを開きます</value>
</data>
<data name="TabCloseAfterToolTip" xml:space="preserve">
<value>このタブの右側にあるすべてのタブを閉じます</value>
@@ -872,10 +872,10 @@
<value>なし</value>
</data>
<data name="ClosePaneText" xml:space="preserve">
<value>ウィンドウを閉じる</value>
<value>ペインを閉じる</value>
</data>
<data name="ClosePaneToolTip" xml:space="preserve">
<value>複数のウィンドウが存在する場合は、アクティブなウィンドウを閉じます</value>
<value>複数のペインが存在する場合は、アクティブなペインを閉じます</value>
</data>
<data name="TabColorClearButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.FullDescription" xml:space="preserve">
<value>タブの色をリセット</value>
@@ -892,7 +892,7 @@
<comment>This text is displayed on context menu for profile entries in add new tab button.</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_ExistingTab" xml:space="preserve">
<value>アクティブなウィンドウを [{0}] タブに移動しました</value>
<value>アクティブなペインを [{0}] タブに移動しました</value>
<comment>{Locked="{0}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the tab the pane was moved to.</comment>
</data>
<data name="TerminalPage_TabMovedAnnouncement_Default" xml:space="preserve">
@@ -908,19 +908,19 @@
<comment>{Locked="{0}"}{Locked="{1}"}This text is read out by screen readers upon a successful tab movement. {0} is the name of the tab. {1} is the new tab index in the tab row.</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_ExistingWindow" xml:space="preserve">
<value>アクティブなウィンドウを "{1}" ウィンドウの [{0}] タブに移動しました</value>
<value>アクティブなペインを "{1}" ウィンドウの [{0}] タブに移動しました</value>
<comment>{Locked="{0}"}{Locked="{1}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the tab the pane was moved to. {1} is the name of the window the pane was moved to. Replaced in 1.19 by TerminalPage_PaneMovedAnnouncement_ExistingWindow2</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_ExistingWindow2" xml:space="preserve">
<value>アクティブなウィンドウを "{0}" ウィンドウに移動しました</value>
<value>アクティブなペインを "{0}" ウィンドウに移動しました</value>
<comment>{Locked="{0}"}This text is read out by screen readers upon a successful pane movement. {0} is the name of the window the pane was moved to.</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_NewWindow" xml:space="preserve">
<value>アクティブなウィンドウを新しいウィンドウに移動しました</value>
<value>アクティブなペインを新しいウィンドウに移動しました</value>
<comment>This text is read out by screen readers upon a successful pane movement to a new window.</comment>
</data>
<data name="TerminalPage_PaneMovedAnnouncement_NewTab" xml:space="preserve">
<value>アクティブなウィンドウを新しいタブに移動しました</value>
<value>アクティブなペインを新しいタブに移動しました</value>
<comment>This text is read out by screen readers upon a successful pane movement to a new tab within the existing window.</comment>
</data>
<data name="CmdAppendCommandLineDesc" xml:space="preserve">
@@ -930,7 +930,7 @@
<value>接続の再起動</value>
</data>
<data name="RestartConnectionToolTip" xml:space="preserve">
<value>アクティブウィンドウ接続を再起動します</value>
<value>アクティブ ペイン接続を再起動します</value>
</data>
<data name="SnippetPaneTitle.Text" xml:space="preserve">
<value>抜粋</value>
@@ -961,10 +961,10 @@
<value>操作の保存に失敗しました</value>
</data>
<data name="CloseSnippetsPaneButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>ウィンドウを閉じる</value>
<value>ペインを閉じる</value>
</data>
<data name="CloseSnippetsPaneButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>ウィンドウを閉じる</value>
<value>ペインを閉じる</value>
</data>
<data name="TabMoveLeft" xml:space="preserve">
<value>左へ移動</value>

View File

@@ -1062,7 +1062,6 @@ namespace winrt::TerminalApp::implementation
dispatcher,
til::throttled_func_options{
.delay = std::chrono::milliseconds{ 200 },
.leading = true,
.trailing = true,
},
[weakThis]() {
@@ -2418,7 +2417,7 @@ namespace winrt::TerminalApp::implementation
const auto& currentDictionary = v.as<ResourceDictionary>();
// TabViewItem.Background
currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackground"), selectedTabBrush);
currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), isHighContrast ? fontBrush : hoverTabBrush);
currentDictionary.Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);

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.

View File

@@ -60,8 +60,42 @@ namespace winrt
namespace clipboard
{
wil::unique_close_clipboard_call open(HWND hwnd)
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.
@@ -80,7 +114,12 @@ namespace clipboard
Sleep(sleep);
}
return wil::unique_close_clipboard_call{ success };
if (!success)
{
ReleaseSRWLockExclusive(&lock);
}
return ClipboardHandle{ success };
}
void write(wil::zwstring_view text, std::string_view html, std::string_view rtf)
@@ -344,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:
@@ -3175,33 +3216,17 @@ namespace winrt::TerminalApp::implementation
}
PackageCatalog catalog = connectResult.PackageCatalog();
// clang-format off
static constexpr std::array<WinGetSearchParams, 3> searches{ {
{ .Field = PackageMatchField::Command, .MatchOption = PackageFieldMatchOption::StartsWithCaseInsensitive },
{ .Field = PackageMatchField::Name, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive },
{ .Field = PackageMatchField::Moniker, .MatchOption = PackageFieldMatchOption::ContainsCaseInsensitive } } };
// clang-format on
PackageMatchFilter filter = WindowsPackageManagerFactory::CreatePackageMatchFilter();
filter.Value(query);
filter.Field(PackageMatchField::Command);
filter.Option(PackageFieldMatchOption::Equals);
FindPackagesOptions options = WindowsPackageManagerFactory::CreateFindPackagesOptions();
options.Filters().Append(filter);
options.ResultLimit(20);
IVectorView<MatchResult> pkgList;
for (const auto& search : searches)
{
filter.Field(search.Field);
filter.Option(search.MatchOption);
const auto result = co_await catalog.FindPackagesAsync(options);
pkgList = result.Matches();
if (pkgList.Size() > 0)
{
break;
}
}
const auto result = co_await catalog.FindPackagesAsync(options);
const IVectorView<MatchResult> pkgList = result.Matches();
co_return pkgList;
}
@@ -3808,6 +3833,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);

View File

@@ -391,7 +391,7 @@ namespace winrt::TerminalApp::implementation
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);
@@ -435,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

@@ -279,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);
}

View File

@@ -1048,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

@@ -149,12 +149,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); });
_renderer->SetRendererEnteredErrorStateCallback([this]() { RendererEnteredErrorState.raise(nullptr, nullptr); });
_renderer->SetRendererEnteredErrorStateCallback([this]() { _rendererEnteredErrorState(); });
}
UpdateSettings(settings, unfocusedAppearance);
}
void ControlCore::_rendererEnteredErrorState()
{
// The first time the renderer fails out (after all of its own retries), switch it to D2D and WARP
// and force it to try again. If it _still_ fails, we can let it halt.
if (_renderFailures++ == 0)
{
const auto lock = _terminal->LockForWriting();
_renderEngine->SetGraphicsAPI(parseGraphicsAPI(GraphicsAPI::Direct2D));
_renderEngine->SetSoftwareRendering(true);
_renderer->EnablePainting();
return;
}
RendererEnteredErrorState.raise(nullptr, nullptr);
}
void ControlCore::_setupDispatcherAndCallbacks()
{
// Get our dispatcher. If we're hosted in-proc with XAML, this will get
@@ -911,6 +926,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetSoftwareRendering(_settings->SoftwareRendering());
// Inform the renderer of our opacity
_renderEngine->EnableTransparentBackground(_isBackgroundTransparent());
_renderFailures = 0; // We may have changed the engine; reset the failure counter.
// Trigger a redraw to repaint the window background and tab colors.
_renderer->TriggerRedrawAll(true, true);
@@ -1912,6 +1928,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// The lock must be held, because it calls into IRenderData which is shared state.
const auto lock = _terminal->LockForWriting();
_renderFailures = 0;
_renderer->EnablePainting();
}

View File

@@ -347,6 +347,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
safe_void_coroutine _renderEngineSwapChainChanged(const HANDLE handle);
void _rendererBackgroundColorChanged();
void _rendererTabColorChanged();
void _rendererEnteredErrorState();
#pragma endregion
void _raiseReadOnlyWarning();
@@ -401,6 +402,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
float _panelWidth{ 0 };
float _panelHeight{ 0 };
float _compositionScale{ 0 };
uint8_t _renderFailures{ 0 };
// Audio stuff.
MidiAudio _midiAudio;

View File

@@ -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);
@@ -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

@@ -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

@@ -462,6 +462,11 @@ void HwndTerminal::SendOutput(std::wstring_view data)
_terminal->Write(data);
}
void _stdcall AvoidBuggyTSFConsoleFlags()
{
Microsoft::Console::TSF::Handle::AvoidBuggyTSFConsoleFlags();
}
HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal)
{
auto publicTerminal = std::make_unique<HwndTerminal>(parentHwnd);

View File

@@ -41,6 +41,7 @@ typedef struct _TerminalTheme
} TerminalTheme, *LPTerminalTheme;
extern "C" {
__declspec(dllexport) void _stdcall AvoidBuggyTSFConsoleFlags();
__declspec(dllexport) HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
__declspec(dllexport) void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data);
__declspec(dllexport) void _stdcall TerminalRegisterScrollCallback(void* terminal, void __stdcall callback(int, int, int));

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

@@ -2164,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)
@@ -2192,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)

View File

@@ -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();

View File

@@ -4,6 +4,7 @@ EXPORTS
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
; Flat C ABI
AvoidBuggyTSFConsoleFlags
CreateTerminal
DestroyTerminal
TerminalBlinkCursor

View File

@@ -720,7 +720,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
_MarkDuplicateBellSoundDirectories();
_CheckBellSoundsExistence();
_NotifyChanges(L"CurrentBellSounds");
}
@@ -732,19 +731,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (!_profile.HasBellSound())
{
std::vector<hstring> newSounds;
std::vector<IMediaResource> newSounds;
if (const auto inheritedSounds = _profile.BellSound())
{
// copy inherited bell sounds to the current layer
newSounds.reserve(inheritedSounds.Size());
for (const auto sound : inheritedSounds)
{
newSounds.push_back(sound);
}
newSounds = wil::to_vector(inheritedSounds);
}
// if we didn't inherit any bell sounds,
// we should still set the bell sound to an empty list (instead of null)
_profile.BellSound(winrt::single_threaded_vector<hstring>(std::move(newSounds)));
_profile.BellSound(winrt::single_threaded_vector(std::move(newSounds)));
}
}
@@ -768,56 +762,35 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
// Method Description:
// - Check if the bell sounds exist on disk. Mark any that don't exist
// so that they show the appropriate UI
safe_void_coroutine ProfileViewModel::_CheckBellSoundsExistence()
BellSoundViewModel::BellSoundViewModel(const Model::IMediaResource& resource) :
_resource{ resource }
{
co_await winrt::resume_background();
std::vector<Editor::BellSoundViewModel> markedSounds;
for (auto&& sound : _CurrentBellSounds)
if (_resource.Ok() && _resource.Path() != _resource.Resolved())
{
if (!std::filesystem::exists(std::wstring_view{ sound.Path() }))
{
markedSounds.push_back(sound);
}
// If the resource was resolved to something other than its path, show the path!
_ShowDirectory = true;
}
co_await winrt::resume_foreground(_dispatcher);
for (auto&& sound : markedSounds)
{
get_self<BellSoundViewModel>(sound)->FileExists(false);
}
}
BellSoundViewModel::BellSoundViewModel(hstring path) :
_Path{ path }
{
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
if (args.PropertyName() == L"FileExists")
{
_NotifyChanges(L"DisplayPath", L"SubText");
}
});
}
hstring BellSoundViewModel::DisplayPath() const
{
if (_FileExists)
if (_resource.Ok())
{
// filename
const std::filesystem::path filePath{ std::wstring_view{ _Path } };
// filename; start from the resolved path to show where it actually landed
auto resolvedPath{ _resource.Resolved() };
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
return hstring{ filePath.filename().wstring() };
}
return _Path;
return _resource.Path();
}
hstring BellSoundViewModel::SubText() const
{
if (_FileExists)
if (_resource.Ok())
{
// Directory
const std::filesystem::path filePath{ std::wstring_view{ _Path } };
auto resolvedPath{ _resource.Resolved() };
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
return hstring{ filePath.parent_path().wstring() };
}
return RS_(L"Profile_BellSoundNotFound");
@@ -825,17 +798,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
hstring ProfileViewModel::BellSoundPreview()
{
const auto& currentSound = BellSound();
if (!currentSound || currentSound.Size() == 0)
if (!_CurrentBellSounds || _CurrentBellSounds.Size() == 0)
{
return RS_(L"Profile_BellSoundPreviewDefault");
}
else if (currentSound.Size() == 1)
if (_CurrentBellSounds.Size() > 1)
{
std::filesystem::path filePath{ std::wstring_view{ currentSound.GetAt(0) } };
return hstring{ filePath.filename().wstring() };
return RS_(L"Profile_BellSoundPreviewMultiple");
}
return RS_(L"Profile_BellSoundPreviewMultiple");
const auto currentBellSound = _CurrentBellSounds.GetAt(0);
if (currentBellSound.FileExists())
{
return currentBellSound.DisplayPath();
}
return RS_(L"Profile_BellSoundNotFound");
}
void ProfileViewModel::RequestAddBellSound(hstring path)
@@ -844,9 +822,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// copy it over to the current layer and apply modifications
_PrepareModelForBellSoundModification();
// No need to check if the file exists. We came from the FilePicker. That's good enough.
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(path));
_profile.BellSound().Append(path);
auto bellResource{ MediaResourceHelper::FromString(path) };
bellResource.Resolve(path); // No need to check if the file exists. We came from the FilePicker. That's good enough.
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(bellResource));
_profile.BellSound().Append(bellResource);
_NotifyChanges(L"CurrentBellSounds");
}

View File

@@ -30,13 +30,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
struct BellSoundViewModel : BellSoundViewModelT<BellSoundViewModel>, ViewModelHelper<BellSoundViewModel>
{
public:
BellSoundViewModel(hstring path);
BellSoundViewModel(const Model::IMediaResource& resource);
hstring Path() const { return _resource.Path(); }
bool FileExists() const { return _resource.Ok(); }
hstring DisplayPath() const;
hstring SubText() const;
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, FileExists, true);
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, Path);
VIEW_MODEL_OBSERVABLE_PROPERTY(bool, ShowDirectory);
private:
Model::IMediaResource _resource;
};
struct ProfileViewModel : ProfileViewModelT<ProfileViewModel>, ViewModelHelper<ProfileViewModel>
@@ -190,7 +193,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _InitializeCurrentBellSounds();
void _PrepareModelForBellSoundModification();
void _MarkDuplicateBellSoundDirectories();
safe_void_coroutine _CheckBellSoundsExistence();
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;
static Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable> _BuiltInIcons;

View File

@@ -27,7 +27,7 @@ namespace Microsoft.Terminal.Settings.Editor
runtimeclass BellSoundViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Path;
String Path { get; };
String DisplayPath { get; };
String SubText { get; };
Boolean ShowDirectory { get; };
@@ -149,7 +149,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.IMediaResource>, BellSound);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RightClickContextMenu);

View File

@@ -371,7 +371,7 @@
<value>入力したテキストをセルにグループ化する方法を変更します。"Grapheme clusters" オプションは最も新しく Unicode で正しい方法ですが、UNIX では "wcswidth" は一般的な方法であり、"Windows コンソール" は Windows での動作方法をレプリケートします。この設定を変更するには、Windows ターミナルの再起動が必要です。この設定は、その中から起動されたアプリケーションにのみ適用されます。</value>
</data>
<data name="Globals_TextMeasurement_Graphemes.Text" xml:space="preserve">
<value>Grapheme クラスター</value>
<value>書記素クラスター</value>
<comment>The default choice between multiple graphics APIs.</comment>
</data>
<data name="Globals_TextMeasurement_Wcswidth.Text" xml:space="preserve">
@@ -1338,7 +1338,7 @@
<comment>"environment variables" are user-definable values that can affect the way running processes will behave on a computer</comment>
</data>
<data name="Profile_ReloadEnvVars.HelpText" xml:space="preserve">
<value>有効にすると、ターミナルは、このプロファイルで新しいタブまたはウィンドウを作成するときに新しい環境ブロックを生成します。無効にすると、代わりにタブ/ペインはターミナルが開始された変数を継承します。</value>
<value>有効にすると、ターミナルは、このプロファイルで新しいタブまたはペインを作成するときに新しい環境ブロックを生成します。無効にすると、代わりにタブ/ペインはターミナルが開始された変数を継承します。</value>
<comment>A description for what the "Reload environment variables" setting does. Presented near "Profile_ReloadEnvVars".</comment>
</data>
<data name="Profile_RightClickContextMenu.Header" xml:space="preserve">
@@ -1382,7 +1382,7 @@
<comment>An option to choose from for the "bell style" setting. When selected, notifications are silenced.</comment>
</data>
<data name="Globals_DisableAnimations.Header" xml:space="preserve">
<value>ウィンドウのアニメーションを無効にする</value>
<value>ペインのアニメーションを無効にする</value>
<comment>Header for a control to toggle animations on panes. "Enabled" value disables the animations.</comment>
</data>
<data name="Profile_FontWeightBlack.Content" xml:space="preserve">
@@ -1733,7 +1733,7 @@
<comment>An error message that appears when the user attempts to rename a color scheme to something invalid. This appears as the title, and explains the issue.</comment>
</data>
<data name="Globals_FocusFollowMouse.Header" xml:space="preserve">
<value>マウスをポイントしたときにフォーカス ウィンドウを自動的に表示する</value>
<value>マウスをポイントしたときにフォーカス ペインを自動的に表示する</value>
<comment>Header for a control to toggle the "focus follow mouse" setting. When enabled, hovering over a pane puts it in focus.</comment>
</data>
<data name="Globals_ScrollToZoom.Header" xml:space="preserve">
@@ -1745,7 +1745,7 @@
<comment>Header for a control to toggle opacity changes with scrolling. When enabled, holding the Ctrl and Shift keys while scrolling will increase or decrease the window opacity.</comment>
</data>
<data name="Globals_DisableAnimationsReversed.Header" xml:space="preserve">
<value>ウィンドウのアニメーション</value>
<value>ペインのアニメーション</value>
<comment>Header for a control to toggle animations on panes. "Enabled" value enables the animations.</comment>
</data>
<data name="Globals_AlwaysShowNotificationIcon.Header" xml:space="preserve">

View File

@@ -729,7 +729,7 @@
<comment>A description for what the "antialiasing mode" setting does. Presented near "Profile_AntialiasingMode.Header".</comment>
</data>
<data name="Profile_AntialiasingModeAliased.Content" xml:space="preserve">
<value>별칭</value>
<value>앨리어싱</value>
<comment>An option to choose from for the "text antialiasing" setting. When selected, the app's text renderer does not use antialiasing techniques.</comment>
</data>
<data name="Profile_AntialiasingModeClearType.Content" xml:space="preserve">

View File

@@ -335,7 +335,7 @@
<comment>This text is shown next to a list of choices.</comment>
</data>
<data name="Globals_GraphicsAPI.HelpText" xml:space="preserve">
<value>Direct3D 11 提供更具效能且功能更豐富的體驗,而 Direct2D 則較穩定。默認選項 [自動] 會挑選最適合您圖形硬體的 API。如果您遇到重大問題請考慮使用 Direct2D。</value>
<value>Direct3D 11 提供更具效能且功能更豐富的體驗,而 Direct2D 則較穩定。預設選項 [自動] 會挑選最適合您圖形硬體的 API。如果您遇到重大問題請考慮使用 Direct2D。</value>
</data>
<data name="Globals_GraphicsAPI_Automatic.Text" xml:space="preserve">
<value>自動</value>
@@ -1018,7 +1018,7 @@
<comment>A description for what the "line height" setting does. Presented near "Profile_LineHeight".</comment>
</data>
<data name="Profile_CellWidth.HelpText" xml:space="preserve">
<value>覆寫終端機的單元格寬度。以字型大小的倍數表示。默認值取決於您的字型通常大約是0.6。</value>
<value>覆寫終端機的單元格寬度。以字型大小的倍數表示。預設值取決於您的字型通常大約是0.6。</value>
<comment>A description for what the "cell width" setting does. Presented near "Profile_CellWidth".</comment>
</data>
<data name="Profile_LineHeightBox.PlaceholderText" xml:space="preserve">

View File

@@ -82,18 +82,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
command->LogSettingChanges(_changeLog);
AddAction(*command, keys);
if (jsonBlock.isMember(JsonKey(KeysKey)))
{
// there are keys in this command block meaning this is the legacy style -
// inform the loader that fixups are needed
_fixupsAppliedDuringLoad = true;
}
if (jsonBlock.isMember(JsonKey(ActionKey)) && !jsonBlock.isMember(JsonKey(IterateOnKey)) && origin == OriginTag::User && !jsonBlock.isMember(JsonKey(IDKey)))
if (jsonBlock.isMember(JsonKey(ActionKey)) && !jsonBlock.isMember(JsonKey(IterateOnKey)) && origin == OriginTag::User &&
(!jsonBlock.isMember(JsonKey(IDKey)) || jsonBlock.isMember(JsonKey(KeysKey))))
{
// for non-nested non-iterable commands,
// if there's no ID in the command block we will generate one for the user -
// inform the loader that the ID needs to be written into the json
// if there's no ID in the command block we will generate one for the user,
// or if there are keys in this command block that means this is the legacy style -
// in either case, inform the loader that fixups are needed
_fixupsAppliedDuringLoad = true;
}
}

View File

@@ -588,6 +588,16 @@ static void _resolveSingleMediaResourceInner(Model::OriginTag origin, std::wstri
}
}
if (origin == winrt::Microsoft::Terminal::Settings::Model::OriginTag::Fragment)
{
if (PathIsUNCEx(resourcePath.c_str(), nullptr))
{
// A UNC path is just another type of network path, which fragments are not allowed to specify.
resource.Reject();
return;
}
}
// Not a URI? Try a path.
try
{

View File

@@ -559,6 +559,7 @@ bool SettingsLoader::AddDynamicProfileFolders()
folderEntry->Inlining(FolderEntryInlining::Auto);
folderEntry->RawEntries(winrt::single_threaded_vector<Model::NewTabMenuEntry>({ *matchProfilesEntry }));
// NewTabMenu is guaranteed to exist by FixupUserSettings, which runs before this fixup.
userSettings.globals->NewTabMenu().Append(folderEntry.as<Model::NewTabMenuEntry>());
state->SSHFolderGenerated(true);
return true;
@@ -689,6 +690,16 @@ bool SettingsLoader::FixupUserSettings()
fixedUp = true;
}
// Terminal 1.24
// Ensure that the user always has a newTabMenu. We used to do this last, after
// resolving all of the new tab menu entries, but there was no conceivable reason
// that it should happen so late.
if (!userSettings.globals->HasNewTabMenu())
{
userSettings.globals->NewTabMenu(winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }));
// This one does not need to be written back to the settings file immediately, it can wait until we write one for another reason.
}
return fixedUp;
}
@@ -1263,8 +1274,8 @@ try
// DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles.
// Similarly FixupUserSettings returns true, when it encountered settings that were patched up.
mustWriteToDisk |= loader.DisableDeletedProfiles();
mustWriteToDisk |= loader.AddDynamicProfileFolders();
mustWriteToDisk |= loader.FixupUserSettings();
mustWriteToDisk |= loader.AddDynamicProfileFolders();
// If this throws, the app will catch it and use the default settings.
const auto settings = winrt::make_self<CascadiaSettings>(std::move(loader));
@@ -1745,16 +1756,6 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
{
remainingProfilesEntry.Profiles(remainingProfiles);
}
// If the configuration does not have a "newTabMenu" field, GlobalAppSettings
// will return a default value containing just a "remainingProfiles" entry. However,
// this value is regenerated on every "get" operation, so the effect of setting
// the remaining profiles above will be undone. So only in the case that no custom
// value is present in GlobalAppSettings, we will store the modified default value.
if (!_globals->HasNewTabMenu())
{
_globals->NewTabMenu(entries);
}
}
// Method Description:

View File

@@ -95,7 +95,7 @@ Author(s):
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
X(bool, RightClickContextMenu, "rightClickContextMenu", false) \
X(Windows::Foundation::Collections::IVector<winrt::hstring>, BellSound, "bellSound", nullptr) \
X(Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, "bellSound", nullptr) \
X(bool, Elevate, "elevate", false) \
X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \
X(bool, ShowMarks, "showMarksOnScrollbar", false) \

View File

@@ -121,16 +121,10 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
#undef PROFILE_SETTINGS_COPY
// BellSound is an IVector<hstring>, so we need to manually copy it over
if (_BellSound)
{
std::vector<hstring> sounds;
sounds.reserve(_BellSound->Size());
for (const auto& sound : *_BellSound)
{
sounds.emplace_back(sound);
}
profile->_BellSound = single_threaded_vector(std::move(sounds));
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
}
if (_UnfocusedAppearance)
@@ -560,6 +554,16 @@ void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver
{
container->ResolveMediaResources(resolver);
}
if (const auto [bellSoundSource, bellSounds]{ _getBellSoundOverrideSourceAndValueImpl() };
bellSoundSource && bellSounds && *bellSounds && bellSounds->Size() > 0)
{
for (const auto& bellSound : *bellSounds)
{
ResolveMediaResource(bellSoundSource->_Origin, bellSoundSource->SourceBasePath, bellSound, resolver);
}
// It's important that we keep the invalid bell sounds in the list, because we may want to write it back out to disk
}
}
void Profile::LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const

View File

@@ -76,7 +76,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<IMediaResource>, BellSound);
INHERITABLE_PROFILE_SETTING(Boolean, Elevate);
INHERITABLE_PROFILE_SETTING(Boolean, AutoMarkPrompts);

View File

@@ -124,10 +124,10 @@
<value>新しいタブ...</value>
</data>
<data name="SplitPaneParentCommandName" xml:space="preserve">
<value>ウィンドウの分割...</value>
<value>ペインの分割...</value>
</data>
<data name="SnippetsPaneCommandName" xml:space="preserve">
<value>スニペット ウィンドウを開く</value>
<value>スニペット ペインを開く</value>
<comment>This will open a pane with a list of the users's saved commands ("snippets") in it</comment>
</data>
<data name="ApplicationDisplayNamePortable" xml:space="preserve">
@@ -153,7 +153,7 @@
<value>他のすべてのタブを閉じる</value>
</data>
<data name="ClosePaneCommandKey" xml:space="preserve">
<value>ウィンドウを閉じる</value>
<value>ペインを閉じる</value>
</data>
<data name="CloseTabAtIndexCommandKey" xml:space="preserve">
<value>インデックス {0}のタブを閉じる</value>
@@ -229,7 +229,7 @@
<value>以前</value>
</data>
<data name="DuplicatePaneCommandKey" xml:space="preserve">
<value>ウィンドウの複製する</value>
<value>ペインの複製する</value>
</data>
<data name="DuplicateTabCommandKey" xml:space="preserve">
<value>タブを複製する</value>
@@ -262,41 +262,41 @@
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", "DirectionDown"</comment>
</data>
<data name="MoveFocusToLastUsedPane" xml:space="preserve">
<value>前回使用したウィンドウにフォーカスを移動する</value>
<value>前回使用したペインにフォーカスを移動する</value>
</data>
<data name="MoveFocusNextInOrder" xml:space="preserve">
<value>次のウィンドウに順番にフォーカスを移動する</value>
<value>次のペインに順番にフォーカスを移動する</value>
</data>
<data name="MoveFocusPreviousInOrder" xml:space="preserve">
<value>前のウィンドウに順番にフォーカスを移動する</value>
<value>前のペインに順番にフォーカスを移動する</value>
</data>
<data name="MoveFocusFirstPane" xml:space="preserve">
<value>最初のウィンドウにフォーカスを移動する</value>
<value>最初のペインにフォーカスを移動する</value>
</data>
<data name="MoveFocusParentPane" xml:space="preserve">
<value>親ウィンドウにフォーカスを移動する</value>
<value>親ペインにフォーカスを移動する</value>
</data>
<data name="MoveFocusChildPane" xml:space="preserve">
<value>子ウィンドウにフォーカスを移動する</value>
<value>子ペインにフォーカスを移動する</value>
</data>
<data name="SwapPaneCommandKey" xml:space="preserve">
<value>ウィンドウを入れ替える</value>
<value>ペインを入れ替える</value>
</data>
<data name="SwapPaneWithArgCommandKey" xml:space="preserve">
<value>ウィンドウを入れ替える {0}</value>
<value>ペインを入れ替える {0}</value>
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", "DirectionDown"</comment>
</data>
<data name="SwapPaneToLastUsedPane" xml:space="preserve">
<value>ウィンドウを最後に使用したウィンドウと入れ替える</value>
<value>ペインを最後に使用したペインと入れ替える</value>
</data>
<data name="SwapPaneNextInOrder" xml:space="preserve">
<value>ウィンドウを次のウィンドウと順番に入れ替える</value>
<value>ペインを次のペインと順番に入れ替える</value>
</data>
<data name="SwapPanePreviousInOrder" xml:space="preserve">
<value>ウィンドウを前のウィンドウと順番に入れ替える</value>
<value>ペインを前のペインと順番に入れ替える</value>
</data>
<data name="SwapPaneFirstPane" xml:space="preserve">
<value>最初のウィンドウとウィンドウを入れ替える</value>
<value>最初のペインとペインを入れ替える</value>
</data>
<data name="NewTabCommandKey" xml:space="preserve">
<value>新しいタブ</value>
@@ -357,10 +357,10 @@
<value>タブ名を変更</value>
</data>
<data name="ResizePaneCommandKey" xml:space="preserve">
<value>ウィンドウのサイズの変更する</value>
<value>ペインのサイズの変更する</value>
</data>
<data name="ResizePaneWithArgCommandKey" xml:space="preserve">
<value>ウィンドウのサイズを変更する {0}</value>
<value>ペインのサイズを変更する {0}</value>
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
</data>
<data name="ScrollDownCommandKey" xml:space="preserve">
@@ -427,19 +427,19 @@
<comment>{0} will be replaced with a color, displayed in hexadecimal (#RRGGBB) notation.</comment>
</data>
<data name="SplitHorizontalCommandKey" xml:space="preserve">
<value>ウィンドウを左右に分割</value>
<value>ペインを左右に分割</value>
</data>
<data name="MovePaneCommandKey" xml:space="preserve">
<value>ウィンドウの移動</value>
<value>ペインの移動</value>
</data>
<data name="MovePaneToNewWindowCommandKey" xml:space="preserve">
<value>ウィンドウを新しいウィンドウに移動</value>
<value>ペインを新しいウィンドウに移動</value>
</data>
<data name="SplitPaneCommandKey" xml:space="preserve">
<value>ウィンドウを分割する</value>
<value>ペインを分割する</value>
</data>
<data name="SplitVerticalCommandKey" xml:space="preserve">
<value>ウィンドウを上下に分割</value>
<value>ペインを上下に分割</value>
</data>
<data name="SwitchToTabCommandKey" xml:space="preserve">
<value>タブに切り替え</value>
@@ -488,19 +488,19 @@
<value>最大化ウィンドウを復元する</value>
</data>
<data name="ToggleSplitOrientationCommandKey" xml:space="preserve">
<value>ウィンドウ分割の方向の切り替え</value>
<value>ペイン分割の方向の切り替え</value>
</data>
<data name="TogglePaneZoomCommandKey" xml:space="preserve">
<value>ウィンドウのズームの切り替え</value>
<value>ペインのズームの切り替え</value>
</data>
<data name="TogglePaneReadOnlyCommandKey" xml:space="preserve">
<value>ウィンドウを読み取り専用モードに切り替える</value>
<value>ペインを読み取り専用モードに切り替える</value>
</data>
<data name="EnablePaneReadOnlyCommandKey" xml:space="preserve">
<value>ウィンドウの読み取り専用モードを有効にする</value>
<value>ペインの読み取り専用モードを有効にする</value>
</data>
<data name="DisablePaneReadOnlyCommandKey" xml:space="preserve">
<value>ウィンドウの読み取り専用モードを無効にする</value>
<value>ペインの読み取り専用モードを無効にする</value>
</data>
<data name="ToggleShaderEffectsCommandKey" xml:space="preserve">
<value>ターミナルの視覚効果の切り替え</value>
@@ -532,7 +532,7 @@
<comment>Quake is a well-known computer game and isn't related to earthquakes, etc. See https://en.wikipedia.org/wiki/Quake_(video_game)</comment>
</data>
<data name="FocusPaneCommandKey" xml:space="preserve">
<value>ウィンドウ {0} をフォーカスする</value>
<value>ペイン {0} をフォーカスする</value>
<comment>{0} will be replaced with a user-specified number</comment>
</data>
<data name="ExportBufferToPathCommandKey" xml:space="preserve">
@@ -585,7 +585,7 @@
<comment>A command to change how transparent the background of the window is</comment>
</data>
<data name="RestoreLastClosedCommandKey" xml:space="preserve">
<value>最後に閉じたウィンドウまたはタブを復元する</value>
<value>最後に閉じたペインまたはタブを復元する</value>
</data>
<data name="SelectAllCommandKey" xml:space="preserve">
<value>すべてのテキストを選択</value>
@@ -695,10 +695,10 @@
<value>コンテキスト メニューの表示</value>
</data>
<data name="CloseOtherPanesCommandKey" xml:space="preserve">
<value>他のすべてのウィンドウを閉じる</value>
<value>他のすべてのペインを閉じる</value>
</data>
<data name="ToggleBroadcastInputCommandKey" xml:space="preserve">
<value>ブロードキャスト入力をすべてのウィンドウに切り替える</value>
<value>ブロードキャスト入力をすべてのペインに切り替える</value>
<comment>When enabled, input will go to all panes in this tab simultaneously</comment>
</data>
<data name="RestartConnectionKey" xml:space="preserve">
@@ -760,4 +760,4 @@
<value>SSH ホスト プロファイル ジェネレーター</value>
<comment>The display name of a dynamic profile generator for SSH hosts</comment>
</data>
</root>
</root>

View File

@@ -35,19 +35,14 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
/*static*/ const std::wregex SshHostGenerator::_configKeyValueRegex{ LR"(^\s*(\w+)\s+([^\s]+.*[^\s])\s*$)" };
/*static*/ std::wstring_view SshHostGenerator::_getProfileName(const std::wstring_view& hostName) noexcept
winrt::hstring _getProfileName(const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"" + PROFILE_TITLE_PREFIX + hostName };
return winrt::hstring{ fmt::format(FMT_COMPILE(L"{0}{1}"), PROFILE_TITLE_PREFIX, hostName) };
}
/*static*/ std::wstring_view SshHostGenerator::_getProfileIconPath() noexcept
winrt::hstring _getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept
{
return PROFILE_ICON_PATH;
}
/*static*/ std::wstring_view SshHostGenerator::_getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"\"" + sshExePath + L"\" " + hostName };
return winrt::hstring{ fmt::format(FMT_COMPILE(LR"("{0}" {1})"), sshExePath, hostName) };
}
/*static*/ bool SshHostGenerator::_tryFindSshExePath(std::wstring& sshExePath) noexcept
@@ -164,8 +159,8 @@ void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementatio
{
const auto profile{ CreateDynamicProfile(_getProfileName(hostName)) };
profile->Commandline(winrt::hstring{ _getProfileCommandLine(sshExePath, hostName) });
profile->Icon(winrt::hstring{ _getProfileIconPath() });
profile->Commandline(_getProfileCommandLine(sshExePath, hostName));
profile->Icon(winrt::hstring{ PROFILE_ICON_PATH });
profiles.emplace_back(profile);
}

View File

@@ -31,10 +31,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model
private:
static const std::wregex _configKeyValueRegex;
static std::wstring_view _getProfileName(const std::wstring_view& hostName) noexcept;
static std::wstring_view _getProfileIconPath() noexcept;
static std::wstring_view _getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept;
static bool _tryFindSshExePath(std::wstring& sshExePath) noexcept;
static bool _tryParseConfigKeyValue(const std::wstring_view& line, std::wstring& key, std::wstring& value) noexcept;
static void _getHostNamesFromConfigFile(const std::wstring_view& configPath, std::vector<std::wstring>& hostNames) noexcept;

View File

@@ -26,12 +26,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model
void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance&) const
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const
{
// We only support version of VS from 15.0.
// Per heaths: The [ISetupConfiguration] COM server only supports Visual Studio 15.0 and newer anyway.
// Eliding the version range will improve the discovery performance by not having to parse or compare the versions.
return true;
std::error_code ec;
return std::filesystem::exists(GetDevCmdScriptPath(instance), ec) && !ec;
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const

View File

@@ -28,7 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return instance.VersionInRange(L"[16.2,)");
std::error_code ec;
return instance.VersionInRange(L"[16.2,)") && std::filesystem::exists(GetDevShellModulePath(instance), ec) && !ec;
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const

View File

@@ -85,7 +85,7 @@
"background": "#141414",
"foreground": "#BAB7B6",
"cursorColor": "#37E57B",
"selectionBackground": "#FFFFFF",
"selectionBackground": "#8DB8E5",
"black": "#000000",
"red": "#CF494C",
"green": "#60B442",
@@ -101,7 +101,7 @@
"brightBlue": "#688DFD",
"brightPurple": "#ED6FE9",
"brightCyan": "#32E0FB",
"brightWhite": "#D3D8D9"
"brightWhite": "#DEE3E4"
},
{
"name": "Ottosson",

View File

@@ -86,4 +86,5 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<Import Project="$(OpenConsoleDir)build\rules\CollectWildcardResources.targets" />
</Project>

View File

@@ -170,7 +170,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
30,
Core::Point{ 0, 30 },
Core::Point{ 0, 0 },
buttonState);
}
@@ -188,7 +188,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers,
-30,
Core::Point{ 0, -30 },
Core::Point{ 0, 0 },
buttonState);
}
@@ -245,7 +245,7 @@ namespace ControlUnitTests
expectedTop = 20;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
@@ -254,18 +254,18 @@ namespace ControlUnitTests
{
expectedTop--;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
}
Log::Comment(L"Scrolling up more should do nothing");
expectedTop = 0;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
@@ -275,7 +275,7 @@ namespace ControlUnitTests
Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i));
expectedTop++;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition));
@@ -283,11 +283,11 @@ namespace ControlUnitTests
Log::Comment(L"Scrolling down more should do nothing");
expectedTop = 21;
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
interactivity->MouseWheel(modifiers,
-WHEEL_DELTA,
Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 },
buttonState);
}
@@ -444,7 +444,7 @@ namespace ControlUnitTests
Log::Comment(L"Scroll up a line, with the left mouse button selected");
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
cursorPosition1.to_core_point(),
leftMouseDown);
@@ -492,55 +492,55 @@ namespace ControlUnitTests
const Core::Point mousePos{ 0, 0 };
Control::MouseButtonState state{};
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, "
L"we'll round the internal scrollbar position to scrolling to the next row.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
Log::Comment(L"Jump to line 5, so we can scroll down from there.");
interactivity->UpdateScrollbar(5);
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta.");
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
Log::Comment(L"Jump to the bottom.");
interactivity->UpdateScrollbar(21);
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
}
@@ -709,7 +709,7 @@ namespace ControlUnitTests
{
expectedTop--;
interactivity->MouseWheel(modifiers,
WHEEL_DELTA,
Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 },
noMouseDown);
}

View File

@@ -91,6 +91,7 @@ namespace SettingsModelUnitTests
TEST_METHOD(ProfileInheritsInvalidIconAndHasNoCommandline);
TEST_METHOD(ProfileSpecifiesNullIcon);
TEST_METHOD(ProfileSpecifiesNullIconAndHasNoCommandline);
TEST_METHOD(ProfileOverwritesBellSound);
// FRAGMENT BEHAVIORS
TEST_METHOD(FragmentUpdatesBaseProfile);
@@ -102,6 +103,7 @@ namespace SettingsModelUnitTests
TEST_METHOD(RealResolverFilePaths);
TEST_METHOD(RealResolverSpecialKeywords);
TEST_METHOD(RealResolverUrlCases);
TEST_METHOD(RealResolverUNCCases);
static constexpr std::wstring_view pingCommandline{ LR"(C:\Windows\System32\PING.EXE)" }; // Normalized by Profile (this is the casing that Windows stores on disk)
static constexpr std::wstring_view overrideCommandline{ LR"(C:\Windows\System32\cscript.exe)" };
@@ -125,7 +127,11 @@ namespace SettingsModelUnitTests
"backgroundImage": "imagePathFromBase",
"guid": "{862d46aa-cc9c-4e6c-b872-9cadaafcdbbe}",
"icon": "iconFromBase",
"name": "Base"
"name": "Base",
"bellSound": [
"C:\\Windows\\Media\\Alarm01.wav",
"C:\\Windows\\Media\\Alarm02.wav"
]
},
{
"backgroundImage": "focusedImagePathFromBase",
@@ -167,7 +173,7 @@ namespace SettingsModelUnitTests
]
})" };
static constexpr int numberOfMediaResourcesInDefaultSettings{ 9 };
static constexpr int numberOfMediaResourcesInDefaultSettings{ 11 };
struct Fragment
{
@@ -1021,6 +1027,37 @@ namespace SettingsModelUnitTests
VERIFY_IS_TRUE(icon.Ok()); // Profile with commandline always has an icon
VERIFY_ARE_EQUAL(cmdCommandline, icon.Resolved());
}
// A profile replaces the bell sounds (2) in the base settings; all bell sounds retained
void MediaResourceTests::ProfileOverwritesBellSound()
{
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
winrt::com_ptr<implementation::CascadiaSettings> settings;
{
auto [t, e] = requireCalled([&](auto&&, auto&&, auto&& resource) {
// All resources are invalid.
resource.Reject();
});
g_mediaResolverHook = t;
settings = createSettings(R"({
"profiles": {
"list": [
{
"guid": "{862d46aa-cc9c-4e6c-b872-9cadaafcdbbe}",
"bellSound": [
"does not matter; resolved rejected"
],
},
]
}
})");
}
auto profile{ settings->GetProfileByName(L"Base") };
auto bellSounds{ profile.BellSound() };
VERIFY_ARE_EQUAL(1u, bellSounds.Size());
VERIFY_IS_FALSE(bellSounds.GetAt(0).Ok());
}
#pragma endregion
#pragma region Real Resolver Tests
@@ -1306,5 +1343,95 @@ namespace SettingsModelUnitTests
VERIFY_ARE_NOT_EQUAL(image.Resolved(), image.Path());
}
}
void MediaResourceTests::RealResolverUNCCases()
{
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
g_mediaResolverHook = nullptr; // Use the real resolver
// For profile, we test images instead of icon because Icon has a fallback behavior.
auto settings = createSettingsWithFragments(R"({})", { Fragment{ L"fragment", fragmentBasePath1, R"(
{
"profiles": {
"list": [
{
"backgroundImage": "\\\\server",
"name": "ProfileUNCServerOnly"
},
{
"backgroundImage": "\\\\server\\share",
"name": "ProfileUNCServerShare"
},
{
"backgroundImage": "\\\\server\\share\\file",
"name": "ProfileUNCFullPath"
},
{
"backgroundImage": "\\\\?\\UNC\\server",
"name": "ProfileWin32NamespaceUNCServerOnly"
},
{
"backgroundImage": "\\\\?\\UNC\\server\\share",
"name": "ProfileWin32NamespaceUNCServerShare"
},
{
"backgroundImage": "\\\\?\\UNC\\server\\share\\file",
"name": "ProfileWin32NamespaceUNCFullPath"
},
{
"backgroundImage": "\\\\?\\C:\\Windows\\System32\\cmd.exe",
"name": "ProfileWin32NamespaceDrivePath"
},
]
}
})" } });
{
auto profile{ settings->GetProfileByName(L"ProfileUNCServerOnly") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
{
auto profile{ settings->GetProfileByName(L"ProfileUNCServerShare") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
{
auto profile{ settings->GetProfileByName(L"ProfileUNCFullPath") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
{
auto profile{ settings->GetProfileByName(L"ProfileWin32NamespaceUNCServerOnly") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
{
auto profile{ settings->GetProfileByName(L"ProfileWin32NamespaceUNCServerShare") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
{
auto profile{ settings->GetProfileByName(L"ProfileWin32NamespaceUNCFullPath") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_FALSE(image.Ok());
}
// The only one of these paths which is OK is the one to \\?\C:\Windows
{
auto profile{ settings->GetProfileByName(L"ProfileWin32NamespaceDrivePath") };
auto image{ profile.DefaultAppearance().BackgroundImagePath() };
VERIFY_IS_TRUE(image.Ok());
}
// We cannot test that user-originated UNC paths resolve properly because we cannot guarantee
// the existence of a network share on any test machine, be it in a lab or owned by a user.
}
#pragma endregion
}

View File

@@ -745,7 +745,7 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&,
// - delta: the wheel delta that triggered this event.
// Return Value:
// - <none>
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta)
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta)
{
if (_windowLogic)
{

View File

@@ -73,7 +73,7 @@ private:
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta);
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta);
void _WindowActivated(bool activated);
void _WindowMoved();

View File

@@ -595,6 +595,7 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
WindowCloseButtonClicked.raise();
return 0;
}
case WM_MOUSEHWHEEL:
case WM_MOUSEWHEEL:
try
{
@@ -623,7 +624,11 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
const auto scale = GetCurrentDpiScale();
const winrt::Windows::Foundation::Point real{ relative.x / scale, relative.y / scale };
const auto wheelDelta = static_cast<short>(HIWORD(wparam));
winrt::Microsoft::Terminal::Core::Point wheelDelta{ 0, std::bit_cast<int16_t>(HIWORD(wparam)) };
if (message == WM_MOUSEHWHEEL)
{
std::swap(wheelDelta.X, wheelDelta.Y);
}
// Raise an event, so any listeners can handle the mouse wheel event manually.
MouseScrolled.raise(real, wheelDelta);

View File

@@ -74,7 +74,7 @@ public:
til::event<winrt::delegate<>> DragRegionClicked;
til::event<winrt::delegate<>> WindowCloseButtonClicked;
til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, int32_t)>> MouseScrolled;
til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, winrt::Microsoft::Terminal::Core::Point)>> MouseScrolled;
til::event<winrt::delegate<void(bool)>> WindowActivated;
til::event<winrt::delegate<void()>> NotifyNotificationIconPressed;
til::event<winrt::delegate<void()>> NotifyWindowHidden;

View File

@@ -194,6 +194,22 @@ static wil::unique_mutex acquireMutexOrAttemptHandoff(const wchar_t* className,
return {};
}
static constexpr bool IsInputKey(WORD vkey) noexcept
{
return vkey != VK_CONTROL &&
vkey != VK_LCONTROL &&
vkey != VK_RCONTROL &&
vkey != VK_MENU &&
vkey != VK_LMENU &&
vkey != VK_RMENU &&
vkey != VK_SHIFT &&
vkey != VK_LSHIFT &&
vkey != VK_RSHIFT &&
vkey != VK_LWIN &&
vkey != VK_RWIN &&
vkey != VK_SNAPSHOT;
}
HWND WindowEmperor::GetMainWindow() const noexcept
{
_assertIsMainThread();
@@ -253,6 +269,29 @@ void WindowEmperor::CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args
_windowCount += 1;
_windows.emplace_back(std::move(host));
if (_windowCount == 1)
{
// The first CoreWindow is created implicitly by XAML and parented to the
// first XAML island. We parent it to our initial window for 2 reasons:
// * On Windows 10 the CoreWindow will show up as a visible window on the taskbar
// due to a WinUI bug, and this will hide it, because our initial window is hidden.
// * When we DestroyWindow() the island it will destroy the CoreWindow,
// and it's not possible to recreate it. That's also a WinUI bug.
//
// Note that this must be done after the first window (= first island) is created.
if (const auto coreWindow = winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread())
{
if (const auto interop = coreWindow.try_as<ICoreWindowInterop>())
{
HWND coreHandle = nullptr;
if (SUCCEEDED(interop->get_WindowHandle(&coreHandle)) && coreHandle)
{
SetParent(coreHandle, _window.get());
}
}
}
}
}
AppHost* WindowEmperor::_mostRecentWindow() const noexcept
@@ -292,7 +331,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
}
if (!IsPackaged())
{
const auto path = wil::GetModuleFileNameW<std::wstring>(nullptr);
const auto path = wil::QueryFullProcessImageNameW<std::wstring>();
const auto hash = til::hash(path);
#ifdef _WIN64
fmt::format_to(std::back_inserter(windowClassName), FMT_COMPILE(L" {:016x}"), hash);
@@ -395,24 +434,6 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(system32.c_str()));
}
// The first CoreWindow is created implicitly by XAML and parented to the
// first XAML island. We parent it to our initial window for 2 reasons:
// * On Windows 10 the CoreWindow will show up as a visible window on the taskbar
// due to a WinUI bug, and this will hide it, because our initial window is hidden.
// * When we DestroyWindow() the island it will destroy the CoreWindow,
// and it's not possible to recreate it. That's also a WinUI bug.
if (const auto coreWindow = winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread())
{
if (const auto interop = coreWindow.try_as<ICoreWindowInterop>())
{
HWND coreHandle = nullptr;
if (SUCCEEDED(interop->get_WindowHandle(&coreHandle)) && coreHandle)
{
SetParent(coreHandle, _window.get());
}
}
}
{
TerminalConnection::ConptyConnection::NewConnection([this](TerminalConnection::ConptyConnection conn) {
TerminalApp::CommandlineArgs args;
@@ -481,7 +502,13 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
if (msg.message == WM_KEYDOWN)
{
IslandWindow::HideCursor();
const auto vkey = static_cast<WORD>(msg.wParam);
// Hide the cursor only when the key pressed is an input key (ignore modifier keys).
if (IsInputKey(vkey))
{
IslandWindow::HideCursor();
}
}
}
@@ -544,6 +571,8 @@ void WindowEmperor::_dispatchSpecialKey(const MSG& msg) const
void WindowEmperor::_dispatchCommandline(winrt::TerminalApp::CommandlineArgs args)
{
_assertIsMainThread();
const auto exitCode = args.ExitCode();
if (const auto msg = args.ExitMessage(); !msg.empty())
@@ -641,7 +670,8 @@ void WindowEmperor::_dispatchCommandlineCommon(winrt::array_view<const winrt::hs
// This is an implementation-detail of _dispatchCommandline().
safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::TerminalApp::CommandlineArgs args)
{
std::weak_ptr<AppHost> mostRecentWeak;
std::shared_ptr<AppHost> mostRecent;
AppHost* window = nullptr;
if (winrt::guid currentDesktop; VirtualDesktopUtils::GetCurrentVirtualDesktopId(reinterpret_cast<GUID*>(&currentDesktop)))
{
@@ -653,19 +683,18 @@ safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::Ter
if (desktopId == currentDesktop && lastActivatedTime > max)
{
max = lastActivatedTime;
mostRecentWeak = w;
mostRecent = w;
}
}
// GetVirtualDesktopId(), as current implemented, should always return on the main thread.
_assertIsMainThread();
window = mostRecent.get();
}
// GetVirtualDesktopId(), as current implemented, should always return on the main thread.
_assertIsMainThread();
const auto mostRecent = mostRecentWeak.lock();
auto window = mostRecent.get();
if (!window)
else
{
// If virtual desktops have never been used, and in turn Explorer never set them up,
// GetCurrentVirtualDesktopId will return false. In this case just use the current (only) desktop.
window = _mostRecentWindow();
}
@@ -681,6 +710,8 @@ safe_void_coroutine WindowEmperor::_dispatchCommandlineCurrentDesktop(winrt::Ter
bool WindowEmperor::_summonWindow(const SummonWindowSelectionArgs& args) const
{
_assertIsMainThread();
AppHost* window = nullptr;
if (args.WindowID)
@@ -721,6 +752,8 @@ bool WindowEmperor::_summonWindow(const SummonWindowSelectionArgs& args) const
void WindowEmperor::_summonAllWindows() const
{
_assertIsMainThread();
TerminalApp::SummonWindowBehavior args;
args.ToggleVisibility(false);
@@ -858,6 +891,9 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
// Did the window counter get out of sync? It shouldn't.
assert(_windowCount == gsl::narrow_cast<int32_t>(_windows.size()));
// !!! NOTE !!!
// At least theoretically the lParam pointer may be invalid.
// We should only access it if we find it in our _windows array.
const auto host = reinterpret_cast<AppHost*>(lParam);
auto it = _windows.begin();
const auto end = _windows.end();
@@ -866,8 +902,16 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
{
if (host == it->get())
{
host->Close();
// NOTE: AppHost::Close is highly non-trivial.
//
// It _may_ call into XAML, which _may_ pump the message loop, which would then recursively
// re-enter this function, which _may_ then handle another WM_CLOSE_TERMINAL_WINDOW,
// which would change the _windows array, and invalidate our iterator and crash.
//
// We can prevent this by deferring Close() until after the erase() call.
const auto strong = *it;
_windows.erase(it);
strong->Close();
break;
}
}

View File

@@ -84,14 +84,14 @@ private:
int32_t _windowCount = 0;
int32_t _messageBoxCount = 0;
#ifdef NDEBUG
#if 0 // #ifdef NDEBUG
static constexpr void _assertIsMainThread() noexcept
{
}
#else
void _assertIsMainThread() const noexcept
{
assert(_mainThreadId == GetCurrentThreadId());
WI_ASSERT_MSG(_mainThreadId == GetCurrentThreadId(), "This part of WindowEmperor must be accessed from the UI thread");
}
DWORD _mainThreadId = GetCurrentThreadId();
#endif

View File

@@ -179,6 +179,9 @@ namespace Microsoft.Terminal.Wpf
SWP_SHOWWINDOW = 0x0040,
}
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, ExactSpelling = true)]
public static extern void AvoidBuggyTSFConsoleFlags();
[DllImport("Microsoft.Terminal.Control.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
public static extern void CreateTerminal(IntPtr parent, out IntPtr hwnd, out IntPtr terminal);

View File

@@ -33,6 +33,11 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
public TerminalContainer()
{
// WPF & TSF can't deal with us setting TF_TMAE_CONSOLE on the UI thread.
// It simply crashes on Windows 10 if you use the Emoji picker.
// (On later versions of Windows it just doesn't work.)
NativeMethods.AvoidBuggyTSFConsoleFlags();
this.MessageHook += this.TerminalContainer_MessageHook;
this.GotFocus += this.TerminalContainer_GotFocus;
this.Focusable = true;

View File

@@ -68,6 +68,13 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_AtlasEngineLoudErrors</name>
<description>Atlas Engine can optionally support signaling every presentation failure to its consumer. For now, we only want that to happen in non-Release builds.</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledReleaseTokens/>
</feature>
<feature>
<name>Feature_NearbyFontLoading</name>
<description>Controls whether fonts in the same directory as the binary are used during rendering. Disabled for conhost so that it doesn't iterate the entire system32 directory.</description>
@@ -133,10 +140,14 @@
<name>Feature_VtChecksumReport</name>
<description>Enables the DECRQCRA checksum report, which can be used to read the screen contents</description>
<id>14974</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
</alwaysEnabledBrandingTokens>
<stage>AlwaysEnabled</stage>
<alwaysDisabledBrandingTokens>
<!--
Since we have a way to turn this on and off in Terminal, we don't need to hide it behind a feature
flag any longer; however, conhost has no way to turn it off so let's just compile it out for now.
-->
<brandingToken>WindowsInbox</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
<feature>

View File

@@ -170,7 +170,12 @@ void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak)
if (gci.HasActiveOutputBuffer())
{
gci.GetActiveOutputBuffer().SnapOnInput(keyEvent.wVirtualKeyCode);
auto& buffer = gci.GetActiveOutputBuffer();
if (WI_IsFlagSet(buffer.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
{
buffer.SnapOnInput(keyEvent.wVirtualKeyCode);
}
}
}
}

View File

@@ -1718,6 +1718,19 @@ void SCREEN_INFORMATION::SnapOnInput(const WORD vkey)
}
}
SCREEN_INFORMATION::SnapOnScopeExit SCREEN_INFORMATION::SnapOnOutput() noexcept
{
const auto call =
// We don't need to snap-on-output the alt buffer since it doesn't scroll anyway.
// More importantly though, in the current architecture the alt buffer gets deallocated
// the second we receive a \033[?1049l, which makes our this pointer hazardous to use.
_psiMainBuffer == nullptr &&
// We only want to snap if the user didn't intentionally scroll away.
// The snapping logic could be improved, but it's alright for now.
_viewport.IsInBounds(_textBuffer->GetCursor().GetPosition());
return SnapOnScopeExit{ call ? this : nullptr };
}
void SCREEN_INFORMATION::_makeCursorVisible()
{
if (_textBuffer->GetCursor().IsVisible())

View File

@@ -50,6 +50,25 @@ class ConversionAreaInfo; // forward decl window. circular reference
class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider
{
public:
// This little helper works like wil::scope_exit but is slimmer
// (= easier to optimize) and has a concrete type (= can declare).
struct SnapOnScopeExit
{
~SnapOnScopeExit()
{
if (self)
{
try
{
self->_makeCursorVisible();
}
CATCH_LOG();
}
}
SCREEN_INFORMATION* self;
};
[[nodiscard]] static NTSTATUS CreateInstance(_In_ til::size coordWindowSize,
const FontInfo fontInfo,
_In_ til::size coordScreenBufferSize,
@@ -73,16 +92,7 @@ public:
void MakeCurrentCursorVisible();
void MakeCursorVisible(til::point position);
void SnapOnInput(WORD vkey);
auto SnapOnOutput() noexcept
{
const auto inBounds = _viewport.IsInBounds(_textBuffer->GetCursor().GetPosition());
return wil::scope_exit([this, inBounds]() {
if (inBounds)
{
_makeCursorVisible();
}
});
}
SnapOnScopeExit SnapOnOutput() noexcept;
void ClipToScreenBuffer(_Inout_ til::inclusive_rect* const psrClip) const;

View File

@@ -366,12 +366,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
if (til::compare_ordinal_insensitive(var, temp) == 0 ||
til::compare_ordinal_insensitive(var, tmp) == 0)
{
return til::details::wil_env::GetShortPathNameW<std::wstring, 256>(value.data());
}
else
{
return std::wstring{ value };
std::wstring shortPath;
if (SUCCEEDED((til::details::wil_env::GetShortPathNameW<std::wstring, 256>(value.data(), shortPath))))
{
return shortPath;
}
}
return std::wstring{ value };
}
static bool is_path_var(std::wstring_view input) noexcept

View File

@@ -567,7 +567,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
if (!fShiftPressed && !pSelection->IsInSelectingState())
{
short sDelta = 0;
if (Message == WM_MOUSEWHEEL)
if (Message == WM_MOUSEWHEEL || Message == WM_MOUSEHWHEEL)
{
sDelta = GET_WHEEL_DELTA_WPARAM(wParam);
}

View File

@@ -61,13 +61,18 @@ catch (const wil::ResultException& exception)
return E_PENDING;
}
if (_p.warningCallback)
if constexpr (Feature_AtlasEngineLoudErrors::IsEnabled())
{
try
// We may fail to present repeatedly, e.g. if there's a short-term device failure.
// We should not bombard the consumer with repeated warning callbacks (where they may present a dialog to the user).
if (_p.warningCallback)
{
_p.warningCallback(hr, {});
try
{
_p.warningCallback(hr, {});
}
CATCH_LOG()
}
CATCH_LOG()
}
_b.reset();

View File

@@ -9,9 +9,10 @@ using namespace Microsoft::Console::Types;
using PointTree = interval_tree::IntervalTree<til::point, size_t>;
static constexpr auto maxRetriesForRenderEngine = 3;
// The renderer will wait this number of milliseconds * how many tries have elapsed before trying again.
static constexpr auto renderBackoffBaseTimeMilliseconds{ 150 };
// We want there to be five retry periods; after the last one, we will mark the render as failed.
static constexpr unsigned int maxRetriesForRenderEngine = 5;
// The renderer will wait this number of milliseconds * 2^tries before trying again.
static constexpr DWORD renderBackoffBaseTimeMilliseconds = 100;
#define FOREACH_ENGINE(var) \
for (auto var : _engines) \
@@ -44,40 +45,44 @@ IRenderData* Renderer::GetRenderData() const noexcept
// - HRESULT S_OK, GDI error, Safe Math error, or state/argument errors.
[[nodiscard]] HRESULT Renderer::PaintFrame()
{
auto tries = maxRetriesForRenderEngine;
while (tries > 0)
HRESULT hr{ S_FALSE };
// Attempt zero doesn't count as a retry. We should try maxRetries + 1 times.
for (unsigned int attempt = 0u; attempt <= maxRetriesForRenderEngine; ++attempt)
{
if (attempt > 0) [[unlikely]]
{
// Add a bit of backoff.
// Sleep 100, 200, 400, 600, 800ms, 1600ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (1 << (attempt - 1)));
}
// BODGY: Optimally we would want to retry per engine, but that causes different
// problems (intermittent inconsistent states between text renderer and UIA output,
// not being able to lock the cursor location, etc.).
const auto hr = _PaintFrame();
hr = _PaintFrame();
if (SUCCEEDED(hr))
{
break;
}
LOG_HR_IF(hr, hr != E_PENDING);
if (--tries == 0)
{
// Stop trying.
_thread.DisablePainting();
if (_pfnRendererEnteredErrorState)
{
_pfnRendererEnteredErrorState();
}
// If there's no callback, we still don't want to FAIL_FAST: the renderer going black
// isn't near as bad as the entire application aborting. We're a component. We shouldn't
// abort applications that host us.
return S_FALSE;
}
// Add a bit of backoff.
// Sleep 150ms, 300ms, 450ms before failing out and disabling the renderer.
Sleep(renderBackoffBaseTimeMilliseconds * (maxRetriesForRenderEngine - tries));
}
return S_OK;
if (FAILED(hr))
{
// Stop trying.
_thread.DisablePainting();
if (_pfnRendererEnteredErrorState)
{
_pfnRendererEnteredErrorState();
}
// If there's no callback, we still don't want to FAIL_FAST: the renderer going black
// isn't near as bad as the entire application aborting. We're a component. We shouldn't
// abort applications that host us.
hr = S_FALSE;
}
return hr;
}
[[nodiscard]] HRESULT Renderer::_PaintFrame() noexcept

View File

@@ -127,10 +127,13 @@ public:
wch = L'!';
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEWHEEL"));
wch = L'`' + (sScrollDelta > 0 ? 0 : 1);
break;
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEHWHEEL"));
wch = L'b' + (sScrollDelta > 0 ? 1 : 0);
break;
case WM_MOUSEMOVE:
default:
Log::Comment(NoThrowString().Format(L"DEFAULT"));
@@ -168,9 +171,11 @@ public:
result = 3 + 0x20; // we add 0x20 to hover events, which are all encoded as WM_MOUSEMOVE events
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 64 : 65);
break;
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 67 : 66);
break;
default:
result = 0;
break;
@@ -582,6 +587,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[B"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[C"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[D"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Enable cursor keys mode");
mouseInput.SetInputMode(TerminalInput::Mode::CursorKey, true);
@@ -591,6 +602,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOB"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOC"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOD"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput.UseAlternateScreenBuffer();
mouseInput.SetInputMode(TerminalInput::Mode::AlternateScroll, false);

View File

@@ -163,9 +163,11 @@ static constexpr wchar_t _windowsButtonToXEncoding(const unsigned int button,
xvalue = 1;
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41;
break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default:
xvalue = 0;
break;
@@ -221,9 +223,11 @@ static constexpr int _windowsButtonToSGREncoding(const unsigned int button,
xvalue = 3;
break;
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41;
break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default:
xvalue = 0;
break;
@@ -378,7 +382,7 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position,
if (ShouldSendAlternateScroll(button, delta))
{
return _makeAlternateScrollOutput(delta);
return _makeAlternateScrollOutput(button, delta);
}
return {};
@@ -493,14 +497,32 @@ bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const s
// - Sends a sequence to the input corresponding to cursor up / down depending on the sScrollDelta.
// Parameters:
// - delta: The scroll wheel delta of the input event
TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const short delta) const
TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const unsigned int button, const short delta) const
{
if (delta > 0)
if (button == WM_MOUSEWHEEL)
{
return MakeOutput(_keyMap.at(VK_UP));
if (delta > 0)
{
return MakeOutput(_keyMap.at(VK_UP));
}
else
{
return MakeOutput(_keyMap.at(VK_DOWN));
}
}
else if (button == WM_MOUSEHWHEEL)
{
if (delta > 0)
{
return MakeOutput(_keyMap.at(VK_RIGHT));
}
else
{
return MakeOutput(_keyMap.at(VK_LEFT));
}
}
else
{
return MakeOutput(_keyMap.at(VK_DOWN));
return {};
}
}

View File

@@ -111,7 +111,7 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isRelease, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const;
[[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
#pragma endregion

View File

@@ -897,6 +897,22 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
eventFlags |= MOUSE_WHEELED;
break;
}
case CsiMouseButtonCodes::ScrollLeft:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_BACKWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::ScrollRight:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_FORWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::Released:
// hover event, we still want to send these but we don't
// need to do anything special here, so just break

View File

@@ -111,6 +111,8 @@ namespace Microsoft::Console::VirtualTerminal
Released = 3,
ScrollForward = 4,
ScrollBack = 5,
ScrollLeft = 6,
ScrollRight = 7,
};
constexpr unsigned short CsiMouseModifierCode_Drag = 32;

View File

@@ -1194,8 +1194,10 @@ void InputEngineTest::SGRMouseTest_Scroll()
// NOTE: scrolling events do NOT send a mouse up event
const std::vector<std::tuple<SGR_PARAMS, MOUSE_EVENT_PARAMS>> testData = {
// TEST INPUT EXPECTED OUTPUT
{ { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollLeft, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
{ { CsiMouseButtonCodes::ScrollRight, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
};
// clang-format on
VerifySGRMouseData(testData);

View File

@@ -12,10 +12,19 @@ Handle Handle::Create()
{
Handle handle;
handle._impl = new Implementation();
handle._impl->Initialize();
if (!handle._impl->Initialize())
{
delete handle._impl;
handle._impl = nullptr;
}
return handle;
}
void Handle::AvoidBuggyTSFConsoleFlags()
{
Implementation::AvoidBuggyTSFConsoleFlags();
}
void Handle::SetDefaultScopeAlphanumericHalfWidth(bool enable)
{
Implementation::SetDefaultScopeAlphanumericHalfWidth(enable);

View File

@@ -33,6 +33,7 @@ namespace Microsoft::Console::TSF
struct Handle
{
static Handle Create();
static void AvoidBuggyTSFConsoleFlags();
static void SetDefaultScopeAlphanumericHalfWidth(bool enable);
Handle() = default;

View File

@@ -27,20 +27,61 @@ static void TfPropertyvalClose(TF_PROPERTYVAL* val)
}
using unique_tf_propertyval = wil::unique_struct<TF_PROPERTYVAL, decltype(&TfPropertyvalClose), &TfPropertyvalClose>;
void Implementation::SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept
// The flags passed to ActivateEx don't replace the flags during previous calls.
// Instead, they're additive. So, if we pass flags that some concurrently running
// TSF clients don't expect we may blow them up accidentally.
//
// Such is the case with WPF (and TSF, which is actually at fault there).
// If you pass TF_TMAE_CONSOLE it'll instantly crash on Windows 10 on first text input.
// On Windows 11 it'll at least not crash but still make emoji input completely non-functional.
//
// --------
//
// In any case, we pass the same flags as conhost v1:
// - TF_TMAE_UIELEMENTENABLEDONLY: TSF activates only text services that are
// categorized in GUID_TFCAT_TIPCAP_UIELEMENTENABLED.
// - TF_TMAE_NOACTIVATEKEYBOARDLAYOUT: TSF does not sync the current keyboard layout
// while this method is called. The keyboard layout will be adjusted when the
// calling thread gets focus. This flag must be used with TF_TMAE_NOACTIVATETIP.
// - TF_TMAE_CONSOLE: A text service is activated for console usage.
// Some IMEs are known to use this as a hint. Particularly a Korean IME can benefit
// from this, because Korean relies on "recomposing" previously finished compositions.
// That can't work in a terminal, since we submit composed text to the shell immediately.
//
// I'm not sure what TF_TMAE_UIELEMENTENABLEDONLY does. I tried to figure it out but failed.
//
// For TF_TMAE_NOACTIVATEKEYBOARDLAYOUT, I'm 99% sure it doesn't do anything, including in
// conhost v1. This is because IMM will be initialized on WM_ACTIVATE, which calls ActivateEx(0).
// Any subsequent ActivateEx() calls will update the flags, _except_ for this one and
// TF_TMAE_NOACTIVATETIP which are explicitly filtered out.
//
// TF_TMAE_NOACTIVATETIP however is important. Without it, TIPs are immediately initialized.
static std::atomic<DWORD> s_activationFlags{ TF_TMAE_NOACTIVATETIP | TF_TMAE_UIELEMENTENABLEDONLY | TF_TMAE_NOACTIVATEKEYBOARDLAYOUT | TF_TMAE_CONSOLE };
void Implementation::AvoidBuggyTSFConsoleFlags() noexcept
{
_wantsAnsiInputScope.store(enable, std::memory_order_relaxed);
s_activationFlags.fetch_and(~static_cast<DWORD>(TF_TMAE_CONSOLE), std::memory_order_relaxed);
}
void Implementation::Initialize()
static std::atomic<bool> s_wantsAnsiInputScope{ false };
void Implementation::SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept
{
_categoryMgr = wil::CoCreateInstance<ITfCategoryMgr>(CLSID_TF_CategoryMgr, CLSCTX_INPROC_SERVER);
s_wantsAnsiInputScope.store(enable, std::memory_order_relaxed);
}
bool Implementation::Initialize()
{
_categoryMgr = wil::CoCreateInstanceNoThrow<ITfCategoryMgr>(CLSID_TF_CategoryMgr);
if (!_categoryMgr)
{
return false;
}
_displayAttributeMgr = wil::CoCreateInstance<ITfDisplayAttributeMgr>(CLSID_TF_DisplayAttributeMgr);
// There's no point in calling TF_GetThreadMgr. ITfThreadMgr is a per-thread singleton.
_threadMgrEx = wil::CoCreateInstance<ITfThreadMgrEx>(CLSID_TF_ThreadMgr, CLSCTX_INPROC_SERVER);
THROW_IF_FAILED(_threadMgrEx->ActivateEx(&_clientId, TF_TMAE_CONSOLE));
THROW_IF_FAILED(_threadMgrEx->ActivateEx(&_clientId, s_activationFlags.load(std::memory_order_relaxed)));
THROW_IF_FAILED(_threadMgrEx->CreateDocumentMgr(_documentMgr.addressof()));
TfEditCookie ecTextStore;
@@ -53,6 +94,7 @@ void Implementation::Initialize()
THROW_IF_FAILED(_contextSource->AdviseSink(IID_ITfTextEditSink, static_cast<ITfTextEditSink*>(this), &_cookieTextEditSink));
THROW_IF_FAILED(_documentMgr->Push(_context.get()));
return true;
}
void Implementation::Uninitialize() noexcept
@@ -319,7 +361,7 @@ STDMETHODIMP Implementation::GetWnd(HWND* phwnd) noexcept
STDMETHODIMP Implementation::GetAttribute(REFGUID rguidAttribute, VARIANT* pvarValue) noexcept
{
if (_wantsAnsiInputScope.load(std::memory_order_relaxed) && IsEqualGUID(rguidAttribute, GUID_PROP_INPUTSCOPE))
if (s_wantsAnsiInputScope.load(std::memory_order_relaxed) && IsEqualGUID(rguidAttribute, GUID_PROP_INPUTSCOPE))
{
_ansiInputScope.AddRef();
pvarValue->vt = VT_UNKNOWN;

View File

@@ -16,11 +16,12 @@ namespace Microsoft::Console::TSF
struct Implementation : ITfContextOwner, ITfContextOwnerCompositionSink, ITfTextEditSink
{
static void AvoidBuggyTSFConsoleFlags() noexcept;
static void SetDefaultScopeAlphanumericHalfWidth(bool enable) noexcept;
virtual ~Implementation() = default;
void Initialize();
bool Initialize();
void Uninitialize() noexcept;
HWND FindWindowOfActiveTSF() noexcept;
void AssociateFocus(IDataProvider* provider);
@@ -129,6 +130,5 @@ namespace Microsoft::Console::TSF
int _compositions = 0;
AnsiInputScope _ansiInputScope{ this };
inline static std::atomic<bool> _wantsAnsiInputScope{ false };
};
}