Xinqi Bao's Git
   1 /* See LICENSE file for copyright and license details. */ 
  10 #include <X11/Xutil.h> 
  12 #include <X11/extensions/Xinerama.h> 
  16 #define INTERSECT(x,y,w,h,r)  (MAX(0, MIN((x)+(w),(r).x_org+(r).width)  - MAX((x),(r).x_org)) \ 
  17                              * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) 
  18 #define MIN(a,b)              ((a) < (b) ? (a) : (b)) 
  19 #define MAX(a,b)              ((a) > (b) ? (a) : (b)) 
  21 typedef struct Item Item
; 
  27 static void appenditem(Item 
*item
, Item 
**list
, Item 
**last
); 
  28 static void calcoffsets(void); 
  29 static char *cistrstr(const char *s
, const char *sub
); 
  30 static void drawmenu(void); 
  31 static void grabkeyboard(void); 
  32 static void insert(const char *str
, ssize_t n
); 
  33 static void keypress(XKeyEvent 
*ev
); 
  34 static void match(void); 
  35 static size_t nextrune(int inc
); 
  36 static void paste(void); 
  37 static void readstdin(void); 
  38 static void run(void); 
  39 static void setup(void); 
  40 static void usage(void); 
  42 static char text
[BUFSIZ
] = ""; 
  43 static int bh
, mw
, mh
; 
  44 static int inputw
, promptw
; 
  45 static size_t cursor 
= 0; 
  46 static const char *font 
= NULL
; 
  47 static const char *prompt 
= NULL
; 
  48 static const char *normbgcolor 
= "#222222"; 
  49 static const char *normfgcolor 
= "#bbbbbb"; 
  50 static const char *selbgcolor  
= "#005577"; 
  51 static const char *selfgcolor  
= "#eeeeee"; 
  52 static unsigned int lines 
= 0; 
  53 static unsigned long normcol
[ColLast
]; 
  54 static unsigned long selcol
[ColLast
]; 
  55 static Atom clip
, utf8
; 
  56 static Bool topbar 
= True
; 
  58 static Item 
*items 
= NULL
; 
  59 static Item 
*matches
, *matchend
; 
  60 static Item 
*prev
, *curr
, *next
, *sel
; 
  64 static int (*fstrncmp
)(const char *, const char *, size_t) = strncmp
; 
  65 static char *(*fstrstr
)(const char *, const char *) = strstr
; 
  68 main(int argc
, char *argv
[]) { 
  72         for(i 
= 1; i 
< argc
; i
++) 
  73                 /* these options take no arguments */ 
  74                 if(!strcmp(argv
[i
], "-v")) {      /* prints version information */ 
  75                         puts("dmenu-"VERSION
", © 2006-2011 dmenu engineers, see LICENSE for details"); 
  78                 else if(!strcmp(argv
[i
], "-b"))   /* appears at the bottom of the screen */ 
  80                 else if(!strcmp(argv
[i
], "-f"))   /* grabs keyboard before reading stdin */ 
  82                 else if(!strcmp(argv
[i
], "-i")) { /* case-insensitive item matching */ 
  83                         fstrncmp 
= strncasecmp
; 
  88                 /* these options take one argument */ 
  89                 else if(!strcmp(argv
[i
], "-l"))   /* number of lines in vertical list */ 
  90                         lines 
= atoi(argv
[++i
]); 
  91                 else if(!strcmp(argv
[i
], "-p"))   /* adds prompt to left of input field */ 
  93                 else if(!strcmp(argv
[i
], "-fn"))  /* font or font set */ 
  95                 else if(!strcmp(argv
[i
], "-nb"))  /* normal background color */ 
  96                         normbgcolor 
= argv
[++i
]; 
  97                 else if(!strcmp(argv
[i
], "-nf"))  /* normal foreground color */ 
  98                         normfgcolor 
= argv
[++i
]; 
  99                 else if(!strcmp(argv
[i
], "-sb"))  /* selected background color */ 
 100                         selbgcolor 
= argv
[++i
]; 
 101                 else if(!strcmp(argv
[i
], "-sf"))  /* selected foreground color */ 
 102                         selfgcolor 
= argv
[++i
]; 
 120         return EXIT_FAILURE
; /* unreachable */ 
 124 appenditem(Item 
*item
, Item 
**list
, Item 
**last
) { 
 126                 (*last
)->right 
= item
; 
 142                 n 
= mw 
- (promptw 
+ inputw 
+ textw(dc
, "<") + textw(dc
, ">")); 
 143         /* calculate which items will begin the next page and previous page */ 
 144         for(i 
= 0, next 
= curr
; next
; next 
= next
->right
) 
 145                 if((i 
+= (lines 
> 0) ? bh 
: MIN(textw(dc
, next
->text
), n
)) > n
) 
 147         for(i 
= 0, prev 
= curr
; prev 
&& prev
->left
; prev 
= prev
->left
) 
 148                 if((i 
+= (lines 
> 0) ? bh 
: MIN(textw(dc
, prev
->left
->text
), n
)) > n
) 
 153 cistrstr(const char *s
, const char *sub
) { 
 156         for(len 
= strlen(sub
); *s
; s
++) 
 157                 if(!strncasecmp(s
, sub
, len
)) 
 170         drawrect(dc
, 0, 0, mw
, mh
, True
, BG(dc
, normcol
)); 
 174                 drawtext(dc
, prompt
, selcol
); 
 177         /* draw input field */ 
 178         dc
->w 
= (lines 
> 0 || !matches
) ? mw 
- dc
->x 
: inputw
; 
 179         drawtext(dc
, text
, normcol
); 
 180         if((curpos 
= textnw(dc
, text
, cursor
) + dc
->h
/2 - 2) < dc
->w
) 
 181                 drawrect(dc
, curpos
, 2, 1, dc
->h 
- 4, True
, FG(dc
, normcol
)); 
 184                 /* draw vertical list */ 
 186                 for(item 
= curr
; item 
!= next
; item 
= item
->right
) { 
 188                         drawtext(dc
, item
->text
, (item 
== sel
) ? selcol 
: normcol
); 
 192                 /* draw horizontal list */ 
 194                 dc
->w 
= textw(dc
, "<"); 
 196                         drawtext(dc
, "<", normcol
); 
 197                 for(item 
= curr
; item 
!= next
; item 
= item
->right
) { 
 199                         dc
->w 
= MIN(textw(dc
, item
->text
), mw 
- dc
->x 
- textw(dc
, ">")); 
 200                         drawtext(dc
, item
->text
, (item 
== sel
) ? selcol 
: normcol
); 
 202                 dc
->w 
= textw(dc
, ">"); 
 205                         drawtext(dc
, ">", normcol
); 
 207         mapdc(dc
, win
, mw
, mh
); 
 214         /* try to grab keyboard, we may have to wait for another process to ungrab */ 
 215         for(i 
= 0; i 
< 1000; i
++) { 
 216                 if(XGrabKeyboard(dc
->dpy
, DefaultRootWindow(dc
->dpy
), True
, 
 217                                  GrabModeAsync
, GrabModeAsync
, CurrentTime
) == GrabSuccess
) 
 221         eprintf("cannot grab keyboard\n"); 
 225 insert(const char *str
, ssize_t n
) { 
 226         if(strlen(text
) + n 
> sizeof text 
- 1) 
 228         /* move existing text out of the way, insert new text, and update cursor */ 
 229         memmove(&text
[cursor 
+ n
], &text
[cursor
], sizeof text 
- cursor 
- MAX(n
, 0)); 
 231                 memcpy(&text
[cursor
], str
, n
); 
 237 keypress(XKeyEvent 
*ev
) { 
 240         KeySym ksym 
= NoSymbol
; 
 243         len 
= XmbLookupString(xic
, ev
, buf
, sizeof buf
, &ksym
, &status
); 
 244         if(status 
== XBufferOverflow
) 
 246         if(ev
->state 
& ControlMask
) { 
 249                 XConvertCase(ksym
, &lower
, &upper
); 
 251                 case XK_a
: ksym 
= XK_Home
;      break; 
 252                 case XK_b
: ksym 
= XK_Left
;      break; 
 253                 case XK_c
: ksym 
= XK_Escape
;    break; 
 254                 case XK_d
: ksym 
= XK_Delete
;    break; 
 255                 case XK_e
: ksym 
= XK_End
;       break; 
 256                 case XK_f
: ksym 
= XK_Right
;     break; 
 257                 case XK_h
: ksym 
= XK_BackSpace
; break; 
 258                 case XK_i
: ksym 
= XK_Tab
;       break; 
 259                 case XK_j
: ksym 
= XK_Return
;    break; 
 260                 case XK_m
: ksym 
= XK_Return
;    break; 
 261                 case XK_n
: ksym 
= XK_Down
;      break; 
 262                 case XK_p
: ksym 
= XK_Up
;        break; 
 264                 case XK_k
: /* delete right */ 
 268                 case XK_u
: /* delete left */ 
 269                         insert(NULL
, 0 - cursor
); 
 271                 case XK_w
: /* delete word */ 
 272                         while(cursor 
> 0 && text
[nextrune(-1)] == ' ') 
 273                                 insert(NULL
, nextrune(-1) - cursor
); 
 274                         while(cursor 
> 0 && text
[nextrune(-1)] != ' ') 
 275                                 insert(NULL
, nextrune(-1) - cursor
); 
 277                 case XK_y
: /* paste selection */ 
 278                         XConvertSelection(dc
->dpy
, (ev
->state 
& ShiftMask
) ? clip 
: XA_PRIMARY
, 
 279                                           utf8
, utf8
, win
, CurrentTime
); 
 291                 if(text
[cursor
] == '\0') 
 293                 cursor 
= nextrune(+1); 
 298                 insert(NULL
, nextrune(-1) - cursor
); 
 301                 if(text
[cursor
] != '\0') { 
 302                         cursor 
= strlen(text
); 
 306                         /* jump to end of list and position items in reverse */ 
 311                         while(next 
&& (curr 
= curr
->right
)) 
 323                 sel 
= curr 
= matches
; 
 327                 if(cursor 
> 0 && (!sel 
|| !sel
->left 
|| lines 
> 0)) { 
 328                         cursor 
= nextrune(-1); 
 333                 if(sel 
&& sel
->left 
&& (sel 
= sel
->left
)->right 
== curr
) { 
 352                 puts((sel 
&& !(ev
->state 
& ShiftMask
)) ? sel
->text 
: text
); 
 355                 if(text
[cursor
] != '\0') { 
 356                         cursor 
= nextrune(+1); 
 361                 if(sel 
&& sel
->right 
&& (sel 
= sel
->right
) == next
) { 
 369                 strncpy(text
, sel
->text
, sizeof text
); 
 370                 cursor 
= strlen(text
); 
 379         static char **tokv 
= NULL
; 
 382         char buf
[sizeof text
], *s
; 
 385         Item 
*item
, *lprefix
, *lsubstr
, *prefixend
, *substrend
; 
 388         /* separate input text into tokens to be matched individually */ 
 389         for(s 
= strtok(buf
, " "); s
; tokv
[tokc
-1] = s
, s 
= strtok(NULL
, " ")) 
 390                 if(++tokc 
> tokn 
&& !(tokv 
= realloc(tokv
, ++tokn 
* sizeof *tokv
))) 
 391                         eprintf("cannot realloc %u bytes\n", tokn 
* sizeof *tokv
); 
 392         len 
= tokc 
? strlen(tokv
[0]) : 0; 
 394         matches 
= lprefix 
= lsubstr 
= matchend 
= prefixend 
= substrend 
= NULL
; 
 395         for(item 
= items
; item 
&& item
->text
; item
++) { 
 396                 for(i 
= 0; i 
< tokc
; i
++) 
 397                         if(!fstrstr(item
->text
, tokv
[i
])) 
 399                 if(i 
!= tokc
) /* not all tokens match */ 
 401                 /* exact matches go first, then prefixes, then substrings */ 
 402                 if(!tokc 
|| !fstrncmp(tokv
[0], item
->text
, len
+1)) 
 403                         appenditem(item
, &matches
, &matchend
); 
 404                 else if(!fstrncmp(tokv
[0], item
->text
, len
)) 
 405                         appenditem(item
, &lprefix
, &prefixend
); 
 407                         appenditem(item
, &lsubstr
, &substrend
); 
 411                         matchend
->right 
= lprefix
; 
 412                         lprefix
->left 
= matchend
; 
 416                 matchend 
= prefixend
; 
 420                         matchend
->right 
= lsubstr
; 
 421                         lsubstr
->left 
= matchend
; 
 425                 matchend 
= substrend
; 
 427         curr 
= sel 
= matches
; 
 435         /* return location of next utf8 rune in the given direction (+1 or -1) */ 
 436         for(n 
= cursor 
+ inc
; n 
+ inc 
>= 0 && (text
[n
] & 0xc0) == 0x80; n 
+= inc
); 
 447         /* we have been given the current selection, now insert it into input */ 
 448         XGetWindowProperty(dc
->dpy
, win
, utf8
, 0, (sizeof text 
/ 4) + 1, False
, 
 449                            utf8
, &da
, &di
, &dl
, &dl
, (unsigned char **)&p
); 
 450         insert(p
, (q 
= strchr(p
, '\n')) ? q
-p 
: (ssize_t
)strlen(p
)); 
 457         char buf
[sizeof text
], *p
, *maxstr 
= NULL
; 
 458         size_t i
, max 
= 0, size 
= 0; 
 460         /* read each line from stdin and add it to the item list */ 
 461         for(i 
= 0; fgets(buf
, sizeof buf
, stdin
); i
++) { 
 462                 if(i
+1 >= size 
/ sizeof *items
) 
 463                         if(!(items 
= realloc(items
, (size 
+= BUFSIZ
)))) 
 464                                 eprintf("cannot realloc %u bytes:", size
); 
 465                 if((p 
= strchr(buf
, '\n'))) 
 467                 if(!(items
[i
].text 
= strdup(buf
))) 
 468                         eprintf("cannot strdup %u bytes:", strlen(buf
)+1); 
 469                 if(strlen(items
[i
].text
) > max
) 
 470                         max 
= strlen(maxstr 
= items
[i
].text
); 
 473                 items
[i
].text 
= NULL
; 
 474         inputw 
= maxstr 
? textw(dc
, maxstr
) : 0; 
 475         lines 
= MIN(lines
, i
); 
 482         while(!XNextEvent(dc
->dpy
, &ev
)) { 
 483                 if(XFilterEvent(&ev
, win
)) 
 487                         if(ev
.xexpose
.count 
== 0) 
 488                                 mapdc(dc
, win
, mw
, mh
); 
 493                 case SelectionNotify
: 
 494                         if(ev
.xselection
.property 
== utf8
) 
 497                 case VisibilityNotify
: 
 498                         if(ev
.xvisibility
.state 
!= VisibilityUnobscured
) 
 499                                 XRaiseWindow(dc
->dpy
, win
); 
 507         int x
, y
, screen 
= DefaultScreen(dc
->dpy
); 
 508         Window root 
= RootWindow(dc
->dpy
, screen
); 
 509         XSetWindowAttributes swa
; 
 513         XineramaScreenInfo 
*info
; 
 516         normcol
[ColBG
] = getcolor(dc
, normbgcolor
); 
 517         normcol
[ColFG
] = getcolor(dc
, normfgcolor
); 
 518         selcol
[ColBG
]  = getcolor(dc
, selbgcolor
); 
 519         selcol
[ColFG
]  = getcolor(dc
, selfgcolor
); 
 521         clip 
= XInternAtom(dc
->dpy
, "CLIPBOARD",   False
); 
 522         utf8 
= XInternAtom(dc
->dpy
, "UTF8_STRING", False
); 
 524         /* calculate menu geometry */ 
 525         bh 
= dc
->font
.height 
+ 2; 
 526         lines 
= MAX(lines
, 0); 
 527         mh 
= (lines 
+ 1) * bh
; 
 529         if((info 
= XineramaQueryScreens(dc
->dpy
, &n
))) { 
 530                 int a
, j
, di
, i 
= 0, area 
= 0; 
 532                 Window w
, pw
, dw
, *dws
; 
 533                 XWindowAttributes wa
; 
 535                 XGetInputFocus(dc
->dpy
, &w
, &di
); 
 536                 if(w 
!= root 
&& w 
!= PointerRoot 
&& w 
!= None
) { 
 537                         /* find top-level window containing current input focus */ 
 539                                 if(XQueryTree(dc
->dpy
, (pw 
= w
), &dw
, &w
, &dws
, &du
) && dws
) 
 541                         } while(w 
!= root 
&& w 
!= pw
); 
 542                         /* find xinerama screen with which the window intersects most */ 
 543                         if(XGetWindowAttributes(dc
->dpy
, pw
, &wa
)) 
 544                                 for(j 
= 0; j 
< n
; j
++) 
 545                                         if((a 
= INTERSECT(wa
.x
, wa
.y
, wa
.width
, wa
.height
, info
[j
])) > area
) { 
 550                 /* no focused window is on screen, so use pointer location instead */ 
 551                 if(!area 
&& XQueryPointer(dc
->dpy
, root
, &dw
, &dw
, &x
, &y
, &di
, &di
, &du
)) 
 552                         for(i 
= 0; i 
< n
; i
++) 
 553                                 if(INTERSECT(x
, y
, 1, 1, info
[i
])) 
 557                 y 
= info
[i
].y_org 
+ (topbar 
? 0 : info
[i
].height 
- mh
); 
 565                 y 
= topbar 
? 0 : DisplayHeight(dc
->dpy
, screen
) - mh
; 
 566                 mw 
= DisplayWidth(dc
->dpy
, screen
); 
 568         promptw 
= prompt 
? textw(dc
, prompt
) : 0; 
 569         inputw 
= MIN(inputw
, mw
/3); 
 572         /* create menu window */ 
 573         swa
.override_redirect 
= True
; 
 574         swa
.background_pixmap 
= ParentRelative
; 
 575         swa
.event_mask 
= ExposureMask 
| KeyPressMask 
| VisibilityChangeMask
; 
 576         win 
= XCreateWindow(dc
->dpy
, root
, x
, y
, mw
, mh
, 0, 
 577                             DefaultDepth(dc
->dpy
, screen
), CopyFromParent
, 
 578                             DefaultVisual(dc
->dpy
, screen
), 
 579                             CWOverrideRedirect 
| CWBackPixmap 
| CWEventMask
, &swa
); 
 581         /* open input methods */ 
 582         xim 
= XOpenIM(dc
->dpy
, NULL
, NULL
, NULL
); 
 583         xic 
= XCreateIC(xim
, XNInputStyle
, XIMPreeditNothing 
| XIMStatusNothing
, 
 584                         XNClientWindow
, win
, XNFocusWindow
, win
, NULL
); 
 586         XMapRaised(dc
->dpy
, win
); 
 587         resizedc(dc
, mw
, mh
); 
 593         fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font]\n" 
 594               "             [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr
);