Skip to content

Commit d3dd405

Browse files
Add screencast-only fullscreen mode for group call screenshare
Introduce a dedicated fullscreen toggle in wide controls for screenshare. When a screen endpoint is focused, fullscreen can now switch to a screencast-only layout that hides side panels. Fullscreen controls in this mode auto-hide on inactivity and reappear on mouse movement. Also disable speaking outline rendering in fullscreen to avoid full-frame green highlighting tied to members panel speaking state.
1 parent dc6b9dd commit d3dd405

7 files changed

Lines changed: 180 additions & 29 deletions

File tree

Telegram/SourceFiles/calls/calls.style

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ groupCallScreenShareSmall: CallButton(groupCallSettingsSmall) {
10081008
rippleAreaPosition: point(8px, 12px);
10091009
}
10101010
}
1011+
10111012
groupCallMenuToggleSmall: CallButton(groupCallSettingsSmall) {
10121013
button: IconButton(groupCallSettingsInner) {
10131014
icon: icon {{ "calls/calls_more", groupCallIconFg }};

Telegram/SourceFiles/calls/group/calls_group_panel.cpp

Lines changed: 143 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,11 @@ Panel::Panel(not_null<GroupCall*> call, ConferencePanelMigration info)
254254
result.setAlphaF(kControlsBackgroundOpacity);
255255
return result;
256256
})
257-
, _hideControlsTimer([=] { toggleWideControls(false); }) {
257+
, _hideControlsTimer([=] {
258+
if (_rtmpFull || fullscreenForScreencastOnly()) {
259+
toggleWideControls(false);
260+
}
261+
}) {
258262
_viewport->widget()->hide();
259263
if (!_viewport->requireARGB32()) {
260264
_call->setNotRequireARGB32();
@@ -387,28 +391,48 @@ void Panel::initWindow() {
387391
subscribeToPeerChanges();
388392
}
389393

394+
const auto wasFullscreen = std::make_shared<bool>(
395+
(window()->windowState() & Qt::WindowFullScreen) != 0);
390396
const auto updateFullScreen = [=] {
391397
const auto state = window()->windowState();
392-
const auto full = (state & Qt::WindowFullScreen)
393-
|| (state & Qt::WindowMaximized);
398+
const auto fullscreen = ((state & Qt::WindowFullScreen) != 0);
399+
const auto maximized = ((state & Qt::WindowMaximized) != 0);
400+
const auto full = (fullscreen || maximized);
401+
if (!fullscreen) {
402+
_screencastOnlyInFullscreen = false;
403+
} else if (!*wasFullscreen && fullscreenForScreencast()) {
404+
_screencastOnlyInFullscreen = true;
405+
}
406+
*wasFullscreen = fullscreen;
394407
_rtmpFull = _call->rtmp() && full;
395408
_fullScreenOrMaximized = full;
409+
if (_subtitle && fullscreenForScreencastOnly()) {
410+
_subtitle.destroy();
411+
}
412+
updateControlsGeometry();
413+
if (fullscreenForScreencastOnly()) {
414+
_hideControlsTimer.callOnce(kHideControlsTimeout);
415+
toggleWideControls(true);
416+
} else if (!_rtmpFull) {
417+
_hideControlsTimer.cancel();
418+
toggleWideControls(_viewport->widget()->underMouse());
419+
}
396420
};
397421
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
398422
const auto type = e->type();
399423
if (type == QEvent::Close && handleClose()) {
400424
e->ignore();
401425
return base::EventFilterResult::Cancel;
402-
} else if (_call->rtmp()
403-
&& (type == QEvent::KeyPress || type == QEvent::KeyRelease)) {
426+
} else if (type == QEvent::KeyPress || type == QEvent::KeyRelease) {
404427
const auto key = static_cast<QKeyEvent*>(e.get())->key();
405-
if (key == Qt::Key_Space) {
428+
if (_call->rtmp() && key == Qt::Key_Space) {
406429
_call->pushToTalk(
407430
e->type() == QEvent::KeyPress,
408431
kSpacePushToTalkDelay);
409-
} else if (key == Qt::Key_Escape
410-
&& _fullScreenOrMaximized.current()) {
411-
toggleFullScreen();
432+
} else if ((type == QEvent::KeyPress)
433+
&& (key == Qt::Key_Escape)
434+
&& window()->isFullScreen()) {
435+
toggleFullScreen(false);
412436
}
413437
} else if (type == QEvent::WindowStateChange) {
414438
updateFullScreen();
@@ -423,6 +447,12 @@ void Panel::initWindow() {
423447
if (!guard) {
424448
return (Flag::None | Flag(0));
425449
}
450+
if (fullscreenForScreencast()
451+
&& _viewport->widget()->geometry().contains(widgetPoint)) {
452+
return window()->isFullScreen()
453+
? (Flag::None | Flag(0))
454+
: (Flag::FullScreen | Flag(0));
455+
}
426456
const auto titleRect = QRect(
427457
0,
428458
0,
@@ -440,7 +470,11 @@ void Panel::initWindow() {
440470
}
441471
const auto shown = _window->topShownLayer();
442472
return (!shown || !shown->geometry().contains(widgetPoint))
443-
? (Flag::Move | Flag::Menu | Flag::Maximize)
473+
? (Flag::Move
474+
| Flag::Menu
475+
| (fullscreenForScreencast()
476+
? Flag::FullScreen
477+
: Flag::Maximize))
444478
: Flag::None;
445479
});
446480

@@ -450,7 +484,8 @@ void Panel::initWindow() {
450484
}, lifetime());
451485

452486
_window->maximizeRequests() | rpl::on_next([=](bool maximized) {
453-
if (_call->rtmp()) {
487+
if (_call->rtmp() || fullscreenForScreencast()) {
488+
_screencastOnlyInFullscreen = false;
454489
toggleFullScreen(maximized);
455490
} else {
456491
window()->setWindowState(maximized
@@ -681,6 +716,9 @@ void Panel::toggleFullScreen() {
681716
}
682717

683718
void Panel::toggleFullScreen(bool fullscreen) {
719+
if (!fullscreen) {
720+
_screencastOnlyInFullscreen = false;
721+
}
684722
if (fullscreen) {
685723
window()->showFullScreen();
686724
} else {
@@ -1027,7 +1065,28 @@ void Panel::setupMembers() {
10271065
) | rpl::filter([=] {
10281066
return !_rtmpFull;
10291067
}) | rpl::on_next([=](bool inside) {
1030-
toggleWideControls(inside);
1068+
if (fullscreenForScreencastOnly()) {
1069+
if (inside) {
1070+
_hideControlsTimer.callOnce(kHideControlsTimeout);
1071+
toggleWideControls(true);
1072+
} else {
1073+
_hideControlsTimer.cancel();
1074+
toggleWideControls(false);
1075+
}
1076+
} else {
1077+
_hideControlsTimer.cancel();
1078+
toggleWideControls(inside);
1079+
}
1080+
}, _viewport->lifetime());
1081+
1082+
_viewport->rp()->events(
1083+
) | rpl::filter([=](not_null<QEvent*> e) {
1084+
return (e->type() == QEvent::MouseMove);
1085+
}) | rpl::on_next([=] {
1086+
if (fullscreenForScreencastOnly()) {
1087+
_hideControlsTimer.callOnce(kHideControlsTimeout);
1088+
toggleWideControls(true);
1089+
}
10311090
}, _viewport->lifetime());
10321091

10331092
_members->show();
@@ -1080,6 +1139,7 @@ void Panel::setupMembers() {
10801139
enlargeVideo();
10811140
}
10821141
_viewport->showLarge(large);
1142+
updateControlsGeometry();
10831143
}, _callLifetime);
10841144
}
10851145

@@ -1261,6 +1321,34 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
12611321
}
12621322
}, viewport->lifetime());
12631323

1324+
if (viewport == _viewport.get()) {
1325+
viewport->doubleClicks(
1326+
) | rpl::on_next([=](const VideoEndpoint &endpoint) {
1327+
if (endpoint.type != VideoEndpointType::Screen) {
1328+
return;
1329+
}
1330+
if (_call->videoEndpointLarge() != endpoint) {
1331+
if (_call->videoEndpointPinned()) {
1332+
_call->pinVideoEndpoint(endpoint);
1333+
} else {
1334+
_call->showVideoEndpointLarge(endpoint);
1335+
}
1336+
}
1337+
if (window()->isFullScreen()) {
1338+
_screencastOnlyInFullscreen = !_screencastOnlyInFullscreen;
1339+
if (fullscreenForScreencastOnly()) {
1340+
_hideControlsTimer.callOnce(kHideControlsTimeout);
1341+
} else {
1342+
_hideControlsTimer.cancel();
1343+
}
1344+
updateControlsGeometry();
1345+
} else {
1346+
_screencastOnlyInFullscreen = true;
1347+
toggleFullScreen(true);
1348+
}
1349+
}, viewport->lifetime());
1350+
}
1351+
12641352
viewport->qualityRequests(
12651353
) | rpl::on_next([=](const VideoQualityRequest &request) {
12661354
_call->requestVideoQuality(request.endpoint, request.quality);
@@ -1284,7 +1372,8 @@ void Panel::updateWideControlsVisibility() {
12841372
if (_wideControlsShown == shown) {
12851373
return;
12861374
}
1287-
_viewport->setCursorShown(!_rtmpFull || shown);
1375+
_viewport->setCursorShown(
1376+
(!_rtmpFull && !fullscreenForScreencastOnly()) || shown);
12881377
_wideControlsShown = shown;
12891378
_wideControlsAnimation.start(
12901379
[=] { updateButtonsGeometry(); },
@@ -1889,9 +1978,9 @@ void Panel::updateButtonsStyles() {
18891978
(wide
18901979
? &st::groupCallMessageActiveSmall
18911980
: &st::groupCallMessageActive));
1892-
_message->setText(wide
1893-
? rpl::single(QString())
1894-
: tr::lng_group_call_message());
1981+
_message->setText(wide
1982+
? rpl::single(QString())
1983+
: tr::lng_group_call_message());
18951984
}
18961985
if (_settings) {
18971986
_settings->setText(wide
@@ -2144,6 +2233,9 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
21442233
}
21452234
});
21462235
toggleWideControls(true);
2236+
if (_rtmpFull || fullscreenForScreencastOnly()) {
2237+
_hideControlsTimer.cancel();
2238+
}
21472239
} else if (type == QEvent::Leave) {
21482240
*over = false;
21492241
crl::on_main(widget, [=] {
@@ -2152,6 +2244,9 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
21522244
}
21532245
});
21542246
toggleWideControls(false);
2247+
if (_rtmpFull || fullscreenForScreencastOnly()) {
2248+
_hideControlsTimer.callOnce(kHideControlsTimeout);
2249+
}
21552250
}
21562251
}, lifetime);
21572252
}
@@ -2468,13 +2563,16 @@ void Panel::updateButtonsGeometry() {
24682563
+ (_settings->width() + skip)
24692564
+ _hangup->width();
24702565
const auto membersSkip = st::groupCallNarrowSkip;
2471-
const auto membersWidth = rtmp
2566+
const auto screencastOnly = fullscreenForScreencastOnly();
2567+
const auto membersWidth = (rtmp || screencastOnly)
24722568
? membersSkip
24732569
: (st::groupCallNarrowMembersWidth + 2 * membersSkip);
2474-
auto left = membersSkip + (widget()->width()
2475-
- membersWidth
2476-
- membersSkip
2477-
- fullWidth) / 2;
2570+
auto left = screencastOnly
2571+
? (widget()->width() - fullWidth) / 2
2572+
: membersSkip + (widget()->width()
2573+
- membersWidth
2574+
- membersSkip
2575+
- fullWidth) / 2;
24782576

24792577
const auto forMessagesLeft = left
24802578
- st::groupCallControlsBackMargin.left();
@@ -2685,21 +2783,28 @@ void Panel::updateMembersGeometry() {
26852783
if (!_members) {
26862784
return;
26872785
}
2688-
_members->setVisible(!_call->rtmp());
2786+
_members->setVisible(!_call->rtmp() && !fullscreenForScreencastOnly());
26892787
const auto desiredHeight = _members->desiredHeight();
26902788
if (mode() == PanelMode::Wide) {
2691-
const auto skip = _rtmpFull ? 0 : st::groupCallNarrowSkip;
2692-
const auto membersWidth = st::groupCallNarrowMembersWidth;
2693-
const auto top = _rtmpFull ? 0 : st::groupCallWideVideoTop;
2789+
const auto screencastOnly = fullscreenForScreencastOnly();
2790+
const auto skip = (_rtmpFull || screencastOnly)
2791+
? 0
2792+
: st::groupCallNarrowSkip;
2793+
const auto membersWidth = screencastOnly
2794+
? 0
2795+
: st::groupCallNarrowMembersWidth;
2796+
const auto top = (_rtmpFull || screencastOnly)
2797+
? 0
2798+
: st::groupCallWideVideoTop;
26942799
_members->setGeometry(
26952800
widget()->width() - skip - membersWidth,
26962801
top,
26972802
membersWidth,
26982803
std::min(desiredHeight, widget()->height() - top - skip));
2699-
const auto viewportSkip = _call->rtmp()
2804+
const auto viewportSkip = (_call->rtmp() || screencastOnly)
27002805
? 0
27012806
: (skip + membersWidth);
2702-
_viewport->setGeometry(_rtmpFull, {
2807+
_viewport->setGeometry(_rtmpFull || screencastOnly, {
27032808
skip,
27042809
top,
27052810
widget()->width() - viewportSkip - 2 * skip,
@@ -2729,6 +2834,17 @@ void Panel::updateMembersGeometry() {
27292834
}
27302835
}
27312836

2837+
bool Panel::fullscreenForScreencast() const {
2838+
const auto &endpoint = _call->videoEndpointLarge();
2839+
return endpoint && (endpoint.type == VideoEndpointType::Screen);
2840+
}
2841+
2842+
bool Panel::fullscreenForScreencastOnly() const {
2843+
return _screencastOnlyInFullscreen
2844+
&& window()->isFullScreen()
2845+
&& fullscreenForScreencast();
2846+
}
2847+
27322848
rpl::producer<QString> Panel::titleText() {
27332849
if (_call->conference()) {
27342850
return tr::lng_confcall_join_title();

Telegram/SourceFiles/calls/group/calls_group_panel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ class Panel final
153153
void updateControlsGeometry();
154154
void updateButtonsGeometry();
155155
void updateTooltipGeometry();
156+
[[nodiscard]] bool fullscreenForScreencast() const;
157+
[[nodiscard]] bool fullscreenForScreencastOnly() const;
156158
void updateButtonsStyles();
157159
void updateMembersGeometry();
158160
void refreshControlsBackground();
@@ -210,6 +212,7 @@ class Panel final
210212
std::shared_ptr<Window> _window;
211213
rpl::variable<PanelMode> _mode;
212214
rpl::variable<bool> _fullScreenOrMaximized = false;
215+
bool _screencastOnlyInFullscreen = false;
213216
bool _unpinnedMaximized = false;
214217
bool _rtmpFull = false;
215218

Telegram/SourceFiles/calls/group/calls_group_viewport.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ void Viewport::setup() {
120120
handleMousePress(
121121
static_cast<QMouseEvent*>(e.get())->pos(),
122122
static_cast<QMouseEvent*>(e.get())->button());
123+
} else if (type == QEvent::MouseButtonDblClick) {
124+
handleMouseDoubleClick(
125+
static_cast<QMouseEvent*>(e.get())->pos(),
126+
static_cast<QMouseEvent*>(e.get())->button());
123127
} else if (type == QEvent::MouseButtonRelease) {
124128
handleMouseRelease(
125129
static_cast<QMouseEvent*>(e.get())->pos(),
@@ -202,10 +206,29 @@ void Viewport::handleMousePress(QPoint position, Qt::MouseButton button) {
202206
setPressed(_selected);
203207
}
204208

209+
void Viewport::handleMouseDoubleClick(
210+
QPoint position,
211+
Qt::MouseButton button) {
212+
handleMouseMove(position);
213+
if (button != Qt::LeftButton || videoStream()) {
214+
return;
215+
}
216+
const auto tile = _selected.tile;
217+
if (!tile) {
218+
return;
219+
}
220+
_skipNextMouseRelease = true;
221+
_doubleClicks.fire_copy(tile->endpoint());
222+
}
223+
205224
void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
206225
handleMouseMove(position);
207226
const auto pressed = _pressed;
208227
setPressed({});
228+
if (_skipNextMouseRelease && button == Qt::LeftButton) {
229+
_skipNextMouseRelease = false;
230+
return;
231+
}
209232
if (const auto tile = pressed.tile) {
210233
if (pressed == _selected) {
211234
if (videoStream()) {
@@ -919,6 +942,10 @@ rpl::producer<VideoEndpoint> Viewport::clicks() const {
919942
return _clicks.events();
920943
}
921944

945+
rpl::producer<VideoEndpoint> Viewport::doubleClicks() const {
946+
return _doubleClicks.events();
947+
}
948+
922949
rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
923950
return _qualityRequests.events();
924951
}

0 commit comments

Comments
 (0)