/* * This file is part of firk's window manager library (libfwm) * Copyright (C) 2023 firk * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #define LIBFWM_INTERNAL #define LIBFWM_INTERNAL_2 #include "libfwm.h" Display *display; Screen *screen; Window rootwin; Atom atoms[atoms_length]; char current_theme[FWM_THEMESET_SIZE]; unsigned long colors[16]; static char logprefix[32]; extern void logprint(char const * fmt, ...) { char buf[110]; va_list arg; va_start(arg, fmt); vsnprintf(buf, sizeof(buf), fmt, arg); va_end(arg); fprintf(stderr, "%s%s\n", logprefix, buf); } extern void failprint(char const * fmt, ...) { char buf[110]; va_list arg; va_start(arg, fmt); vsnprintf(buf, sizeof(buf), fmt, arg); va_end(arg); fprintf(stderr, "%s(fatal) %s\n", logprefix, buf); exit(-1); } static void init_colors(void) { XColor c; unsigned int n; for(n=0;n<16;n++) { c.flags = DoRed|DoGreen|DoBlue; c.red = ((n&8)?0x5500:0) + ((n&4)?0xAA00:0); c.green = ((n&8)?0x5500:0) + ((n&2)?0xAA00:0); c.blue = ((n&8)?0x5500:0) + ((n&1)?0xAA00:0); if(n==6) c.green=0x5500; if(!XAllocColor(display, DefaultColormapOfScreen(screen), &c)) logprint("color[%d] alloc failed", n); colors[n] = c.pixel; } } static void free_colors(void) { XFreeColors(display, DefaultColormapOfScreen(screen), colors, 16, 0); } extern void LIBFWM_Init(char const *program_name, char const *display_name) { size_t n; if(program_name && (n=strlen(program_name))) { if(n>sizeof(logprefix)-3) n = sizeof(logprefix)-3; bcopy(program_name, logprefix, n); logprefix[n++] = ':'; logprefix[n++] = ' '; logprefix[n++] = 0; } else logprefix[0] = 0; if(!display && !(display = XOpenDisplay(display_name))) failprint("XOpenDisplay(%s) failed", display_name); if(!(screen = DefaultScreenOfDisplay(display))) failprint("DefaultScreenOfDisplay() failed"); rootwin = RootWindowOfScreen(screen); init_colors(); if(!XInternAtoms(display, (char**)atom_names, atoms_length, False, atoms)) failprint("XInternAtoms() failed"); LIBFWM_GetGlobalTheme(NULL, NULL, 0, 1); } extern unsigned long base_color(unsigned char c) { #ifdef NO_COLOR_THEMES return colors[c&15]; #else return colors[(c^((unsigned char)(current_theme[0])))&15]; #endif } static int set_atom_string(Window w, Atom atom, char const *st, int len) { return XChangeProperty(display, w, atom, atom, 8, PropModeReplace, (unsigned char const*)st, len); } /* NOTE: XGetWindowProperty() always allocates one extra zero byte after data get_atom_string() considers zero n_items as a missing prop so, success means at least two bytes available in (*dst)[] */ static int get_atom_string(Window window, Atom atom, char **dst, long long_length, size_t *r_len) { Atom type; int fmt; unsigned long nitems, rembytes; unsigned char *prop; prop = NULL; if(XGetWindowProperty(display, window, atom, 0, long_length, False, atom, &type, &fmt, &nitems, &rembytes, &prop)!=Success) { /* XFree(prop)? */ *dst = NULL; return -1; } if(type!=atom || fmt!=8 || !nitems || rembytes || !prop) { /* application didn't set this property or set it wrongly for some reason - ignore */ if(prop) XFree(prop); *dst = NULL; return 0; } *dst = (char*)prop; if(r_len) *r_len = nitems; return 1; } extern int LIBFWM_SetWClass(Window w, unsigned char cat, char const *wcl) { char tmp[32]; size_t len; tmp[0] = cat; if(!wcl) len = 0; else { len = strnlen(wcl, 31); bcopy(wcl, tmp+1, len); } return set_atom_string(w, atom_FWM_WCLASS, tmp, len+1); } extern int LIBFWM_SetGlobalTheme(unsigned char cat, char const *theme) { size_t j; char c; char tmp[FWM_THEMESET_SIZE]; int changed; bzero(tmp, sizeof(tmp)); tmp[0] = cat; if(theme) { for(j=1; (c=theme[j-1]) && j=0 && c<=31 || c==127) break; tmp[j] = c; } /* if(c) return -1; */ } XChangeProperty(display, rootwin, atom_FWM_THEMESET, atom_FWM_THEMESET, 8, PropModeReplace, (unsigned char*)tmp, strlen(tmp+1)+2); return 0; } /* return 1+ if retrieved theme differs from previously saved theme; return 2+ if category differs */ extern int LIBFWM_GetGlobalTheme(unsigned char *cat, char *dst, size_t dstsz, int use) { char *tmp; size_t len; int r; char tt[FWM_THEMESET_SIZE]; bzero(tt, sizeof(tt)); if(get_atom_string(rootwin, atom_FWM_THEMESET, &tmp, FWM_THEMESET_SIZE/4, &len)>0) { /* get_atom_string never returns zero 'len' on success */ tt[0] = tmp[0]; strncpy(tt+1, tmp+1, FWM_THEMESET_SIZE-2); XFree(tmp); } if(tt[0]!=current_theme[0]) r = 2; else if(strcmp(tt+1,current_theme+1)) r = 1; else r = 0; if(use) bcopy(tt, current_theme, FWM_THEMESET_SIZE); if(cat) *cat = tt[0]; if(dst && dstsz) { strncpy(dst, tt+1, dstsz-1); dst[dstsz-1] = 0; } return r; } extern void LIBFWM_SetCurrentTheme(unsigned char cat, char const *theme) { size_t j; char c; char tmp[FWM_THEMESET_SIZE]; int changed; bzero(tmp, sizeof(tmp)); tmp[0] = cat; if(theme) { for(j=1; (c=theme[j-1]) && j=0 && c<=31 || c==127) break; tmp[j] = c; } /* if(c) return -1; */ } bcopy(tmp, current_theme, FWM_THEMESET_SIZE); } extern void LIBFWM_GetCurrentTheme(unsigned char *cat, char *dst, size_t dstsz) { if(cat) *cat = current_theme[0]; if(dst && dstsz) { strncpy(dst, current_theme+1, dstsz-1); dst[dstsz-1] = 0; } } extern Window LIBFWM_CreateTrayIcon(int is_wide, char const *class, unsigned long background_pixel, long event_mask) { XSetWindowAttributes wa; Window win; if(!background_pixel) background_pixel = base_color(BLACK); if(event_mask==-1) event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|LeaveWindowMask; bzero(&wa, sizeof(wa)); wa.background_pixel = background_pixel; wa.override_redirect = False; /* may set to True when FWM missing, but need to manage x,y manually */ wa.event_mask = event_mask; win = XCreateWindow(display, rootwin, 0, 0, is_wide?42:16, 16, 0, /* dummy x0,y0,w,h,bwidth */ CopyFromParent, InputOutput, CopyFromParent, CWBackPixel|CWOverrideRedirect|CWEventMask, &wa); if(!LIBFWM_SetWClass(win, is_wide?WCLASS_TRAYWIDE:WCLASS_TRAYICON, class)) logprint("CreateTrayIcon():SetWClass() failed"); XSetStandardProperties(display, win, class, class, None, NULL, 0, NULL); return win; } extern Window LIBFWM_CreateWidgetWindow(unsigned char type, char const *class, char const *title, int x0, int y0, unsigned int w, unsigned int h, unsigned int bw, unsigned long background_pixel, unsigned long border_pixel, long event_mask, Bool override_redirect) { XSetWindowAttributes wa; Window win; if(!background_pixel) background_pixel = base_color(BLACK); if(event_mask==-1) event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|LeaveWindowMask|StructureNotifyMask; bzero(&wa, sizeof(wa)); wa.background_pixel = background_pixel; wa.border_pixel = border_pixel; wa.override_redirect = override_redirect; wa.event_mask = event_mask; win = XCreateWindow(display, rootwin, x0, y0, w, h, bw, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel|CWBorderPixel|CWOverrideRedirect|CWEventMask, &wa); if(!LIBFWM_SetWClass(win, type, class)) logprint("CreateWidgetWindow():SetWClass() failed"); XSetStandardProperties(display, win, title, title, None, NULL, 0, NULL); return win; } extern unsigned long get_ms(void) { static int mode; struct timeval tv; struct timespec ts; switch(mode) { case 0: if(clock_gettime(CLOCK_MONOTONIC, &ts)>=0) { mode = 1; return ((unsigned long)ts.tv_sec)*1000+((unsigned long)ts.tv_nsec)/1000000; } if(gettimeofday(&tv, NULL)>=0) { mode = 2; return ((unsigned long)tv.tv_sec)*1000+((unsigned long)tv.tv_usec)/1000; } mode = 3; break; case 1: clock_gettime(CLOCK_MONOTONIC, &ts); return ((unsigned long)ts.tv_sec)*1000+((unsigned long)ts.tv_nsec)/1000000; case 2: gettimeofday(&tv, NULL); return ((unsigned long)tv.tv_sec)*1000+((unsigned long)tv.tv_usec)/1000; } return ((unsigned long)time(NULL))*1000; } static int poll_in(unsigned long timeout) { struct pollfd pfd; int r, to, er; er = 0; to = timeout; while(to<0 || timeout!=(unsigned)to) { er=1; timeout/=2; to=timeout; } pfd.fd = ConnectionNumber(display); pfd.events = POLLIN; pfd.revents = 0; r = poll(&pfd, 1, timeout); if(!r && er) { errno=EINTR; return -1; } if(r<=0) return r; if(pfd.revents & (POLLIN|POLLHUP)) return 2; return 1; /* <- most likely this shouldn't happen */ } /* this was just socket waiting code, but it is not what we need static int poll_nointr(unsigned long until) { unsigned long diff; int r; while(1) { diff = until - get_ms(); if(((long)diff)<0) diff = 0; r = poll_in(diff); if(r==2) return 2; if(!diff) return 0; } } extern int LIBFWM_WaitEventTimeout(unsigned int timeout, int nointr) { if(!nointr) return poll_in(timeout); return poll_nointr(get_ms()+timeout); } extern int LIBFWM_WaitEventUntil(unsigned long until, int nointr) { unsigned long diff; if(!nointr) { diff = until - get_ms(); if(((long)diff)<0) diff = 0; return poll_in(diff); } return poll_nointr(until); } */ static int poll_nointr(unsigned long now, unsigned long until) { unsigned long diff; int r; while(1) { diff = until - now; if(((long)diff)<=0) break; r = poll_in(diff); if(r<0 && errno!=EINTR) usleep(10000); if(XPending(display)) return 1; now = get_ms(); } if(XPending(display)) return 1; return 0; } extern int LIBFWM_WaitEventTimeout(unsigned int timeout, int nointr) { unsigned long now, until, diff; now = get_ms(); until = now + timeout; if(XPending(display)) return 1; if(!timeout) return 0; if(!nointr) { diff = until - get_ms(); if(((long)diff)>0) poll_in(diff); if(XPending(display)) return 1; return 0; } now = get_ms(); return poll_nointr(now, now+timeout); } extern int LIBFWM_WaitEventUntil(unsigned long until, int nointr) { unsigned long now, diff; diff = until - get_ms(); if(XPending(display)) return 1; if(((long)diff)<=0) return 0; if(!nointr) { diff = until - get_ms(); if(((long)diff)>0) poll_in(diff); if(XPending(display)) return 1; return 0; } now = get_ms(); return poll_nointr(now, until); }