deskflow / deskflow/deskflow

macOS: client traps (SIGTRAP/dispatch_assert_queue) in OSXKeyState::pollActiveGroup() on forwarded key events — TIS main-queue assert, still unguarded on master

Open
#10,136 2 comments 0 reactions 0 assignees View on GitHub
:apple: mac :large_orange_diamond: pending response
Dominant language
C++
Stars
28.8k
Forks
4.9k
Avg merge
2d 17h
Merged PRs (30d)
21

Description

### Deskflow version info

```
Deskflow: 1.26.0.0
Qt: 6.10.1
System: macOS 26.5.2 (25F84), Apple Silicon (Mac16,10)
```

### Build types

- Official release (installer/package from GitHub releases)

### Deskflow configuration

- Windows server, macOS 26.5.2 client (Mac mini, Apple Silicon)
- Client runs headless as a LaunchAgent (`org.deskflow.client`, `KeepAlive=true`), GUI not required
- Single monitor per machine
- Both systems report language `tr` (`local languages: tr` / `remote languages: tr`)

### What steps will reproduce the problem?

1. Windows server, macOS client, both with a Turkish (non-ABC) input source active.
2. Connect the macOS client to the server.
3. Switch to the macOS screen and type on the Windows keyboard.
4. `deskflow-core` traps with `EXC_BREAKPOINT (SIGTRAP)`. Because the client is a LaunchAgent with `KeepAlive=true`, launchd immediately respawns it, so the visible symptom is "the keyboard dies for a second and comes back" rather than an app disappearing.

The crash is **intermittent, not every keystroke** — it fires when HIToolbox needs to refresh its input-source list rather than serve it from cache. In one session it crashed 3 times within 3 minutes (19:15, 19:17, 19:17), then ran fine for 2 minutes.

### The crash

Two distinct call sites, same root cause. **The first one is not covered by #9924** — it is on the *client*, in the keystroke path:

**A) `OSXKeyState::pollActiveGroup()` via `KeyState::fakeKeyDown()` — on every forwarded key event**

```
Thread 5 CRASHED:
0 libdispatch.dylib _dispatch_assert_queue_fail + 120
1 libdispatch.dylib dispatch_assert_queue$V2.cold.1 + 116
2 libdispatch.dylib dispatch_assert_queue + 108
3 HIToolbox islGetInputSourceListWithAdditions + 160
4 HIToolbox isValidateInputSourceRef + 88
5 HIToolbox TSMGetInputSourceProperty + 36
6 deskflow-core OSXKeyState::pollActiveGroup() const + 40
7 deskflow-core KeyState::fakeKeyDown(unsigned int, unsigned int, unsigned short, std::string const&) + 252
8 deskflow-core ServerProxy::parseMessage(unsigned char const*) + 980
9 deskflow-core ServerProxy::handleData() + 188
10 deskflow-core EventQueue::dispatchEvent(Event const&) + 332
11 deskflow-core StreamFilter::filterEvent(Event const&) + 84
12 deskflow-core EventQueue::dispatchEvent(Event const&) + 332
13 deskflow-core EventQueue::loop() + 344
14 deskflow-core App::runEventsLoop(void const*) + 24
15 deskflow-core Thread::threadFunc(void*) + 128
16 deskflow-core ArchMultithreadPosix::doThreadFunc(ArchThreadImpl*) + 76
17 deskflow-core ArchMultithreadPosix::threadFunc(void*) + 56
18 libsystem_pthread _pthread_start + 136
19 libsystem_pthread thread_start + 8
```

**B) `AppUtilUnix::getKeyboardLayoutList()` via `ServerProxy` construction — on connect**

```
0 libdispatch.dylib _dispatch_assert_queue_fail + 120
1 libdispatch.dylib dispatch_assert_queue$V2.cold.1 + 116
2 libdispatch.dylib dispatch_assert_queue + 108
3 HIToolbox islGetInputSourceListWithAdditions + 160
4 HIToolbox TSMGetInputSourceCountWithFilteredAdditions + 40
5 HIToolbox TISCreateInputSourceList + 100
6 deskflow-core AppUtilUnix::getKeyboardLayoutList() + 132
7 deskflow-core ServerProxy::ServerProxy(Client*, deskflow::IStream*, IEventQueue*)
```

Variant B is the client-side mirror of #9924 (which was on the server, via `ClientProxy1_8::synchronizeLanguages()`).

`Thread 0` is parked in `-[NSApplication run]` (`runCocoaApp`) in both cases — the main queue is alive and idle, the TIS call is simply made from `EventQueue::loop()`'s thread.

### Why I'm filing this rather than commenting on #9924

#9924 was closed as *not planned / cannot reproduce* on macOS 27 beta 3. This report differs on three points:

1. **It reproduces on shipping macOS 26.5.2 (25F84)**, not a 27 beta. The main-queue enforcement on the TIS/TSM path is not exclusive to macOS 27.
2. **Call site A is new.** #9924 only covers `getKeyboardLayoutList()`. `OSXKeyState::pollActiveGroup()` reaches `TSMGetInputSourceProperty` through `isValidateInputSourceRef`, and sits directly in `fakeKeyDown()` — so it is hit on forwarded key events, not just at connection time. Fixing `AppUtilUnix.cpp` alone would leave this path crashing.
3. It is the **client** side; #9924 was the server side.

@amagus — in #9924 you asked for a full all-thread stack trace. Full `.ips` reports for 7 crashes are attached below; happy to provide a debug build trace or any other data.

### Additional information

- **Not a regression — present since first use.** This is a clean install, not an upgrade: Deskflow 1.26.0.0 was installed 2026-09-05 00:42 and the first crash is 2026-09-05 03:56, ~3 hours later. Both `Deskflow.conf` and `Deskflow.conf.bak` record `lastVersion=1.26.0.0`, so no earlier version ever ran here and I cannot claim an earlier release behaved differently. The macOS install itself has been unchanged since 2026-06-25 (`SystemVersion.plist` mtime; `InstallHistory.plist` shows only XProtect updates since), so this is not an OS-update side effect either.
- **Still unfixed on `master`.** At `a930c87ea4ec`, `OSXKeyState::pollActiveGroup()` still guards its `TISCopyCurrentKeyboardLayoutInputSource()` / `TISGetInputSourceProperty()` pair with `g_tisMutex` alone. A mutex serialises Deskflow's own threads but cannot satisfy `dispatch_assert_queue(main_queue)`, so the failing call is unchanged since the version I am running. The 2026-05-08 commit *"fix(mac): protect TIS API calls with a mutex (they are not thread safe)"* addresses thread-safety, which is a different constraint from the queue-affinity one that traps here.
- **7 crash reports**, all `EXC_BREAKPOINT (SIGTRAP)` / `dispatch_assert_queue`: 4 on 2026-09-05 (2× variant B, 2× variant A), 3 on 2026-09-07 (all variant A).
- **Does it stop me using Deskflow?** Not entirely — `KeepAlive` masks it — but typing on the Mac from the Windows keyboard is unreliable.
- **Restarting** the client clears it only until the next refresh of the input-source list.
- **Note on the sanity checks:** this is the official 1.26.0.0 release and I have not run a continuous build. Rather than guess, I read the current source instead — see the `master` note above; the crashing call site is unchanged, so a newer build should not be expected to behave differently. Happy to run one and confirm if that would still be useful.
- On the suggested fix — I note @amagus' comment in #9924 that "using the main queue is not an option on several of the TIS use cases". Call site A runs per-keystroke, so a `dispatch_sync` to the main queue there would also be a latency concern. Caching the active group and invalidating it from a main-queue `kTISNotifySelectedKeyboardInputSourceChanged` observer may fit better than dispatching per keystroke.

Show log

```ceylon
[2026-09-07T19:17:42.165] NOTE: configured 1 server address(es)
[2026-09-07T19:17:42.179] NOTE: started client
[2026-09-07T19:17:42.179] IPC: connecting to '': :24800
2026-09-07 19:17:42.206 deskflow-core[27315:520335] starting cocoa loop
[2026-09-07T19:17:42.249] IPC: peer fingerprint:
[2026-09-07T19:17:42.250] INFO: connected to secure socket
[2026-09-07T19:17:42.250] INFO: server tls certificate info: /CN=Deskflow
[2026-09-07T19:17:42.250] INFO: network encryption protocol: TLSv1.3
[2026-09-07T19:17:42.270] INFO: local languages: tr
[2026-09-07T19:17:42.275] INFO: remote languages: tr
[2026-09-07T19:17:42.281] WARNING: cursor may not be visible
[2026-09-07T19:17:42.283] IPC: connected to server
[2026-09-07T19:17:55.316] INFO: entering screen
[2026-09-07T19:17:55.319] INFO: clipboard was updated
[2026-09-07T19:17:55.321] INFO: clipboard was updated
<-- crash here, launchd respawns
initial settings file: /Users//Library/Deskflow/Deskflow.conf
[2026-09-07T19:17:59.569] NOTE: configured 1 server address(es)
[2026-09-07T19:17:59.583] NOTE: started client
[2026-09-07T19:17:59.584] IPC: connecting to '': :24800
2026-09-07 19:17:59.609 deskflow-core[27316:520671] starting cocoa loop
[2026-09-07T19:17:59.651] INFO: connected to secure socket
[2026-09-07T19:17:59.676] INFO: local languages: tr
[2026-09-07T19:17:59.681] INFO: remote languages: tr
[2026-09-07T19:17:59.688] IPC: connected to server
[2026-09-07T19:18:27.470] INFO: entering screen
```

Contributor guide

Open the contributing guide

Research direction

Start with OSXKeyState::pollActiveGroup() and AppUtilUnix::getKeyboardLayoutList(), then trace their callers through KeyState::fakeKeyDown() and ServerProxy construction. Review the existing g_tisMutex protection and the related discussion in #9924. Done means TIS calls no longer trigger dispatch_assert_queue crashes during connection or forwarded key events, including input-source refreshes.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, macos
Domain
desktop-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.