panel.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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. delete bg;
  112. PanelPixmap = image->createPixmap(Dpy, Scr, Root);
  113. // Read (and substitute vars in) the welcome message
  114. welcome_message = cfg->getWelcomeMessage();
  115. intro_message = cfg->getOption("intro_msg");
  116. // Init In
  117. In = new Input(cfg);
  118. }
  119. Panel::~Panel() {
  120. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &inputcolor);
  121. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &msgcolor);
  122. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &welcomecolor);
  123. XftColorFree (Dpy, DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr), &entercolor);
  124. XFreeGC(Dpy, TextGC);
  125. XftFontClose(Dpy, font);
  126. XftFontClose(Dpy, msgfont);
  127. XftFontClose(Dpy, introfont);
  128. XftFontClose(Dpy, welcomefont);
  129. XftFontClose(Dpy, enterfont);
  130. delete In;
  131. delete image;
  132. }
  133. void Panel::OpenPanel() {
  134. // Create window
  135. Win = XCreateSimpleWindow(Dpy, Root, X, Y,
  136. image->Width(),
  137. image->Height(),
  138. 0, GetColor("white"), GetColor("white"));
  139. // Events
  140. XSelectInput(Dpy, Win, ExposureMask | KeyPressMask);
  141. // Set background
  142. XSetWindowBackgroundPixmap(Dpy, Win, PanelPixmap);
  143. // Show window
  144. XMapWindow(Dpy, Win);
  145. XMoveWindow(Dpy, Win, X, Y); // override wm positioning (for tests)
  146. // Grab keyboard
  147. XGrabKeyboard(Dpy, Win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
  148. XFlush(Dpy);
  149. }
  150. void Panel::ClosePanel() {
  151. XUngrabKeyboard(Dpy, CurrentTime);
  152. XUnmapWindow(Dpy, Win);
  153. XDestroyWindow(Dpy, Win);
  154. XFlush(Dpy);
  155. }
  156. void Panel::ClearPanel() {
  157. session = "";
  158. In->Reset();
  159. XClearWindow(Dpy, Root);
  160. XClearWindow(Dpy, Win);
  161. Cursor(SHOW);
  162. ShowText();
  163. XFlush(Dpy);
  164. }
  165. void Panel::Message(const char* text) {
  166. string cfgX, cfgY;
  167. XGlyphInfo extents;
  168. XftDraw *draw = XftDrawCreate(Dpy, Root,
  169. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  170. XftTextExtents8(Dpy, msgfont, (XftChar8*)text,
  171. strlen(text), &extents);
  172. cfgX = cfg->getOption("msg_x");
  173. cfgY = cfg->getOption("msg_y");
  174. int shadowXOffset =
  175. Cfg::string2int(cfg->getOption("msg_shadow_xoffset").c_str());
  176. int shadowYOffset =
  177. Cfg::string2int(cfg->getOption("msg_shadow_yoffset").c_str());
  178. int msg_x = Cfg::absolutepos(cfgX, XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
  179. int msg_y = Cfg::absolutepos(cfgY, XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.height);
  180. SlimDrawString8 (draw, &msgcolor, msgfont, msg_x, msg_y,
  181. (XftChar8*)text, strlen(text),
  182. &msgshadowcolor,
  183. shadowXOffset, shadowYOffset);
  184. XFlush(Dpy);
  185. XftDrawDestroy(draw);
  186. }
  187. void Panel::Error(const char* text) {
  188. ClosePanel();
  189. Message(text);
  190. sleep(ERROR_DURATION);
  191. OpenPanel();
  192. ClearPanel();
  193. }
  194. Input* Panel::GetInput() {
  195. return In;
  196. }
  197. unsigned long Panel::GetColor(const char* colorname) {
  198. XColor color;
  199. XWindowAttributes attributes;
  200. XGetWindowAttributes(Dpy, Root, &attributes);
  201. color.pixel = 0;
  202. if(!XParseColor(Dpy, attributes.colormap, colorname, &color))
  203. cerr << APPNAME << ": can't parse color " << colorname << endl;
  204. else if(!XAllocColor(Dpy, attributes.colormap, &color))
  205. cerr << APPNAME << ": can't allocate color " << colorname << endl;
  206. return color.pixel;
  207. }
  208. void Panel::Cursor(int visible) {
  209. char* text;
  210. int xx, yy, y2, cheight;
  211. char* txth = "Wj"; // used to get cursor height
  212. switch(In->GetField()) {
  213. case GET_PASSWD:
  214. text = In->GetHiddenPasswd();
  215. xx = input_pass_x;
  216. yy = input_pass_y;
  217. break;
  218. case GET_NAME:
  219. text = In->GetName();
  220. xx = input_name_x;
  221. yy = input_name_y;
  222. break;
  223. default: /* Origin & NULL string as default values. */
  224. text = (char *)NULL;
  225. xx = (int)0;
  226. yy = (int)0;
  227. break;
  228. }
  229. XGlyphInfo extents;
  230. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  231. cheight = extents.height;
  232. y2 = yy - extents.y + extents.height;
  233. XftTextExtents8(Dpy, font, (XftChar8*)text, strlen(text), &extents);
  234. xx += extents.width;
  235. if(visible == SHOW) {
  236. XSetForeground(Dpy, TextGC,
  237. GetColor(cfg->getOption("input_color").c_str()));
  238. XDrawLine(Dpy, Win, TextGC,
  239. xx+1, yy-cheight,
  240. xx+1, y2);
  241. } else {
  242. XClearArea(Dpy, Win, xx+1, yy-cheight,
  243. 1, y2-(yy-cheight)+1, false);
  244. }
  245. }
  246. int Panel::EventHandler(XEvent* event) {
  247. Action = WAIT;
  248. switch(event->type) {
  249. case Expose:
  250. OnExpose(event);
  251. break;
  252. case KeyPress:
  253. OnKeyPress(event);
  254. break;
  255. }
  256. return Action;
  257. }
  258. void Panel::OnExpose(XEvent* event) {
  259. char* name = In->GetName();
  260. char* passwd = In->GetHiddenPasswd();
  261. XftDraw *draw = XftDrawCreate(Dpy, Win,
  262. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  263. if (input_pass_x != input_name_x || input_pass_y != input_name_y){
  264. SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y,
  265. (XftChar8*)name, strlen(name),
  266. &inputshadowcolor,
  267. inputShadowXOffset, inputShadowYOffset);
  268. SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y,
  269. (XftChar8*)passwd, strlen(passwd),
  270. &inputshadowcolor,
  271. inputShadowXOffset, inputShadowYOffset);
  272. } else { //single input mode
  273. switch(In->GetField()) {
  274. case GET_PASSWD:
  275. SlimDrawString8 (draw, &inputcolor, font,
  276. input_pass_x, input_pass_y,
  277. (XftChar8*)passwd, strlen(passwd),
  278. &inputshadowcolor,
  279. inputShadowXOffset, inputShadowYOffset);
  280. break;
  281. case GET_NAME:
  282. SlimDrawString8 (draw, &inputcolor, font,
  283. input_name_x, input_name_y,
  284. (XftChar8*)name, strlen(name),
  285. &inputshadowcolor,
  286. inputShadowXOffset, inputShadowYOffset);
  287. break;
  288. }
  289. }
  290. XftDrawDestroy (draw);
  291. Cursor(SHOW);
  292. ShowText();
  293. }
  294. void Panel::OnKeyPress(XEvent* event) {
  295. char del;
  296. char buffer;
  297. KeySym keysym;
  298. XComposeStatus compstatus;
  299. int xx;
  300. int yy;
  301. char* text;
  302. bool singleInputMode =
  303. input_name_x == input_pass_x &&
  304. input_name_y == input_pass_y;
  305. Cursor(HIDE);
  306. XLookupString(&event->xkey, &buffer, 1, &keysym, &compstatus);
  307. del = In->Key(buffer, keysym, singleInputMode);
  308. Action = In->GetAction();
  309. XGlyphInfo extents;
  310. XftDraw *draw = XftDrawCreate(Dpy, Win,
  311. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  312. if (keysym == XK_F1) {
  313. SwitchSession();
  314. }
  315. bool clearField = false;
  316. string formerString = "";
  317. if ((((XKeyEvent*)event)->state & ControlMask)) {
  318. if (keysym == XK_w || keysym == XK_u) {
  319. clearField = true;
  320. }
  321. }
  322. switch(In->GetField()) {
  323. case GET_PASSWD:
  324. if (strlen(In->GetHiddenPasswd()) == 0){
  325. // clear name and welcome label if we just entered the
  326. // password field
  327. if (singleInputMode) {
  328. xx = input_name_x;
  329. yy = input_name_y;
  330. text = In->GetName();
  331. XftTextExtents8(Dpy, font, (XftChar8*)text,
  332. strlen(text), &extents);
  333. XClearWindow(Dpy, Win);
  334. ShowText();
  335. }
  336. }
  337. if (clearField) {
  338. formerString = In->GetHiddenPasswd();
  339. In->ResetPassword();
  340. }
  341. text = In->GetHiddenPasswd();
  342. xx = input_pass_x;
  343. yy = input_pass_y;
  344. break;
  345. case GET_NAME:
  346. if (clearField) {
  347. formerString = In->GetName();
  348. In->ResetName();
  349. }
  350. text = In->GetName();
  351. xx = input_name_x;
  352. yy = input_name_y;
  353. break;
  354. default: /* Origin & NULL string as default values. */
  355. text = (char *)NULL;
  356. xx = (int)0;
  357. yy = (int)0;
  358. break;
  359. }
  360. char* txth = "Wj"; // get proper maximum height ?
  361. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  362. int maxHeight = extents.height;
  363. string tmp = "";
  364. if (clearField) {
  365. tmp = formerString;
  366. } else {
  367. tmp = text;
  368. tmp = tmp + del;
  369. }
  370. XftTextExtents8(Dpy, font, (XftChar8*)tmp.c_str(),
  371. strlen(tmp.c_str()), &extents);
  372. int maxLength = extents.width;
  373. XClearArea(Dpy, Win, xx-3, yy-maxHeight-3,
  374. maxLength+6, maxHeight+6, false);
  375. if (!clearField) {
  376. SlimDrawString8 (draw, &inputcolor, font, xx, yy,
  377. (XftChar8*)text, strlen(text),
  378. &inputshadowcolor,
  379. inputShadowXOffset, inputShadowYOffset);
  380. }
  381. XftDrawDestroy (draw);
  382. Cursor(SHOW);
  383. }
  384. // Draw welcome and "enter username" message
  385. void Panel::ShowText(){
  386. string cfgX, cfgY;
  387. XGlyphInfo extents;
  388. bool singleInputMode =
  389. input_name_x == input_pass_x &&
  390. input_name_y == input_pass_y;
  391. XftDraw *draw = XftDrawCreate(Dpy, Win,
  392. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  393. /* welcome message */
  394. XftTextExtents8(Dpy, welcomefont, (XftChar8*)welcome_message.c_str(),
  395. strlen(welcome_message.c_str()), &extents);
  396. cfgX = cfg->getOption("welcome_x");
  397. cfgY = cfg->getOption("welcome_y");
  398. int shadowXOffset =
  399. Cfg::string2int(cfg->getOption("welcome_shadow_xoffset").c_str());
  400. int shadowYOffset =
  401. Cfg::string2int(cfg->getOption("welcome_shadow_yoffset").c_str());
  402. welcome_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  403. welcome_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  404. if (welcome_x >= 0 && welcome_y >= 0) {
  405. SlimDrawString8 (draw, &welcomecolor, welcomefont,
  406. welcome_x, welcome_y,
  407. (XftChar8*)welcome_message.c_str(),
  408. strlen(welcome_message.c_str()),
  409. &welcomeshadowcolor, shadowXOffset, shadowYOffset);
  410. }
  411. /* Enter username-password message */
  412. string msg;
  413. if (!singleInputMode|| In->GetField() == GET_PASSWD ) {
  414. msg = cfg->getOption("password_msg");
  415. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  416. strlen(msg.c_str()), &extents);
  417. cfgX = cfg->getOption("password_x");
  418. cfgY = cfg->getOption("password_y");
  419. int shadowXOffset =
  420. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  421. int shadowYOffset =
  422. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  423. password_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  424. password_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  425. if (password_x >= 0 && password_y >= 0){
  426. SlimDrawString8 (draw, &entercolor, enterfont, password_x, password_y,
  427. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  428. &entershadowcolor, shadowXOffset, shadowYOffset);
  429. }
  430. }
  431. if (!singleInputMode|| In->GetField() == GET_NAME ) {
  432. msg = cfg->getOption("username_msg");
  433. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  434. strlen(msg.c_str()), &extents);
  435. cfgX = cfg->getOption("username_x");
  436. cfgY = cfg->getOption("username_y");
  437. int shadowXOffset =
  438. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  439. int shadowYOffset =
  440. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  441. username_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  442. username_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  443. if (username_x >= 0 && username_y >= 0){
  444. SlimDrawString8 (draw, &entercolor, enterfont, username_x, username_y,
  445. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  446. &entershadowcolor, shadowXOffset, shadowYOffset);
  447. }
  448. }
  449. XftDrawDestroy(draw);
  450. }
  451. string Panel::getSession() {
  452. return session;
  453. }
  454. // choose next available session type
  455. void Panel::SwitchSession() {
  456. session = cfg->nextSession(session);
  457. if (session.size() > 0) {
  458. ShowSession();
  459. }
  460. }
  461. // Display session type on the screen
  462. void Panel::ShowSession() {
  463. XClearWindow(Dpy, Root);
  464. string currsession = "Session: " + session;
  465. char* text = (char*) currsession.c_str();
  466. XGlyphInfo extents;
  467. XftDraw *draw = XftDrawCreate(Dpy, Root,
  468. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  469. XftTextExtents8(Dpy, msgfont, (XftChar8*)text,
  470. strlen(text), &extents);
  471. int msg_x = Cfg::absolutepos("50%", XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
  472. int msg_y = XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)) - extents.height -100;
  473. int shadowXOffset =
  474. Cfg::string2int(cfg->getOption("msg_shadow_xoffset").c_str());
  475. int shadowYOffset =
  476. Cfg::string2int(cfg->getOption("msg_shadow_yoffset").c_str());
  477. SlimDrawString8(draw, &msgcolor, msgfont, msg_x, msg_y,
  478. (XftChar8*)text, strlen(text),
  479. &msgshadowcolor,
  480. shadowXOffset, shadowYOffset);
  481. XFlush(Dpy);
  482. XftDrawDestroy(draw);
  483. }
  484. void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font,
  485. int x, int y, XftChar8 *string, int len,
  486. XftColor* shadowColor,
  487. int xOffset, int yOffset)
  488. {
  489. if (xOffset && yOffset) {
  490. XftDrawString8(d, shadowColor, font, x+xOffset, y+yOffset,
  491. string, len);
  492. }
  493. XftDrawString8(d, color, font, x, y, string, len);
  494. }