2025-02-18 15:10:40 +00:00
# include "ANRManager.hpp"
# include "../helpers/fs/FsUtils.hpp"
# include "../debug/Log.hpp"
# include "../macros.hpp"
# include "HookSystemManager.hpp"
# include "../Compositor.hpp"
# include "../protocols/XDGShell.hpp"
# include "./eventLoop/EventLoopManager.hpp"
# include "../config/ConfigValue.hpp"
2025-02-21 21:26:53 +01:00
# include "../xwayland/XSurface.hpp"
2025-02-18 15:10:40 +00:00
using namespace Hyprutils : : OS ;
static constexpr auto TIMER_TIMEOUT = std : : chrono : : milliseconds ( 1500 ) ;
CANRManager : : CANRManager ( ) {
if ( ! NFsUtils : : executableExistsInPath ( " hyprland-dialog " ) ) {
Debug : : log ( ERR , " hyprland-dialog missing from PATH, cannot start ANRManager " ) ;
return ;
}
m_timer = makeShared < CEventLoopTimer > ( TIMER_TIMEOUT , [ this ] ( SP < CEventLoopTimer > self , void * data ) { onTick ( ) ; } , this ) ;
g_pEventLoopManager - > addTimer ( m_timer ) ;
m_active = true ;
static auto P = g_pHookSystem - > hookDynamic ( " openWindow " , [ this ] ( void * self , SCallbackInfo & info , std : : any data ) {
auto window = std : : any_cast < PHLWINDOW > ( data ) ;
2025-02-21 21:26:53 +01:00
for ( const auto & d : m_data ) {
if ( d - > fitsWindow ( window ) )
2025-02-18 15:10:40 +00:00
return ;
}
2025-02-21 21:26:53 +01:00
m_data . emplace_back ( makeShared < SANRData > ( window ) ) ;
2025-02-18 15:10:40 +00:00
} ) ;
m_timer - > updateTimeout ( TIMER_TIMEOUT ) ;
}
void CANRManager : : onTick ( ) {
2025-02-21 21:26:53 +01:00
std : : erase_if ( m_data , [ ] ( const auto & e ) { return e - > isDefunct ( ) ; } ) ;
2025-02-18 15:10:40 +00:00
static auto PENABLEANR = CConfigValue < Hyprlang : : INT > ( " misc:enable_anr_dialog " ) ;
if ( ! * PENABLEANR ) {
m_timer - > updateTimeout ( TIMER_TIMEOUT * 10 ) ;
return ;
}
2025-02-21 21:26:53 +01:00
for ( auto & data : m_data ) {
2025-02-18 15:10:40 +00:00
PHLWINDOW firstWindow ;
int count = 0 ;
for ( const auto & w : g_pCompositor - > m_vWindows ) {
2025-02-21 21:26:53 +01:00
if ( ! w - > m_bIsMapped )
2025-02-18 15:10:40 +00:00
continue ;
2025-02-21 21:26:53 +01:00
if ( ! data - > fitsWindow ( w ) )
2025-02-18 15:10:40 +00:00
continue ;
count + + ;
if ( ! firstWindow )
firstWindow = w ;
}
if ( count = = 0 )
continue ;
if ( data - > missedResponses > 0 ) {
if ( ! data - > isThreadRunning ( ) & & ! data - > dialogThreadSaidWait ) {
2025-02-21 21:26:53 +01:00
data - > runDialog ( " Application Not Responding " , firstWindow - > m_szTitle , firstWindow - > m_szClass , data - > getPid ( ) ) ;
2025-02-18 15:10:40 +00:00
for ( const auto & w : g_pCompositor - > m_vWindows ) {
2025-02-21 21:26:53 +01:00
if ( ! w - > m_bIsMapped )
2025-02-18 15:10:40 +00:00
continue ;
2025-02-21 21:26:53 +01:00
if ( ! data - > fitsWindow ( w ) )
2025-02-18 15:10:40 +00:00
continue ;
* w - > m_notRespondingTint = 0.2F ;
}
}
} else if ( data - > isThreadRunning ( ) )
data - > killDialog ( ) ;
if ( data - > missedResponses = = 0 )
data - > dialogThreadSaidWait = false ;
data - > missedResponses + + ;
2025-02-21 21:26:53 +01:00
data - > ping ( ) ;
2025-02-18 15:10:40 +00:00
}
m_timer - > updateTimeout ( TIMER_TIMEOUT ) ;
}
void CANRManager : : onResponse ( SP < CXDGWMBase > wmBase ) {
2025-02-21 21:26:53 +01:00
const auto DATA = dataFor ( wmBase ) ;
if ( ! DATA )
2025-02-18 15:10:40 +00:00
return ;
2025-02-21 21:26:53 +01:00
onResponse ( DATA ) ;
}
void CANRManager : : onResponse ( SP < CXWaylandSurface > pXwaylandSurface ) {
const auto DATA = dataFor ( pXwaylandSurface ) ;
if ( ! DATA )
return ;
onResponse ( DATA ) ;
}
void CANRManager : : onResponse ( SP < CANRManager : : SANRData > data ) {
2025-02-18 15:10:40 +00:00
data - > missedResponses = 0 ;
if ( data - > isThreadRunning ( ) )
data - > killDialog ( ) ;
}
2025-02-21 21:26:53 +01:00
bool CANRManager : : isNotResponding ( PHLWINDOW pWindow ) {
const auto DATA = dataFor ( pWindow ) ;
if ( ! DATA )
return false ;
return isNotResponding ( DATA ) ;
}
bool CANRManager : : isNotResponding ( SP < CANRManager : : SANRData > data ) {
return data - > missedResponses > 1 ;
}
SP < CANRManager : : SANRData > CANRManager : : dataFor ( PHLWINDOW pWindow ) {
auto it = m_data . end ( ) ;
if ( pWindow - > m_pXWaylandSurface )
it = std : : ranges : : find_if ( m_data , [ & pWindow ] ( const auto & data ) { return data - > xwaylandSurface & & data - > xwaylandSurface = = pWindow - > m_pXWaylandSurface ; } ) ;
else if ( pWindow - > m_pXDGSurface )
it = std : : ranges : : find_if ( m_data , [ & pWindow ] ( const auto & data ) { return data - > xdgBase & & data - > xdgBase = = pWindow - > m_pXDGSurface - > owner ; } ) ;
return it = = m_data . end ( ) ? nullptr : * it ;
}
SP < CANRManager : : SANRData > CANRManager : : dataFor ( SP < CXDGWMBase > wmBase ) {
auto it = std : : ranges : : find_if ( m_data , [ & wmBase ] ( const auto & data ) { return data - > xdgBase & & data - > xdgBase = = wmBase ; } ) ;
return it = = m_data . end ( ) ? nullptr : * it ;
}
SP < CANRManager : : SANRData > CANRManager : : dataFor ( SP < CXWaylandSurface > pXwaylandSurface ) {
auto it = std : : ranges : : find_if ( m_data , [ & pXwaylandSurface ] ( const auto & data ) { return data - > xwaylandSurface & & data - > xwaylandSurface = = pXwaylandSurface ; } ) ;
return it = = m_data . end ( ) ? nullptr : * it ;
}
CANRManager : : SANRData : : SANRData ( PHLWINDOW pWindow ) :
xwaylandSurface ( pWindow - > m_pXWaylandSurface ) , xdgBase ( pWindow - > m_pXDGSurface ? pWindow - > m_pXDGSurface - > owner : WP < CXDGWMBase > { } ) {
;
}
CANRManager : : SANRData : : ~ SANRData ( ) {
if ( dialogThread . joinable ( ) ) {
killDialog ( ) ;
// dangerous: might lock if the above failed!!
dialogThread . join ( ) ;
}
}
2025-02-18 15:10:40 +00:00
void CANRManager : : SANRData : : runDialog ( const std : : string & title , const std : : string & appName , const std : : string appClass , pid_t dialogWmPID ) {
if ( ! dialogThreadExited )
killDialog ( ) ;
// dangerous: might lock if the above failed!!
if ( dialogThread . joinable ( ) )
dialogThread . join ( ) ;
dialogThreadExited = false ;
dialogThreadSaidWait = false ;
dialogThread = std : : thread ( [ title , appName , appClass , dialogWmPID , this ] ( ) {
2025-02-21 21:26:53 +01:00
SP < CProcess > proc = makeShared < CProcess > ( " hyprland-dialog " ,
std : : vector < std : : string > { " --title " , title , " --text " ,
std : : format ( " Application {} with class of {} is not responding. \n What do you want to do with it? " ,
appName . empty ( ) ? " unknown " : appName , appClass . empty ( ) ? " unknown " : appClass ) ,
" --buttons " , " Terminate;Wait " } ) ;
2025-02-18 15:10:40 +00:00
dialogProc = proc ;
proc - > runSync ( ) ;
dialogThreadExited = true ;
if ( proc - > stdOut ( ) . empty ( ) )
return ;
2025-02-18 15:20:00 +00:00
if ( proc - > stdOut ( ) . starts_with ( " Terminate " ) )
2025-02-18 15:10:40 +00:00
kill ( dialogWmPID , SIGKILL ) ;
2025-02-18 15:20:00 +00:00
if ( proc - > stdOut ( ) . starts_with ( " Wait " ) )
2025-02-18 15:10:40 +00:00
dialogThreadSaidWait = true ;
} ) ;
}
bool CANRManager : : SANRData : : isThreadRunning ( ) {
if ( dialogThread . native_handle ( ) = = 0 )
return false ;
if ( dialogThreadExited )
return false ;
return pthread_kill ( dialogThread . native_handle ( ) , 0 ) ! = ESRCH ;
}
void CANRManager : : SANRData : : killDialog ( ) const {
if ( ! dialogProc )
return ;
2025-02-21 16:58:17 +00:00
if ( ! dialogProc - > pid ( ) ) {
Debug : : log ( ERR , " ANR: cannot kill dialogProc, as it doesn't have a pid. If you have hyprutils <= 0.6.0, you will crash soon. Otherwise, dialog failed to spawn?? " ) ;
return ;
}
2025-02-18 15:10:40 +00:00
kill ( dialogProc - > pid ( ) , SIGKILL ) ;
}
2025-02-21 21:26:53 +01:00
bool CANRManager : : SANRData : : fitsWindow ( PHLWINDOW pWindow ) const {
if ( pWindow - > m_pXWaylandSurface )
return pWindow - > m_pXWaylandSurface = = xwaylandSurface ;
else if ( pWindow - > m_pXDGSurface )
return pWindow - > m_pXDGSurface - > owner = = xdgBase & & xdgBase ;
return false ;
}
bool CANRManager : : SANRData : : isDefunct ( ) const {
return xdgBase . expired ( ) & & xwaylandSurface . expired ( ) ;
}
pid_t CANRManager : : SANRData : : getPid ( ) const {
if ( xdgBase ) {
pid_t pid = 0 ;
wl_client_get_credentials ( xdgBase - > client ( ) , & pid , nullptr , nullptr ) ;
return pid ;
2025-02-18 15:10:40 +00:00
}
2025-02-21 21:26:53 +01:00
if ( xwaylandSurface )
return xwaylandSurface - > pid ;
return 0 ;
2025-02-18 15:10:40 +00:00
}
2025-02-21 21:26:53 +01:00
void CANRManager : : SANRData : : ping ( ) {
if ( xdgBase ) {
xdgBase - > ping ( ) ;
return ;
}
if ( xwaylandSurface )
xwaylandSurface - > ping ( ) ;
2025-02-18 15:10:40 +00:00
}