diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 02b05df..d5d9ae8 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -5,7 +5,7 @@ pkgdesc="Qt-based Linux port of Safe Exam Browser" arch=('x86_64') url="https://github.com/example/safe-exam-browser-linux" license=('MPL2') -depends=('qt6-base' 'qt6-webengine' 'zlib' 'hicolor-icon-theme' 'shared-mime-info' 'desktop-file-utils') +depends=('qt6-base' 'qt6-webengine' 'zlib' 'hicolor-icon-theme' 'shared-mime-info' 'desktop-file-utils' 'polkit') makedepends=('qt6-tools' 'gcc' 'make') source=("safe-exam-browser-${pkgver}.tar.gz") sha256sums=('SKIP') diff --git a/scripts/build-release.sh b/scripts/build-release.sh index c4a58a0..4d61f59 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -26,7 +26,7 @@ Section: education Priority: optional Architecture: amd64 Maintainer: SEB Linux contributors -Depends: libqt6core6, libqt6gui6, libqt6network6, libqt6webenginecore6, libqt6webenginewidgets6, shared-mime-info +Depends: libqt6core6, libqt6gui6, libqt6network6, libqt6webenginecore6, libqt6webenginewidgets6, shared-mime-info, pkexec Description: Safe Exam Browser Linux Qt port Qt-based Safe Exam Browser launcher for Linux with .seb file and sebs:// support. EOF diff --git a/src/main.cpp b/src/main.cpp index f7a3d21..a28cfb8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,9 +10,108 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace { +int g_tty0_fd = -1; +int g_new_tty_fd = -1; +int g_original_vt = -1; + +void cleanup_vt_and_exit() { + if (g_new_tty_fd >= 0) { + ioctl(g_new_tty_fd, KDSETMODE, KD_TEXT); + } + if (g_tty0_fd >= 0) { + int target_vt = (g_original_vt > 0) ? g_original_vt : 1; + ioctl(g_tty0_fd, VT_ACTIVATE, target_vt); + ioctl(g_tty0_fd, VT_WAITACTIVE, target_vt); + } + if (g_new_tty_fd >= 0) close(g_new_tty_fd); + if (g_tty0_fd >= 0) close(g_tty0_fd); +} + +void barebones_sig_handler(int signum) { + cleanup_vt_and_exit(); + if (signum == SIGSEGV || signum == SIGABRT || signum == SIGFPE || signum == SIGILL) { + signal(signum, SIG_DFL); + raise(signum); + } else { + _exit(1); + } +} + +bool setup_barebones_vt() { + signal(SIGINT, barebones_sig_handler); + signal(SIGTERM, barebones_sig_handler); + signal(SIGSEGV, barebones_sig_handler); + signal(SIGABRT, barebones_sig_handler); + signal(SIGILL, barebones_sig_handler); + signal(SIGFPE, barebones_sig_handler); + + g_tty0_fd = open("/dev/tty0", O_RDWR); + if (g_tty0_fd < 0) { + qWarning() << "Could not open /dev/tty0. Did you run the app as root?"; + return false; + } + + int free_vt = -1; + if (ioctl(g_tty0_fd, VT_OPENQRY, &free_vt) < 0 || free_vt == -1) { + qWarning() << "Could not find a free VT."; + close(g_tty0_fd); + g_tty0_fd = -1; + return false; + } + + char vt_name[20]; + snprintf(vt_name, sizeof(vt_name), "/dev/tty%d", free_vt); + g_new_tty_fd = open(vt_name, O_RDWR); + if (g_new_tty_fd < 0) { + qWarning() << "Could not open VT" << vt_name; + close(g_tty0_fd); + g_tty0_fd = -1; + return false; + } + + struct vt_stat vts; + if (ioctl(g_tty0_fd, VT_GETSTATE, &vts) == 0) { + g_original_vt = vts.v_active; + } + + if (ioctl(g_tty0_fd, VT_ACTIVATE, free_vt) < 0 || + ioctl(g_tty0_fd, VT_WAITACTIVE, free_vt) < 0) { + qWarning() << "Could not switch to VT" << free_vt; + close(g_new_tty_fd); + g_new_tty_fd = -1; + close(g_tty0_fd); + g_tty0_fd = -1; + return false; + } + + if (ioctl(g_new_tty_fd, KDSETMODE, KD_GRAPHICS) < 0) { + qWarning() << "Error setting graphics mode on VT" << free_vt; + int target_vt = (g_original_vt > 0) ? g_original_vt : 1; + ioctl(g_tty0_fd, VT_ACTIVATE, target_vt); + ioctl(g_tty0_fd, VT_WAITACTIVE, target_vt); + close(g_new_tty_fd); + g_new_tty_fd = -1; + close(g_tty0_fd); + g_tty0_fd = -1; + return false; + } + + atexit(cleanup_vt_and_exit); + return true; +} + QString findConfigPath(int argc, char *argv[]) { for (int index = 1; index < argc; ++index) { @@ -37,6 +136,18 @@ void applyEarlyEnvironment(int argc, char *argv[]) settings = loaded.settings; } } + for (int i = 0; i < argc; ++i) { // we have to use a loop because the command line parser was not yet loaded. + if (QString::fromLocal8Bit(argv[i]) == QStringLiteral("--anti-cheat")) { + if (!setup_barebones_vt()) { + qCritical() << "Anti-cheat VT setup failed; aborting."; + _exit(1); + } + qputenv("QT_QPA_PLATFORM", "linuxfb"); + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox"); + qputenv("QT_QUICK_BACKEND", "software"); + break; + } + } seb::browser::applyWebEngineEnvironment(settings); } @@ -73,7 +184,9 @@ void applyCommandLineOverrides(const QCommandLineParser &parser, seb::SebSetting if (parser.isSet("windowed")) { settings.browser.mainWindow.fullScreenMode = false; } - + if (parser.isSet("fullscreen")){ + settings.browser.mainWindow.fullScreenMode = true; + } if (parser.isSet("always-on-top")) { settings.browser.mainWindow.alwaysOnTop = true; } @@ -94,7 +207,8 @@ int main(int argc, char *argv[]) applyEarlyEnvironment(argc, argv); QApplication app(argc, argv); - const QIcon appIcon(QStringLiteral(":/assets/icons/safe-exam-browser.png")); + + const QIcon appIcon(QStringLiteral(":/assets/icons/safe-exam-browser.png")); app.setWindowIcon(appIcon); app.setDesktopFileName(QStringLiteral("safe-exam-browser")); QCoreApplication::setApplicationName(QStringLiteral("Safe Exam Browser")); @@ -127,6 +241,7 @@ int main(int argc, char *argv[]) QStringLiteral("allow-devtools"), QStringLiteral("Enable the developer tools shortcut (F12)."))); parser.addOption(QCommandLineOption(QStringLiteral("windowed"), QStringLiteral("Force the main window to stay windowed."))); + parser.addOption(QCommandLineOption(QStringLiteral("fullscreen"), QStringLiteral("Force the main window to be fullscreen."))); parser.addOption(QCommandLineOption(QStringLiteral("always-on-top"), QStringLiteral("Keep the main window above other windows."))); parser.addOption(QCommandLineOption( QStringLiteral("disable-minimize"), @@ -134,6 +249,9 @@ int main(int argc, char *argv[]) parser.addOption(QCommandLineOption( QStringLiteral("disable-quit"), QStringLiteral("Disable manual termination even if the configuration allows it."))); + parser.addOption(QCommandLineOption( + QStringLiteral("anti-cheat"), + QStringLiteral("Enable anticheat mode."))); parser.process(app); @@ -144,9 +262,18 @@ int main(int argc, char *argv[]) ? parser.value("config") : (parser.positionalArguments().isEmpty() ? QString() : parser.positionalArguments().constFirst()); + QString userPassword; + bool usedPassword = false; + QStringList warnings; if (!resource.isEmpty()) { - const seb::ResourceLoadResult loaded = seb::loadSettingsFromResource(resource, [](bool hashed) { + const seb::ResourceLoadResult loaded = seb::loadSettingsFromResource(resource, [&userPassword, &usedPassword](bool hashed) { + if (qEnvironmentVariableIsSet("SEB_PASSWORD")) { + userPassword = QString::fromUtf8(qgetenv("SEB_PASSWORD")); + usedPassword = true; + return userPassword; + } + bool accepted = false; const QString password = QInputDialog::getText( nullptr, @@ -157,6 +284,10 @@ int main(int argc, char *argv[]) QLineEdit::Password, QString(), &accepted); + if (accepted) { + userPassword = password; + usedPassword = true; + } return accepted ? password : QString(); }); if (!loaded.ok) { @@ -170,6 +301,35 @@ int main(int argc, char *argv[]) applyCommandLineOverrides(parser, settings); + if (!parser.isSet("anti-cheat")) { + if (settings.browser.mainWindow.fullScreenMode && settings.browser.mainWindow.alwaysOnTop) { + QStringList args = QCoreApplication::arguments(); + args.removeFirst(); // Remove executable path + if (!args.contains(QStringLiteral("--anti-cheat"))) { + args.prepend(QStringLiteral("--anti-cheat")); + } + + QProcess child; + child.setProgram(QStringLiteral("pkexec")); + + QStringList pkexecArgs; + pkexecArgs << QStringLiteral("--keep-cwd"); + if (usedPassword) { + pkexecArgs << QStringLiteral("env"); + pkexecArgs << (QStringLiteral("SEB_PASSWORD=") + userPassword); + } + pkexecArgs << QCoreApplication::applicationFilePath(); + pkexecArgs << args; + + child.setArguments(pkexecArgs); + + if (child.startDetached()) { + return 0; + } + return 1; + } + } + AppController controller; QString launchError; if (!controller.launchResolved(settings, warnings, &launchError)) {