suckless-utils/slim/cfg.cpp
Alexis Jhon Gaspar e1207e1d0d Added slim-fork to the repo
- This introduces a minimalist display manager for the suckless-utils suite
- Added crudely written scripts for reloaading slim's theme via pywal,
  meaning no on-the-fly reloading using keybinds as nost people wouldn't
have their sudo passwordless
- This is based on slim-fork 1.4.0 version.
2023-10-12 23:08:23 +08:00

479 lines
13 KiB
C++

/* SLiM - Simple Login Manager
* Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
* Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>
* Copyright (C) 2012-13 Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
* Copyright (C) 2022-23 Rob Pearce <slim@flitspace.org.uk>
*
* 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.
*/
#include <fstream>
#include <string>
#include <iostream>
#include <algorithm>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "util.h" // for Util::random
#include "log.h" // for logStream
#include "cfg.h"
using namespace std;
typedef pair<string,string> option;
/**
* Constructor: creates the Cfg object and populates the available options
* with default values.
*/
Cfg::Cfg()
: currentSession(-1)
{
/* Configuration options */
options.insert(option("default_path","/bin:/usr/bin:/usr/local/bin"));
options.insert(option("default_xserver","/usr/bin/X"));
options.insert(option("xserver_arguments",""));
options.insert(option("numlock",""));
options.insert(option("daemon",""));
options.insert(option("xauth_path","/usr/bin/xauth"));
options.insert(option("login_cmd","exec /bin/bash -login ~/.xinitrc %session"));
options.insert(option("halt_cmd","/sbin/shutdown -h now"));
options.insert(option("reboot_cmd","/sbin/shutdown -r now"));
options.insert(option("suspend_cmd",""));
options.insert(option("sessionstart_cmd",""));
options.insert(option("sessionstop_cmd",""));
options.insert(option("console_cmd","/usr/bin/xterm -C -fg white -bg black +sb -g %dx%d+%d+%d -fn %dx%d -T ""Console login"" -e /bin/sh -c ""/bin/cat /etc/issue; exec /bin/login"""));
options.insert(option("screenshot_cmd","import -window root /slim.png"));
options.insert(option("default_user",""));
options.insert(option("focus_password","no"));
options.insert(option("auto_login","no"));
options.insert(option("current_theme","default"));
options.insert(option("lockfile","/var/run/slim.lock"));
options.insert(option("logfile","/var/log/slim.log"));
options.insert(option("authfile","/var/run/slim.auth"));
options.insert(option("shutdown_msg","The system is halting..."));
options.insert(option("reboot_msg","The system is rebooting..."));
options.insert(option("sessions", ""));
options.insert(option("sessiondir",""));
options.insert(option("hidecursor","false"));
/* Theme stuff */
options.insert(option("background_style","stretch"));
options.insert(option("background_color","#CCCCCC"));
options.insert(option("input_panel_x","50%")); /* Panel position on screen */
options.insert(option("input_panel_y","40%"));
options.insert(option("input_font","Verdana:size=11"));
options.insert(option("input_color", "#000000"));
options.insert(option("input_shadow_xoffset", "0"));
options.insert(option("input_shadow_yoffset", "0"));
options.insert(option("input_shadow_color","#FFFFFF"));
options.insert(option("input_name_x","200")); /* relative to panel */
options.insert(option("input_name_y","154"));
options.insert(option("input_pass_x","-1")); /* default is single inputbox */
options.insert(option("input_pass_y","-1"));
options.insert(option("welcome_msg","Welcome to %host"));
options.insert(option("welcome_font","Verdana:size=14"));
options.insert(option("welcome_color","#FFFFFF"));
options.insert(option("welcome_x","-1"));
options.insert(option("welcome_y","-1"));
options.insert(option("welcome_shadow_xoffset", "0"));
options.insert(option("welcome_shadow_yoffset", "0"));
options.insert(option("welcome_shadow_color","#FFFFFF"));
options.insert(option("username_msg","Please enter your username"));
options.insert(option("username_font","Verdana:size=12"));
options.insert(option("username_color","#FFFFFF"));
options.insert(option("username_x","-1"));
options.insert(option("username_y","-1"));
options.insert(option("username_shadow_xoffset", "0"));
options.insert(option("username_shadow_yoffset", "0"));
options.insert(option("username_shadow_color","#FFFFFF"));
options.insert(option("password_msg","Please enter your password"));
options.insert(option("password_x","-1"));
options.insert(option("password_y","-1"));
options.insert(option("msg_font","Verdana:size=16:bold"));
options.insert(option("msg_color","#FFFFFF"));
options.insert(option("msg_x","40"));
options.insert(option("msg_y","40"));
options.insert(option("msg_shadow_xoffset", "0"));
options.insert(option("msg_shadow_yoffset", "0"));
options.insert(option("msg_shadow_color","#FFFFFF"));
options.insert(option("session_msg","Session:"));
options.insert(option("session_font","Verdana:size=16:bold"));
options.insert(option("session_color","#FFFFFF"));
options.insert(option("session_x","50%"));
options.insert(option("session_y","90%"));
options.insert(option("session_shadow_xoffset", "0"));
options.insert(option("session_shadow_yoffset", "0"));
options.insert(option("session_shadow_color","#FFFFFF"));
// What to do if the authorisation fails
options.insert(option("keep_user_on_fail", "0"));
options.insert(option("wrong_passwd_timeout", "2"));
options.insert(option("passwd_feedback_msg", "Authentication failed"));
options.insert(option("passwd_feedback_capslock", "Authentication failed (CapsLock is on)"));
options.insert(option("passwd_feedback_x", "-1")); /* no feedback by default */
options.insert(option("passwd_feedback_y", "-1"));
options.insert(option("bell", "0"));
// slimlock-specific options
options.insert(option("dpms_standby_timeout", "60"));
options.insert(option("dpms_off_timeout", "600"));
options.insert(option("show_username", "1"));
options.insert(option("show_welcome_msg", "0"));
options.insert(option("tty_lock", "1"));
error = "";
}
Cfg::~Cfg()
{
options.clear();
}
/**
* Parses known options from the given configfile / themefile
*
* @param configfile Path to configuration or theme
* @return true on sucess, false if file not found
*/
bool Cfg::readConf(string configfile)
{
int n = -1;
size_t pos = 0;
string line, next, op, fn(configfile);
map<string,string>::iterator it;
ifstream cfgfile(fn.c_str());
if (!cfgfile)
{
error = "Cannot read configuration file: " + configfile;
return false;
}
while (getline(cfgfile, line))
{
// New parser to fix ticket #4
pos = line.length();
if ( ( pos > 0 ) && ( line[pos-1] == '\\' ) )
{
line.replace ( pos-1, 1, " " );
next = next + line;
continue;
}
if ( !next.empty() )
{
line = next + line;
next = "";
}
// Ignore blank lines and comment lines
if ( line.empty() || line[0] == '#' )
continue;
// Now parse and assign
if ( !parseOption ( line ) )
cerr << error << '\n'; // not a fatal error
}
cfgfile.close();
fillSessionList();
return true;
}
/**
* Sets an option value from a line. Returns true on success.
*/
bool Cfg::parseOption ( string line )
{
size_t pos = 0;
const string delims = " \t";
string name, value;
// First split the line into a name/value pair
pos = line.find_first_of ( delims );
if ( pos == string::npos )
{
error = "Badly formed line: " + line;
return false;
}
name = line.substr ( 0, pos );
value = Trim ( line.substr ( pos ) );
if ( value.empty() )
{
error = "Badly formed line: " + line;
return false;
}
// Look to see if it's a known option
if ( options.find ( name ) == options.end() )
{
error = "Unknown option name: " + name;
return false;
}
// finally assign it
options[name] = value;
return true;
}
const string& Cfg::getError() const
{
return error;
}
string& Cfg::getOption(string option)
{
return options[option];
}
/* return a trimmed string */
string Cfg::Trim( const string& s )
{
if ( s.empty() ) {
return s;
}
int pos = 0;
string line = s;
int len = line.length();
while ( pos < len && isspace( line[pos] ) ) {
++pos;
}
line.erase( 0, pos );
pos = line.length()-1;
while ( pos > -1 && isspace( line[pos] ) ) {
--pos;
}
if ( pos != -1 ) {
line.erase( pos+1 );
}
return line;
}
/* Return the welcome message with replaced vars */
string Cfg::getWelcomeMessage()
{
string s = getOption("welcome_msg");
int n = s.find("%host");
if (n >= 0) {
string tmp = s.substr(0, n);
char host[40];
gethostname(host,40);
tmp = tmp + host;
tmp = tmp + s.substr(n+5, s.size() - n);
s = tmp;
}
n = s.find("%domain");
if (n >= 0) {
string tmp = s.substr(0, n);
char domain[40];
if ( getdomainname(domain,40) == 0 )
tmp = tmp + domain;
else
tmp = tmp + "<unknown domain>";
tmp = tmp + s.substr(n+7, s.size() - n);
s = tmp;
}
return s;
}
int Cfg::string2int(const char* string, bool* ok)
{
char* err = 0;
int l = (int)strtol(string, &err, 10);
if (ok) {
*ok = (*err == 0);
}
return (*err == 0) ? l : 0;
}
int Cfg::getIntOption(std::string option)
{
return string2int(options[option].c_str());
}
/**
* Get absolute position
*
* Converts a config position string into absolute coordinates. If the string
* is a plain number, this is just an atoi but if there is a percentage sign
* then the value is converted using the size of the canvas and the object.
*
* @param position Configured position as a string
* @param max Size of canvas in the relevant axis
* @param width Size of the object being placed
* @return Absolute coordinate to achieve placement
*/
int Cfg::absolutepos(const string& position, int max, int width)
{
int n = position.find("%");
if (n>0) { /* X Position expressed in percentage */
int result = (max*string2int(position.substr(0, n).c_str())/100) - (width / 2);
return result < 0 ? 0 : result ;
} else { /* Absolute X position */
return string2int(position.c_str());
}
}
/* split a comma separated string into a vector of strings */
void Cfg::split(vector<string>& v, const string& str, char c, bool useEmpty)
{
v.clear();
string::const_iterator s = str.begin();
string tmp;
while (true) {
string::const_iterator begin = s;
while (*s != c && s != str.end())
{
++s;
}
tmp = string(begin, s);
if (useEmpty || tmp.size() > 0)
v.push_back(tmp);
if (s == str.end()) {
break;
}
if (++s == str.end()) {
if (useEmpty)
v.push_back("");
break;
}
}
}
void Cfg::fillSessionList()
{
string strSessionList = getOption("sessions");
string strSessionDir = getOption("sessiondir");
sessions.clear();
if ( !strSessionDir.empty() )
{
DIR *pDir = opendir(strSessionDir.c_str());
if (pDir != NULL) {
struct dirent *pDirent = NULL;
while ((pDirent = readdir(pDir)) != NULL) {
string strFile(strSessionDir);
strFile += "/";
strFile += pDirent->d_name;
struct stat oFileStat;
if (stat(strFile.c_str(), &oFileStat) == 0) {
if (S_ISREG(oFileStat.st_mode) &&
access(strFile.c_str(), R_OK) == 0){
ifstream desktop_file(strFile.c_str());
if (desktop_file){
string line, session_name = "", session_exec = "";
while (getline( desktop_file, line )) {
if (line.substr(0, 5) == "Name=") {
session_name = line.substr(5);
if (!session_exec.empty())
break;
}
else if (line.substr(0, 5) == "Exec=") {
session_exec = line.substr(5);
if (!session_name.empty())
break;
}
}
desktop_file.close();
if (!session_name.empty() && !session_exec.empty()) {
pair<string,string> session(session_name,session_exec);
sessions.push_back(session);
} else if (access(strFile.c_str(), X_OK) == 0) {
pair<string,string> session(string(pDirent->d_name),strFile);
sessions.push_back(session);
}
}
}
}
}
closedir(pDir);
}
}
if (sessions.empty())
{
if (strSessionList.empty())
{
pair<string,string> session("","");
sessions.push_back(session);
}
else
{
// iterate through the split of the session list
vector<string> sessit;
split(sessit,strSessionList,',',false);
for (vector<string>::iterator it = sessit.begin(); it != sessit.end(); ++it)
{
pair<string,string> session(*it,*it);
sessions.push_back(session);
}
}
}
}
pair<string,string> Cfg::nextSession()
{
currentSession = (currentSession + 1) % sessions.size();
return sessions[currentSession];
}
/*
* Choose a theme at random from the list in the config file. IF the theme
* file cannot be found then issue a warning and try again.
*/
string Cfg::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.erase(name.length() - 1);
}
Util::srandom(Util::makeseed());
vector<string> themes;
string themefile;
Cfg::split(themes, name, ',');
do {
int sel = Util::random() % themes.size();
name = Cfg::Trim(themes[sel]);
themefile = string(THEMESDIR) +"/" + name + THEMESFILE;
if (stat(themefile.c_str(), &buf) != 0)
{
themes.erase(find(themes.begin(), themes.end(), name));
logStream << APPNAME << ": Invalid theme in config: "
<< name << endl;
name = "";
}
} while (name == "" && themes.size());
return name;
}