diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b1ab59..c17fadd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,26 +45,49 @@ set(SLIM_DEFINITIONS ${SLIM_DEFINITIONS} "-DSYSCONFDIR=\"${SYSCONFDIR}\"") set(slim_srcs main.cpp app.cpp - cfg.cpp - image.cpp numlock.cpp - panel.cpp switchuser.cpp - util.cpp - log.cpp png.c jpeg.c - coord.cpp ) +set(slimlock_srcs + slimlock.cpp +) + +set(common_srcs + cfg.cpp + image.cpp + log.cpp + panel.cpp + util.cpp + coord.cpp +) if(USE_PAM) - set(slim_srcs ${slim_srcs} PAM.cpp) + set(common_srcs ${common_srcs} PAM.cpp) + # for now, only build slimlock if we are using PAM. + set(BUILD_SLIMLOCK 1) endif(USE_PAM) + +# Build common library +set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries") + +if (BUILD_SHARED_LIBS) + message(STATUS "Enable shared library building") + add_library(libslim ${common_srcs}) +else(BUILD_SHARED_LIBS) + message(STATUS "Disable shared library building") + add_library(libslim STATIC ${common_srcs}) +endif(BUILD_SHARED_LIBS) + if(USE_CONSOLEKIT) set(slim_srcs ${slim_srcs} Ck.cpp) endif(USE_CONSOLEKIT) add_executable(${PROJECT_NAME} ${slim_srcs}) +if(BUILD_SLIMLOCK) + add_executable(slimlock ${slimlock_srcs}) +endif(BUILD_SLIMLOCK) #Set the custom CMake module directory where our include/lib finders are set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") @@ -91,6 +114,8 @@ if(USE_PAM) message("\tPAM Found") set(SLIM_DEFINITIONS ${SLIM_DEFINITIONS} "-DUSE_PAM") target_link_libraries(${PROJECT_NAME} ${PAM_LIBRARY}) + target_link_libraries(libslim ${PAM_LIBRARY}) + target_link_libraries(slimlock ${PAM_LIBRARY}) include_directories(${PAM_INCLUDE_DIR}) else(PAM_FOUND) message("\tPAM Not Found") @@ -130,6 +155,7 @@ endif(USE_CONSOLEKIT) find_library(M_LIB m) find_library(RT_LIB rt) find_library(CRYPTO_LIB crypt) +find_package(Threads) add_definitions(${SLIM_DEFINITIONS}) @@ -138,6 +164,7 @@ include_directories( ${X11_INCLUDE_DIR} ${X11_Xft_INCLUDE_PATH} ${X11_Xrender_INCLUDE_PATH} + ${X11_Xrandr_INCLUDE_PATH} ${FREETYPE_INCLUDE_DIRS} ${X11_Xmu_INCLUDE_PATH} ${ZLIB_INCLUDE_DIR} @@ -145,7 +172,15 @@ include_directories( ${PNG_INCLUDE_DIR} ) -#Set up library with all found packages +target_link_libraries(libslim + ${RT_LIB} + ${X11_Xft_LIB} + ${X11_Xrandr_LIB} + ${JPEG_LIBRARIES} + ${PNG_LIBRARIES} +) + +#Set up library with all found packages for slim target_link_libraries(${PROJECT_NAME} ${M_LIB} ${RT_LIB} @@ -153,18 +188,60 @@ target_link_libraries(${PROJECT_NAME} ${X11_X11_LIB} ${X11_Xft_LIB} ${X11_Xrender_LIB} + ${X11_Xrandr_LIB} ${X11_Xmu_LIB} ${FREETYPE_LIBRARY} ${JPEG_LIBRARIES} ${PNG_LIBRARIES} - ) + libslim +) + +if(BUILD_SLIMLOCK) + #Set up library with all found packages for slimlock + target_link_libraries(slimlock + ${M_LIB} + ${RT_LIB} + ${CRYPTO_LIB} + ${X11_X11_LIB} + ${X11_Xft_LIB} + ${X11_Xrender_LIB} + ${X11_Xrandr_LIB} + ${X11_Xmu_LIB} + ${X11_Xext_LIB} + ${FREETYPE_LIBRARY} + ${JPEG_LIBRARIES} + ${PNG_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + libslim + ) +endif(BUILD_SLIMLOCK) ####### install # slim install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin ) +install(TARGETS slimlock RUNTIME DESTINATION bin) + +if (BUILD_SHARED_LIBS) + set_target_properties(libslim PROPERTIES + OUTPUT_NAME slim + SOVERSION ${SLIM_VERSION}) + + install(TARGETS libslim + LIBRARY DESTINATION lib${LIB_SUFFIX} + ARCHIVE DESTINATION lib${LIB_SUFFIX} + ) +endif (BUILD_SHARED_LIBS) + # man file install(FILES slim.1 DESTINATION ${MANDIR}/man1/) # configure install(FILES slim.conf DESTINATION ${SYSCONFDIR}) + +#slimlock +if(BUILD_SLIMLOCK) +install(TARGETS slimlock RUNTIME DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID) +install(FILES slimlock.1 DESTINATION ${MANDIR}/man1/) +endif(BUILD_SLIMLOCK) + # themes directory subdirs(themes) diff --git a/app.cpp b/app.cpp index 7f00364..0d48dd8 100644 --- a/app.cpp +++ b/app.cpp @@ -300,6 +300,9 @@ void App::Run() { Scr = DefaultScreen(Dpy); Root = RootWindow(Dpy, Scr); + // Intern _XROOTPMAP_ID property + BackgroundPixmapId = XInternAtom(Dpy, "_XROOTPMAP_ID", False); + // for tests we use a standard window if (testing) { Window RealRoot = RootWindow(Dpy, Scr); @@ -313,7 +316,7 @@ void App::Run() { HideCursor(); // Create panel - LoginPanel = new Panel(Dpy, Scr, Root, cfg, themedir); + LoginPanel = new Panel(Dpy, Scr, Root, cfg, themedir, Panel::Mode_DM); bool firstloop = true; // 1st time panel is shown (for automatic username) bool focuspass = cfg->getOption("focus_password")=="yes"; bool autologin = cfg->getOption("auto_login")=="yes"; @@ -1092,6 +1095,8 @@ void App::setBackground(const string& themedir) { } Pixmap p = image->createPixmap(Dpy, Scr, Root); XSetWindowBackgroundPixmap(Dpy, Root, p); + XChangeProperty(Dpy, Root, BackgroundPixmapId, XA_PIXMAP, 32, + PropModeReplace, (unsigned char *)&p, 1); } XClearWindow(Dpy, Root); diff --git a/app.h b/app.h index 12a2eb2..1ed50c3 100644 --- a/app.h +++ b/app.h @@ -13,6 +13,7 @@ #define _APP_H_ #include +#include #include #include #include @@ -99,6 +100,7 @@ class App { void blankScreen(); Image* image; + Atom BackgroundPixmapId; void setBackground(const std::string& themedir); bool firstlogin; diff --git a/cfg.cpp b/cfg.cpp index 21795fa..599e7c9 100644 --- a/cfg.cpp +++ b/cfg.cpp @@ -1,6 +1,7 @@ /* SLiM - Simple Login Manager Copyright (C) 2004-06 Simone Rota Copyright (C) 2004-06 Johannes Winkelmann + Copyright (C) 2012-13 Nobuhiro Iwamatsu 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 @@ -120,6 +121,19 @@ Cfg::Cfg() options.insert(option("session_shadow_yoffset", "0")); options.insert(option("session_shadow_color","#FFFFFF")); + // slimlock-specific options + options.insert(option("dpms_standby_timeout", "60")); + options.insert(option("dpms_off_timeout", "600")); + options.insert(option("wrong_passwd_timeout", "2")); + options.insert(option("passwd_feedback_x", "50%")); + options.insert(option("passwd_feedback_y", "10%")); + options.insert(option("passwd_feedback_msg", "Authentication failed")); + options.insert(option("passwd_feedback_capslock", "Authentication failed (CapsLock is on)")); + options.insert(option("show_username", "1")); + options.insert(option("show_welcome_msg", "0")); + options.insert(option("tty_lock", "1")); + options.insert(option("bell", "1")); + error = ""; } diff --git a/image.cpp b/image.cpp index 2b13f57..21b4ad8 100644 --- a/image.cpp +++ b/image.cpp @@ -288,6 +288,59 @@ void Image::Merge(Image* background, const int x, const int y) { } +/* Merge the image with a background, taking care of the + * image Alpha transparency. (background alpha is ignored). + * The images is merged on position (x, y) on the + * background, the background must contain the image. + */ +#define IMG_POS_RGB(p, x) (3 * p + x) +void Image::Merge_non_crop(Image* background, const int x, const int y) +{ + int bg_w = background->Width(); + int bg_h = background->Height(); + + if (x + width > bg_w || y + height > bg_h) + return; + + double tmp; + unsigned char *new_rgb = (unsigned char *)malloc(3 * bg_w * bg_h); + const unsigned char *bg_rgb = background->getRGBData(); + int pnl_pos = 0; + int bg_pos = 0; + int pnl_w_end = x + width; + int pnl_h_end = y + height; + + memcpy(new_rgb, bg_rgb, 3 * bg_w * bg_h); + + for (int j = 0; j < bg_h; j++) { + for (int i = 0; i < bg_w; i++) { + if (j >= y && i >= x && j < pnl_h_end && i < pnl_w_end ) { + for (int k = 0; k < 3; k++) { + if (png_alpha != NULL) + tmp = rgb_data[IMG_POS_RGB(pnl_pos, k)] + * png_alpha[pnl_pos]/255.0 + + bg_rgb[IMG_POS_RGB(bg_pos, k)] + * (1 - png_alpha[pnl_pos]/255.0); + else + tmp = rgb_data[IMG_POS_RGB(pnl_pos, k)]; + + new_rgb[IMG_POS_RGB(bg_pos, k)] = static_cast(tmp); + } + pnl_pos++; + } + bg_pos++; + } + } + + width = bg_w; + height = bg_h; + + free(rgb_data); + free(png_alpha); + rgb_data = new_rgb; + png_alpha = NULL; +} + /* Tile the image growing its size to the minimum entire * multiple of w * h. * The new dimensions should be > of the current ones. diff --git a/image.h b/image.h index 02506c3..b816424 100644 --- a/image.h +++ b/image.h @@ -53,6 +53,7 @@ class Image { void Reduce(const int factor); void Resize(const int w, const int h); void Merge(Image* background, const int x, const int y); + void Merge_non_crop(Image* background, const int x, const int y); void Crop(const int x, const int y, const int w, const int h); void Tile(const int w, const int h); void Center(const int w, const int h, const char *hex); diff --git a/panel.cpp b/panel.cpp index e8dabe4..691df7d 100644 --- a/panel.cpp +++ b/panel.cpp @@ -11,17 +11,22 @@ #include #include +#include #include "panel.h" using namespace std; -Panel::Panel(Display* dpy, int scr, Window root, Cfg* config, const string& themedir) - : Dpy(dpy), Scr(scr), Root(root), cfg(config), session_name(""), session_exec(""), +Panel::Panel(Display* dpy, int scr, Window root, Cfg* config, const string& themedir, PanelType panel_mode) + : Dpy(dpy), Scr(scr), Root(root), cfg(config), mode(panel_mode), session_name(""), session_exec(""), // Load properties from config / theme input_name(cfg->getIntOption("input_name_x"), cfg->getIntOption("input_name_y")), input_pass(cfg->getIntOption("input_pass_x"), cfg->getIntOption("input_pass_y")), inputShadowOffset(cfg->getIntOption("input_shadow_xoffset"), cfg->getIntOption("input_shadow_yoffset")) { + if (mode == Mode_Lock) { + Win = root; + viewport = GetPrimaryViewport(); + } // Init GC XGCValues gcv; @@ -29,7 +34,18 @@ Panel::Panel(Display* dpy, int scr, Window root, Cfg* config, const string& them gcv.foreground = GetColor("black"); gcv.background = GetColor("white"); gcv.graphics_exposures = False; - TextGC = XCreateGC(Dpy, Root, gcm, &gcv); + if (mode == Mode_Lock) { + TextGC = XCreateGC(Dpy, Win, gcm, &gcv); + 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); + } + } else { + TextGC = XCreateGC(Dpy, Root, gcm, &gcv); + } font = XftFontOpenName(Dpy, Scr, cfg->getOption("input_font").c_str()); welcomefont = XftFontOpenName(Dpy, Scr, cfg->getOption("welcome_font").c_str()); @@ -93,44 +109,93 @@ Panel::Panel(Display* dpy, int scr, Window root, Cfg* config, const string& them } } } - if (bgstyle == "stretch") { - bg->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr))); - } else if (bgstyle == "tile") { - bg->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr))); - } else if (bgstyle == "center") { - string hexvalue = cfg->getOption("background_color"); - hexvalue = hexvalue.substr(1,6); - bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), - XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), - hexvalue.c_str()); - } else { // plain color or error - string hexvalue = cfg->getOption("background_color"); - hexvalue = hexvalue.substr(1,6); - bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), - XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), - hexvalue.c_str()); + + if (mode == Mode_Lock) { + if (bgstyle == "stretch") { + bg->Resize(viewport.width, viewport.height); + //bg->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), + // XHeightOfScreen(ScreenOfDisplay(Dpy, Scr))); + } else if (bgstyle == "tile") { + bg->Tile(viewport.width, viewport.height); + } else if (bgstyle == "center") { + string hexvalue = cfg->getOption("background_color"); + hexvalue = hexvalue.substr(1,6); + bg->Center(viewport.width, + viewport.height, + hexvalue.c_str()); + } else { // plain color or error + string hexvalue = cfg->getOption("background_color"); + hexvalue = hexvalue.substr(1,6); + bg->Center(viewport.width, + viewport.height, + hexvalue.c_str()); + } + } else { + if (bgstyle == "stretch") { + bg->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr))); + } else if (bgstyle == "tile") { + bg->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr))); + } else if (bgstyle == "center") { + string hexvalue = cfg->getOption("background_color"); + hexvalue = hexvalue.substr(1,6); + bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), + XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), + hexvalue.c_str()); + } else { // plain color or error + string hexvalue = cfg->getOption("background_color"); + hexvalue = hexvalue.substr(1,6); + bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), + XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), + hexvalue.c_str()); + } } string cfgX = cfg->getOption("input_panel_x"); string cfgY = cfg->getOption("input_panel_y"); - X = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Width()); - Y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Height()); - // Merge image into background - image->Merge(bg, X, Y); + if (mode == Mode_Lock) { + X = Cfg::absolutepos(cfgX, viewport.width, image->Width()); + Y = Cfg::absolutepos(cfgY, viewport.height, image->Height()); + + input_name.x += X; + input_name.y += Y; + input_pass.x += X; + input_pass.y += Y; + + // Merge image into background without crop + image->Merge_non_crop(bg, X, Y); + PanelPixmap = image->createPixmap(Dpy, Scr, Win); + } else { + X = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Width()); + Y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Height()); + + // Merge image into background + image->Merge(bg, X, Y); + PanelPixmap = image->createPixmap(Dpy, Scr, Root); + } delete bg; - PanelPixmap = image->createPixmap(Dpy, Scr, Root); // Read (and substitute vars in) the welcome message welcome_message = cfg->getWelcomeMessage(); intro_message = cfg->getOption("intro_msg"); + + if (mode == Mode_Lock) { + SetName(getenv("USER")); + field = Get_Passwd; + OnExpose(); + } } Panel::~Panel() { XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &inputcolor); - XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &msgcolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &inputshadowcolor); XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &welcomecolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &welcomeshadowcolor); XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &entercolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &entershadowcolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &msgcolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &msgshadowcolor); + XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &introcolor); XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &sessioncolor); XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &sessionshadowcolor); XFreeGC(Dpy, TextGC); @@ -139,8 +204,12 @@ Panel::~Panel() { XftFontClose(Dpy, introfont); XftFontClose(Dpy, welcomefont); XftFontClose(Dpy, enterfont); - delete image; + if (mode == Mode_Lock) { + XFreeGC(Dpy, WinGC); + } + + delete image; } void Panel::OpenPanel() { @@ -185,20 +254,70 @@ void Panel::ClearPanel() { XFlush(Dpy); } +void Panel::WrongPassword(int timeout) { + string message; + XGlyphInfo extents; + + /* + if (CapsLockOn) + message = cfg->getOption("passwd_feedback_capslock"); + else */ + message = cfg->getOption("passwd_feedback_msg"); + + XftDraw *draw = XftDrawCreate(Dpy, Win, + DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr)); + XftTextExtents8(Dpy, msgfont, reinterpret_cast(message.c_str()), + message.length(), &extents); + + 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, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width); + int msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height); + + OnExpose(); + 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(); + 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. + 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 = XftDrawCreate(Dpy, Root, - DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr)); + XftDraw *draw; + if (mode == Mode_Lock) { + draw = XftDrawCreate(Dpy, Win, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr)); + } else { + draw = XftDrawCreate(Dpy, Root, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr)); + } XftTextExtentsUtf8(Dpy, msgfont, reinterpret_cast(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 = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width); - int msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height); - + int msg_x, msg_y; + if (mode == Mode_Lock) { + msg_x = Cfg::absolutepos(cfgX, viewport.width, extents.width); + msg_y = Cfg::absolutepos(cfgY, viewport.height, extents.height); + } else { + msg_x = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width); + msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height); + } SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y, text, &msgshadowcolor, @@ -220,7 +339,11 @@ unsigned long Panel::GetColor(const char* colorname) { XColor color; XWindowAttributes attributes; - XGetWindowAttributes(Dpy, Root, &attributes); + if (mode == Mode_Lock) { + XGetWindowAttributes(Dpy, Win, &attributes); + } else { + XGetWindowAttributes(Dpy, Root, &attributes); + } color.pixel = 0; if(!XParseColor(Dpy, attributes.colormap, colorname, &color)) @@ -233,10 +356,14 @@ unsigned long Panel::GetColor(const char* colorname) { void Panel::Cursor(int visible) { const char* text; - int xx, yy, y2, cheight; + int xx = 0, yy = 0, y2 = 0, cheight = 0; const char* txth = "Wj"; // used to get cursor height - switch(field) { + if (mode == Mode_Lock) { + text = HiddenPasswdBuffer.c_str(); + xx = input_pass.x; + yy = input_pass.y; + } else switch(field) { case Get_Passwd: text = HiddenPasswdBuffer.c_str(); xx = input_pass.x; @@ -258,14 +385,24 @@ void Panel::Cursor(int visible) { 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 { - XClearArea(Dpy, Win, xx+1, yy-cheight, - 1, y2-(yy-cheight)+1, false); + 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); + } } } @@ -273,7 +410,10 @@ void Panel::EventHandler(const Panel::FieldType& curfield) { XEvent event; field=curfield; bool loop = true; - OnExpose(); + + if (mode == Mode_DM) { + OnExpose(); + } struct pollfd x11_pfd = {0}; x11_pfd.fd = ConnectionNumber(Dpy); @@ -301,7 +441,13 @@ void Panel::EventHandler(const Panel::FieldType& curfield) { void Panel::OnExpose(void) { XftDraw *draw = XftDrawCreate(Dpy, Win, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr)); - XClearWindow(Dpy, Win); + + 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, @@ -339,8 +485,8 @@ bool Panel::OnKeyPress(XEvent& event) { char ascii; KeySym keysym; XComposeStatus compstatus; - int xx; - int yy; + int xx = 0; + int yy = 0; string text; string formerString = ""; @@ -371,8 +517,12 @@ bool Panel::OnKeyPress(XEvent& event) { action = Suspend; } else if (NameBuffer==EXIT_STR){ action = Exit; - } else{ - action = Login; + } else { + if (mode == Mode_DM) { + action = Login; + } else { + action = Lock; + } } }; return false; @@ -415,11 +565,32 @@ bool Panel::OnKeyPress(XEvent& event) { formerString = NameBuffer; NameBuffer.clear(); break; + } + break; + } + // Deliberate fall-through (??) + case XK_h: + if (reinterpret_cast(event).state & ControlMask) { + // note - this is a copy of code from XK_BackSpace + 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; }; break; } // Deliberate fall-through - + default: if (isprint(ascii) && (keysym < XK_Shift_L || keysym > XK_Hyper_R)){ switch(field) { @@ -468,8 +639,14 @@ bool Panel::OnKeyPress(XEvent& event) { formerString.length(), &extents); int maxLength = extents.width; - XClearArea(Dpy, Win, xx-3, yy-maxHeight-3, - maxLength+6, maxHeight+6, false); + 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()) { @@ -514,7 +691,7 @@ void Panel::ShowText(){ /* Enter username-password message */ string msg; - if (!singleInputMode|| field == Get_Passwd ) { + if ((!singleInputMode|| field == Get_Passwd) && mode == Mode_DM) { msg = cfg->getOption("password_msg"); XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(), strlen(msg.c_str()), &extents); @@ -545,6 +722,15 @@ void Panel::ShowText(){ } } 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() { @@ -595,11 +781,21 @@ void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font, XftColor* shadowColor, int xOffset, int yOffset) { + int calc_x = 0; + int calc_y = 0; + if (mode == Mode_Lock) { + calc_x = viewport.x; + calc_y = viewport.y; + } + if (xOffset && yOffset) { - XftDrawStringUtf8(d, shadowColor, font, x+xOffset, y+yOffset, - reinterpret_cast(str.c_str()), str.length()); + XftDrawStringUtf8(d, shadowColor, font, + x + xOffset + calc_x, + y + yOffset + calc_y, + reinterpret_cast(str.c_str()), str.length()); } - XftDrawStringUtf8(d, color, font, x, y, reinterpret_cast(str.c_str()), str.length()); + XftDrawStringUtf8(d, color, font, x + calc_x, y + calc_y, + reinterpret_cast(str.c_str()), str.length()); } Panel::ActionType Panel::getAction(void) const{ @@ -622,7 +818,11 @@ void Panel::ResetPasswd(void){ void Panel::SetName(const string& name){ NameBuffer=name; - action = Login; + if (mode == Mode_DM) { + action = Login; + } else { + action = Lock; + } }; const string& Panel::GetName(void) const{ @@ -632,3 +832,85 @@ const string& Panel::GetName(void) const{ const string& Panel::GetPasswd(void) const{ return PasswdBuffer; }; + +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); + + primary = XRRGetOutputPrimary(Dpy, Win); + if (!primary) { + return fallback; + } + resources = XRRGetScreenResources(Dpy, Win); + if (!resources) + return fallback; + + primary_info = XRRGetOutputInfo(Dpy, resources, primary); + if (!primary_info) { + XRRFreeScreenResources(resources); + return fallback; + } + + // Fixes bug with multiple monitors. Just pick first monitor if + // XRRGetOutputInfo gives returns bad into 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) { + 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; +}; + +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."; + } +}; diff --git a/panel.h b/panel.h index fd48836..e8d8e66 100644 --- a/panel.h +++ b/panel.h @@ -2,6 +2,7 @@ Copyright (C) 1997, 1998 Per Liden Copyright (C) 2004-06 Simone Rota Copyright (C) 2004-06 Johannes Winkelmann + Copyright (C) 2013 Nobuhiro Iwamatsu 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 @@ -31,11 +32,25 @@ #include "log.h" #include "image.h" #include "coord.h" +struct Rectangle { + int x; + int y; + unsigned int width; + unsigned int height; + + Rectangle() : x(0), y(0), width(0), height(0) {}; + Rectangle(int x, int y, unsigned int width, unsigned int height) : + x(x), y(y), width(width), height(height) {}; + bool is_empty() const { + return width == 0 || height == 0; + } +}; class Panel { public: enum ActionType { Login, + Lock, Console, Reboot, Halt, @@ -46,14 +61,18 @@ class Panel { Get_Name, Get_Passwd }; - + enum PanelType { + Mode_DM, + Mode_Lock + }; Panel(Display* dpy, int scr, Window root, Cfg* config, - const std::string& themed); + const std::string& themed, PanelType panel_mode); ~Panel(); void OpenPanel(); void ClosePanel(); void ClearPanel(); + void WrongPassword(int timeout); void Message(const std::string& text); void Error(const std::string& text); void EventHandler(const FieldType& curfield); @@ -81,15 +100,20 @@ class Panel { XftColor* shadowColor, int xOffset, int yOffset); - Cfg* cfg; + Rectangle GetPrimaryViewport(); + void ApplyBackground(Rectangle = Rectangle()); // Private data + PanelType mode; // work mode + Cfg *cfg; + Window Win; Window Root; Display* Dpy; int Scr; int X, Y; GC TextGC; + GC WinGC; XftFont* font; XftColor inputshadowcolor; XftColor inputcolor; @@ -115,6 +139,9 @@ class Panel { std::string PasswdBuffer; std::string HiddenPasswdBuffer; + // screen stuff + Rectangle viewport; + // Configuration Coord input_name; Coord input_pass; diff --git a/slimlock.1 b/slimlock.1 new file mode 100644 index 0000000..fa776b6 --- /dev/null +++ b/slimlock.1 @@ -0,0 +1,61 @@ +.TH slimlock 1 "June 10, 2011" "version 0.8" +.SH NAME +\fBslimlock\fP - Unholy Screen Locker +\fB +.SH SYNOPSIS +.nf +.fam C +\fBslimlock\fP [-v] +.fam T +.fi +.SH DESCRIPTION +The Frankenstein's monster of screen lockers. Grafting SLiM and slock together +leads to blood, tears, and locked screens. +.SH OPTIONS +.TP +.B +\fB-v\fP +display version information +.SH CONFIGURATION +Slimlock reads the same configuration files you use for SLiM. It looks in \fICFGDIR/slim.conf\fP and \fICFGDIR/slimlock.conf\fP, where \fICFGDIR\fP is defined in the makefile. The options that are read from slim.conf are hidecursor, current_theme, background_color, and background_style, screenshot_cmd, and welcome_msg. See the SLiM docs for more information. + +slimlock uses the following settings: + +.TP +.B dpms_standby_timeout +number of seconds of inactivity before the screen blanks. +.BI "Default: " 60 +.TP +.B dpms_off_timeout +number of seconds of inactivity before the screen is turned off. +.BI "Default: " 600 +.TP +.B wrong_passwd_timeout +delay in seconds after an incorrect password is entered. +.BI "Default: " 2 +.TP +.B passwd_feedback_msg +message to display after a failed authentication attempt. +.BI "Default: " "Authentication failed" +.TP +.B passwd_feedback_capslock +message to display after a failed authentication attempt if the CapsLock is on. +.BI "Default: " "Authentication failed (CapsLock is on)" +.TP +.B show_username +1 to show username on themes with single input field; 0 to disable. +.BI "Default: " 1 +.TP +.B show_welcome_msg +1 to show SLiM's welcome message; 0 to disable. +.BI "Default: " 0 +.TP +.B tty_lock +1 to disallow virtual terminals switching; 0 to allow. +.BI "Default: " 1 +.TP +.B bell +1 to enable the bell on authentication failure; 0 to disable. +.BI "Default: " 1 +.SH "SEE ALSO" +.BR slim (1) diff --git a/slimlock.conf b/slimlock.conf new file mode 100644 index 0000000..61e02fe --- /dev/null +++ b/slimlock.conf @@ -0,0 +1,11 @@ +dpms_standby_timeout 60 +dpms_off_timeout 600 + +wrong_passwd_timeout 2 +passwd_feedback_x 50% +passwd_feedback_y 10% +passwd_feedback_msg Authentication failed +passwd_feedback_capslock Authentication failed (CapsLock is on) +show_username 1 +show_welcome_msg 0 +tty_lock 0 diff --git a/slimlock.cpp b/slimlock.cpp new file mode 100644 index 0000000..04c4886 --- /dev/null +++ b/slimlock.cpp @@ -0,0 +1,370 @@ +/* slimlock + * Copyright (c) 2010-2012 Joel Burget + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "util.h" +#include "panel.h" + +#undef APPNAME +#define APPNAME "slimlock" +#define SLIMLOCKCFG SYSCONFDIR"/slimlock.conf" + +using namespace std; + +void setBackground(const string& themedir); +void HideCursor(); +bool AuthenticateUser(); +static int ConvCallback(int num_msgs, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr); +string findValidRandomTheme(const string& set); +void HandleSignal(int sig); +void *RaiseWindow(void *data); + +// I really didn't wanna put these globals here, but it's the only way... +Display* dpy; +int scr; +Window win; +Cfg* cfg; +Panel* loginPanel; +string themeName = ""; + +pam_handle_t *pam_handle; +struct pam_conv conv = {ConvCallback, NULL}; + +CARD16 dpms_standby, dpms_suspend, dpms_off, dpms_level; +BOOL dpms_state, using_dpms; +int term; + +static void +die(const char *errstr, ...) { + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { + if((argc == 2) && !strcmp("-v", argv[1])) + die(APPNAME"-"VERSION", © 2010-2012 Joel Burget\n"); + else if(argc != 1) + die("usage: "APPNAME" [-v]\n"); + + void (*prev_fn)(int); + + // restore DPMS settings should slimlock be killed in the line of duty + prev_fn = signal(SIGTERM, HandleSignal); + if (prev_fn == SIG_IGN) signal(SIGTERM, SIG_IGN); + + // create a lock file to solve mutliple instances problem + // /var/lock used to be the place to put this, now it's /run/lock + // ...i think + struct stat statbuf; + int lock_file; + + // try /run/lock first, since i believe it's preferred + if (!stat("/run/lock", &statbuf)) + lock_file = open("/run/lock/"APPNAME".lock", O_CREAT | O_RDWR, 0666); + else + lock_file = open("/var/lock/"APPNAME".lock", O_CREAT | O_RDWR, 0666); + + int rc = flock(lock_file, LOCK_EX | LOCK_NB); + + if(rc) { + if(EWOULDBLOCK == errno) + die(APPNAME" already running\n"); + } + + unsigned int cfg_passwd_timeout; + // Read user's current theme + cfg = new Cfg; + cfg->readConf(CFGFILE); + cfg->readConf(SLIMLOCKCFG); + string themebase = ""; + string themefile = ""; + string themedir = ""; + themeName = ""; + themebase = string(THEMESDIR) + "/"; + themeName = cfg->getOption("current_theme"); + string::size_type pos; + if ((pos = themeName.find(",")) != string::npos) { + themeName = findValidRandomTheme(themeName); + } + + bool loaded = false; + while (!loaded) { + themedir = themebase + themeName; + themefile = themedir + THEMESFILE; + if (!cfg->readConf(themefile)) { + if (themeName == "default") { + cerr << APPNAME << ": Failed to open default theme file " + << themefile << endl; + exit(ERR_EXIT); + } else { + cerr << APPNAME << ": Invalid theme in config: " + << themeName << endl; + themeName = "default"; + } + } else { + loaded = true; + } + } + + const char *display = getenv("DISPLAY"); + if (!display) + display = DISPLAY; + + if(!(dpy = XOpenDisplay(display))) + die(APPNAME": cannot open display\n"); + scr = DefaultScreen(dpy); + + XSetWindowAttributes wa; + wa.override_redirect = 1; + wa.background_pixel = BlackPixel(dpy, scr); + + // Create a full screen window + Window root = RootWindow(dpy, scr); + win = XCreateWindow(dpy, + root, + 0, + 0, + DisplayWidth(dpy, scr), + DisplayHeight(dpy, scr), + 0, + DefaultDepth(dpy, scr), + CopyFromParent, + DefaultVisual(dpy, scr), + CWOverrideRedirect | CWBackPixel, + &wa); + XMapWindow(dpy, win); + + XFlush(dpy); + for (int len = 1000; len; len--) { + if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime) + == GrabSuccess) + break; + usleep(1000); + } + XSelectInput(dpy, win, ExposureMask | KeyPressMask); + + // This hides the cursor if the user has that option enabled in their + // configuration + HideCursor(); + + loginPanel = new Panel(dpy, scr, win, cfg, themedir, Panel::Mode_Lock); + + int ret = pam_start(APPNAME, loginPanel->GetName().c_str(), &conv, &pam_handle); + // If we can't start PAM, just exit because slimlock won't work right + if (ret != PAM_SUCCESS) + die("PAM: %s\n", pam_strerror(pam_handle, ret)); + + // disable tty switching + if(cfg->getOption("tty_lock") == "1") { + if ((term = open("/dev/console", O_RDWR)) == -1) + perror("error opening console"); + + if ((ioctl(term, VT_LOCKSWITCH)) == -1) + perror("error locking console"); + } + + // Set up DPMS + unsigned int cfg_dpms_standby, cfg_dpms_off; + cfg_dpms_standby = Cfg::string2int(cfg->getOption("dpms_standby_timeout").c_str()); + cfg_dpms_off = Cfg::string2int(cfg->getOption("dpms_off_timeout").c_str()); + using_dpms = DPMSCapable(dpy) && (cfg_dpms_standby > 0); + if (using_dpms) { + DPMSGetTimeouts(dpy, &dpms_standby, &dpms_suspend, &dpms_off); + + DPMSSetTimeouts(dpy, cfg_dpms_standby, + cfg_dpms_standby, cfg_dpms_off); + + DPMSInfo(dpy, &dpms_level, &dpms_state); + if (!dpms_state) + DPMSEnable(dpy); + } + + // Get password timeout + cfg_passwd_timeout = Cfg::string2int(cfg->getOption("wrong_passwd_timeout").c_str()); + // Let's just make sure it has a sane value + cfg_passwd_timeout = cfg_passwd_timeout > 60 ? 60 : cfg_passwd_timeout; + + pthread_t raise_thread; + pthread_create(&raise_thread, NULL, RaiseWindow, NULL); + + // Main loop + while (true) + { + loginPanel->ResetPasswd(); + + // AuthenticateUser returns true if authenticated + if (AuthenticateUser()) + break; + + loginPanel->WrongPassword(cfg_passwd_timeout); + } + + // kill thread before destroying the window that it's supposed to be raising + pthread_cancel(raise_thread); + + loginPanel->ClosePanel(); + delete loginPanel; + + // Get DPMS stuff back to normal + if (using_dpms) { + DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off); + // turn off DPMS if it was off when we entered + if (!dpms_state) + DPMSDisable(dpy); + } + + XCloseDisplay(dpy); + + close(lock_file); + + if(cfg->getOption("tty_lock") == "1") { + if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) { + perror("error unlocking console"); + } + } + close(term); + + return 0; +} + +void HideCursor() +{ + if (cfg->getOption("hidecursor") == "true") { + XColor black; + char cursordata[1]; + Pixmap cursorpixmap; + Cursor cursor; + cursordata[0] = 0; + cursorpixmap = XCreateBitmapFromData(dpy, win, cursordata, 1, 1); + black.red = 0; + black.green = 0; + black.blue = 0; + cursor = XCreatePixmapCursor(dpy, cursorpixmap, cursorpixmap, + &black, &black, 0, 0); + XFreePixmap(dpy, cursorpixmap); + XDefineCursor(dpy, win, cursor); + } +} + +static int ConvCallback(int num_msgs, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +{ + loginPanel->EventHandler(Panel::Get_Passwd); + + // PAM expects an array of responses, one for each message + if (num_msgs == 0 || + (*resp = (pam_response*) calloc(num_msgs, sizeof(struct pam_message))) == NULL) + return PAM_BUF_ERR; + + for (int i = 0; i < num_msgs; i++) { + if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF && + msg[i]->msg_style != PAM_PROMPT_ECHO_ON) + continue; + + // return code is currently not used but should be set to zero + resp[i]->resp_retcode = 0; + if ((resp[i]->resp = strdup(loginPanel->GetPasswd().c_str())) == NULL) { + free(*resp); + return PAM_BUF_ERR; + } + } + + return PAM_SUCCESS; +} + +bool AuthenticateUser() +{ + return(pam_authenticate(pam_handle, 0) == PAM_SUCCESS); +} + +string 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 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)); + cerr << APPNAME << ": Invalid theme in config: " + << name << endl; + name = ""; + } + } while (name == "" && themes.size()); + return name; +} + +void HandleSignal(int sig) +{ + // Get DPMS stuff back to normal + if (using_dpms) { + DPMSSetTimeouts(dpy, dpms_standby, dpms_suspend, dpms_off); + // turn off DPMS if it was off when we entered + if (!dpms_state) + DPMSDisable(dpy); + } + + if ((ioctl(term, VT_UNLOCKSWITCH)) == -1) { + perror("error unlocking console"); + } + close(term); + + loginPanel->ClosePanel(); + delete loginPanel; + + die(APPNAME": Caught signal; dying\n"); +} + +void* RaiseWindow(void *data) { + while(1) { + XRaiseWindow(dpy, win); + sleep(1); + } + + return (void *)0; +} diff --git a/slimlock.pam.sample b/slimlock.pam.sample new file mode 100644 index 0000000..7b88c2e --- /dev/null +++ b/slimlock.pam.sample @@ -0,0 +1,2 @@ +#%PAM-1.0 +auth required pam_unix.so nodelay nullok