e98d5d6a15
If we were on e.g. vt 1 and pressed Ctrl+Alt+F1 KWin broke as it disabled rendering for vt switch and vt switched to vt 1, which obviously failed. So let's check whether it's going to switch to the vt we are already on and ignore surch requests.
218 lines
5.4 KiB
C++
218 lines
5.4 KiB
C++
/********************************************************************
|
|
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 "main.h"
|
|
#include "utils.h"
|
|
// Qt
|
|
#include <QDebug>
|
|
#include <QSocketNotifier>
|
|
// linux
|
|
#include <linux/major.h>
|
|
#include <linux/kd.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 (ioctl(m_vt, KDSETMODE, KD_GRAPHICS) < 0) {
|
|
qCWarning(KWIN_CORE()) << "Failed to set tty " << vtNr << " in graphics mode";
|
|
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;
|
|
}
|
|
m_vtNumber = vtNr;
|
|
setActive(true);
|
|
emit kwinApp()->virtualTerminalCreated();
|
|
}
|
|
|
|
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:
|
|
setActive(false);
|
|
ioctl(m_vt, VT_RELDISP, 1);
|
|
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;
|
|
}
|
|
if (vt == m_vtNumber) {
|
|
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);
|
|
}
|
|
|
|
}
|