views:

106

answers:

2

I'm reading the moust cursor pixmap data from the StdFBShmem_t structure, as defined in the IOFrameBufferShared API.

Everything works fine, 90% of the time. However, I have noticed that some applications on the Mac set a cursor in a different format. According to the documentation for the data structures, the cursor pixmap format should always be in the same format as the frame buffer. My frame buffer is 32 bpp. I expect the pixmap data to be in the format 0xAARRGGBB, which it is (most of the time). However, in some cases, I'm reading data that looks like a mask. Specifically, the pixels in this data will either be 0x00FFFFFF or `0x00000000. This looks to me to be a mask for separate pixel data stored somewhere else.

As far as I can tell, the only application that uses this cursor pixel format is Qt Creator, but I need to work with all applications, so I'd like to sort this out.

The code I'm using to read the cursor pixmap data is:

NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init];

NSPoint mouseLocation = [NSEvent mouseLocation];
NSArray *allScreens = [NSScreen screens];
NSEnumerator *screensEnum = [allScreens objectEnumerator];
NSScreen *screen;
NSDictionary *screenDesc = nil;
while ((screen = [screensEnum nextObject]))
{
    NSRect screenFrame = [screen frame];
    screenDesc = [screen deviceDescription];
    if (NSMouseInRect(mouseLocation, screenFrame, NO))
        break;
}

if (screen)
{
    kern_return_t err;

    CGDirectDisplayID displayID = (CGDirectDisplayID) [[screenDesc objectForKey:@"NSScreenNumber"] pointerValue];
    task_port_t taskPort = mach_task_self();
    io_service_t displayServicePort = CGDisplayIOServicePort(displayID);
    io_connect_t displayConnection =0;
    err = IOFramebufferOpen(displayServicePort,
                            taskPort,
                            kIOFBSharedConnectType,
                            &displayConnection);
    if (KERN_SUCCESS == err)
    {
        union
        {
            vm_address_t vm_ptr;
            StdFBShmem_t *fbshmem;
        } cursorInfo;
        vm_size_t size;

        err = IOConnectMapMemory(displayConnection,
                                 kIOFBCursorMemory,
                                 taskPort,
                                 &cursorInfo.vm_ptr,
                                 &size,
                                 kIOMapAnywhere | kIOMapDefaultCache | kIOMapReadOnly);
        if (KERN_SUCCESS == err)
        {
            // For some reason, cursor data is not always in the same format as
            // the frame buffer. For this reason, we need some way to detect
            // which structure we should be reading.
            QByteArray pixData(
              (const char*)cursorInfo.fbshmem->cursor.rgb24.image[currentFrame],
              m_mouseInfo.currentSize.width() * m_mouseInfo.currentSize.height() * 4);

            IOConnectUnmapMemory(displayConnection,
                                 kIOFBCursorMemory,
                                 taskPort,
                                 cursorInfo.vm_ptr);
        } // IOConnectMapMemory
        else
            qDebug() << "IOConnectMapMemory Failed:" << err;
        IOServiceClose(displayConnection);
    } // IOServiceOpen
    else
        qDebug() << "IOFramebufferOpen Failed:" << err;
}// if screen
[autoReleasePool release];

My questions are:

  1. How can I detect if the cursor is a different format from the framebuffer?

  2. Where can I read the actual pixel data? The bm18Cursor structure contains a mask section, but it's not in the right place for me to be reading it using the code above.

+1  A: 

How can I detect if the cursor is a different format from the framebuffer?

The cursor is in the framebuffer. It can't be in a different format than itself.

There is no way to tell what format it's in (x-radar://problem/7751503). There would be a way to divine at least the number of bytes per pixel if you could tell how many frames the cursor has, but since you can't (that information isn't set as of 10.6.1 — x-radar://problem/7751530), you are left trying to figure out two factors of a four-factor product (bytes per pixel × width × height × number of frames, where you only have the width, the height, and the product). And even if you can figure out those missing two factors, you still don't know what order the bytes are in or whether the color components are premultiplied by the alpha component.

Where can I read the actual pixel data?

In the cursor member of the shared-cursor-memory structure.

You should define IOFB_ARBITRARY_SIZE_CURSOR before including the I/O Kit headers. Cursors can be any size now, not just 16×16, which is the size you expect when you don't define that constant. As an example, the usual Mac arrow cursor is 24×24, the “Windows” arrow cursor in CrossOver is 32×32, and the arrow cursor in X11 is 10×16.

However, in some cases, I'm reading data that looks like a mask. Specifically, the pixels in this data will either be 0x00FFFFFF or 0x00000000. This looks to me to be a mask for separate pixel data stored somewhere else.

That sounds to me more like 16-bit pixels with an 8-bit alpha channel. At least it's more probably 5-6-5 than 5-5-5.

As far as I can tell, the only application that uses this cursor pixel format is Qt Creator, but I need to work with all applications, so I'd like to sort this out.

I'm able to capture the current cursor in that app just fine with my new cursor-capturing app. Is there a specific part of the app I should hit to make it show me a specific cursor?

Peter Hosey
It's the I-beam cursor in Qt creator that doesn't work for me. The arrow cursor works fine...
Thomi
I've just tried defining IOFB_ARBITRATY_SIZE_CURSOR, and reading from the "cursor" member instead of the specific struct. The problem still exists for me.I'm doing my own alpha blending of the cursor pixmap data with a separate image - this routine works perfectly with all other cursors, but the alpha channel for the Qt-creator cursor is always 0x00, resulting in a totally transparent cursor - not what you want!
Thomi
Ah. I tried it with my app and got an interestingly different result: Nothing. Completely blank, black pixels. Using Color Matrix to use the green channel as the alpha channel did nothing. I do wonder how the cursor shows up on the screen either way.
Peter Hosey
I didn't mean that `IOFB_ARBITRARY_SIZE_CURSOR` would solve your problem, only that you should do that to avoid choking on cursors that aren't 16 × 16.
Peter Hosey
Aye, well, at least you have the same problem!Interestingly, I was reading 24x24 cursors fine without the IOFB_ARBITRATY_... set. I guess it must have been defined elsewhere.It's a mystery how the cursor shows up on screen correctly...
Thomi
+1  A: 

You might try the CGSCreateRegisteredCursorImage function, as demonstrated by Karsten in a comment on my weblog.

It is a private function, so it may change or go away at any time, so you should check whether it exists and hold IOFramebuffer in reserve, but as long as it does exist, you may find it more reliable than the complex and thinly-documented IOFramebuffer.

Peter Hosey
If I can avoid it, I'd rather not use a private function, but if there's no other way...
Thomi