#include "video_grid_widget.h" #include "../media/screen_capture.h" #include "../connection/client_connection.h" #include "../../shared/protocol/message.h" #include #include #include #include #include namespace scar { VideoGridWidget::VideoGridWidget(QWidget* parent) : QWidget(parent), isScreenSharing_(false), screenShareStartSent_(false), connection_(nullptr) { gridLayout_ = new QGridLayout(this); gridLayout_->setSpacing(5); gridLayout_->setContentsMargins(5, 5, 5, 5); // Placeholder when no streams placeholderLabel_ = new QLabel("No active video streams", this); placeholderLabel_->setAlignment(Qt::AlignCenter); placeholderLabel_->setStyleSheet("color: #888; font-size: 16px;"); gridLayout_->addWidget(placeholderLabel_, 0, 0); // Create screen share button (floating above video streams) screenShareButton_ = new QPushButton("Share Screen", this); screenShareButton_->setStyleSheet( "QPushButton {" " background-color: #5865F2;" " color: white;" " border: none;" " border-radius: 8px;" " padding: 12px 24px;" " font-size: 14px;" " font-weight: bold;" "}" "QPushButton:hover {" " background-color: #4752C4;" "}" "QPushButton:pressed {" " background-color: #3C45A5;" "}" "QPushButton:disabled {" " background-color: #4F545C;" " color: #72767D;" "}" ); screenShareButton_->setCursor(Qt::PointingHandCursor); screenShareButton_->setFixedSize(160, 48); screenShareButton_->raise(); // Ensure button is above other widgets connect(screenShareButton_, &QPushButton::clicked, this, &VideoGridWidget::onScreenShareClicked); // Initialize screen capture screenCapture_ = new ScreenCapture(); updateScreenShareButtonPosition(); } VideoGridWidget::~VideoGridWidget() { delete screenCapture_; } void VideoGridWidget::setConnection(ClientConnection* connection) { connection_ = connection; // Set up frame callback for screen capture if (screenCapture_) { screenCapture_->setFrameCallback([this](const std::vector& frameData, int width, int height) { if (!connection_) { return; } try { // Send ScreenShareStart with actual dimensions on first frame if (!screenShareStartSent_) { ScreenShareStart startMsg(width, height); auto startSerialized = startMsg.serialize(); connection_->sendData(startSerialized); std::cout << "Sent ScreenShareStart with actual dimensions: " << width << "x" << height << std::endl; screenShareStartSent_ = true; } static int callback_count = 0; if (++callback_count % 30 == 0) { std::cout << "Sending screen share frame: " << callback_count << " Size: " << frameData.size() << " bytes" << " Resolution: " << width << "x" << height << std::endl; } // frameData is already H.264 encoded by ScreenCapture // Send it directly to server ScreenShareData frameMsg(frameData); auto serialized = frameMsg.serialize(); connection_->sendData(serialized); } catch (const std::exception& e) { std::cerr << "Error in frame callback: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown error in frame callback" << std::endl; } }); } } void VideoGridWidget::addStream(const QString& streamId, const QString& username) { if (streams_.size() >= MAX_STREAMS) { return; // Max streams reached } // Remove placeholder if this is first stream if (streams_.empty()) { placeholderLabel_->hide(); } VideoStream stream; stream.streamId = streamId; stream.username = username; // Create video label stream.videoLabel = new QLabel(this); stream.videoLabel->setMinimumSize(160, 120); stream.videoLabel->setStyleSheet("QLabel { background-color: #2C2F33; border: 1px solid #23272A; }"); stream.videoLabel->setScaledContents(true); stream.videoLabel->setAlignment(Qt::AlignCenter); stream.videoLabel->setText(username + "\n(No video)"); stream.videoLabel->setToolTip(username); streams_.push_back(stream); updateGridLayout(); } void VideoGridWidget::removeStream(const QString& streamId) { for (auto it = streams_.begin(); it != streams_.end(); ++it) { if (it->streamId == streamId) { delete it->videoLabel; streams_.erase(it); break; } } if (streams_.empty()) { placeholderLabel_->show(); } else { updateGridLayout(); } } void VideoGridWidget::updateFrame(const QString& streamId, const QPixmap& frame) { for (auto& stream : streams_) { if (stream.streamId == streamId) { stream.videoLabel->setPixmap(frame); break; } } } void VideoGridWidget::clear() { for (auto& stream : streams_) { delete stream.videoLabel; } streams_.clear(); placeholderLabel_->show(); } void VideoGridWidget::updateGridLayout() { // Remove all widgets from layout for (auto& stream : streams_) { gridLayout_->removeWidget(stream.videoLabel); } int columns = calculateColumns(streams_.size()); int rows = std::ceil(static_cast(streams_.size()) / columns); // Re-add widgets in grid for (size_t i = 0; i < streams_.size(); ++i) { int row = i / columns; int col = i % columns; gridLayout_->addWidget(streams_[i].videoLabel, row, col); } } int VideoGridWidget::calculateColumns(int streamCount) { if (streamCount <= 1) return 1; if (streamCount <= 4) return 2; if (streamCount <= 9) return 3; if (streamCount <= 16) return 4; if (streamCount <= 25) return 5; if (streamCount <= 36) return 6; return std::ceil(std::sqrt(streamCount)); } void VideoGridWidget::onScreenShareClicked() { if (isScreenSharing_) { // Stop screen sharing screenCapture_->stop(); isScreenSharing_ = false; screenShareButton_->setText("Share Screen"); screenShareButton_->setStyleSheet( "QPushButton {" " background-color: #5865F2;" " color: white;" " border: none;" " border-radius: 8px;" " padding: 12px 24px;" " font-size: 14px;" " font-weight: bold;" "}" "QPushButton:hover {" " background-color: #4752C4;" "}" "QPushButton:pressed {" " background-color: #3C45A5;" "}" ); screenShareStartSent_ = false; // Send stop message if (connection_) { ScreenShareStop stopMsg; auto serialized = stopMsg.serialize(); connection_->sendData(serialized); qDebug() << "Sent screen share stop message"; } } else { // Start screen sharing - this will open xdg-desktop-portal dialog if (screenCapture_->start()) { isScreenSharing_ = true; screenShareButton_->setText("Stop Sharing"); screenShareButton_->setStyleSheet( "QPushButton {" " background-color: #ED4245;" " color: white;" " border: none;" " border-radius: 8px;" " padding: 12px 24px;" " font-size: 14px;" " font-weight: bold;" "}" "QPushButton:hover {" " background-color: #C03537;" "}" "QPushButton:pressed {" " background-color: #A12D2F;" "}" ); // ScreenShareStart will be sent with actual dimensions when first frame arrives emit screenShareRequested(); } } } void VideoGridWidget::onScreenShareFrameReceived(std::vector rgbData, int width, int height) { std::cout << "=== onScreenShareFrameReceived START: size=" << rgbData.size() << ", " << width << "x" << height << " ===" << std::endl; if (rgbData.empty()) { std::cout << "Empty rgbData, returning" << std::endl; return; } static int frame_count = 0; if (++frame_count % 30 == 0) { std::cout << "Received screen share frame " << frame_count << " (" << width << "x" << height << ", " << rgbData.size() << " bytes)" << std::endl; } // Calculate linesize (must match decoder's alignment) int linesize = width * 3; linesize = (linesize + 3) & ~3; // Create QImage and copy data directly (avoid non-owning constructor) QImage frameCopy(width, height, QImage::Format_RGB888); if (frameCopy.isNull()) { std::cerr << "Failed to create QImage" << std::endl; return; } // Manually copy each scanline to handle potential linesize differences for (int y = 0; y < height; ++y) { const uint8_t* src_line = rgbData.data() + (y * linesize); uint8_t* dst_line = frameCopy.scanLine(y); memcpy(dst_line, src_line, width * 3); } // Create a stream if this is the first frame from a remote screen share // Use a special stream ID for remote screen shares const QString remoteStreamId = "remote_screen_share"; // Find existing stream or create new one auto it = std::find_if(streams_.begin(), streams_.end(), [&remoteStreamId](const VideoStream& s) { return s.streamId == remoteStreamId; }); if (it == streams_.end()) { // Create new stream for remote screen share addStream(remoteStreamId, "Screen Share"); it = std::find_if(streams_.begin(), streams_.end(), [&remoteStreamId](const VideoStream& s) { return s.streamId == remoteStreamId; }); } if (it != streams_.end() && it->videoLabel) { // Convert QImage to QPixmap and scale to fit the label QPixmap pixmap = QPixmap::fromImage(frameCopy); QPixmap scaled = pixmap.scaled(it->videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); it->videoLabel->setPixmap(scaled); } } void VideoGridWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateScreenShareButtonPosition(); } void VideoGridWidget::updateScreenShareButtonPosition() { // Position button at bottom center of the widget int buttonWidth = screenShareButton_->width(); int buttonHeight = screenShareButton_->height(); int x = (width() - buttonWidth) / 2; int y = height() - buttonHeight - 20; // 20px margin from bottom screenShareButton_->move(x, y); } } // namespace scar