Skip to content

Commit 6a11bc3

Browse files
committed
general: add screen cast border indicators
1 parent 6175ecd commit 6a11bc3

File tree

8 files changed

+103
-9
lines changed

8 files changed

+103
-9
lines changed

src/config/ConfigDescriptions.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
1515
.type = CONFIG_OPTION_INT,
1616
.data = SConfigOptionDescription::SRangeData{1, 0, 20},
1717
},
18+
SConfigOptionDescription{
19+
.value = "general:screencast_border.enabled",
20+
.description = "enable red border indicator when screen sharing",
21+
.type = CONFIG_OPTION_BOOL,
22+
.data = SConfigOptionDescription::SBoolData{true},
23+
},
24+
SConfigOptionDescription{
25+
.value = "general:screencast_border.color",
26+
.description = "border color when screen sharing",
27+
.type = CONFIG_OPTION_GRADIENT,
28+
.data = SConfigOptionDescription::SGradientData{"0xffff0000"},
29+
},
1830
SConfigOptionDescription{
1931
.value = "general:gaps_in",
2032
.description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)",

src/config/ConfigManager.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ CConfigManager::CConfigManager() {
443443
m_config = makeUnique<Hyprlang::CConfig>(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true});
444444

445445
registerConfigVar("general:border_size", Hyprlang::INT{1});
446+
registerConfigVar("general:screencast_border.enabled", Hyprlang::INT{1});
447+
registerConfigVar("general:screencast_border.color", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffff0000"});
446448
registerConfigVar("general:gaps_in", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "5"});
447449
registerConfigVar("general:gaps_out", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "20"});
448450
registerConfigVar("general:float_gaps", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "0"});

src/protocols/Screencopy.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,11 @@ void CScreencopyProtocol::destroyResource(CScreencopyClient* client) {
514514
}
515515

516516
void CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) {
517+
// Damage the monitor before destroying the frame so the border updates
518+
if (frame && frame->m_monitor) {
519+
if (auto monitor = frame->m_monitor.lock())
520+
g_pHyprRenderer->damageMonitor(monitor);
521+
}
517522
std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; });
518523
std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; });
519524
}

src/protocols/Screencopy.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ class CScreencopyClient {
3636
CTimer m_lastFrame;
3737
int m_frameCounter = 0;
3838

39+
bool m_sentScreencast = false;
40+
3941
private:
4042
SP<CZwlrScreencopyManagerV1> m_resource;
4143

4244
int m_framesInLastHalfSecond = 0;
4345
CTimer m_lastMeasure;
44-
bool m_sentScreencast = false;
4546

4647
SP<HOOK_CALLBACK_FN> m_tickCallback;
4748
void onTick();
@@ -59,11 +60,10 @@ class CScreencopyFrame {
5960

6061
WP<CScreencopyFrame> m_self;
6162
WP<CScreencopyClient> m_client;
63+
PHLMONITORREF m_monitor;
6264

6365
private:
6466
SP<CZwlrScreencopyFrameV1> m_resource;
65-
66-
PHLMONITORREF m_monitor;
6767
bool m_overlayCursor = false;
6868
bool m_withDamage = false;
6969

@@ -97,8 +97,9 @@ class CScreencopyProtocol : public IWaylandProtocol {
9797

9898
void onOutputCommit(PHLMONITOR pMonitor);
9999

100-
private:
101100
std::vector<SP<CScreencopyFrame>> m_frames;
101+
102+
private:
102103
std::vector<WP<CScreencopyFrame>> m_framesAwaitingWrite;
103104
std::vector<SP<CScreencopyClient>> m_clients;
104105

src/protocols/ToplevelExport.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ bool CToplevelExportClient::good() {
7474
return m_resource->resource();
7575
}
7676

77-
CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) {
77+
CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_window(pWindow_), m_resource(resource_) {
7878
if UNLIKELY (!good())
7979
return;
8080

@@ -411,6 +411,10 @@ void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) {
411411
}
412412

413413
void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) {
414+
// Damage the window before destroying the frame so the border updates
415+
if (frame && frame->m_window) {
416+
g_pHyprRenderer->damageWindow(frame->m_window);
417+
}
414418
std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; });
415419
std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; });
416420
}

src/protocols/ToplevelExport.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ class CToplevelExportClient {
2222
CTimer m_lastFrame;
2323
int m_frameCounter = 0;
2424

25+
bool m_sentScreencast = false;
26+
2527
private:
2628
SP<CHyprlandToplevelExportManagerV1> m_resource;
2729

2830
int m_framesInLastHalfSecond = 0;
2931
CTimer m_lastMeasure;
30-
bool m_sentScreencast = false;
3132

3233
SP<HOOK_CALLBACK_FN> m_tickCallback;
3334
void onTick();
@@ -45,11 +46,10 @@ class CToplevelExportFrame {
4546

4647
WP<CToplevelExportFrame> m_self;
4748
WP<CToplevelExportClient> m_client;
49+
PHLWINDOW m_window;
4850

4951
private:
5052
SP<CHyprlandToplevelExportFrameV1> m_resource;
51-
52-
PHLWINDOW m_window;
5353
bool m_cursorOverlayRequested = false;
5454
bool m_ignoreDamage = false;
5555

@@ -79,9 +79,10 @@ class CToplevelExportProtocol : IWaylandProtocol {
7979

8080
void onOutputCommit(PHLMONITOR pMonitor);
8181

82+
std::vector<SP<CToplevelExportFrame>> m_frames;
83+
8284
private:
8385
std::vector<SP<CToplevelExportClient>> m_clients;
84-
std::vector<SP<CToplevelExportFrame>> m_frames;
8586
std::vector<WP<CToplevelExportFrame>> m_framesAwaitingWrite;
8687

8788
void onWindowUnmap(PHLWINDOW pWindow);

src/render/Renderer.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
#include "../protocols/types/ContentType.hpp"
4242
#include "../helpers/MiscFunctions.hpp"
4343
#include "render/OpenGL.hpp"
44+
#include "../protocols/Screencopy.hpp"
45+
#include "../config/ConfigDataValues.hpp"
46+
#include "../helpers/Color.hpp"
47+
#include "pass/BorderPassElement.hpp"
4448

4549
#include <hyprutils/utils/ScopeGuard.hpp>
4650
using namespace Hyprutils::Utils;
@@ -1457,6 +1461,46 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
14571461
m_renderPass.add(makeUnique<CRectPassElement>(data));
14581462
}
14591463

1464+
// Render border around monitor if it's being shared
1465+
static auto PENABLED = CConfigValue<Hyprlang::INT>("general:screencast_border.enabled");
1466+
if (*PENABLED) {
1467+
// Check if monitor is being shared through screencopy
1468+
bool isMonitorShared = false;
1469+
if (PROTO::screencopy) {
1470+
for (const auto& frame : PROTO::screencopy->m_frames) {
1471+
if (frame && frame->m_monitor && frame->m_monitor.lock() == pMonitor) {
1472+
isMonitorShared = frame->m_client->m_sentScreencast;
1473+
break;
1474+
}
1475+
}
1476+
}
1477+
1478+
if (isMonitorShared) {
1479+
static auto PBORDERSIZE = CConfigValue<Hyprlang::INT>("general:border_size");
1480+
static auto PCOLOR = CConfigValue<Hyprlang::CUSTOMTYPE>("general:screencast_border.color");
1481+
const int borderSize = *PBORDERSIZE > 0 ? *PBORDERSIZE : 1;
1482+
1483+
const int scaledBorderSize = std::round(borderSize * pMonitor->m_scale);
1484+
CBox monitorBox = {scaledBorderSize, scaledBorderSize, std::max(1, sc<int>(pMonitor->m_transformedSize.x) - (2 * scaledBorderSize)),
1485+
std::max(1, sc<int>(pMonitor->m_transformedSize.y) - (2 * scaledBorderSize))};
1486+
1487+
const auto* const PGRAD = sc<CGradientValueData*>((PCOLOR.ptr())->getData());
1488+
CGradientValueData borderGrad = PGRAD ? *PGRAD : CGradientValueData(Colors::RED); // fallback to red if config not loaded
1489+
1490+
CBorderPassElement::SBorderData borderData;
1491+
borderData.box = monitorBox;
1492+
borderData.grad1 = borderGrad;
1493+
borderData.round = 0;
1494+
borderData.outerRound = -1;
1495+
borderData.roundingPower = 2.0f;
1496+
borderData.a = 1.0f;
1497+
borderData.borderSize = borderSize;
1498+
borderData.hasGrad2 = false;
1499+
1500+
m_renderPass.add(makeUnique<CBorderPassElement>(borderData));
1501+
}
1502+
}
1503+
14601504
EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT);
14611505

14621506
endRender();

src/render/decorations/CHyprBorderDecoration.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include "../pass/BorderPassElement.hpp"
66
#include "../Renderer.hpp"
77
#include "../../managers/HookSystemManager.hpp"
8+
#include "../../protocols/Screencopy.hpp"
9+
#include "../../protocols/ToplevelExport.hpp"
10+
#include "../../helpers/Color.hpp"
11+
#include "../../config/ConfigDataValues.hpp"
812

913
CHyprBorderDecoration::CHyprBorderDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) {
1014
;
@@ -60,6 +64,27 @@ void CHyprBorderDecoration::draw(PHLMONITOR pMonitor, float const& a) {
6064
auto grad = m_window->m_realBorderColor;
6165
const bool ANIMATED = m_window->m_borderFadeAnimationProgress->isBeingAnimated();
6266

67+
static auto PENABLED = CConfigValue<Hyprlang::INT>("general:screencast_border.enabled");
68+
static auto PCOLOR = CConfigValue<Hyprlang::CUSTOMTYPE>("general:screencast_border.color");
69+
70+
if (*PENABLED) {
71+
// Check if window is being shared through toplevel export
72+
bool isWindowShared = false;
73+
if (PROTO::toplevelExport) {
74+
for (const auto& frame : PROTO::toplevelExport->m_frames) {
75+
if (frame && frame->m_window && frame->m_window == m_window) {
76+
isWindowShared = frame->m_client->m_sentScreencast;
77+
break;
78+
}
79+
}
80+
}
81+
82+
if (isWindowShared) {
83+
const auto* const PGRAD = sc<CGradientValueData*>((PCOLOR.ptr())->getData());
84+
grad = PGRAD ? *PGRAD : CGradientValueData(Colors::RED); // fallback to red if config not loaded
85+
}
86+
}
87+
6388
if (m_window->m_borderAngleAnimationProgress->enabled()) {
6489
grad.m_angle += m_window->m_borderAngleAnimationProgress->value() * M_PI * 2;
6590
grad.m_angle = normalizeAngleRad(grad.m_angle);

0 commit comments

Comments
 (0)