Skip to content

v1.15.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 10:22
· 51 commits to main since this release

Install

npx -y @harusame64/desktop-touch-mcp

Changes

  • A window that closes stops catching your clicks. When an app you had been
    working with went away, its last known rectangle stayed in the window cache
    and kept answering "the click at this point belongs to me" — with no expiry
    and nothing to remove it. Depending on where the point landed you would see
    clicks refused with "coordinates are outside the target window", or worse, a
    click quietly delivered to whatever now occupies that part of the screen.
    Restarting the server was the only thing that reliably cleared it, which is
    why reconnecting appeared to be the fix.

    Two things change. A cached window now stops being a click target once its
    entry ages past the cache lifetime, instead of never. And when a window that
    was already resolved stops reporting a position at all, its entry is dropped
    at that moment, and the click is refused with Target window rect could not be read — the window may have closed rather than being waved through unchecked.

    An expired entry means "check again", not "unreachable": when a click lands
    somewhere the cache cannot currently account for, the window list is re-read
    once and the click proceeds normally if the window is there. So a window that
    is open and simply has not been looked at for a while stays clickable, and a
    click that is refused has actually been checked rather than merely missing
    from a cache.

    A click that names a window is now refused when the point turns out to be
    inside a different application's window, and the response says which window is
    actually there. It used to be delivered to that other window and reported as a
    success, with the mismatch mentioned only in the server's own log. The same
    applies when the named window is not open at all — the click is refused and
    the response names the window that occupies the point, instead of quietly
    clicking it. A click whose point lands in another window of the SAME
    application still goes through — a dialog the application opened, but also a
    second window of it — so this refusal is about hitting a different program,
    not a different window. A browser window still matches its browser's name
    ("Google Chrome", "Microsoft Edge") even when the page title comes first.

    What you may notice: a click that used to land somewhere unintended now
    fails, and tells you either that the target window was replaced or which
    window your coordinates actually fall in. Take a fresh screenshot and carry
    on. A window that is open and has not moved, addressed by a title it still
    carries or by its handle, behaves exactly as before.

    Not covered by this change: the separate cache of screenshot-time window
    positions, used to correct coordinates when a window moves between the
    screenshot and the click. It keeps entries for 90 seconds and does not record
    which window they belong to, so a window that closes and reopens can still
    skew that correction until the entry expires. That one is a separate fix.

  • The diagnostic log now records how a terminal window relates to the server
    process itself.
    When a write lands in a terminal, it is worth knowing
    whether that terminal is the one this server is running inside — the failure
    mode where a command is typed into your own session instead of the window you
    named. The log now answers that from data rather than from guesswork: at
    startup a topology_snapshot record notes the console window this process is
    attached to (if any), whether it owns a console host of its own, and the chain
    of processes it was launched from; then a keyboard or terminal write that
    resolves to a terminal window appends a topology_relation record saying who
    owns that window, whether that owner is the server process or one of its
    ancestors and how far up, and — for a classic console — whether the console
    host's parent is still alive. Records are written for every such destination,
    not only the suspicious ones, so ordinary use produces a usable baseline.
    Reads are not recorded, so a long terminal(action='run') does not bury the
    writes under its own polling.

    When a destination does turn out to be owned by this server's own process
    tree, the tool adds a non-blocking warning to its response where it reports
    warnings at all. Nothing is refused and nothing is retargeted — the check
    is deliberately imprecise (Windows Terminal hosts several unrelated windows in
    one process, so it can point at a window that is perfectly fine to write to),
    and it exists to measure how often the situation arises, not to guard against
    it. Turn the whole log off with DESKTOP_TOUCH_DIAGNOSTIC_LOG_DISABLE=1.

  • The diagnostic log now records how a windowTitle was resolved, and where
    the input actually went.
    When a windowTitle matches more than one window,
    or matches none and a fallback picks a window by process name instead, the
    tool response has never said so — the keystrokes simply arrive somewhere else.
    Each resolution now appends a resolve record to the diagnostic log with the
    number of matching windows, the window that was chosen, the runners-up, and a
    flag when the process-name fallback fired; each input dispatch — keyboard,
    terminal, scroll and desktop_act's background writes — appends a
    matching dispatch_sink record naming the channel used, the window it was
    addressed to, and the window that was in the foreground at that instant. The
    record is written immediately before the write leaves the process, so a call
    that is refused or fails first is not on record as having sent anything. Records from one tool call share a correlation id, so a
    resolution can be matched to the write it produced even when calls overlap.
    Behaviour is unchanged — nothing is refused or retargeted because of this —
    with one addition: scroll with a windowTitle that matches several windows
    now returns a N windows match "…"; using the frontmost warning on the
    response, the same wording other tools already use.

    Window titles are hashed by default. A title can carry a file name, a mail
    subject, or a browser page title, and the diagnostic log is on by default, so
    titles and queries are recorded as a short hash plus their length. Set
    DESKTOP_TOUCH_RESOLVE_LOG_RAW=1 to record the text in clear alongside the
    hash — useful when reading your own log, and best left off otherwise. The log
    lives at %USERPROFILE%\.desktop-touch-mcp\logs\diagnostic.log; set
    DESKTOP_TOUCH_DIAGNOSTIC_LOG_DISABLE=1 to turn the whole log off.

  • keyboard write actions (type / press / sequence) now require a
    destination.
    Calls without windowTitle or hwnd used to land on whatever
    window happened to be foreground at that instant — including a window you had
    just clicked into yourself. They now stop with a typed
    code:"DestinationRequired" error and recovery suggestions before any key is
    sent
    , so a missing target costs one retry instead of stray characters in
    your own editor. Passing hwnd on its own is enough, even for a window with
    no title. Nothing changes for calls that already name a target, and
    mouse_* / terminal are unaffected. To deliberately type into the current
    foreground window, set DESKTOP_TOUCH_REQUIRE_DESTINATION=0: the stop becomes
    a warning on the response instead — never a silent pass. Setting
    DESKTOP_TOUCH_AUTO_GUARD=0 still turns the whole guard layer off, this check
    included. A windowTitle that is empty or only spaces counts as no target at
    all. A window that has no title can be addressed by hwnd, but only while it
    is already the foreground window — keyboard focus and guarding cannot target a
    titleless window yet — and that call comes back with a warning saying the
    input was delivered unguarded.

  • The npm launcher no longer kills the runtime mid-startup when its own stdin
    is already closed at launch.
    In closed-stdin environments — CI harnesses,
    one-shot checks like npx … --help with input redirected from an empty
    source, or clients that close stdin immediately — the launcher treated the
    end-of-input as a shutdown request and force-stopped the runtime after
    1 second. The runtime needs longer than that just to load its native
    components, so it was terminated before it could even parse its command
    line and the command exited with no output. The launcher now always
    allows the runtime a 10-second startup window measured from launch:
    end-of-input never forces a stop before that window has passed, and
    after it has passed the previous 1-second limit applies as before.
    Interactive use and normal MCP clients (which hold stdin open and shut
    down promptly when their input ends) are unaffected.