So I am trying to put together a simple fullscreen OpenGL application using CGL and IOHIDManager in order to learn the lower-level APIs. Currently, I am creating an OpenGL context and starting it fullscreen. I am trying to now add keyboard input so I can quit the app. I've found many similar examples of using IOHIDManager to read keys, but no matter what I do my callback does not fire.
My callback is just a function that prints "here". I'm not sure where I am going wrong -- I've tried both CFRunLoopGetCurrent() and CFRunLoopMain(). My main is simply a while loop. What gives?
CFMutableDictionaryRef CreateMatchingDictionary(UInt32 usage_page, UInt32 usage) {
CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFNumberRef page_number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_page);
CFDictionarySetValue(dictionary, CFSTR(kIOHIDDeviceUsagePageKey), page_number);
CFRelease(page_number);
CFNumberRef usage_number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
CFDictionarySetValue(dictionary, CFSTR(kIOHIDDeviceUsageKey), usage_number);
CFRelease(usage_number);
return dictionary;
}
void CreateInputManager() {
IOHIDManagerRef hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
CFMutableDictionaryRef matching_dictionary = CreateMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
IOHIDManagerSetDeviceMatching(hid_manager, matching_dictionary);
IOHIDManagerRegisterInputValueCallback(hid_manager, KeyboardCallback, NULL);
IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone);
IOHIDManagerScheduleWithRunLoop(hid_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
void KeyboardCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) {
puts("CALLBACK!");
}
int main() {
// Commented out CGL context & fullscreen window creation
CreateInputManager();
while(true) {
;
}
}
UPDATE
If I put CFRunLoopRun() at the end of function CreateInputManager, my callback is called but the function never returns. How is this supposed to work in a single-threaded CGL app? Is it a strict requirement that IOHIDManager requires a run loop to function?
IOKit and HID work via Mach messaging, which in turn is deeply integrated with the runloop mechanism, as you've found. If you really do want to busy-poll, you can use the CFRunLoopRunInMode function with a zero timeout to check for events.
You may wish to consider using a CVDisplayLink to invoke your rendering code on every vertical frame refresh instead. The display link's callback will be called from the runloop, so you can leave your main thread running in CFRunLoopRun().
See https://developer.apple.com/library/mac/qa/qa1385/_index.html for how Apple recommends you structure event handling in OpenGL applications.
Turns out I need to create a separate pthread with the CreateInputManager function, specify that the IOHIDManager is to schedule the callback on CFRunLoopGetCurrent() and kick off a run loop on that thread by calling CFRunLoopRun().
I wonder if there is a way to get IOHIDManager to work with plain-old polling instead of these callbacks...
Related
I am working on some code that uses IOConnectCallAsyncScalarMethod() to get callbacks from a DriverKit extension. The setup is quite heavy, involving spawning a thread, manually creating a CFMachPortRef, adding its CFRunLoopSourceRef to a CFRunLoop and then pumping that run loop.
In order to simplify this code and reduce the risk of race conditions, I would like to get the IOKit callback on a dispatch queue instead. Is there any way to achieve this?
After going back and forth on this unsuccessfully, I finally found the answer in the OS X and iOS Kernel Programming book (page 95, listing 5-15).
The trick is to use a IONotificationPortRef along with IONotificationPortSetDispatchQueue to set the target dispatch queue. Then to actually have the callback dispatched to that queue, set up an io_async_ref64_t and use it. Here's an outline of what the code would look like:
// Create a notification port for IOKit service callbacks
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
// Run notification callbacks on the desired dispatch queue
IONotificationPortSetDispatchQueue(self.notificationPort, dispatch_get_main_queue());
io_async_ref64_t asyncRef;
asyncRef[kIOAsyncCalloutFuncIndex] = (uint64_t)callback;
asyncRef[kIOAsyncCalloutRefconIndex] = (uint64_t)(__bridge void *)self;
uint32_t cmd = 0xCAFE; // Method as defined by the service
kern_return_t error = IOConnectCallAsyncScalarMethod(connection, cmd, IONotificationPortGetMachPort(notificationPort), asyncRef, kIOAsyncCalloutCount, NULL, 0, NULL, NULL);
callback should have this signature: void commandReadyCallback(void *context, IOReturn result). (AKA. IOAsyncCallback0)
I hope this helps some poor soul in the future.
I try to monitor file changes on OSX 10.10, starting with a fresh Cocoa application in Xcode, just adding the following code.
If I uncomment the last line in the snippet then I receive the file change events perfectly fine. But I can not make this last call because it should be a Cocoa GUI application.
I digged through a lot of documentation and can't find my error. Do I have to initialize or start this whole dispatch subsystem somehow?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
int fd = open("<FILENAME>", O_EVTONLY);
if (fd == -1) return;
dispatch_queue_t qu = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!qu) {
printf("can not get queue");
return;
}
unsigned long mask =
DISPATCH_VNODE_DELETE |
DISPATCH_VNODE_WRITE |
DISPATCH_VNODE_EXTEND |
DISPATCH_VNODE_ATTRIB |
DISPATCH_VNODE_LINK |
DISPATCH_VNODE_RENAME |
DISPATCH_VNODE_REVOKE;
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, qu);
printf("source created\n");
if (!source) {
close(fd);
return;
}
printf("source valid\n");
dispatch_source_set_event_handler(source, ^{
printf("FILE CHANGED\n");
});
dispatch_resume(source);
printf("source resumed\n");
// If I call dispatch_main() I will receive the file system events as expected.
// But as a Cocoa application, I must not call this.
// Instead, I was under the impression that NSApplicationMain handles this.
//dispatch_main();
}
Grand Central Dispatch objects, such as dispatch sources, are automatically retained and released by ARC in recent versions of the compiler and frameworks.
At the end of your method, the last strong reference to source is lost and ARC is issuing an automatic dispatch_release(source). (It would also release the queue, but the source has another strong reference to that. So, if the source survived, so would the queue.)
You need to keep a strong reference to the source in an instance variable.
I'm debugging Qt5.3.1 on Mac, because my program freezes sometimes (intermittent ). I discovered that it is because the QTimer can't work properly.
In Qt code, they use the following two lines to trigger function activateTimersSourceCallback
CFRunLoopSourceSignal(d->activateTimersSourceRef);
CFRunLoopWakeUp(mainRunLoop());
void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
{
static int counter = 0;
NSLog(#"finished activeteTimersSourceCallback %d", counter++);
}
but sometimes, these two lines doesn't work, activateTimersSourceCallback won't get called.
I googled, but I couldn't find any solution? is this a known OS bug?
the initialization details:
// keep our sources running when modal loops are running
CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
CFRunLoopSourceContext context;
bzero(&context, sizeof(CFRunLoopSourceContext));
context.info = d;
context.equal = runLoopSourceEqualCallback;
// source used to activate timers
context.perform = QCocoaEventDispatcherPrivate::activateTimersSourceCallback;
d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(d->activateTimersSourceRef);
CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
Such behavior very likely can occur when UI event loop is overloaded with events or some business logic takes too long time. You should to check your business logic and move it to separate thread or run asynchronous.
I'm writing an application that runs an algorithm, but allows you to 'step through' the algorithm by pressing a button - displaying what's happening at each step.
How do I listen for events while within a method?
eg, look at the code I've got.
static int proceed;
button1Event(GtkWidget *widget)
{
proceed = 0;
int i = 0;
for (i=0; i<15; i++) //this is our example 'algorithm'
{
while (proceed ==0) continue;
printf("the nunmber is %d\n", i);
proceed = 0;
}
}
button2Event(GtkWidget *widget)
{
proceed = 1;
}
This doesn't work because it's required to exit out of the button1 method before it can listen for button2 (or any other events).
I'm thinking something like in that while loop.
while(proceed == 0)
{
listen_for_button_click();
}
What method is that?
The "real" answer here (the one any experienced GTK+ programmer will give you) isn't one you will like perhaps: don't do this, your code is structured the wrong way.
The options include:
recommended: restructure the app to be event-driven instead; probably you need to keep track of your state (either a state machine or just a boolean flag) and ignore whichever button is not currently applicable.
you can run a recursive main loop, as in the other answer with gtk_main_iteration(); however this is quite dangerous because any UI event can happen in that loop, such as windows closing or other totally unrelated stuff. Not workable in most real apps of any size.
move the blocking logic to another thread and communicate via a GAsyncQueue or something along those lines (caution, this is hard-ish to get right and likely to be overkill).
I think you are going wrong here:
while(proceed == 0)
{
listen_for_button_click();
}
You don't want while loops like this; you just want the GTK+ main loop doing your blocking. When you get the button click, in the callback for it, then write whatever the code after this while loop would have been.
You could check for pending events & handle the events in while loop in the clicked callback. Something on these lines:
button1Event(GtkWidget *widget)
{
proceed = 0;
int i = 0;
for (i=0; i<15; i++) //this is our example 'algorithm'
{
while (proceed ==0)
{
/* Check for all pending events */
while(gtk_events_pending())
{
gtk_main_iteration(); /* Handle the events */
}
continue;
}
printf("the nunmber is %d\n", i);
proceed = 0;
}
}
This way when the events related click on the second button is added to the event queue to be handled, the check will see the events as pending and handle them & then proceed. This way your global value changes can be reflected & stepping should be possible.
Hope this helps!
If you want to do it like this, the only way that comes to my mind is to create a separate thread for your algorithm and use some synchronization methods to notify that thread from within button click handlers.
GTK+ (glib, to be more specific) has its own API for threads and synchronization. As far as I know Condition variables are a standard way to implement wait-notify logic.
I wonder if it is possible to resize Windows OnScreen-keyboard in my program? What Windows methods to use for that?
simply use standard Win32 api.
I know this question is old, but the given answer is really short. To add value to this topic I could not resist to add the following information:
You could do something like this, the flag SWP_NOREPOSITION should make the iPosX and iPosY to be ignored by SetWindowPos. So only the width and height should change. I have not tested this code though.
HWND hWndOSK = FindWindow("IPTip_Main_Window", null); //Only the class is known, the window has no name
int iPosX=0;
int iPosY=0;
int iWidth=1000;
int iHeight=600;
if(hWndOSK != NULL)
{
//Window is up
if(!SetWindowPos(hWndOSK, HWND_TOPMOST, iPosX, iPosY, iWidth, iHeight, SWP_NOREPOSITION))
{
//Something went wrong do some error handling
}
}
SetWindowPos: http://msdn.microsoft.com/en-us/library/ms633545.aspx
FindWindow: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx