From f4005c7f74bf8b7df8e443a1d33f66d01a05303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Tue, 31 Mar 2015 09:30:19 +0200 Subject: [PATCH] [wayland] Add initial support for taking over VirtualTerminals A new Singleton VirtualTerminal is added. It interacts with Logind to get the VTNr to take over. To get the signal to release and acquire the VT we use a signalfd with a QSocketNotifier to monitor for signals. The used signals must be blocked for all threads prior to startup otherwise they are delivered to secondary threads causing issues. --- CMakeLists.txt | 8 ++ main_wayland.cpp | 6 ++ virtual_terminal.cpp | 206 +++++++++++++++++++++++++++++++++++++++++++ virtual_terminal.h | 60 +++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 virtual_terminal.cpp create mode 100644 virtual_terminal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8b493b3ab..b124a703bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,12 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS XmlGui ) +find_package(Threads) +set_package_properties(Threads PROPERTIES + PURPOSE "Needed for VirtualTerminal support in KWin Wayland" + TYPE REQUIRED + ) + # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES @@ -417,6 +423,7 @@ if(HAVE_WAYLAND) abstract_backend.cpp screens_wayland.cpp screens_x11windowed.cpp + virtual_terminal.cpp wayland_backend.cpp wayland_server.cpp x11windowed_backend.cpp @@ -504,6 +511,7 @@ set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer + ${CMAKE_THREAD_LIBS_INIT} ) set(kwin_WAYLAND_EGL_LIBS diff --git a/main_wayland.cpp b/main_wayland.cpp index d7a1fe5837..7d22da9df6 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -349,6 +349,12 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); + // ensure that no thread takes SIGUSR + sigset_t userSiganls; + sigemptyset(&userSiganls); + sigaddset(&userSiganls, SIGUSR1); + sigaddset(&userSiganls, SIGUSR2); + pthread_sigmask(SIG_BLOCK, &userSiganls, nullptr); // enforce wayland plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "wayland", true); diff --git a/virtual_terminal.cpp b/virtual_terminal.cpp new file mode 100644 index 0000000000..4cc395001f --- /dev/null +++ b/virtual_terminal.cpp @@ -0,0 +1,206 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "virtual_terminal.h" +// kwin +#include "logind.h" +#include "utils.h" +// Qt +#include +#include +// linux +#include +#include +// system +#include +#include +#include +#include +#include +#include + +#define RELEASE_SIGNAL SIGUSR1 +#define ACQUISITION_SIGNAL SIGUSR2 + +namespace KWin +{ + +KWIN_SINGLETON_FACTORY(VirtualTerminal) + +VirtualTerminal::VirtualTerminal(QObject *parent) + : QObject(parent) +{ +} + +void VirtualTerminal::init() +{ + auto logind = LogindIntegration::self(); + if (logind->vt() != -1) { + setup(logind->vt()); + } + connect(logind, &LogindIntegration::virtualTerminalChanged, this, &VirtualTerminal::setup); + if (logind->isConnected()) { + logind->takeControl(); + } else { + connect(logind, &LogindIntegration::connectedChanged, logind, &LogindIntegration::takeControl); + } +} + +VirtualTerminal::~VirtualTerminal() +{ + s_self = nullptr; + closeFd(); +} + +static bool isTty(int fd) +{ + if (fd < 0) { + return false; + } + struct stat st; + if (fstat(fd, &st) == -1) { + return false; + } + if (major(st.st_rdev) != TTY_MAJOR || minor (st.st_rdev) <= 0 || minor(st.st_rdev) >= 64) { + return false; + } + return true; +} + +void VirtualTerminal::setup(int vtNr) +{ + if (m_vt != -1) { + return; + } + if (vtNr == -1) { + // error condition + return; + } + QString ttyName = QStringLiteral("/dev/tty%1").arg(vtNr); + + m_vt = LogindIntegration::self()->takeDevice(ttyName.toUtf8().constData()); + if (m_vt < 0) { + qCWarning(KWIN_CORE) << "Failed to open tty through logind, trying without"; + } + m_vt = open(ttyName.toUtf8().constData(), O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (m_vt < 0) { + qCWarning(KWIN_CORE) << "Failed to open tty" << vtNr; + return; + } + if (!isTty(m_vt)) { + qCWarning(KWIN_CORE) << vtNr << " is no tty"; + closeFd(); + return; + } + if (!createSignalHandler()) { + qCWarning(KWIN_CORE) << "Failed to create signalfd"; + closeFd(); + return; + } + vt_mode mode = { + VT_PROCESS, + 0, + RELEASE_SIGNAL, + ACQUISITION_SIGNAL, + 0 + }; + if (ioctl(m_vt, VT_SETMODE, &mode) < 0) { + qCWarning(KWIN_CORE) << "Failed to take over virtual terminal"; + closeFd(); + return; + } + setActive(true); +} + +void VirtualTerminal::closeFd() +{ + if (m_vt < 0) { + return; + } + if (m_notifier) { + const int sfd = m_notifier->socket(); + delete m_notifier; + m_notifier = nullptr; + close(sfd); + } + close(m_vt); + m_vt = -1; +} + +bool VirtualTerminal::createSignalHandler() +{ + if (m_notifier) { + return false; + } + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, RELEASE_SIGNAL); + sigaddset(&mask, ACQUISITION_SIGNAL); + pthread_sigmask(SIG_BLOCK, &mask, nullptr); + + const int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (fd < 0) { + return false; + } + m_notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, + [this] { + if (m_vt < 0) { + return; + } + while (true) { + signalfd_siginfo sigInfo; + if (read(m_notifier->socket(), &sigInfo, sizeof(sigInfo)) != sizeof(sigInfo)) { + break; + } + switch (sigInfo.ssi_signo) { + case RELEASE_SIGNAL: + ioctl(m_vt, VT_RELDISP, 1); + setActive(false); + break; + case ACQUISITION_SIGNAL: + ioctl(m_vt, VT_RELDISP, VT_ACKACQ); + setActive(true); + break; + } + } + } + ); + return true; +} + +void VirtualTerminal::activate(int vt) +{ + if (m_vt < 0) { + return; + } + ioctl(m_vt, VT_ACTIVATE, vt); + setActive(false); +} + +void VirtualTerminal::setActive(bool active) +{ + if (m_active == active) { + return; + } + m_active = active; + emit activeChanged(m_active); +} + +} diff --git a/virtual_terminal.h b/virtual_terminal.h new file mode 100644 index 0000000000..84bd80ff6c --- /dev/null +++ b/virtual_terminal.h @@ -0,0 +1,60 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_VIRTUAL_TERMINAL_H +#define KWIN_VIRTUAL_TERMINAL_H +#include + +#include + +class QSocketNotifier; + +namespace KWin +{ + +class VirtualTerminal : public QObject +{ + Q_OBJECT +public: + virtual ~VirtualTerminal(); + + void init(); + void activate(int vt); + bool isActive() const { + return m_active; + } + +Q_SIGNALS: + void activeChanged(bool); + +private: + void setup(int vtNr); + void closeFd(); + bool createSignalHandler(); + void setActive(bool active); + int m_vt = -1; + QSocketNotifier *m_notifier = nullptr; + bool m_active = false; + + KWIN_SINGLETON(VirtualTerminal) +}; + +} + +#endif