From b101f5ad018c69a86d4f8963ac84512d7576e60d Mon Sep 17 00:00:00 2001 From: historic_bruno Date: Sat, 21 Apr 2012 07:53:53 +0000 Subject: [PATCH] Adds hardware cursors for Linux and OS X. Requires libxcursor on Linux. Fixes #748. Adds explicit links to frameworks we need on OS X. This was SVN commit r11596. --- build/premake/extern_libs4.lua | 7 + build/premake/premake4.lua | 8 +- source/lib/res/graphics/cursor.cpp | 2 +- source/lib/sysdep/os/android/android.cpp | 57 +++++++ source/lib/sysdep/os/osx/osx_sys_cursor.mm | 104 +++++++++++++ source/lib/sysdep/os/unix/unix.cpp | 57 ------- source/lib/sysdep/os/unix/x/x.cpp | 165 +++++++++++++++++++-- 7 files changed, 324 insertions(+), 76 deletions(-) create mode 100644 source/lib/sysdep/os/osx/osx_sys_cursor.mm diff --git a/build/premake/extern_libs4.lua b/build/premake/extern_libs4.lua index f599128983..2fe97915a8 100644 --- a/build/premake/extern_libs4.lua +++ b/build/premake/extern_libs4.lua @@ -587,6 +587,13 @@ extern_lib_defs = { }) end, }, + xcursor = { + link_settings = function() + add_default_links({ + unix_names = { "Xcursor" }, + }) + end, + }, zlib = { compile_settings = function() if os.is("windows") then diff --git a/build/premake/premake4.lua b/build/premake/premake4.lua index 476db1e63d..a328aa492d 100644 --- a/build/premake/premake4.lua +++ b/build/premake/premake4.lua @@ -707,8 +707,9 @@ used_extern_libs = { "valgrind", } -if not os.is("windows") and not _OPTIONS["android"] then +if not os.is("windows") and not _OPTIONS["android"] and not os.is("macosx") then table.insert(used_extern_libs, "x11") + table.insert(used_extern_libs, "xcursor") end if not _OPTIONS["without-audio"] then @@ -760,7 +761,7 @@ function setup_main_exe () elseif os.is("linux") or os.is("bsd") then - if not _OPTIONS["without-fam"] then + if not _OPTIONS["without-fam"] then links { "fam" } end @@ -797,7 +798,10 @@ function setup_main_exe () configuration { } elseif os.is("macosx") then + links { "pthread" } + linkoptions { "-framework ApplicationServices", "-framework Cocoa", "-framework CoreFoundation" } + end end diff --git a/source/lib/res/graphics/cursor.cpp b/source/lib/res/graphics/cursor.cpp index b4e303f511..4512ca2550 100644 --- a/source/lib/res/graphics/cursor.cpp +++ b/source/lib/res/graphics/cursor.cpp @@ -39,7 +39,7 @@ // On Windows, allow runtime choice between system cursors and OpenGL // cursors (Windows = more responsive, OpenGL = more consistent with what // the game sees) -#if OS_WIN +#if OS_WIN || OS_UNIX # define ALLOW_SYS_CURSOR 1 #else # define ALLOW_SYS_CURSOR 0 diff --git a/source/lib/sysdep/os/android/android.cpp b/source/lib/sysdep/os/android/android.cpp index 7c1ffc1664..2fc215c972 100644 --- a/source/lib/sysdep/os/android/android.cpp +++ b/source/lib/sysdep/os/android/android.cpp @@ -23,6 +23,9 @@ #include "precompiled.h" #include "lib/sysdep/sysdep.h" +#include "lib/sysdep/cursor.h" + +#include "lib/external_libraries/libsdl.h" Status sys_clipboard_set(const wchar_t* UNUSED(text)) { @@ -61,3 +64,57 @@ Status GetVideoMode(int* xres, int* yres, int* bpp, int* freq) } } + +// stub implementation of sys_cursor* functions + +// note: do not return ERR_NOT_IMPLEMENTED or similar because that +// would result in WARN_ERRs. +Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) +{ + UNUSED2(w); + UNUSED2(h); + UNUSED2(hx); + UNUSED2(hy); + UNUSED2(bgra_img); + + *cursor = 0; + return INFO::OK; +} + +// returns a dummy value representing an empty cursor +Status sys_cursor_create_empty(sys_cursor* cursor) +{ + *cursor = (void*)1; // any non-zero value, since the cursor NULL has special meaning + return INFO::OK; +} + +// replaces the current system cursor with the one indicated. need only be +// called once per cursor; pass 0 to restore the default. +Status sys_cursor_set(sys_cursor cursor) +{ + if (cursor) // dummy empty cursor + SDL_ShowCursor(SDL_DISABLE); + else // restore default cursor + SDL_ShowCursor(SDL_ENABLE); + + return INFO::OK; +} + +// destroys the indicated cursor and frees its resources. if it is +// currently the system cursor, the default cursor is restored first. +Status sys_cursor_free(sys_cursor cursor) +{ + // bail now to prevent potential confusion below; there's nothing to do. + if(!cursor) + return INFO::OK; + + SDL_ShowCursor(SDL_ENABLE); + + return INFO::OK; +} + +Status sys_cursor_reset() +{ + return INFO::OK; +} + diff --git a/source/lib/sysdep/os/osx/osx_sys_cursor.mm b/source/lib/sysdep/os/osx/osx_sys_cursor.mm new file mode 100644 index 0000000000..fbda78b6b8 --- /dev/null +++ b/source/lib/sysdep/os/osx/osx_sys_cursor.mm @@ -0,0 +1,104 @@ +/* Copyright (c) 2012 Wildfire Games + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "precompiled.h" +#import "lib/sysdep/cursor.h" + +#import +#import +#import + +//TODO: make sure these are threadsafe +Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) +{ + NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:0 pixelsWide:w pixelsHigh:h + bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:w*4 bitsPerPixel:0]; + if (!bitmap) + { + debug_printf(L"sys_cursor_create: Error creating NSBitmapImageRep!\n"); + return ERR::FAIL; + } + + u8* planes[5]; + [bitmap getBitmapDataPlanes:planes]; + const u8* bgra = static_cast(bgra_img); + u8* dst = planes[0]; + for (int i = 0; i < w*h*4; i += 4) + { + dst[i] = bgra[i+2]; + dst[i+1] = bgra[i+1]; + dst[i+2] = bgra[i]; + dst[i+3] = bgra[i+3]; + } + + NSImage* image = [[NSImage alloc] init]; + if (!image) + { + [bitmap release]; + debug_printf(L"sys_cursor_create: Error creating NSImage!\n"); + return ERR::FAIL; + } + + [image addRepresentation:bitmap]; + [bitmap release]; + NSCursor* impl = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(hx, hy)]; + [image release]; + + if (!impl) + { + debug_printf(L"sys_cursor_create: Error creating NSCursor!\n"); + return ERR::FAIL; + } + + *cursor = static_cast(impl); + + return INFO::OK; +} + +Status sys_cursor_free(sys_cursor cursor) +{ + NSCursor* impl = static_cast(cursor); + [impl release]; + return INFO::OK; +} + +Status sys_cursor_create_empty(sys_cursor* cursor) +{ + static u8 empty_bgra[] = {0, 0, 0, 0}; + sys_cursor_create(1, 1, reinterpret_cast(empty_bgra), 0, 0, cursor); + return INFO::OK; +} + +Status sys_cursor_set(sys_cursor cursor) +{ + NSCursor* impl = static_cast(cursor); + [impl set]; + return INFO::OK; +} + +Status sys_cursor_reset() +{ + return INFO::OK; +} + diff --git a/source/lib/sysdep/os/unix/unix.cpp b/source/lib/sysdep/os/unix/unix.cpp index c4fc4ce4df..aa4883c5f9 100644 --- a/source/lib/sysdep/os/unix/unix.cpp +++ b/source/lib/sysdep/os/unix/unix.cpp @@ -26,10 +26,8 @@ #include #include -#include "lib/external_libraries/libsdl.h" #include "lib/utf8.h" #include "lib/sysdep/sysdep.h" -#include "lib/sysdep/cursor.h" #include "udbg.h" #include @@ -45,7 +43,6 @@ #define URL_OPEN_COMMAND "xdg-open" #endif - bool sys_IsDebuggerPresent() { return false; @@ -283,60 +280,6 @@ Status sys_StatusDescription(int err, wchar_t* buf, size_t max_chars) return ERR::FAIL; } -// stub for sys_cursor_create - we don't need to implement this (SDL/X11 only -// has monochrome cursors so we need to use the software cursor anyways) - -// note: do not return ERR_NOT_IMPLEMENTED or similar because that -// would result in WARN_ERRs. -Status sys_cursor_create(size_t w, size_t h, void* bgra_img, size_t hx, size_t hy, sys_cursor* cursor) -{ - UNUSED2(w); - UNUSED2(h); - UNUSED2(hx); - UNUSED2(hy); - UNUSED2(bgra_img); - - *cursor = 0; - return INFO::OK; -} - -// returns a dummy value representing an empty cursor -Status sys_cursor_create_empty(sys_cursor* cursor) -{ - *cursor = (void*)1; // any non-zero value, since the cursor NULL has special meaning - return INFO::OK; -} - -// replaces the current system cursor with the one indicated. need only be -// called once per cursor; pass 0 to restore the default. -Status sys_cursor_set(sys_cursor cursor) -{ - if (cursor) // dummy empty cursor - SDL_ShowCursor(SDL_DISABLE); - else // restore default cursor - SDL_ShowCursor(SDL_ENABLE); - - return INFO::OK; -} - -// destroys the indicated cursor and frees its resources. if it is -// currently the system cursor, the default cursor is restored first. -Status sys_cursor_free(sys_cursor cursor) -{ - // bail now to prevent potential confusion below; there's nothing to do. - if(!cursor) - return INFO::OK; - - SDL_ShowCursor(SDL_ENABLE); - - return INFO::OK; -} - -Status sys_cursor_reset() -{ - return INFO::OK; -} - // note: just use the sector size: Linux aio doesn't really care about // the alignment of buffers/lengths/offsets, so we'll just pick a // sane value and not bother scanning all drives. diff --git a/source/lib/sysdep/os/unix/x/x.cpp b/source/lib/sysdep/os/unix/x/x.cpp index 5da89582e2..a928f30508 100644 --- a/source/lib/sysdep/os/unix/x/x.cpp +++ b/source/lib/sysdep/os/unix/x/x.cpp @@ -34,6 +34,7 @@ #include "lib/debug.h" #include "lib/sysdep/gfx.h" +#include "lib/sysdep/cursor.h" #include "ps/VideoMode.h" @@ -42,6 +43,7 @@ #include #include #include +#include #include "SDL.h" #include "SDL_syswm.h" @@ -147,7 +149,7 @@ Expansions: wchar_t *sys_clipboard_get() { Display *disp=XOpenDisplay(NULL); - if (!disp) + if(!disp) return NULL; // We use CLIPBOARD as the default, since the CLIPBOARD semantics are much @@ -155,7 +157,7 @@ wchar_t *sys_clipboard_get() Atom selSource=XInternAtom(disp, "CLIPBOARD", False); Window selOwner=XGetSelectionOwner(disp, selSource); - if (selOwner == None) + if(selOwner == None) { // However, since many apps don't use CLIPBOARD, but use PRIMARY instead // we use XA_PRIMARY as a fallback clipboard. This is true for xterm, @@ -163,7 +165,7 @@ wchar_t *sys_clipboard_get() selSource=XA_PRIMARY; selOwner=XGetSelectionOwner(disp, selSource); } - if (selOwner != None) { + if(selOwner != None) { Atom pty=XInternAtom(disp, "SelectionPropertyTemp", False); XConvertSelection(disp, selSource, XA_STRING, pty, selOwner, CurrentTime); XFlush(disp); @@ -171,7 +173,7 @@ wchar_t *sys_clipboard_get() Atom type; int format=0, result=0; unsigned long len=0, bytes_left=0, dummy=0; - unsigned char *data=NULL; + u8 *data=NULL; // Get the length of the property and some attributes // bytes_left will contain the length of the selection @@ -183,22 +185,22 @@ wchar_t *sys_clipboard_get() &format, // return format &len, &bytes_left, &data); - if (result != Success) + if(result != Success) debug_printf(L"clipboard_get: result: %d type:%lu len:%lu format:%d bytes_left:%lu\n", result, type, len, format, bytes_left); - if (result == Success && bytes_left > 0) + if(result == Success && bytes_left > 0) { result = XGetWindowProperty (disp, selOwner, pty, 0, bytes_left, 0, AnyPropertyType, &type, &format, &len, &dummy, &data); - if (result == Success) + if(result == Success) { debug_printf(L"clipboard_get: XGetWindowProperty succeeded, returning data\n"); debug_printf(L"clipboard_get: data was: \"%hs\", type was %lu, XA_STRING atom is %lu\n", data, type, XA_STRING); - if (type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t + if(type == XA_STRING) //Latin-1: Just copy into low byte of wchar_t { wchar_t *ret=(wchar_t *)malloc((bytes_left+1)*sizeof(wchar_t)); std::copy(data, data+bytes_left, ret); @@ -239,7 +241,7 @@ int clipboard_filter(const SDL_Event* event) { /* Pass on all non-window manager specific events immediately */ /* And do nothing if we don't actually have a clip-out to send out */ - if (event->type != SDL_SYSWMEVENT || !selection_data) + if(event->type != SDL_SYSWMEVENT || !selection_data) return 1; /* Handle window-manager specific clipboard events */ @@ -250,7 +252,7 @@ int clipboard_filter(const SDL_Event* event) #else XEvent* xevent = &event->syswm.msg->event.xevent; #endif - switch (xevent->type) { + switch(xevent->type) { /* Copy the selection from our buffer to the requested property, and convert to the requested target format */ case SelectionRequest: { @@ -267,12 +269,12 @@ int clipboard_filter(const SDL_Event* event) sevent.xselection.time = req->time; // Simply strip all non-Latin1 characters and replace with '?' // We should support XA_UTF8 - if (req->target == XA_STRING) + if(req->target == XA_STRING) { size_t size = wcslen(selection_data); u8* buf = (u8*)alloca(size); - for (size_t i = 0; i < size; i++) + for(size_t i = 0; i < size; i++) { buf[i] = selection_data[i] < 0x100 ? selection_data[i] : '?'; } @@ -302,13 +304,13 @@ Status x11_clipboard_init() SDL_VERSION(&info.version); #if SDL_VERSION_ATLEAST(2, 0, 0) - if (SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &info)) + if(SDL_GetWindowWMInfo(g_VideoMode.GetWindow(), &info)) #else - if (SDL_GetWMInfo(&info)) + if(SDL_GetWMInfo(&info)) #endif { /* Save the information for later use */ - if (info.subsystem == SDL_SYSWM_X11) + if(info.subsystem == SDL_SYSWM_X11) { g_SDL_Display = info.info.x11.display; g_SDL_Window = info.info.x11.window; @@ -352,7 +354,7 @@ Status sys_clipboard_set(const wchar_t *str) debug_printf(L"sys_clipboard_set: %ls\n", str); - if (selection_data) + if(selection_data) { free(selection_data); selection_data = NULL; @@ -383,4 +385,135 @@ Status sys_clipboard_set(const wchar_t *str) return INFO::OK; } +struct sys_cursor_impl +{ + XcursorImage* image; + X__Cursor cursor; +}; + +static XcursorPixel cursor_pixel_to_x11_format(const XcursorPixel& bgra_pixel) +{ + BOOST_STATIC_ASSERT(sizeof(XcursorPixel) == 4 * sizeof(u8)); + XcursorPixel ret; + u8* dst = reinterpret_cast(&ret); + const u8* b = reinterpret_cast(&bgra_pixel); + const u8 a = b[3]; + + for(size_t i = 0; i < 3; ++i) + *dst++ = (b[i]) * a / 255; + *dst = a; + return ret; +} + +static bool get_wminfo(SDL_SysWMinfo& wminfo) +{ + SDL_VERSION(&wminfo.version); + const int ret = SDL_GetWMInfo(&wminfo); + if(ret == 1) + return true; + + if(ret == -1) + { + debug_printf(L"SDL_GetWMInfo failed\n"); + return false; + } + if(ret == 0) + { + debug_printf(L"SDL_GetWMInfo is not implemented on this platform\n"); + return false; + } + + debug_printf(L"SDL_GetWMInfo returned an unknown value: %d\n", ret); + return false; +} + +Status sys_cursor_create(int w, int h, void* bgra_img, int hx, int hy, sys_cursor* cursor) +{ + debug_printf(L"Using Xcursor to sys_cursor_create %d x %d cursor\n", w, h); + XcursorImage* image = XcursorImageCreate(w, h); + if(!image) + WARN_RETURN(ERR::FAIL); + + const XcursorPixel* bgra_img_begin = reinterpret_cast(bgra_img); + std::transform(bgra_img_begin, bgra_img_begin + (w*h), image->pixels, + cursor_pixel_to_x11_format); + image->xhot = hx; + image->yhot = hy; + + SDL_SysWMinfo wminfo; + if(!get_wminfo(wminfo)) + WARN_RETURN(ERR::FAIL); + + sys_cursor_impl* impl = new sys_cursor_impl; + impl->image = image; + impl->cursor = XcursorImageLoadCursor(wminfo.info.x11.display, image); + if(impl->cursor == None) + WARN_RETURN(ERR::FAIL); + + *cursor = static_cast(impl); + return INFO::OK; +} + +// returns a dummy value representing an empty cursor +Status sys_cursor_create_empty(sys_cursor* cursor) +{ + static u8 transparent_bgra[] = { 0x0, 0x0, 0x0, 0x0 }; + + return sys_cursor_create(1, 1, static_cast(transparent_bgra), 0, 0, cursor); +} + +// replaces the current system cursor with the one indicated. need only be +// called once per cursor; pass 0 to restore the default. +Status sys_cursor_set(sys_cursor cursor) +{ + if(!cursor) // restore default cursor + SDL_ShowCursor(SDL_DISABLE); + else + { + SDL_SysWMinfo wminfo; + if(!get_wminfo(wminfo)) + WARN_RETURN(ERR::FAIL); + + wminfo.info.x11.lock_func(); + SDL_ShowCursor(SDL_ENABLE); + // wminfo.info.x11.window is sometimes 0, in which case + // it causes a crash; in these cases use fswindow instead + Window& window = wminfo.info.x11.window ? wminfo.info.x11.window : wminfo.info.x11.fswindow; + XDefineCursor(wminfo.info.x11.display, window, + static_cast(cursor)->cursor); + wminfo.info.x11.unlock_func(); + } + + return INFO::OK; +} + +// destroys the indicated cursor and frees its resources. if it is +// currently the system cursor, the default cursor is restored first. +Status sys_cursor_free(sys_cursor cursor) +{ + // bail now to prevent potential confusion below; there's nothing to do. + if(!cursor) + return INFO::OK; + + sys_cursor_set(0); // restore default cursor + sys_cursor_impl* impl = static_cast(cursor); + + XcursorImageDestroy(impl->image); + + SDL_SysWMinfo wminfo; + if(!get_wminfo(wminfo)) + return ERR::FAIL; + XFreeCursor(wminfo.info.x11.display, impl->cursor); + + delete impl; + + return INFO::OK; +} + +Status sys_cursor_reset() +{ + return INFO::OK; +} + + #endif // #if HAVE_X