[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.
icc-effect-5.14.5
Martin Gräßlin 2015-03-31 09:30:19 +02:00
parent 3d5899cbfd
commit f4005c7f74
4 changed files with 280 additions and 0 deletions

View File

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

View File

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

206
virtual_terminal.cpp Normal file
View File

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

60
virtual_terminal.h Normal file
View File

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