5 min readJonas Höttler

Why a window is so hard to recognise twice

A window has no lasting name. Its ID dies when it closes, its title moves while you work, and its app will happily open forty more that look identical. Here is how a note finds its way back to the right one.

Attaching a note to a window sounds like a five-minute job. You have a window, it has an identifier, you write the identifier next to the note, and tomorrow you look it up again.

The identifier is the part that does not work. On macOS a CGWindowID is unique only for as long as the window is open, and the number is handed out again afterwards. On Windows an HWND behaves the same way. Store one, quit the app, come back, and the number either points at nothing or — worse — at somebody else's window.

So the interesting question is not how to draw a sticky note. It is: what is a window, such that it can be recognised tomorrow?

A window has no name, only symptoms

What an operating system will tell you about a foreign window is a short list:

  • which application owns it, as a bundle identifier and a display name
  • its title, as it reads right now
  • where it is and how big it is
  • sometimes, if the app is generous, the path of the document it is showing

That is the entire evidence base. There is no field called identity, and every item on that list can change without the window changing.

The title is the worst offender, because it is also the most useful. It moves constantly:

TimeWhat the window reportsWhat is compared
09:14importer.dart — cellalertimporter.dart — cellalert
09:31● importer.dart — cellalertimporter.dart — cellalert
10:02● importer.dart — cellalert — Zedimporter.dart — cellalert
10:47importer.dart — cellalertimporter.dart — cellalert
Four titles in ninety minutes, one window. On the right, what survives normalisation — and what is actually compared.

The same window, ninety minutes apart, reporting four different strings. A naive comparison would decide three times that this is a different window and drop the note.

Normalise first, compare second

So nothing is compared raw. Before a title is used as evidence it is stripped of the things that are known to move:

  • the unsaved-changes marker some editors put in front of the name (● file.txt)
  • unread counters ((12) Inbox)
  • a trailing application name, where the app appends one
  • terminal window sizes (— 80×24)

What is left is the part a human would call "the title of that window". It is stable enough to compare, and stripping it is precisely what lets a window stay matchable while it is being used rather than only when it is idle.

There is a second, subtler rule here. A trailing segment like — cellalert usually names the project a file belongs to, which is a genuinely useful signal — two files in the same repository probably belong to the same working context. But some trailing segments recur across completely unrelated windows: bare numbers, window dimensions, shell names. Those are never taken as a project signal, because a signal that matches everything matches nothing.

A ladder, not a rule

With normalisation done, the actual matching is a ranked list. Every note carries a fingerprint — bundle identifier, application name, the title as it read when the note was made, the document path if there was one — and on every change those fingerprints are re-bound onto the windows that are open right now, strongest signal first.

  1. Document path
  2. Window title
  3. A former title
  4. Project
  5. Similarity
  6. Position
  7. Only window
Greedy over the whole candidate set: the strongest evidence anywhere wins first, and a certain match can never be stolen by a weaker one.

The ordering is the whole design. A document path outranks a title because a file is an identity and a title is a description of one. A remembered title outranks a similarity score because it is something the window actually wore. Position outranks nothing except desperation.

And the assignment is greedy over the whole candidate set, not window by window. That matters: if note A could plausibly attach to windows 1 and 2, and note B can only attach to window 1 with certainty, B takes window 1 first and A goes to window 2. Matching each note in isolation would let a weak guess steal a window a strong match needed.

Two rules that sit above the ladder

The ladder decides between plausible answers. Two rules decide what is plausible at all.

Different documents in the same app never match. Two windows of the same editor showing two different files are two different things, whatever the similarity score says about their titles. Putting your note about the migration on the wrong source file is worse than showing you no note at all, and it is worse in a way you might not notice for an hour.

The confidence is on screen. Each match reports whether it is certain, probable, or something you assigned by hand — and a probable match says so, in the interface, next to the note. This is not a disclaimer. An admitted guess is genuinely more useful than a confident wrong answer, because it tells you when to look twice.

Only certainty is allowed to teach

The last piece is the one that took the longest to get right.

While a binding is certain — an exact title match, a document path, a note you pinned by hand — the app collects every title that window subsequently wears, up to eight of them, and records where it sat. After a restart, any of those remembered titles re-identifies it. This is what makes terminals work at all: a terminal's title is whatever process is running in it, so it may have worn six different strings by lunchtime, and any one of them is now a way back in.

The temptation is to let probable matches teach as well. It is exactly one line of code, and it doubles the amount the app learns.

It is also the bug that ruins the product. A probable match is a guess. If a guess is allowed to write itself back into the fingerprint, one wrong guess becomes permanent evidence for the next wrong guess, and the note drifts to a window it never belonged to — quietly, and for good. So probable matches display, and they never teach.

What this buys

The result is undramatic, which is the point. You close a window, restart the machine the next morning, open the same project, and the note is there — with a small label saying how sure the app is that this is the right window.

The note is a child of the window — not of the screen.

None of the above is visible while it works. All of it is visible the first time it does not, which is why the confidence label exists.

Questions this raises
Does a window have a permanent identifier?
No. On macOS a CGWindowID is unique only while the window exists and is reused afterwards; on Windows an HWND behaves the same way. Neither survives a restart of the app, let alone of the machine.
What happens if two windows match equally well?
Assignment is greedy over the whole candidate set, so the strongest evidence anywhere is placed first and a certain match can never be displaced by a weaker one. Where nothing separates two candidates, the match is reported as probable rather than certain.
Why are window titles normalised before they are compared?
Because they move while you work. Unsaved-changes markers, unread counters, trailing app names and terminal window sizes all change without the window changing. Stripping them is what keeps a window matchable during use.
Filed underFensterTechnikmacOS

The app this was written next to

Balane WindowNote pins a note to a window, and gives it back to you when the window comes back. Free for two windows, on macOS and Windows.

Sources and further reading
More from the journal