2024-11-09 16:12:27 +01:00
# include "globals.hpp"
# include <chrono>
# include <cmath>
2025-03-06 13:48:22 +01:00
# include <future>
# include <hyprcursor/hyprcursor.hpp>
2024-11-09 16:12:27 +01:00
# include <hyprlang.hpp>
# include <hyprland/src/managers/eventLoop/EventLoopTimer.hpp> // required so we don't "unprivate" chrono
2025-03-06 13:48:22 +01:00
# include <hyprutils/memory/UniquePtr.hpp>
2024-11-09 16:12:27 +01:00
# define private public
2025-01-18 22:40:48 +01:00
# include <hyprland/src/managers/CursorManager.hpp>
2024-11-09 16:12:27 +01:00
# undef private
# include "highres.hpp"
# include "config/config.hpp"
# include "src/debug/Log.hpp"
CHighresHandler : : CHighresHandler ( ) {
// load stuff on creation
update ( ) ;
// and reload on config reload
static const auto PCALLBACK = HyprlandAPI : : registerCallbackDynamic ( PHANDLE , " configReloaded " , [ & ] ( void * self , SCallbackInfo & , std : : any data ) {
update ( ) ;
} ) ;
}
static void hcLogger ( enum eHyprcursorLogLevel level , char * message ) {
if ( level = = HC_LOG_TRACE ) return ;
Debug : : log ( NONE , " [hc (dynamic)] {} " , message ) ;
}
void CHighresHandler : : update ( ) {
static auto * const * PENABLED = ( Hyprlang : : INT * const * ) getConfig ( CONFIG_HIGHRES_ENABLED ) ;
static auto * const * PUSEHYPRCURSOR = ( Hyprlang : : INT * const * ) getHyprlandConfig ( " cursor:enable_hyprcursor " ) ;
static auto * const * PSIZE = ( Hyprlang : : INT * const * ) getConfig ( CONFIG_HIGHRES_SIZE ) ;
static auto * const * PSHAKE_BASE = ( Hyprlang : : FLOAT * const * ) getConfig ( CONFIG_SHAKE_BASE ) ;
static auto * const * PSHAKE = ( Hyprlang : : INT * const * ) getConfig ( CONFIG_SHAKE ) ; // currently only needed for shake
2024-11-18 09:42:41 +01:00
if ( ! isEnabled ( ) | | ! * * PENABLED | | ! * * PUSEHYPRCURSOR | | ! * * PSHAKE ) {
2024-11-09 16:12:27 +01:00
// free manager if no longer enabled
if ( manager ) {
manager = nullptr ;
texture = nullptr ;
buffer = nullptr ;
}
return ;
}
std : : string name = g_pCursorManager - > m_szTheme ;
unsigned int size = * * PSIZE ! = - 1 ? * * PSIZE : std : : round ( g_pCursorManager - > m_sCurrentStyleInfo . size * * * PSHAKE_BASE * 1.5f ) ; // * 1.5f to accomodate for slight growth
// we already have loaded the same theme and size
if ( manager & & loadedName = = name & & loadedSize = = size )
return ;
2025-03-06 13:48:22 +01:00
// we are currently loading another theme
if ( managerFuture ) {
// in this case we don't do anything as proceeding would block until the future is done (thanks cpp apis)
// we just skip the update, but when retrieving the future we check again and then these changes will be loaded
2024-11-09 16:12:27 +01:00
2025-03-06 13:48:22 +01:00
Debug : : log ( LOG , " Skipping hyprcursor theme reload for dynamic cursors because one is already being loaded " ) ;
2024-11-09 16:12:27 +01:00
return ;
}
style = Hyprcursor : : SCursorStyleInfo { size } ;
2025-03-06 13:48:22 +01:00
2024-11-09 16:12:27 +01:00
loadedSize = size ;
loadedName = name ;
2025-03-06 13:48:22 +01:00
Debug : : log ( LOG , " Creating future for loading hyprcursor theme for dynamic cursors " ) ;
auto fut = std : : async ( std : : launch : : async , [ = , style = style ] ( ) - > UP < Hyprcursor : : CHyprcursorManager > {
Debug : : log ( INFO , " Starting to load hyprcursor theme '{}' of size {} for dynamic cursors asynchronously ... " , name , size ) ;
auto time = std : : chrono : : system_clock : : now ( ) ;
auto options = Hyprcursor : : SManagerOptions ( ) ;
options . logFn = hcLogger ;
options . allowDefaultFallback = true ;
auto manager = makeUnique < Hyprcursor : : CHyprcursorManager > ( name . empty ( ) ? nullptr : name . c_str ( ) , options ) ;
if ( ! manager - > valid ( ) ) {
Debug : : log ( ERR , " ... hyprcursor for dynamic cursors failed loading theme '{}', falling back to pixelated trash. " , name ) ;
return nullptr ;
}
manager - > loadThemeStyle ( style ) ;
float ms = std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( std : : chrono : : system_clock : : now ( ) - time ) . count ( ) ;
Debug : : log ( INFO , " ... hyprcursor for dynamic cursors loading finished, took {}ms " , ms ) ;
return manager ;
} ) ;
manager = nullptr ; // free old manager
managerFuture = makeUnique < std : : future < UP < Hyprcursor : : CHyprcursorManager > > > ( std : : move ( fut ) ) ;
2024-11-09 16:12:27 +01:00
}
void CHighresHandler : : loadShape ( const std : : string & name ) {
static auto const * PFALLBACK = ( Hyprlang : : STRING const * ) getConfig ( CONFIG_HIGHRES_FALLBACK ) ;
2025-03-06 13:48:22 +01:00
if ( ! manager ) {
// don't show old, potentially outdated shapes
texture = nullptr ;
buffer = nullptr ;
if ( ! managerFuture | | managerFuture - > wait_for ( std : : chrono : : seconds ( 0 ) ) ! = std : : future_status : : ready ) return ;
Debug : : log ( INFO , " Future for hyprcursor theme for dynamic cursors is ready, using new theme " ) ;
manager = managerFuture - > get ( ) ;
managerFuture = nullptr ;
if ( ! manager ) return ; // could've failed
else {
// in case someone has updated the theme again in the meantime
update ( ) ;
if ( ! manager ) return ; // new manager could be on the way
}
}
2024-11-09 16:12:27 +01:00
Hyprcursor : : SCursorShapeData shape = manager - > getShape ( name . c_str ( ) , style ) ;
// try load fallback image
if ( shape . images . size ( ) = = 0 ) {
shape = manager - > getShape ( * PFALLBACK , style ) ;
if ( shape . images . size ( ) = = 0 ) {
Debug : : log ( WARN , " Failed to load fallback shape {}, for shape {}! " , * PFALLBACK , name ) ;
texture = nullptr ;
buffer = nullptr ;
return ;
}
}
buffer = makeShared < CCursorBuffer > (
shape . images [ 0 ] . surface ,
Vector2D { shape . images [ 0 ] . size , shape . images [ 0 ] . size } ,
Vector2D { shape . images [ 0 ] . hotspotX , shape . images [ 0 ] . hotspotY }
) ;
texture = makeShared < CTexture > ( buffer ) ;
}
SP < CTexture > CHighresHandler : : getTexture ( ) {
return texture ;
}
SP < CCursorBuffer > CHighresHandler : : getBuffer ( ) {
return buffer ;
}