/* * Copyright 2001-2024, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * DarkWyrm */ /*! Handles the system's cursor infrastructure */ #include "CursorManager.h" #include "ServerCursor.h" #include "ServerConfig.h" #include "ServerTokenSpace.h" #include #include #include #include #include #include #include #include #include "CursorData.cpp" CursorManager::CursorManager() : BLocker("CursorManager") { } CursorManager::~CursorManager() { ReleaseCursors(); } void CursorManager::InitializeCursors(float scale) { if (scale < 1.0) scale = 1.0; const BPoint kHandHotspot(1, 1); const BPoint kResizeHotspot(8, 8); struct StandardCursor { ServerCursor*& member; BCursorID id; const uint8* data; uint32 dataLength; BPoint hotspot; #define C(IDNAME, NAME, HOTSPOT) {fCursor##NAME, B_CURSOR_ID_##IDNAME, \ kCursor##NAME, B_COUNT_OF(kCursor##NAME), HOTSPOT} } standardCursors[] = { {fCursorNoCursor, B_CURSOR_ID_NO_CURSOR, NULL, 0, BPoint(0, 0)}, C(SYSTEM_DEFAULT, SystemDefault, kHandHotspot), C(CONTEXT_MENU, ContextMenu, kHandHotspot), C(COPY, Copy, kHandHotspot), C(CREATE_LINK, CreateLink, kHandHotspot), C(CROSS_HAIR, CrossHair, BPoint(10, 10)), C(FOLLOW_LINK, FollowLink, BPoint(5, 0)), C(GRAB, Grab, kHandHotspot), C(GRABBING, Grabbing, kHandHotspot), C(HELP, Help, BPoint(0, 8)), C(I_BEAM, IBeam, BPoint(7, 9)), C(I_BEAM_HORIZONTAL, IBeamHorizontal, BPoint(8, 8)), C(MOVE, Move, kResizeHotspot), C(NOT_ALLOWED, NotAllowed, BPoint(8, 8)), C(PROGRESS, Progress, BPoint(7, 10)), C(RESIZE_EAST, ResizeEast, kResizeHotspot), C(RESIZE_EAST_WEST, ResizeEastWest, kResizeHotspot), C(RESIZE_NORTH, ResizeNorth, kResizeHotspot), C(RESIZE_NORTH_EAST, ResizeNorthEast, kResizeHotspot), C(RESIZE_NORTH_EAST_SOUTH_WEST, ResizeNorthEastSouthWest, kResizeHotspot), C(RESIZE_NORTH_SOUTH, ResizeNorthSouth, kResizeHotspot), C(RESIZE_NORTH_WEST, ResizeNorthWest, kResizeHotspot), C(RESIZE_NORTH_WEST_SOUTH_EAST, ResizeNorthWestSouthEast, kResizeHotspot), C(RESIZE_SOUTH, ResizeSouth, kResizeHotspot), C(RESIZE_SOUTH_EAST, ResizeSouthEast, kResizeHotspot), C(RESIZE_SOUTH_WEST, ResizeSouthWest, kResizeHotspot), C(RESIZE_WEST, ResizeWest, kResizeHotspot), C(ZOOM_IN, ZoomIn, BPoint(6, 6)), C(ZOOM_OUT, ZoomOut, BPoint(6, 6)) }; #undef C for (size_t i = 0; i < B_COUNT_OF(standardCursors); i++) { const StandardCursor& info = standardCursors[i]; _InitCursor(info.member, info.id, info.data, info.dataLength, info.hotspot, scale); } } void CursorManager::ReleaseCursors() { for (int32 i = 0; i < fCursorList.CountItems(); i++) { ServerCursor* cursor = ((ServerCursor*)fCursorList.ItemAtFast(i)); cursor->fManager = NULL; fTokenSpace.RemoveToken(cursor->Token()); cursor->ReleaseReference(); } fCursorList.MakeEmpty(); } ServerCursor* CursorManager::CreateCursor(team_id clientTeam, const uint8* cursorData) { if (!Lock()) return NULL; ServerCursorReference cursor(_FindCursor(clientTeam, cursorData), false); if (!cursor) { cursor.SetTo(new (std::nothrow) ServerCursor(cursorData), true); if (cursor) { cursor->SetOwningTeam(clientTeam); if (AddCursor(cursor) < B_OK) cursor = NULL; } } Unlock(); return cursor.Detach(); } ServerCursor* CursorManager::CreateCursor(team_id clientTeam, BRect r, color_space format, int32 flags, BPoint hotspot, int32 bytesPerRow) { if (!Lock()) return NULL; ServerCursor* cursor = new (std::nothrow) ServerCursor(r, format, flags, hotspot, bytesPerRow); if (cursor != NULL) { cursor->SetOwningTeam(clientTeam); if (AddCursor(cursor) < B_OK) { delete cursor; cursor = NULL; } } Unlock(); return cursor; } /*! \brief Registers a cursor with the manager. \param cursor ServerCursor object to register \return The token assigned to the cursor or B_ERROR if cursor is NULL */ int32 CursorManager::AddCursor(ServerCursor* cursor, int32 token) { if (cursor == NULL) return B_BAD_VALUE; if (!Lock()) return B_ERROR; ServerCursor* oldCursor = FindCursor(token); if (oldCursor != NULL) { fCursorList.ReplaceItem(fCursorList.IndexOf(oldCursor), cursor); oldCursor->ReleaseReference(); } else { if (!fCursorList.AddItem(cursor)) { Unlock(); return B_NO_MEMORY; } } if (token == -1) token = fTokenSpace.NewToken(kCursorToken, cursor); else fTokenSpace.SetToken(token, kCursorToken, cursor); cursor->fToken = token; cursor->AttachedToManager(this); Unlock(); return token; } /*! \brief Removes a cursor. If this was the last reference to this cursor, it will be deleted. */ void CursorManager::RemoveCursor(ServerCursor* cursor) { if (!Lock()) return; _RemoveCursor(cursor); cursor->ReleaseReference(); Unlock(); } /*! \brief Removes and deletes all of an application's cursors \param signature Signature to which the cursors belong */ void CursorManager::DeleteCursors(team_id team) { if (!Lock()) return; for (int32 index = fCursorList.CountItems(); index-- > 0;) { ServerCursor* cursor = (ServerCursor*)fCursorList.ItemAtFast(index); if (cursor->OwningTeam() != team) continue; _RemoveCursor(cursor); cursor->ReleaseReference(); } Unlock(); } /*! \brief Sets all the cursors from a specified CursorSet \param path Path to the cursor set All cursors in the set will be assigned. If the set does not specify a cursor for a particular cursor specifier, it will remain unchanged. This function will fail if passed a NULL path, an invalid path, or the path to a non-CursorSet file. */ void CursorManager::SetCursorSet(const char* path) { BAutolock locker (this); CursorSet cursorSet(NULL); if (!path || cursorSet.Load(path) != B_OK) return; _LoadCursor(fCursorSystemDefault, cursorSet, B_CURSOR_ID_SYSTEM_DEFAULT); _LoadCursor(fCursorContextMenu, cursorSet, B_CURSOR_ID_CONTEXT_MENU); _LoadCursor(fCursorCopy, cursorSet, B_CURSOR_ID_COPY); _LoadCursor(fCursorCreateLink, cursorSet, B_CURSOR_ID_CREATE_LINK); _LoadCursor(fCursorCrossHair, cursorSet, B_CURSOR_ID_CROSS_HAIR); _LoadCursor(fCursorFollowLink, cursorSet, B_CURSOR_ID_FOLLOW_LINK); _LoadCursor(fCursorGrab, cursorSet, B_CURSOR_ID_GRAB); _LoadCursor(fCursorGrabbing, cursorSet, B_CURSOR_ID_GRABBING); _LoadCursor(fCursorHelp, cursorSet, B_CURSOR_ID_HELP); _LoadCursor(fCursorIBeam, cursorSet, B_CURSOR_ID_I_BEAM); _LoadCursor(fCursorIBeamHorizontal, cursorSet, B_CURSOR_ID_I_BEAM_HORIZONTAL); _LoadCursor(fCursorMove, cursorSet, B_CURSOR_ID_MOVE); _LoadCursor(fCursorNotAllowed, cursorSet, B_CURSOR_ID_NOT_ALLOWED); _LoadCursor(fCursorProgress, cursorSet, B_CURSOR_ID_PROGRESS); _LoadCursor(fCursorResizeEast, cursorSet, B_CURSOR_ID_RESIZE_EAST); _LoadCursor(fCursorResizeEastWest, cursorSet, B_CURSOR_ID_RESIZE_EAST_WEST); _LoadCursor(fCursorResizeNorth, cursorSet, B_CURSOR_ID_RESIZE_NORTH); _LoadCursor(fCursorResizeNorthEast, cursorSet, B_CURSOR_ID_RESIZE_NORTH_EAST); _LoadCursor(fCursorResizeNorthEastSouthWest, cursorSet, B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST); _LoadCursor(fCursorResizeNorthSouth, cursorSet, B_CURSOR_ID_RESIZE_NORTH_SOUTH); _LoadCursor(fCursorResizeNorthWest, cursorSet, B_CURSOR_ID_RESIZE_NORTH_WEST); _LoadCursor(fCursorResizeNorthWestSouthEast, cursorSet, B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST); _LoadCursor(fCursorResizeSouth, cursorSet, B_CURSOR_ID_RESIZE_SOUTH); _LoadCursor(fCursorResizeSouthEast, cursorSet, B_CURSOR_ID_RESIZE_SOUTH_EAST); _LoadCursor(fCursorResizeSouthWest, cursorSet, B_CURSOR_ID_RESIZE_SOUTH_WEST); _LoadCursor(fCursorResizeWest, cursorSet, B_CURSOR_ID_RESIZE_WEST); _LoadCursor(fCursorZoomIn, cursorSet, B_CURSOR_ID_ZOOM_IN); _LoadCursor(fCursorZoomOut, cursorSet, B_CURSOR_ID_ZOOM_OUT); } /*! \brief Acquire the cursor which is used for a particular system cursor \param which Which system cursor to get \return Pointer to the particular cursor used or NULL if which is invalid or the cursor has not been assigned */ ServerCursor* CursorManager::GetCursor(BCursorID which) { BAutolock locker(this); switch (which) { case B_CURSOR_ID_SYSTEM_DEFAULT: return fCursorSystemDefault; case B_CURSOR_ID_CONTEXT_MENU: return fCursorContextMenu; case B_CURSOR_ID_COPY: return fCursorCopy; case B_CURSOR_ID_CREATE_LINK: return fCursorCreateLink; case B_CURSOR_ID_CROSS_HAIR: return fCursorCrossHair; case B_CURSOR_ID_FOLLOW_LINK: return fCursorFollowLink; case B_CURSOR_ID_GRAB: return fCursorGrab; case B_CURSOR_ID_GRABBING: return fCursorGrabbing; case B_CURSOR_ID_HELP: return fCursorHelp; case B_CURSOR_ID_I_BEAM: return fCursorIBeam; case B_CURSOR_ID_I_BEAM_HORIZONTAL: return fCursorIBeamHorizontal; case B_CURSOR_ID_MOVE: return fCursorMove; case B_CURSOR_ID_NO_CURSOR: return fCursorNoCursor; case B_CURSOR_ID_NOT_ALLOWED: return fCursorNotAllowed; case B_CURSOR_ID_PROGRESS: return fCursorProgress; case B_CURSOR_ID_RESIZE_EAST: return fCursorResizeEast; case B_CURSOR_ID_RESIZE_EAST_WEST: return fCursorResizeEastWest; case B_CURSOR_ID_RESIZE_NORTH: return fCursorResizeNorth; case B_CURSOR_ID_RESIZE_NORTH_EAST: return fCursorResizeNorthEast; case B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST: return fCursorResizeNorthEastSouthWest; case B_CURSOR_ID_RESIZE_NORTH_SOUTH: return fCursorResizeNorthSouth; case B_CURSOR_ID_RESIZE_NORTH_WEST: return fCursorResizeNorthWest; case B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST: return fCursorResizeNorthWestSouthEast; case B_CURSOR_ID_RESIZE_SOUTH: return fCursorResizeSouth; case B_CURSOR_ID_RESIZE_SOUTH_EAST: return fCursorResizeSouthEast; case B_CURSOR_ID_RESIZE_SOUTH_WEST: return fCursorResizeSouthWest; case B_CURSOR_ID_RESIZE_WEST: return fCursorResizeWest; case B_CURSOR_ID_ZOOM_IN: return fCursorZoomIn; case B_CURSOR_ID_ZOOM_OUT: return fCursorZoomOut; default: return NULL; } } /*! \brief Internal function which finds the cursor with a particular ID \param token ID of the cursor to find \return The cursor or NULL if not found */ ServerCursor* CursorManager::FindCursor(int32 token) { if (!Lock()) return NULL; ServerCursor* cursor; if (fTokenSpace.GetToken(token, kCursorToken, (void**)&cursor) != B_OK) cursor = NULL; Unlock(); return cursor; } BBitmap CursorManager::_RenderVectorCursor(uint32 size, const uint8* vector, uint32 vectorSize, float shadowStrength) { const uint32 flags = B_BITMAP_NO_SERVER_LINK; // The cursor HVIFs need to be rendered at an equivalent of 32x32, // with everything outside the 22x22 area discarded. const int32 renderRectSize = (int32)(size * (32.0f / 22.0f)); BBitmap renderCursor(BRect(0, 0, renderRectSize - 1, renderRectSize - 1), flags, B_RGBA32); status_t status = BIconUtils::GetVectorIcon(vector, vectorSize, &renderCursor); if (status != B_OK) { BBitmap fallback(BRect(0, 0, 21, 21), B_BITMAP_NO_SERVER_LINK, B_RGBA32); fallback.SetBits(kCursorFallbackBits, 22 * 22 * 4, 0, B_RGBA32); return fallback; } const BRect rect(0, 0, size - 1, size - 1); BBitmap cursor(rect, flags, B_RGBA32); cursor.ImportBits(&renderCursor, B_ORIGIN, B_ORIGIN, rect.Size()); BBitmap shadow(rect, flags, B_RGBA32); memset(shadow.Bits(), 0, shadow.BitsLength()); { int32 offset = size / 32; if (offset == 0) offset = 1; // <32px cursors shadow.ImportBits(&cursor, BPoint(0, 0), BPoint(offset, offset), BSize(size - offset - 1, size - offset - 1)); agg::rendering_buffer buffer((unsigned char*)shadow.Bits(), size, size, shadow.BytesPerRow()); agg::pixfmt_rgba32 pixFmt(buffer); agg::recursive_blur > blur; blur.blur(pixFmt, 1); for (int32 i = 0; i < shadow.BitsLength(); i += 4) { uint8* bits = (uint8*)shadow.Bits() + i; bits[0] = 0; bits[1] = 0; bits[2] = 0; bits[3] = (uint8)(bits[3] * shadowStrength); } } BBitmap composite(rect, flags, B_RGBA32); uint8* s = (uint8*)shadow.Bits(); uint8* c = (uint8*)cursor.Bits(); uint8* d = (uint8*)composite.Bits(); for (uint32 y = 0; y < size; y++) { for (uint32 x = 0; x < size; x++) { uint8 a = (uint8)(c[3] + (255 - c[3]) * (s[3] / 255.0)); d[3] = a; for (int32 i = 0; i < 3; ++i) { d[i] = ((s[i] * (255 - c[3]) + 255) >> 8) + c[i]; // premultiply d[i] = (uint8)(d[i] * int32(a) / 255.0); } s += 4; c += 4; d += 4; } } return composite; } /*! \brief Initializes a predefined system cursor. This method must only be called in the CursorManager's constructor, as it may throw exceptions. */ void CursorManager::_InitCursor(ServerCursor*& cursorMember, BCursorID id, const uint8* vector, uint32 vectorSize, const BPoint& hotSpot, float scale) { int32 cursorSize = (int32)(22 * scale); float shadow = 3 / 10.0; BPoint scaledHotspot((int32)(hotSpot.x * scale), (int32)(hotSpot.y * scale)); if (vector != NULL) { BBitmap bitmap = _RenderVectorCursor(cursorSize, vector, vectorSize, shadow); cursorMember = new ServerCursor((uint8*)bitmap.Bits(), cursorSize, cursorSize, bitmap.ColorSpace()); } else { const unsigned char noCursor[] = {0x00, 0x00, 0x00, 0x00}; cursorMember = new ServerCursor(noCursor, 1, 1, B_RGBA32); } cursorMember->SetHotSpot(scaledHotspot); AddCursor(cursorMember, id); } void CursorManager::_LoadCursor(ServerCursor*& cursorMember, const CursorSet& set, BCursorID id) { ServerCursor* cursor; if (set.FindCursor(id, &cursor) == B_OK) { AddCursor(cursor, id); cursorMember = cursor; } } ServerCursor* CursorManager::_FindCursor(team_id clientTeam, const uint8* cursorData) { int32 count = fCursorList.CountItems(); for (int32 i = 0; i < count; i++) { ServerCursor* cursor = (ServerCursor*)fCursorList.ItemAtFast(i); if (cursor->OwningTeam() == clientTeam && cursor->CursorData() && memcmp(cursor->CursorData(), cursorData, 68) == 0) { return cursor; } } return NULL; } void CursorManager::_RemoveCursor(ServerCursor* cursor) { fCursorList.RemoveItem(cursor); fTokenSpace.RemoveToken(cursor->fToken); cursor->fToken = -1; }