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"
2025-09-20 17:42:02 +02:00
# include "SharedDefs.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-08-22 20:24:25 +03:00
# include "../protocols/core/DataDevice.hpp"
2025-01-17 15:21:35 +00:00
# include "../render/Renderer.hpp"
# include "../managers/EventManager.hpp"
# include "../managers/LayoutManager.hpp"
2025-08-28 11:20:29 +02:00
# include "../managers/animation/AnimationManager.hpp"
# include "../managers/animation/DesktopAnimationManager.hpp"
2025-01-17 15:21:35 +00:00
# include "../managers/input/InputManager.hpp"
2025-09-17 14:03:49 +02:00
# include "../hyprerror/HyprError.hpp"
2025-11-16 14:51:14 +00:00
# include "../i18n/Engine.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"
2025-07-08 12:41:10 +02:00
# include "MonitorFrameScheduler.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>
2025-07-31 01:12:05 +02:00
# include <climits>
2024-12-28 07:32:04 -06:00
# 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 ;
2025-08-22 13:13:55 +03:00
using namespace NColorManagement ;
2024-03-03 18:39:20 +00:00
2025-04-30 23:45:20 +02:00
CMonitor : : CMonitor ( SP < Aquamarine : : IOutput > output_ ) : m_state ( this ) , m_output ( output_ ) {
2025-06-11 17:52:16 +02:00
g_pAnimationManager - > createAnimation ( 0.f , m_specialFade , g_pConfigManager - > getAnimationPropertyConfig ( " specialWorkspaceIn " ) , AVARDAMAGE_NONE ) ;
m_specialFade - > setUpdateCallback ( [ this ] ( auto ) { g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ; } ) ;
2025-07-01 11:33:48 +02:00
static auto PZOOMFACTOR = CConfigValue < Hyprlang : : FLOAT > ( " cursor:zoom_factor " ) ;
g_pAnimationManager - > createAnimation ( * PZOOMFACTOR , m_cursorZoom , g_pConfigManager - > getAnimationPropertyConfig ( " zoomFactor " ) , AVARDAMAGE_NONE ) ;
m_cursorZoom - > setUpdateCallback ( [ this ] ( auto ) { g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ; } ) ;
2025-08-16 20:02:15 +01:00
g_pAnimationManager - > createAnimation ( 0.F , m_zoomAnimProgress , g_pConfigManager - > getAnimationPropertyConfig ( " monitorAdded " ) , AVARDAMAGE_NONE ) ;
m_zoomAnimProgress - > setUpdateCallback ( [ this ] ( auto ) { g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ; } ) ;
2025-09-18 22:10:30 +02:00
g_pAnimationManager - > createAnimation ( 0.F , m_backgroundOpacity , g_pConfigManager - > getAnimationPropertyConfig ( " monitorAdded " ) , AVARDAMAGE_NONE ) ;
m_backgroundOpacity - > setUpdateCallback ( [ this ] ( auto ) { g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ; } ) ;
2025-08-17 08:37:13 +01:00
g_pAnimationManager - > createAnimation ( 0.F , m_dpmsBlackOpacity , g_pConfigManager - > getAnimationPropertyConfig ( " fadeDpms " ) , AVARDAMAGE_NONE ) ;
m_dpmsBlackOpacity - > setUpdateCallback ( [ this ] ( auto ) { g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ; } ) ;
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-08-16 20:02:15 +01:00
m_zoomAnimProgress - > setValueAndWarp ( 0.F ) ;
2025-08-17 17:14:29 +01:00
m_zoomAnimFrameCounter = 0 ;
2025-08-16 20:02:15 +01:00
2025-01-26 14:40:42 +00:00
g_pEventLoopManager - > doLater ( [ ] { g_pConfigManager - > ensurePersistentWorkspacesPresent ( ) ; } ) ;
2025-08-16 16:52:28 +02:00
m_listeners . frame = m_output - > events . frame . listen ( [ this ] {
if ( m_frameScheduler )
m_frameScheduler - > onFrame ( ) ;
} ) ;
2025-07-08 09:56:40 -07:00
m_listeners . commit = m_output - > events . commit . listen ( [ this ] {
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-07-08 09:56:40 -07:00
m_listeners . needsFrame = m_output - > events . needsFrame . listen ( [ this ] { g_pCompositor - > scheduleFrameForMonitor ( m_self . lock ( ) , Aquamarine : : IOutput : : AQ_SCHEDULE_NEEDS_FRAME ) ; } ) ;
2024-08-19 18:44:22 +02:00
2025-07-08 09:56:40 -07:00
m_listeners . presented = m_output - > events . present . listen ( [ this ] ( const Aquamarine : : IOutput : : SPresentEvent & event ) {
2025-08-17 08:37:13 +01:00
if ( m_pendingDpmsAnimation ) {
2025-08-17 17:14:29 +01:00
m_pendingDpmsAnimationCounter + + ;
// we give ourselves 5 frames of a buffer. The first presentation event still doesn't usually say that we actually
// are scanning out to the CRTC, and it could still be modesetting.
// this is not ideal (some CRTCs will just eat frames) but it's better than nothing
2025-08-17 08:37:13 +01:00
m_dpmsBlackOpacity - > setValueAndWarp ( 1.F ) ;
2025-08-17 17:14:29 +01:00
if ( m_pendingDpmsAnimationCounter = = 5 ) {
* m_dpmsBlackOpacity = 0.F ;
m_pendingDpmsAnimation = false ;
}
2025-08-17 08:37:13 +01:00
}
2025-07-08 09:56:40 -07:00
timespec * ts = event . 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 )
2025-07-08 09:56:40 -07:00
PROTO : : presentation - > onPresented ( m_self . lock ( ) , Time : : steadyNow ( ) , event . refresh , event . seq , event . flags ) ;
2025-05-27 21:26:47 +02:00
else
2025-07-08 09:56:40 -07:00
PROTO : : presentation - > onPresented ( m_self . lock ( ) , Time : : fromTimespec ( event . when ) , event . refresh , event . seq , event . flags ) ;
2025-07-08 12:41:10 +02:00
2025-08-17 17:14:29 +01:00
if ( m_zoomAnimFrameCounter < 5 ) {
m_zoomAnimFrameCounter + + ;
// we give ourselves 5 frames of a buffer. The first presentation event still doesn't usually say that we actually
// are scanning out to the CRTC, and it could still be modesetting.
// this is not ideal (some CRTCs will just eat frames) but it's better than nothing
m_zoomAnimProgress - > setValueAndWarp ( 0.F ) ;
if ( m_zoomAnimFrameCounter = = 5 ) {
// start the animation for realzies
* m_zoomAnimProgress = 1.F ;
}
// damage the entire display to force a frame immediately
g_pEventLoopManager - > doLater ( [ self = m_self ] {
if ( ! self )
return ;
g_pHyprRenderer - > damageMonitor ( self . lock ( ) ) ;
} ) ;
}
2025-08-16 20:02:15 +01:00
2025-07-08 12:41:10 +02:00
m_frameScheduler - > onPresented ( ) ;
2025-11-06 14:25:49 +01:00
m_events . presented . emit ( ) ;
2024-07-21 13:09:54 +02:00
} ) ;
2025-07-08 09:56:40 -07:00
m_listeners . destroy = m_output - > events . destroy . listen ( [ this ] {
2025-04-30 23:45:20 +02:00
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-07-08 09:56:40 -07:00
m_listeners . state = m_output - > events . state . listen ( [ this ] ( const Aquamarine : : IOutput : : SStateEvent & event ) {
if ( event . size = = Vector2D { } ) {
2024-07-21 13:09:54 +02:00
// 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 ;
2025-07-08 09:56:40 -07:00
const auto SIZE = event . size ;
2024-07-21 13:09:54 +02:00
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 ;
2025-11-17 18:34:02 +00:00
if ( SIZE = = rule . resolution )
return ;
rule . resolution = SIZE ;
2024-07-21 13:09:54 +02:00
2024-12-28 07:32:04 -06:00
applyMonitorRule ( & rule ) ;
2024-07-21 13:09:54 +02:00
} ) ;
2025-08-16 16:52:28 +02:00
m_frameScheduler = makeUnique < CMonitorFrameScheduler > ( m_self . lock ( ) ) ;
m_frameScheduler - > m_self = WP < CMonitorFrameScheduler > ( m_frameScheduler ) ;
2025-07-08 12:41:10 +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 " ) ;
2025-06-13 08:17:32 -05:00
for ( auto & [ name , lease ] : PROTO : : lease ) {
if ( ! lease | | m_output - > getBackend ( ) ! = lease - > getBackend ( ) )
continue ;
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-08-14 19:44:56 +05:00
Debug : : log ( LOG , " Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x} " , m_output - > name , m_position , m_pixelSize , rc < 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-07-28 22:08:05 +02:00
for ( auto const & ws : g_pCompositor - > getWorkspacesCopy ( ) ) {
if ( ! valid ( ws ) )
2024-04-05 16:56:46 +01:00
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 */ ) {
2025-07-28 22:08:05 +02:00
g_pCompositor - > moveWorkspaceToMonitor ( ws , m_self . lock ( ) ) ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( ws , CDesktopAnimationManager : : ANIMATION_TYPE_IN , 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-07-25 15:19:23 +00:00
if ( ! g_pCompositor - > m_lastMonitor ) // set the last monitor if it isn't 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
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 ( ) ) ;
2025-10-26 13:52:09 +00:00
g_pCompositor - > scheduleMonitorStateRecheck ( ) ;
2024-10-05 14:37:12 +01:00
} } ;
2022-08-03 16:05:25 +02:00
2025-07-08 12:41:10 +02:00
m_frameScheduler . reset ( ) ;
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-07-24 00:36:29 +02:00
for ( auto const & w : g_pCompositor - > getWorkspaces ( ) ) {
2025-04-30 23:45:20 +02:00
if ( w - > m_monitor = = m_self | | ! w - > m_monitor )
2025-07-24 00:36:29 +02:00
wspToMove . emplace_back ( w . lock ( ) ) ;
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 ) ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( w , CDesktopAnimationManager : : ANIMATION_TYPE_IN , true , true ) ;
2023-09-01 22:03:56 +02:00
}
} 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
2025-10-29 08:53:42 -04:00
void CMonitor : : applyCMType ( NCMType : : eCMType cmType , int cmSdrEotf ) {
auto oldImageDescription = m_imageDescription ;
static auto PSDREOTF = CConfigValue < Hyprlang : : INT > ( " render:cm_sdr_eotf " ) ;
auto chosenSdrEotf = cmSdrEotf = = 0 ? ( * PSDREOTF > 0 ? NColorManagement : : CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement : : CM_TRANSFER_FUNCTION_SRGB ) :
( cmSdrEotf = = 1 ? NColorManagement : : CM_TRANSFER_FUNCTION_SRGB : NColorManagement : : CM_TRANSFER_FUNCTION_GAMMA22 ) ;
2025-06-23 15:33:09 +03:00
switch ( cmType ) {
2025-10-29 08:53:42 -04:00
case NCMType : : CM_SRGB : m_imageDescription = { . transferFunction = chosenSdrEotf } ; break ; // assumes SImageDescription defaults to sRGB
2025-10-24 21:18:39 +02:00
case NCMType : : CM_WIDE :
2025-10-29 08:53:42 -04:00
m_imageDescription = { . transferFunction = chosenSdrEotf ,
. primariesNameSet = true ,
2025-06-23 15:33:09 +03:00
. primariesNamed = NColorManagement : : CM_PRIMARIES_BT2020 ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_BT2020 ) } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_DCIP3 :
2025-10-29 08:53:42 -04:00
m_imageDescription = { . transferFunction = chosenSdrEotf ,
. primariesNameSet = true ,
2025-09-29 15:22:42 +03:00
. primariesNamed = NColorManagement : : CM_PRIMARIES_DCI_P3 ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_DCI_P3 ) } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_DP3 :
2025-10-29 08:53:42 -04:00
m_imageDescription = { . transferFunction = chosenSdrEotf ,
. primariesNameSet = true ,
2025-09-29 15:22:42 +03:00
. primariesNamed = NColorManagement : : CM_PRIMARIES_DISPLAY_P3 ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_DISPLAY_P3 ) } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_ADOBE :
2025-10-29 08:53:42 -04:00
m_imageDescription = { . transferFunction = chosenSdrEotf ,
. primariesNameSet = true ,
2025-09-29 15:22:42 +03:00
. primariesNamed = NColorManagement : : CM_PRIMARIES_ADOBE_RGB ,
. primaries = NColorManagement : : getPrimaries ( NColorManagement : : CM_PRIMARIES_ADOBE_RGB ) } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_EDID :
2025-10-29 08:53:42 -04:00
m_imageDescription = { . transferFunction = chosenSdrEotf ,
. primariesNameSet = true ,
2025-06-23 15:33:09 +03:00
. 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 } ,
} } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_HDR :
2025-06-23 15:33:09 +03: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 } } ;
break ;
2025-10-24 21:18:39 +02:00
case NCMType : : CM_HDR_EDID :
2025-06-23 15:33:09 +03: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 } } ;
break ;
default : UNREACHABLE ( ) ;
}
if ( m_minLuminance > = 0 )
m_imageDescription . luminances . min = m_minLuminance ;
if ( m_maxLuminance > = 0 )
m_imageDescription . luminances . max = m_maxLuminance ;
if ( m_maxAvgLuminance > = 0 )
m_imageDescription . luminances . reference = m_maxAvgLuminance ;
2025-07-15 20:33:14 +03:00
if ( oldImageDescription ! = m_imageDescription ) {
m_imageDescription . updateId ( ) ;
2025-10-02 13:05:54 +03:00
if ( PROTO : : colorManagement )
PROTO : : colorManagement - > onMonitorImageDescriptionChanged ( m_self ) ;
2025-07-15 20:33:14 +03:00
}
2025-06-23 15:33:09 +03: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 */
2025-06-08 01:12:43 +05:00
& & m_autoDir = = RULE - > autoDir /* Auto direction is the same */
2025-05-05 23:40:37 +02:00
/* 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 ) )
2025-07-25 15:19:23 +00:00
/* other properties hadn't changed */
2025-05-05 23:40:37 +02:00
& & m_transform = = RULE - > transform & & RULE - > enable10bit = = m_enabled10bit & & RULE - > cmType = = m_cmType & & RULE - > sdrSaturation = = m_sdrSaturation & &
2025-06-15 13:15:18 +03:00
RULE - > sdrBrightness = = m_sdrBrightness & & RULE - > sdrMinLuminance = = m_minLuminance & & RULE - > sdrMaxLuminance = = m_maxLuminance & &
RULE - > supportsWideColor = = m_supportsWideColor & & RULE - > supportsHDR = = m_supportsHDR & & RULE - > minLuminance = = m_minLuminance & & RULE - > maxLuminance = = m_maxLuminance & &
RULE - > maxAvgLuminance = = m_maxAvgLuminance & & ! 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 ;
2025-06-08 01:12:43 +05:00
m_autoDir = RULE - > autoDir ;
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-08-14 19:44:56 +05:00
else if ( DELTALESSTHAN ( sc < float > ( a - > refreshRate ) , sc < 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 ;
} ) ;
2025-07-25 15:19:23 +00:00
// if the best mode isn't 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 ;
2025-11-16 14:51:14 +00:00
auto errorMessage = I18n : : i18nEngine ( ) - > localize ( I18n : : TXT_KEY_NOTIF_MONITOR_MODE_FAIL ,
{ { " name " , m_name } , { " mode " , std : : format ( " {:X0}@{:.2f}Hz " , 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 ;
2025-08-14 19:44:56 +05:00
for ( auto const & fmt : formats [ sc < 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-06-15 13:15:18 +03:00
m_supportsWideColor = RULE - > supportsHDR ;
m_supportsHDR = RULE - > supportsHDR ;
2025-07-15 20:33:14 +03:00
m_cmType = RULE - > cmType ;
2025-04-30 23:45:20 +02:00
switch ( m_cmType ) {
2025-10-24 21:18:39 +02:00
case NCMType : : CM_AUTO : m_cmType = m_enabled10bit & & supportsWideColor ( ) ? NCMType : : CM_WIDE : NCMType : : CM_SRGB ; break ;
case NCMType : : CM_EDID : m_cmType = m_output - > parsedEDID . chromaticityCoords . has_value ( ) ? NCMType : : CM_EDID : NCMType : : CM_SRGB ; break ;
case NCMType : : CM_HDR :
case NCMType : : CM_HDR_EDID : m_cmType = supportsHDR ( ) ? m_cmType : NCMType : : CM_SRGB ; break ;
2025-03-14 02:15:18 +03:00
default : break ;
}
2025-06-15 13:15:18 +03:00
2025-10-29 08:53:42 -04:00
m_sdrEotf = RULE - > sdrEotf ;
2025-06-15 13:15:18 +03:00
m_sdrMinLuminance = RULE - > sdrMinLuminance ;
m_sdrMaxLuminance = RULE - > sdrMaxLuminance ;
2025-06-23 15:33:09 +03:00
m_minLuminance = RULE - > minLuminance ;
m_maxLuminance = RULE - > maxLuminance ;
2025-06-15 13:15:18 +03:00
m_maxAvgLuminance = RULE - > maxAvgLuminance ;
2025-06-23 15:33:09 +03:00
2025-10-29 08:53:42 -04:00
applyCMType ( m_cmType , m_sdrEotf ) ;
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 ) ;
2025-09-20 17:42:02 +02:00
static auto PDISABLENOTIFICATION = CConfigValue < Hyprlang : : INT > ( " misc:disable_scale_notification " ) ;
if ( ! * PDISABLENOTIFICATION )
2025-11-16 14:51:14 +00:00
g_pHyprNotificationOverlay - > addNotification (
I18n : : i18nEngine ( ) - > localize ( I18n : : TXT_KEY_NOTIF_MONITOR_AUTO_SCALE ,
{ { " name " , m_name } , { " scale " , std : : format ( " {:.2f} " , m_scale ) } , { " fixed_scale " , std : : format ( " {:.2f} " , searchScale ) } } ) ,
CHyprColor ( 1.0 , 0.0 , 0.0 , 1.0 ) , 5000 , ICON_WARNING ) ;
2025-09-20 17:42:02 +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
2025-10-26 13:52:09 +00:00
g_pCompositor - > scheduleMonitorStateRecheck ( ) ;
2024-12-28 07:32:04 -06:00
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-08-14 19:44:56 +05: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 , sc < int > ( m_transform ) ,
m_position , sc < 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 ) {
2025-07-01 11:33:48 +02:00
if ( m_cursorZoom - > value ( ) ! = 1.f & & g_pCompositor - > getMonitorFromCursor ( ) = = m_self ) {
2025-04-30 23:45:20 +02:00
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 ) {
2025-07-01 11:33:48 +02:00
if ( m_cursorZoom - > value ( ) ! = 1.f & & g_pCompositor - > getMonitorFromCursor ( ) = = m_self ) {
2025-04-30 23:45:20 +02:00
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-08-29 14:31:07 +03:00
const bool shouldSkip = inFullscreenMode ( ) & & ( * 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-07-24 00:36:29 +02:00
wsID = std : : ranges : : distance ( g_pCompositor - > getWorkspaces ( ) ) + 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
2025-08-14 19:44:56 +05:00
Debug : : log ( LOG , " New monitor: WORKSPACEID {}, exists: {} " , wsID , sc < 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 ) ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( PNEWWORKSPACE , CDesktopAnimationManager : : ANIMATION_TYPE_IN , true , true ) ;
2022-09-13 15:25:42 +02:00
} else {
2025-05-31 23:49:50 +05:00
if ( newDefaultWorkspaceName . empty ( ) )
2024-06-23 00:52:42 +03:00
newDefaultWorkspaceName = std : : to_string ( wsID ) ;
2022-09-13 15:25:42 +02:00
2025-07-24 00:36:29 +02:00
PNEWWORKSPACE = 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
2025-07-08 09:56:40 -07:00
PNEWWORKSPACE - > m_events . activeChanged . emit ( ) ;
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 ) ;
2025-08-14 19:44:56 +05:00
applyMonitorRule ( const_cast < 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-07-24 00:36:29 +02:00
for ( auto const & w : g_pCompositor - > getWorkspaces ( ) ) {
2025-04-30 23:45:20 +02:00
if ( w - > m_monitor = = m_self | | ! w - > m_monitor )
2025-07-24 00:36:29 +02:00
wspToMove . emplace_back ( w . lock ( ) ) ;
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 ) ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( w , CDesktopAnimationManager : : ANIMATION_TYPE_IN , true , true ) ;
2022-09-13 15:25:42 +02:00
}
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
2025-10-26 13:52:09 +00:00
g_pCompositor - > scheduleMonitorStateRecheck ( ) ;
2023-08-14 14:22:06 +02:00
2025-04-22 15:23:29 +02:00
g_pCompositor - > setActiveMonitor ( g_pCompositor - > m_monitors . front ( ) ) ;
2022-12-26 13:26:53 +01:00
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 ;
2025-07-24 00:36:29 +02:00
for ( auto const & w : g_pCompositor - > getWorkspaces ( ) ) {
2025-05-05 20:54:27 -05:00
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-06-26 09:32:44 -07:00
m_activeWorkspace = pWorkspace ;
if ( POLDWORKSPACE ) {
2025-04-25 02:37:12 +02:00
POLDWORKSPACE - > m_visible = false ;
2025-07-08 09:56:40 -07:00
POLDWORKSPACE - > m_events . activeChanged . emit ( ) ;
2025-06-26 09:32:44 -07:00
}
2023-04-14 15:03:53 +01:00
2025-06-26 09:32:44 -07:00
pWorkspace - > m_visible = true ;
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 )
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( POLDWORKSPACE , CDesktopAnimationManager : : ANIMATION_TYPE_OUT , ANIMTOLEFT ) ;
g_pDesktopAnimationManager - > startAnimation ( pWorkspace , CDesktopAnimationManager : : ANIMATION_TYPE_IN , ANIMTOLEFT ) ;
2023-04-14 15:03:53 +01:00
// 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-07-08 09:56:40 -07:00
pWorkspace - > m_events . activeChanged . emit ( ) ;
2025-06-26 09:32:44 -07:00
2025-04-30 23:45:20 +02:00
g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ;
2023-04-30 01:12:20 +01:00
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > setFullscreenFadeAnimation (
pWorkspace , pWorkspace - > m_hasFullscreenWindow ? CDesktopAnimationManager : : ANIMATION_TYPE_IN : CDesktopAnimationManager : : ANIMATION_TYPE_OUT ) ;
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 )
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > setFullscreenFadeAnimation (
m_activeSpecialWorkspace , m_activeSpecialWorkspace - > m_hasFullscreenWindow ? CDesktopAnimationManager : : ANIMATION_TYPE_IN : CDesktopAnimationManager : : ANIMATION_TYPE_OUT ) ;
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-06-26 09:32:44 -07:00
const auto POLDSPECIAL = m_activeSpecialWorkspace ;
2025-06-11 17:52:16 +02:00
m_specialFade - > setConfig ( g_pConfigManager - > getAnimationPropertyConfig ( pWorkspace ? " specialWorkspaceIn " : " specialWorkspaceOut " ) ) ;
* m_specialFade = pWorkspace ? 1.F : 0.F ;
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 ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( m_activeSpecialWorkspace , CDesktopAnimationManager : : ANIMATION_TYPE_OUT , false ) ;
2025-04-30 23:45:20 +02:00
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-06-26 09:32:44 -07:00
if ( POLDSPECIAL )
2025-07-08 09:56:40 -07:00
POLDSPECIAL - > m_events . activeChanged . emit ( ) ;
2025-06-26 09:32:44 -07: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-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > setFullscreenFadeAnimation (
m_activeWorkspace , m_activeWorkspace - > m_hasFullscreenWindow ? CDesktopAnimationManager : : ANIMATION_TYPE_IN : CDesktopAnimationManager : : ANIMATION_TYPE_OUT ) ;
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 ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( m_activeSpecialWorkspace , CDesktopAnimationManager : : ANIMATION_TYPE_OUT , false ) ;
2024-04-03 10:09:42 +01:00
}
2023-05-06 16:10:51 +01:00
2025-06-26 09:32:44 -07:00
bool wasActive = false ;
2023-09-04 13:11:51 +00:00
//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 ;
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > setFullscreenFadeAnimation ( PACTIVEWORKSPACE ,
PACTIVEWORKSPACE & & PACTIVEWORKSPACE - > m_hasFullscreenWindow ? CDesktopAnimationManager : : ANIMATION_TYPE_IN :
CDesktopAnimationManager : : ANIMATION_TYPE_OUT ) ;
2024-03-07 05:27:58 -08:00
2025-06-26 09:32:44 -07:00
wasActive = true ;
2023-09-04 13:11:51 +00:00
}
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 ;
2025-06-26 09:32:44 -07:00
if ( POLDSPECIAL )
2025-07-08 09:56:40 -07:00
POLDSPECIAL - > m_events . activeChanged . emit ( ) ;
2025-06-26 09:32:44 -07:00
if ( PMONITORWORKSPACEOWNER ! = m_self )
2025-07-08 09:56:40 -07:00
pWorkspace - > m_events . monitorChanged . emit ( ) ;
2025-06-26 09:32:44 -07:00
if ( ! wasActive )
2025-07-08 09:56:40 -07:00
pWorkspace - > m_events . activeChanged . emit ( ) ;
2025-06-26 09:32:44 -07:00
if ( ! wasActive )
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > startAnimation ( pWorkspace , CDesktopAnimationManager : : ANIMATION_TYPE_IN , 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 ( ) ) {
2025-07-25 15:19:23 +00:00
// if it's floating and the middle isn't on the current mon, move it to the center
2023-12-26 19:44:38 +01:00
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
2025-08-28 11:20:29 +02:00
g_pDesktopAnimationManager - > setFullscreenFadeAnimation (
pWorkspace , pWorkspace - > m_hasFullscreenWindow ? CDesktopAnimationManager : : ANIMATION_TYPE_IN : CDesktopAnimationManager : : ANIMATION_TYPE_OUT ) ;
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
}
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
}
2025-11-13 00:08:04 +00:00
CBox CMonitor : : logicalBoxMinusExtents ( ) {
return { m_position + m_reservedTopLeft , m_size - m_reservedTopLeft - m_reservedBottomRight } ;
}
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
}
2025-09-17 14:03:49 +02:00
uint32_t CMonitor : : isSolitaryBlocked ( bool full ) {
uint32_t reasons = 0 ;
2024-07-21 13:09:54 +02:00
2025-08-22 20:24:25 +03:00
if ( g_pHyprNotificationOverlay - > hasAny ( ) ) {
reasons | = SC_NOTIFICATION ;
if ( ! full )
return reasons ;
}
2024-07-21 13:09:54 +02:00
2025-09-17 14:03:49 +02:00
if ( g_pHyprError - > active ( ) & & g_pCompositor - > m_lastMonitor = = m_self ) {
reasons | = SC_ERRORBAR ;
if ( ! full )
return reasons ;
}
2025-08-22 20:24:25 +03:00
if ( g_pSessionLockManager - > isSessionLocked ( ) ) {
reasons | = SC_LOCK ;
if ( ! full )
return reasons ;
}
2024-07-21 13:09:54 +02:00
2025-08-22 20:24:25 +03:00
const auto PWORKSPACE = m_activeWorkspace ;
if ( ! PWORKSPACE ) {
reasons | = SC_WORKSPACE ;
return reasons ;
}
2024-07-21 13:09:54 +02:00
2025-08-22 20:24:25 +03:00
if ( ! PWORKSPACE - > m_hasFullscreenWindow ) {
reasons | = SC_WINDOWED ;
if ( ! full )
return reasons ;
}
2024-07-21 13:09:54 +02:00
2025-08-22 20:24:25 +03:00
if ( PROTO : : data - > dndActive ( ) ) {
reasons | = SC_DND ;
if ( ! full )
return reasons ;
}
2025-03-08 13:24:22 -06:00
2025-08-22 20:24:25 +03:00
if ( m_activeSpecialWorkspace ) {
reasons | = SC_SPECIAL ;
if ( ! full )
return reasons ;
}
if ( PWORKSPACE - > m_alpha - > value ( ) ! = 1.f ) {
reasons | = SC_ALPHA ;
if ( ! full )
return reasons ;
}
if ( PWORKSPACE - > m_renderOffset - > value ( ) ! = Vector2D { } ) {
reasons | = SC_OFFSET ;
if ( ! full )
return reasons ;
}
const auto PCANDIDATE = PWORKSPACE - > getFullscreenWindow ( ) ;
if ( ! PCANDIDATE ) {
reasons | = SC_CANDIDATE ;
return reasons ;
}
if ( ! PCANDIDATE - > opaque ( ) ) {
reasons | = SC_OPAQUE ;
if ( ! full )
return reasons ;
}
if ( PCANDIDATE - > m_realSize - > value ( ) ! = m_size | | PCANDIDATE - > m_realPosition - > value ( ) ! = m_position | | PCANDIDATE - > m_realPosition - > isBeingAnimated ( ) | |
PCANDIDATE - > m_realSize - > isBeingAnimated ( ) ) {
reasons | = SC_TRANSFORM ;
if ( ! full )
return reasons ;
}
if ( ! m_layerSurfaceLayers [ ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ] . empty ( ) ) {
reasons | = SC_OVERLAYS ;
if ( ! full )
return reasons ;
}
for ( auto const & topls : m_layerSurfaceLayers [ ZWLR_LAYER_SHELL_V1_LAYER_TOP ] ) {
if ( topls - > m_alpha - > value ( ) ! = 0.f ) {
reasons | = SC_OVERLAYS ;
if ( ! full )
return reasons ;
}
}
for ( auto const & w : g_pCompositor - > m_windows ) {
if ( w = = PCANDIDATE | | ( ! w - > m_isMapped & & ! w - > m_fadingOut ) | | w - > isHidden ( ) )
continue ;
if ( w - > workspaceID ( ) = = PCANDIDATE - > workspaceID ( ) & & w - > m_isFloating & & w - > m_createdOverFullscreen & & w - > visibleOnMonitor ( m_self . lock ( ) ) ) {
reasons | = SC_FLOAT ;
if ( ! full )
return reasons ;
}
}
for ( auto const & ws : g_pCompositor - > getWorkspaces ( ) ) {
if ( ws - > m_alpha - > value ( ) < = 0.F | | ! ws - > m_isSpecialWorkspace | | ws - > m_monitor ! = m_self )
continue ;
reasons | = SC_WORKSPACES ;
if ( ! full )
return reasons ;
}
// check if it did not open any subsurfaces or shit
if ( ! PCANDIDATE - > getSolitaryResource ( ) )
reasons | = SC_SURFACES ;
return reasons ;
}
void CMonitor : : recheckSolitary ( ) {
m_solitaryClient . reset ( ) ; // reset it, if we find one it will be set.
if ( isSolitaryBlocked ( ) )
return ;
m_solitaryClient = m_activeWorkspace - > getFullscreenWindow ( ) ;
}
uint8_t CMonitor : : isTearingBlocked ( bool full ) {
uint8_t reasons = 0 ;
static auto PTEARINGENABLED = CConfigValue < Hyprlang : : INT > ( " general:allow_tearing " ) ;
if ( ! m_tearingState . nextRenderTorn ) {
reasons | = TC_NOT_TORN ;
if ( ! full )
return reasons ;
}
if ( ! * PTEARINGENABLED ) {
reasons | = TC_USER ;
2025-08-29 14:31:07 +03:00
if ( ! full ) {
Debug : : log ( WARN , " Tearing commit requested but the master switch general:allow_tearing is off, ignoring " ) ;
2025-08-22 20:24:25 +03:00
return reasons ;
2025-08-29 14:31:07 +03:00
}
2025-08-22 20:24:25 +03:00
}
if ( g_pHyprOpenGL - > m_renderData . mouseZoomFactor ! = 1.0 ) {
reasons | = TC_ZOOM ;
2025-08-29 14:31:07 +03:00
if ( ! full ) {
Debug : : log ( WARN , " Tearing commit requested but scale factor is not 1, ignoring " ) ;
2025-08-22 20:24:25 +03:00
return reasons ;
2025-08-29 14:31:07 +03:00
}
2025-08-22 20:24:25 +03:00
}
if ( ! m_tearingState . canTear ) {
reasons | = TC_SUPPORT ;
2025-08-29 14:31:07 +03:00
if ( ! full ) {
Debug : : log ( WARN , " Tearing commit requested but monitor doesn't support it, ignoring " ) ;
2025-08-22 20:24:25 +03:00
return reasons ;
2025-08-29 14:31:07 +03:00
}
2025-08-22 20:24:25 +03:00
}
if ( m_solitaryClient . expired ( ) ) {
reasons | = TC_CANDIDATE ;
return reasons ;
}
if ( ! m_solitaryClient - > canBeTorn ( ) )
reasons | = TC_WINDOW ;
return reasons ;
}
bool CMonitor : : updateTearing ( ) {
m_tearingState . activelyTearing = ! isTearingBlocked ( ) ;
m_tearingState . nextRenderTorn = false ;
return m_tearingState . activelyTearing ;
}
uint16_t CMonitor : : isDSBlocked ( bool full ) {
uint16_t reasons = 0 ;
static auto PDIRECTSCANOUT = CConfigValue < Hyprlang : : INT > ( " render:direct_scanout " ) ;
2025-08-29 14:31:07 +03:00
static auto PPASS = CConfigValue < Hyprlang : : INT > ( " render:cm_fs_passthrough " ) ;
2025-10-02 13:05:54 +03:00
static auto PNONSHADER = CConfigValue < Hyprlang : : INT > ( " render:non_shader_cm " ) ;
2025-08-22 20:24:25 +03:00
if ( * PDIRECTSCANOUT = = 0 ) {
reasons | = DS_BLOCK_USER ;
if ( ! full )
return reasons ;
}
if ( * PDIRECTSCANOUT = = 2 ) {
if ( ! m_activeWorkspace | | ! m_activeWorkspace - > m_hasFullscreenWindow | | m_activeWorkspace - > m_fullscreenMode ! = FSMODE_FULLSCREEN ) {
reasons | = DS_BLOCK_WINDOWED ;
if ( ! full )
return reasons ;
} else if ( m_activeWorkspace - > getFullscreenWindow ( ) - > getContentType ( ) ! = CONTENT_TYPE_GAME ) {
reasons | = DS_BLOCK_CONTENT ;
if ( ! full )
return reasons ;
}
}
if ( m_tearingState . activelyTearing ) {
reasons | = DS_BLOCK_TEARING ;
if ( ! full )
return reasons ;
}
if ( ! m_mirrors . empty ( ) | | isMirror ( ) ) {
reasons | = DS_BLOCK_MIRROR ;
if ( ! full )
return reasons ;
}
if ( g_pHyprRenderer - > m_directScanoutBlocked ) {
reasons | = DS_BLOCK_RECORD ;
if ( ! full )
return reasons ;
}
if ( g_pPointerManager - > softwareLockedFor ( m_self . lock ( ) ) ) {
reasons | = DS_BLOCK_SW ;
if ( ! full )
return reasons ;
}
const auto PCANDIDATE = m_solitaryClient . lock ( ) ;
if ( ! PCANDIDATE ) {
reasons | = DS_BLOCK_CANDIDATE ;
return reasons ;
}
const auto PSURFACE = PCANDIDATE - > getSolitaryResource ( ) ;
if ( ! PSURFACE | | ! PSURFACE - > m_current . texture | | ! PSURFACE - > m_current . buffer ) {
reasons | = DS_BLOCK_SURFACE ;
return reasons ;
}
if ( PSURFACE - > m_current . bufferSize ! = m_pixelSize | | PSURFACE - > m_current . transform ! = m_transform ) {
reasons | = DS_BLOCK_TRANSFORM ;
if ( ! full )
return reasons ;
}
2024-07-21 13:09:54 +02:00
// we can't scanout shm buffers.
2025-05-03 16:02:49 +02:00
const auto params = PSURFACE - > m_current . buffer - > dmabuf ( ) ;
2025-08-29 14:31:07 +03:00
if ( ! params . success | | ! PSURFACE - > m_current . texture - > m_eglImage /* dmabuf */ ) {
2025-08-22 20:24:25 +03:00
reasons | = DS_BLOCK_DMA ;
2025-08-29 14:31:07 +03:00
if ( ! full )
return reasons ;
}
2025-10-02 13:05:54 +03:00
if ( needsCM ( ) & & * PNONSHADER ! = CM_NS_IGNORE & & ! canNoShaderCM ( ) & & ( ! inHDR ( ) | | ( PSURFACE - > m_colorManagement . valid ( ) & & PSURFACE - > m_colorManagement - > isWindowsScRGB ( ) ) ) & &
* PPASS ! = 1 )
2025-08-29 14:31:07 +03:00
reasons | = DS_BLOCK_CM ;
2025-08-22 20:24:25 +03:00
return reasons ;
}
bool CMonitor : : attemptDirectScanout ( ) {
const auto blockedReason = isDSBlocked ( ) ;
2025-10-21 19:10:06 +01:00
if ( blockedReason )
2024-07-21 13:09:54 +02:00
return false ;
2025-08-22 20:24:25 +03:00
const auto PCANDIDATE = m_solitaryClient . lock ( ) ;
const auto PSURFACE = PCANDIDATE - > getSolitaryResource ( ) ;
const auto params = PSURFACE - > m_current . buffer - > dmabuf ( ) ;
2024-07-21 13:09:54 +02:00
2025-08-22 20:24:25 +03:00
Debug : : log ( TRACE , " attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {}) " , rc < uintptr_t > ( PSURFACE . get ( ) ) ,
rc < uintptr_t > ( PSURFACE - > m_current . buffer . m_buffer . get ( ) ) , m_drmFormat , params . format , params . modifier ) ;
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-11-06 14:25:49 +01:00
// #TODO this entire bit needs figuring out, vrr goes down the drain without it
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-08-22 20:24:25 +03:00
Debug : : log ( TRACE , " attemptDirectScanout: failed basic test on cursor update " ) ;
2025-03-17 16:06:41 -05:00
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
}
2025-11-06 14:25:49 +01:00
//#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked.
if ( PSURFACE - > m_fifo )
PSURFACE - > m_stateQueue . unlockFirst ( LOCK_REASON_FIFO ) ;
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 ) ;
2025-08-22 20:24:25 +03:00
Debug : : log ( TRACE , " attemptDirectScanout: setting presentation mode " ) ;
2025-04-30 23:45:20 +02:00
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-08-14 19:44:56 +05:00
Debug : : log ( LOG , " Entered a direct scanout to {:x}: \" {} \" " , rc < 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 ( ) ;
2025-07-09 17:39:36 +05:00
PBUFFER - > onBackendRelease ( [ wb = WP < IHLBuffer > { PBUFFER } ] {
if ( wb )
wb - > unlock ( ) ;
} ) ;
2025-03-08 13:24:22 -06:00
2024-07-21 13:09:54 +02:00
return true ;
}
2025-08-17 08:37:13 +01:00
void CMonitor : : setDPMS ( bool on ) {
2025-08-19 11:30:26 -07:00
// Don't trigger animation if the target state is the same
if ( m_dpmsStatus = = on )
return ;
2025-08-17 08:37:13 +01:00
m_dpmsStatus = on ;
m_events . dpmsChanged . emit ( ) ;
if ( on ) {
// enable the monitor. Wait for the frame to be presented, then begin animation
m_dpmsBlackOpacity - > setCallbackOnEnd ( nullptr ) ;
2025-10-27 13:34:08 +00:00
m_dpmsBlackOpacity - > setValueAndWarp ( 1.F ) ;
2025-08-17 17:14:29 +01:00
m_pendingDpmsAnimation = true ;
m_pendingDpmsAnimationCounter = 0 ;
2025-08-17 08:37:13 +01:00
commitDPMSState ( true ) ;
} else {
// disable the monitor. Begin the animation, then do dpms on its end.
2025-10-27 13:34:08 +00:00
m_dpmsBlackOpacity - > setCallbackOnEnd ( nullptr ) ;
2025-08-17 08:37:13 +01:00
m_dpmsBlackOpacity - > setValueAndWarp ( 0.F ) ;
* m_dpmsBlackOpacity = 1.F ;
m_dpmsBlackOpacity - > setCallbackOnEnd (
[ this , self = m_self ] ( auto ) {
if ( ! self )
return ;
// commit DPMS to disable the monitor, it's fully black now
commitDPMSState ( false ) ;
} ,
true ) ;
}
}
void CMonitor : : commitDPMSState ( bool state ) {
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setEnabled ( state ) ;
if ( ! m_state . commit ( ) ) {
2025-10-27 13:34:08 +00:00
Debug : : log ( ERR , " Couldn't commit output {} for DPMS = {}, will retry. " , m_name , state ) ;
// retry in 2 frames. This could happen when the DRM backend rejects our commit
// because disable + enable were sent almost instantly
m_dpmsRetryTimer = makeShared < CEventLoopTimer > (
std : : chrono : : milliseconds ( 2000 / sc < int > ( m_refreshRate ) ) ,
[ this , self = m_self ] ( SP < CEventLoopTimer > s , void * d ) {
if ( ! self )
return ;
m_output - > state - > resetExplicitFences ( ) ;
m_output - > state - > setEnabled ( m_dpmsStatus ) ;
if ( ! m_state . commit ( ) ) {
Debug : : log ( ERR , " Couldn't retry committing output {} for DPMS = {} " , m_name , m_dpmsStatus ) ;
return ;
}
m_dpmsRetryTimer . reset ( ) ;
} ,
nullptr ) ;
g_pEventLoopManager - > addTimer ( m_dpmsRetryTimer ) ;
2025-08-17 08:37:13 +01:00
return ;
}
if ( state )
g_pHyprRenderer - > damageMonitor ( m_self . lock ( ) ) ;
}
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
}
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-06-15 13:15:18 +03:00
bool CMonitor : : supportsWideColor ( ) {
return m_supportsWideColor | | m_output - > parsedEDID . supportsBT2020 ;
}
bool CMonitor : : supportsHDR ( ) {
return supportsWideColor ( ) & & ( m_supportsHDR | | ( m_output - > parsedEDID . hdrMetadata . has_value ( ) ? m_output - > parsedEDID . hdrMetadata - > supportsPQ : false ) ) ;
}
2025-08-19 21:28:52 +03:00
float CMonitor : : minLuminance ( float defaultValue ) {
return m_minLuminance > = 0 ? m_minLuminance : ( m_output - > parsedEDID . hdrMetadata . has_value ( ) ? m_output - > parsedEDID . hdrMetadata - > desiredContentMinLuminance : defaultValue ) ;
2025-06-15 13:15:18 +03:00
}
2025-08-19 21:28:52 +03:00
int CMonitor : : maxLuminance ( int defaultValue ) {
return m_maxLuminance > = 0 ? m_maxLuminance : ( m_output - > parsedEDID . hdrMetadata . has_value ( ) ? m_output - > parsedEDID . hdrMetadata - > desiredContentMaxLuminance : defaultValue ) ;
2025-06-15 13:15:18 +03:00
}
2025-08-19 21:28:52 +03:00
int CMonitor : : maxAvgLuminance ( int defaultValue ) {
return m_maxAvgLuminance > = 0 ? m_maxAvgLuminance :
( m_output - > parsedEDID . hdrMetadata . has_value ( ) ? m_output - > parsedEDID . hdrMetadata - > desiredMaxFrameAverageLuminance : defaultValue ) ;
2025-06-15 13:15:18 +03:00
}
2025-08-22 13:13:55 +03:00
bool CMonitor : : wantsWideColor ( ) {
return supportsWideColor ( ) & & ( wantsHDR ( ) | | m_imageDescription . primariesNamed = = CM_PRIMARIES_BT2020 ) ;
}
bool CMonitor : : wantsHDR ( ) {
return supportsHDR ( ) & & inHDR ( ) ;
}
bool CMonitor : : inHDR ( ) {
return m_output - > state - > state ( ) . hdrMetadata . hdmi_metadata_type1 . eotf = = 2 ;
}
2025-08-29 14:31:07 +03:00
bool CMonitor : : inFullscreenMode ( ) {
return m_activeWorkspace & & m_activeWorkspace - > m_hasFullscreenWindow & & m_activeWorkspace - > m_fullscreenMode = = FSMODE_FULLSCREEN ;
}
std : : optional < NColorManagement : : SImageDescription > CMonitor : : getFSImageDescription ( ) {
if ( ! inFullscreenMode ( ) )
return { } ;
const auto FS_WINDOW = m_activeWorkspace - > getFullscreenWindow ( ) ;
if ( ! FS_WINDOW )
return { } ; // should be unreachable
const auto ROOT_SURF = FS_WINDOW - > m_wlSurface - > resource ( ) ;
const auto SURF = ROOT_SURF - > findWithCM ( ) ;
return SURF ? SURF - > m_colorManagement - > imageDescription ( ) : SImageDescription { } ;
}
bool CMonitor : : needsCM ( ) {
2025-10-02 13:05:54 +03:00
const auto SRC_DESC = getFSImageDescription ( ) ;
return SRC_DESC . has_value ( ) & & SRC_DESC . value ( ) ! = m_imageDescription ;
2025-08-29 14:31:07 +03:00
}
// TODO support more drm properties
bool CMonitor : : canNoShaderCM ( ) {
2025-10-02 13:05:54 +03:00
static auto PNONSHADER = CConfigValue < Hyprlang : : INT > ( " render:non_shader_cm " ) ;
if ( * PNONSHADER = = CM_NS_DISABLE )
return false ;
2025-08-29 14:31:07 +03:00
const auto SRC_DESC = getFSImageDescription ( ) ;
if ( ! SRC_DESC . has_value ( ) )
return false ;
if ( SRC_DESC . value ( ) = = m_imageDescription )
return true ; // no CM needed
if ( SRC_DESC - > icc . fd > = 0 | | m_imageDescription . icc . fd > = 0 )
return false ; // no ICC support
// only primaries differ
if ( SRC_DESC - > transferFunction = = m_imageDescription . transferFunction & & SRC_DESC - > transferFunctionPower = = m_imageDescription . transferFunctionPower & &
2025-10-02 13:05:54 +03:00
( ! inHDR ( ) | | SRC_DESC - > luminances = = m_imageDescription . luminances ) & & SRC_DESC - > masteringLuminances = = m_imageDescription . masteringLuminances & &
2025-08-29 14:31:07 +03:00
SRC_DESC - > maxCLL = = m_imageDescription . maxCLL & & SRC_DESC - > maxFALL = = m_imageDescription . maxFALL )
return true ;
return false ;
}
bool CMonitor : : doesNoShaderCM ( ) {
return m_noShaderCTM ;
}
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 ;
2025-07-08 12:41:10 +02:00
options . length = 3 ;
2024-07-21 13:09:54 +02:00
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
}