spmenu/spmenu.c

583 lines
13 KiB
C
Raw Normal View History

2023-04-02 01:49:58 +02:00
/* spmenu - fancy dynamic menu
* See LICENSE file for copyright and license details.
*/
2023-01-20 23:17:30 +01:00
#include <ctype.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
2023-01-20 23:17:30 +01:00
2023-05-22 01:20:54 +02:00
// set version
#ifndef VERSION
#define VERSION "unknown"
#endif
// check if we should enable right to left language support
#ifndef RTL
#define USERTL 0
2023-02-28 15:51:32 +01:00
#else
#define USERTL 1
#endif
// check if we should enable image support
#ifndef IMAGE
2023-02-26 16:38:26 +01:00
#define USEIMAGE 0
#else
#define USEIMAGE 1
#endif
// check if we should enable multimonitor support using libXinerama
2023-03-28 21:46:23 +02:00
#ifndef XINERAMA
2023-02-28 15:51:32 +01:00
#define USEXINERAMA 0
2023-03-28 21:46:23 +02:00
#else
#define USEXINERAMA 1
2023-02-28 15:51:32 +01:00
#endif
// check if we should enable config file support using libconfig
#ifndef CONFIG
#define USECONFIG 0
#else
#define USECONFIG 1
#endif
// check if we should enable .Xresources support
#ifndef XRESOURCES
#define USEXRESOURCES 0
#else
#define USEXRESOURCES 1
#endif
// check if we should enable Wayland support
#ifndef WAYLAND
#define USEWAYLAND 0
#else
#define USEWAYLAND 1
#endif
// check if we should enable X11 support
#ifndef X11
#define USEX 0
#else
#define USEX 1
#endif
// include fribidi used for right to left language support
2023-02-28 15:51:32 +01:00
#if USERTL
#include <fribidi.h>
#endif
// include libraries used for image support
#if USEIMAGE
#include <errno.h>
#include <pwd.h>
#include <Imlib2.h>
#include <openssl/md5.h>
#endif
2023-01-20 23:17:30 +01:00
// include Xlib
#if USEX
#include <X11/Xlib.h>
#endif
// include macros and other defines
#include "libs/define.c"
2023-06-23 03:38:21 +02:00
enum {
ClickWindow,
ClickPrompt,
ClickInput,
ClickLArrow,
ClickItem,
ClickSelItem,
ClickRArrow,
ClickNumber,
ClickCaps,
ClickMode,
};
struct item {
char *text;
char *clntext;
char *image;
char *ex;
struct item *left, *right;
int hp;
int index;
double distance;
};
struct sp {
int bh; // height of each menu item
int mw; // width
int mh; // height
int vp; // vertical padding for bar
int sp; // side padding for bar
int lrpad; // sum of left and right padding
int mode; // current mode
int allowkeys; // interpret a keypress as an insertion?
int capslockstate; // caps lock state
int inputw; // input width
int promptw; // prompt width
int plw; // powerline width
int itemnumber; // item number
size_t cursor;
int ignoreconfkeys; // can be set globally if you don't want to override keybinds with config file keys
int ignoreglobalkeys; // should be set in the config file, if 1, the Keys keys array is ignored
int ignoreconfmouse; // same for mouse
int ignoreglobalmouse; // same for mouse
};
#if USEIMAGE
struct img {
int setlines;
int flip;
int longestedge;
int imagew;
int imageh;
int imageg;
int ow;
int oh;
};
#endif
struct tx {
char modetext[64]; // mode text
char text[BUFSIZ]; // input text
char numbers[NUMBERSBUFSIZE]; // number text
char capstext[64]; // caps lock text
};
#if USEX
struct x11 {
int numlockmask;
int useargb;
int depth;
char *embed;
int screen;
Visual *visual;
Colormap cmap;
};
#endif
static struct sp sp = {0};
static struct tx tx = {0};
#if USEIMAGE
static struct img img = {0};
#endif
#if USEX
static struct x11 x11 = {0};
#endif
static struct item *items = NULL, *backup_items, *list_items;
static struct item *matches, *matchend;
static struct item *prev, *curr, *next, *sel;
// various headers
#include "libs/draw/draw.h"
2023-06-12 03:54:40 +02:00
#include "libs/main.h"
2023-03-26 16:42:11 +02:00
#include "libs/draw.h"
#include "libs/stream.h"
#include "libs/schemes.h"
#include "libs/arg.h"
2023-05-23 22:14:24 +02:00
#include "libs/x11/inc.h" // include x11
#include "libs/wl/inc.h" // include wayland
2023-03-09 10:10:29 +01:00
#include "libs/sort.h"
2023-03-31 12:42:15 +02:00
#include "libs/history.h"
2023-02-25 17:44:52 +01:00
static Draw_t *draw;
2023-06-23 03:38:21 +02:00
// high priority
static int hplength = 0;
static char **hpitems = NULL;
static int *sel_index = NULL;
static unsigned int sel_size = 0;
static int protocol_override = 0;
2023-06-23 03:38:21 +02:00
static int itemn = 0;
static int fullscreen = 0;
#if USERTL
static int isrtl = 1;
#else
static int isrtl = 0;
#endif
2023-03-16 17:25:39 +01:00
// declare functions
static int is_selected(size_t index);
static void calcoffsets(void);
2023-03-02 11:40:52 +01:00
static void recalculatenumbers(void);
static void insert(const char *str, ssize_t n);
static void cleanup(void);
static void navigatehistfile(int dir);
static void resizeclient(void);
static void get_width(void);
2023-06-11 16:46:36 +02:00
static void get_mh(void);
static void set_mode(void);
static void handle(void);
2023-03-13 21:21:40 +01:00
static void appenditem(struct item *item, struct item **list, struct item **last);
2023-03-08 17:20:32 +01:00
static int max_textw(void);
2023-03-16 14:56:41 +01:00
static size_t nextrune(int inc);
2023-05-23 22:14:24 +02:00
static char * cistrstr(const char *s, const char *sub);
static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
static char *(*fstrstr)(const char *, const char *) = cistrstr;
2023-02-25 17:44:52 +01:00
2023-06-23 03:38:21 +02:00
#if USEX
static void pastesel(void);
static void grabfocus(void);
#endif
static char **list;
static size_t listsize;
static int listcount;
static int listchanged = 0;
2023-03-16 14:56:41 +01:00
// user configuration
#include "libs/options.h"
#include "libs/keybinds.h"
#include "libs/mouse.h"
2023-03-06 16:23:03 +01:00
static char *fonts[] = { font };
2023-05-23 22:14:24 +02:00
// color array
#include "libs/colors.h"
// config file
#if USECONFIG
#include "libs/conf/config.h"
#include "libs/conf/config.c"
#endif
2023-03-13 21:21:40 +01:00
// include functions
#include "libs/img.h"
#include "libs/img.c"
#include "libs/rtl.h"
#include "libs/rtl.c"
2023-03-09 10:10:29 +01:00
#include "libs/sort.c"
#include "libs/match.h"
#include "libs/match.c"
2023-03-02 11:40:52 +01:00
#include "libs/draw.c"
#include "libs/schemes.c"
2023-03-02 14:53:48 +01:00
#include "libs/argv.h"
#include "libs/argv.c"
2023-05-23 22:42:07 +02:00
// include x11 code
#include "libs/x11/inc.c"
#include "libs/wl/inc.c"
2023-05-23 22:42:07 +02:00
// include more functions
2023-03-31 12:42:15 +02:00
#include "libs/history.c"
2023-03-13 22:45:04 +01:00
#include "libs/arg.c"
#include "libs/stream.c"
2023-02-25 17:44:52 +01:00
int is_selected(size_t index) {
for (int i = 0; i < sel_size; i++) {
if (sel_index[i] == index) {
return 1;
}
}
return 0;
}
void appenditem(struct item *item, struct item **list, struct item **last) {
2023-05-08 23:00:45 +02:00
if (*last)
(*last)->right = item;
else
*list = item;
item->left = *last;
item->right = NULL;
*last = item;
2023-01-20 23:17:30 +01:00
}
void recalculatenumbers(void) {
unsigned int numer = 0, denom = 0, selected = 0;
2023-05-08 23:00:45 +02:00
struct item *item;
if (matchend) {
numer++;
2023-03-31 12:42:15 +02:00
// walk through items that match and add to numer
2023-05-08 23:00:45 +02:00
for (item = matchend; item && item->left; item = item->left)
numer++;
}
2023-03-31 12:42:15 +02:00
// walk through all items, matching or not and add to denom
2023-05-08 23:00:45 +02:00
for (item = items; item && item->text; item++)
denom++;
2023-03-31 12:42:15 +02:00
for (int i = 0; i < sel_size; i++) {
if (sel_index[i] == -1) {
break;
}
selected++;
}
if (selected) {
2023-06-23 03:38:21 +02:00
snprintf(tx.numbers, NUMBERSBUFSIZE, "%d/%d/%d", numer, denom, selected);
} else {
2023-06-23 03:38:21 +02:00
snprintf(tx.numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom);
}
2023-01-20 23:17:30 +01:00
}
void calcoffsets(void) {
2023-05-08 23:00:45 +02:00
int i, n;
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
if (lines > 0)
2023-06-23 03:38:21 +02:00
n = lines * columns * sp.bh;
2023-05-08 23:00:45 +02:00
else { // no lines, therefore the size of items must be decreased to fit the menu elements
int numberWidth = 0;
int modeWidth = 0;
int larrowWidth = 0;
int rarrowWidth = 0;
2023-04-21 09:49:38 +02:00
int capsWidth = 0;
2023-06-23 03:38:21 +02:00
if (!hidematchcount) numberWidth = pango_numbers ? TEXTWM(tx.numbers) : TEXTW(tx.numbers);
if (!hidemode) modeWidth = pango_mode ? TEXTWM(tx.modetext) : TEXTW(tx.modetext);
2023-03-02 18:25:28 +01:00
if (!hidelarrow) larrowWidth = pango_leftarrow ? TEXTWM(leftarrow) : TEXTW(leftarrow);
if (!hiderarrow) rarrowWidth = pango_rightarrow ? TEXTWM(rightarrow) : TEXTW(rightarrow);
2023-06-23 03:38:21 +02:00
if (!hidecaps) capsWidth = pango_caps ? TEXTWM(tx.capstext) : TEXTW(tx.capstext);
2023-06-23 03:38:21 +02:00
if (!strcmp(tx.capstext, ""))
2023-04-21 09:49:38 +02:00
capsWidth = 0;
2023-06-23 03:38:21 +02:00
n = sp.mw - (sp.promptw + sp.inputw + larrowWidth + rarrowWidth + modeWidth + numberWidth + capsWidth + menumarginh);
}
2023-05-08 23:00:45 +02:00
// calculate which items will begin the next page
for (i = 0, next = curr; next; next = next->right)
2023-06-23 03:38:21 +02:00
if ((i += (lines > 0) ? sp.bh : MIN(TEXTWM(next->text) + (powerlineitems ? !lines ? 2 * sp.plw : 0 : 0), n)) > n)
2023-05-08 23:00:45 +02:00
break;
2023-03-31 12:42:15 +02:00
2023-05-08 23:00:45 +02:00
// calculate which items will begin the previous page
for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
2023-06-23 03:38:21 +02:00
if ((i += (lines > 0) ? sp.bh : MIN(TEXTWM(prev->left->text) + (powerlineitems ? !lines ? 2 * sp.plw : 0 : 0), n)) > n)
2023-05-08 23:00:45 +02:00
break;
2023-01-20 23:17:30 +01:00
}
int max_textw(void) {
2023-05-08 23:00:45 +02:00
int len = 0;
2023-03-31 12:42:15 +02:00
2023-05-08 23:00:45 +02:00
for (struct item *item = items; item && item->text; item++)
len = MAX(TEXTW(item->text), len);
2023-03-31 12:42:15 +02:00
2023-05-08 23:00:45 +02:00
return len;
2023-01-20 23:17:30 +01:00
}
void cleanup(void) {
2023-05-08 23:00:45 +02:00
size_t i;
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
#if USEIMAGE
2023-03-31 12:42:15 +02:00
cleanupimage(); // function frees images
2023-05-08 23:00:45 +02:00
#endif
2023-03-02 11:46:44 +01:00
2023-03-31 12:42:15 +02:00
// free high priority items
2023-03-09 11:56:44 +01:00
for (i = 0; i < hplength; ++i)
2023-05-08 23:00:45 +02:00
free(hpitems[i]);
2023-03-09 11:56:44 +01:00
2023-03-31 12:42:15 +02:00
// free drawing and close the display
draw_free(draw);
#if USEX
if (!protocol) {
cleanup_x11(dpy);
}
#endif
free(sel_index);
2023-01-20 23:17:30 +01:00
}
char * cistrstr(const char *h, const char *n) {
2023-05-08 23:00:45 +02:00
size_t i;
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
if (!n[0])
return (char *)h;
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
for (; *h; ++h) {
for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
tolower((unsigned char)h[i]); ++i);
2023-03-31 12:42:15 +02:00
2023-05-08 23:00:45 +02:00
if (n[i] == '\0')
return (char *)h;
}
2023-03-31 12:42:15 +02:00
2023-05-08 23:00:45 +02:00
return NULL;
2023-01-20 23:17:30 +01:00
}
#if USEX
void grabfocus(void) {
grabfocus_x11();
2023-01-20 23:17:30 +01:00
}
#endif
2023-01-20 23:17:30 +01:00
void insert(const char *str, ssize_t n) {
2023-06-23 03:38:21 +02:00
if (strlen(tx.text) + n > sizeof tx.text - 1)
2023-05-08 23:00:45 +02:00
return;
2023-03-31 12:42:15 +02:00
static char l[BUFSIZ] = "";
2023-06-23 03:38:21 +02:00
if (requirematch) memcpy(l, tx.text, BUFSIZ);
2023-05-08 23:00:45 +02:00
// move existing text out of the way, insert new text, and update cursor
2023-06-23 03:38:21 +02:00
memmove(&tx.text[sp.cursor + n], &tx.text[sp.cursor], sizeof tx.text - sp.cursor - MAX(n, 0));
2023-03-31 12:42:15 +02:00
// update cursor
2023-05-08 23:00:45 +02:00
if (n > 0 && str && n)
2023-06-23 03:38:21 +02:00
memcpy(&tx.text[sp.cursor], str, n);
2023-03-31 12:42:15 +02:00
// add to cursor position and continue matching
2023-06-23 03:38:21 +02:00
sp.cursor += n;
2023-05-08 23:00:45 +02:00
match();
if (!matches && requirematch) {
2023-06-23 03:38:21 +02:00
memcpy(tx.text, l, BUFSIZ);
sp.cursor -= n;
match();
}
2023-01-20 23:17:30 +01:00
}
size_t nextrune(int inc) {
2023-05-08 23:00:45 +02:00
ssize_t n;
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
// return location of next utf8 rune in the given direction (+1 or -1)
2023-06-23 03:38:21 +02:00
for (n = sp.cursor + inc; n + inc >= 0 && (tx.text[n] & 0xc0) == 0x80; n += inc)
2023-03-31 12:42:15 +02:00
;
2023-05-08 23:00:45 +02:00
return n;
2023-01-20 23:17:30 +01:00
}
#if USEX
void pastesel(void) {
if (!protocol) {
pastesel_x11();
}
2023-01-20 23:17:30 +01:00
}
#endif
2023-01-20 23:17:30 +01:00
void resizeclient(void) {
#if USEWAYLAND
if (protocol) {
resizeclient_wl(&state);
} else {
#if USEX
resizeclient_x11();
#endif
}
#elif USEX
resizeclient_x11();
#endif
}
2023-01-20 23:17:30 +01:00
void get_width(void) {
2023-06-23 03:38:21 +02:00
sp.inputw = sp.mw / 3;
}
2023-01-20 23:17:30 +01:00
2023-06-11 16:46:36 +02:00
void get_mh(void) {
2023-06-23 03:38:21 +02:00
sp.mh = (lines + 1) * sp.bh;
sp.mh += 2 * menumarginv;
if ((hideprompt && hideinput && hidemode && hidematchcount && hidecaps) && lines) {
2023-06-23 03:38:21 +02:00
sp.mh -= sp.bh;
}
2023-06-11 16:46:36 +02:00
}
void set_mode(void) {
if (!type) { // no typing allowed, require normal mode
mode = 0;
}
// set default mode, must be done before the event loop or keybindings will not work
if (mode) {
2023-06-23 03:38:21 +02:00
sp.mode = 1;
sp.allowkeys = 1;
2023-06-23 03:38:21 +02:00
sp_strncpy(tx.modetext, instext, sizeof(tx.modetext));
} else {
2023-06-23 03:38:21 +02:00
sp.mode = 0;
sp.allowkeys = !sp.mode;
2023-06-23 03:38:21 +02:00
sp_strncpy(tx.modetext, normtext, sizeof(tx.modetext));
}
}
void handle(void) {
if (!protocol) {
#if USEX
handle_x11();
2023-01-20 23:17:30 +01:00
if (!draw_font_create(draw, fonts, LENGTH(fonts))) {
die("no fonts could be loaded.");
}
2023-01-20 23:17:30 +01:00
loadhistory(); // read history entries
#if USEIMAGE
store_image_vars();
#endif
2023-01-20 23:17:30 +01:00
// fast (-f) means we grab keyboard before reading standard input
if (fast && !isatty(0)) {
grabkeyboard_x11();
readstdin();
} else {
readstdin();
grabkeyboard_x11();
}
set_mode();
init_appearance(); // init colorschemes by reading arrays
setupdisplay_x11(); // set up display and create window
eventloop_x11(); // function is a loop which checks X11 events and calls other functions accordingly
#endif
#if USEWAYLAND
2023-05-08 23:00:45 +02:00
} else {
loadhistory();
#if USEIMAGE
store_image_vars();
setimageopts();
#endif
// Disable some X11 only features
menupaddingv = menupaddingh = 0;
xpos = ypos = 0;
borderwidth = 0;
managed = 0;
draw = draw_create_wl(protocol);
if (!draw_font_create(draw, fonts, LENGTH(fonts))) {
die("no fonts could be loaded.");
}
2023-05-08 23:00:45 +02:00
readstdin();
set_mode();
init_appearance();
handle_wl();
#endif
2023-05-08 23:00:45 +02:00
}
}
int main(int argc, char *argv[]) {
readargs(argc, argv); // start by reading arguments
// pledge limits what programs can do, so here we specify what spmenu should be allowed to do
#ifdef __OpenBSD__
if (pledge("stdio rpath wpath cpath", NULL) == -1)
die("pledge");
#endif
handle();
2023-01-20 23:17:30 +01:00
2023-05-08 23:00:45 +02:00
return 1; // should be unreachable
2023-01-20 23:17:30 +01:00
}