panel.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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, x2,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. }
  218. XGlyphInfo extents;
  219. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  220. cheight = extents.height;
  221. y2 = yy - extents.y + extents.height;
  222. XftTextExtents8(Dpy, font, (XftChar8*)text, strlen(text), &extents);
  223. xx += extents.width;
  224. if(visible == SHOW) {
  225. XSetForeground(Dpy, TextGC,
  226. GetColor(cfg->getOption("input_color").c_str()));
  227. XDrawLine(Dpy, Win, TextGC,
  228. xx+1, yy-cheight,
  229. xx+1, y2);
  230. } else {
  231. XClearArea(Dpy, Win, xx+1, yy-cheight,
  232. 1, y2-(yy-cheight)+1, false);
  233. }
  234. }
  235. int Panel::EventHandler(XEvent* event) {
  236. Action = WAIT;
  237. switch(event->type) {
  238. case Expose:
  239. OnExpose(event);
  240. break;
  241. case KeyPress:
  242. OnKeyPress(event);
  243. break;
  244. }
  245. return Action;
  246. }
  247. void Panel::OnExpose(XEvent* event) {
  248. char* name = In->GetName();
  249. char* passwd = In->GetHiddenPasswd();
  250. XftDraw *draw = XftDrawCreate(Dpy, Win,
  251. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  252. if (input_pass_x != input_name_x || input_pass_y != input_name_y){
  253. SlimDrawString8 (draw, &inputcolor, font, input_name_x, input_name_y,
  254. (XftChar8*)name, strlen(name),
  255. &inputshadowcolor,
  256. inputShadowXOffset, inputShadowYOffset);
  257. SlimDrawString8 (draw, &inputcolor, font, input_pass_x, input_pass_y,
  258. (XftChar8*)passwd, strlen(passwd),
  259. &inputshadowcolor,
  260. inputShadowXOffset, inputShadowYOffset);
  261. } else { //single input mode
  262. switch(In->GetField()) {
  263. case GET_PASSWD:
  264. SlimDrawString8 (draw, &inputcolor, font,
  265. input_pass_x, input_pass_y,
  266. (XftChar8*)passwd, strlen(passwd),
  267. &inputshadowcolor,
  268. inputShadowXOffset, inputShadowYOffset);
  269. break;
  270. case GET_NAME:
  271. SlimDrawString8 (draw, &inputcolor, font,
  272. input_name_x, input_name_y,
  273. (XftChar8*)name, strlen(name),
  274. &inputshadowcolor,
  275. inputShadowXOffset, inputShadowYOffset);
  276. break;
  277. }
  278. }
  279. XftDrawDestroy (draw);
  280. Cursor(SHOW);
  281. ShowText();
  282. }
  283. void Panel::OnKeyPress(XEvent* event) {
  284. char del;
  285. char buffer;
  286. KeySym keysym;
  287. XComposeStatus compstatus;
  288. int xx;
  289. int yy;
  290. char* text;
  291. bool singleInputMode =
  292. input_name_x == input_pass_x &&
  293. input_name_y == input_pass_y;
  294. Cursor(HIDE);
  295. XLookupString(&event->xkey, &buffer, 1, &keysym, &compstatus);
  296. del = In->Key(buffer, keysym, singleInputMode);
  297. Action = In->GetAction();
  298. XGlyphInfo extents, delextents;
  299. XftDraw *draw = XftDrawCreate(Dpy, Win,
  300. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  301. if (keysym == XK_F1) {
  302. SwitchSession();
  303. }
  304. bool clearField = false;
  305. string formerString = "";
  306. if ((((XKeyEvent*)event)->state & ControlMask)) {
  307. if (keysym == XK_w || keysym == XK_u) {
  308. clearField = true;
  309. }
  310. }
  311. switch(In->GetField()) {
  312. case GET_PASSWD:
  313. if (strlen(In->GetHiddenPasswd()) == 0){
  314. // clear name and welcome label if we just entered the
  315. // password field
  316. if (singleInputMode) {
  317. xx = input_name_x;
  318. yy = input_name_y;
  319. text = In->GetName();
  320. XftTextExtents8(Dpy, font, (XftChar8*)text,
  321. strlen(text), &extents);
  322. XClearWindow(Dpy, Win);
  323. ShowText();
  324. }
  325. }
  326. if (clearField) {
  327. formerString = In->GetHiddenPasswd();
  328. In->ResetPassword();
  329. }
  330. text = In->GetHiddenPasswd();
  331. xx = input_pass_x;
  332. yy = input_pass_y;
  333. break;
  334. case GET_NAME:
  335. if (clearField) {
  336. formerString = In->GetName();
  337. In->ResetName();
  338. }
  339. text = In->GetName();
  340. xx = input_name_x;
  341. yy = input_name_y;
  342. break;
  343. }
  344. char* txth = "Wj"; // get proper maximum height ?
  345. XftTextExtents8(Dpy, font, (XftChar8*)txth, strlen(txth), &extents);
  346. int maxHeight = extents.height;
  347. string tmp = "";
  348. if (clearField) {
  349. tmp = formerString;
  350. } else {
  351. tmp = text;
  352. tmp = tmp + del;
  353. }
  354. XftTextExtents8(Dpy, font, (XftChar8*)tmp.c_str(),
  355. strlen(tmp.c_str()), &extents);
  356. int maxLength = extents.width;
  357. XClearArea(Dpy, Win, xx-3, yy-maxHeight-3,
  358. maxLength+6, maxHeight+6, false);
  359. if (!clearField) {
  360. SlimDrawString8 (draw, &inputcolor, font, xx, yy,
  361. (XftChar8*)text, strlen(text),
  362. &inputshadowcolor,
  363. inputShadowXOffset, inputShadowYOffset);
  364. }
  365. XftDrawDestroy (draw);
  366. Cursor(SHOW);
  367. }
  368. // Draw welcome and "enter username" message
  369. void Panel::ShowText(){
  370. string cfgX, cfgY;
  371. int n=-1;
  372. XGlyphInfo extents;
  373. bool singleInputMode =
  374. input_name_x == input_pass_x &&
  375. input_name_y == input_pass_y;
  376. XftDraw *draw = XftDrawCreate(Dpy, Win,
  377. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  378. /* welcome message */
  379. XftTextExtents8(Dpy, welcomefont, (XftChar8*)welcome_message.c_str(),
  380. strlen(welcome_message.c_str()), &extents);
  381. cfgX = cfg->getOption("welcome_x");
  382. cfgY = cfg->getOption("welcome_y");
  383. int shadowXOffset =
  384. Cfg::string2int(cfg->getOption("welcome_shadow_xoffset").c_str());
  385. int shadowYOffset =
  386. Cfg::string2int(cfg->getOption("welcome_shadow_yoffset").c_str());
  387. welcome_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  388. welcome_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  389. if (welcome_x >= 0 && welcome_y >= 0) {
  390. SlimDrawString8 (draw, &welcomecolor, welcomefont,
  391. welcome_x, welcome_y,
  392. (XftChar8*)welcome_message.c_str(),
  393. strlen(welcome_message.c_str()),
  394. &welcomeshadowcolor, shadowXOffset, shadowYOffset);
  395. }
  396. /* Enter username-password message */
  397. string msg;
  398. if (!singleInputMode|| In->GetField() == GET_PASSWD ) {
  399. msg = cfg->getOption("password_msg");
  400. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  401. strlen(msg.c_str()), &extents);
  402. cfgX = cfg->getOption("password_x");
  403. cfgY = cfg->getOption("password_y");
  404. int shadowXOffset =
  405. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  406. int shadowYOffset =
  407. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  408. password_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  409. password_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  410. if (password_x >= 0 && password_y >= 0){
  411. SlimDrawString8 (draw, &entercolor, enterfont, password_x, password_y,
  412. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  413. &entershadowcolor, shadowXOffset, shadowYOffset);
  414. }
  415. }
  416. if (!singleInputMode|| In->GetField() == GET_NAME ) {
  417. msg = cfg->getOption("username_msg");
  418. XftTextExtents8(Dpy, enterfont, (XftChar8*)msg.c_str(),
  419. strlen(msg.c_str()), &extents);
  420. cfgX = cfg->getOption("username_x");
  421. cfgY = cfg->getOption("username_y");
  422. int shadowXOffset =
  423. Cfg::string2int(cfg->getOption("username_shadow_xoffset").c_str());
  424. int shadowYOffset =
  425. Cfg::string2int(cfg->getOption("username_shadow_yoffset").c_str());
  426. username_x = Cfg::absolutepos(cfgX, image->Width(), extents.width);
  427. username_y = Cfg::absolutepos(cfgY, image->Height(), extents.height);
  428. if (username_x >= 0 && username_y >= 0){
  429. SlimDrawString8 (draw, &entercolor, enterfont, username_x, username_y,
  430. (XftChar8*)msg.c_str(), strlen(msg.c_str()),
  431. &entershadowcolor, shadowXOffset, shadowYOffset);
  432. }
  433. }
  434. XftDrawDestroy(draw);
  435. }
  436. string Panel::getSession() {
  437. return session;
  438. }
  439. // choose next available session type
  440. void Panel::SwitchSession() {
  441. session = cfg->nextSession(session);
  442. //TODO: get sessions from cfg and cycle to the next one
  443. ShowSession();
  444. }
  445. // Display session type on the screen
  446. void Panel::ShowSession() {
  447. XClearWindow(Dpy, Root);
  448. string currsession = "Session: " + session;
  449. char* text = (char*) currsession.c_str();
  450. XGlyphInfo extents;
  451. XftDraw *draw = XftDrawCreate(Dpy, Root,
  452. DefaultVisual(Dpy, Scr), DefaultColormap(Dpy, Scr));
  453. XftTextExtents8(Dpy, msgfont, (XftChar8*)text,
  454. strlen(text), &extents);
  455. int msg_x = Cfg::absolutepos("50%", XWidthOfScreen(ScreenOfDisplay(Dpy, Scr)), extents.width);
  456. int msg_y = XHeightOfScreen(ScreenOfDisplay(Dpy, Scr)) - extents.height -100;
  457. int shadowXOffset =
  458. Cfg::string2int(cfg->getOption("welcome_shadow_xoffset").c_str());
  459. int shadowYOffset =
  460. Cfg::string2int(cfg->getOption("welcome_shadow_yoffset").c_str());
  461. SlimDrawString8(draw, &msgcolor, msgfont, msg_x, msg_y,
  462. (XftChar8*)text, strlen(text),
  463. &msgshadowcolor,
  464. shadowXOffset, shadowYOffset);
  465. XFlush(Dpy);
  466. XftDrawDestroy(draw);
  467. }
  468. void Panel::SlimDrawString8(XftDraw *d, XftColor *color, XftFont *font,
  469. int x, int y, XftChar8 *string, int len,
  470. XftColor* shadowColor,
  471. int xOffset, int yOffset)
  472. {
  473. if (xOffset && yOffset) {
  474. XftDrawString8(d, shadowColor, font, x+xOffset, y+yOffset,
  475. string, len);
  476. }
  477. XftDrawString8(d, color, font, x, y, string, len);
  478. }