diff --git a/scripts/build.sh b/scripts/build.sh index accc374..876c9be 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -23,7 +23,156 @@ fi echo "Configuring build with: qmake6 ../seb-linux-qt.pro ${QMAKE_ARGS[*]}" qmake6 ../seb-linux-qt.pro "${QMAKE_ARGS[@]}" printf '%s\n' "${QMAKE_ARGS_FINGERPRINT}" > "${BUILD_CONFIG_STAMP}" -make -j"$(nproc)" + +# --- Interactive Progress Bar Logic --- + +is_tty=0 +if [[ -t 1 ]]; then + is_tty=1 +fi + +# Temporarily disable exit-on-error and pipefail to handle grep exiting with 1 on no matches +set +eo pipefail +total_steps=$(make -n | grep -E '\-o\s+' | grep -Ei '(g\+\+|clang\+\+|moc|rcc|uic)' | wc -l) +set -eo pipefail + +if [[ "${total_steps}" -eq 0 ]]; then + total_steps=1 +fi + +nproc=$(nproc 2>/dev/null || echo 1) +if [[ "${is_tty}" -eq 1 ]]; then + echo "Starting parallel build with ${nproc} jobs..." +else + echo "Starting non-interactive build with ${nproc} jobs (CI/redirect detected)..." +fi + +extract_filename() { + local line="$1" + local parts=($line) + local part + for (( i=${#parts[@]}-1; i>=0; i-- )); do + part="${parts[i]}" + part="${part//[\"\']/}" + if [[ "${part}" =~ \.(cpp|cc|c|h|qrc)$ ]]; then + echo "${part##*/}" + return + fi + done + + for (( i=0; i<${#parts[@]}; i++ )); do + if [[ "${parts[i]}" == "-o" && $((i+1)) -lt ${#parts[@]} ]]; then + local outfile="${parts[i+1]}" + echo "${outfile##*/}" + return + fi + done +} + +last_percent=-1 + +print_progress() { + local current=$1 + local total=$2 + local file=$3 + + local percent=$(( current * 100 / total )) + if [[ "${percent}" -gt 100 ]]; then + percent=100 + fi + + if [[ "${is_tty}" -eq 1 ]]; then + local bar_length=30 + local filled_length=$(( bar_length * current / total )) + if [[ "${filled_length}" -gt "${bar_length}" ]]; then + filled_length="${bar_length}" + fi + + local bar="" + local i + for ((i=0; i/dev/null || echo 80) + local action="Building..." + if [[ -n "${file}" ]]; then + action="Compiling: ${file}" + fi + + local status_line="[${bar}] ${percent}% (${current}/${total}) ${action}" + local max_len=$(( columns - 4 )) + if [[ "${#status_line}" -gt "${max_len}" ]]; then + status_line="${status_line:0:max_len}..." + fi + + printf "\r%s\e[K" "${status_line}" + else + if [[ "${percent}" -ge $(( last_percent + 10 )) || "${current}" -eq "${total}" || "${current}" -eq 1 ]]; then + last_percent=$(( percent / 10 * 10 )) + local action="Building..." + if [[ -n "${file}" ]]; then + action="Compiling: ${file}" + fi + printf "[%3d%%] (%d/%d) %s\n" "${percent}" "${current}" "${total}" "${action}" + fi + fi +} + +log_file=$(mktemp) +trap 'rm -f "${log_file}"' EXIT + +completed_steps=0 +make_exit_code=0 + +# Temporarily disable exit-on-error to capture and handle make errors manually +set +e +while read -r line; do + if [[ "${line}" == "EXIT_STATUS:"* ]]; then + make_exit_code="${line#EXIT_STATUS:}" + continue + fi + + echo "${line}" >> "${log_file}" + + line_lower=$(echo "${line}" | tr '[:upper:]' '[:lower:]') + if [[ "${line}" == *"-o "* ]] && ( [[ "${line_lower}" == *"g++"* ]] || [[ "${line_lower}" == *"clang++"* ]] || [[ "${line_lower}" == *"moc"* ]] || [[ "${line_lower}" == *"rcc"* ]] || [[ "${line_lower}" == *"uic"* ]] ); then + completed_steps=$(( completed_steps + 1 )) + if [[ "${completed_steps}" -gt "${total_steps}" ]]; then + total_steps="${completed_steps}" + fi + filename=$(extract_filename "${line}") + print_progress "${completed_steps}" "${total_steps}" "${filename}" + fi +done < <(make -j"${nproc}" 2>&1; echo "EXIT_STATUS:$?") +set -e + +if [[ "${make_exit_code}" -ne 0 ]]; then + echo -e "\n\nBuild failed! Compiler Output:" + echo "============================================================" + cat "${log_file}" + echo "============================================================" + exit "${make_exit_code}" +else + print_progress "${total_steps}" "${total_steps}" "Finished successfully!" + echo -e "\n\nBuild succeeded!" +fi + popd >/dev/null -echo "Build output: ${BUILD_DIR}/bin/safe-exam-browser" +target_name="safe-exam-browser" +for arg in "${QMAKE_ARGS[@]}"; do + if [[ "${arg}" == *"dev_bypass"* ]]; then + target_name="safe-exam-browser_dev" + break + fi +done + +echo "Build output: ${BUILD_DIR}/bin/${target_name}" diff --git a/scripts/dependencies.sh b/scripts/dependencies.sh index 8edf098..6011f93 100755 --- a/scripts/dependencies.sh +++ b/scripts/dependencies.sh @@ -1,37 +1,55 @@ -#!/bin/bash +#!/usr/bin/env bash +set -euo pipefail -if command -v apt >/dev/null; then - echo "installing dependencies" +if command -v apt-get >/dev/null; then + echo "Detected Debian/Ubuntu-based system, installing dependencies..." sudo apt-get update sudo apt-get install -y \ - build-essential \ - desktop-file-utils \ - dpkg-dev \ - libqt6svg6-dev \ - qt6-base-dev \ - qt6-tools-dev-tools \ - qt6-webengine-dev \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - shared-mime-info \ - zlib1g-dev \ - libssl-dev \ - file \ - libatomic1 \ - libdeflate0 \ - libjbig0 \ - liblerc4 \ - libngtcp2-dev \ - libngtcp2-crypto-gnutls-dev \ - libqt6pdf6 \ - libqt6qmlworkerscript6 \ - libnss3 \ - libssh2-1 \ - libssl3 \ - libtiff-dev \ - libxcb-cursor0 \ - libxcb-xinput0 \ - libjpeg62 + build-essential \ + desktop-file-utils \ + dpkg-dev \ + libqt6svg6-dev \ + qt6-base-dev \ + qt6-tools-dev-tools \ + qt6-webengine-dev \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + shared-mime-info \ + zlib1g-dev \ + libssl-dev \ + file \ + libatomic1 \ + libdeflate0 \ + libjbig0 \ + liblerc4 \ + libngtcp2-dev \ + libngtcp2-crypto-gnutls-dev \ + libqt6pdf6 \ + libqt6qmlworkerscript6 \ + libnss3 \ + libssh2-1 \ + libssl3 \ + libtiff-dev \ + libxcb-cursor0 \ + libxcb-xinput0 \ + libjpeg62 +elif command -v pacman >/dev/null; then + echo "Detected Arch Linux-based system, installing dependencies..." + sudo pacman -Syu --needed --noconfirm \ + gcc \ + make \ + pkgconf \ + qt6-base \ + qt6-webengine \ + webkit2gtk-4.1 \ + gtk3 \ + zlib \ + hicolor-icon-theme \ + shared-mime-info \ + desktop-file-utils \ + polkit \ + qt6-tools else - echo "detected non-debian system, skipping dependency install" + echo "Detected unsupported package manager. Please manually install the Qt6 and WebEngine dependencies." + exit 1 fi diff --git a/seb-linux-qt.pro b/seb-linux-qt.pro index 9a80797..648ca5a 100644 --- a/seb-linux-qt.pro +++ b/seb-linux-qt.pro @@ -1,4 +1,4 @@ -QT += core gui widgets network xml +QT += core gui widgets network xml dbus # Safe Exam Browser for Linux: Browser Engine Detection # We support Qt WebEngine (primary) and WebKitGTK (fallback for RISC-V/Older systems). @@ -64,17 +64,18 @@ DEFINES += SEB_HAS_QTWEBENGINE=$$seb_has_qtwebengine DEFINES += SEB_HAS_WEBKITGTK=$$seb_has_webkitgtk DEFINES += SEB_HAS_ANY_ENGINE=$$seb_has_any_engine +CONFIG += c++20 console warn_on object_parallel_to_source +TEMPLATE = app +TARGET = safe-exam-browser + # Dev Bypass Build Option # Usage: qmake CONFIG+=dev_bypass dev_bypass { DEFINES += SEB_DEV_BYPASS_DEFAULT=1 + TARGET = safe-exam-browser_dev message("Building with PERSISTENT developer bypass enabled.") } -CONFIG += c++20 console warn_on object_parallel_to_source -TEMPLATE = app -TARGET = safe-exam-browser - QMAKE_CXXFLAGS += -Wall -Wextra -Wpedantic INCLUDEPATH += src diff --git a/src/shell/taskbar/platform/audio_controller.cpp b/src/shell/taskbar/platform/audio_controller.cpp index 806b194..d8ac49c 100644 --- a/src/shell/taskbar/platform/audio_controller.cpp +++ b/src/shell/taskbar/platform/audio_controller.cpp @@ -1,7 +1,5 @@ #include "audio_controller.h" -#include "command_helper.h" - #include #include @@ -23,17 +21,33 @@ const AudioState &AudioController::state() const } void AudioController::refresh() +{ + if (getVolumeProcess_ && getVolumeProcess_->state() != QProcess::NotRunning) { + return; + } + + if (!getVolumeProcess_) { + getVolumeProcess_ = new QProcess(this); + connect(getVolumeProcess_, &QProcess::finished, this, &AudioController::onGetVolumeFinished); + } + + getVolumeProcess_->start(QStringLiteral("wpctl"), {QStringLiteral("get-volume"), QStringLiteral("@DEFAULT_AUDIO_SINK@")}); +} + +void AudioController::onGetVolumeFinished(int exitCode, QProcess::ExitStatus exitStatus) { AudioState next; - const QString output = runCommand(QStringLiteral("wpctl"), {QStringLiteral("get-volume"), QStringLiteral("@DEFAULT_AUDIO_SINK@")}); - if (!output.isEmpty()) { + if (exitStatus == QProcess::NormalExit && exitCode == 0) { next.available = true; + const QString output = QString::fromUtf8(getVolumeProcess_->readAllStandardOutput()).trimmed(); next.muted = output.contains(QStringLiteral("[MUTED]"), Qt::CaseInsensitive); const QRegularExpression regex(QStringLiteral("([0-9]+(?:\\.[0-9]+)?)")); const auto match = regex.match(output); if (match.hasMatch()) { next.volumePercent = qBound(0, qRound(match.captured(1).toDouble() * 100.0), 100); } + } else { + next.available = false; } if (next.available != state_.available || next.muted != state_.muted || next.volumePercent != state_.volumePercent) { @@ -47,8 +61,13 @@ void AudioController::setMuted(bool muted) if (!state_.available) { return; } - runCommand(QStringLiteral("wpctl"), {QStringLiteral("set-mute"), QStringLiteral("@DEFAULT_AUDIO_SINK@"), muted ? QStringLiteral("1") : QStringLiteral("0")}); - refresh(); + + QProcess::startDetached(QStringLiteral("wpctl"), {QStringLiteral("set-mute"), QStringLiteral("@DEFAULT_AUDIO_SINK@"), muted ? QStringLiteral("1") : QStringLiteral("0")}); + + if (state_.muted != muted) { + state_.muted = muted; + emit stateChanged(); + } } void AudioController::setVolume(int volumePercent) @@ -57,8 +76,13 @@ void AudioController::setVolume(int volumePercent) return; } const int bounded = qBound(0, volumePercent, 100); - runCommand(QStringLiteral("wpctl"), {QStringLiteral("set-volume"), QStringLiteral("@DEFAULT_AUDIO_SINK@"), QStringLiteral("%1%").arg(bounded)}); - refresh(); + + QProcess::startDetached(QStringLiteral("wpctl"), {QStringLiteral("set-volume"), QStringLiteral("@DEFAULT_AUDIO_SINK@"), QStringLiteral("%1%").arg(bounded)}); + + if (state_.volumePercent != bounded) { + state_.volumePercent = bounded; + emit stateChanged(); + } } } // namespace seb::shell::taskbar::platform diff --git a/src/shell/taskbar/platform/audio_controller.h b/src/shell/taskbar/platform/audio_controller.h index e3f0578..3d2594d 100644 --- a/src/shell/taskbar/platform/audio_controller.h +++ b/src/shell/taskbar/platform/audio_controller.h @@ -1,6 +1,7 @@ #pragma once #include +#include QT_BEGIN_NAMESPACE class QTimer; @@ -30,9 +31,13 @@ class AudioController : public QObject signals: void stateChanged(); +private slots: + void onGetVolumeFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: AudioState state_; QTimer *timer_ = nullptr; + QProcess *getVolumeProcess_ = nullptr; }; } // namespace seb::shell::taskbar::platform diff --git a/src/shell/taskbar/platform/battery_controller.cpp b/src/shell/taskbar/platform/battery_controller.cpp index 884e6c6..e7e27d1 100644 --- a/src/shell/taskbar/platform/battery_controller.cpp +++ b/src/shell/taskbar/platform/battery_controller.cpp @@ -1,19 +1,23 @@ #include "battery_controller.h" -#include "command_helper.h" - -#include -#include +#include +#include +#include namespace seb::shell::taskbar::platform { BatteryController::BatteryController(QObject *parent) : QObject(parent) { - timer_ = new QTimer(this); - timer_->setInterval(10000); - connect(timer_, &QTimer::timeout, this, &BatteryController::refresh); - timer_->start(); + QDBusConnection::systemBus().connect( + QStringLiteral("org.freedesktop.UPower"), + QStringLiteral("/org/freedesktop/UPower/devices/DisplayDevice"), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("PropertiesChanged"), + this, + SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)) + ); + refresh(); } @@ -24,24 +28,57 @@ const BatteryState &BatteryController::state() const void BatteryController::refresh() { + QDBusInterface iface( + QStringLiteral("org.freedesktop.UPower"), + QStringLiteral("/org/freedesktop/UPower/devices/DisplayDevice"), + QStringLiteral("org.freedesktop.UPower.Device"), + QDBusConnection::systemBus() + ); + + if (!iface.isValid()) { + BatteryState next; + next.available = false; + if (next.available != state_.available) { + state_ = next; + emit stateChanged(); + } + return; + } + BatteryState next; - const QString output = runCommand(QStringLiteral("upower"), {QStringLiteral("-i"), QStringLiteral("/org/freedesktop/UPower/devices/DisplayDevice")}); - if (!output.isEmpty()) { - next.available = true; - const QStringList lines = output.split('\n'); - for (const QString &line : lines) { - const QString trimmed = line.trimmed(); - if (trimmed.startsWith(QStringLiteral("percentage:"))) { - const QRegularExpression re(QStringLiteral("([0-9]+)%")); - const auto match = re.match(trimmed); - if (match.hasMatch()) { - next.percentage = match.captured(1).toInt(); - } - } else if (trimmed.startsWith(QStringLiteral("state:"))) { - next.charging = trimmed.contains(QStringLiteral("charging")); - } else if (trimmed.startsWith(QStringLiteral("time to empty:")) || trimmed.startsWith(QStringLiteral("time to full:"))) { - next.timeRemaining = trimmed.section(':', 1).trimmed(); + next.available = true; + + double percentageVal = iface.property("Percentage").toDouble(); + uint stateVal = iface.property("State").toUInt(); + qlonglong timeToFull = iface.property("TimeToFull").toLongLong(); + qlonglong timeToEmpty = iface.property("TimeToEmpty").toLongLong(); + + next.percentage = qBound(0, qRound(percentageVal), 100); + next.charging = (stateVal == 1); // 1 = Charging in UPower + + if (stateVal == 0) { + next.available = false; + } + + qlonglong seconds = 0; + if (stateVal == 1) { + seconds = timeToFull; + } else if (stateVal == 2) { // 2 = Discharging + seconds = timeToEmpty; + } + + // Limit to < 24h to ignore UPower calculation anomalies + if (seconds > 0 && seconds < 86400) { + int hours = seconds / 3600; + int minutes = (seconds % 3600) / 60; + if (hours > 0) { + if (minutes > 0) { + next.timeRemaining = QStringLiteral("%1h %2m").arg(hours).arg(minutes); + } else { + next.timeRemaining = QStringLiteral("%1h").arg(hours); } + } else if (minutes > 0) { + next.timeRemaining = QStringLiteral("%1m").arg(minutes); } } @@ -54,4 +91,12 @@ void BatteryController::refresh() } } +void BatteryController::onPropertiesChanged(const QString &interfaceName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) +{ + Q_UNUSED(interfaceName); + Q_UNUSED(changedProperties); + Q_UNUSED(invalidatedProperties); + refresh(); +} + } // namespace seb::shell::taskbar::platform diff --git a/src/shell/taskbar/platform/battery_controller.h b/src/shell/taskbar/platform/battery_controller.h index d01c4a1..ed7503f 100644 --- a/src/shell/taskbar/platform/battery_controller.h +++ b/src/shell/taskbar/platform/battery_controller.h @@ -2,10 +2,8 @@ #include #include - -QT_BEGIN_NAMESPACE -class QTimer; -QT_END_NAMESPACE +#include +#include namespace seb::shell::taskbar::platform { @@ -30,9 +28,11 @@ class BatteryController : public QObject signals: void stateChanged(); +private slots: + void onPropertiesChanged(const QString &interfaceName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties); + private: BatteryState state_; - QTimer *timer_ = nullptr; }; } // namespace seb::shell::taskbar::platform diff --git a/src/shell/taskbar/platform/network_controller.cpp b/src/shell/taskbar/platform/network_controller.cpp index db2a9b6..e5f6293 100644 --- a/src/shell/taskbar/platform/network_controller.cpp +++ b/src/shell/taskbar/platform/network_controller.cpp @@ -1,7 +1,5 @@ #include "network_controller.h" -#include "command_helper.h" - #include namespace seb::shell::taskbar::platform { @@ -23,11 +21,33 @@ const NetworkState &NetworkController::state() const void NetworkController::refresh() { - NetworkState next; - const QString deviceOutput = runCommand( + if ((deviceProcess_ && deviceProcess_->state() != QProcess::NotRunning) || + (wifiProcess_ && wifiProcess_->state() != QProcess::NotRunning)) { + return; + } + + pendingState_ = NetworkState(); + + if (!deviceProcess_) { + deviceProcess_ = new QProcess(this); + connect(deviceProcess_, &QProcess::finished, this, &NetworkController::onDeviceQueryFinished); + } + + deviceProcess_->start( QStringLiteral("nmcli"), - {QStringLiteral("-t"), QStringLiteral("-f"), QStringLiteral("DEVICE,TYPE,STATE,CONNECTION"), QStringLiteral("device")}); + {QStringLiteral("-t"), QStringLiteral("-f"), QStringLiteral("DEVICE,TYPE,STATE,CONNECTION"), QStringLiteral("device")} + ); +} +void NetworkController::onDeviceQueryFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus != QProcess::NormalExit || exitCode != 0) { + pendingState_.available = false; + finalizeState(); + return; + } + + const QString deviceOutput = QString::fromUtf8(deviceProcess_->readAllStandardOutput()).trimmed(); const QStringList deviceLines = deviceOutput.split('\n', Qt::SkipEmptyParts); for (const QString &line : deviceLines) { const QStringList parts = line.split(':'); @@ -39,29 +59,44 @@ void NetworkController::refresh() const QString connection = parts.mid(3).join(QStringLiteral(":")); if (type == QStringLiteral("wifi")) { - next.available = true; - next.type = NetworkState::Type::Wireless; - next.activeConnection = connection; + pendingState_.available = true; + pendingState_.type = NetworkState::Type::Wireless; + pendingState_.activeConnection = connection; if (state == QStringLiteral("connected")) { - next.status = NetworkState::Status::Connected; + pendingState_.status = NetworkState::Status::Connected; } else if (state.contains(QStringLiteral("connecting"))) { - next.status = NetworkState::Status::Connecting; + pendingState_.status = NetworkState::Status::Connecting; } break; } - if (type == QStringLiteral("ethernet") && next.type == NetworkState::Type::None) { - next.available = true; - next.type = NetworkState::Type::Wired; - next.activeConnection = connection; - next.status = state == QStringLiteral("connected") ? NetworkState::Status::Connected : NetworkState::Status::Disconnected; + if (type == QStringLiteral("ethernet") && pendingState_.type == NetworkState::Type::None) { + pendingState_.available = true; + pendingState_.type = NetworkState::Type::Wired; + pendingState_.activeConnection = connection; + pendingState_.status = state == QStringLiteral("connected") ? NetworkState::Status::Connected : NetworkState::Status::Disconnected; } } - if (next.type == NetworkState::Type::Wireless) { - const QString wifiOutput = runCommand( + if (pendingState_.type == NetworkState::Type::Wireless) { + if (!wifiProcess_) { + wifiProcess_ = new QProcess(this); + connect(wifiProcess_, &QProcess::finished, this, &NetworkController::onWifiListQueryFinished); + } + wifiProcess_->start( QStringLiteral("nmcli"), - {QStringLiteral("-t"), QStringLiteral("-f"), QStringLiteral("ACTIVE,SSID,SIGNAL"), QStringLiteral("device"), QStringLiteral("wifi"), QStringLiteral("list")}); + {QStringLiteral("-t"), QStringLiteral("-f"), QStringLiteral("ACTIVE,SSID,SIGNAL"), QStringLiteral("device"), QStringLiteral("wifi"), QStringLiteral("list")} + ); + } else { + finalizeState(); + } +} + +void NetworkController::onWifiListQueryFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + const QString wifiOutput = QString::fromUtf8(wifiProcess_->readAllStandardOutput()).trimmed(); const QStringList wifiLines = wifiOutput.split('\n', Qt::SkipEmptyParts); + QStringList seenSsids; for (const QString &line : wifiLines) { const QStringList parts = line.split(':'); if (parts.size() < 3) { @@ -71,23 +106,60 @@ void NetworkController::refresh() network.active = parts.at(0) == QStringLiteral("yes"); network.ssid = parts.at(1); network.signalPercent = parts.at(2).toInt(); - if (!network.ssid.isEmpty()) { - next.networks.push_back(network); + + if (network.ssid.isEmpty()) { + continue; + } + + int index = seenSsids.indexOf(network.ssid); + if (index != -1) { + WirelessNetwork &existing = pendingState_.networks[index]; + if (network.active) { + existing.active = true; + } + if (network.signalPercent > existing.signalPercent) { + existing.signalPercent = network.signalPercent; + } + } else { + seenSsids.append(network.ssid); + pendingState_.networks.push_back(network); } } } + finalizeState(); +} + +void NetworkController::finalizeState() +{ const bool changed = - next.available != state_.available || - next.type != state_.type || - next.status != state_.status || - next.activeConnection != state_.activeConnection || - next.networks != state_.networks; + pendingState_.available != state_.available || + pendingState_.type != state_.type || + pendingState_.status != state_.status || + pendingState_.activeConnection != state_.activeConnection || + pendingState_.networks != state_.networks; if (changed) { - state_ = next; + state_ = pendingState_; emit stateChanged(); } } +void NetworkController::connectToNetwork(const QString &ssid) +{ + if (ssid.isEmpty()) { + return; + } + + QProcess::startDetached(QStringLiteral("nmcli"), {QStringLiteral("device"), QStringLiteral("wifi"), QStringLiteral("connect"), ssid}); + + if (state_.activeConnection != ssid || state_.status != NetworkState::Status::Connecting) { + state_.activeConnection = ssid; + state_.status = NetworkState::Status::Connecting; + emit stateChanged(); + } + + QTimer::singleShot(2000, this, &NetworkController::refresh); +} + } // namespace seb::shell::taskbar::platform diff --git a/src/shell/taskbar/platform/network_controller.h b/src/shell/taskbar/platform/network_controller.h index 90e8741..45c2263 100644 --- a/src/shell/taskbar/platform/network_controller.h +++ b/src/shell/taskbar/platform/network_controller.h @@ -3,6 +3,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE class QTimer; @@ -43,13 +44,23 @@ class NetworkController : public QObject const NetworkState &state() const; void refresh(); + void connectToNetwork(const QString &ssid); signals: void stateChanged(); +private slots: + void onDeviceQueryFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onWifiListQueryFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: + void finalizeState(); + NetworkState state_; + NetworkState pendingState_; QTimer *timer_ = nullptr; + QProcess *deviceProcess_ = nullptr; + QProcess *wifiProcess_ = nullptr; }; } // namespace seb::shell::taskbar::platform diff --git a/src/ui/taskbar/controls/network_control.cpp b/src/ui/taskbar/controls/network_control.cpp index 5ecedc0..b8b2df5 100644 --- a/src/ui/taskbar/controls/network_control.cpp +++ b/src/ui/taskbar/controls/network_control.cpp @@ -89,7 +89,11 @@ void NetworkControl::rebuildPopup() .arg(network.active ? QStringLiteral("•") : QStringLiteral(" ")) .arg(network.ssid) .arg(network.signalPercent)); - connect(row, &QPushButton::clicked, popup_, &QWidget::hide); + QString ssid = network.ssid; + connect(row, &QPushButton::clicked, this, [this, ssid] { + controller_.connectToNetwork(ssid); + popup_->hide(); + }); popupLayout_->addWidget(row); } }