From b51fdb9ddff92b61ae8ad3fab9ba33c0653cd235 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 15:15:39 +0300 Subject: [PATCH 1/6] Default-disable sw cursor for Linux Workaround for #2114 The reason why we still default-enable for Windows is #1705 Should figure out the actual cause of the offset bug, but it's better to the kind of cursor which works best for Windows/Linux until then. --- src/game/client/view.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index 7e792a1f17..57d2aab641 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -147,15 +147,39 @@ static ConVar r_farz( "r_farz", "-1", FCVAR_CHEAT, "Override the far clipping pl static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view during demo playback" ); +#ifdef NEO +enum ESoftwareCursor // stored in user configs, don't reorder +{ + Disabled = 0, + EnabledForWindows = (1 << 0), + EnabledForLinux = (1 << 1), + + Maximum = (EnabledForWindows | EnabledForLinux) +}; void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue ) { ConVar *pConVar = (ConVar *)pVar; - vgui::surface()->SetSoftwareCursor( pConVar->GetBool() || UseVR() ); + bool enabled; +#ifdef _WIN32 + enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForWindows); +#elif defined(LINUX) + enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForLinux); +#else + enabled = false; Assert(!"unimplemented"); +#endif + vgui::surface()->SetSoftwareCursor( enabled || UseVR() ); } -#ifdef NEO -static ConVar cl_software_cursor ( "cl_software_cursor", "1", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB ); +static ConVar cl_software_cursor( "cl_software_cursor", "1", FCVAR_ARCHIVE, + "Switches the game to use a larger software cursor instead of the normal OS cursor. " + "Set as bitflags. 1: enabled for Windows, 2: enabled for Linux, 3: enabled for both", + true, ESoftwareCursor::Disabled, true, ESoftwareCursor::Maximum, SoftwareCursorChangedCB ); #else static ConVar cl_software_cursor ( "cl_software_cursor", "0", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB ); +void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue ) +{ + ConVar *pConVar = (ConVar *)pVar; + vgui::surface()->SetSoftwareCursor( pConVar->GetBool() || UseVR() ); +} #endif @@ -343,6 +367,9 @@ void CViewRender::Init( void ) #endif #ifdef NEO + // Call manually once to verify compatibility of the current setting (workaround for Linux bug #2114) + SoftwareCursorChangedCB(&cl_software_cursor, cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); + ITexture *pDepthOld = materials->FindTexture("_rt_FullFrameDepth", TEXTURE_GROUP_RENDER_TARGET); const bool bDepthTexOk = ((pDepthOld != NULL) && (!pDepthOld->IsError())); const int flags = (bDepthTexOk ? pDepthOld->GetFlags() : TEXTUREFLAGS_NOMIP | From 391381514fd3e8417e81a830aacc103a517da0d5 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 15:38:37 +0300 Subject: [PATCH 2/6] fix loading screen logic --- src/game/client/hl2mp/clientmode_hl2mpnormal.cpp | 7 ++++++- src/game/client/neo/ui/neo_loading.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp index c756774d03..e18b63f850 100644 --- a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp +++ b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp @@ -128,7 +128,12 @@ ClientModeHL2MPNormal::ClientModeHL2MPNormal() { if (auto* surface = vgui::surface()) { - surface->SetSoftwareCursor(cl_software_cursor.GetBool()); + const int swCursorPreference = cl_software_cursor.GetInt(); + if (swCursorPreference) // force the cvar callback to run by flipping the value + { + cl_software_cursor.SetValue(0); + cl_software_cursor.SetValue(swCursorPreference); + } } else { diff --git a/src/game/client/neo/ui/neo_loading.cpp b/src/game/client/neo/ui/neo_loading.cpp index bb4159598a..a269772a26 100644 --- a/src/game/client/neo/ui/neo_loading.cpp +++ b/src/game/client/neo/ui/neo_loading.cpp @@ -97,7 +97,12 @@ void CNeoLoading::OnMessage(const KeyValues *params, vgui::VPANEL fromPanel) // Revert the software cursor option back to user preference once we exit the loading screen. Assert(cl_software_cursor.IsValid()); - vgui::surface()->SetSoftwareCursor(cl_software_cursor.GetBool()); + const int swCursorPreference = cl_software_cursor.GetInt(); + if (swCursorPreference) // force the cvar callback to run by flipping the value + { + cl_software_cursor.SetValue(0); + cl_software_cursor.SetValue(swCursorPreference); + } if (engine->IsConnected() && !engine->IsLevelMainMenuBackground()) { From fe46dbbff9c38851f16e967680eace42662eecc1 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 15:58:03 +0300 Subject: [PATCH 3/6] fix options menu logic cvar type from bool -> int and use RingBoxFlag for toggling the enum bits --- src/game/client/neo/ui/neo_root_settings.cpp | 9 ++++++--- src/game/client/neo/ui/neo_root_settings.h | 2 +- src/game/client/view.cpp | 8 -------- src/game/client/view.h | 17 +++++++++++++++++ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index 8057264bf7..c765e2b337 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -12,6 +12,7 @@ #include #include #include "vgui/ISystem.h" +#include "view.h" #include "voice_status.h" #include "neo_ui.h" @@ -662,7 +663,7 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey pVideo->flGamma = cvr->mat_monitorgamma.GetFloat(); pVideo->iFov = cvr->neo_fov.GetInt(); pVideo->iViewmodelFov = cvr->neo_viewmodel_fov_offset.GetInt(); - pVideo->bSoftwareCursor = cvr->cl_software_cursor.GetBool(); + pVideo->iSoftwareCursor = cvr->cl_software_cursor.GetInt(); } { NeoSettings::Crosshair *pCrosshair = &ns->crosshair; @@ -933,7 +934,7 @@ void NeoSettingsSave(const NeoSettings *ns) cvr->mat_monitorgamma.SetValue(pVideo->flGamma); cvr->neo_fov.SetValue(pVideo->iFov); cvr->neo_viewmodel_fov_offset.SetValue(pVideo->iViewmodelFov); - cvr->cl_software_cursor.SetValue(pVideo->bSoftwareCursor); + cvr->cl_software_cursor.SetValue(pVideo->iSoftwareCursor); } { const NeoSettings::Crosshair *pCrosshair = &ns->crosshair; @@ -1353,7 +1354,9 @@ void NeoSettings_Video(NeoSettings *ns) NeoUI::Slider(L"Gamma", &pVideo->flGamma, 1.6, 2.6, 2, 0.1f); NeoUI::SliderInt(L"FOV", &pVideo->iFov, MIN_FOV, MAX_FOV); NeoUI::SliderInt(L"Viewmodel FOV Offset", &pVideo->iViewmodelFov, -20, 40); - NeoUI::RingBoxBool(L"Software Cursor", &pVideo->bSoftwareCursor); +#ifndef LINUX // disabled on Linux for now, see bug #2114 + NeoUI::RingBoxFlag(L"Software Cursor", ESoftwareCursor::EnabledForPlatform, &pVideo->iSoftwareCursor); +#endif NeoUI::Divider(L"VISUALS"); NeoUI::RingBox(L"Model detail", QUALITY_LABELS, 3, &pVideo->iModelDetail); diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index 6065ce8cc0..966c0b0d08 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -161,7 +161,7 @@ struct NeoSettings float flGamma; int iFov; int iViewmodelFov; - bool bSoftwareCursor; + int iSoftwareCursor; // Video modes int iVMListSize; diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index 57d2aab641..0c4eaf7b2b 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -148,14 +148,6 @@ static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view #ifdef NEO -enum ESoftwareCursor // stored in user configs, don't reorder -{ - Disabled = 0, - EnabledForWindows = (1 << 0), - EnabledForLinux = (1 << 1), - - Maximum = (EnabledForWindows | EnabledForLinux) -}; void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue ) { ConVar *pConVar = (ConVar *)pVar; diff --git a/src/game/client/view.h b/src/game/client/view.h index 754c2ab3aa..fef3e151de 100644 --- a/src/game/client/view.h +++ b/src/game/client/view.h @@ -20,6 +20,23 @@ class Vector; class QAngle; class VPlane; +#ifdef NEO +enum ESoftwareCursor // stored in user configs, don't reorder +{ + Disabled = 0, + EnabledForWindows = (1 << 0), + EnabledForLinux = (1 << 1), + +#ifdef _WIN32 + EnabledForPlatform = EnabledForWindows, +#elif defined(LINUX) + EnabledForPlatform = EnabledForLinux, +#else + EnabledForPlatform = 0, +#endif + Maximum = (EnabledForWindows | EnabledForLinux) +}; +#endif // near and far Z it uses to render the world. #ifdef NEO From 0e2c7cb9d2ad8f315cc03527fa855a9cada50937 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 16:15:20 +0300 Subject: [PATCH 4/6] refactor --- .../client/hl2mp/clientmode_hl2mpnormal.cpp | 19 +------------------ src/game/client/neo/ui/neo_loading.cpp | 9 +-------- src/game/client/view.cpp | 19 ++++++++++--------- src/game/client/view.h | 2 ++ 4 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp index e18b63f850..e45a59777f 100644 --- a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp +++ b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp @@ -122,24 +122,7 @@ ClientModeHL2MPNormal::ClientModeHL2MPNormal() m_pViewport = new CHudViewport(); m_pViewport->Start(gameuifuncs, gameeventmanager); #ifdef NEO - ConVarRef cl_software_cursor( "cl_software_cursor" ); - Assert(cl_software_cursor.IsValid()); - if (cl_software_cursor.IsValid()) - { - if (auto* surface = vgui::surface()) - { - const int swCursorPreference = cl_software_cursor.GetInt(); - if (swCursorPreference) // force the cvar callback to run by flipping the value - { - cl_software_cursor.SetValue(0); - cl_software_cursor.SetValue(swCursorPreference); - } - } - else - { - Assert(false); - } - } + SwCursorHack_RestoreValue(); #endif } diff --git a/src/game/client/neo/ui/neo_loading.cpp b/src/game/client/neo/ui/neo_loading.cpp index a269772a26..ab40a4c624 100644 --- a/src/game/client/neo/ui/neo_loading.cpp +++ b/src/game/client/neo/ui/neo_loading.cpp @@ -93,16 +93,9 @@ void CNeoLoading::OnMessage(const KeyValues *params, vgui::VPANEL fromPanel) else if (V_strcmp(pSzMsgName, "deactivate") == 0) { g_pNeoRoot->m_bOnLoadingScreen = false; - static ConVarRef cl_software_cursor( "cl_software_cursor" ); // Revert the software cursor option back to user preference once we exit the loading screen. - Assert(cl_software_cursor.IsValid()); - const int swCursorPreference = cl_software_cursor.GetInt(); - if (swCursorPreference) // force the cvar callback to run by flipping the value - { - cl_software_cursor.SetValue(0); - cl_software_cursor.SetValue(swCursorPreference); - } + SwCursorHack_RestoreValue(); if (engine->IsConnected() && !engine->IsLevelMainMenuBackground()) { diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index 0c4eaf7b2b..34dd6bb55f 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -148,23 +148,24 @@ static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view #ifdef NEO -void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue ) +void SoftwareCursorChangedCB( IConVar *pVar, const char *, float ) { ConVar *pConVar = (ConVar *)pVar; - bool enabled; -#ifdef _WIN32 - enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForWindows); -#elif defined(LINUX) - enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForLinux); -#else - enabled = false; Assert(!"unimplemented"); -#endif + bool enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForPlatform); vgui::surface()->SetSoftwareCursor( enabled || UseVR() ); } static ConVar cl_software_cursor( "cl_software_cursor", "1", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor. " "Set as bitflags. 1: enabled for Windows, 2: enabled for Linux, 3: enabled for both", true, ESoftwareCursor::Disabled, true, ESoftwareCursor::Maximum, SoftwareCursorChangedCB ); +void SwCursorHack_RestoreValue() +{ + if (cl_software_cursor.GetBool()) + { + SoftwareCursorChangedCB(&cl_software_cursor, + cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); + } +} #else static ConVar cl_software_cursor ( "cl_software_cursor", "0", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB ); void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue ) diff --git a/src/game/client/view.h b/src/game/client/view.h index fef3e151de..afaf976b00 100644 --- a/src/game/client/view.h +++ b/src/game/client/view.h @@ -36,6 +36,8 @@ enum ESoftwareCursor // stored in user configs, don't reorder #endif Maximum = (EnabledForWindows | EnabledForLinux) }; + +void SwCursorHack_RestoreValue(); #endif // near and far Z it uses to render the world. From 263594431635774bce31b082ee161acff9b268df Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 16:30:42 +0300 Subject: [PATCH 5/6] fix vr mode logic --- src/game/client/view.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index 34dd6bb55f..6253461672 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -160,11 +160,7 @@ static ConVar cl_software_cursor( "cl_software_cursor", "1", FCVAR_ARCHIVE, true, ESoftwareCursor::Disabled, true, ESoftwareCursor::Maximum, SoftwareCursorChangedCB ); void SwCursorHack_RestoreValue() { - if (cl_software_cursor.GetBool()) - { - SoftwareCursorChangedCB(&cl_software_cursor, - cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); - } + SoftwareCursorChangedCB(&cl_software_cursor, cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); } #else static ConVar cl_software_cursor ( "cl_software_cursor", "0", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB ); From a3b9c8b50b286449a33c0be59d189a8a557b52e1 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 13 Sep 2026 16:34:34 +0300 Subject: [PATCH 6/6] refactor --- src/game/client/view.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index 6253461672..e5ef02894a 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -148,19 +148,15 @@ static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view #ifdef NEO -void SoftwareCursorChangedCB( IConVar *pVar, const char *, float ) -{ - ConVar *pConVar = (ConVar *)pVar; - bool enabled = (pConVar->GetInt() & ESoftwareCursor::EnabledForPlatform); - vgui::surface()->SetSoftwareCursor( enabled || UseVR() ); -} static ConVar cl_software_cursor( "cl_software_cursor", "1", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor. " "Set as bitflags. 1: enabled for Windows, 2: enabled for Linux, 3: enabled for both", - true, ESoftwareCursor::Disabled, true, ESoftwareCursor::Maximum, SoftwareCursorChangedCB ); + true, ESoftwareCursor::Disabled, true, ESoftwareCursor::Maximum, + [](IConVar*, const char*, float) { SwCursorHack_RestoreValue(); }); void SwCursorHack_RestoreValue() { - SoftwareCursorChangedCB(&cl_software_cursor, cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); + bool enabled = (cl_software_cursor.GetInt() & ESoftwareCursor::EnabledForPlatform); + vgui::surface()->SetSoftwareCursor(enabled || UseVR()); } #else static ConVar cl_software_cursor ( "cl_software_cursor", "0", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB ); @@ -356,8 +352,7 @@ void CViewRender::Init( void ) #endif #ifdef NEO - // Call manually once to verify compatibility of the current setting (workaround for Linux bug #2114) - SoftwareCursorChangedCB(&cl_software_cursor, cl_software_cursor.GetString(), cl_software_cursor.GetFloat()); + SwCursorHack_RestoreValue(); ITexture *pDepthOld = materials->FindTexture("_rt_FullFrameDepth", TEXTURE_GROUP_RENDER_TARGET); const bool bDepthTexOk = ((pDepthOld != NULL) && (!pDepthOld->IsError()));