Files
amiwm-neo/module.c
Adrian Chadd 2a9bd912a1 [amiwm] Add the basic battery info printing bits
* add a batteryinfo config option to enable whether or not to display
  the current battery info string

* populate that battery info string whenever we receive a battery
  update from the module

* document it all

This is very specific to a battery thing rather than a generic menu
widget thing, but I think I'll go and twiddle with this stuff a bit
more first and then work on a more generic "menu widget" thing later.

It'd be nice to have "menu bar things" like battery and the date/time
field on a linked list of "things".

There's also some bugs - notably, rendering artifacts when the
string length shrinks, and it only updating on the 'current' menu bar
(when you have multiple desktops visible at once.)  Those also
should be fixed for completeness. :-)
2022-05-15 19:36:03 -07:00

633 lines
15 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <X11/Xlib.h>
#include "alloc.h"
#include "drawinfo.h"
#include "screen.h"
#include "prefs.h"
#include "module.h"
#include "client.h"
#include "icon.h"
#include "version.h"
extern XContext client_context, icon_context, screen_context;
extern FILE *rcfile;
extern Display *dpy;
extern void add_fd_to_set(int);
extern void remove_fd_from_set(int);
extern void screentoback();
extern void raiselowerclient(Client *, int);
extern void wberror(Scrn *, char *);
extern Icon *createappicon(struct module *, Window, char *,
Pixmap, Pixmap, Pixmap, int, int);
extern struct Item *own_items(struct module *, Scrn *,
int, int, int, struct Item *);
extern void disown_item_chain(struct module *, struct Item *);
struct module *modules = NULL;
struct mcmd_keygrab *keygrabs = NULL;
static struct mcmd_keygrab *find_keygrab(int keycode, unsigned int modifiers)
{
struct mcmd_keygrab *kg=keygrabs;
while(kg)
if(kg->keycode == keycode && kg->modifiers == modifiers)
return kg;
else kg=kg->next;
return NULL;
}
int create_keygrab(struct module *m, int keycode, unsigned int modifiers)
{
static int id=1;
struct mcmd_keygrab *kg=malloc(sizeof(struct mcmd_keygrab));
Client *c;
if(kg) {
kg->next = keygrabs;
kg->id=id++;
kg->owner=m;
kg->keycode=keycode;
kg->modifiers=modifiers;
keygrabs=kg;
for(c=clients; c; c=c->next)
if(c->parent && c->parent!=c->scr->root)
XGrabKey(dpy, keycode, modifiers, c->window, False,
GrabModeAsync, GrabModeAsync);
return kg->id;
} else return -1;
}
void delete_keygrab(struct module *m, int id)
{
struct mcmd_keygrab *kg=keygrabs, *last=NULL;
Client *c;
while(kg) {
if(kg->owner==m && (id<0 || kg->id==id)) {
if(last)
last->next=kg->next;
else
keygrabs=kg->next;
if(!find_keygrab(kg->keycode, kg->modifiers))
for(c=clients; c; c=c->next)
if(c->parent && c->parent!=c->scr->root)
XUngrabKey(dpy, kg->keycode, kg->modifiers, c->window);
} else last=kg;
kg=kg->next;
}
}
static void destroy_module(struct module *m)
{
Scrn *s = get_front_scr();
delete_keygrab(m, -1);
do {
Icon *i, *ni;
for(i=scr->icons; i; i=ni) {
ni=i->next;
if(i->module==m)
rmicon(i);
}
s=s->behind;
} while(s != get_front_scr());
disown_item_chain(m, m->menuitems);
if(m->in_fd>=0) { remove_fd_from_set(m->in_fd); close(m->in_fd); }
if(m->out_fd>=0) { close(m->out_fd); }
free(m);
}
static void sieve_modules()
{
struct module *m, **p;
for(p=&modules; (m=*p); )
if(!m->pid) {
*p=m->next;
destroy_module(m);
} else p=&(m->next);
}
void reap_children(int sig)
{
pid_t pid;
int stat;
#ifdef HAVE_WAITPID
while((pid=waitpid(-1, &stat, WNOHANG))>0)
#else
#ifdef HAVE_WAIT3
while((pid=wait3(&stat, WNOHANG, NULL))>0)
#else
if((pid=wait(&stat))>0)
#endif
#endif
#ifdef WIFSTOPPED
if(!WIFSTOPPED(stat)) {
#else
{
#endif
struct module *m;
for(m=modules; m; m=m->next)
if(m->pid==pid) {
m->pid=0;
break;
}
}
if(pid<0 && errno!=ECHILD && errno!=EINTR)
perror("wait");
if(sig>0)
signal(sig, reap_children);
}
void init_modules()
{
modules = NULL;
#ifdef SIGCHLD
signal(SIGCHLD, reap_children);
#else
#ifdef SIGCLD
signal(SIGCLD, reap_children);
#endif
#endif
}
void create_module(Scrn *screen, char *module_name, char *module_arg)
{
pid_t pid;
int fds1[2], fds2[2];
char fd1num[16], fd2num[16], scrnum[16], destpath[1024], *pathelt=NULL;
struct module *m;
char *temppath;
#ifdef HAVE_WAITPID
reap_children(0);
#else
#ifdef HAVE_WAIT3
reap_children(0);
#endif
#endif
sieve_modules();
#ifdef HAVE_ALLOCA
temppath = alloca(strlen(prefs.module_path)+2);
{
#else
if((temppath = malloc(strlen(prefs.module_path)+2))) {
#endif
strcpy(temppath, prefs.module_path);
for(pathelt=strtok(temppath, ":"); pathelt;
pathelt=strtok(NULL, ":")) {
sprintf(destpath, "%s/%s", pathelt, module_name);
if(access(destpath, X_OK)>=0)
break;
}
#ifndef HAVE_ALLOCA
free(temppath);
#endif
}
if(!pathelt) {
fprintf(stderr, "%s: no such module\n", module_name);
return;
}
if(pipe(fds1)>=0) {
if(pipe(fds2)>=0) {
if((pid=fork())) {
close(fds1[0]);
close(fds2[1]);
if(pid<0)
perror("fork");
else {
m=calloc(sizeof(struct module),1);
m->pid=pid;
m->in_fd=fds2[0];
m->out_fd=fds1[1];
m->in_buf=malloc(m->in_buf_size=64);
m->in_phase=0;
m->in_ptr=(char *)&m->mcmd;
m->in_left=sizeof(m->mcmd);
m->next=modules;
modules=m;
add_fd_to_set(m->in_fd);
}
} else {
if(rcfile)
close(fileno(rcfile));
for(m=modules; m; m=m->next) {
if(m->in_fd>=0) close(m->in_fd);
if(m->out_fd>=0) close(m->out_fd);
}
close(fds1[1]);
close(fds2[0]);
sprintf(fd1num, "%d", fds1[0]);
sprintf(fd2num, "%d", fds2[1]);
sprintf(scrnum, "0x%08lx", (screen? (screen->back):None));
execl(destpath, module_name, fd1num, fd2num, scrnum, module_arg, NULL);
perror(destpath);
_exit(1);
}
} else {
perror("pipe");
close(fds1[0]);
close(fds1[1]);
}
} else perror("pipe");
}
static int m_write(int fd, char *ptr, int len)
{
char *p=ptr;
int r, tot=0;
while(len>0) {
if((r=write(fd, p, len))<0) {
if(errno==EINTR) {
continue;
} else {
return r;
}
}
if(!r)
return tot;
tot+=r;
p+=r;
len-=r;
}
return tot;
}
static void reply_module(struct module *m, char *data, int len)
{
m_write(m->out_fd, (char *)&len, sizeof(len));
if(len<0) len=~len;
if(len>0)
m_write(m->out_fd, data, len);
}
int dispatch_event_to_broker(XEvent *e, unsigned long mask, struct module *m)
{
Client *c;
Icon *i;
/* XXX TODO: overloading display here seems .. dangerous? */
e->xany.display=(Display *)(uintptr_t) 0;
if(!XFindContext(dpy, e->xany.window, client_context, (XPointer *)&c))
if(e->xany.window==c->window)
e->xany.display=(Display *)(uintptr_t) 1;
else
e->xany.display=(Display *)(uintptr_t) 2;
else if(!XFindContext(dpy, e->xany.window, icon_context, (XPointer *)&i))
if(e->xany.window==i->window)
e->xany.display=(Display *)(uintptr_t) 3;
while(m) {
if(m->out_fd>=0 && ((m->broker.mask & mask)||(m->broker.exists && !mask))) {
struct mcmd_event me;
me.mask=mask;
me.event=*e;
reply_module(m, (char *)&me, ~((int) sizeof(me)));
return 1;
}
m=m->next;
}
return 0;
}
static void incoming_event(struct module *m, struct mcmd_event *me)
{
extern void internal_broker(XEvent *);
if(!dispatch_event_to_broker(&me->event, me->mask, m->next))
internal_broker(&me->event);
}
void mod_menuselect(struct module *m, int menu, int item, int subitem)
{
fprintf(stderr, "No value assigned for menu item %d:%d:%d.\n"
"Read manual for module %d\n?",
menu, item, subitem, (int)m->pid);
}
extern void lowertopmostclient(Scrn *scr);
extern void raisebottommostclient(Scrn *scr);
static void handle_module_cmd(struct module *m, char *data, int data_len)
{
extern Scrn *getscreen(Window);
extern int iconcolormask;
XID id=m->mcmd.id;
Client *c;
switch(m->mcmd.cmd) {
case MCMD_NOP:
reply_module(m, NULL, 0);
break;
case MCMD_GET_VERSION:
reply_module(m, "amiwm "VERSION, sizeof("amiwm "VERSION));
break;
case MCMD_SEND_EVENT:
if(data_len>=sizeof(struct mcmd_event)) {
incoming_event(m, (struct mcmd_event *)data);
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_SET_BROKER:
if(data_len>=sizeof(m->broker.mask)) {
m->broker.mask=*(unsigned long *)data;
m->broker.exists=1;
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_ROTATE_SCREEN:
scr=getscreen(id);
screentoback();
reply_module(m, NULL, 0);
break;
case MCMD_ROTATE_WINDOW_RAISE:
/* Get the current screen */
scr = getscreen(id);
/* raise away! */
raisebottommostclient(scr);
reply_module(m, NULL, 0);
break;
case MCMD_ROTATE_WINDOW_LOWER:
/* Get the current screen */
scr = getscreen(id);
/* lower away! */
lowertopmostclient(scr);
reply_module(m, NULL, 0);
break;
case MCMD_ADD_KEYGRAB:
if(data_len>=sizeof(int[2])) {
int res=create_keygrab(m, ((int*)data)[0], ((int*)data)[1]);
reply_module(m, (char *)&res, sizeof(res));
} else reply_module(m, NULL, -1);
break;
case MCMD_DEL_KEYGRAB:
if(data_len>=sizeof(int)) {
delete_keygrab(m, ((int*)data)[0]);
reply_module(m, NULL, 0);
} else reply_module(m, NULL, -1);
break;
case MCMD_FRONT:
if(!XFindContext(dpy, id, client_context, (XPointer*)&c)) {
raiselowerclient(c, PlaceOnTop);
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_BACK:
if(!XFindContext(dpy, id, client_context, (XPointer*)&c)) {
raiselowerclient(c, PlaceOnBottom);
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_ICONIFY:
if(!XFindContext(dpy, id, client_context, (XPointer*)&c)) {
if(!(c->icon))
createicon(c);
XUnmapWindow(dpy, c->parent);
/* XUnmapWindow(dpy, c->window); */
adjusticon(c->icon);
XMapWindow(dpy, c->icon->window);
if(c->icon->labelwidth)
XMapWindow(dpy, c->icon->labelwin);
c->icon->mapped=1;
setclientstate(c, IconicState);
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_CREATEAPPICON:
if(data_len>=sizeof(struct NewAppIcon)) {
struct NewAppIcon *nai=(struct NewAppIcon *)data;
Window w=None;
Icon *i=createappicon(m, id, nai->name,
nai->pm1, nai->pm2, nai->pmm, nai->x, nai->y);
if(i!=NULL) w=i->window;
reply_module(m, (char *)&w, sizeof(w));
} else
reply_module(m, NULL, -1);
break;
case MCMD_ERRORMSG:
if(data_len>0) {
extern char *free_screentitle;
if(free_screentitle) free(free_screentitle);
free_screentitle=malloc(data_len+1);
if(free_screentitle==NULL)
reply_module(m, NULL, -1);
else {
scr=getscreen(id);
memcpy(free_screentitle, data, data_len);
free_screentitle[data_len]='\0';
wberror(scr, free_screentitle);
reply_module(m, NULL, 0);
}
} else
reply_module(m, NULL, -1);
break;
case MCMD_SETAPPWINDOW:
if(!XFindContext(dpy, id, client_context, (XPointer*)&c)) {
c->module=m;
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
break;
case MCMD_GETICONDIR:
reply_module(m, prefs.icondir, strlen(prefs.icondir)+1);
break;
case MCMD_GETICONPALETTE:
if(!XFindContext(dpy, id, client_context, (XPointer*)&c)) {
scr=c->scr;
} else
if(XFindContext(dpy, id, screen_context, (XPointer*)&scr)) {
reply_module(m, NULL, -1);
break;
}
reply_module(m, (void *)scr->iconcolor,
(iconcolormask+1)*sizeof(unsigned long));
break;
case MCMD_MANAGEMENU:
if(data_len>=sizeof(int[3])) {
struct Item *i;
scr=getscreen(id);
if((i=own_items(m, scr, ((int*)data)[0], ((int*)data)[1],
((int*)data)[3], m->menuitems))) {
m->menuitems = i;
reply_module(m, NULL, 0);
} else
reply_module(m, NULL, -1);
} else
reply_module(m, NULL, -1);
break;
case MCMD_UPDATE_BATTERY:
{
struct mcmd_update_battery *batt;
extern char battery_status[];
if (data == NULL) {
reply_module(m, NULL, -1);
break;
}
if (data_len != sizeof(struct mcmd_update_battery)) {
reply_module(m, NULL, -1);
break;
}
batt = (void *) data;
#if 0
fprintf(stderr, "%s: called, BATTERY, pct=%d, time=%d, ac=%d\n",
__func__,
batt->battery_pct,
batt->battery_time,
batt->battery_ac);
#endif
/*
* XXX TODO: for now we're just populating a string here.
* Later on we should just store the current battery state
* somewhere (a key/value table would be nice!) and then
* the widget code can pull out its needed state to render.
*/
snprintf(battery_status, 128, "%d pct%s", batt->battery_pct,
batt->battery_ac == 1 ? " A" : " -");
reply_module(m, NULL, 0);
break;
}
break;
default:
reply_module(m, NULL, -1);
}
}
static void module_input_callback(struct module *m)
{
unsigned char buffer[8192], *p=buffer;
int r;
if(!(m->pid)) {
sieve_modules();
return;
}
r=read(m->in_fd, buffer, sizeof(buffer));
if(r==0 || (r<0 && errno!=EINTR)) {
if(r<0)
perror("module");
else
fprintf(stderr, "module %d exited!?\n", (int)m->pid);
remove_fd_from_set(m->in_fd);
close(m->in_fd);
close(m->out_fd);
m->in_fd=m->out_fd=-1;
#ifdef HAVE_WAITPID
reap_children(0);
#else
#ifdef HAVE_WAIT3
reap_children(0);
#endif
#endif
sieve_modules();
return;
}
while(r>0) {
int t=(r>m->in_left? m->in_left:r);
memcpy(m->in_ptr, p, t);
m->in_ptr+=t;
if(!(m->in_left-=t)) {
if(m->in_phase || ((!m->mcmd.len)&&(m->in_ptr=m->in_buf))) {
*m->in_ptr=0;
handle_module_cmd(m, m->in_buf, m->mcmd.len);
m->in_ptr=(char *)&m->mcmd;
m->in_left=sizeof(m->mcmd);
m->in_phase=0;
} else {
if(m->mcmd.len>=m->in_buf_size) {
/* Resize buffer if needed */
if((m->in_buf_size<<=1)<=m->mcmd.len) {
m->in_buf_size=m->mcmd.len+1;
m->in_buf=realloc(m->in_buf, m->in_buf_size);
}
}
m->in_ptr=m->in_buf;
m->in_left=m->mcmd.len;
m->in_phase++;
}
}
p+=t;
r-=t;
}
}
void handle_module_input(fd_set *r)
{
struct module *m;
for(m=modules; m; m=m->next)
if(m->in_fd>=0 && FD_ISSET(m->in_fd, r)) {
module_input_callback(m);
break;
}
}
void flushmodules()
{
struct module *n, *m=modules;
int i;
extern char *progname;
while(m) {
n=m->next;
if(m->in_fd>=0) {
close(m->in_fd);
remove_fd_from_set(m->in_fd);
m->in_fd=-1;
}
if(m->out_fd>=0) {
close(m->out_fd);
m->out_fd=-1;
}
if(m->pid>0)
kill(m->pid, SIGHUP);
m=n;
}
for(i=5; i>0; --i) {
sieve_modules();
if(!modules)
break;
sleep(1);
}
sieve_modules();
if(modules)
fprintf(stderr, "%s: giving up waiting for modules to die!\n", progname);
}