panel.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /* SLiM - Simple Login Manager
  2. Copyright (C) 1997, 1998 Per Liden
  3. Copyright (C) 2004-06 Simone Rota <sip@varlock.com>
  4. Copyright (C) 2004-06 Johannes Winkelmann <jw@tks6.net>
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. */
  10. #include <sstream>
  11. #include "panel.h"
  12. using namespace std;
  13. Panel::Panel(Display* dpy, int scr, Window root, Cfg* config,
  14. const string& themedir) {
  15. // Set display
  16. Dpy = dpy;
  17. Scr = scr;
  18. Root = root;
  19. cfg = config;
  20. session = "";
  21. // Init GC
  22. XGCValues gcv;
  23. unsigned long gcm;
  24. gcm = GCForeground|GCBackground|GCGraphicsExposures;
  25. gcv.foreground = GetColor("black");
  26. gcv.background = GetColor("white");
  27. gcv.graphics_exposures = False;
  28. TextGC = XCreateGC(Dpy, Root, gcm, &gcv);
  29. font = XftFontOpenName(Dpy, Scr, cfg->getOption("input_font").c_str());
  30. welcomefont = XftFontOpenName(Dpy, Scr, cfg->getOption("welcome_font").c_str());
  31. introfont = XftFontOpenName(Dpy, Scr, cfg->getOption("intro_font").c_str());
  32. enterfont = XftFontOpenName(Dpy, Scr, cfg->getOption("username_font").c_str());
  33. msgfont = XftFontOpenName(Dpy, Scr, cfg->getOption("msg_font").c_str());
  34. Visual* visual = DefaultVisual(Dpy, Scr);
  35. Colormap colormap = DefaultColormap(Dpy, Scr);
  36. // NOTE: using XftColorAllocValue() would be a better solution. Lazy me.
  37. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("input_color").c_str(), &inputcolor);
  38. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("input_shadow_color").c_str(), &inputshadowcolor);
  39. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("welcome_color").c_str(), &welcomecolor);
  40. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("welcome_shadow_color").c_str(), &welcomeshadowcolor);
  41. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("username_color").c_str(), &entercolor);
  42. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("username_shadow_color").c_str(), &entershadowcolor);
  43. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("msg_color").c_str(), &msgcolor);
  44. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("msg_shadow_color").c_str(), &msgshadowcolor);
  45. XftColorAllocName(Dpy, visual, colormap, cfg->getOption("intro_color").c_str(), &introcolor);
  46. // Load properties from config / theme
  47. input_name_x = Cfg::string2int(cfg->getOption("input_name_x").c_str());
  48. input_name_y = Cfg::string2int(cfg->getOption("input_name_y").c_str());
  49. input_pass_x = Cfg::string2int(cfg->getOption("input_pass_x").c_str());
  50. input_pass_y = Cfg::string2int(cfg->getOption("input_pass_y").c_str());
  51. inputShadowXOffset =
  52. Cfg::string2int(cfg->getOption("input_shadow_xoffset").c_str());
  53. inputShadowYOffset =
  54. Cfg::string2int(cfg->getOption("input_shadow_yoffset").c_str());
  55. if (input_pass_x < 0 || input_pass_y < 0){ // single inputbox mode
  56. input_pass_x = input_name_x;
  57. input_pass_y = input_name_y;
  58. }
  59. // Load panel and background image
  60. string panelpng = "";
  61. panelpng = panelpng + themedir +"/panel.png";
  62. image = new Image;
  63. bool loaded = image->Read(panelpng.c_str());
  64. if (!loaded) { // try jpeg if png failed
  65. panelpng = themedir + "/panel.jpg";
  66. loaded = image->Read(panelpng.c_str());
  67. if (!loaded) {
  68. cerr << APPNAME << ": could not load panel image for theme '"
  69. << basename((char*)themedir.c_str()) << "'"
  70. << endl;
  71. exit(ERR_EXIT);
  72. }
  73. }
  74. Image* bg = new Image();
  75. string bgstyle = cfg->getOption("background_style");
  76. if (bgstyle != "color") {
  77. panelpng = themedir +"/background.png";
  78. loaded = bg->Read(panelpng.c_str());
  79. if (!loaded) { // try jpeg if png failed
  80. panelpng = themedir + "/background.jpg";
  81. loaded = bg->Read(panelpng.c_str());
  82. if (!loaded){
  83. cerr << APPNAME << ": could not load background image for theme '"
  84. << basename((char*)themedir.c_str()) << "'"
  85. << endl;
  86. exit(ERR_EXIT);
  87. }
  88. }
  89. }
  90. if (bgstyle == "stretch") {
  91. bg->Resize(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
  92. } else if (bgstyle == "tile") {
  93. bg->Tile(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)));
  94. } else if (bgstyle == "center") {
  95. string hexvalue = cfg->getOption("background_color");
  96. hexvalue = hexvalue.substr(1,6);
  97. bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
  98. hexvalue.c_str());
  99. } else { // plain color or error
  100. string hexvalue = cfg->getOption("background_color");
  101. hexvalue = hexvalue.substr(1,6);
  102. bg->Center(XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)),
  103. hexvalue.c_str());
  104. }
  105. string cfgX = cfg->getOption("input_panel_x");
  106. string cfgY = cfg->getOption("input_panel_y");
  107. X = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Width());
  108. Y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), image->Height());
  109. // Merge image into background
  110. image->Merge(bg, X, Y);
  111. PanelPixmap = image->createPixmap(Dpy, Scr, Root);
  112. // Read (and substitute vars in) the welcome message
  113. welcome_message = cfg->getWelcomeMessage();
  114. intro_message = cfg->getOption("intro_msg");
  115. // Init In
  116. In = new Input(cfg);
  117. }
  118. Panel::~Panel() {
  119. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &inputcolor);
  120. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &msgcolor);
  121. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &welcomecolor);
  122. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &entercolor);
  123. XFreeGC(Dpy, TextGC);
  124. delete In;
  125. delete image;
  126. }
  127. void Panel::OpenPanel() {
  128. // Create window
  129. Win = XCreateSimpleWindow(Dpy, Root, X, Y,
  130. image->Width(),
  131. image->Height(),
  132. 0, GetColor("white"), GetColor("white"));
  133. // Events
  134. XSelectInput(Dpy, Win, ExposureMask | KeyPressMask);
  135. // Set background
  136. XSetWindowBackgroundPixmap(Dpy, Win, PanelPixmap);
  137. // Show window
  138. XMapWindow(Dpy, Win);
  139. XMoveWindow(Dpy, Win, X, Y); // override wm positioning (for tests)
  140. // Grab keyboard
  141. XGrabKeyboard(Dpy, Win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
  142. XFlush(Dpy);
  143. }
  144. void Panel::ClosePanel() {
  145. XUngrabKeyboard(Dpy, CurrentTime);
  146. XUnmapWindow(Dpy, Win);
  147. XDestroyWindow(Dpy, Win);
  148. XFlush(Dpy);
  149. }
  150. void Panel::ClearPanel() {
  151. session = "";
  152. In->Reset();
  153. XClearWindow(Dpy, Root);
  154. XClearWindow(Dpy, Win);
  155. Cursor(SHOW);
  156. ShowText();
  157. XFlush(Dpy);
  158. }
  159. void Panel::Message(const char* text) {
  160. string cfgX, cfgY;
  161. XGlyphInfo extents;
  162. XftDraw *draw = XftDrawCreate(Dpy, Root,
  163. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  164. XftTextExtents8(Dpy, msgfont, (XftChar8*)text,
  165. strlen(text), &extents);
  166. cfgX = cfg->getOption("msg_x");
  167. cfgY = cfg->getOption("msg_y");
  168. int shadowXOffset =
  169. Cfg::string2int(cfg->getOption("msg_shadow_xoffset").c_str());
  170. int shadowYOffset =
  171. Cfg::string2int(cfg->getOption("msg_shadow_yoffset").c_str());
  172. int msg_x = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
  173. int msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
  174. SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y,
  175. (XftChar8*)text, strlen(text),
  176. &msgshadowcolor,
  177. shadowXOffset, shadowYOffset);
  178. XFlush(Dpy);
  179. XftDrawDestroy(draw);
  180. }
  181. void Panel::Error(const char* text) {
  182. ClosePanel();
  183. Message(text);
  184. sleep(ERROR_DURATION);
  185. OpenPanel();
  186. ClearPanel();
  187. }
  188. Input* Panel::GetInput() {
  189. return In;
  190. }
  191. unsigned long Panel::GetColor(const char* colorname) {
  192. XColor color;
  193. XWindowAttributes attributes;
  194. XGetWindowAttributes(Dpy, Root, &attributes);
  195. color.pixel = 0;
  196. if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
  197. cerr << APPNAME << ": can't parse color " << colorname << endl;
  198. else if(!XAllocColor(Dpy, attributes.colormap, &color))
  199. cerr << APPNAME << ": can't allocate color " << colorname << endl;
  200. return color.pixel;
  201. }
  202. void Panel::Cursor(int visible) {
  203. char* text;
  204. int xx, yy, y2, cheight;
  205. char* txth = "Wj"; // used to get cursor height
  206. switch(In->GetField()) {
  207. case GET_PASSWD:
  208. text = In->GetHiddenPasswd();
  209. xx = input_pass_x;
  210. yy = input_pass_y;
  211. break;
  212. case GET_NAME:
  213. text = In->GetName();
  214. xx = input_name_x;
  215. yy = input_name_y;
  216. break;
  217. default: /* Origin & NULL string as default values. */
  218. text = (char *)NULL;
  219. xx = (int)0;
  220. yy = (int)0;
  221. break;
  222. }
  223. XGlyphInfo extents;
  224. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  225. cheight = extents.height;
  226. y2 = yy - extents.y + extents.height;
  227. XftTextExtents8(Dpy, font, (XftChar8*)text, strlen(text), &extents);
  228. xx += extents.width;
  229. if(visible == SHOW) {
  230. XSetForeground(Dpy, TextGC,
  231. GetColor(cfg->getOption("input_color").c_str()));
  232. XDrawLine(Dpy, Win, TextGC,
  233. xx+1, yy-cheight,
  234. xx+1, y2);
  235. } else {
  236. XClearArea(Dpy, Win, xx+1, yy-cheight,
  237. 1, y2-(yy-cheight)+1, false);
  238. }
  239. }
  240. int Panel::EventHandler(XEvent* event) {
  241. Action = WAIT;
  242. switch(event->type) {
  243. case Expose:
  244. OnExpose(event);
  245. break;
  246. case KeyPress:
  247. OnKeyPress(event);
  248. break;
  249. }
  250. return Action;
  251. }
  252. void Panel::OnExpose(XEvent* event) {
  253. char* name = In->GetName();
  254. char* passwd = In->GetHiddenPasswd();
  255. XftDraw *draw = XftDrawCreate(Dpy, Win,
  256. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  257. if (input_pass_x != input_name_x || input_pass_y != input_name_y){
  258. SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y,
  259. (XftChar8*)name, strlen(name),
  260. &inputshadowcolor,
  261. inputShadowXOffset, inputShadowYOffset);
  262. SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y,
  263. (XftChar8*)passwd, strlen(passwd),
  264. &inputshadowcolor,
  265. inputShadowXOffset, inputShadowYOffset);
  266. } else { //single input mode
  267. switch(In->GetField()) {
  268. case GET_PASSWD:
  269. SlimDrawString8 (draw, &inputcolor, font,
  270. input_pass_x, input_pass_y,
  271. (XftChar8*)passwd, strlen(passwd),
  272. &inputshadowcolor,
  273. inputShadowXOffset, inputShadowYOffset);
  274. break;
  275. case GET_NAME:
  276. SlimDrawString8 (draw, &inputcolor, font,
  277. input_name_x, input_name_y,
  278. (XftChar8*)name, strlen(name),
  279. &inputshadowcolor,
  280. inputShadowXOffset, inputShadowYOffset);
  281. break;
  282. }
  283. }
  284. XftDrawDestroy (draw);
  285. Cursor(SHOW);
  286. ShowText();
  287. }
  288. void Panel::OnKeyPress(XEvent* event) {
  289. char del;
  290. char buffer;
  291. KeySym keysym;
  292. XComposeStatus compstatus;
  293. int xx;
  294. int yy;
  295. char* text;
  296. bool singleInputMode =
  297. input_name_x == input_pass_x &&
  298. input_name_y == input_pass_y;
  299. Cursor(HIDE);
  300. XLookupString(&event->xkey, &buffer, 1, &keysym, &compstatus);
  301. del = In->Key(buffer, keysym, singleInputMode);
  302. Action = In->GetAction();
  303. XGlyphInfo extents;
  304. XftDraw *draw = XftDrawCreate(Dpy, Win,
  305. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  306. if (keysym == XK_F1) {
  307. SwitchSession();
  308. }
  309. bool clearField = false;
  310. string formerString = "";
  311. if ((((XKeyEvent*)event)->state & ControlMask)) {
  312. if (keysym == XK_w || keysym == XK_u) {
  313. clearField = true;
  314. }
  315. }
  316. switch(In->GetField()) {
  317. case GET_PASSWD:
  318. if (strlen(In->GetHiddenPasswd()) == 0){
  319. // clear name and welcome label if we just entered the
  320. // password field
  321. if (singleInputMode) {
  322. xx = input_name_x;
  323. yy = input_name_y;
  324. text = In->GetName();
  325. XftTextExtents8(Dpy, font, (XftChar8*)text,
  326. strlen(text), &extents);
  327. XClearWindow(Dpy, Win);
  328. ShowText();
  329. }
  330. }
  331. if (clearField) {
  332. formerString = In->GetHiddenPasswd();
  333. In->ResetPassword();
  334. }
  335. text = In->GetHiddenPasswd();
  336. xx = input_pass_x;
  337. yy = input_pass_y;
  338. break;
  339. case GET_NAME:
  340. if (clearField) {
  341. formerString = In->GetName();
  342. In->ResetName();
  343. }
  344. text = In->GetName();
  345. xx = input_name_x;
  346. yy = input_name_y;
  347. break;
  348. default: /* Origin & NULL string as default values. */
  349. text = (char *)NULL;
  350. xx = (int)0;
  351. yy = (int)0;
  352. break;
  353. }
  354. char* txth = "Wj"; // get proper maximum height ?
  355. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  356. int maxHeight = extents.height;
  357. string tmp = "";
  358. if (clearField) {
  359. tmp = formerString;
  360. } else {
  361. tmp = text;
  362. tmp = tmp + del;
  363. }
  364. XftTextExtents8(Dpy, font, (XftChar8*)tmp.c_str(),
  365. strlen(tmp.c_str()), &extents);
  366. int maxLength = extents.width;
  367. XClearArea(Dpy, Win, xx-3, yy-maxHeight-3,
  368. maxLength+6, maxHeight+6, false);
  369. if (!clearField) {
  370. SlimDrawString8 (draw, &inputcolor, font, xx, yy,
  371. (XftChar8*)text, strlen(text),
  372. &inputshadowcolor,
  373. inputShadowXOffset, inputShadowYOffset);
  374. }
  375. XftDrawDestroy (draw);
  376. Cursor(SHOW);
  377. }
  378. // Draw welcome and "enter username" message
  379. void Panel::ShowText(){
  380. string cfgX, cfgY;
  381. XGlyphInfo extents;
  382. bool singleInputMode =
  383. input_name_x == input_pass_x &&
  384. input_name_y == input_pass_y;
  385. XftDraw *draw = XftDrawCreate(Dpy, Win,
  386. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  387. /* welcome message */
  388. XftTextExtents8(Dpy, welcomefont, (XftChar8*)welcome_message.c_str(),
  389. strlen(welcome_message.c_str()), &extents);
  390. cfgX = cfg->getOption("welcome_x");
  391. cfgY = cfg->getOption("welcome_y");
  392. int shadowXOffset =
  393. Cfg::string2int(cfg->getOption("welcome_shadow_xoffset").c_str());
  394. int shadowYOffset =
  395. Cfg::string2int(cfg->getOption("welcome_shadow_yoffset").c_str());
  396. welcome_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  397. welcome_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  398. if (welcome_x >= 0 && welcome_y >= 0) {
  399. SlimDrawString8 (draw, &welcomecolor, welcomefont,
  400. welcome_x, welcome_y,
  401. (XftChar8*)welcome_message.c_str(),
  402. strlen(welcome_message.c_str()),
  403. &welcomeshadowcolor, shadowXOffset, shadowYOffset);
  404. }
  405. /* Enter username-password message */
  406. string msg;
  407. if (!singleInputMode|| In->GetField() == GET_PASSWD ) {
  408. msg = cfg->getOption("password_msg");
  409. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  410. strlen(msg.c_str()), &extents);
  411. cfgX = cfg->getOption("password_x");
  412. cfgY = cfg->getOption("password_y");
  413. int shadowXOffset =
  414. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  415. int shadowYOffset =
  416. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  417. password_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  418. password_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  419. if (password_x >= 0 && password_y >= 0){
  420. SlimDrawString8 (draw, &entercolor, enterfont, password_x, password_y,
  421. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  422. &entershadowcolor, shadowXOffset, shadowYOffset);
  423. }
  424. }
  425. if (!singleInputMode|| In->GetField() == GET_NAME ) {
  426. msg = cfg->getOption("username_msg");
  427. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  428. strlen(msg.c_str()), &extents);
  429. cfgX = cfg->getOption("username_x");
  430. cfgY = cfg->getOption("username_y");
  431. int shadowXOffset =
  432. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  433. int shadowYOffset =
  434. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  435. username_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  436. username_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  437. if (username_x >= 0 && username_y >= 0){
  438. SlimDrawString8 (draw, &entercolor, enterfont, username_x, username_y,
  439. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  440. &entershadowcolor, shadowXOffset, shadowYOffset);
  441. }
  442. }
  443. XftDrawDestroy(draw);
  444. }
  445. string Panel::getSession() {
  446. return session;
  447. }
  448. // choose next available session type
  449. void Panel::SwitchSession() {
  450. session = cfg->nextSession(session);
  451. //TODO: get sessions from cfg and cycle to the next one
  452. ShowSession();
  453. }
  454. // Display session type on the screen
  455. void Panel::ShowSession() {
  456. XClearWindow(Dpy, Root);
  457. string currsession = "Session: " + session;
  458. char* text = (char*) currsession.c_str();
  459. XGlyphInfo extents;
  460. XftDraw *draw = XftDrawCreate(Dpy, Root,
  461. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  462. XftTextExtents8(Dpy, msgfont, (XftChar8*)text,
  463. strlen(text), &extents);
  464. int msg_x = Cfg::absolutepos("50%", XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
  465. int msg_y = XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)) - extents.height -100;
  466. int shadowXOffset =
  467. Cfg::string2int(cfg->getOption("welcome_shadow_xoffset").c_str());
  468. int shadowYOffset =
  469. Cfg::string2int(cfg->getOption("welcome_shadow_yoffset").c_str());
  470. SlimDrawString8(draw, &msgcolor, msgfont, msg_x, msg_y,
  471. (XftChar8*)text, strlen(text),
  472. &msgshadowcolor,
  473. shadowXOffset, shadowYOffset);
  474. XFlush(Dpy);
  475. XftDrawDestroy(draw);
  476. }
  477. void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font,
  478. int x, int y, XftChar8 *string, int len,
  479. XftColor* shadowColor,
  480. int xOffset, int yOffset)
  481. {
  482. if (xOffset && yOffset) {
  483. XftDrawString8(d, shadowColor, font, x+xOffset, y+yOffset,
  484. string, len);
  485. }
  486. XftDrawString8(d, color, font, x, y, string, len);
  487. }