Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions knowledge-base/assets/kb0118/windows-compliant-app-sequence.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
sequenceDiagram
autonumber
participant User as User
participant OS as Windows OS
participant TSF as TSF Manager
participant Keyman as Keyman TIP
participant App as Application (Compliant)

User->>OS: Press key (e.g. 'D')
OS->>TSF: Deliver key event
TSF->>Keyman: Call OnKeyDown(ITfContext)

Note right of Keyman: Keyman create TIPEditSession & requests context
Keyman->>TSF: ITfContext.GetStatus()
Keyman->>TSF: ITfContext.GetSelection()
TSF->>App: Request current selection
App-->>TSF: Return caret/selection
TSF-->>Keyman: Return GetSelection


Note right of Keyman: Keyman applies keyboard rules and computes output

Keyman->>TSF: ITfContext.SetText()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does SetText allow to specify whether to insert the text or replace the selection, or do we have to update the selection first?

TSF->>App: Delete or replace text in range

App-->>User: Display “∆” in document
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 58 additions & 1 deletion knowledge-base/kb0118.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,61 @@ on the Keyman system service which emits a fake key event. That way it is possib

## Compliance on Windows

TODO
Compliance on Windows

On Windows, Keyman integrates with applications primarily through the Text Services Framework (TSF). Keyman implements a Text Input Processor (TIP) that communicates with the TSF manager and the target application.

When the user types, the TSF manager passes key events and text context to Keyman through the ITfContext interface, which provides access to the current edit context within the application. Keyman relies on this interface to retrieve context, determine the current selection, and insert or replace text.
For ITfContext details see [Learn Microsoft](https://learn.microsoft.com/en-us/windows/win32/api/msctf/nn-msctf-itfcontext).

Relevant for Keyman are the following API functions:

**`GetStatus()`** — Retrieves the current status of the text context. Keyman uses this to check for the TF_SS_TRANSITORY flag, which indicates temporary or non-standard contexts that may not behave consistently.

**`GetSelection()`** — Retrieves the current text selection or caret position.

**`SetSelection()`** — Updates the current selection or caret position after Keyman inserts or replaces text.

**`GetText()`** — Retrieves text from a specified range around the caret, giving Keyman the context needed to process complex keyboard rules.

**`SetText()`** — Replaces text in a specific range within the context.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see GetText and SetText on the linked MS docs for ITfContext.

Copy link
Copy Markdown
Contributor Author

@rc-swag rc-swag Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is correct I was not sure how much detail to go into. The API linked you make GetSelection call which will return a pointer to TF_SECTION. TF_SECTION in turn contains a ITfRange it is actually ITfRange that has methonds GetText and SetText. Even then Keyman for Windows provides wrapper functions DeleteLeftOfSelection and InsertTextAtSelection that use methods of ITfRange and the ITfContext to achieve the goals. I will have a go at updating this.


Applications can restrict how much surrounding text can be read; Keyman limits its requests to 64 characters of context.

<img src="./assets/kb0118/windows-compliant-app-sequence.png" width="75%" alt="compliant sequence on Windows"/>

The sequence diagram is for a rule that replaces "D" with "∆".

Keyman determines whether an application is compliant by inspecting the status returned by `ITfContext::GetStatus()`.

- **Compliant applications** provide a stable, non-transitory context and correctly implement the TSF APIs needed to retrieve context and modify text.
- **Non-compliant applications** may expose only a transitory context or fail to return reliable selection or text data.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can TSF API be used for non-compliant apps?

Copy link
Copy Markdown
Contributor Author

@rc-swag rc-swag Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, to attempt to get context. It it is determined it is non compliant when attempting to get the context then it does not use the API to set selection.


If the `TF_SS_TRANSITORY` flag is present or context cannot not be read as expected, Keyman treats the application as non-compliant and falls back to legacy behavior, maintaining its own internal context buffer and simulating text operations where possible.

### Non-compliance on Windows

When a non-compliant application is detected, Keyman falls back to **legacy behavior**.

With non-compliant applications, Keyman maintains an **internal context buffer** that records the characters it has recently output. This buffered context is used to apply keyboard rules that depend on previously typed characters.


### Limitations of legacy behavior

The internal context buffer remains valid only while Keyman has exclusive knowledge of text changes. The buffer becomes invalid if the user performs actions such as:

- Moving the caret using the mouse or keyboard
- Changing the selection
- Using shortcut keys or menu commands to edit text
- Switching to another application or document

When such an action occurs, Keyman discards its internal context and assumes an empty context. If this happens in the middle of typing a multi-character sequence, the sequence may not be recognized correctly and may need to be retyped.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Keyman, there is no real concept of a multi-character sequence. (Did you mean multi-keystroke sequence?) Each key event is isolated. We should be careful not to imply a state mechanism that does not exist.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did mean character, however I realise character is an overloaded term with when thinking about unicode points vs charcater etc.
It was refernce back to the intro of this KB https://github.com/keymanapp/help.keyman.com/pull/2326/changes#diff-0bc53b3e68388acdad32bd067b6f9940b76087f855f1ebbbe14b4c204ba5ac2fL43

I have reworked this whole section because it was repeating what had already been said in the preamble. I have now just tried to emphasise the Windows specific work. I have also added link back to the start fo the document rather than repeating it.

Each key event is isolated. We should be careful not to imply a state mechanism that does not exist.

Huh? Each key event is not isolatated that is what the KB is about. It is processed against the context (whether tracked internal or read from context aware) this is state. When that state is lost the keyboard rule cannot work. We haven't mentioned deadkeys or markers in this KB but that information in our internal context is state and a keystroke is dependent on that state.

Back to the multi-character vs multi-keystroke sequence - both have problems with causing confusion. Because the Keyboard rules are actually based on the "charcaters" in the context NOT the keystrokes that preceeded the current keystroke, they have already been turned into a character/s or a deadkey. That's is why compliant apps can work because the read the context and get the characters.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you are right -- there is state, but not in the form of a key sequence. The problem is people think of Keyman keyboards as describing key sequences, but they don't. They describe key + context transforms. Even deadkeys are implemented as invisible marker characters with a position in the context text stream.

In the ideal case -- apart from deadkeys -- this can be described as 'stateless': we don't carry information from keystroke to keystroke. You can position the text caret anywhere in a document and the keystroke will work with the context that it finds already there.

Historically, in a much earlier version of Keyman for Windows, we even inserted deadkeys as invisible 'embeds' into the text stream. This worked well in Word, but the vast majority of even TSF-aware apps didn't support embedding data objects, so we stripped that back out again. (And it turns out that that was better because deadkeys work best as ephemeral state-carrying markers!) But in that scenario, Keyman carried no state at all between keystrokes but queried the document every keystroke to determine the full context.

I'm not sure if I am articulating what I mean clearly though!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think the trouble is describing what is occuring without words that carry multiple meanings.


### Simulating text operations

Because TSF APIs cannot be relied upon in non-compliant applications, Keyman may simulate text operations by generating keyboard events:

- **Backspace key events** are used to delete previously typed characters
- **Synthetic key events** may be used to insert characters

These simulated events are asynchronous and are processed by the application in sequence. As a result, text deletion and insertion may not always behave exactly as intended, particularly when dealing with composed characters or surrogate pairs.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may need a little more elaboration. The main reason text deletion may not behave as expected is because there is no fixed definition of how many codepoints should be deleted in a Backspace key event -- one code point, a grapheme cluster, or something else. As a result, applications may have varied behavior here.

Very old applications may not handle surrogate pairs correctly and split them apart, but this is now rare.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have decided to add link to the wiki to go into the weeds on the deletions you mention here.