#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 #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; }; // Login dialog for authentication class LoginDialog : public QDialog { Q_OBJECT public: LoginDialog(QWidget *parent = nullptr) : QDialog(parent) { setWindowTitle("SCAR Chat - Login"); setModal(true); setGeometry(100, 100, 400, 300); QVBoxLayout *layout = new QVBoxLayout(this); // Title QLabel *title = new QLabel("SCAR Chat", this); title->setStyleSheet("font-size: 24px; font-weight: bold;"); title->setAlignment(Qt::AlignCenter); layout->addWidget(title); layout->addSpacing(20); // Server address QHBoxLayout *server_layout = new QHBoxLayout(); QLabel *server_label = new QLabel("Server:", this); server_input = new QLineEdit("localhost", this); server_layout->addWidget(server_label); server_layout->addWidget(server_input); layout->addLayout(server_layout); // Server port QHBoxLayout *port_layout = new QHBoxLayout(); QLabel *port_label = new QLabel("Port:", this); port_input = new QLineEdit("42317", this); port_input->setMaximumWidth(100); port_layout->addWidget(port_label); port_layout->addWidget(port_input); port_layout->addStretch(); layout->addLayout(port_layout); layout->addSpacing(15); // Username QHBoxLayout *user_layout = new QHBoxLayout(); QLabel *user_label = new QLabel("Username:", this); username_input = new QLineEdit(this); user_layout->addWidget(user_label); user_layout->addWidget(username_input); layout->addLayout(user_layout); // Password QHBoxLayout *pass_layout = new QHBoxLayout(); QLabel *pass_label = new QLabel("Password:", this); password_input = new QLineEdit(this); password_input->setEchoMode(QLineEdit::Password); pass_layout->addWidget(pass_label); pass_layout->addWidget(password_input); layout->addLayout(pass_layout); layout->addSpacing(15); // Status label status_label = new QLabel("", this); status_label->setAlignment(Qt::AlignCenter); status_label->setStyleSheet("color: #666;"); layout->addWidget(status_label); // Buttons QHBoxLayout *btn_layout = new QHBoxLayout(); login_btn = new QPushButton("Login", this); cancel_btn = new QPushButton("Cancel", this); btn_layout->addStretch(); btn_layout->addWidget(login_btn); btn_layout->addWidget(cancel_btn); layout->addLayout(btn_layout); connect(login_btn, &QPushButton::clicked, this, &LoginDialog::accept); connect(cancel_btn, &QPushButton::clicked, this, &LoginDialog::reject); connect(password_input, &QLineEdit::returnPressed, this, &LoginDialog::accept); } QString get_server() const { return server_input->text(); } int get_port() const { return port_input->text().toInt(); } QString get_username() const { return username_input->text(); } QString get_password() const { return password_input->text(); } void set_status(const QString &msg) { status_label->setText(msg); } void set_login_enabled(bool enabled) { login_btn->setEnabled(enabled); } private: QLineEdit *server_input; QLineEdit *port_input; QLineEdit *username_input; QLineEdit *password_input; QLabel *status_label; QPushButton *login_btn; QPushButton *cancel_btn; }; class ChatClient : public QMainWindow { Q_OBJECT public: ChatClient(QWidget *parent = nullptr) : QMainWindow(parent), m_camera(nullptr), m_authenticated(false) { setWindowTitle("SCAR Chat Client"); setGeometry(100, 100, 1000, 700); // Create central widget with tabs QWidget *central = new QWidget(this); setCentralWidget(central); QVBoxLayout *main_layout = new QVBoxLayout(central); // Status bar showing logged-in user status_label = new QLabel("Not authenticated", this); status_label->setStyleSheet("font-weight: bold;"); main_layout->addWidget(status_label); // 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(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."); // Show login dialog show_login_dialog(); } ~ChatClient() { if (m_camera) { m_camera->stop(); delete m_camera; } } private slots: void show_login_dialog() { LoginDialog dialog(this); if (dialog.exec() == QDialog::Accepted) { QString server = dialog.get_server(); int port = dialog.get_port(); QString username = dialog.get_username(); QString password = dialog.get_password(); if (server.isEmpty() || username.isEmpty() || password.isEmpty()) { QMessageBox::warning(this, "Error", "Please fill in all fields"); show_login_dialog(); return; } chat_display->append("[System] Connecting to " + server + ":" + QString::number(port) + "..."); m_current_username = username; m_current_password = password; m_login_dialog_server = server; m_login_dialog_port = port; socket->connectToHostEncrypted(server, port); } else { // User cancelled login QApplication::quit(); } } 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_socket_connected() { chat_display->append("[System] Connected to server!"); // Send LOGIN command QString login_cmd = "LOGIN:" + m_current_username + ":" + m_current_password; socket->write(login_cmd.toUtf8() + "\n"); message_input->setFocus(); } void on_socket_disconnected() { chat_display->append("[System] Disconnected from server"); if (camera_toggle->isChecked()) { camera_toggle->setChecked(false); } stop_camera(); if (m_authenticated) { QMessageBox::information(this, "Disconnected", "You have been disconnected from the server"); show_login_dialog(); } } void on_socket_read() { while (socket->canReadLine()) { QString message = QString::fromUtf8(socket->readLine()).trimmed(); // Handle authentication response if (message.startsWith("LOGIN_SUCCESS:")) { QString username = message.mid(14); m_authenticated = true; status_label->setText("Logged in as: " + username); chat_display->append("[System] Successfully logged in as " + username); } else if (message.startsWith("LOGIN_FAILED:")) { QString reason = message.mid(13); m_authenticated = false; chat_display->append("[System] Login failed: " + reason); socket->disconnectFromHost(); QMessageBox::critical(this, "Login Failed", "Login failed: " + reason); show_login_dialog(); } else if (message.startsWith("ERROR:")) { QString error = message.mid(6); chat_display->append("[Error] " + error); } else 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 { // Regular chat message 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 QLabel *status_label; 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; // Authentication bool m_authenticated; QString m_current_username; QString m_current_password; QString m_login_dialog_server; int m_login_dialog_port; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); ChatClient client; client.show(); return app.exec(); } #include "main.moc"