Schlimm is an attempt to create a fork of the SLiM desktop manager.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1374 lines
37 KiB

/*
* This file is part of schlimm
* Copyright (C) 2019-2021 Moritz Strohm <ncc1988@posteo.de>
* and others (see the AUTHORS file).
*
* SLiM - Simple Login Manager
* Copyright (C) 1997, 1998 Per Liden
* Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
* Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "App.h"
using namespace std;
//static attribute initialisation:
std::shared_ptr<App> App::instance = nullptr;
//method implementations:
void App::init(int argc, char** argv)
{
if (App::instance == nullptr) {
App::instance = std::make_shared<App>(
App::SingletonEnforcer{},
argc,
argv
);
}
}
std::shared_ptr<App> App::getInstance()
{
return App::instance;
}
App::App(App::SingletonEnforcer s, int argc, char** argv)
{
this->config = std::make_shared<Schlimm::Config>(CFGFILE);
this->log = Schlimm::Log::init(APPNAME, this->config->getOption("logfile"));
#ifdef USE_PAM
this->pam = std::make_unique<PAM::PAMAuth>();
#endif
int tmp;
this->server_pid = -1;
this->testing = false;
this->serverStarted = false;
this->mcookie = std::string(this->mcookiesize, 'a');
this->daemonmode = false;
this->force_nodaemon = false;
this->firstlogin = true;
this->display = nullptr;
/* Parse command line
Note: we force a option for nodaemon switch to handle "-nodaemon" */
while ((tmp = getopt(argc, argv, "vhp:n:d?")) != -1) {
switch (tmp) {
case 'p': /* Test theme */
testtheme = optarg;
testing = true;
if (testtheme == nullptr) {
this->log->append("The -p option requires an argument");
exit(ERR_EXIT);
}
break;
case 'd': /* Daemon mode */
daemonmode = true;
break;
case 'n': /* Daemon mode */
daemonmode = false;
force_nodaemon = true;
break;
case 'v': /* Version */
std::cout << APPNAME << " version " << VERSION << endl;
exit(OK_EXIT);
break;
case '?': /* Illegal */
this->log->append("Illegal option \"?\".");
case 'h': /* Help */
this->log->append(
fmt::format(
"usage: {} [option ...]\n"s
+ "options:\n"
+ " -d: daemon mode\n"
+ " -nodaemon: no-daemon mode\n"
+ " -v: show version\n"
+ " -p /path/to/theme/dir: preview theme\n",
APPNAME
)
);
exit(OK_EXIT);
break;
}
}
#ifndef XNEST_DEBUG
if (getuid() != 0 && !testing) {
this->log->append("Only root can run this program!");
exit(ERR_EXIT);
}
#endif /* XNEST_DEBUG */
}
/*
App::~App()
{
//TODO: do something destructive
}*/
void App::run()
{
this->display_name = DISPLAY;
#ifdef XNEST_DEBUG
std::string new_display_name = getenv("DISPLAY");
if (!new_display_name.empty()) {
this->display_name = new_display_name;
std::cout << fmt::format(
"Using the display named \"{}\".",
this->display_name
) << std::endl;
}
#endif
this->display_client = std::make_shared<Schlimm::X11XLibClient>(this->display_name);
//Read theme:
string themebase = "";
string themefile = "";
string themedir = "";
themeName = "";
if (testing) {
themeName = testtheme;
} else {
themebase = std::string(THEMESDIR) + "/";
themeName = this->config->getOption("current_theme");
string::size_type pos;
if ((pos = themeName.find(",")) != string::npos) {
/* input is a set */
themeName = findValidRandomTheme(themeName);
if (themeName == "") {
themeName = "default";
}
}
}
#ifdef USE_PAM
try {
this->pam->start("slim");
this->pam->set_item(PAM::PAMAuth::TTY, this->display_name.c_str());
this->pam->set_item(PAM::PAMAuth::Requestor, "root");
} catch(Schlimm::Exception& e) {
this->log->append(std::string(e.toString()));
exit(ERR_EXIT);
}
#endif
themedir = themebase + themeName;
themefile = themedir + THEMESFILE;
if (!this->config->readConf(themefile)) {
if (themeName != "default") {
this->log->append(
fmt::format(
"Invalid theme file (\"{}\") specified in the configuration!",
themeName
)
);
themeName = "default";
themedir = themebase + themeName;
themefile = themedir + THEMESFILE;
if (!this->config->readConf(themefile)) {
this->log->append(
fmt::format(
"Failed to open the default theme file \"{}\"!",
themefile
)
);
}
}
}
if (!testing) {
/* Create lock file */
this->getLock();
/* Start x-server */
setenv("DISPLAY", this->display_name.c_str(), 1);
signal(SIGQUIT, App::catchSignal);
signal(SIGTERM, App::catchSignal);
signal(SIGKILL, App::catchSignal);
signal(SIGINT, App::catchSignal);
signal(SIGHUP, App::catchSignal);
signal(SIGPIPE, App::catchSignal);
signal(SIGUSR1, App::handleUser1Signal);
#ifndef XNEST_DEBUG
if (!force_nodaemon && this->config->getOption("daemon") == "yes") {
daemonmode = true;
}
/* Daemonize */
if (daemonmode) {
if (daemon(0, 0) == -1) {
this->log->append(
fmt::format(
"Daemon initialisation failed: {}",
strerror(errno)
)
);
exit(ERR_EXIT);
}
}
if (daemonmode) {
this->updatePID();
}
this->createServerAuth();
this->startServer();
#endif
}
/* Open display */
if ((this->display = XOpenDisplay(this->display_name.c_str())) == 0) {
this->log->append(
fmt::format(
"Could not open display \"{}\"!",
this->display_name
)
);
if (!testing) {
this->stopServer();
}
exit(ERR_EXIT);
}
/* Get screen and root window */
this->screen = DefaultScreen(this->display);
this->root_window = RootWindow(this->display, this->screen);
// Intern _XROOTPMAP_ID property
BackgroundPixmapId = XInternAtom(this->display, "_XROOTPMAP_ID", False);
/* for tests we use a standard window */
if (testing) {
Window RealRoot = RootWindow(this->display, this->screen);
this->root_window = XCreateSimpleWindow(this->display, RealRoot, 0, 0, 1280, 1024, 0, 0, 0);
XMapWindow(this->display, this->root_window);
XFlush(this->display);
} else {
blankScreen();
}
this->hideCursor();
/* Create panel */
this->login_panel = new Schlimm::Panel(this->display, this->screen, this->root_window, this->config.get(), themedir, Schlimm::Panel::Mode_DM);
bool firstloop = true; /* 1st time panel is shown (for automatic username) */
bool focuspass = this->config->getOption("focus_password") == "yes";
bool autologin = this->config->getOption("auto_login") == "yes";
if (firstlogin && this->config->getOption("default_user") != "") {
this->login_panel->setName(this->config->getOption("default_user"));
#ifdef USE_PAM
this->pam->setPanel(this->login_panel);
this->pam->set_item(PAM::PAMAuth::User, this->config->getOption("default_user").c_str());
#endif
firstlogin = false;
if (autologin) {
this->login();
}
}
/* Set NumLock */
string numlock = this->config->getOption("numlock");
if (numlock == "on") {
Schlimm::NumLock::setOn(this->display);
} else if (numlock == "off") {
Schlimm::NumLock::setOff(this->display);
}
/* Start looping */
int panelclosed = 1;
Schlimm::Panel::ActionType Action;
while (true) {
if (panelclosed) {
/* Init root */
setBackground(themedir);
/* Close all clients */
if (!testing) {
this->killAllClients(false);
this->killAllClients(true);
}
/* Show panel */
this->login_panel->openPanel();
}
this->login_panel->reset();
if (firstloop && this->config->getOption("default_user") != "")
this->login_panel->setName(this->config->getOption("default_user"));
if (firstloop)
this->login_panel->switchSession();
if (!this->authenticateUser(focuspass && firstloop)) {
panelclosed = 0;
firstloop = false;
this->login_panel->clearPanel();
XBell(this->display, 100);
continue;
}
firstloop = false;
Action = this->login_panel->getAction();
/* for themes test we just quit */
if (testing) {
Action = Schlimm::Panel::Exit;
}
panelclosed = 1;
this->login_panel->closePanel();
switch (Action) {
case Schlimm::Panel::Login:
this->login();
break;
case Schlimm::Panel::Console:
this->console();
break;
case Schlimm::Panel::Reboot:
this->reboot();
break;
case Schlimm::Panel::Halt:
this->halt();
break;
case Schlimm::Panel::Suspend:
this->suspend();
break;
case Schlimm::Panel::Exit:
this->Exit();
break;
default:
break;
}
}
}
#ifdef USE_PAM
bool App::authenticateUser(bool focuspass)
{
/* Reset the username */
try {
if (!focuspass) {
this->pam->set_item(PAM::PAMAuth::User, 0);
}
this->pam->authenticate();
} catch(PAM::Auth_Exception& e) {
switch (this->login_panel->getAction()) {
case Schlimm::Panel::Exit:
case Schlimm::Panel::Console:
return true; /* <--- This is simply fake! */
default:
break;
}
this->log->append(
fmt::format(
"PAM authentication failed: {}",
e.toString()
)
);
return false;
} catch (Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
exit(ERR_EXIT);
}
return true;
}
#else
bool App::authenticateUser(bool focuspass)
{
if (!focuspass) {
this->login_panel->eventHandler(Schlimm::Panel::Get_Name);
switch (this->login_panel->getAction()) {
case Schlimm::Panel::Exit:
case Schlimm::Panel::Console:
this->log->append(
fmt::format(
"Got a special command ({})",
this->login_panel->getName()
)
);
return true; /* <--- This is simply fake! */
default:
break;
}
}
this->login_panel->eventHandler(Schlimm::Panel::Get_Passwd);
char *encrypted, *correct;
struct passwd *pw;
switch (this->login_panel->getAction()) {
case Schlimm::Panel::Suspend:
case Schlimm::Panel::Halt:
case Schlimm::Panel::Reboot:
pw = getpwnam("root");
break;
case Schlimm::Panel::Console:
case Schlimm::Panel::Exit:
case Schlimm::Panel::Login:
pw = getpwnam(this->login_panel->getName().c_str());
break;
}
endpwent();
if (pw == 0) {
return false;
}
#ifdef HAVE_SHADOW
struct spwd *sp = getspnam(pw->pw_name);
endspent();
if (sp) {
correct = sp->sp_pwdp;
} else {
correct = pw->pw_passwd;
}
#else
correct = pw->pw_passwd;
#endif
if (correct == 0 || correct[0] == '\0') {
return true;
}
encrypted = crypt(this->login_panel->getPasswd().c_str(), correct);
return ((encrypted && strcmp(encrypted, correct) == 0) ? true : false);
}
#endif
int App::getServerPID()
{
return this->server_pid;
}
/* Hide the cursor */
void App::hideCursor()
{
if (this->config->getOption("hidecursor") == "true") {
XColor black;
char cursordata[1];
Pixmap cursorpixmap;
Cursor cursor;
cursordata[0] = 0;
cursorpixmap = XCreateBitmapFromData(this->display, this->root_window,cursordata,1,1);
black.red = 0;
black.green = 0;
black.blue = 0;
cursor = XCreatePixmapCursor(this->display,cursorpixmap,cursorpixmap,&black,&black,0,0);
XDefineCursor(this->display, this->root_window, cursor);
}
}
void App::login()
{
struct passwd *pw;
pid_t pid;
#ifdef USE_PAM
try{
this->pam->openSession();
pw = getpwnam(static_cast<const char*>(this->pam->get_item(PAM::PAMAuth::User)));
} catch(PAM::Cred_Exception& e) {
/* Credentials couldn't be established */
this->log->append(
fmt::format(
"Error while setting credentials: {}",
e.toString()
)
);
return;
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
exit(ERR_EXIT);
}
#else
pw = getpwnam(this->login_panel->getName().c_str());
#endif
endpwent();
if (pw == 0) {
return;
}
if (pw->pw_shell[0] == '\0') {
setusershell();
strcpy(pw->pw_shell, getusershell());
endusershell();
}
/* Setup the environment */
char* term = getenv("TERM");
std::string maildir = _PATH_MAILDIR;
maildir.append("/");
maildir.append(pw->pw_name);
std::string xauthority = pw->pw_dir;
xauthority.append("/.Xauthority");
#ifdef USE_PAM
/* Setup the PAM environment */
try {
if (term) {
this->pam->setenv("TERM", term);
}
this->pam->setenv("HOME", pw->pw_dir);
this->pam->setenv("PWD", pw->pw_dir);
this->pam->setenv("SHELL", pw->pw_shell);
this->pam->setenv("USER", pw->pw_name);
this->pam->setenv("LOGNAME", pw->pw_name);
this->pam->setenv("PATH", this->config->getOption("default_path").c_str());
this->pam->setenv("DISPLAY", this->display_name);
this->pam->setenv("MAIL", maildir.c_str());
this->pam->setenv("XAUTHORITY", xauthority.c_str());
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
exit(ERR_EXIT);
}
#endif
/* Create new process */
pid = fork();
if (pid == 0) {
#ifdef USE_PAM
/* Get a copy of the environment and close the child's copy */
/* of the PAM-handle. */
char** child_env = this->pam->getenvlist();
#else
const int Num_Of_Variables = 11; /* Number of env. variables + 1 */
std::vector<std::string> child_env;
if (term) {
child_env.push_back(fmt::format("TERM={}", term));
}
child_env.push_back(fmt::format("HOME={}", pw->pw_dir));
child_env.push_back(fmt::format("PWD={}", pw->pw_dir));
child_env.push_back(fmt::format("SHELL={}", pw->pw_shell));
child_env.push_back(fmt::format("USER={}", pw->pw_name));
child_env.push_back(fmt::format("LOGNAME={}", pw->pw_name));
child_env.push_back(fmt::format("PATH={}", this->config->getOption("default_path")));
child_env.push_back(fmt::format("DISPLAY={}", this->display_name));
child_env.push_back(fmt::format("MAIL={}", maildir));
child_env.push_back(fmt::format("XAUTHORITY={}", xauthority));
#endif
/* Login process starts here */
SwitchUser Su(pw, this->config, this->display_name, child_env);
string session = this->login_panel->getSession();
std::string loginCommand = this->config->getOption("login_cmd");
replaceVariables(loginCommand, SESSION_VAR, session);
replaceVariables(loginCommand, THEME_VAR, themeName);
string sessStart = this->config->getOption("sessionstart_cmd");
if (sessStart != "") {
replaceVariables(sessStart, USER_VAR, pw->pw_name);
system(sessStart.c_str());
}
Su.Login(loginCommand.c_str(), mcookie.c_str());
_exit(OK_EXIT);
}
/* Wait until user is logging out (login process terminates) */
pid_t wpid = -1;
int status;
while (wpid != pid) {
wpid = wait(&status);
if (wpid == this->server_pid) {
this->restartServer(); /* Server died */
}
}
if (WIFEXITED(status) && WEXITSTATUS(status)) {
this->login_panel->message("Failed to execute login command");
sleep(3);
} else {
string sessStop = this->config->getOption("sessionstop_cmd");
if (sessStop != "") {
replaceVariables(sessStop, USER_VAR, pw->pw_name);
system(sessStop.c_str());
}
}
#ifdef USE_PAM
try {
this->pam->closeSession();
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
}
#endif
/* Close all clients */
this->killAllClients(false);
this->killAllClients(true);
/* Send HUP signal to clientgroup */
killpg(pid, SIGHUP);
/* Send TERM signal to clientgroup, if error send KILL */
if (killpg(pid, SIGTERM)) {
killpg(pid, SIGKILL);
}
this->hideCursor();
#ifndef XNEST_DEBUG
this->restartServer();
#endif
}
void App::reboot()
{
#ifdef USE_PAM
try {
this->pam->end();
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
}
#endif
/* Write message */
this->login_panel->message((char*)this->config->getOption("reboot_msg").c_str());
sleep(3);
/* Stop server and reboot */
this->stopServer();
this->removeLock();
system(this->config->getOption("reboot_cmd").c_str());
exit(OK_EXIT);
}
void App::halt()
{
#ifdef USE_PAM
try {
this->pam->end();
} catch (Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
}
#endif
/* Write message */
this->login_panel->message((char*)this->config->getOption("shutdown_msg").c_str());
sleep(3);
/* Stop server and halt */
this->stopServer();
this->removeLock();
system(this->config->getOption("halt_cmd").c_str());
exit(OK_EXIT);
}
void App::suspend()
{
sleep(1);
system(this->config->getOption("suspend_cmd").c_str());
}
void App::console()
{
int posx = 40;
int posy = 40;
int fontx = 9;
int fonty = 15;
int width = (XWidthOfScreen(ScreenOfDisplay(this->display, this->screen)) - (posx * 2)) / fontx;
int height = (XHeightOfScreen(ScreenOfDisplay(this->display, this->screen)) - (posy * 2)) / fonty;
/* Execute console */
const char* cmd = this->config->getOption("console_cmd").c_str();
char *tmp = new char[strlen(cmd) + 60];
sprintf(tmp, cmd, width, height, posx, posy, fontx, fonty);
system(tmp);
delete tmp;
}
//This is still in uppercase to avoid conflicts with the exit() function
//that is called at several places in App methods.
void App::Exit()
{
#ifdef USE_PAM
try {
this->pam->end();
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
}
#endif
if (testing) {
const char* testmsg = "This is a test message :-)";
this->login_panel->message(testmsg);
sleep(3);
delete this->login_panel;
XCloseDisplay(this->display);
} else {
delete this->login_panel;
this->stopServer();
this->removeLock();
}
exit(OK_EXIT);
}
int App::catchErrors(Display *dpy, XErrorEvent *ev)
{
return 0;
}
void App::restartServer()
{
#ifdef USE_PAM
try {
this->pam->end();
} catch(Schlimm::Exception& e) {
this->log->append(
fmt::format(
"General failure: {}",
e.toString()
)
);
}
#endif
this->stopServer();
this->removeLock();
while (waitpid(-1, NULL, WNOHANG) > 0); /* Collects all dead childrens */
this->run();
}
void App::killAllClients(bool top)
{
Window dummywindow;
Window *children;
unsigned int nchildren;
unsigned int i;
XWindowAttributes attr;
XSync(this->display, 0);
XSetErrorHandler(App::catchErrors);
nchildren = 0;
XQueryTree(this->display, this->root_window, &dummywindow, &dummywindow, &children, &nchildren);
if (!top) {
for (i=0; i<nchildren; i++) {
if (XGetWindowAttributes(this->display, children[i], &attr) && (attr.map_state == IsViewable)) {
children[i] = XmuClientWindow(this->display, children[i]);
} else {
children[i] = 0;
}
}
}
for (i = 0; i < nchildren; i++) {
if (children[i]) {
XKillClient(this->display, children[i]);
}
}
XFree((char *)children);
XSync(this->display, 0);
XSetErrorHandler(nullptr);
}
bool App::checkXServerProcess(const uint16_t timeout, const std::string& waiting_text)
{
uint16_t i = 0;
pid_t wait_result = -1;
while (true) {
wait_result = waitpid(this->server_pid, nullptr, WNOHANG);
if (wait_result == this->server_pid) {
break;
}
if (timeout && waiting_text.length()) {
this->log->append(waiting_text);
}
if (timeout) {
sleep(1);
}
i++;
if (i > timeout) {
break;
}
}
return (this->server_pid != wait_result);
}
int App::waitForServer()
{
int ncycles = 120;
int cycles;
for (cycles = 0; cycles < ncycles; cycles++) {
if ((this->display = XOpenDisplay(this->display_name.c_str()))) {
XSetIOErrorHandler(App::handleXIOError);
return 1;
} else {
if (!this->checkXServerProcess(1, "Waiting for X server to begin accepting connections ...")) {
break;
}
}
}
this->log->append("Giving up.");
return 0;
}
int App::startServer()
{
this->server_pid = fork();
int argc = 1, pos = 0, i;
static const int MAX_XSERVER_ARGS = 256;
static char* server[MAX_XSERVER_ARGS+2] = { NULL };
server[0] = (char *)this->config->getOption("default_xserver").c_str();
std::string argOption = this->config->getOption("xserver_arguments");
/* Add mandatory -xauth option */
argOption = argOption + " -auth " + this->config->getOption("authfile");
char* args = new char[argOption.length()+2]; /* NULL plus vt */
strcpy(args, argOption.c_str());
serverStarted = false;
bool hasVtSet = false;
while (args[pos] != '\0') {
if (args[pos] == ' ' || args[pos] == '\t') {
*(args+pos) = '\0';
server[argc++] = args+pos+1;
} else if (pos == 0) {
server[argc++] = args+pos;
}
++pos;
if (argc+1 >= MAX_XSERVER_ARGS) {
/* ignore _all_ arguments to make sure the server starts at */
/* all */
argc = 1;
break;
}
}
for (i = 0; i < argc; i++) {
if (server[i][0] == 'v' && server[i][1] == 't') {
bool ok = false;
Schlimm::Config::string2int(server[i]+2, &ok);
if (ok) {
hasVtSet = true;
}
}
}
if (!hasVtSet && daemonmode) {
server[argc++] = (char*)"vt07";
}
server[argc] = NULL;
switch (this->server_pid) {
case 0:
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGUSR1, SIG_IGN);
setpgid(0,getpid());
execvp(server[0], server);
this->log->append("X server could not be started");
exit(ERR_EXIT);
break;
case -1:
break;
default:
errno = 0;
if (!this->checkXServerProcess(0, "")) {
this->server_pid = -1;
break;
}
/* Wait for server to start up */
if (this->waitForServer() == 0) {
this->log->append("Unable to connect to X server");
this->stopServer();
this->server_pid = -1;
exit(ERR_EXIT);
}
break;
}
delete [] args;
serverStarted = true;
return this->server_pid;
}
jmp_buf CloseEnv; //TODO: move/remove this!
int App::ignoreXIO(Display *d)
{
Schlimm::Log::log("Connection to X server lost.");
longjmp(CloseEnv, 1); //do not jump, call a method instead!
}
void App::stopServer()
{
signal(SIGQUIT, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, SIG_DFL);
signal(SIGKILL, SIG_DFL);
//Close the display if the X server is still running.
//We need to switch the IOErrorHandler to avoid the
//X server to be restarted by the default handler.
XSetIOErrorHandler(App::ignoreXIO);
if (!setjmp(CloseEnv) && this->display) {
XCloseDisplay(this->display);
}
/* Send HUP to process group */
errno = 0;
if ((killpg(getpid(), SIGHUP) != 0) && (errno != ESRCH)) {
this->log->append(
fmt::format(
"Cannot send hangup signal (SIGHUP) to process group {}!",
getpid()
)
);
}
/* Send TERM to server */
if (this->server_pid < 0) {
return;
}
errno = 0;
if (killpg(this->server_pid, SIGTERM) < 0) {
if (errno == EPERM) {
this->log->append("Cannot kill X server");
exit(ERR_EXIT);
}
if (errno == ESRCH) {
return;
}
}
/* Wait for server to shut down */
if (!this->checkXServerProcess(10, "Waiting for X server to shut down ...")) {
return;
}
this->log->append(
"The X server is shutting down too slowly. Now trying a forced shutdown by sending the KILL signal."
);
/* Send KILL to server */
errno = 0;
if (killpg(this->server_pid, SIGKILL) < 0) {
if (errno == ESRCH) {
return;
}
}
/* Wait for server to die */
if (this->checkXServerProcess(3, "Waiting for X server to be shut down ...")) {
this->log->append("Cannot shut down the X server using force!");
exit(ERR_EXIT);
}
}
void App::blankScreen()
{
GC gc = XCreateGC(this->display, this->root_window, 0, 0);
XSetForeground(this->display, gc, BlackPixel(this->display, this->screen));
XFillRectangle(
this->display,
this->root_window,
gc,
0,
0,
XWidthOfScreen(ScreenOfDisplay(this->display, this->screen)),
XHeightOfScreen(ScreenOfDisplay(this->display, this->screen))
);
XFlush(this->display);
XFreeGC(this->display, gc);
}
void App::setBackground(const std::string& themedir)
{
std::string filename;
filename = themedir + "/background.png";
auto image = std::make_unique<Schlimm::Image>();
if (image == nullptr) {
this->log->append("Cannot initialise image handler!");
return;
}
bool loaded = image->Read(filename.c_str());
if (!loaded) { /* try jpeg if png failed */
this->log->append(
fmt::format(
"Could not load image file {0}! Trying to load file {1} instead.",
filename,
themedir + "/background.jpg"
)
);
filename = themedir + "/background.jpg";
loaded = image->Read(filename.c_str());
if (!loaded) {
this->log->append(
fmt::format(
"Could not load image file {} either!",
filename
)
);
}
}
if (loaded) {
std::string bgstyle = this->config->getOption("background_style");
auto screen_of_display = ScreenOfDisplay(this->display, this->screen);
auto screen_width = XWidthOfScreen(screen_of_display);
auto screen_height = XHeightOfScreen(screen_of_display);
if (bgstyle == "stretch") {
image->Resize(screen_width, screen_height);
} else if (bgstyle == "tile") {
image->Tile(screen_width, screen_height);
} else if (bgstyle == "center") {
std::string hexvalue = this->config->getOption("background_color");
hexvalue = hexvalue.substr(1,6);
image->Center(screen_width, screen_height, hexvalue.c_str());
} else { /* plain color or error */
std::string hexvalue = this->config->getOption("background_color");
hexvalue = hexvalue.substr(1,6);
image->Center(screen_width, screen_height, hexvalue.c_str());
}
Pixmap p = image->createPixmap(this->display, this->screen, this->root_window);
XSetWindowBackgroundPixmap(this->display, this->root_window, p);
XChangeProperty(
this->display,
this->root_window,
BackgroundPixmapId,
XA_PIXMAP,
32,
PropModeReplace,
(unsigned char *)&p,
1
);
}
XClearWindow(this->display, this->root_window);
XFlush(this->display);
}
/* Check if there is a lockfile and a corresponding process */
void App::getLock()
{
std::ifstream lockfile(this->config->getOption("lockfile").c_str());
if (!lockfile) {
/* no lockfile present, create one */
std::ofstream lockfile(this->config->getOption("lockfile").c_str(), ios_base::out);
if (!lockfile) {
this->log->append(
fmt::format(
"Could not create lock file \"{}\"!",
this->config->getOption("lockfile")
)
);
exit(ERR_EXIT);
}
lockfile << getpid() << std::endl;
lockfile.close();
} else {
/* lockfile present, read pid from it */
int pid = 0;
lockfile >> pid;
lockfile.close();
if (pid > 0) {
/* see if process with this pid exists */
int ret = kill(pid, 0);
if (ret == 0 || (ret == -1 && errno == EPERM) ) {
this->log->append(
fmt::format(
"Another instance of the program is already running with the process-ID {}!",
pid
)
);
exit(0);
} else {
this->log->append("Stale lockfile found, removing it");
std::ofstream lockfile(this->config->getOption("lockfile").c_str(), ios_base::out);
if (!lockfile) {
this->log->append(
fmt::format(
"Could not create new lock file \"{}\"!",
this->config->getOption("lockfile")
)
);
exit(ERR_EXIT);
}
lockfile << getpid() << std::endl;
lockfile.close();
}
}
}
}
/* Remove lockfile and close logs */
void App::removeLock()
{
remove(this->config->getOption("lockfile").c_str());
}
/* Get server start check flag. */
bool App::isServerStarted()
{
return serverStarted;
}
int App::handleXIOError(Display *disp)
{
std::shared_ptr<App> instance = App::getInstance();
Schlimm::Log::log("X IO error occurred. Restarting X server.");
instance->restartServer();
return 0;
}
void App::catchSignal(int sig)
{
Schlimm::Log::log(
fmt::format(
"Unexpected signal with code {} received!",
sig
)
);
std::shared_ptr<App> instance = App::getInstance();
if (instance->isServerStarted()) {
instance->stopServer();
}
instance->removeLock();
exit(ERR_EXIT);
}
void App::handleUser1Signal(int sig)
{
signal(sig, App::handleUser1Signal);
}
/* Redirect stdout and stderr to log file */
void App::OpenLog()
{
if (this->log == nullptr) {
this->log = Schlimm::Log::init(APPNAME, this->config->getOption("logfile"));
if (this->log == nullptr) {
//We tried to initialise it.
return;
}
this->log->log(fmt::format("Could not accesss log file \"{}\"!", this->config->getOption("logfile")));
this->removeLock();
exit(ERR_EXIT);
}
/* I should set the buffers to imediate write, but I just flush on every << operation. */
}
/* Relases stdout/err */
void App::CloseLog()
{
//nothing to do here anymore.
}
std::string App::findValidRandomTheme(const string& set)
{
/* extract random theme from theme set; return empty string on error */
string name = set;
struct stat buf;
if (name[name.length()-1] == ',') {
name = name.substr(0, name.length() - 1);
}
Util::srandom(Util::makeseed());
vector<string> themes;
string themefile;
Schlimm::Config::split(themes, name, ',');
do {
int sel = Util::random() % themes.size();
name = Schlimm::Config::Trim(themes[sel]);
themefile = std::string(THEMESDIR) +"/" + name + THEMESFILE;
if (stat(themefile.c_str(), &buf) != 0) {
themes.erase(find(themes.begin(), themes.end(), name));
Schlimm::Log::log(fmt::format("Invalid theme in config: {}", name));
name = "";
}
} while (name == "" && themes.size());
return name;
}
void App::replaceVariables(
std::string& input,
const string& var,
const string& value
)
{
std::string::size_type pos = 0;
int len = var.size();
while ((pos = input.find(var, pos)) != string::npos) {
input = input.substr(0, pos) + value + input.substr(pos+len);
}
}
/*
* We rely on the fact that all bits generated by Util::random()
* are usable, so we are taking full words from its output.
*/
void App::createServerAuth()
{
/* create mit cookie */
uint16_t word;
uint8_t hi, lo;
int i;
string authfile;
const char *digits = "0123456789abcdef";
Util::srandom(Util::makeseed());
for (i = 0; i < App::mcookiesize; i+=4) {
word = Util::random() & 0xffff;
lo = word & 0xff;
hi = word >> 8;
mcookie[i] = digits[lo & 0x0f];
mcookie[i+1] = digits[lo >> 4];
mcookie[i+2] = digits[hi & 0x0f];
mcookie[i+3] = digits[hi >> 4];
}
/* reinitialize auth file */
authfile = this->config->getOption("authfile");
remove(authfile.c_str());
std::string xauthority_envvar = "XAUTHORITY=" + authfile;
this->log->append("DEBUG: " + xauthority_envvar);
char* xa_char = new char[xauthority_envvar.size() + 1];
if (xa_char == nullptr) {
//TODO: throw an exception
return;
}
size_t xa_char_length = xauthority_envvar.copy(
xa_char,
xauthority_envvar.size(),
0
);
xa_char[xa_char_length] = '\0';
putenv(xa_char);
Util::add_mcookie(mcookie, ":0", this->config->getOption("xauth_path"),
authfile);
}
void App::updatePID()
{
std::ofstream lockfile(this->config->getOption("lockfile").c_str(), ios_base::out);
if (!lockfile) {
this->log->append(
fmt::format(
"Could not update lock file \"{}\"!",
this->config->getOption("lockfile")
)
);
exit(ERR_EXIT);
}
lockfile << getpid() << endl;
lockfile.close();
}