Browse Source

Add slimlock and add some function for this

I received a request to be included slimlock[0] in the slim slimlock
from Diego Principe.
This is first step to import the source code of slimlock.

[0]: http://joelburget.com/slimlock/

Signed-off-by: Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
Nobuhiro Iwamatsu 8 years ago
parent
commit
fccae89bbf
11 changed files with 927 additions and 53 deletions
  1. 29 1
      CMakeLists.txt
  2. 1 1
      app.cpp
  3. 13 0
      cfg.cpp
  4. 55 0
      image.cpp
  5. 1 0
      image.h
  6. 352 49
      panel.cpp
  7. 32 2
      panel.h
  8. 61 0
      slimlock.1
  9. 11 0
      slimlock.conf
  10. 370 0
      slimlock.cpp
  11. 2 0
      slimlock.pam

+ 29 - 1
CMakeLists.txt

@@ -60,6 +60,15 @@ set(slim_srcs
 	jpeg.c
 )
 
+set(slimlock_srcs
+	slimlock.cpp
+	cfg.cpp
+	image.cpp
+	panel.cpp
+	util.cpp
+	log.cpp
+)
+
 if(USE_PAM)
 	set(slim_srcs ${slim_srcs} PAM.cpp)
 endif(USE_PAM)
@@ -68,6 +77,7 @@ if(USE_CONSOLEKIT)
 endif(USE_CONSOLEKIT)
 
 add_executable(${PROJECT_NAME} ${slim_srcs})
+add_executable(slimlock ${slimlock_srcs})
 
 #Set the custom CMake module directory where our include/lib finders are
 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
@@ -95,6 +105,7 @@ if(USE_PAM)
 		message("\tPAM Found")
 		set(SLIM_DEFINITIONS ${SLIM_DEFINITIONS} "-DUSE_PAM")
 		target_link_libraries(${PROJECT_NAME} ${PAM_LIBRARY})
+		target_link_libraries(slimlock ${PAM_LIBRARY})
 		include_directories(${PAM_INCLUDE_DIR})
 	else(PAM_FOUND)
 		message("\tPAM Not Found")
@@ -142,6 +153,7 @@ include_directories(
 	${X11_INCLUDE_DIR}
 	${X11_Xft_INCLUDE_PATH}
 	${X11_Xrender_INCLUDE_PATH}
+	${X11_Xrandr_INCLUDE_PATH}
 	${FREETYPE_INCLUDE_DIR_freetype2}
 	${X11_Xmu_INCLUDE_PATH}
 	${ZLIB_INCLUDE_DIR}
@@ -149,7 +161,7 @@ include_directories(
 	${PNG_INCLUDE_DIR}
 )
 
-#Set up library with all found packages
+#Set up library with all found packages for slim
 target_link_libraries(${PROJECT_NAME}
 	${M_LIB}
 	${RT_LIB}
@@ -157,6 +169,22 @@ 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}
+	)
+
+#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}
 	${FREETYPE_LIBRARY}
 	${JPEG_LIBRARIES}

+ 1 - 1
app.cpp

@@ -315,7 +315,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";

+ 13 - 0
cfg.cpp

@@ -120,6 +120,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 = "";
 }
 

+ 55 - 0
image.cpp

@@ -286,6 +286,60 @@ 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);
+	memset(new_rgb, 0, 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<unsigned char>(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.
@@ -895,3 +949,4 @@ file_close:
 	fclose(infile);
 	return(ret);
 }
+

+ 1 - 0
image.h

@@ -53,6 +53,7 @@ public:
 	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);

+ 352 - 49
panel.cpp

@@ -9,21 +9,30 @@
    (at your option) any later version.
 */
 
+#define MODE_LOCK 1
 #include <sstream>
 #include <poll.h>
+#include <X11/extensions/Xrandr.h>
 #include "panel.h"
 
 using namespace std;
 
 Panel::Panel(Display* dpy, int scr, Window root, Cfg* config,
-			 const string& themedir) {
+			 const string& themedir, PanelType panel_mode) {
 	/* Set display */
 	Dpy = dpy;
 	Scr = scr;
 	Root = root;
 	cfg = config;
+	mode = panel_mode;
 
 	session = "";
+#if MODE_LOCK
+	if (mode == Mode_Lock) {
+		Win = root;
+		viewport = GetPrimaryViewport();
+	}
+#endif
 
 	/* Init GC */
 	XGCValues gcv;
@@ -32,8 +41,25 @@ Panel::Panel(Display* dpy, int scr, Window root, Cfg* config,
 	gcv.foreground = GetColor("black");
 	gcv.background = GetColor("white");
 	gcv.graphics_exposures = False;
-	TextGC = XCreateGC(Dpy, Root, gcm, &gcv);
-
+#if MODE_LOCK
+	if (mode == Mode_Lock)
+		TextGC = XCreateGC(Dpy, Win, gcm, &gcv);
+	else
+#endif
+		TextGC = XCreateGC(Dpy, Root, gcm, &gcv);
+
+#if MODE_LOCK
+	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);
+		}
+	}
+#endif
 	font = XftFontOpenName(Dpy, Scr, cfg->getOption("input_font").c_str());
 	welcomefont = XftFontOpenName(Dpy, Scr, cfg->getOption("welcome_font").c_str());
 	introfont = XftFontOpenName(Dpy, Scr, cfg->getOption("intro_font").c_str());
@@ -104,37 +130,97 @@ Panel::Panel(Display* dpy, int scr, Window root, Cfg* config,
 			}
 		}
 	}
-	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)),
+
+#if MODE_LOCK
+	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 {
+#endif
+		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_LOCK
 	}
+#endif
 
 	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_LOCK
+	if (mode == Mode_Lock) {
+#else
+	if (0) {
+#endif
+		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;
+	
+	} else {
+		X = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Width());
+		Y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Height());
+	}
+
+	if (mode == Mode_Lock) {
+		/* Merge image into background without crop */
+		image->Merge_non_crop(bg, X, Y);
+		PanelPixmap = image->createPixmap(Dpy, Scr, Win);
+	} else {
+		/* 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_LOCK
+	if (mode == Mode_Lock) {
+		SetName(getenv("USER"));
+		field = Get_Passwd;
+		OnExpose();
+	}
+#endif
 }
 
 Panel::~Panel() {
@@ -150,6 +236,12 @@ Panel::~Panel() {
 	XftFontClose(Dpy, introfont);
 	XftFontClose(Dpy, welcomefont);
 	XftFontClose(Dpy, enterfont);
+
+#if MODE_LOCK
+	if (mode == Mode_Lock)
+		XFreeGC(Dpy, WinGC);
+#endif	
+
 	delete image;
 }
 
@@ -193,20 +285,81 @@ void Panel::ClearPanel() {
 	XFlush(Dpy);
 }
 
+void Panel::WrongPassword(int timeout) {
+	string message;
+	XGlyphInfo extents;
+
+#if 0
+	if (CapsLockOn)
+		message = cfg->getOption("passwd_feedback_capslock");
+	else
+#endif
+	message = cfg->getOption("passwd_feedback_msg");
+
+	XftDraw *draw = XftDrawCreate(Dpy, Win,
+	DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
+	XftTextExtents8(Dpy, msgfont, reinterpret_cast<const XftChar8*>(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));
-	XftTextExtents8(Dpy, msgfont, reinterpret_cast<const XftChar8*>(text.c_str()),
+	XftDraw *draw;
+
+#if MODE_LOCK
+	if (mode == Mode_Lock)
+		draw = XftDrawCreate(Dpy, Win,
+			DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
+	else
+#endif
+		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 = 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_LOCK
+	if (mode == Mode_Lock) {
+		msg_x = Cfg::absolutepos(cfgX, viewport.width, extents.width);
+		msg_y = Cfg::absolutepos(cfgY, viewport.height, extents.height);
+	} else {
+#endif
+		msg_x = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
+		msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
+#if MODE_LOCK
+	}
+#endif
 	SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y,
 					 text,
 					 &msgshadowcolor,
@@ -223,12 +376,17 @@ void Panel::Error(const string& text) {
 	ClearPanel();
 }
 
-
 unsigned long Panel::GetColor(const char* colorname) {
 	XColor color;
 	XWindowAttributes attributes;
 
-	XGetWindowAttributes(Dpy, Root, &attributes);
+#if MODE_LOCK
+	if (mode == Mode_Lock)
+		XGetWindowAttributes(Dpy, Win, &attributes);
+	else
+#endif
+		XGetWindowAttributes(Dpy, Root, &attributes);
+	
 	color.pixel = 0;
 
 	if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
@@ -244,6 +402,15 @@ void Panel::Cursor(int visible) {
 	int xx, yy, y2, cheight;
 	const char* txth = "Wj"; /* used to get cursor height */
 
+#if MODE_LOCK
+	if (this->mode == Mode_Lock) {
+#else
+	if (0) {
+#endif
+			text = HiddenPasswdBuffer.c_str();
+			xx = input_pass_x;
+			yy = input_pass_y;
+	} else {
 	switch(field) {
 		case Get_Passwd:
 			text = HiddenPasswdBuffer.c_str();
@@ -257,6 +424,7 @@ void Panel::Cursor(int visible) {
 			yy = input_name_y;
 			break;
 	}
+	}
 
 	XGlyphInfo extents;
 	XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
@@ -266,28 +434,48 @@ void Panel::Cursor(int visible) {
 	xx += extents.width;
 
 	if(visible == SHOW) {
+#if MODE_LOCK
+		if (mode == Mode_Lock) {
+			xx += viewport.x;
+			yy += viewport.y;
+			y2 += viewport.y;
+		}
+#endif
 		XSetForeground(Dpy, TextGC,
-					   GetColor(cfg->getOption("input_color").c_str()));
+			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_LOCK
+		if (mode == Mode_Lock) {
+			ApplyBackground(Rectangle(xx+1, yy-cheight,
+				1, y2-(yy-cheight)+1));
+		}
+		else
+#endif
+			XClearArea(Dpy, Win, xx+1, yy-cheight,
+				1, y2-(yy-cheight)+1, false);
 	}
+
 }
 
 void Panel::EventHandler(const Panel::FieldType& curfield) {
 	XEvent event;
-	field=curfield;
+	field = curfield;
 	bool loop = true;
-	OnExpose();
+
+	if (mode == Mode_DM)
+		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 (loop) {
+		if (XPending(Dpy) || poll(&x11_pfd, 1, -1) > 0) {
 			while(XPending(Dpy)) {
 				XNextEvent(Dpy, &event);
 				switch(event.type) {
@@ -308,8 +496,16 @@ 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);
+		DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
+
+#if MODE_LOCK
+	if (mode == Mode_Lock) {
+		ApplyBackground();
+	} else 
+#endif
+		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,
@@ -380,7 +576,10 @@ bool Panel::OnKeyPress(XEvent& event) {
 				} else if (NameBuffer==EXIT_STR){
 					action = Exit;
 				} else{
-					action = Login;
+					if (mode == Mode_DM)
+						action = Login;
+					else
+						action = Lock;
 				}
 			};
 			return false;
@@ -475,9 +674,16 @@ bool Panel::OnKeyPress(XEvent& event) {
 		XftTextExtents8(Dpy, font, reinterpret_cast<const XftChar8*>(formerString.c_str()),
 						formerString.length(), &extents);
 		int maxLength = extents.width;
-
-		XClearArea(Dpy, Win, xx-3, yy-maxHeight-3,
-				   maxLength+6, maxHeight+6, false);
+		
+#if MODE_LOCK
+		if (mode == Mode_Lock)
+			ApplyBackground(Rectangle(input_pass_x - 3,
+				input_pass_y - maxHeight - 3,
+				maxLength + 6, maxHeight + 6));
+		else
+#endif
+			XClearArea(Dpy, Win, xx - 3, yy-maxHeight - 3,
+				maxLength + 6, maxHeight + 6, false);
 	}
 
 	if (!text.empty()) {
@@ -522,7 +728,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);
@@ -537,7 +743,8 @@ void Panel::ShowText(){
 							 msg, &entershadowcolor, shadowXOffset, shadowYOffset);
 		}
 	}
-	if (!singleInputMode|| field == Get_Name ) {
+
+	if (!singleInputMode|| field == Get_Name) {
 		msg = cfg->getOption("username_msg");
 		XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
 						strlen(msg.c_str()), &extents);
@@ -553,6 +760,17 @@ void Panel::ShowText(){
 		}
 	}
 	XftDrawDestroy(draw);
+
+#if MODE_LOCK
+	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);
+		}
+	}
+#endif
 }
 
 string Panel::getSession() {
@@ -601,11 +819,28 @@ 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_LOCK
+	if (mode == Mode_Lock) {
+		calc_x = viewport.x;
+		calc_y = viewport.y;
+	}
+#endif
+
 	if (xOffset && yOffset) {
-		XftDrawStringUtf8(d, shadowColor, font, x+xOffset, y+yOffset,
-					   reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
+		XftDrawStringUtf8(d, shadowColor, font,
+			x + xOffset + calc_x,
+			y + yOffset + calc_y,
+			reinterpret_cast<const FcChar8*>(str.c_str()),
+			str.length());
 	}
-	XftDrawStringUtf8(d, color, font, x, y, reinterpret_cast<const FcChar8*>(str.c_str()), str.length());
+
+	XftDrawStringUtf8(d, color, font,
+		x + calc_x,
+		y + calc_y,
+		reinterpret_cast<const FcChar8*>(str.c_str()),
+		str.length());
 }
 
 Panel::ActionType Panel::getAction(void) const{
@@ -628,7 +863,10 @@ 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{
@@ -638,3 +876,68 @@ 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;
+
+	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;
+	}
+
+	crtc_info = XRRGetCrtcInfo(Dpy, resources, primary_info->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.";
+}

+ 32 - 2
panel.h

@@ -2,6 +2,7 @@
    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) 2013 Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -31,10 +32,26 @@
 #include "log.h"
 #include "image.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,
@@ -47,13 +64,18 @@ public:
 		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 +103,19 @@ private:
 							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;
@@ -109,12 +135,16 @@ private:
 	XftColor entershadowcolor;
 	ActionType action;
 	FieldType field;
+	//Pixmap   background;
 	
 	/* Username/Password */
 	std::string NameBuffer;
 	std::string PasswdBuffer;
 	std::string HiddenPasswdBuffer;
 
+	/* screen stuff */
+	Rectangle viewport;
+
 	/* Configuration */
 	int input_name_x;
 	int input_name_y;

+ 61 - 0
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.conf contains 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)

+ 11 - 0
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

+ 370 - 0
slimlock.cpp

@@ -0,0 +1,370 @@
+/* slimlock
+ * Copyright (c) 2010-2012 Joel Burget <joelburget@gmail.com>
+ *
+ * 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 <cstdio>
+#include <cstring>
+#include <algorithm>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/dpms.h>
+#include <security/pam_appl.h>
+#include <pthread.h>
+#include <err.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <fcntl.h>
+
+#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;
+        
+   		cerr << APPNAME << ": HOGE14b: " << endl;
+        loginPanel->WrongPassword(cfg_passwd_timeout);
+   		cerr << APPNAME << ": HOGE14c: " << endl;
+   }
+ 
+    // 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<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));
+            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);
+    }
+}

+ 2 - 0
slimlock.pam

@@ -0,0 +1,2 @@
+#%PAM-1.0
+auth	required pam_unix.so nodelay nullok