2022-07-27 12:32:00 +02:00
# include "Monitor.hpp"
2024-02-15 07:22:20 -07:00
# include "MiscFunctions.hpp"
2025-01-17 19:14:55 +01:00
# include "../macros.hpp"
2024-07-21 13:09:54 +02:00
# include "math/Math.hpp"
2025-03-14 02:15:18 +03:00
# include "../protocols/ColorManagement.hpp"
2022-07-27 12:32:00 +02:00
# include "../Compositor.hpp"
2024-03-03 18:39:20 +00:00
# include "../config/ConfigValue.hpp"
2025-04-15 23:00:40 +00:00
# include "../config/ConfigManager.hpp"
2024-04-22 18:21:03 +01:00
# include "../protocols/GammaControl.hpp"
2024-05-03 22:34:10 +01:00
# include "../devices/ITouch.hpp"
2024-05-09 21:47:21 +01:00
# include "../protocols/LayerShell.hpp"
2024-05-10 02:27:54 +01:00
# include "../protocols/PresentationTime.hpp"
2024-07-21 13:09:54 +02:00
# include "../protocols/DRMLease.hpp"
2024-08-06 14:52:19 +01:00
# include "../protocols/DRMSyncobj.hpp"
2024-07-21 13:09:54 +02:00
# include "../protocols/core/Output.hpp"
2024-10-19 16:21:47 +01:00
# include "../protocols/Screencopy.hpp"
# include "../protocols/ToplevelExport.hpp"
2024-06-19 10:24:28 -04:00
# include "../managers/PointerManager.hpp"
2024-10-05 00:44:30 +01:00
# include "../managers/eventLoop/EventLoopManager.hpp"
2024-07-21 13:09:54 +02:00
# include "../protocols/core/Compositor.hpp"
2025-01-17 15:21:35 +00:00
# include "../render/Renderer.hpp"
# include "../managers/EventManager.hpp"
# include "../managers/LayoutManager.hpp"
# include "../managers/input/InputManager.hpp"
2024-07-21 13:09:54 +02:00
# include "sync/SyncTimeline.hpp"
2025-04-16 01:37:48 +01:00
# include "time/Time.hpp"
2025-01-17 15:21:35 +00:00
# include "../desktop/LayerSurface.hpp"
2024-07-21 13:09:54 +02:00
# include <aquamarine/output/Output.hpp>
2024-10-12 03:29:51 +03:00
# include "debug/Log.hpp"
2025-01-17 15:21:35 +00:00
# include "debug/HyprNotificationOverlay.hpp"
2024-06-11 17:17:45 +02:00
# include <hyprutils/string/String.hpp>
2024-09-21 00:33:48 +01:00
# include <hyprutils/utils/ScopeGuard.hpp>
2024-12-28 07:32:04 -06:00
# include <cstring>
# include <ranges>
2025-05-30 18:25:59 +05:00
# include <vector>
# include <algorithm>
2025-04-16 01:37:48 +01:00
2024-06-11 17:17:45 +02:00
using namespace Hyprutils : : String ;
2024-09-21 00:33:48 +01:00
using namespace Hyprutils : : Utils ;
2025-01-30 12:30:12 +01:00
using namespace Hyprutils : : OS ;
2025-02-02 22:25:29 +03:00
using enum NContentType : : eContentType ;
2024-03-03 18:39:20 +00:00
2024-12-18 19:56:01 -06:00
static int ratHandler ( void * data ) {
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > renderMonitor ( ( ( CMonitor * ) data ) - > m_self . lock ( ) ) ;
2023-03-24 19:23:16 +00:00
return 1 ;
}
2025-04-30 23:45:20 +02:00
CMonitor : : CMonitor ( SP < Aquamarine : : IOutput > output_ ) : m_state ( this ) , m_output ( output_ ) {
2024-06-19 18:25:20 +02:00
;
2023-04-07 12:18:40 +01:00
}
CMonitor : : ~ CMonitor ( ) {
2025-04-30 23:45:20 +02:00
m_events . destroy . emit ( ) ;
2025-04-18 10:37:51 -05:00
if ( g_pHyprOpenGL )
2025-04-30 23:45:20 +02:00
g_pHyprOpenGL - > destroyMonitorResources ( m_self ) ;
2023-04-07 12:18:40 +01:00
}
2024-07-21 13:09:54 +02:00
void CMonitor : : onConnect ( bool noRule ) {
2025-04-30 23:45:20 +02:00
EMIT_HOOK_EVENT ( " preMonitorAdded " , m_self . lock ( ) ) ;
2024-10-05 14:41:44 +01:00
CScopeGuard x = { [ ] ( ) { g_pCompositor - > arrangeMonitors ( ) ; } } ;
2024-05-10 02:27:54 +01:00
2025-01-26 14:40:42 +00:00
g_pEventLoopManager - > doLater ( [ ] { g_pConfigManager - > ensurePersistentWorkspacesPresent ( ) ; } ) ;
2025-04-30 23:45:20 +02:00
m_listeners . frame = m_output - > events . frame . registerListener ( [ this ] ( std : : any d ) { onMonitorFrame ( ) ; } ) ;
m_listeners . commit = m_output - > events . commit . registerListener ( [ this ] ( std : : any d ) {
2024-10-19 16:21:47 +01:00
if ( true ) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
2025-04-30 23:45:20 +02:00
PROTO : : screencopy - > onOutputCommit ( m_self . lock ( ) ) ;
PROTO : : toplevelExport - > onOutputCommit ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
}
} ) ;
2025-04-30 23:45:20 +02:00
m_listeners . needsFrame =
m_output - > events . needsFrame . registerListener ( [ this ] ( std : : any d ) { g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_NEEDS_FRAME ) ; } ) ;
2024-08-19 18:44:22 +02:00
2025-04-30 23:45:20 +02:00
m_listeners . presented = m_output - > events . present . registerListener ( [ this ] ( std : : any d ) {
2025-04-16 01:37:48 +01:00
auto E = std : : any_cast < Aquamarine : : IOutput : : SPresentEvent > ( d ) ;
timespec * ts = E . when ;
2025-05-27 21:26:47 +02:00
if ( ts & & ts - > tv_sec < = 2 ) {
// drop this timestamp, it's not valid. Likely drm is cringe. We can't push it further because
// a) it's wrong, b) our translations aren't 100% accurate and risk underflows
ts = nullptr ;
}
if ( ! ts )
PROTO : : presentation - > onPresented ( m_self . lock ( ) , Time : : steadyNow ( ) , E . refresh , E . seq , E . flags ) ;
else
2025-04-30 23:45:20 +02:00
PROTO : : presentation - > onPresented ( m_self . lock ( ) , Time : : fromTimespec ( E . when ) , E . refresh , E . seq , E . flags ) ;
2024-07-21 13:09:54 +02:00
} ) ;
2025-04-30 23:45:20 +02:00
m_listeners . destroy = m_output - > events . destroy . registerListener ( [ this ] ( std : : any d ) {
Debug : : log ( LOG , " Destroy called for monitor {} " , m_name ) ;
2024-08-19 18:44:22 +02:00
onDisconnect ( true ) ;
2025-04-30 23:45:20 +02:00
m_output = nullptr ;
m_renderingInitPassed = false ;
2024-08-19 18:44:22 +02:00
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Removing monitor {} from realMonitors " , m_name ) ;
2024-08-19 18:44:22 +02:00
2025-04-22 15:23:29 +02:00
std : : erase_if ( g_pCompositor - > m_realMonitors , [ & ] ( PHLMONITOR & el ) { return el . get ( ) = = this ; } ) ;
2024-08-19 18:44:22 +02:00
} ) ;
2025-04-30 23:45:20 +02:00
m_listeners . state = m_output - > events . state . registerListener ( [ this ] ( std : : any d ) {
2024-07-21 13:09:54 +02:00
auto E = std : : any_cast < Aquamarine : : IOutput : : SStateEvent > ( d ) ;
if ( E . size = = Vector2D { } ) {
// an indication to re-set state
// we can't do much for createdByUser displays I think
2025-04-30 23:45:20 +02:00
if ( m_createdByUser )
2024-07-21 13:09:54 +02:00
return ;
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Reapplying monitor rule for {} from a state request " , m_name ) ;
applyMonitorRule ( & m_activeMonitorRule , true ) ;
2024-07-21 13:09:54 +02:00
return ;
}
2024-05-10 02:27:54 +01:00
2025-04-30 23:45:20 +02:00
if ( ! m_createdByUser )
2024-07-21 13:09:54 +02:00
return ;
const auto SIZE = E . size ;
2025-04-30 23:45:20 +02:00
m_forceSize = SIZE ;
2024-07-21 13:09:54 +02:00
2025-04-30 23:45:20 +02:00
SMonitorRule rule = m_activeMonitorRule ;
2024-07-21 13:09:54 +02:00
rule . resolution = SIZE ;
2024-12-28 07:32:04 -06:00
applyMonitorRule ( & rule ) ;
2024-07-21 13:09:54 +02:00
} ) ;
2025-04-30 23:45:20 +02:00
m_tearingState . canTear = m_output - > getBackend ( ) - > type ( ) = = Aquamarine : : AQ_BACKEND_DRM ;
2023-09-28 21:48:33 +01:00
2025-04-30 23:45:20 +02:00
m_name = m_output - > name ;
2022-08-10 13:44:04 +02:00
2025-04-30 23:45:20 +02:00
m_description = m_output - > description ;
2023-11-30 02:48:10 +01:00
// remove comma character from description. This allow monitor specific rules to work on monitor with comma on their description
2025-04-30 23:45:20 +02:00
std : : erase ( m_description , ' , ' ) ;
2023-11-30 02:48:10 +01:00
2024-02-15 07:22:20 -07:00
// field is backwards-compatible with intended usage of `szDescription` but excludes the parenthesized DRM node name suffix
2025-04-30 23:45:20 +02:00
m_shortDescription = trim ( std : : format ( " {} {} {} " , m_output - > make , m_output - > model , m_output - > serial ) ) ;
std : : erase ( m_shortDescription , ' , ' ) ;
2024-02-15 07:22:20 -07:00
2025-04-30 23:45:20 +02:00
if ( m_output - > getBackend ( ) - > type ( ) ! = Aquamarine : : AQ_BACKEND_DRM )
m_createdByUser = true ; // should be true. WL and Headless backends should be addable / removable
2022-11-05 18:04:44 +00:00
2022-07-27 12:32:00 +02:00
// get monitor rule that matches
2025-04-30 23:45:20 +02:00
SMonitorRule monitorRule = g_pConfigManager - > getMonitorRuleFor ( m_self . lock ( ) ) ;
2022-07-27 12:32:00 +02:00
2025-05-05 23:40:37 +02:00
if ( m_enabled & & ! monitorRule . disabled ) {
applyMonitorRule ( & monitorRule , m_pixelSize = = Vector2D { } ) ;
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setEnabled ( true ) ;
m_state . commit ( ) ;
return ;
}
2022-07-27 12:32:00 +02:00
// if it's disabled, disable and ignore
if ( monitorRule . disabled ) {
2022-08-10 13:31:58 +02:00
2025-04-30 23:45:20 +02:00
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setEnabled ( false ) ;
2022-09-25 20:07:48 +02:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . commit ( ) )
Debug : : log ( ERR , " Couldn't commit disabled state on output {} " , m_output - > name ) ;
2022-08-10 13:44:04 +02:00
2025-04-30 23:45:20 +02:00
m_enabled = false ;
2022-08-10 13:31:58 +02:00
2025-04-30 23:45:20 +02:00
m_listeners . frame . reset ( ) ;
2022-07-27 12:32:00 +02:00
return ;
}
2025-04-30 23:45:20 +02:00
if ( m_output - > nonDesktop ) {
2022-08-27 17:10:13 -04:00
Debug : : log ( LOG , " Not configuring non-desktop output " ) ;
2024-07-21 13:09:54 +02:00
if ( PROTO : : lease )
2025-04-30 23:45:20 +02:00
PROTO : : lease - > offer ( m_self . lock ( ) ) ;
2022-08-27 17:10:13 -04:00
2024-07-21 13:09:54 +02:00
return ;
2022-08-10 13:44:04 +02:00
}
2024-10-26 02:06:13 +01:00
PHLMONITOR * thisWrapper = nullptr ;
2022-08-03 17:42:19 +02:00
2023-11-01 18:53:36 +00:00
// find the wrap
2025-04-22 15:23:29 +02:00
for ( auto & m : g_pCompositor - > m_realMonitors ) {
2025-04-30 23:45:20 +02:00
if ( m - > m_id = = m_id ) {
2023-11-01 18:53:36 +00:00
thisWrapper = & m ;
break ;
2022-08-03 17:42:19 +02:00
}
}
2023-11-01 18:53:36 +00:00
RASSERT ( thisWrapper - > get ( ) , " CMonitor::onConnect: Had no wrapper??? " ) ;
2025-05-30 18:25:59 +05:00
if ( std : : ranges : : find_if ( g_pCompositor - > m_monitors , [ & ] ( auto & other ) { return other . get ( ) = = this ; } ) = = g_pCompositor - > m_monitors . end ( ) )
2025-04-22 15:23:29 +02:00
g_pCompositor - > m_monitors . push_back ( * thisWrapper ) ;
2022-09-25 20:07:48 +02:00
2025-04-30 23:45:20 +02:00
m_enabled = true ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setEnabled ( true ) ;
2022-07-27 12:32:00 +02:00
// set mode, also applies
if ( ! noRule )
2024-12-28 07:32:04 -06:00
applyMonitorRule ( & monitorRule , true ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . commit ( ) )
2024-07-21 13:09:54 +02:00
Debug : : log ( WARN , " state.commit() failed in CMonitor::onCommit " ) ;
2023-11-01 18:53:36 +00:00
2025-04-30 23:45:20 +02:00
m_damage . setSize ( m_transformedSize ) ;
2023-04-07 12:18:40 +01:00
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x} " , m_output - > name , m_position , m_pixelSize , ( uintptr_t ) m_output . get ( ) ) ;
2022-07-27 12:32:00 +02:00
2022-09-13 15:25:42 +02:00
setupDefaultWS ( monitorRule ) ;
2022-07-27 12:32:00 +02:00
2025-04-22 15:23:29 +02:00
for ( auto const & ws : g_pCompositor - > m_workspaces ) {
2024-04-05 16:56:46 +01:00
if ( ! valid ( ws ) )
continue ;
2025-04-30 23:45:20 +02:00
if ( ws - > m_lastMonitor = = m_name | | g_pCompositor - > m_monitors . size ( ) = = 1 /* avoid lost workspaces on recover */ ) {
g_pCompositor - > moveWorkspaceToMonitor ( ws , m_self . lock ( ) ) ;
2023-05-03 15:15:56 +01:00
ws - > startAnim ( true , true , true ) ;
2025-04-25 02:37:12 +02:00
ws - > m_lastMonitor = " " ;
2023-05-03 15:15:56 +01:00
}
}
2025-04-30 23:45:20 +02:00
m_scale = monitorRule . scale ;
if ( m_scale < 0.1 )
m_scale = getDefaultScale ( ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
m_forceFullFrames = 3 ; // force 3 full frames to make sure there is no blinking due to double-buffering.
2022-07-27 12:32:00 +02:00
//
2025-04-30 23:45:20 +02:00
if ( ! m_activeMonitorRule . mirrorOf . empty ( ) )
setMirror ( m_activeMonitorRule . mirrorOf ) ;
2024-06-09 22:28:51 +02:00
2025-04-22 15:23:29 +02:00
if ( ! g_pCompositor - > m_lastMonitor ) // set the last monitor if it isnt set yet
2025-04-30 23:45:20 +02:00
g_pCompositor - > setActiveMonitor ( m_self . lock ( ) ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > arrangeLayersForMonitor ( m_id ) ;
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( m_id ) ;
2022-08-10 21:54:09 +02:00
2022-10-22 21:45:17 +01:00
// ensure VRR (will enable if necessary)
2025-04-30 23:45:20 +02:00
g_pConfigManager - > ensureVRR ( m_self . lock ( ) ) ;
2022-12-12 20:51:20 +00:00
// verify last mon valid
bool found = false ;
2025-04-22 15:23:29 +02:00
for ( auto const & m : g_pCompositor - > m_monitors ) {
if ( m = = g_pCompositor - > m_lastMonitor ) {
2022-12-12 20:51:20 +00:00
found = true ;
break ;
}
}
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " checking if we have seen this monitor before: {} " , m_name ) ;
2025-04-14 02:07:53 -07:00
// if we saw this monitor before, set it to the workspace it was on
2025-04-30 23:45:20 +02:00
if ( g_pCompositor - > m_seenMonitorWorkspaceMap . contains ( m_name ) ) {
auto workspaceID = g_pCompositor - > m_seenMonitorWorkspaceMap [ m_name ] ;
Debug : : log ( LOG , " Monitor {} was on workspace {}, setting it to that " , m_name , workspaceID ) ;
2025-04-14 02:07:53 -07:00
auto ws = g_pCompositor - > getWorkspaceByID ( workspaceID ) ;
if ( ws ) {
2025-04-30 23:45:20 +02:00
g_pCompositor - > moveWorkspaceToMonitor ( ws , m_self . lock ( ) ) ;
2025-04-14 02:07:53 -07:00
changeWorkspace ( ws , true , false , false ) ;
}
} else
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {} was not on any workspace " , m_name ) ;
2025-04-14 02:07:53 -07:00
2022-12-12 20:51:20 +00:00
if ( ! found )
2025-04-30 23:45:20 +02:00
g_pCompositor - > setActiveMonitor ( m_self . lock ( ) ) ;
2023-03-24 19:23:16 +00:00
2025-04-30 23:45:20 +02:00
m_renderTimer = wl_event_loop_add_timer ( g_pCompositor - > m_wlEventLoop , ratHandler , this ) ;
2023-11-01 18:53:36 +00:00
2025-04-30 23:45:20 +02:00
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_NEW_MONITOR ) ;
2024-04-22 18:21:03 +01:00
2025-04-30 23:45:20 +02:00
PROTO : : gamma - > applyGammaToState ( m_self . lock ( ) ) ;
2024-04-22 18:21:03 +01:00
2025-04-30 23:45:20 +02:00
m_events . connect . emit ( ) ;
2024-09-15 22:03:42 +02:00
2025-04-30 23:45:20 +02:00
g_pEventManager - > postEvent ( SHyprIPCEvent { " monitoradded " , m_name } ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " monitoraddedv2 " , std : : format ( " {},{},{} " , m_id , m_name , m_shortDescription ) } ) ;
EMIT_HOOK_EVENT ( " monitorAdded " , m_self . lock ( ) ) ;
2022-07-27 12:32:00 +02:00
}
2023-11-12 14:14:05 +01:00
void CMonitor : : onDisconnect ( bool destroy ) {
2025-04-30 23:45:20 +02:00
EMIT_HOOK_EVENT ( " preMonitorRemoved " , m_self . lock ( ) ) ;
2024-10-05 14:37:12 +01:00
CScopeGuard x = { [ this ] ( ) {
2025-04-22 15:23:29 +02:00
if ( g_pCompositor - > m_isShuttingDown )
2024-10-05 14:37:12 +01:00
return ;
2025-04-30 23:45:20 +02:00
g_pEventManager - > postEvent ( SHyprIPCEvent { " monitorremoved " , m_name } ) ;
2025-05-05 18:52:36 -07:00
g_pEventManager - > postEvent ( SHyprIPCEvent { " monitorremovedv2 " , std : : format ( " {},{},{} " , m_id , m_name , m_shortDescription ) } ) ;
2025-04-30 23:45:20 +02:00
EMIT_HOOK_EVENT ( " monitorRemoved " , m_self . lock ( ) ) ;
2024-10-05 14:41:44 +01:00
g_pCompositor - > arrangeMonitors ( ) ;
2024-10-05 14:37:12 +01:00
} } ;
2022-08-03 16:05:25 +02:00
2025-04-30 23:45:20 +02:00
if ( m_renderTimer ) {
wl_event_source_remove ( m_renderTimer ) ;
m_renderTimer = nullptr ;
2023-03-24 19:23:16 +00:00
}
2025-04-30 23:45:20 +02:00
if ( ! m_enabled | | g_pCompositor - > m_isShuttingDown )
2022-08-03 16:05:25 +02:00
return ;
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " onDisconnect called for {} " , m_output - > name ) ;
2022-11-19 13:01:32 +00:00
2025-04-30 23:45:20 +02:00
m_events . disconnect . emit ( ) ;
2025-04-18 10:37:51 -05:00
if ( g_pHyprOpenGL )
2025-04-30 23:45:20 +02:00
g_pHyprOpenGL - > destroyMonitorResources ( m_self ) ;
2024-04-22 18:21:03 +01:00
2025-04-14 02:07:53 -07:00
// record what workspace this monitor was on
2025-04-30 23:45:20 +02:00
if ( m_activeWorkspace ) {
Debug : : log ( LOG , " Disconnecting Monitor {} was on workspace {} " , m_name , m_activeWorkspace - > m_id ) ;
g_pCompositor - > m_seenMonitorWorkspaceMap [ m_name ] = m_activeWorkspace - > m_id ;
2025-04-14 02:07:53 -07:00
}
2022-07-27 12:32:00 +02:00
// Cleanup everything. Move windows back, snap cursor, shit.
2024-10-19 23:03:29 +01:00
PHLMONITOR BACKUPMON = nullptr ;
2025-04-22 15:23:29 +02:00
for ( auto const & m : g_pCompositor - > m_monitors ) {
2022-07-27 12:32:00 +02:00
if ( m . get ( ) ! = this ) {
2024-10-19 23:03:29 +01:00
BACKUPMON = m ;
2022-07-27 12:32:00 +02:00
break ;
}
}
2022-09-13 15:25:42 +02:00
// remove mirror
2025-04-30 23:45:20 +02:00
if ( m_mirrorOf ) {
2025-05-30 18:25:59 +05:00
m_mirrorOf - > m_mirrors . erase ( std : : ranges : : find_if ( m_mirrorOf - > m_mirrors , [ & ] ( const auto & other ) { return other = = m_self ; } ) ) ;
2024-06-19 10:24:28 -04:00
// unlock software for mirrored monitor
2025-04-30 23:45:20 +02:00
g_pPointerManager - > unlockSoftwareForMonitor ( m_mirrorOf . lock ( ) ) ;
m_mirrorOf . reset ( ) ;
2022-09-13 15:25:42 +02:00
}
2025-04-30 23:45:20 +02:00
if ( ! m_mirrors . empty ( ) ) {
for ( auto const & m : m_mirrors ) {
2022-09-13 15:25:42 +02:00
m - > setMirror ( " " ) ;
}
2025-04-20 20:39:33 +02:00
g_pConfigManager - > m_wantsMonitorReload = true ;
2022-09-13 15:25:42 +02:00
}
2025-04-30 23:45:20 +02:00
m_listeners . frame . reset ( ) ;
m_listeners . presented . reset ( ) ;
m_listeners . needsFrame . reset ( ) ;
m_listeners . commit . reset ( ) ;
2022-07-27 12:32:00 +02:00
2023-01-02 16:16:28 +01:00
for ( size_t i = 0 ; i < 4 ; + + i ) {
2025-04-30 23:45:20 +02:00
for ( auto const & ls : m_layerSurfaceLayers [ i ] ) {
2025-04-24 20:49:49 +02:00
if ( ls - > m_layerSurface & & ! ls - > m_fadingOut )
ls - > m_layerSurface - > sendClosed ( ) ;
2023-01-02 16:16:28 +01:00
}
2025-04-30 23:45:20 +02:00
m_layerSurfaceLayers [ i ] . clear ( ) ;
2023-01-02 16:16:28 +01:00
}
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Removed monitor {}! " , m_name ) ;
2023-01-19 16:27:04 +01:00
2022-08-10 21:54:09 +02:00
if ( ! BACKUPMON ) {
Debug : : log ( WARN , " Unplugged last monitor, entering an unsafe state. Good luck my friend. " ) ;
2023-09-24 18:04:38 +01:00
g_pCompositor - > enterUnsafeState ( ) ;
2022-08-10 21:54:09 +02:00
}
2025-04-30 23:45:20 +02:00
m_enabled = false ;
m_renderingInitPassed = false ;
2023-11-01 18:53:36 +00:00
2023-09-01 22:03:56 +02:00
if ( BACKUPMON ) {
// snap cursor
2025-04-30 23:45:20 +02:00
g_pCompositor - > warpCursorTo ( BACKUPMON - > m_position + BACKUPMON - > m_transformedSize / 2.F , true ) ;
2022-07-27 12:32:00 +02:00
2023-09-01 22:03:56 +02:00
// move workspaces
2024-12-16 15:58:19 +00:00
std : : vector < PHLWORKSPACE > wspToMove ;
2025-04-22 15:23:29 +02:00
for ( auto const & w : g_pCompositor - > m_workspaces ) {
2025-04-30 23:45:20 +02:00
if ( w - > m_monitor = = m_self | | ! w - > m_monitor )
2024-04-02 20:32:39 +01:00
wspToMove . push_back ( w ) ;
2022-07-27 12:32:00 +02:00
}
2024-08-26 20:24:30 +02:00
for ( auto const & w : wspToMove ) {
2025-04-30 23:45:20 +02:00
w - > m_lastMonitor = m_name ;
2023-09-01 22:03:56 +02:00
g_pCompositor - > moveWorkspaceToMonitor ( w , BACKUPMON ) ;
w - > startAnim ( true , true , true ) ;
}
} else {
2025-04-22 15:23:29 +02:00
g_pCompositor - > m_lastFocus . reset ( ) ;
g_pCompositor - > m_lastWindow . reset ( ) ;
g_pCompositor - > m_lastMonitor . reset ( ) ;
2022-07-27 12:32:00 +02:00
}
2025-04-30 23:45:20 +02:00
if ( m_activeWorkspace )
m_activeWorkspace - > m_visible = false ;
m_activeWorkspace . reset ( ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setAdaptiveSync ( false ) ;
m_output - > state - > setEnabled ( false ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . commit ( ) )
2024-07-21 13:09:54 +02:00
Debug : : log ( WARN , " state.commit() failed in CMonitor::onDisconnect " ) ;
2022-07-27 12:32:00 +02:00
2025-04-30 23:45:20 +02:00
if ( g_pCompositor - > m_lastMonitor = = m_self )
2025-04-22 15:23:29 +02:00
g_pCompositor - > setActiveMonitor ( BACKUPMON ? BACKUPMON : g_pCompositor - > m_unsafeOutput . lock ( ) ) ;
2022-12-21 15:17:24 +00:00
2025-05-05 23:44:49 +02:00
if ( g_pHyprRenderer - > m_mostHzMonitor = = m_self ) {
2024-10-19 23:03:29 +01:00
int mostHz = 0 ;
PHLMONITOR pMonitorMostHz = nullptr ;
2022-12-22 12:15:00 +00:00
2025-04-22 15:23:29 +02:00
for ( auto const & m : g_pCompositor - > m_monitors ) {
2025-04-30 23:45:20 +02:00
if ( m - > m_refreshRate > mostHz & & m ! = m_self ) {
2024-10-19 23:03:29 +01:00
pMonitorMostHz = m ;
2025-04-30 23:45:20 +02:00
mostHz = m - > m_refreshRate ;
2022-12-22 12:15:00 +00:00
}
}
2025-05-05 23:44:49 +02:00
g_pHyprRenderer - > m_mostHzMonitor = pMonitorMostHz ;
2022-12-22 12:15:00 +00:00
}
2025-04-22 15:23:29 +02:00
std : : erase_if ( g_pCompositor - > m_monitors , [ & ] ( PHLMONITOR & el ) { return el . get ( ) = = this ; } ) ;
2022-07-27 12:32:00 +02:00
}
2022-08-23 16:07:47 +02:00
2024-12-28 07:32:04 -06:00
bool CMonitor : : applyMonitorRule ( SMonitorRule * pMonitorRule , bool force ) {
static auto PDISABLESCALECHECKS = CConfigValue < Hyprlang : : INT > ( " debug:disable_scale_checks " ) ;
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Applying monitor rule for {} " , m_name ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
m_activeMonitorRule = * pMonitorRule ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( m_forceSize . has_value ( ) )
m_activeMonitorRule . resolution = m_forceSize . value ( ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
const auto RULE = & m_activeMonitorRule ;
2024-12-28 07:32:04 -06:00
// if it's disabled, disable and ignore
if ( RULE - > disabled ) {
2025-04-30 23:45:20 +02:00
if ( m_enabled )
2024-12-28 07:32:04 -06:00
onDisconnect ( ) ;
2025-04-30 23:45:20 +02:00
m_events . modeChanged . emit ( ) ;
2024-12-28 07:32:04 -06:00
return true ;
}
// don't touch VR headsets
2025-04-30 23:45:20 +02:00
if ( m_output - > nonDesktop )
2024-12-28 07:32:04 -06:00
return true ;
2025-04-30 23:45:20 +02:00
if ( ! m_enabled ) {
2024-12-28 07:32:04 -06:00
onConnect ( true ) ; // enable it.
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {} is disabled but is requested to be enabled " , m_name ) ;
2024-12-28 07:32:04 -06:00
force = true ;
}
// Check if the rule isn't already applied
// TODO: clean this up lol
2025-05-05 23:40:37 +02:00
if ( ! force & & DELTALESSTHAN ( m_pixelSize . x , RULE - > resolution . x , 1 ) /* ↓ */
& & DELTALESSTHAN ( m_pixelSize . y , RULE - > resolution . y , 1 ) /* Resolution is the same */
& & m_pixelSize . x > 1 & & m_pixelSize . y > 1 /* Active resolution is not invalid */
& & DELTALESSTHAN ( m_refreshRate , RULE - > refreshRate , 1 ) /* Refresh rate is the same */
& & m_setScale = = RULE - > scale /* Scale is the same */
/* position is set correctly */
& & ( ( DELTALESSTHAN ( m_position . x , RULE - > offset . x , 1 ) & & DELTALESSTHAN ( m_position . y , RULE - > offset . y , 1 ) ) | | RULE - > offset = = Vector2D ( - INT32_MAX , - INT32_MAX ) )
/* other properties hadnt changed */
& & m_transform = = RULE - > transform & & RULE - > enable10bit = = m_enabled10bit & & RULE - > cmType = = m_cmType & & RULE - > sdrSaturation = = m_sdrSaturation & &
2025-04-30 23:45:20 +02:00
RULE - > sdrBrightness = = m_sdrBrightness & & ! std : : memcmp ( & m_customDrmMode , & RULE - > drmMode , sizeof ( m_customDrmMode ) ) ) {
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Not applying a new rule to {} because it's already applied! " , m_name ) ;
2024-12-28 07:32:04 -06:00
setMirror ( RULE - > mirrorOf ) ;
return true ;
}
bool autoScale = false ;
if ( RULE - > scale > 0.1 ) {
2025-04-30 23:45:20 +02:00
m_scale = RULE - > scale ;
2024-12-28 07:32:04 -06:00
} else {
autoScale = true ;
const auto DEFAULTSCALE = getDefaultScale ( ) ;
2025-04-30 23:45:20 +02:00
m_scale = DEFAULTSCALE ;
2024-12-28 07:32:04 -06:00
}
2025-04-30 23:45:20 +02:00
m_setScale = m_scale ;
m_transform = RULE - > transform ;
2024-12-28 07:32:04 -06:00
// accumulate requested modes in reverse order (cause inesrting at front is inefficient)
std : : vector < SP < Aquamarine : : SOutputMode > > requestedModes ;
2025-01-31 06:23:32 -06:00
std : : string requestedStr = " unknown " ;
2024-12-28 07:32:04 -06:00
// use sortFunc, add best 3 to requestedModes in reverse, since we test in reverse
auto addBest3Modes = [ & ] ( auto const & sortFunc ) {
2025-04-30 23:45:20 +02:00
auto sortedModes = m_output - > modes ;
2024-12-28 07:32:04 -06:00
std : : ranges : : sort ( sortedModes , sortFunc ) ;
if ( sortedModes . size ( ) > 3 )
sortedModes . erase ( sortedModes . begin ( ) + 3 , sortedModes . end ( ) ) ;
2025-05-30 18:25:59 +05:00
requestedModes . insert_range ( requestedModes . end ( ) , sortedModes | std : : views : : reverse ) ;
2024-12-28 07:32:04 -06:00
} ;
2025-01-31 06:23:32 -06:00
// last fallback is always preferred mode
2025-04-30 23:45:20 +02:00
if ( ! m_output - > preferredMode ( ) )
Debug : : log ( ERR , " Monitor {} has NO PREFERRED MODE " , m_output - > name ) ;
2024-12-28 07:32:04 -06:00
else
2025-04-30 23:45:20 +02:00
requestedModes . push_back ( m_output - > preferredMode ( ) ) ;
2024-12-28 07:32:04 -06:00
2025-01-31 06:23:32 -06:00
if ( RULE - > resolution = = Vector2D ( ) ) {
requestedStr = " preferred " ;
// fallback to first 3 modes if preferred fails/doesn't exist
2025-04-30 23:45:20 +02:00
requestedModes = m_output - > modes ;
2025-01-31 06:23:32 -06:00
if ( requestedModes . size ( ) > 3 )
requestedModes . erase ( requestedModes . begin ( ) + 3 , requestedModes . end ( ) ) ;
std : : ranges : : reverse ( requestedModes . begin ( ) , requestedModes . end ( ) ) ;
2025-04-30 23:45:20 +02:00
if ( m_output - > preferredMode ( ) )
requestedModes . push_back ( m_output - > preferredMode ( ) ) ;
2025-01-31 06:23:32 -06:00
} else if ( RULE - > resolution = = Vector2D ( - 1 , - 1 ) ) {
2024-12-28 07:32:04 -06:00
requestedStr = " highrr " ;
// sort prioritizing refresh rate 1st and resolution 2nd, then add best 3
addBest3Modes ( [ ] ( auto const & a , auto const & b ) {
2025-01-29 13:16:50 +00:00
if ( std : : round ( a - > refreshRate ) > std : : round ( b - > refreshRate ) )
2024-12-28 07:32:04 -06:00
return true ;
2025-01-29 13:16:50 +00:00
else if ( DELTALESSTHAN ( ( float ) a - > refreshRate , ( float ) b - > refreshRate , 1.F ) & & a - > pixelSize . x > b - > pixelSize . x & & a - > pixelSize . y > b - > pixelSize . y )
2024-12-28 07:32:04 -06:00
return true ;
return false ;
} ) ;
} else if ( RULE - > resolution = = Vector2D ( - 1 , - 2 ) ) {
requestedStr = " highres " ;
2025-05-26 18:25:58 +01:00
// sort prioritizing resolution 1st and refresh rate 2nd, then add best 3
2024-12-28 07:32:04 -06:00
addBest3Modes ( [ ] ( auto const & a , auto const & b ) {
if ( a - > pixelSize . x > b - > pixelSize . x & & a - > pixelSize . y > b - > pixelSize . y )
return true ;
2025-01-29 13:16:50 +00:00
else if ( DELTALESSTHAN ( a - > pixelSize . x , b - > pixelSize . x , 1 ) & & DELTALESSTHAN ( a - > pixelSize . y , b - > pixelSize . y , 1 ) & &
std : : round ( a - > refreshRate ) > std : : round ( b - > refreshRate ) )
2024-12-28 07:32:04 -06:00
return true ;
return false ;
} ) ;
2025-05-26 18:25:58 +01:00
} else if ( RULE - > resolution = = Vector2D ( - 1 , - 3 ) ) {
requestedStr = " maxwidth " ;
// sort prioritizing widest resolution 1st and refresh rate 2nd, then add best 3
addBest3Modes ( [ ] ( auto const & a , auto const & b ) {
if ( a - > pixelSize . x > b - > pixelSize . x )
return true ;
if ( a - > pixelSize . x = = b - > pixelSize . x & & std : : round ( a - > refreshRate ) > std : : round ( b - > refreshRate ) )
return true ;
return false ;
} ) ;
2024-12-28 07:32:04 -06:00
} else if ( RULE - > resolution ! = Vector2D ( ) ) {
// user requested mode
requestedStr = std : : format ( " {:X0}@{:.2f}Hz " , RULE - > resolution , RULE - > refreshRate ) ;
// sort by closeness to requested, then add best 3
addBest3Modes ( [ & ] ( auto const & a , auto const & b ) {
if ( abs ( a - > pixelSize . x - RULE - > resolution . x ) < abs ( b - > pixelSize . x - RULE - > resolution . x ) )
return true ;
if ( a - > pixelSize . x = = b - > pixelSize . x & & abs ( a - > pixelSize . y - RULE - > resolution . y ) < abs ( b - > pixelSize . y - RULE - > resolution . y ) )
return true ;
if ( a - > pixelSize = = b - > pixelSize & & abs ( ( a - > refreshRate / 1000.f ) - RULE - > refreshRate ) < abs ( ( b - > refreshRate / 1000.f ) - RULE - > refreshRate ) )
return true ;
return false ;
} ) ;
2024-12-30 12:45:42 -06:00
// if the best mode isnt close to requested, then try requested as custom mode first
2025-01-01 13:47:16 +01:00
if ( ! requestedModes . empty ( ) ) {
auto bestMode = requestedModes . back ( ) ;
if ( ! DELTALESSTHAN ( bestMode - > pixelSize . x , RULE - > resolution . x , 1 ) | | ! DELTALESSTHAN ( bestMode - > pixelSize . y , RULE - > resolution . y , 1 ) | |
! DELTALESSTHAN ( bestMode - > refreshRate / 1000.f , RULE - > refreshRate , 1 ) )
requestedModes . push_back ( makeShared < Aquamarine : : SOutputMode > ( Aquamarine : : SOutputMode { . pixelSize = RULE - > resolution , . refreshRate = RULE - > refreshRate * 1000.f } ) ) ;
}
2024-12-30 12:45:42 -06:00
2024-12-28 07:32:04 -06:00
// then if requested is custom, try custom mode first
if ( RULE - > drmMode . type = = DRM_MODE_TYPE_USERDEF ) {
2025-04-30 23:45:20 +02:00
if ( m_output - > getBackend ( ) - > type ( ) ! = Aquamarine : : eBackendType : : AQ_BACKEND_DRM )
2024-12-28 07:32:04 -06:00
Debug : : log ( ERR , " Tried to set custom modeline on non-DRM output " ) ;
else
requestedModes . push_back ( makeShared < Aquamarine : : SOutputMode > (
Aquamarine : : SOutputMode { . pixelSize = { RULE - > drmMode . hdisplay , RULE - > drmMode . vdisplay } , . refreshRate = RULE - > drmMode . vrefresh , . modeInfo = RULE - > drmMode } ) ) ;
}
}
2025-04-30 23:45:20 +02:00
const auto WAS10B = m_enabled10bit ;
const auto OLDRES = m_pixelSize ;
2024-12-28 07:32:04 -06:00
bool success = false ;
// Needed in case we are switching from a custom modeline to a standard mode
2025-04-30 23:45:20 +02:00
m_customDrmMode = { } ;
m_currentMode = nullptr ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
m_output - > state - > setFormat ( DRM_FORMAT_XRGB8888 ) ;
m_prevDrmFormat = m_drmFormat ;
m_drmFormat = DRM_FORMAT_XRGB8888 ;
m_output - > state - > resetExplicitFences ( ) ;
2024-12-28 07:32:04 -06:00
2025-04-21 20:42:02 +02:00
if ( Debug : : m_trace ) {
2025-04-30 23:45:20 +02:00
Debug : : log ( TRACE , " Monitor {} requested modes: " , m_name ) ;
2025-01-01 13:47:16 +01:00
if ( requestedModes . empty ( ) )
Debug : : log ( TRACE , " | None " ) ;
else {
for ( auto const & mode : requestedModes | std : : views : : reverse ) {
Debug : : log ( TRACE , " | {:X0}@{:.2f}Hz " , mode - > pixelSize , mode - > refreshRate / 1000.f ) ;
}
2024-12-28 07:32:04 -06:00
}
}
for ( auto const & mode : requestedModes | std : : views : : reverse ) {
std : : string modeStr = std : : format ( " {:X0}@{:.2f}Hz " , mode - > pixelSize , mode - > refreshRate / 1000.f ) ;
if ( mode - > modeInfo . has_value ( ) & & mode - > modeInfo - > type = = DRM_MODE_TYPE_USERDEF ) {
2025-04-30 23:45:20 +02:00
m_output - > state - > setCustomMode ( mode ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . test ( ) ) {
Debug : : log ( ERR , " Monitor {}: REJECTED custom mode {}! " , m_name , modeStr ) ;
2024-12-28 07:32:04 -06:00
continue ;
}
2025-04-30 23:45:20 +02:00
m_customDrmMode = mode - > modeInfo . value ( ) ;
2024-12-28 07:32:04 -06:00
} else {
2025-04-30 23:45:20 +02:00
m_output - > state - > setMode ( mode ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . test ( ) ) {
Debug : : log ( ERR , " Monitor {}: REJECTED available mode {}! " , m_name , modeStr ) ;
2024-12-28 07:32:04 -06:00
if ( mode - > preferred )
2025-04-30 23:45:20 +02:00
Debug : : log ( ERR , " Monitor {}: REJECTED preferred mode!!! " , m_name ) ;
2024-12-28 07:32:04 -06:00
continue ;
}
2025-04-30 23:45:20 +02:00
m_customDrmMode = { } ;
2024-12-28 07:32:04 -06:00
}
2025-04-30 23:45:20 +02:00
m_refreshRate = mode - > refreshRate / 1000.f ;
m_size = mode - > pixelSize ;
m_currentMode = mode ;
2024-12-28 07:32:04 -06:00
success = true ;
if ( mode - > preferred )
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {}: requested {}, using preferred mode {} " , m_name , requestedStr , modeStr ) ;
2024-12-28 07:32:04 -06:00
else if ( mode - > modeInfo . has_value ( ) & & mode - > modeInfo - > type = = DRM_MODE_TYPE_USERDEF )
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {}: requested {}, using custom mode {} " , m_name , requestedStr , modeStr ) ;
2024-12-28 07:32:04 -06:00
else
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {}: requested {}, using available mode {} " , m_name , requestedStr , modeStr ) ;
2024-12-28 07:32:04 -06:00
break ;
}
// try requested as custom mode jic it works
if ( ! success & & RULE - > resolution ! = Vector2D ( ) & & RULE - > resolution ! = Vector2D ( - 1 , - 1 ) & & RULE - > resolution ! = Vector2D ( - 1 , - 2 ) ) {
2025-04-30 23:45:20 +02:00
auto refreshRate = m_output - > getBackend ( ) - > type ( ) = = Aquamarine : : eBackendType : : AQ_BACKEND_DRM ? RULE - > refreshRate * 1000 : 0 ;
2024-12-28 07:32:04 -06:00
auto mode = makeShared < Aquamarine : : SOutputMode > ( Aquamarine : : SOutputMode { . pixelSize = RULE - > resolution , . refreshRate = refreshRate } ) ;
std : : string modeStr = std : : format ( " {:X0}@{:.2f}Hz " , mode - > pixelSize , mode - > refreshRate / 1000.f ) ;
2025-04-30 23:45:20 +02:00
m_output - > state - > setCustomMode ( mode ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( m_state . test ( ) ) {
Debug : : log ( LOG , " Monitor {}: requested {}, using custom mode {} " , m_name , requestedStr , modeStr ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
refreshRate = mode - > refreshRate / 1000.f ;
m_size = mode - > pixelSize ;
m_currentMode = mode ;
m_customDrmMode = { } ;
2024-12-28 07:32:04 -06:00
success = true ;
} else
2025-04-30 23:45:20 +02:00
Debug : : log ( ERR , " Monitor {}: REJECTED custom mode {}! " , m_name , modeStr ) ;
2024-12-28 07:32:04 -06:00
}
// try any of the modes if none of the above work
if ( ! success ) {
2025-04-30 23:45:20 +02:00
for ( auto const & mode : m_output - > modes ) {
m_output - > state - > setMode ( mode ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . test ( ) )
2024-12-28 07:32:04 -06:00
continue ;
auto errorMessage =
2025-04-30 23:45:20 +02:00
std : : format ( " Monitor {} failed to set any requested modes, falling back to mode {:X0}@{:.2f}Hz " , m_name , mode - > pixelSize , mode - > refreshRate / 1000.f ) ;
2024-12-28 07:32:04 -06:00
Debug : : log ( WARN , errorMessage ) ;
g_pHyprNotificationOverlay - > addNotification ( errorMessage , CHyprColor ( 0xff0000ff ) , 5000 , ICON_WARNING ) ;
2025-04-30 23:45:20 +02:00
m_refreshRate = mode - > refreshRate / 1000.f ;
m_size = mode - > pixelSize ;
m_currentMode = mode ;
m_customDrmMode = { } ;
2024-12-28 07:32:04 -06:00
success = true ;
break ;
}
}
if ( ! success ) {
2025-04-30 23:45:20 +02:00
Debug : : log ( ERR , " Monitor {} has NO FALLBACK MODES, and an INVALID one was requested: {:X0}@{:.2f}Hz " , m_name , RULE - > resolution , RULE - > refreshRate ) ;
2024-12-28 07:32:04 -06:00
return true ;
}
2025-04-30 23:45:20 +02:00
m_vrrActive = m_output - > state - > state ( ) . adaptiveSync // disabled here, will be tested in CConfigManager::ensureVRR()
| | m_createdByUser ; // wayland backend doesn't allow for disabling adaptive_sync
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
m_pixelSize = m_size ;
2024-12-28 07:32:04 -06:00
// clang-format off
static const std : : array < std : : vector < std : : pair < std : : string , uint32_t > > , 2 > formats {
std : : vector < std : : pair < std : : string , uint32_t > > { /* 10-bit */
{ " DRM_FORMAT_XRGB2101010 " , DRM_FORMAT_XRGB2101010 } , { " DRM_FORMAT_XBGR2101010 " , DRM_FORMAT_XBGR2101010 } , { " DRM_FORMAT_XRGB8888 " , DRM_FORMAT_XRGB8888 } , { " DRM_FORMAT_XBGR8888 " , DRM_FORMAT_XBGR8888 }
} ,
std : : vector < std : : pair < std : : string , uint32_t > > { /* 8-bit */
{ " DRM_FORMAT_XRGB8888 " , DRM_FORMAT_XRGB8888 } , { " DRM_FORMAT_XBGR8888 " , DRM_FORMAT_XBGR8888 }
}
} ;
// clang-format on
bool set10bit = false ;
for ( auto const & fmt : formats [ ( int ) ! RULE - > enable10bit ] ) {
2025-04-30 23:45:20 +02:00
m_output - > state - > setFormat ( fmt . second ) ;
m_prevDrmFormat = m_drmFormat ;
m_drmFormat = fmt . second ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . test ( ) ) {
Debug : : log ( ERR , " output {} failed basic test on format {} " , m_name , fmt . first ) ;
2024-12-28 07:32:04 -06:00
} else {
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " output {} succeeded basic test on format {} " , m_name , fmt . first ) ;
2024-12-28 07:32:04 -06:00
if ( RULE - > enable10bit & & fmt . first . contains ( " 101010 " ) )
set10bit = true ;
break ;
}
}
2025-04-30 23:45:20 +02:00
m_enabled10bit = set10bit ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
auto oldImageDescription = m_imageDescription ;
m_cmType = RULE - > cmType ;
switch ( m_cmType ) {
case CM_AUTO : m_cmType = m_enabled10bit & & m_output - > parsedEDID . supportsBT2020 ? CM_WIDE : CM_SRGB ; break ;
case CM_EDID : m_cmType = m_output - > parsedEDID . chromaticityCoords . has_value ( ) ? CM_EDID : CM_SRGB ; break ;
2025-03-14 02:15:18 +03:00
case CM_HDR :
case CM_HDR_EDID :
2025-04-30 23:45:20 +02:00
m_cmType = m_output - > parsedEDID . supportsBT2020 & & m_output - > parsedEDID . hdrMetadata . has_value ( ) & & m_output - > parsedEDID . hdrMetadata - > supportsPQ ? m_cmType : CM_SRGB ;
2025-03-14 02:15:18 +03:00
break ;
default : break ;
}
2025-04-30 23:45:20 +02:00
switch ( m_cmType ) {
case CM_SRGB : m_imageDescription = { } ; break ; // assumes SImageDescirption defaults to sRGB
2025-03-14 02:15:18 +03:00
case CM_WIDE :
2025-04-30 23:45:20 +02:00
m_imageDescription = { . primariesNameSet = true ,
. primariesNamed = NColorManagement : : CM_PRIMARIES_BT2020 ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_BT2020 ) } ;
2025-03-14 02:15:18 +03:00
break ;
case CM_EDID :
2025-04-30 23:45:20 +02:00
m_imageDescription = { . primariesNameSet = false ,
. primariesNamed = NColorManagement : : CM_PRIMARIES_BT2020 ,
. primaries = {
. red = { . x = m_output - > parsedEDID . chromaticityCoords - > red . x , . y = m_output - > parsedEDID . chromaticityCoords - > red . y } ,
. green = { . x = m_output - > parsedEDID . chromaticityCoords - > green . x , . y = m_output - > parsedEDID . chromaticityCoords - > green . y } ,
. blue = { . x = m_output - > parsedEDID . chromaticityCoords - > blue . x , . y = m_output - > parsedEDID . chromaticityCoords - > blue . y } ,
. white = { . x = m_output - > parsedEDID . chromaticityCoords - > white . x , . y = m_output - > parsedEDID . chromaticityCoords - > white . y } ,
} } ;
2025-03-14 02:15:18 +03:00
break ;
case CM_HDR :
2025-04-30 23:45:20 +02:00
m_imageDescription = { . transferFunction = NColorManagement : : CM_TRANSFER_FUNCTION_ST2084_PQ ,
. primariesNameSet = true ,
. primariesNamed = NColorManagement : : CM_PRIMARIES_BT2020 ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_BT2020 ) ,
. luminances = { . min = 0 , . max = 10000 , . reference = 203 } } ;
2025-03-14 02:15:18 +03:00
break ;
case CM_HDR_EDID :
2025-04-30 23:45:20 +02:00
m_imageDescription = { . transferFunction = NColorManagement : : CM_TRANSFER_FUNCTION_ST2084_PQ ,
. primariesNameSet = false ,
. primariesNamed = NColorManagement : : CM_PRIMARIES_BT2020 ,
. primaries = m_output - > parsedEDID . chromaticityCoords . has_value ( ) ?
NColorManagement : : SPCPRimaries {
. red = { . x = m_output - > parsedEDID . chromaticityCoords - > red . x , . y = m_output - > parsedEDID . chromaticityCoords - > red . y } ,
. green = { . x = m_output - > parsedEDID . chromaticityCoords - > green . x , . y = m_output - > parsedEDID . chromaticityCoords - > green . y } ,
. blue = { . x = m_output - > parsedEDID . chromaticityCoords - > blue . x , . y = m_output - > parsedEDID . chromaticityCoords - > blue . y } ,
. white = { . x = m_output - > parsedEDID . chromaticityCoords - > white . x , . y = m_output - > parsedEDID . chromaticityCoords - > white . y } ,
} :
NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_BT2020 ) ,
. luminances = { . min = m_output - > parsedEDID . hdrMetadata - > desiredContentMinLuminance ,
. max = m_output - > parsedEDID . hdrMetadata - > desiredContentMaxLuminance ,
. reference = m_output - > parsedEDID . hdrMetadata - > desiredMaxFrameAverageLuminance } } ;
2025-03-14 02:15:18 +03:00
break ;
default : UNREACHABLE ( ) ;
}
2025-04-30 23:45:20 +02:00
if ( oldImageDescription ! = m_imageDescription )
PROTO : : colorManagement - > onMonitorImageDescriptionChanged ( m_self ) ;
2025-03-14 02:15:18 +03:00
2025-04-30 23:45:20 +02:00
m_sdrSaturation = RULE - > sdrSaturation ;
m_sdrBrightness = RULE - > sdrBrightness ;
2025-03-14 02:15:18 +03:00
2025-04-30 23:45:20 +02:00
Vector2D logicalSize = m_pixelSize / m_scale ;
2024-12-28 07:32:04 -06:00
if ( ! * PDISABLESCALECHECKS & & ( logicalSize . x ! = std : : round ( logicalSize . x ) | | logicalSize . y ! = std : : round ( logicalSize . y ) ) ) {
// invalid scale, will produce fractional pixels.
// find the nearest valid.
2025-04-30 23:45:20 +02:00
float searchScale = std : : round ( m_scale * 120.0 ) ;
2024-12-28 07:32:04 -06:00
bool found = false ;
double scaleZero = searchScale / 120.0 ;
2025-04-30 23:45:20 +02:00
Vector2D logicalZero = m_pixelSize / scaleZero ;
2024-12-28 07:32:04 -06:00
if ( logicalZero = = logicalZero . round ( ) )
2025-04-30 23:45:20 +02:00
m_scale = scaleZero ;
2024-12-28 07:32:04 -06:00
else {
for ( size_t i = 1 ; i < 90 ; + + i ) {
double scaleUp = ( searchScale + i ) / 120.0 ;
double scaleDown = ( searchScale - i ) / 120.0 ;
2025-04-30 23:45:20 +02:00
Vector2D logicalUp = m_pixelSize / scaleUp ;
Vector2D logicalDown = m_pixelSize / scaleDown ;
2024-12-28 07:32:04 -06:00
if ( logicalUp = = logicalUp . round ( ) ) {
found = true ;
searchScale = scaleUp ;
break ;
}
if ( logicalDown = = logicalDown . round ( ) ) {
found = true ;
searchScale = scaleDown ;
break ;
}
}
if ( ! found ) {
if ( autoScale )
2025-04-30 23:45:20 +02:00
m_scale = std : : round ( scaleZero ) ;
2024-12-28 07:32:04 -06:00
else {
2025-04-30 23:45:20 +02:00
Debug : : log ( ERR , " Invalid scale passed to monitor, {} failed to find a clean divisor " , m_scale ) ;
g_pConfigManager - > addParseError ( " Invalid scale passed to monitor " + m_name + " , failed to find a clean divisor " ) ;
m_scale = getDefaultScale ( ) ;
2024-12-28 07:32:04 -06:00
}
} else {
if ( ! autoScale ) {
2025-04-30 23:45:20 +02:00
Debug : : log ( ERR , " Invalid scale passed to monitor, {} found suggestion {} " , m_scale , searchScale ) ;
2024-12-28 07:32:04 -06:00
g_pConfigManager - > addParseError (
2025-04-30 23:45:20 +02:00
std : : format ( " Invalid scale passed to monitor {}, failed to find a clean divisor. Suggested nearest scale: {:5f} " , m_name , searchScale ) ) ;
m_scale = getDefaultScale ( ) ;
2024-12-28 07:32:04 -06:00
} else
2025-04-30 23:45:20 +02:00
m_scale = searchScale ;
2024-12-28 07:32:04 -06:00
}
}
}
2025-04-30 23:45:20 +02:00
m_output - > scheduleFrame ( ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . commit ( ) )
Debug : : log ( ERR , " Couldn't commit output named {} " , m_output - > name ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
Vector2D xfmd = m_transform % 2 = = 1 ? Vector2D { m_pixelSize . y , m_pixelSize . x } : m_pixelSize ;
m_size = ( xfmd / m_scale ) . round ( ) ;
m_transformedSize = xfmd ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
if ( m_createdByUser ) {
CBox transformedBox = { 0 , 0 , m_transformedSize . x , m_transformedSize . y } ;
transformedBox . transform ( wlTransformToHyprutils ( invertTransform ( m_transform ) ) , m_transformedSize . x , m_transformedSize . y ) ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
m_pixelSize = Vector2D ( transformedBox . width , transformedBox . height ) ;
2024-12-28 07:32:04 -06:00
}
updateMatrix ( ) ;
2025-04-30 23:45:20 +02:00
if ( WAS10B ! = m_enabled10bit | | OLDRES ! = m_pixelSize )
g_pHyprOpenGL - > destroyMonitorResources ( m_self ) ;
2024-12-28 07:32:04 -06:00
g_pCompositor - > arrangeMonitors ( ) ;
2025-04-30 23:45:20 +02:00
m_damage . setSize ( m_transformedSize ) ;
2024-12-28 07:32:04 -06:00
// Set scale for all surfaces on this monitor, needed for some clients
// but not on unsafe state to avoid crashes
2025-04-22 15:23:29 +02:00
if ( ! g_pCompositor - > m_unsafeState ) {
for ( auto const & w : g_pCompositor - > m_windows ) {
2024-12-28 07:32:04 -06:00
w - > updateSurfaceScaleTransformDetails ( ) ;
}
}
// updato us
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > arrangeLayersForMonitor ( m_id ) ;
2024-12-28 07:32:04 -06:00
// reload to fix mirrors
2025-04-20 20:39:33 +02:00
g_pConfigManager - > m_wantsMonitorReload = true ;
2024-12-28 07:32:04 -06:00
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {} " , m_name , m_pixelSize , m_refreshRate , m_scale , ( int ) m_transform ,
m_position , ( int ) m_enabled10bit ) ;
2024-12-28 07:32:04 -06:00
EMIT_HOOK_EVENT ( " monitorLayoutChanged " , nullptr ) ;
2025-04-30 23:45:20 +02:00
m_events . modeChanged . emit ( ) ;
2024-12-28 07:32:04 -06:00
return true ;
}
2023-04-07 16:31:51 +01:00
void CMonitor : : addDamage ( const pixman_region32_t * rg ) {
2024-05-09 22:23:01 +01:00
static auto PZOOMFACTOR = CConfigValue < Hyprlang : : FLOAT > ( " cursor:zoom_factor " ) ;
2025-04-30 23:45:20 +02:00
if ( * PZOOMFACTOR ! = 1.f & & g_pCompositor - > getMonitorFromCursor ( ) = = m_self ) {
m_damage . damageEntire ( ) ;
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_DAMAGE ) ;
} else if ( m_damage . damage ( rg ) )
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_DAMAGE ) ;
2022-08-23 16:07:47 +02:00
}
2025-01-26 15:05:34 +00:00
void CMonitor : : addDamage ( const CRegion & rg ) {
addDamage ( const_cast < CRegion * > ( & rg ) - > pixman ( ) ) ;
2023-07-19 20:09:49 +02:00
}
2025-01-26 15:05:34 +00:00
void CMonitor : : addDamage ( const CBox & box ) {
2024-05-09 22:23:01 +01:00
static auto PZOOMFACTOR = CConfigValue < Hyprlang : : FLOAT > ( " cursor:zoom_factor " ) ;
2025-04-30 23:45:20 +02:00
if ( * PZOOMFACTOR ! = 1.f & & g_pCompositor - > getMonitorFromCursor ( ) = = m_self ) {
m_damage . damageEntire ( ) ;
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_DAMAGE ) ;
2023-04-16 14:48:38 +01:00
}
2025-04-30 23:45:20 +02:00
if ( m_damage . damage ( box ) )
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_DAMAGE ) ;
2022-08-23 16:07:47 +02:00
}
2022-09-13 15:25:42 +02:00
2024-06-14 14:45:32 +03:00
bool CMonitor : : shouldSkipScheduleFrameOnMouseEvent ( ) {
static auto PNOBREAK = CConfigValue < Hyprlang : : INT > ( " cursor:no_break_fs_vrr " ) ;
static auto PMINRR = CConfigValue < Hyprlang : : INT > ( " cursor:min_refresh_rate " ) ;
// skip scheduling extra frames for fullsreen apps with vrr
2025-04-30 23:45:20 +02:00
const bool shouldSkip = m_activeWorkspace & & m_activeWorkspace - > m_hasFullscreenWindow & & m_activeWorkspace - > m_fullscreenMode = = FSMODE_FULLSCREEN & &
( * PNOBREAK = = 1 | | ( * PNOBREAK = = 2 & & m_activeWorkspace - > getFullscreenWindow ( ) - > getContentType ( ) = = CONTENT_TYPE_GAME ) ) & & m_output - > state - > state ( ) . adaptiveSync ;
2024-06-14 14:45:32 +03:00
// keep requested minimum refresh rate
2025-04-30 23:45:20 +02:00
if ( shouldSkip & & * PMINRR & & m_lastPresentationTimer . getMillis ( ) > 1000.0f / * PMINRR ) {
2024-06-14 14:45:32 +03:00
// damage whole screen because some previous cursor box damages were skipped
2025-04-30 23:45:20 +02:00
m_damage . damageEntire ( ) ;
2024-06-14 14:45:32 +03:00
return false ;
}
return shouldSkip ;
}
2022-09-13 15:25:42 +02:00
bool CMonitor : : isMirror ( ) {
2025-04-30 23:45:20 +02:00
return m_mirrorOf ! = nullptr ;
2022-09-13 15:25:42 +02:00
}
2024-02-27 23:11:59 +01:00
bool CMonitor : : matchesStaticSelector ( const std : : string & selector ) const {
if ( selector . starts_with ( " desc: " ) ) {
// match by description
2025-03-30 03:12:15 +02:00
const auto DESCRIPTIONSELECTOR = trim ( selector . substr ( 5 ) ) ;
2024-02-27 23:11:59 +01:00
2025-04-30 23:45:20 +02:00
return m_description . starts_with ( DESCRIPTIONSELECTOR ) | | m_shortDescription . starts_with ( DESCRIPTIONSELECTOR ) ;
2024-02-27 23:11:59 +01:00
} else {
// match by selector
2025-04-30 23:45:20 +02:00
return m_name = = selector ;
2024-02-27 23:11:59 +01:00
}
}
2024-08-08 21:01:50 +02:00
WORKSPACEID CMonitor : : findAvailableDefaultWS ( ) {
for ( WORKSPACEID i = 1 ; i < LONG_MAX ; + + i ) {
2022-12-09 17:17:02 +00:00
if ( g_pCompositor - > getWorkspaceByID ( i ) )
continue ;
2025-04-30 23:45:20 +02:00
if ( const auto BOUND = g_pConfigManager - > getBoundMonitorStringForWS ( std : : to_string ( i ) ) ; ! BOUND . empty ( ) & & BOUND ! = m_name )
2022-12-09 17:17:02 +00:00
continue ;
2022-12-16 17:17:31 +00:00
2022-12-09 17:17:02 +00:00
return i ;
}
2024-08-08 21:01:50 +02:00
return LONG_MAX ; // shouldn't be reachable
2022-12-09 17:17:02 +00:00
}
2022-09-13 15:25:42 +02:00
void CMonitor : : setupDefaultWS ( const SMonitorRule & monitorRule ) {
// Workspace
std : : string newDefaultWorkspaceName = " " ;
2024-06-23 00:52:42 +03:00
int64_t wsID = WORKSPACE_INVALID ;
2025-04-30 23:45:20 +02:00
if ( g_pConfigManager - > getDefaultWorkspaceFor ( m_name ) . empty ( ) )
2024-06-23 00:52:42 +03:00
wsID = findAvailableDefaultWS ( ) ;
else {
2025-04-30 23:45:20 +02:00
const auto ws = getWorkspaceIDNameFromString ( g_pConfigManager - > getDefaultWorkspaceFor ( m_name ) ) ;
2024-06-23 00:52:42 +03:00
wsID = ws . id ;
newDefaultWorkspaceName = ws . name ;
}
2022-09-13 15:25:42 +02:00
2024-06-23 00:52:42 +03:00
if ( wsID = = WORKSPACE_INVALID | | ( wsID > = SPECIAL_WORKSPACE_START & & wsID < = - 2 ) ) {
2025-04-22 15:23:29 +02:00
wsID = g_pCompositor - > m_workspaces . size ( ) + 1 ;
2024-06-23 00:52:42 +03:00
newDefaultWorkspaceName = std : : to_string ( wsID ) ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
Debug : : log ( LOG , " Invalid workspace= directive name in monitor parsing, workspace name \" {} \" is invalid. " , g_pConfigManager - > getDefaultWorkspaceFor ( m_name ) ) ;
2022-09-13 15:25:42 +02:00
}
2024-06-23 00:52:42 +03:00
auto PNEWWORKSPACE = g_pCompositor - > getWorkspaceByID ( wsID ) ;
2022-09-13 15:25:42 +02:00
2024-06-23 00:52:42 +03:00
Debug : : log ( LOG , " New monitor: WORKSPACEID {}, exists: {} " , wsID , ( int ) ( PNEWWORKSPACE ! = nullptr ) ) ;
2022-09-13 15:25:42 +02:00
if ( PNEWWORKSPACE ) {
// workspace exists, move it to the newly connected monitor
2025-04-30 23:45:20 +02:00
g_pCompositor - > moveWorkspaceToMonitor ( PNEWWORKSPACE , m_self . lock ( ) ) ;
m_activeWorkspace = PNEWWORKSPACE ;
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( m_id ) ;
2022-09-13 15:25:42 +02:00
PNEWWORKSPACE - > startAnim ( true , true , true ) ;
} else {
if ( newDefaultWorkspaceName = = " " )
2024-06-23 00:52:42 +03:00
newDefaultWorkspaceName = std : : to_string ( wsID ) ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
PNEWWORKSPACE = g_pCompositor - > m_workspaces . emplace_back ( CWorkspace : : create ( wsID , m_self . lock ( ) , newDefaultWorkspaceName ) ) ;
2022-09-13 15:25:42 +02:00
}
2025-04-30 23:45:20 +02:00
m_activeWorkspace = PNEWWORKSPACE ;
2022-09-13 15:25:42 +02:00
PNEWWORKSPACE - > setActive ( true ) ;
2025-04-25 02:37:12 +02:00
PNEWWORKSPACE - > m_visible = true ;
PNEWWORKSPACE - > m_lastMonitor = " " ;
2022-09-13 15:25:42 +02:00
}
void CMonitor : : setMirror ( const std : : string & mirrorOf ) {
const auto PMIRRORMON = g_pCompositor - > getMonitorFromString ( mirrorOf ) ;
2025-04-30 23:45:20 +02:00
if ( PMIRRORMON = = m_mirrorOf )
2022-09-13 15:25:42 +02:00
return ;
if ( PMIRRORMON & & PMIRRORMON - > isMirror ( ) ) {
Debug : : log ( ERR , " Cannot mirror a mirror! " ) ;
return ;
}
2025-04-30 23:45:20 +02:00
if ( PMIRRORMON = = m_self ) {
2022-09-13 15:25:42 +02:00
Debug : : log ( ERR , " Cannot mirror self! " ) ;
return ;
}
if ( ! PMIRRORMON ) {
// disable mirroring
2025-04-30 23:45:20 +02:00
if ( m_mirrorOf ) {
2025-05-30 18:25:59 +05:00
m_mirrorOf - > m_mirrors . erase ( std : : ranges : : find_if ( m_mirrorOf - > m_mirrors , [ & ] ( const auto & other ) { return other = = m_self ; } ) ) ;
2024-06-19 10:24:28 -04:00
// unlock software for mirrored monitor
2025-04-30 23:45:20 +02:00
g_pPointerManager - > unlockSoftwareForMonitor ( m_mirrorOf . lock ( ) ) ;
2022-09-13 15:25:42 +02:00
}
2025-04-30 23:45:20 +02:00
m_mirrorOf . reset ( ) ;
2022-09-13 15:25:42 +02:00
// set rule
2025-04-30 23:45:20 +02:00
const auto RULE = g_pConfigManager - > getMonitorRuleFor ( m_self . lock ( ) ) ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
m_position = RULE . offset ;
2022-09-13 15:25:42 +02:00
// push to mvmonitors
2023-11-01 18:53:36 +00:00
2024-10-26 02:06:13 +01:00
PHLMONITOR * thisWrapper = nullptr ;
2023-11-01 18:53:36 +00:00
// find the wrap
2025-04-22 15:23:29 +02:00
for ( auto & m : g_pCompositor - > m_realMonitors ) {
2025-04-30 23:45:20 +02:00
if ( m - > m_id = = m_id ) {
2023-11-01 18:53:36 +00:00
thisWrapper = & m ;
break ;
2022-09-13 15:25:42 +02:00
}
}
2023-11-01 18:53:36 +00:00
RASSERT ( thisWrapper - > get ( ) , " CMonitor::setMirror: Had no wrapper??? " ) ;
2025-05-30 18:25:59 +05:00
if ( std : : ranges : : find_if ( g_pCompositor - > m_monitors , [ & ] ( auto & other ) { return other . get ( ) = = this ; } ) = = g_pCompositor - > m_monitors . end ( ) ) {
2025-04-22 15:23:29 +02:00
g_pCompositor - > m_monitors . push_back ( * thisWrapper ) ;
2022-09-13 15:25:42 +02:00
}
setupDefaultWS ( RULE ) ;
2024-12-28 07:32:04 -06:00
applyMonitorRule ( ( SMonitorRule * ) & RULE , true ) ; // will apply the offset and stuff
2022-09-13 15:25:42 +02:00
} else {
2024-10-19 23:03:29 +01:00
PHLMONITOR BACKUPMON = nullptr ;
2025-04-22 15:23:29 +02:00
for ( auto const & m : g_pCompositor - > m_monitors ) {
2022-09-13 15:25:42 +02:00
if ( m . get ( ) ! = this ) {
2024-10-19 23:03:29 +01:00
BACKUPMON = m ;
2022-09-13 15:25:42 +02:00
break ;
}
}
// move all the WS
2024-12-16 15:58:19 +00:00
std : : vector < PHLWORKSPACE > wspToMove ;
2025-04-22 15:23:29 +02:00
for ( auto const & w : g_pCompositor - > m_workspaces ) {
2025-04-30 23:45:20 +02:00
if ( w - > m_monitor = = m_self | | ! w - > m_monitor )
2024-04-02 20:32:39 +01:00
wspToMove . push_back ( w ) ;
2022-09-13 15:25:42 +02:00
}
2024-08-26 20:24:30 +02:00
for ( auto const & w : wspToMove ) {
2022-09-13 15:25:42 +02:00
g_pCompositor - > moveWorkspaceToMonitor ( w , BACKUPMON ) ;
w - > startAnim ( true , true , true ) ;
}
2025-04-30 23:45:20 +02:00
m_activeWorkspace . reset ( ) ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
m_position = PMIRRORMON - > m_position ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
m_mirrorOf = PMIRRORMON ;
2022-09-13 15:25:42 +02:00
2025-04-30 23:45:20 +02:00
m_mirrorOf - > m_mirrors . push_back ( m_self ) ;
2022-09-13 15:25:42 +02:00
// remove from mvmonitors
2025-04-30 23:45:20 +02:00
std : : erase_if ( g_pCompositor - > m_monitors , [ & ] ( const auto & other ) { return other = = m_self ; } ) ;
2022-09-13 15:25:42 +02:00
2023-08-14 14:22:06 +02:00
g_pCompositor - > arrangeMonitors ( ) ;
2025-04-22 15:23:29 +02:00
g_pCompositor - > setActiveMonitor ( g_pCompositor - > m_monitors . front ( ) ) ;
2022-12-26 13:26:53 +01:00
g_pCompositor - > sanityCheckWorkspaces ( ) ;
2024-06-19 10:24:28 -04:00
// Software lock mirrored monitor
g_pPointerManager - > lockSoftwareForMonitor ( PMIRRORMON ) ;
2022-09-13 15:25:42 +02:00
}
2024-06-09 22:28:51 +02:00
2025-04-30 23:45:20 +02:00
m_events . modeChanged . emit ( ) ;
2022-09-19 20:44:33 +02:00
}
2022-12-14 17:57:18 +00:00
float CMonitor : : getDefaultScale ( ) {
2025-04-30 23:45:20 +02:00
if ( ! m_enabled )
2022-12-14 17:57:18 +00:00
return 1 ;
static constexpr double MMPERINCH = 25.4 ;
2025-04-30 23:45:20 +02:00
const auto DIAGONALPX = sqrt ( pow ( m_pixelSize . x , 2 ) + pow ( m_pixelSize . y , 2 ) ) ;
const auto DIAGONALIN = sqrt ( pow ( m_output - > physicalSize . x / MMPERINCH , 2 ) + pow ( m_output - > physicalSize . y / MMPERINCH , 2 ) ) ;
2022-12-14 17:57:18 +00:00
2022-12-16 17:17:31 +00:00
const auto PPI = DIAGONALPX / DIAGONALIN ;
2022-12-14 17:57:18 +00:00
if ( PPI > 200 /* High PPI, 2x*/ )
return 2 ;
2022-12-20 13:33:29 +00:00
else if ( PPI > 140 /* Medium PPI, 1.5x*/ )
2022-12-14 17:57:18 +00:00
return 1.5 ;
return 1 ;
}
2023-04-14 15:03:53 +01:00
2025-05-05 20:54:27 -05:00
static bool shouldWraparound ( const WORKSPACEID id1 , const WORKSPACEID id2 ) {
static auto PWORKSPACEWRAPAROUND = CConfigValue < Hyprlang : : INT > ( " animations:workspace_wraparound " ) ;
if ( ! * PWORKSPACEWRAPAROUND )
return false ;
WORKSPACEID lowestID = INT64_MAX ;
WORKSPACEID highestID = INT64_MIN ;
for ( auto const & w : g_pCompositor - > m_workspaces ) {
if ( w - > m_id < 0 | | w - > m_isSpecialWorkspace )
continue ;
lowestID = std : : min ( w - > m_id , lowestID ) ;
highestID = std : : max ( w - > m_id , highestID ) ;
}
return std : : min ( id1 , id2 ) = = lowestID & & std : : max ( id1 , id2 ) = = highestID ;
}
2024-04-02 20:32:39 +01:00
void CMonitor : : changeWorkspace ( const PHLWORKSPACE & pWorkspace , bool internal , bool noMouseMove , bool noFocus ) {
2023-04-14 15:28:22 +01:00
if ( ! pWorkspace )
2023-04-14 15:03:53 +01:00
return ;
2025-04-25 02:37:12 +02:00
if ( pWorkspace - > m_isSpecialWorkspace ) {
2025-04-30 23:45:20 +02:00
if ( m_activeSpecialWorkspace ! = pWorkspace ) {
2025-04-25 02:37:12 +02:00
Debug : : log ( LOG , " changeworkspace on special, togglespecialworkspace to id {} " , pWorkspace - > m_id ) ;
2024-04-13 06:39:20 -07:00
setSpecialWorkspace ( pWorkspace ) ;
2023-05-05 15:01:28 +01:00
}
2023-04-17 13:32:35 +01:00
return ;
}
2025-04-30 23:45:20 +02:00
if ( pWorkspace = = m_activeWorkspace )
2023-09-03 17:21:55 +02:00
return ;
2025-04-30 23:45:20 +02:00
const auto POLDWORKSPACE = m_activeWorkspace ;
2025-02-03 19:53:14 +00:00
if ( POLDWORKSPACE )
2025-04-25 02:37:12 +02:00
POLDWORKSPACE - > m_visible = false ;
pWorkspace - > m_visible = true ;
2023-04-14 15:03:53 +01:00
2025-04-30 23:45:20 +02:00
m_activeWorkspace = pWorkspace ;
2023-04-14 15:03:53 +01:00
if ( ! internal ) {
2025-05-05 20:54:27 -05:00
const auto ANIMTOLEFT = POLDWORKSPACE & & ( shouldWraparound ( pWorkspace - > m_id , POLDWORKSPACE - > m_id ) ^ ( pWorkspace - > m_id > POLDWORKSPACE - > m_id ) ) ;
2025-02-03 19:53:14 +00:00
if ( POLDWORKSPACE )
POLDWORKSPACE - > startAnim ( false , ANIMTOLEFT ) ;
2023-04-14 15:03:53 +01:00
pWorkspace - > startAnim ( true , ANIMTOLEFT ) ;
// move pinned windows
2025-04-22 15:23:29 +02:00
for ( auto const & w : g_pCompositor - > m_windows ) {
2025-04-28 22:25:22 +02:00
if ( w - > m_workspace = = POLDWORKSPACE & & w - > m_pinned )
2024-04-02 20:32:39 +01:00
w - > moveToWorkspace ( pWorkspace ) ;
2023-04-14 15:03:53 +01:00
}
2025-04-30 23:45:20 +02:00
if ( ! noFocus & & ! g_pCompositor - > m_lastMonitor - > m_activeSpecialWorkspace & &
! ( g_pCompositor - > m_lastWindow . lock ( ) & & g_pCompositor - > m_lastWindow - > m_pinned & & g_pCompositor - > m_lastWindow - > m_monitor = = m_self ) ) {
2024-03-03 18:39:20 +00:00
static auto PFOLLOWMOUSE = CConfigValue < Hyprlang : : INT > ( " input:follow_mouse " ) ;
2025-04-25 02:37:12 +02:00
auto pWindow = pWorkspace - > m_hasFullscreenWindow ? pWorkspace - > getFullscreenWindow ( ) : pWorkspace - > getLastFocusedWindow ( ) ;
2023-08-21 18:54:02 +00:00
2023-12-07 22:12:08 +00:00
if ( ! pWindow ) {
2024-03-03 18:39:20 +00:00
if ( * PFOLLOWMOUSE = = 1 )
2024-02-04 15:40:20 +00:00
pWindow = g_pCompositor - > vectorToWindowUnified ( g_pInputManager - > getMouseCoordsInternal ( ) , RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING ) ;
2023-08-21 18:54:02 +00:00
2023-12-07 22:12:08 +00:00
if ( ! pWindow )
2024-11-22 16:01:02 +00:00
pWindow = pWorkspace - > getTopLeftWindow ( ) ;
2023-08-21 18:54:02 +00:00
2023-12-07 22:12:08 +00:00
if ( ! pWindow )
2024-11-22 16:01:02 +00:00
pWindow = pWorkspace - > getFirstWindow ( ) ;
2023-12-07 22:12:08 +00:00
}
2023-08-21 18:54:02 +00:00
g_pCompositor - > focusWindow ( pWindow ) ;
2023-04-14 15:03:53 +01:00
}
2023-12-07 22:12:08 +00:00
2023-09-08 17:17:04 +00:00
if ( ! noMouseMove )
g_pInputManager - > simulateMouseMovement ( ) ;
2023-08-21 18:54:02 +00:00
2025-04-30 23:45:20 +02:00
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( m_id ) ;
2023-04-16 01:11:57 +01:00
2025-04-25 02:37:12 +02:00
g_pEventManager - > postEvent ( SHyprIPCEvent { " workspace " , pWorkspace - > m_name } ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " workspacev2 " , std : : format ( " {},{} " , pWorkspace - > m_id , pWorkspace - > m_name ) } ) ;
2023-04-14 15:03:53 +01:00
EMIT_HOOK_EVENT ( " workspace " , pWorkspace ) ;
}
2023-04-15 16:16:33 +01:00
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ;
2023-04-30 01:12:20 +01:00
g_pCompositor - > updateFullscreenFadeOnWorkspace ( pWorkspace ) ;
2023-10-12 17:26:31 +01:00
2025-04-30 23:45:20 +02:00
g_pConfigManager - > ensureVRR ( m_self . lock ( ) ) ;
2023-12-23 22:30:49 +01:00
g_pCompositor - > updateSuspendedStates ( ) ;
2024-03-07 05:27:58 -08:00
2025-04-30 23:45:20 +02:00
if ( m_activeSpecialWorkspace )
g_pCompositor - > updateFullscreenFadeOnWorkspace ( m_activeSpecialWorkspace ) ;
2023-04-14 15:03:53 +01:00
}
2024-08-08 21:01:50 +02:00
void CMonitor : : changeWorkspace ( const WORKSPACEID & id , bool internal , bool noMouseMove , bool noFocus ) {
2024-02-10 00:47:00 +01:00
changeWorkspace ( g_pCompositor - > getWorkspaceByID ( id ) , internal , noMouseMove , noFocus ) ;
2023-04-14 15:03:53 +01:00
}
2024-04-02 20:32:39 +01:00
void CMonitor : : setSpecialWorkspace ( const PHLWORKSPACE & pWorkspace ) {
2025-04-30 23:45:20 +02:00
if ( m_activeSpecialWorkspace = = pWorkspace )
2024-07-07 21:27:18 +02:00
return ;
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ;
2023-04-15 16:16:33 +01:00
2023-04-14 15:03:53 +01:00
if ( ! pWorkspace ) {
// remove special if exists
2025-04-30 23:45:20 +02:00
if ( m_activeSpecialWorkspace ) {
m_activeSpecialWorkspace - > m_visible = false ;
m_activeSpecialWorkspace - > startAnim ( false , false ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecial " , " , " + m_name } ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecialv2 " , " ,, " + m_name } ) ;
2023-09-04 13:11:51 +00:00
}
2025-04-30 23:45:20 +02:00
m_activeSpecialWorkspace . reset ( ) ;
2023-04-14 15:03:53 +01:00
2025-04-30 23:45:20 +02:00
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( m_id ) ;
2023-04-14 15:03:53 +01:00
2025-04-30 23:45:20 +02:00
if ( ! ( g_pCompositor - > m_lastWindow . lock ( ) & & g_pCompositor - > m_lastWindow - > m_pinned & & g_pCompositor - > m_lastWindow - > m_monitor = = m_self ) ) {
if ( const auto PLAST = m_activeWorkspace - > getLastFocusedWindow ( ) ; PLAST )
2024-04-07 22:13:56 +00:00
g_pCompositor - > focusWindow ( PLAST ) ;
else
g_pInputManager - > refocus ( ) ;
}
2023-04-14 15:03:53 +01:00
2025-04-30 23:45:20 +02:00
g_pCompositor - > updateFullscreenFadeOnWorkspace ( m_activeWorkspace ) ;
2024-03-04 15:29:45 -08:00
2025-04-30 23:45:20 +02:00
g_pConfigManager - > ensureVRR ( m_self . lock ( ) ) ;
2024-03-04 15:29:45 -08:00
2023-12-23 22:30:49 +01:00
g_pCompositor - > updateSuspendedStates ( ) ;
2023-04-14 15:03:53 +01:00
return ;
}
2025-04-30 23:45:20 +02:00
if ( m_activeSpecialWorkspace ) {
m_activeSpecialWorkspace - > m_visible = false ;
m_activeSpecialWorkspace - > startAnim ( false , false ) ;
2024-04-03 10:09:42 +01:00
}
2023-05-06 16:10:51 +01:00
2023-09-04 13:11:51 +00:00
bool animate = true ;
//close if open elsewhere
2025-04-25 02:37:12 +02:00
const auto PMONITORWORKSPACEOWNER = pWorkspace - > m_monitor . lock ( ) ;
2025-04-30 23:45:20 +02:00
if ( const auto PMWSOWNER = pWorkspace - > m_monitor . lock ( ) ; PMWSOWNER & & PMWSOWNER - > m_activeSpecialWorkspace = = pWorkspace ) {
PMWSOWNER - > m_activeSpecialWorkspace . reset ( ) ;
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( PMWSOWNER - > m_id ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecial " , " , " + PMWSOWNER - > m_name } ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecialv2 " , " ,, " + PMWSOWNER - > m_name } ) ;
2024-03-07 05:27:58 -08:00
2025-04-30 23:45:20 +02:00
const auto PACTIVEWORKSPACE = PMWSOWNER - > m_activeWorkspace ;
2024-03-07 05:27:58 -08:00
g_pCompositor - > updateFullscreenFadeOnWorkspace ( PACTIVEWORKSPACE ) ;
2023-09-04 13:11:51 +00:00
animate = false ;
}
2023-04-14 15:03:53 +01:00
// open special
2025-04-30 23:45:20 +02:00
pWorkspace - > m_monitor = m_self ;
m_activeSpecialWorkspace = pWorkspace ;
m_activeSpecialWorkspace - > m_visible = true ;
2023-09-04 13:11:51 +00:00
if ( animate )
pWorkspace - > startAnim ( true , true ) ;
2023-04-14 15:03:53 +01:00
2025-04-22 15:23:29 +02:00
for ( auto const & w : g_pCompositor - > m_windows ) {
2025-04-28 22:25:22 +02:00
if ( w - > m_workspace = = pWorkspace ) {
2025-04-30 23:45:20 +02:00
w - > m_monitor = m_self ;
2024-01-09 18:14:08 +01:00
w - > updateSurfaceScaleTransformDetails ( ) ;
2024-03-23 15:14:50 -07:00
w - > setAnimationsToMove ( ) ;
2023-12-26 19:44:38 +01:00
const auto MIDDLE = w - > middle ( ) ;
2025-05-03 21:54:15 +05:00
if ( w - > m_isFloating & & VECNOTINRECT ( MIDDLE , m_position . x , m_position . y , m_position . x + m_size . x , m_position . y + m_size . y ) & & ! w - > isX11OverrideRedirect ( ) ) {
2023-12-26 19:44:38 +01:00
// if it's floating and the middle isnt on the current mon, move it to the center
const auto PMONFROMMIDDLE = g_pCompositor - > getMonitorFromVector ( MIDDLE ) ;
2025-04-28 22:25:22 +02:00
Vector2D pos = w - > m_realPosition - > goal ( ) ;
2025-05-03 21:54:15 +05:00
if ( VECNOTINRECT ( MIDDLE , PMONFROMMIDDLE - > m_position . x , PMONFROMMIDDLE - > m_position . y , PMONFROMMIDDLE - > m_position . x + PMONFROMMIDDLE - > m_size . x ,
PMONFROMMIDDLE - > m_position . y + PMONFROMMIDDLE - > m_size . y ) ) {
2023-12-26 19:44:38 +01:00
// not on any monitor, center
2025-04-28 22:25:22 +02:00
pos = middle ( ) / 2.f - w - > m_realSize - > goal ( ) / 2.f ;
2023-12-26 19:44:38 +01:00
} else
2025-04-30 23:45:20 +02:00
pos = pos - PMONFROMMIDDLE - > m_position + m_position ;
2023-12-26 19:44:38 +01:00
2025-04-28 22:25:22 +02:00
* w - > m_realPosition = pos ;
w - > m_position = pos ;
2023-12-26 19:44:38 +01:00
}
2023-06-02 20:14:34 +02:00
}
}
2025-04-30 23:45:20 +02:00
g_pLayoutManager - > getCurrentLayout ( ) - > recalculateMonitor ( m_id ) ;
2023-04-14 15:03:53 +01:00
2025-04-30 23:45:20 +02:00
if ( ! ( g_pCompositor - > m_lastWindow . lock ( ) & & g_pCompositor - > m_lastWindow - > m_pinned & & g_pCompositor - > m_lastWindow - > m_monitor = = m_self ) ) {
2024-04-07 22:13:56 +00:00
if ( const auto PLAST = pWorkspace - > getLastFocusedWindow ( ) ; PLAST )
g_pCompositor - > focusWindow ( PLAST ) ;
else
g_pInputManager - > refocus ( ) ;
}
2023-09-04 13:11:51 +00:00
2025-04-30 23:45:20 +02:00
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecial " , pWorkspace - > m_name + " , " + m_name } ) ;
g_pEventManager - > postEvent ( SHyprIPCEvent { " activespecialv2 " , std : : to_string ( pWorkspace - > m_id ) + " , " + pWorkspace - > m_name + " , " + m_name } ) ;
2023-09-22 23:36:28 +01:00
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ;
2023-12-23 22:30:49 +01:00
2024-03-04 15:29:45 -08:00
g_pCompositor - > updateFullscreenFadeOnWorkspace ( pWorkspace ) ;
2025-04-30 23:45:20 +02:00
g_pConfigManager - > ensureVRR ( m_self . lock ( ) ) ;
2024-03-04 15:29:45 -08:00
2023-12-23 22:30:49 +01:00
g_pCompositor - > updateSuspendedStates ( ) ;
2023-04-14 15:03:53 +01:00
}
2024-08-08 21:01:50 +02:00
void CMonitor : : setSpecialWorkspace ( const WORKSPACEID & id ) {
2023-04-14 15:03:53 +01:00
setSpecialWorkspace ( g_pCompositor - > getWorkspaceByID ( id ) ) ;
}
2023-08-14 14:22:06 +02:00
void CMonitor : : moveTo ( const Vector2D & pos ) {
2025-04-30 23:45:20 +02:00
m_position = pos ;
2023-08-14 14:22:06 +02:00
}
2023-09-11 09:09:34 +00:00
2025-01-11 19:05:53 +03:00
SWorkspaceIDName CMonitor : : getPrevWorkspaceIDName ( const WORKSPACEID id ) {
2025-04-30 23:45:20 +02:00
while ( ! m_prevWorkSpaces . empty ( ) ) {
const int PREVID = m_prevWorkSpaces . top ( ) ;
m_prevWorkSpaces . pop ( ) ;
2025-01-11 19:05:53 +03:00
if ( PREVID = = id ) // skip same workspace
continue ;
// recheck if previous workspace's was moved to another monitor
const auto ws = g_pCompositor - > getWorkspaceByID ( PREVID ) ;
2025-04-30 23:45:20 +02:00
if ( ws & & ws - > monitorID ( ) = = m_id )
2025-04-25 02:37:12 +02:00
return { . id = PREVID , . name = ws - > m_name } ;
2025-01-11 19:05:53 +03:00
}
return { . id = WORKSPACE_INVALID } ;
}
void CMonitor : : addPrevWorkspaceID ( const WORKSPACEID id ) {
2025-04-30 23:45:20 +02:00
if ( ! m_prevWorkSpaces . empty ( ) & & m_prevWorkSpaces . top ( ) = = id )
2025-01-11 19:05:53 +03:00
return ;
2025-04-30 23:45:20 +02:00
m_prevWorkSpaces . emplace ( id ) ;
2025-01-11 19:05:53 +03:00
}
2023-09-11 09:09:34 +00:00
Vector2D CMonitor : : middle ( ) {
2025-04-30 23:45:20 +02:00
return m_position + m_size / 2.f ;
2023-11-12 14:14:05 +01:00
}
2023-11-24 10:54:21 +00:00
void CMonitor : : updateMatrix ( ) {
2025-04-30 23:45:20 +02:00
m_projMatrix = Mat3x3 : : identity ( ) ;
if ( m_transform ! = WL_OUTPUT_TRANSFORM_NORMAL )
m_projMatrix . translate ( m_pixelSize / 2.0 ) . transform ( wlTransformToHyprutils ( m_transform ) ) . translate ( - m_transformedSize / 2.0 ) ;
2023-11-30 02:48:10 +01:00
}
2024-01-27 19:11:03 +00:00
2024-08-08 21:01:50 +02:00
WORKSPACEID CMonitor : : activeWorkspaceID ( ) {
2025-04-30 23:45:20 +02:00
return m_activeWorkspace ? m_activeWorkspace - > m_id : 0 ;
2024-04-02 20:32:39 +01:00
}
2024-05-05 22:18:10 +01:00
2024-08-08 21:01:50 +02:00
WORKSPACEID CMonitor : : activeSpecialWorkspaceID ( ) {
2025-04-30 23:45:20 +02:00
return m_activeSpecialWorkspace ? m_activeSpecialWorkspace - > m_id : 0 ;
2024-04-02 20:32:39 +01:00
}
2024-05-05 22:18:10 +01:00
CBox CMonitor : : logicalBox ( ) {
2025-04-30 23:45:20 +02:00
return { m_position , m_size } ;
2024-05-05 22:18:10 +01:00
}
2024-10-05 00:44:30 +01:00
void CMonitor : : scheduleDone ( ) {
2025-04-30 23:45:20 +02:00
if ( m_doneScheduled )
2024-07-21 13:09:54 +02:00
return ;
2025-04-30 23:45:20 +02:00
m_doneScheduled = true ;
2024-07-21 13:09:54 +02:00
2025-04-30 23:45:20 +02:00
g_pEventLoopManager - > doLater ( [ M = m_self ] {
2024-10-05 00:44:30 +01:00
if ( ! M ) // if M is gone, we got destroyed, doesn't matter.
return ;
2025-04-30 23:45:20 +02:00
if ( ! PROTO : : outputs . contains ( M - > m_name ) )
2024-10-05 00:44:30 +01:00
return ;
2024-07-21 13:09:54 +02:00
2025-04-30 23:45:20 +02:00
PROTO : : outputs . at ( M - > m_name ) - > sendDone ( ) ;
M - > m_doneScheduled = false ;
2024-10-05 00:44:30 +01:00
} ) ;
2024-07-21 13:09:54 +02:00
}
2024-10-08 16:59:15 +01:00
void CMonitor : : setCTM ( const Mat3x3 & ctm_ ) {
2025-04-30 23:45:20 +02:00
m_ctm = ctm_ ;
m_ctmUpdated = true ;
g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : scheduleFrameReason : : AQ_SCHEDULE_NEEDS_FRAME ) ;
2024-10-08 16:59:15 +01:00
}
2024-07-21 13:09:54 +02:00
bool CMonitor : : attemptDirectScanout ( ) {
2025-05-05 23:44:49 +02:00
if ( ! m_mirrors . empty ( ) | | isMirror ( ) | | g_pHyprRenderer - > m_directScanoutBlocked )
2024-07-21 13:09:54 +02:00
return false ; // do not DS if this monitor is being mirrored. Will break the functionality.
2025-04-30 23:45:20 +02:00
if ( g_pPointerManager - > softwareLockedFor ( m_self . lock ( ) ) )
2024-07-21 13:09:54 +02:00
return false ;
2025-04-30 23:45:20 +02:00
const auto PCANDIDATE = m_solitaryClient . lock ( ) ;
2024-07-21 13:09:54 +02:00
if ( ! PCANDIDATE )
return false ;
const auto PSURFACE = g_pXWaylandManager - > getWindowSurface ( PCANDIDATE ) ;
2025-05-03 16:02:49 +02:00
if ( ! PSURFACE | | ! PSURFACE - > m_current . texture | | ! PSURFACE - > m_current . buffer )
2025-03-08 13:24:22 -06:00
return false ;
2025-05-03 16:02:49 +02:00
if ( PSURFACE - > m_current . bufferSize ! = m_pixelSize | | PSURFACE - > m_current . transform ! = m_transform )
2024-07-21 13:09:54 +02:00
return false ;
// we can't scanout shm buffers.
2025-05-03 16:02:49 +02:00
const auto params = PSURFACE - > m_current . buffer - > dmabuf ( ) ;
2025-05-05 23:44:49 +02:00
if ( ! params . success | | ! PSURFACE - > m_current . texture - > m_eglImage /* dmabuf */ )
2024-07-21 13:09:54 +02:00
return false ;
2025-05-03 18:54:50 +02:00
Debug : : log ( TRACE , " attemptDirectScanout: surface {:x} passed, will attempt, buffer {} " , ( uintptr_t ) PSURFACE . get ( ) , ( uintptr_t ) PSURFACE - > m_current . buffer . m_buffer . get ( ) ) ;
2025-03-08 13:24:22 -06:00
2025-05-03 18:54:50 +02:00
auto PBUFFER = PSURFACE - > m_current . buffer . m_buffer ;
2025-03-17 16:06:41 -05:00
2025-04-30 23:45:20 +02:00
if ( PBUFFER = = m_output - > state - > state ( ) . buffer ) {
PSURFACE - > presentFeedback ( Time : : steadyNow ( ) , m_self . lock ( ) ) ;
2025-04-15 18:02:31 -05:00
2025-04-30 23:45:20 +02:00
if ( m_scanoutNeedsCursorUpdate ) {
if ( ! m_state . test ( ) ) {
2025-03-17 16:06:41 -05:00
Debug : : log ( TRACE , " attemptDirectScanout: failed basic test " ) ;
return false ;
}
2025-04-30 23:45:20 +02:00
if ( ! m_output - > commit ( ) ) {
2025-03-17 16:06:41 -05:00
Debug : : log ( TRACE , " attemptDirectScanout: failed to commit cursor update " ) ;
2025-04-30 23:45:20 +02:00
m_lastScanout . reset ( ) ;
2025-03-17 16:06:41 -05:00
return false ;
}
2025-04-30 23:45:20 +02:00
m_scanoutNeedsCursorUpdate = false ;
2025-03-17 16:06:41 -05:00
}
return true ;
}
2024-08-06 14:52:19 +01:00
2024-07-21 13:09:54 +02:00
// FIXME: make sure the buffer actually follows the available scanout dmabuf formats
// and comes from the appropriate device. This may implode on multi-gpu!!
2024-08-31 08:07:52 -05:00
// entering into scanout, so save monitor format
2025-04-30 23:45:20 +02:00
if ( m_lastScanout . expired ( ) )
m_prevDrmFormat = m_drmFormat ;
2024-08-31 08:07:52 -05:00
2025-04-30 23:45:20 +02:00
if ( m_drmFormat ! = params . format ) {
m_output - > state - > setFormat ( params . format ) ;
m_drmFormat = params . format ;
2024-08-31 08:07:52 -05:00
}
2025-04-30 23:45:20 +02:00
m_output - > state - > setBuffer ( PBUFFER ) ;
m_output - > state - > setPresentationMode ( m_tearingState . activelyTearing ? Aquamarine : : eOutputPresentationMode : : AQ_OUTPUT_PRESENTATION_IMMEDIATE :
Aquamarine : : eOutputPresentationMode : : AQ_OUTPUT_PRESENTATION_VSYNC ) ;
2024-07-21 13:09:54 +02:00
2025-04-30 23:45:20 +02:00
if ( ! m_state . test ( ) ) {
2024-08-06 14:52:19 +01:00
Debug : : log ( TRACE , " attemptDirectScanout: failed basic test " ) ;
2024-07-21 13:09:54 +02:00
return false ;
2024-08-06 14:52:19 +01:00
}
2025-04-30 23:45:20 +02:00
PSURFACE - > presentFeedback ( Time : : steadyNow ( ) , m_self . lock ( ) ) ;
2024-07-21 13:09:54 +02:00
2025-05-03 16:02:49 +02:00
m_output - > state - > addDamage ( PSURFACE - > m_current . accumulateBufferDamage ( ) ) ;
2025-04-30 23:45:20 +02:00
m_output - > state - > resetExplicitFences ( ) ;
2024-08-06 14:52:19 +01:00
2025-04-15 18:02:31 -05:00
// no need to do explicit sync here as surface current can only ever be ready to read
2024-08-06 14:52:19 +01:00
2025-04-30 23:45:20 +02:00
bool ok = m_output - > commit ( ) ;
2024-08-06 14:52:19 +01:00
2025-03-08 13:24:22 -06:00
if ( ! ok ) {
2024-08-06 14:52:19 +01:00
Debug : : log ( TRACE , " attemptDirectScanout: failed to scanout surface " ) ;
2025-04-30 23:45:20 +02:00
m_lastScanout . reset ( ) ;
2024-07-21 13:09:54 +02:00
return false ;
}
2025-04-30 23:45:20 +02:00
if ( m_lastScanout . expired ( ) ) {
m_lastScanout = PCANDIDATE ;
2025-04-28 22:25:22 +02:00
Debug : : log ( LOG , " Entered a direct scanout to {:x}: \" {} \" " , ( uintptr_t ) PCANDIDATE . get ( ) , PCANDIDATE - > m_title ) ;
2025-03-08 13:24:22 -06:00
}
2025-04-30 23:45:20 +02:00
m_scanoutNeedsCursorUpdate = false ;
2025-03-17 16:06:41 -05:00
2025-05-03 18:54:50 +02:00
if ( ! PBUFFER - > lockedByBackend | | PBUFFER - > m_hlEvents . backendRelease )
2025-03-08 13:24:22 -06:00
return true ;
// lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then
// btw buffer's syncReleaser will take care of signaling release point, so we don't do that here
PBUFFER - > lock ( ) ;
PBUFFER - > onBackendRelease ( [ PBUFFER ] ( ) { PBUFFER - > unlock ( ) ; } ) ;
2024-07-21 13:09:54 +02:00
return true ;
}
2024-10-12 03:29:51 +03:00
void CMonitor : : debugLastPresentation ( const std : : string & message ) {
2025-04-30 23:45:20 +02:00
Debug : : log ( TRACE , " {} (last presentation {} - {} fps) " , message , m_lastPresentationTimer . getMillis ( ) ,
m_lastPresentationTimer . getMillis ( ) > 0 ? 1000.0f / m_lastPresentationTimer . getMillis ( ) : 0.0f ) ;
2024-10-12 03:29:51 +03:00
}
2024-10-19 16:21:47 +01:00
void CMonitor : : onMonitorFrame ( ) {
2025-04-22 15:23:29 +02:00
if ( ( g_pCompositor - > m_aqBackend - > hasSession ( ) & & ! g_pCompositor - > m_aqBackend - > session - > active ) | | ! g_pCompositor - > m_sessionActive | | g_pCompositor - > m_unsafeState ) {
2024-10-19 16:21:47 +01:00
Debug : : log ( WARN , " Attempted to render frame on inactive session! " ) ;
2025-04-30 23:45:20 +02:00
if ( g_pCompositor - > m_unsafeState & & std : : ranges : : any_of ( g_pCompositor - > m_monitors . begin ( ) , g_pCompositor - > m_monitors . end ( ) , [ & ] ( auto & m ) {
return m - > m_output ! = g_pCompositor - > m_unsafeOutput - > m_output ;
} ) ) {
2024-10-19 16:21:47 +01:00
// restore from unsafe state
g_pCompositor - > leaveUnsafeState ( ) ;
}
return ; // cannot draw on session inactive (different tty)
}
2025-04-30 23:45:20 +02:00
if ( ! m_enabled )
2024-10-19 16:21:47 +01:00
return ;
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > recheckSolitaryForMonitor ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
m_tearingState . busy = false ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
if ( m_tearingState . activelyTearing & & m_solitaryClient . lock ( ) /* can be invalidated by a recheck */ ) {
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
if ( ! m_tearingState . frameScheduledWhileBusy )
2024-10-19 16:21:47 +01:00
return ; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
2025-04-30 23:45:20 +02:00
m_tearingState . nextRenderTorn = true ;
m_tearingState . frameScheduledWhileBusy = false ;
2024-10-19 16:21:47 +01:00
}
static auto PENABLERAT = CConfigValue < Hyprlang : : INT > ( " misc:render_ahead_of_time " ) ;
static auto PRATSAFE = CConfigValue < Hyprlang : : INT > ( " misc:render_ahead_safezone " ) ;
2025-04-30 23:45:20 +02:00
m_lastPresentationTimer . reset ( ) ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
if ( * PENABLERAT & & ! m_tearingState . nextRenderTorn ) {
if ( ! m_ratsScheduled ) {
2024-10-19 16:21:47 +01:00
// render
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > renderMonitor ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
}
2025-04-30 23:45:20 +02:00
m_ratsScheduled = false ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
const auto & [ avg , max , min ] = g_pHyprRenderer - > getRenderTimes ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
if ( max + * PRATSAFE > 1000.0 / m_refreshRate )
2024-10-19 16:21:47 +01:00
return ;
2025-04-30 23:45:20 +02:00
const auto MSLEFT = ( 1000.0 / m_refreshRate ) - m_lastPresentationTimer . getMillis ( ) ;
2024-10-19 16:21:47 +01:00
2025-04-30 23:45:20 +02:00
m_ratsScheduled = true ;
2024-10-19 16:21:47 +01:00
const auto ESTRENDERTIME = std : : ceil ( avg + * PRATSAFE ) ;
const auto TIMETOSLEEP = std : : floor ( MSLEFT - ESTRENDERTIME ) ;
if ( MSLEFT < 1 | | MSLEFT < ESTRENDERTIME | | TIMETOSLEEP < 1 )
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > renderMonitor ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
else
2025-04-30 23:45:20 +02:00
wl_event_source_timer_update ( m_renderTimer , TIMETOSLEEP ) ;
2024-10-19 16:21:47 +01:00
} else
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > renderMonitor ( m_self . lock ( ) ) ;
2024-10-19 16:21:47 +01:00
}
2025-02-15 00:18:43 +00:00
void CMonitor : : onCursorMovedOnMonitor ( ) {
2025-04-30 23:45:20 +02:00
if ( ! m_tearingState . activelyTearing | | ! m_solitaryClient | | ! g_pHyprRenderer - > shouldRenderCursor ( ) )
2025-02-15 00:18:43 +00:00
return ;
// submit a frame immediately. This will only update the cursor pos.
// output->state->setBuffer(output->state->state().buffer);
// output->state->addDamage(CRegion{});
// output->state->setPresentationMode(Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE);
// if (!output->commit())
// Debug::log(ERR, "onCursorMovedOnMonitor: tearing and wanted to update cursor, failed.");
// FIXME: try to do the above. We currently can't just render because drm is a fucking bitch
// and throws a "nO pRoP cAn Be ChAnGeD dUrInG AsYnC fLiP" on crtc_x
// this will throw too but fix it if we use sw cursors
2025-04-30 23:45:20 +02:00
m_tearingState . frameScheduledWhileBusy = true ;
2025-02-15 00:18:43 +00:00
}
2025-04-30 23:45:20 +02:00
CMonitorState : : CMonitorState ( CMonitor * owner ) : m_owner ( owner ) {
2024-12-07 18:51:18 +01:00
;
2024-01-28 01:57:13 +00:00
}
2024-07-21 13:09:54 +02:00
void CMonitorState : : ensureBufferPresent ( ) {
2025-04-30 23:45:20 +02:00
const auto STATE = m_owner - > m_output - > state - > state ( ) ;
2024-08-28 08:07:13 -05:00
if ( ! STATE . enabled ) {
2024-07-21 13:09:54 +02:00
Debug : : log ( TRACE , " CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled " ) ;
return ;
}
2024-08-28 08:07:13 -05:00
if ( STATE . buffer ) {
2025-04-30 23:45:20 +02:00
if ( const auto params = STATE . buffer - > dmabuf ( ) ; params . success & & params . format = = m_owner - > m_drmFormat )
2024-08-28 08:07:13 -05:00
return ;
}
2024-01-28 01:57:13 +00:00
2024-07-21 13:09:54 +02:00
// this is required for modesetting being possible and might be missing in case of first tests in the renderer
// where we test modes and buffers
2024-08-28 08:07:13 -05:00
Debug : : log ( LOG , " CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible " ) ;
2025-04-30 23:45:20 +02:00
m_owner - > m_output - > state - > setBuffer ( m_owner - > m_output - > swapchain - > next ( nullptr ) ) ;
m_owner - > m_output - > swapchain - > rollback ( ) ; // restore the counter, don't advance the swapchain
2024-01-28 01:57:13 +00:00
}
bool CMonitorState : : commit ( ) {
2024-07-21 13:09:54 +02:00
if ( ! updateSwapchain ( ) )
return false ;
2025-04-30 23:45:20 +02:00
EMIT_HOOK_EVENT ( " preMonitorCommit " , m_owner - > m_self . lock ( ) ) ;
2024-07-30 15:32:38 +02:00
2024-07-21 13:09:54 +02:00
ensureBufferPresent ( ) ;
2025-04-30 23:45:20 +02:00
bool ret = m_owner - > m_output - > commit ( ) ;
2024-01-28 01:57:13 +00:00
return ret ;
}
2024-01-28 00:41:54 +00:00
2024-01-28 01:57:13 +00:00
bool CMonitorState : : test ( ) {
2024-07-21 13:09:54 +02:00
if ( ! updateSwapchain ( ) )
return false ;
ensureBufferPresent ( ) ;
2025-04-30 23:45:20 +02:00
return m_owner - > m_output - > test ( ) ;
2024-07-21 13:09:54 +02:00
}
bool CMonitorState : : updateSwapchain ( ) {
2025-04-30 23:45:20 +02:00
auto options = m_owner - > m_output - > swapchain - > currentOptions ( ) ;
const auto & STATE = m_owner - > m_output - > state - > state ( ) ;
2024-07-21 13:09:54 +02:00
const auto & MODE = STATE . mode ? STATE . mode : STATE . customMode ;
if ( ! MODE ) {
Debug : : log ( WARN , " updateSwapchain: No mode? " ) ;
return true ;
}
2025-04-30 23:45:20 +02:00
options . format = m_owner - > m_drmFormat ;
2024-07-21 13:09:54 +02:00
options . scanout = true ;
options . length = 2 ;
options . size = MODE - > pixelSize ;
2025-04-30 23:45:20 +02:00
return m_owner - > m_output - > swapchain - > reconfigure ( options ) ;
2024-01-27 19:11:03 +00:00
}