[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.
master
Martin Gräßlin 10 years ago
parent 3d5899cbfd
commit f4005c7f74

@ -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

@ -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);

@ -0,0 +1,206 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "virtual_terminal.h"
// kwin
#include "logind.h"
#include "utils.h"
// Qt
#include <QDebug>
#include <QSocketNotifier>
// linux
#include <linux/major.h>
#include <linux/vt.h>
// system
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#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);
}
}

@ -0,0 +1,60 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_VIRTUAL_TERMINAL_H
#define KWIN_VIRTUAL_TERMINAL_H
#include <kwinglobals.h>
#include <QObject>
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
Loading…
Cancel
Save