How to know when a HID USB/Bluetooth device is connected in Cocoa? - macos

How can I get a simple call back when a HID device, or at last, any USB/Bluetooth device gets connected/disconnected?
I made a simple app that shows the connected joysticks and the pressed buttons/axis for mac in a pretty way. Since I am not very familiar with cocoa yet, I made the UI using a webview, and used the SDL Joystick library.
Everything is working nice, the only problem is that the user needs to scan for new joysticks manually if he/she connects/disconnects something while the program is running.
With a callback, I I can just call the Scan function. I don't want to handle the device or do something fancy, just know when there is something new happening...
Thanks.

Take a look at IOServiceAddMatchingNotification() and related functions. I've only worked with it in the context of serial ports (which are in fact USB to serial adapters, though that doesn't matter), but it should be applicable to any IOKit accessible device. I'm not sure about Bluetooth, but it should at least work for USB devices. Here's a snippet of code I use:
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notificationPort),
kCFRunLoopDefaultMode);
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
CFRetain(matchingDict); // Need to use it twice and IOServiceAddMatchingNotification() consumes a reference
CFDictionaryAddValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type));
io_iterator_t portIterator = 0;
// Register for notifications when a serial port is added to the system
kern_return_t result = IOServiceAddMatchingNotification(notificationPort,
kIOPublishNotification,
matchingDictort,
SerialDeviceWasAddedFunction,
self,
&portIterator);
io_object_t d;
// Run out the iterator or notifications won't start (you can also use it to iterate the available devices).
while ((d = IOIteratorNext(iterator))) { IOObjectRelease(d); }
// Also register for removal notifications
IONotificationPortRef terminationNotificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(terminationNotificationPort),
kCFRunLoopDefaultMode);
result = IOServiceAddMatchingNotification(terminationNotificationPort,
kIOTerminatedNotification,
matchingDict,
SerialPortWasRemovedFunction,
self, // refCon/contextInfo
&portIterator);
io_object_t d;
// Run out the iterator or notifications won't start (you can also use it to iterate the available devices).
while ((d = IOIteratorNext(iterator))) { IOObjectRelease(d); }
My SerialPortDeviceWasAddedFunction() and SerialPortWasRemovedFunction() are called when a serial port becomes available on the system or is removed, respectively.
Relevant documentation is here, particularly under the heading Getting Notifications of Device Arrival and Departure.

Use IOHIDManager to get the notifications.

Based on the earlier answers from Andrew and Arjuna, I ended up with the following snippet using IOHIDManager that should work with an Apple HID device (e.g. a bluetooth trackpad was tested). This appears to also send notifications more than once without needing to clear/decrement anything.
- (void) startHIDNotification
{
ioHIDManager = IOHIDManagerCreate ( kCFAllocatorDefault, kIOHIDManagerOptionNone );
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey);
CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDManufacturerKey), CFSTR("Apple"));
IOHIDManagerSetDeviceMatching (ioHIDManager, matchingDict);
IOHIDManagerRegisterDeviceMatchingCallback( ioHIDManager, AppleHIDDeviceWasAddedFunction, (__bridge void *)(self) );
IOHIDManagerRegisterDeviceRemovalCallback( ioHIDManager, AppleHIDDeviceWasRemovedFunction, (__bridge void *)(self) );
hidNotificationRunLoop = CFRunLoopGetCurrent();
IOHIDManagerScheduleWithRunLoop(ioHIDManager,
hidNotificationRunLoop,
kCFRunLoopDefaultMode);
}
and the callback methods
void AppleHIDDeviceWasAddedFunction( void * context,
IOReturn result,
void * sender,
IOHIDDeviceRef device)
{
NSLog(#"added");
}
void AppleHIDDeviceWasRemovedFunction( void * context,
IOReturn result,
void * sender,
IOHIDDeviceRef device)
{
NSLog(#"removed");
}

Related

How to change state of Bluetooth on iOS is PowerOn on Xamarin Forms?

On iOS, I only can check state of Bluetooth. I'm find the solutions on network and use it.
public class CallBluetoothIphoneService : ICallBlueTooth
{
public void LaunchBluetoothOnPhone()
{
try
{
// Is bluetooth enabled?
var bluetoothManager = new CBCentralManager();
if (bluetoothManager.State == CBCentralManagerState.PoweredOff|| bluetoothManager.State == CBCentralManagerState.Unknown)
// Does not go directly to bluetooth on every OS version though, but opens the Settings on most
UIApplication.SharedApplication.OpenUrl(new NSUrl("App-Prefs:root=Bluetooth"));
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
}
But when I try turn off Bluetooth and test code, state of bluetooth is "Unknown".
Then I run code, device open settings, toggle button has color green (turn on bluetooth), but when I check state of Bluetooth in code, State of Bluetooth is "Unknown", is not "Power on".
I'm using Xamarin 3.3 and test on device iOS version 12.0.
I am not sure exactly what you want to do, but if your intent is to open the Bluetooth settings page, this:
UIApplication.SharedApplication.OpenUrl(new NSUrl("App-Prefs:root=Bluetooth"));
won't work. Apple has at some points allowed this (iOS 8 IIRC) and at other points it has disallowed this (most versions of iOS). See this long SO thread about this issue: How to open Settings programmatically like in Facebook app?
Regardless, there is no need. When iOS detects that your app has created a CBCentralManager type with delegate, iOS will display an alert to the user that allows them to go to the bluetooth settings to enable bluetooth by tapping the "Settings" button in the alert.
As far as always getting state as "Unknown", you need to check the state in the delegate for the CBCentralManager. You cannot use the parameterless CBCentralManager constructor new CBCentralManager();. Check the apple docs: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager?language=objc and note that there are only two listed init methods, one that takes delegate and queue parameters, and one that takes delegate, queue, and options parameters, although no one complains if you use the parameterless constructor... but you will never get the correct state if you use it. See: https://stackoverflow.com/a/36824770/2913599
So try this:
public class CallBluetoothIphoneService : ICallBluetooth
{
public void LaunchBluetoothOnPhone()
{
try
{
// Is bluetooth enabled?
var bluetoothManager = new CBCentralManager(new MySimpleCBCentralManagerDelegate(), DispatchQueue.CurrentQueue);
// This will always show state "Unknown". You need to check it in the delegate's UpdatedState method
Console.WriteLine($"State: {bluetoothManager.State.ToString()}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
public class MySimpleCBCentralManagerDelegate : CBCentralManagerDelegate
{
override public void UpdatedState(CBCentralManager mgr)
{
// You can check the state in this delegate method
Console.WriteLine($"UpdatedState: {mgr.State.ToString()}");
if (mgr.State == CBCentralManagerState.PoweredOn)
{
//Passing in null scans for all peripherals. Peripherals can be targeted by using CBUIIDs
CBUUID[] cbuuids = null;
mgr.ScanForPeripherals(cbuuids); //Initiates async calls of DiscoveredPeripheral
//Timeout after 30 seconds
var timer = new Timer(30 * 1000);
timer.Elapsed += (sender, e) => mgr.StopScan();
}
else
{
//Invalid state -- Bluetooth powered down, unavailable, etc.
System.Console.WriteLine("Bluetooth is not available");
}
}
public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
{
Console.WriteLine("Discovered {0}, data {1}, RSSI {2}", peripheral.Name, advertisementData, RSSI);
}
}
Bottom line: always create a CBCentralManager object with one of the following constructors:
CBCentralManager(ICBCentralManagerDelegate, DispatchQueue)
CBCentralManager(ICBCentralManagerDelegate, DispatchQueue, CBCentralInitOptions)

Xamarin.Forms - BLE any device connection lost

In the last project I use BLE plugin.
adapter.DeviceDiscovered += (s, a) =>
{
myDeviceList.Add(a.Device);
}
await adapter.StartScanningForDevicesAsync();
But right now I'm just looking for devices and adding what you find directly to this list.
I want this scan to work continuously and if any device gets lost, it can automatically delete it here.
BLE has StartScanningForDevicesAsync class but I don't know if this is useful for me.
event EventHandler<DeviceErrorEventArgs> DeviceConnectionLost;
//
// Summary:
// Occurs when a device has been disconnected. This occurs on intendet disconnects
// after Plugin.BLE.Abstractions.Contracts.IAdapter.DisconnectDeviceAsync(Plugin.BLE.Abstractions.Contracts.IDevice).
Is this possible?
I think, you can use something like this (pseudocode + C#):
StartTimer(TimeSpan.FromSeconds(30),
() =>
{
if (!isScanning)
{
new Handler().PostDelayed(StopScan, 10000); // stop
scanning after 10 sec
isScanning = true;
StartScan();
}
return true; // this result will tell to fire onTimer event again
});
Here StartScan and StopScan - functions you use for communicating with BLE

Not receiving messages after sometime

I am using JNA to access User32 functions (I dont think it has got to do with Java here, more of concept problem). In my application, I have a Java process which communicates with the Canon SDK. To dispatch any messages I am using the below function:
private void peekMessage(WinUser.MSG msg) throws InterruptedException {
int hasMessage = lib.GetMessage(msg, null, 0, 0);
if (hasMessage != 0) {
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
Thread.sleep(1);
}
peekMessage is called in a loop and it all works well. Whenever an Image is taken from camera, I get the event and do the rest.
But I have observed, say after about 15 seconds (sometimes never or sometimes just at start) of no activity with camera, taking picture does not give me any download event. Later the whole application becomes unusable as it doesn't get any events from camera.
What can be the reason for this? Please let me know of any other info needed, I can paste the respective code along.
Edit:
Initialization:
Map<String, Integer> options = new HashMap<String, Integer>();
lib = User32.INSTANCE;
hMod = Kernel32.INSTANCE.GetModuleHandle("");
options.put(Library.OPTION_CALLING_CONVENTION, StdCallLibrary.STDCALL_CONVENTION);
this.EDSDK = (EdSdkLibrary) Native.loadLibrary("EDSDK/dll/EDSDK.dll", EdSdkLibrary.class, options);
private void runNow() throws InterruptedException {
while (!Thread.currentThread().isInterrupted()) {
Task task = queue.poll();
if (task != null) {
int taskResult = task.call();
switch (taskResult) {
case (Task.INITIALIZE_STATE):
break;
case (Task.PROCESS_STATE):
break;
case (Task.TERMINATE_STATE): {
//queue.add(new InitializeTask());
Thread.currentThread().interrupt();
break;
}
default:
;
}
}
getOSEvents();
}
}
WinUser.MSG msg = new WinUser.MSG();
private void getOSEvents() throws InterruptedException {
if (isMac) {
receiveEvents();
} else {
peekMessage(msg);
}
}
Above, whenever I get my camera event, it add's it to the queue and in each loop I check the queue to process any Task. One more important information: This is a process running on cmd and has no window. I just need the events from my camera and nothing else.
The code where I register callback functions:
/**
* Adds handlers.
*/
private void addHandlers() {
EdSdkLibrary.EdsVoid context = new EdSdkLibrary.EdsVoid(new Pointer(0));
int result = EDSDK.EdsSetObjectEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsObjectEvent_All), new ObjectEventHandler(), context).intValue();
//above ObjectEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetCameraStateEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new StateEventHandler(), context).intValue();
//above StateEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetPropertyEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new PropertyEventHandler(), context).intValue();
//above PropertyEventHandler contains a function "apply" which is set as callback function
}
You are getting ALL messages from ALL windows that belong to this thread, that includes all mouse moves, paints etc. if you aren't rapidly calling this function your message queue will overflow and cause the behavior you describe.
The sleep you definitely don't want as GetMessage yields if no message is waiting.
So if there exists a normal message pump(s) (i.e GetMessage/DispatchMessage) loop somewhere else for this threads window(s) then you should let that pump do most of the work, perhaps use wMsgFilterMin, wMsgFilterMax to just get the event message you require; or even better in this case use peekmessage with PM_NOREMOVE (then you will need your sleep
call as peekmessage returns immediately).
Alternatively provide the hWnd of the window that generates the event to reduce the workload.
Use spy++ to look into which windows this thread owns and what messages are being produced.
To take this answer further please provide answers to: what else is this thread doing and what windows does it own; also is this message pump the only one or do you call into the SDK API where it may be pumping messages too?
There is an OpenSource project wrapping EDSDK with JNA and it has a version of your code that is probably working better:
https://github.com/kritzikratzi/edsdk4j/blob/master/src/edsdk/api/CanonCamera.java#L436
Unfortunately this is not platform independent and specifically the way things work on windows. I am currently in the process of trying to get a MacOS version of things working at:
https://github.com/WolfgangFahl/edsdk4j

How to pass window handle to wndproc?

I have written this code in c# application for tracking messages ...
protected override void WndProc(ref Message m)
{
// Listen for operating system messages.
switch (m.Msg)
{
case WM_CHAR:
FileStream fs = new FileStream("d:/Type.txt",FileMode.Append,FileAccess.Write);
//set up a streamwriter for adding text
StreamWriter sw = new StreamWriter(fs);
sw.BaseStream.Seek(0, SeekOrigin.End);
int no=(int)m.WParam;
sw.Write(Convert.ToChar(no));
sw.Flush();
sw.Close();
break;
}
base.WndProc(ref m);
}
I want to track messages for different window so how can I pass different window handle to wndproc ? please help me...
You'd have to register global keyboard hook instead of passing different handle. This article shows how to do that. Basic idea behind that is that you register your function for polling all keyboard-related system events (for every message) and filter out only those you need.
The functionality is achieved with SetWindowsHookEx winapi function.
You can filter all the messages in the application by calling Application.AddMessageFilter(IMessageFilter filter) at the beginning of your program.
The IMessageFilter interface has just one method:
bool PreFilterMessage(ref Message m);
That is called for every message handled by the application. There you can use m.HWnd to identify the different windows of your program.

CGEventTapCreate breaks down mysteriously with "key down" events

I'm using CGEventTapCreate to "steal" media keys from iTunes when my app is running. The code inside of the callback that I pass to CGEventTapCreate examines the event, and if it finds that it's one of the media keys, posts an appropriate notification to the default notification center.
Now, this works fine if I post a notification for the "key up" event. If I do that for "key down" events, eventually my app stops getting media key events and iTunes takes over. Any ideas on what can be causing this? The relevant part of the code is below
enum {
...
PlayPauseKeyDown = 0x100A00,
PlayPauseKeyUp = 0x100B00,
...
};
static CGEventRef event_tap_callback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
if (!(type == NX_SYSDEFINED) || (type == NX_KEYUP) || (type == NX_KEYDOWN))
return event;
NSEvent* keyEvent = [NSEvent eventWithCGEvent: event];
if (keyEvent.type != NSSystemDefined) return event;
switch(keyEvent.data1)
{
case PlayPauseKeyUp: // <--- this works reliably
//case PlayPauseKeyDown: // <--- this will break eventually
post_notification(#"PlayPauseMediaKeyPressed", nil, nil);
return NULL;
... and so on ...
Does something kill my event tap if the callback takes too long?
Some people suspect that Snow Leopard has a bug that sometimes disables event taps even if they don't take too long. To handle that, you can watch for the event type kCGEventTapDisabledByTimeout, and respond by re-enabling your tap with CGEventTapEnable.
First of all, why is your first "if" allowing key-down and key-up events to pass? Your second "if" only lets system events pass through anyway. So for all key-down/-up events you create a NSEvent, just to drop the event one line further downwards. That makes little sense. An Event Tap should always be as fast as possible, otherwise it will slow down all event processing of the whole system. Your callback should not even be called for key-down/-up events, since system events are not key-down/-up events, they are system events. If they were key events, you would for sure never access data1, but instead use the "type" and "keyCode" methods to get the relevant information from them.
static CGEventRef event_tap_callback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
NSEvent * sysEvent;
// No event we care for? return ASAP
if (type != NX_SYSDEFINED) return event;
sysEvent = [NSEvent eventWithCGEvent:event];
// No need to test event type, we know it is NSSystemDefined,
// becuase that is the same as NX_SYSDEFINED
Also you cannot determine if that is the right kind of event by just looking at the data, you must also verify the subtype, that must be 8 for this kind of event:
if ([sysEvent subtype] != 8) return event;
The next logical step is to split the data up into its components:
int data = [sysEvent data1];
int keyCode = (data & 0xFFFF0000) >> 16;
int keyFlags = (data & 0xFFFF);
int keyState = (keyFlags & 0xFF00) >> 8;
BOOL keyIsRepeat = (keyFlags & 0x1) > 0;
And you probably don't care for repeating key events (that is when I keep the key pressed and it keeps sending the same event over and over again).
// You probably won't care for repeating events
if (keyIsRepeat) return event;
Finally you should not define any own constant, the system has ready to use constants for those keys:
// Analyze the key
switch (keyCode) {
case NX_KEYTYPE_PLAY:
// Play/Pause key
if (keyState == 0x0A) {
// Key down
// ...do your stuff here...
return NULL;
} else if (keyState == 0x0B) {
// Key Up
// ...do your stuff here...
return NULL;
}
// If neither down nor up, we don't know
// what it is and better ignore it
break;
case NX_KEYTYPE_FAST:
// (Fast) Forward
break;
case NX_KEYTYPE_REWIND:
// Rewind key
break;
}
// If we get here, we have not handled
// the event and want system to handle it
return event;
}
And if this still not works, my next question would be what your post_notification function looks like and do you also see the described problem if you don't call post_notification there, but just make a NSLog call about the event you just saw?
In your handler, check for the following type, and just re-enable the listener.
if (type == kCGEventTapDisabledByTimeout) {
NSLog(#"Event Taps Disabled! Re-enabling");
CGEventTapEnable(eventTap, true);
return event;
}

Resources