#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class VideoFeedWidget : public QWidget { Q_OBJECT public: VideoFeedWidget(const QString &username, QWidget *parent = nullptr) : QWidget(parent), m_username(username) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(5, 5, 5, 5); // Title QLabel *title = new QLabel(m_username + " (Offline)", this); title->setStyleSheet("font-weight: bold; font-size: 12px;"); layout->addWidget(title); // Video placeholder m_video_label = new QLabel(this); m_video_label->setMinimumSize(320, 240); m_video_label->setStyleSheet("border: 2px solid gray; background-color: black;"); m_video_label->setAlignment(Qt::AlignCenter); m_video_label->setText("Waiting for video..."); layout->addWidget(m_video_label); setStyleSheet("border: 1px solid gray; padding: 5px;"); } void update_video_frame(const QPixmap &pixmap) { m_video_label->setPixmap(pixmap.scaled(320, 240, Qt::KeepAspectRatio)); } void set_status(bool online) { QLabel *title = findChild(); if (title) { title->setText(m_username + (online ? " (Online)" : " (Offline)")); } } private: QString m_username; QLabel *m_video_label; }; class ChatClient : public QMainWindow { Q_OBJECT public: ChatClient(QWidget *parent = nullptr) : QMainWindow(parent), m_camera(nullptr) { setWindowTitle("SCAR Chat Client - Video Edition"); setGeometry(100, 100, 1000, 700); // Create central widget with tabs QWidget *central = new QWidget(this); setCentralWidget(central); QVBoxLayout *main_layout = new QVBoxLayout(central); // Connection section QHBoxLayout *conn_layout = new QHBoxLayout(); QLabel *host_label = new QLabel("Host:", this); host_input = new QLineEdit("localhost", this); QLabel *port_label = new QLabel("Port:", this); port_input = new QLineEdit("42317", this); connect_btn = new QPushButton("Connect", this); conn_layout->addWidget(host_label); conn_layout->addWidget(host_input); conn_layout->addWidget(port_label); conn_layout->addWidget(port_input); conn_layout->addWidget(connect_btn); main_layout->addLayout(conn_layout); // Tab widget for chat and video QTabWidget *tabs = new QTabWidget(this); // ==================== CHAT TAB ==================== QWidget *chat_tab = new QWidget(this); QVBoxLayout *chat_layout = new QVBoxLayout(chat_tab); // Chat display chat_display = new QTextEdit(this); chat_display->setReadOnly(true); chat_layout->addWidget(chat_display); // Message input QHBoxLayout *msg_layout = new QHBoxLayout(); message_input = new QLineEdit(this); send_btn = new QPushButton("Send", this); msg_layout->addWidget(message_input); msg_layout->addWidget(send_btn); chat_layout->addLayout(msg_layout); // Chat settings QHBoxLayout *chat_settings = new QHBoxLayout(); bg_color_btn = new QPushButton("BG Color", this); text_color_btn = new QPushButton("Text Color", this); QLabel *trans_label = new QLabel("Transparency:", this); transparency_slider = new QSlider(Qt::Horizontal, this); transparency_slider->setMinimum(0); transparency_slider->setMaximum(100); transparency_slider->setValue(100); transparency_value = new QLabel("100%", this); chat_settings->addWidget(bg_color_btn); chat_settings->addWidget(text_color_btn); chat_settings->addWidget(trans_label); chat_settings->addWidget(transparency_slider); chat_settings->addWidget(transparency_value); chat_layout->addLayout(chat_settings); tabs->addTab(chat_tab, "Chat"); // ==================== VIDEO TAB ==================== QWidget *video_tab = new QWidget(this); QVBoxLayout *video_layout = new QVBoxLayout(video_tab); // Camera controls QHBoxLayout *camera_ctrl = new QHBoxLayout(); camera_combo = new QComboBox(this); camera_toggle = new QCheckBox("Enable Camera", this); camera_toggle->setChecked(false); camera_info_label = new QLabel("No camera selected", this); camera_ctrl->addWidget(new QLabel("Camera:", this)); camera_ctrl->addWidget(camera_combo); camera_ctrl->addWidget(camera_toggle); camera_ctrl->addWidget(camera_info_label); camera_ctrl->addStretch(); video_layout->addLayout(camera_ctrl); // Local camera view QLabel *local_title = new QLabel("Your Camera Feed:", this); local_title->setStyleSheet("font-weight: bold;"); video_layout->addWidget(local_title); local_video_label = new QLabel(this); local_video_label->setMinimumSize(640, 480); local_video_label->setMaximumSize(640, 480); local_video_label->setStyleSheet("border: 2px solid gray; background-color: black;"); local_video_label->setAlignment(Qt::AlignCenter); local_video_label->setText("Camera Disabled\nClick 'Enable Camera' to start"); video_layout->addWidget(local_video_label, 0, Qt::AlignHCenter); // Remote video feeds QLabel *remote_title = new QLabel("Connected Users:", this); remote_title->setStyleSheet("font-weight: bold; margin-top: 20px;"); video_layout->addWidget(remote_title); // Scroll area for remote feeds QScrollArea *scroll = new QScrollArea(this); scroll->setWidgetResizable(true); QWidget *scroll_content = new QWidget(this); remote_feeds_layout = new QGridLayout(scroll_content); scroll->setWidget(scroll_content); video_layout->addWidget(scroll); tabs->addTab(video_tab, "Video Feeds"); main_layout->addWidget(tabs); // Connect signals connect(connect_btn, &QPushButton::clicked, this, &ChatClient::on_connect); connect(send_btn, &QPushButton::clicked, this, &ChatClient::on_send_message); connect(message_input, &QLineEdit::returnPressed, this, &ChatClient::on_send_message); connect(bg_color_btn, &QPushButton::clicked, this, &ChatClient::on_bg_color); connect(text_color_btn, &QPushButton::clicked, this, &ChatClient::on_text_color); connect(transparency_slider, &QSlider::valueChanged, this, &ChatClient::on_transparency_changed); connect(camera_combo, QOverload::of(&QComboBox::currentIndexChanged), this, &ChatClient::on_camera_changed); connect(camera_toggle, &QCheckBox::stateChanged, this, &ChatClient::on_camera_toggle); // Initialize socket socket = new QSslSocket(this); connect(socket, &QSslSocket::connected, this, &ChatClient::on_socket_connected); connect(socket, &QSslSocket::disconnected, this, &ChatClient::on_socket_disconnected); connect(socket, &QSslSocket::readyRead, this, &ChatClient::on_socket_read); connect(socket, QOverload::of(&QSslSocket::errorOccurred), this, &ChatClient::on_socket_error); connect(socket, QOverload &>::of(&QSslSocket::sslErrors), this, &ChatClient::on_ssl_errors); // Initialize colors bg_color = Qt::white; text_color = Qt::black; update_window_colors(); // Populate camera list populate_cameras(); chat_display->append("[System] SCAR Chat Client started."); chat_display->append("[Info] Camera detection active."); chat_display->append("[Info] Select a camera and enable it to start video capture."); } ~ChatClient() { if (m_camera) { m_camera->stop(); delete m_camera; } } private slots: void populate_cameras() { camera_combo->clear(); const QList cameras = QCameraInfo::availableCameras(); if (cameras.isEmpty()) { camera_combo->addItem("No camera detected"); camera_toggle->setEnabled(false); camera_info_label->setText("No cameras found on this system"); chat_display->append("[Warning] No cameras detected on system"); } else { for (int i = 0; i < cameras.size(); ++i) { const QCameraInfo &camera = cameras.at(i); QString device_name = camera.deviceName(); QString description = camera.description(); QString label = description.isEmpty() ? ("Camera " + QString::number(i)) : description; camera_combo->addItem(label, device_name); } camera_toggle->setEnabled(true); if (!cameras.isEmpty()) { camera_info_label->setText("Available cameras: " + QString::number(cameras.size())); chat_display->append("[System] Found " + QString::number(cameras.size()) + " camera(s)"); for (int i = 0; i < cameras.size(); ++i) { chat_display->append(" [" + QString::number(i + 1) + "] " + cameras.at(i).description()); } } } } void on_camera_changed(int index) { if (index < 0) return; if (m_camera && camera_toggle->isChecked()) { m_camera->stop(); } QString device_name = camera_combo->currentData().toString(); if (device_name.isEmpty()) { camera_info_label->setText("Invalid camera selection"); return; } QCameraInfo camera_info(device_name.toLatin1()); if (m_camera) { delete m_camera; } m_camera = new QCamera(camera_info, this); QString cam_desc = camera_info.description(); if (cam_desc.isEmpty()) { cam_desc = camera_combo->currentText(); } camera_info_label->setText("Selected: " + cam_desc); chat_display->append("[System] Camera changed to: " + cam_desc); if (camera_toggle->isChecked()) { start_camera(); } } void on_camera_toggle(int state) { if (state == Qt::Checked) { start_camera(); } else { stop_camera(); } } void start_camera() { if (!m_camera) { chat_display->append("[Error] No camera selected"); camera_toggle->setChecked(false); return; } try { m_camera->start(); chat_display->append("[System] Camera enabled: " + camera_info_label->text()); local_video_label->setText("Camera Active\n(Video capture enabled)"); local_video_label->setStyleSheet("border: 2px solid green; background-color: #1a1a1a; color: white;"); if (socket->state() == QSslSocket::ConnectedState) { socket->write("CAMERA_ENABLE\n"); } } catch (const std::exception &e) { chat_display->append("[Error] Failed to start camera: " + QString(e.what())); camera_toggle->setChecked(false); } } void stop_camera() { if (m_camera) { m_camera->stop(); } chat_display->append("[System] Camera disabled"); local_video_label->setText("Camera Disabled\nClick 'Enable Camera' to start"); local_video_label->setStyleSheet("border: 2px solid gray; background-color: black; color: white;"); if (socket->state() == QSslSocket::ConnectedState) { socket->write("CAMERA_DISABLE\n"); } } void on_connect() { if (socket->state() == QSslSocket::ConnectedState) { socket->disconnectFromHost(); connect_btn->setText("Connect"); return; } QString host = host_input->text(); int port = port_input->text().toInt(); chat_display->append("[System] Connecting to " + host + ":" + QString::number(port) + "..."); socket->connectToHostEncrypted(host, port); } void on_socket_connected() { chat_display->append("[System] Connected to server!"); connect_btn->setText("Disconnect"); message_input->setFocus(); } void on_socket_disconnected() { chat_display->append("[System] Disconnected from server"); connect_btn->setText("Connect"); if (camera_toggle->isChecked()) { camera_toggle->setChecked(false); } stop_camera(); } void on_socket_read() { while (socket->canReadLine()) { QString message = QString::fromUtf8(socket->readLine()).trimmed(); if (message.startsWith("USER_CAMERA_ON:")) { QString username = message.mid(15); add_remote_user(username, true); chat_display->append("[System] " + username + " enabled their camera"); } else if (message.startsWith("USER_CAMERA_OFF:")) { QString username = message.mid(16); add_remote_user(username, false); chat_display->append("[System] " + username + " disabled their camera"); } else if (message.startsWith("VIDEO_FRAME:")) { // Placeholder for future video frame implementation } else { chat_display->append("[Server] " + message); } } } void on_socket_error(QAbstractSocket::SocketError error) { Q_UNUSED(error); chat_display->append("[Error] " + socket->errorString()); } void on_ssl_errors(const QList &errors) { for (const QSslError &error : errors) { std::cerr << "SSL Error: " << error.errorString().toStdString() << std::endl; } socket->ignoreSslErrors(); } void on_send_message() { if (socket->state() != QSslSocket::ConnectedState) { chat_display->append("[System] Not connected!"); return; } QString message = message_input->text(); if (message.isEmpty()) return; socket->write(message.toUtf8() + "\n"); chat_display->append("[You] " + message); message_input->clear(); } void on_bg_color() { QColor color = QColorDialog::getColor(bg_color, this, "Select Background Color"); if (color.isValid()) { bg_color = color; update_window_colors(); } } void on_text_color() { QColor color = QColorDialog::getColor(text_color, this, "Select Text Color"); if (color.isValid()) { text_color = color; update_window_colors(); } } void on_transparency_changed(int value) { transparency_value->setText(QString::number(value) + "%"); double opacity = value / 100.0; setWindowOpacity(opacity); } void add_remote_user(const QString &username, bool camera_active) { if (remote_users.find(username) == remote_users.end()) { VideoFeedWidget *widget = new VideoFeedWidget(username, this); remote_users[username] = widget; remote_feeds_layout->addWidget(widget); } remote_users[username]->set_status(camera_active); } private: void update_window_colors() { QString stylesheet = QString( "QTextEdit { background-color: %1; color: %2; }" "QLineEdit { background-color: %1; color: %2; }" "QPushButton { background-color: %1; color: %2; border: 1px solid gray; padding: 4px; }" "QLabel { color: %2; }" "QSlider { color: %2; }" "QComboBox { background-color: %1; color: %2; }" "QCheckBox { color: %2; }" ).arg(bg_color.name()).arg(text_color.name()); setStyleSheet(stylesheet); } private: // Chat UI QLineEdit *host_input; QLineEdit *port_input; QPushButton *connect_btn; QTextEdit *chat_display; QLineEdit *message_input; QPushButton *send_btn; QPushButton *bg_color_btn; QPushButton *text_color_btn; QSlider *transparency_slider; QLabel *transparency_value; // Video UI QComboBox *camera_combo; QCheckBox *camera_toggle; QLabel *camera_info_label; QLabel *local_video_label; QGridLayout *remote_feeds_layout; std::map remote_users; // Camera QCamera *m_camera; // Connection QSslSocket *socket; QColor bg_color; QColor text_color; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); ChatClient client; client.show(); return app.exec(); } #include "main.moc"