suckless-utils/slim/panel.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

1036 lines
28 KiB
C++

/*
* 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>
* 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 <sstream>
#include <poll.h>
#include <libgen.h>
#include <X11/extensions/Xrandr.h>
#include <X11/Xatom.h> // for XA_PIXMAP
#include <unistd.h> // for sleep
#include "const.h"
#include "image.h"
#include "log.h"
#include "cfg.h"
#include "switchuser.h"
#include "panel.h"
using namespace std;
Panel::Panel(Display* dpy, int scr, Window root, Cfg* config,
const string& themedir, PanelType panel_mode)
: cfg(config), mode(panel_mode), Dpy(dpy), Scr(scr), Root(root),
session_name(""), session_exec("")
{
if (mode == Mode_Lock)
{
Win = root;
viewport = GetPrimaryViewport();
}
else if ( mode == Mode_Test )
{
XWindowAttributes attributes;
XGetWindowAttributes(Dpy, Root, &attributes);
viewport.x = 0; // Not actually used. The window's position
viewport.y = 0; // is irrelevant to our drawing functions
viewport.width = attributes.width;
viewport.height = attributes.height;
}
else
{
/* The existing behaviour in DM mode was to always use the full
* screen size */
viewport.x = 0;
viewport.y = 0;
// AFAICT the following two lines, used in GetPrimaryViewport, do
// exactly the same as the ones that follow, which were used elsewhere
//viewport.width = DisplayWidth(Dpy, Scr);
//viewport.height = DisplayHeight(Dpy, Scr);
viewport.width = XWidthOfScreen(ScreenOfDisplay(Dpy, Scr));
viewport.height = XHeightOfScreen(ScreenOfDisplay(Dpy, Scr));
}
/* Init GC */
XGCValues gcv;
unsigned long gcm;
gcm = GCForeground|GCBackground|GCGraphicsExposures;
gcv.foreground = GetColor("black");
gcv.background = GetColor("white");
gcv.graphics_exposures = False;
TextGC = XCreateGC(Dpy, Root, gcm, &gcv);
if (mode == Mode_Lock) {
gcm = GCGraphicsExposures;
gcv.graphics_exposures = False;
WinGC = XCreateGC(Dpy, Win, gcm, &gcv);
if (WinGC == 0) {
cerr << APPNAME << ": failed to create pixmap\n.";
exit(ERR_EXIT);
}
}
// Intern _XROOTPMAP_ID property - does this belong here?
BackgroundPixmapId = XInternAtom(Dpy, "_XROOTPMAP_ID", False);
font = XftFontOpenName(Dpy, Scr, cfg->getOption("input_font").c_str());
welcomefont = XftFontOpenName(Dpy, Scr, cfg->getOption("welcome_font").c_str());
enterfont = XftFontOpenName(Dpy, Scr, cfg->getOption("username_font").c_str());
msgfont = XftFontOpenName(Dpy, Scr, cfg->getOption("msg_font").c_str());
Visual* visual = DefaultVisual(Dpy, Scr);
Colormap colormap = DefaultColormap(Dpy, Scr);
/* NOTE: using XftColorAllocValue() would be a better solution. Lazy me. */
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("input_color").c_str(), &inputcolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("input_shadow_color").c_str(), &inputshadowcolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("welcome_color").c_str(), &welcomecolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("welcome_shadow_color").c_str(), &welcomeshadowcolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("username_color").c_str(), &entercolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("username_shadow_color").c_str(), &entershadowcolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("msg_color").c_str(), &msgcolor);
XftColorAllocName(Dpy, visual, colormap, cfg->getOption("msg_shadow_color").c_str(), &msgshadowcolor);
XftColorAllocName(Dpy, visual, colormap,
cfg->getOption("session_color").c_str(), &sessioncolor);
XftColorAllocName(Dpy, visual, colormap,
cfg->getOption("session_shadow_color").c_str(), &sessionshadowcolor);
/* Load properties from config / theme */
input_name_x = cfg->getIntOption("input_name_x");
input_name_y = cfg->getIntOption("input_name_y");
input_pass_x = cfg->getIntOption("input_pass_x");
input_pass_y = cfg->getIntOption("input_pass_y");
inputShadowXOffset = cfg->getIntOption("input_shadow_xoffset");
inputShadowYOffset = cfg->getIntOption("input_shadow_yoffset");
if (input_pass_x < 0 || input_pass_y < 0){ /* single inputbox mode */
input_pass_x = input_name_x;
input_pass_y = input_name_y;
}
/* Load panel and background image */
string panelpng = "";
panelpng = panelpng + themedir +"/panel.png";
image = new Image;
bool loaded = image->Read(panelpng.c_str());
if (!loaded) { /* try jpeg if png failed */
panelpng = themedir + "/panel.jpg";
loaded = image->Read(panelpng.c_str());
if (!loaded) {
logStream << APPNAME
<< ": could not load panel image for theme '"
<< basename((char*)themedir.c_str()) << "'"
<< endl;
exit(ERR_EXIT);
}
}
bgImg = new Image();
string bgstyle = cfg->getOption("background_style");
if (bgstyle != "color") {
panelpng = themedir +"/background.png";
loaded = bgImg->Read(panelpng.c_str());
if (!loaded) { /* try jpeg if png failed */
panelpng = themedir + "/background.jpg";
loaded = bgImg->Read(panelpng.c_str());
if (!loaded){
logStream << APPNAME
<< ": could not load background image for theme '"
<< basename((char*)themedir.c_str()) << "'"
<< endl;
exit(ERR_EXIT);
}
}
}
if (bgstyle == "stretch")
bgImg->Resize(viewport.width, viewport.height);
else if (bgstyle == "tile")
bgImg->Tile(viewport.width, viewport.height);
else if (bgstyle == "center") {
string hexvalue = cfg->getOption("background_color");
hexvalue = hexvalue.substr(1,6);
bgImg->Center(viewport.width,
viewport.height,
hexvalue.c_str());
} else { // plain color or error
string hexvalue = cfg->getOption("background_color");
hexvalue = hexvalue.substr(1,6);
bgImg->Center(viewport.width,
viewport.height,
hexvalue.c_str());
}
string cfgX = cfg->getOption("input_panel_x");
string cfgY = cfg->getOption("input_panel_y");
X = Cfg::absolutepos(cfgX, viewport.width, image->Width());
Y = Cfg::absolutepos(cfgY, viewport.height, image->Height());
if (mode == Mode_Lock) {
// In slimlock, the window we draw on is always the root, not the
// XSimpleWindow created by OpenPanel to represent the panel area.
// Thus we apply a hack to offset the input positions. It's a shame
// we don't do the job properly, as this hack isn't applied to any
// of the static text
input_name_x += X;
input_name_y += Y;
input_pass_x += X;
input_pass_y += Y;
}
if (mode == Mode_Lock) {
/* Merge image into background without crop, so that PanelPixmap is
* the whole screen (background image with panel drawn on it) */
image->Merge_non_crop(bgImg, X, Y);
} else {
/* Merge image with cropped background, so that PanelPixmap is the
* panel with the relevant part of the X root image included instead
* of the alpha channel */
image->Merge(bgImg, X, Y);
}
PanelPixmap = image->createPixmap(Dpy, Scr, Root);
/* Read (and substitute vars in) the welcome message */
welcome_message = cfg->getWelcomeMessage();
if (mode == Mode_Lock) {
SetName(getenv("USER"));
field = Get_Passwd;
OnExpose();
}
MsgExtents.width = 0;
}
Panel::~Panel()
{
Visual* visual = DefaultVisual(Dpy, Scr);
Colormap colormap = DefaultColormap(Dpy, Scr);
XftColorFree(Dpy, visual, colormap, &inputcolor);
XftColorFree(Dpy, visual, colormap, &inputshadowcolor);
XftColorFree(Dpy, visual, colormap, &welcomecolor);
XftColorFree(Dpy, visual, colormap, &welcomeshadowcolor);
XftColorFree(Dpy, visual, colormap, &entercolor);
XftColorFree(Dpy, visual, colormap, &entershadowcolor);
XftColorFree(Dpy, visual, colormap, &msgcolor);
XftColorFree(Dpy, visual, colormap, &msgshadowcolor);
XftColorFree(Dpy, visual, colormap, &sessioncolor);
XftColorFree(Dpy, visual, colormap, &sessionshadowcolor);
XFreeGC(Dpy, TextGC);
XftFontClose(Dpy, font);
XftFontClose(Dpy, msgfont);
XftFontClose(Dpy, welcomefont);
XftFontClose(Dpy, enterfont);
if (mode == Mode_Lock)
XFreeGC(Dpy, WinGC);
delete bgImg;
delete image;
}
/**
* Set the (previously loaded and adjusted) background image as the window
* background for the root window.
*/
void Panel::setBackground(void)
{
Pixmap p = bgImg->createPixmap(Dpy, Scr, Root);
XSetWindowBackgroundPixmap(Dpy, Root, p);
XChangeProperty(Dpy, Root, BackgroundPixmapId, XA_PIXMAP, 32,
PropModeReplace, (unsigned char *)&p, 1);
XClearWindow(Dpy, Root);
XFlush(Dpy);
}
/* Hide the cursor */
void Panel::HideCursor()
{
if (cfg->getOption("hidecursor") == "true")
{
XColor black;
char cursordata[1];
Pixmap cursorpixmap;
Cursor cursor;
cursordata[0]=0;
cursorpixmap = XCreateBitmapFromData(Dpy, Root, cursordata, 1, 1);
black.red=0;
black.green=0;
black.blue=0;
cursor = XCreatePixmapCursor(Dpy, cursorpixmap, cursorpixmap, &black, &black, 0, 0);
//XFreePixmap(dpy, cursorpixmap); // man page is confusing as to whether this is right
XDefineCursor(Dpy, Root, cursor);
}
}
/* Open the login panel. Not used by slimlock */
void Panel::OpenPanel()
{
/* Create window */
Win = XCreateSimpleWindow(Dpy, Root, X, Y,
image->Width(),
image->Height(),
0, GetColor("white"), GetColor("white"));
/* Events */
XSelectInput(Dpy, Win, ExposureMask | KeyPressMask);
/* Set background */
XSetWindowBackgroundPixmap(Dpy, Win, PanelPixmap);
/* Show window */
XMapWindow(Dpy, Win);
XMoveWindow(Dpy, Win, X, Y); /* override wm positioning (for tests) */
/* Grab keyboard */
XGrabKeyboard(Dpy, Win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
XFlush(Dpy);
}
void Panel::ClosePanel()
{
XUngrabKeyboard(Dpy, CurrentTime);
XUnmapWindow(Dpy, Win);
XDestroyWindow(Dpy, Win);
XFlush(Dpy);
}
void Panel::WrongPassword(int timeout)
{
string message;
if ( mode != Mode_Lock )
{
XClearWindow(Dpy, Root);
}
#if 0
if (CapsLockOn)
message = cfg->getOption("passwd_feedback_capslock");
else
#endif
message = cfg->getOption("passwd_feedback_msg");
XftDraw *draw = XftDrawCreate ( Dpy, Root,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr) );
XftTextExtents8(Dpy, msgfont, reinterpret_cast<const XftChar8*>(message.c_str()),
message.length(), &MsgExtents);
string cfgX = cfg->getOption("passwd_feedback_x");
string cfgY = cfg->getOption("passwd_feedback_y");
int shadowXOffset = cfg->getIntOption("msg_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("msg_shadow_yoffset");
int msg_x = Cfg::absolutepos(cfgX, viewport.width, MsgExtents.width);
int msg_y = Cfg::absolutepos(cfgY, viewport.height, MsgExtents.height);
MsgExtents.x = msg_x;
MsgExtents.y = msg_y - MsgExtents.height;
if ( timeout > 0 )
{
OnExpose();
if ( msg_x >= 0 && msg_y >= 0 )
SlimDrawString8(draw, &msgcolor, msgfont, msg_x, msg_y, message,
&msgshadowcolor, shadowXOffset, shadowYOffset);
if (cfg->getOption("bell") == "1")
XBell(Dpy, 100);
XFlush(Dpy);
sleep(timeout);
}
ResetPasswd();
if ( mode != Mode_Lock )
{
if ( cfg->getIntOption("keep_user_on_fail") == 0 )
{
ResetName();
}
field = Get_Name;
}
OnExpose();
// The message should stay on the screen even after the password field is
// cleared, methinks. I don't like this solution, but it works.
if ( msg_x >= 0 && msg_y >= 0 )
SlimDrawString8(draw, &msgcolor, msgfont, msg_x, msg_y, message,
&msgshadowcolor, shadowXOffset, shadowYOffset);
XSync(Dpy, True);
XftDrawDestroy(draw);
}
void Panel::Message(const string& text)
{
string cfgX, cfgY;
XGlyphInfo extents;
XftDraw *draw;
// The message positions are screen-relative, not panel-relative
draw = XftDrawCreate(Dpy, Root,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
XftTextExtents8(Dpy, msgfont,
reinterpret_cast<const XftChar8*>(text.c_str()),
text.length(), &extents);
cfgX = cfg->getOption("msg_x");
cfgY = cfg->getOption("msg_y");
int shadowXOffset = cfg->getIntOption("msg_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("msg_shadow_yoffset");
int msg_x, msg_y;
msg_x = Cfg::absolutepos(cfgX, viewport.width, extents.width);
msg_y = Cfg::absolutepos(cfgY, viewport.height, extents.height);
SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y,
text,
&msgshadowcolor,
shadowXOffset, shadowYOffset);
XFlush(Dpy);
XftDrawDestroy(draw);
}
unsigned long Panel::GetColor(const char* colorname)
{
XColor color;
XWindowAttributes attributes;
XGetWindowAttributes(Dpy, Root, &attributes);
color.pixel = 0;
if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
logStream << APPNAME << ": can't parse color " << colorname << endl;
else if(!XAllocColor(Dpy, attributes.colormap, &color))
logStream << APPNAME << ": can't allocate color " << colorname << endl;
return color.pixel;
}
void Panel::TextCursor(int visible)
{
const char* text = NULL;
int xx = 0, yy = 0, y2 = 0, cheight = 0;
const char* txth = "Wj"; /* used to get cursor height */
// The constructor and other conditionals guarantee that
// if (mode == Mode_Lock) field = Get_Passwd;
switch(field) {
case Get_Passwd:
text = HiddenPasswdBuffer.c_str();
xx = input_pass_x;
yy = input_pass_y;
break;
case Get_Name:
text = NameBuffer.c_str();
xx = input_name_x;
yy = input_name_y;
break;
}
XGlyphInfo extents;
XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
cheight = extents.height;
y2 = yy - extents.y + extents.height;
XftTextExtents8(Dpy, font, (XftChar8*)text, strlen(text), &extents);
xx += extents.width;
if(visible == SHOW) {
if (mode == Mode_Lock) {
xx += viewport.x;
yy += viewport.y;
y2 += viewport.y;
}
XSetForeground(Dpy, TextGC,
GetColor(cfg->getOption("input_color").c_str()));
XDrawLine(Dpy, Win, TextGC,
xx+1, yy-cheight,
xx+1, y2);
} else {
if (mode == Mode_Lock)
ApplyBackground(Rectangle(xx+1, yy-cheight,
1, y2-(yy-cheight)+1));
else
XClearArea(Dpy, Win, xx+1, yy-cheight,
1, y2-(yy-cheight)+1, false);
}
}
void Panel::EventHandler(const Panel::FieldType& curfield)
{
XEvent event;
field = curfield;
bool loop = true;
if ( (mode != Mode_Lock) && ( MsgExtents.width == 0 ) )
OnExpose();
struct pollfd x11_pfd = {0};
x11_pfd.fd = ConnectionNumber(Dpy);
x11_pfd.events = POLLIN;
while (loop)
{
if (XPending(Dpy) || poll(&x11_pfd, 1, -1) > 0)
{
while(XPending(Dpy))
{
XNextEvent(Dpy, &event);
switch(event.type)
{
case Expose:
OnExpose();
break;
case KeyPress:
loop=OnKeyPress(event);
break;
}
}
if ( MsgExtents.width > 0 )
{
if (mode == Mode_Lock)
ApplyBackground(Rectangle(MsgExtents.x,
MsgExtents.y, MsgExtents.width+1,
MsgExtents.height+2));
else
XClearArea(Dpy, Root, MsgExtents.x, MsgExtents.y,
MsgExtents.width+1, MsgExtents.height+2, false);
MsgExtents.width = 0;
}
}
}
return;
}
void Panel::OnExpose(void)
{
XftDraw *draw = XftDrawCreate(Dpy, Win,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
if (mode == Mode_Lock)
ApplyBackground();
else
XClearWindow(Dpy, Win);
if (input_pass_x != input_name_x || input_pass_y != input_name_y){
SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y,
NameBuffer,
&inputshadowcolor,
inputShadowXOffset, inputShadowYOffset);
SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y,
HiddenPasswdBuffer,
&inputshadowcolor,
inputShadowXOffset, inputShadowYOffset);
} else { /*single input mode */
switch(field) {
case Get_Passwd:
SlimDrawString8 (draw, &inputcolor, font,
input_pass_x, input_pass_y,
HiddenPasswdBuffer,
&inputshadowcolor,
inputShadowXOffset, inputShadowYOffset);
break;
case Get_Name:
SlimDrawString8 (draw, &inputcolor, font,
input_name_x, input_name_y,
NameBuffer,
&inputshadowcolor,
inputShadowXOffset, inputShadowYOffset);
break;
}
}
XftDrawDestroy (draw);
TextCursor(SHOW);
ShowText();
}
void Panel::EraseLastChar(std::string &formerString)
{
switch(field) {
case Get_Name:
if (! NameBuffer.empty()) {
formerString=NameBuffer;
NameBuffer.erase(--NameBuffer.end());
}
break;
case Get_Passwd:
if (!PasswdBuffer.empty()) {
formerString=HiddenPasswdBuffer;
PasswdBuffer.erase(--PasswdBuffer.end());
HiddenPasswdBuffer.erase(--HiddenPasswdBuffer.end());
}
break;
}
}
bool Panel::OnKeyPress(XEvent& event)
{
char ascii;
KeySym keysym;
XComposeStatus compstatus;
int xx = 0;
int yy = 0;
string text;
string formerString = "";
XLookupString(&event.xkey, &ascii, 1, &keysym, &compstatus);
switch(keysym){
case XK_F1:
if ( mode != Mode_Lock ) // Can't change session in a screen lock
SwitchSession();
return true;
case XK_F11:
/* Take a screenshot */
if ( system(cfg->getOption("screenshot_cmd").c_str()) < 0 )
logStream << APPNAME << ": screenshot_cmd failed" << endl;
return true;
case XK_Return:
case XK_KP_Enter:
if (field==Get_Name)
{
/* Don't allow an empty username */
if (NameBuffer.empty())
return true;
if (NameBuffer==CONSOLE_STR)
action = Console;
else if (NameBuffer==HALT_STR)
action = Halt;
else if (NameBuffer==REBOOT_STR)
action = Reboot;
else if (NameBuffer==SUSPEND_STR)
action = Suspend;
else if (NameBuffer==EXIT_STR)
action = Exit;
else
action = Login;
}
return false;
default:
break;
}
TextCursor(HIDE);
switch(keysym){
case XK_Delete:
case XK_BackSpace:
EraseLastChar(formerString);
break;
case XK_w:
case XK_u:
if (reinterpret_cast<XKeyEvent&>(event).state & ControlMask)
{
switch(field) {
case Get_Passwd:
formerString = HiddenPasswdBuffer;
HiddenPasswdBuffer.clear();
PasswdBuffer.clear();
break;
case Get_Name:
formerString = NameBuffer;
NameBuffer.clear();
break;
}
break;
}
/* Deliberate fall-through */
case XK_h:
if (reinterpret_cast<XKeyEvent&>(event).state & ControlMask)
{
EraseLastChar(formerString);
break;
}
/* Deliberate fall-through */
default:
if (isprint(ascii) && (keysym < XK_Shift_L || keysym > XK_Hyper_R))
{
switch(field) {
case Get_Name:
formerString=NameBuffer;
if (NameBuffer.length() < INPUT_MAXLENGTH_NAME-1)
{
NameBuffer.append(&ascii,1);
}
break;
case Get_Passwd:
formerString=HiddenPasswdBuffer;
if (PasswdBuffer.length() < INPUT_MAXLENGTH_PASSWD-1)
{
PasswdBuffer.append(&ascii,1);
HiddenPasswdBuffer.append("*");
}
break;
}
}
else
{ // *RP* I think this is to fix the fake bolding if the user presses TAB
return true; //nodraw if notchange
}
break;
}
XGlyphInfo extents;
XftDraw *draw = XftDrawCreate(Dpy, Win,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
switch(field) {
case Get_Name:
text = NameBuffer;
xx = input_name_x;
yy = input_name_y;
break;
case Get_Passwd:
text = HiddenPasswdBuffer;
xx = input_pass_x;
yy = input_pass_y;
break;
}
if (!formerString.empty())
{
const char* txth = "Wj"; /* get proper maximum height ? */
XftTextExtents8(Dpy, font,
reinterpret_cast<const XftChar8*>(txth), strlen(txth), &extents);
int maxHeight = extents.height;
XftTextExtents8(Dpy, font,
reinterpret_cast<const XftChar8*>(formerString.c_str()),
formerString.length(), &extents);
int maxLength = extents.width;
if (mode == Mode_Lock)
ApplyBackground(Rectangle(input_pass_x - 3,
input_pass_y - maxHeight - 3,
maxLength + 6, maxHeight + 6));
else
XClearArea(Dpy, Win, xx - 3, yy-maxHeight - 3,
maxLength + 6, maxHeight + 6, false);
}
if (!text.empty())
{
SlimDrawString8 (draw, &inputcolor, font, xx, yy,
text,
&inputshadowcolor,
inputShadowXOffset, inputShadowYOffset);
}
XftDrawDestroy (draw);
TextCursor(SHOW);
return true;
}
/* Draw welcome and "enter username" messages */
void Panel::ShowText()
{
string cfgX, cfgY;
XGlyphInfo extents;
bool singleInputMode = ( input_name_x == input_pass_x
&& input_name_y == input_pass_y );
/// @bug this draw context is assumed relative to the panel but in lock
/// mode it's actually relative to the background
XftDraw *draw = XftDrawCreate(Dpy, Win,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
/* welcome message */
XftTextExtents8(Dpy, welcomefont, (XftChar8*)welcome_message.c_str(),
strlen(welcome_message.c_str()), &extents);
cfgX = cfg->getOption("welcome_x");
cfgY = cfg->getOption("welcome_y");
int shadowXOffset = cfg->getIntOption("welcome_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("welcome_shadow_yoffset");
welcome_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
welcome_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
if (welcome_x >= 0 && welcome_y >= 0) {
SlimDrawString8 (draw, &welcomecolor, welcomefont,
welcome_x, welcome_y,
welcome_message,
&welcomeshadowcolor, shadowXOffset, shadowYOffset);
}
/* Enter username-password message */
string msg;
if (!singleInputMode|| field == Get_Passwd) {
msg = cfg->getOption("password_msg");
XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
strlen(msg.c_str()), &extents);
cfgX = cfg->getOption("password_x");
cfgY = cfg->getOption("password_y");
int shadowXOffset = cfg->getIntOption("username_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("username_shadow_yoffset");
password_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
password_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
if (password_x >= 0 && password_y >= 0){
SlimDrawString8 (draw, &entercolor, enterfont, password_x, password_y,
msg, &entershadowcolor, shadowXOffset, shadowYOffset);
}
}
if ((!singleInputMode|| field == Get_Name) && mode != Mode_Lock ) {
msg = cfg->getOption("username_msg");
XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
strlen(msg.c_str()), &extents);
cfgX = cfg->getOption("username_x");
cfgY = cfg->getOption("username_y");
int shadowXOffset = cfg->getIntOption("username_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("username_shadow_yoffset");
username_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
username_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
if (username_x >= 0 && username_y >= 0){
SlimDrawString8 (draw, &entercolor, enterfont, username_x, username_y,
msg, &entershadowcolor, shadowXOffset, shadowYOffset);
}
}
XftDrawDestroy(draw);
if (mode == Mode_Lock) {
// If only the password box is visible, draw the user name somewhere too
string user_msg = "User: " + GetName();
int show_username = cfg->getIntOption("show_username");
if (singleInputMode && show_username) {
Message(user_msg);
}
}
}
string Panel::getSession()
{
return session_exec;
}
/* choose next available session type. Not used in lock mode */
void Panel::SwitchSession()
{
pair<string,string> ses = cfg->nextSession();
session_name = ses.first;
session_exec = ses.second;
if (session_name.size() > 0) {
ShowSession();
}
}
/* Display session type on the screen. Not used in lock mode */
void Panel::ShowSession()
{
string msg_x, msg_y;
XClearWindow(Dpy, Root);
string currsession = cfg->getOption("session_msg") + " " + session_name;
XGlyphInfo extents;
sessionfont = XftFontOpenName(Dpy, Scr, cfg->getOption("session_font").c_str());
XftDraw *draw = XftDrawCreate(Dpy, Root,
DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
XftTextExtents8(Dpy, sessionfont, reinterpret_cast<const XftChar8*>(currsession.c_str()),
currsession.length(), &extents);
msg_x = cfg->getOption("session_x");
msg_y = cfg->getOption("session_y");
int x = Cfg::absolutepos(msg_x, viewport.width, extents.width);
int y = Cfg::absolutepos(msg_y, viewport.height, extents.height);
int shadowXOffset = cfg->getIntOption("session_shadow_xoffset");
int shadowYOffset = cfg->getIntOption("session_shadow_yoffset");
SlimDrawString8(draw, &sessioncolor, sessionfont, x, y,
currsession,
&sessionshadowcolor,
shadowXOffset, shadowYOffset);
XFlush(Dpy);
XftDrawDestroy(draw);
}
void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font,
int x, int y, const string& str,
XftColor* shadowColor,
int xOffset, int yOffset)
{
if (mode == Mode_Lock) {
x += viewport.x;
y += viewport.y;
}
if (xOffset && yOffset) {
XftDrawStringUtf8(d, shadowColor, font, x+xOffset, y+yOffset,
reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
}
XftDrawStringUtf8(d, color, font, x, y,
reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
}
Panel::ActionType Panel::getAction(void) const
{
return action;
}
void Panel::Reset(void)
{
ResetName();
ResetPasswd();
}
void Panel::ResetName(void)
{
NameBuffer.clear();
}
void Panel::ResetPasswd(void)
{
PasswdBuffer.clear();
HiddenPasswdBuffer.clear();
}
/* Pre-load the user name input box with the provided string.
* Used to set the default user, if so configured, and by slimlock to set the
* currently logged-in user.
*/
void Panel::SetName(const string& name)
{
NameBuffer=name;
action = Login;
}
const string& Panel::GetName(void) const
{
return NameBuffer;
}
const string& Panel::GetPasswd(void) const
{
return PasswdBuffer;
}
/**
* Identify the viewport (physical screen?) to draw on. This allows slimlock
* to handle Xinerama-type multi-monitor setups. Not currently used by slim
*/
Rectangle Panel::GetPrimaryViewport()
{
Rectangle fallback;
Rectangle result;
RROutput primary;
XRROutputInfo *primary_info;
XRRScreenResources *resources;
XRRCrtcInfo *crtc_info;
int crtc;
fallback.x = 0;
fallback.y = 0;
fallback.width = DisplayWidth(Dpy, Scr);
fallback.height = DisplayHeight(Dpy, Scr);
resources = XRRGetScreenResources(Dpy, Win);
if (!resources)
{
cerr << "XRRGetScreenResources failed\n";
return fallback;
}
primary = XRRGetOutputPrimary(Dpy, Win);
if (!primary) {
// No "primary" defined (by the WM, usually) but could still have
// multiple monitors or setups, so default to the first output.
primary = resources->outputs[0];
}
primary_info = XRRGetOutputInfo(Dpy, resources, primary);
if (!primary_info) {
cerr << "XRRGetOutputInfo failed\n";
XRRFreeScreenResources(resources);
return fallback;
}
// Fixes bug with multiple monitors. Just pick first monitor if
// XRRGetOutputInfo gives returns bad value for crtc.
if (primary_info->crtc < 1) {
if (primary_info->ncrtc > 0) {
crtc = primary_info->crtcs[0];
} else {
cerr << "Cannot get crtc from xrandr.\n";
exit(EXIT_FAILURE);
}
} else {
crtc = primary_info->crtc;
}
crtc_info = XRRGetCrtcInfo(Dpy, resources, crtc);
if (!crtc_info) {
cerr << "XRRGetCrtcInfo failed\n";
XRRFreeOutputInfo(primary_info);
XRRFreeScreenResources(resources);
return fallback;
}
result.x = crtc_info->x;
result.y = crtc_info->y;
result.width = crtc_info->width;
result.height = crtc_info->height;
XRRFreeCrtcInfo(crtc_info);
XRRFreeOutputInfo(primary_info);
XRRFreeScreenResources(resources);
return result;
}
/**
* Re-draw the background over a rectangle. This method is only used in "lock"
* mode - the DM mode uses XClearArea instead.
*/
void Panel::ApplyBackground(Rectangle rect)
{
int ret = 0;
if (rect.is_empty()) {
rect.x = 0;
rect.y = 0;
rect.width = viewport.width;
rect.height = viewport.height;
}
ret = XCopyArea(Dpy, PanelPixmap, Win, WinGC,
rect.x, rect.y, rect.width, rect.height,
viewport.x + rect.x, viewport.y + rect.y);
if (!ret)
cerr << APPNAME << ": failed to put pixmap on the screen\n.";
}