Bug 4660 - Option for grabbing keys ("send system keys") in non-fullscreen mode
Summary: Option for grabbing keys ("send system keys") in non-fullscreen mode
Status: NEW
Alias: None
Product: ThinLinc
Classification: Unclassified
Component: VNC (show other bugs)
Version: trunk
Hardware: PC Unknown
: P2 Normal
Target Milestone: MediumPrio
Assignee: Pierre Ossman
Keywords: upstream
Depends on: 4975 5505
Blocks: keyboard
  Show dependency treegraph
Reported: 2013-05-27 12:51 CEST by Peter Åstrand
Modified: 2023-09-20 13:00 CEST (History)
2 users (show)

See Also:
Acceptance Criteria:


Description Peter Åstrand cendio 2013-05-27 12:51:57 CEST
When bug 3417 was implemented, the functionality was restricted to fullscreen mode. This is difficult from how rdesktop works (which was given in the initial description). Also, NoMachine has an option "Grab the keyboard when the client has focus" (http://www.nomachine.com/documents/configuration/client-guide.php), and customers have requested this functionality.
Comment 2 Peter Åstrand cendio 2013-05-28 13:24:24 CEST
One use case for this feature is the ability to switch windows in the remote session. On some client platforms (ie Linux), it is sometimes possible to remap so that two different keyboard combinations are used, ie Alt-Tab switches windows locally while Super-Tab (Windows key) switches windows in the remote session. Unfortunately, this does not work on Windows clients, since the key has different uses on that platforms.
Comment 3 Pierre Ossman cendio 2013-11-04 12:28:47 CET
There is another suggestion regarding this on the TigerVNC mailing list:

Comment 4 Samuel Mannehed cendio 2018-04-04 14:14:47 CEST Comment hidden (obsolete)
Comment 5 Samuel Mannehed cendio 2018-04-10 12:50:14 CEST Comment hidden (obsolete)
Comment 6 Pierre Ossman cendio 2018-05-09 13:11:03 CEST
This is not currently possible on macOS, given the current method of grabbing the keyboard.

However there is an alternative method, using "event taps" (see CGEventTap). It seems to provide the functionality we need, but the downside is that it requires some user interaction to activate (which is sensible given the possible problems low level event access can cause).

There is an old API, AXIsProcessTrusted(), that allows us to check if we can do what we want. There is also a newer event, AXIsProcessTrustedWithOptions(), that will notify and help the user if we are not "trusted".

We also need to check how this system behaves with our dual bundle design, as the "trust" is associated with a bundle.
Comment 7 Pierre Ossman cendio 2018-05-09 13:16:28 CEST
The current keyboard grabbing method is also causing other issues, like bug 7160.
Comment 8 Pierre Ossman cendio 2021-09-03 14:49:40 CEST
There is also a macOS API called PushSymbolicHotKeyMode() that might do what we want.

We could also see if we can figure out what Apple's own screen sharing client does since it apparently forwards all key presses. Several sources claim it has a background service called RFBEventHelper that is essential for this to work.
Comment 9 Pierre Ossman cendio 2021-11-16 16:51:20 CET
Upstream issue:

Comment 10 Pierre Ossman cendio 2021-12-22 17:40:04 CET
(In reply to Pierre Ossman from comment #8)
> We could also see if we can figure out what Apple's own screen sharing
> client does since it apparently forwards all key presses. Several sources
> claim it has a background service called RFBEventHelper that is essential
> for this to work.

It seems to use the EventTap system, given that it imports those symbols:

> $ cbrun osx64 x86_64-apple-darwin10-otool -v -I RFBEventHelper.bundle/Contents/MacOS/RFBEventHelperd | grep Event
> RFBEventHelper.bundle/Contents/MacOS/RFBEventHelperd:
> 0x0000000100005de8    29 _CGEventGetFlags
> 0x0000000100005dee    30 _CGEventGetIntegerValueField
> 0x0000000100005df4    31 _CGEventSetType
> 0x0000000100005dfa    32 _CGEventTapCreate
> 0x0000000100005e00    33 _CGEventTapEnable
> 0x000000010000c0d8    29 _CGEventGetFlags
> 0x000000010000c0e0    30 _CGEventGetIntegerValueField
> 0x000000010000c0e8    31 _CGEventSetType
> 0x000000010000c0f0    32 _CGEventTapCreate
> 0x000000010000c0f8    33 _CGEventTapEnable

There is nothing about PushSymbolicHotKeyMode() among the imported symbols.

It's unclear how it gets the extra privileges it needs. I don't see anything obvious in Info.plist. This is a system installed service though so it might have a hard coded back door for this.
Comment 11 Pierre Ossman cendio 2021-12-30 09:23:36 CET
A quick test shows that event taps seem to work fine for this. And I can confirm we don't have privileges to do this by default, and AXIsProcessTrustedWithOptions() will indeed prompt the user in that case. It allows the user to quickly open the correct part of the system settings and give us the necessary access. Once the user does that things start to work right away. No need to restart things. We just need to attempt to do set up for the event tap again.

Googling the error message you get ("Foo.app would like to control this computer using accessibility features") you get some hits saying that applications like Steam and Dropbox do this. So users should be somewhat used to them, which is fortunate as the error message is rather misleading as we don't need this for accessibility features.

This article discusses the issue a bit:


Event taps have been around for forever, but AXIsProcessTrustedWithOptions() was added in macOS 10.9. This makes things a bit messy for us as we are stuck on 10.6. It is possible to work around it using dlsym(), but it would be nice to raise our system requirements (bug 5878).

I also haven't tested how/if this works with the nested bundling we have in ThinLinc. I've only tried it with a standalone vncviewer so far.
Comment 12 Pierre Ossman cendio 2022-01-21 12:34:10 CET
I tested the nesting a bit and it seems like vncviewer is considered part of the tlclient bundle in this regard. Giving access to tlclient also gives access to vncviewer (at least according to AXIsProcessTrusted()).

However another issue popped up. Signatures seem to affect how the trust is handled. Which makes sense as otherwise a malicious application could easily claim to be a trusted application and get access it shouldn't.

I saw this by the fact that I was not able to get my custom build trusted, even if I enabled the trust in settings. The system console showed a lot of errors with an error code that means that the software is unsigned (forgot to note the exact number).

However if I changed the CFBundleIdentifier of the bundle, then a new entry popped up in settings. Enabling that got things working. I still saw some signature errors in the system console, but not as many.

Unfortunately knowledge about application bundles is cached somewhere in the system. Removing an application from the trust settings is not enough as it will just get restored on the next access request, and the signature is remembered.

I've not been able to figure out where this cache is, or how to control it. I've not been able to come up with a search query that gives any decent results. It also takes the name from one specific bundle (we have loads of ThinLinc bundles on the test mac), not the name stored in the plist. Not sure why it picked that specific one over all others.

We'll need to play around with this a bit more to understand how this caching works.

Worst case is that we have to make sure every build is signed for keyboard grabs to work. This could also cause issues if we need to change our signing certificate in the future.
Comment 13 Pierre Ossman cendio 2022-01-21 15:54:13 CET
I played around a bit more with how it tracks things.

You can right click an entry in the settings and get the location of the application. If you delete that (putting it in the bin is sufficient), then the entry in the settings is replaced with a different instance of the same application (I assume mathing CFBundleIdentifier).

If you keep doing this then eventually you'll get your unsigned custom build as the target. At this point it will start granting access to your unsigned bundle!
(you may need to enable/disable it first, or even remove/add)

So there doesn't seem to be a memory of the signing certificate itself. Instead it seems to know all copies of the application on the system.

That means that if you want to test an unsigned version, all you need to do is remove all signed versions and it should gladly let you play around without signatures.

As a side note, once you done this then it is no longer possible for a *signed* version to get access. So the signature check seems to work both ways.

I'm not sure what this means for upgrades though where the new version completely replaces the old. If it doesn't remember the old signature, then how can it verify that the new version has the same source?

Note You need to log in before you can comment on or make changes to this bug.