/* * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "sdorfehs.h" #include #include #include #include #include #include #include #include #include /* * Several systems seem not to have WAIT_ANY defined, so define it if it isn't. */ #ifndef WAIT_ANY #define WAIT_ANY -1 #endif /* Some systems don't define the close-on-exec flag in fcntl.h */ #ifndef FD_CLOEXEC #define FD_CLOEXEC 1 #endif int alarm_signalled = 0; int kill_signalled = 0; int hup_signalled = 0; int chld_signalled = 0; int rp_font_ascent, rp_font_descent, rp_font_width; Atom wm_name; Atom wm_state; Atom wm_change_state; Atom wm_protocols; Atom wm_delete; Atom wm_take_focus; Atom wm_colormaps; Atom rp_selection; /* TEXT atoms */ Atom xa_string; Atom xa_compound_text; Atom xa_utf8_string; /* netwm atoms */ Atom _net_active_window; Atom _net_client_list; Atom _net_client_list_stacking; Atom _net_current_desktop; Atom _net_number_of_desktops; Atom _net_supported; Atom _net_workarea; Atom _net_wm_name; Atom _net_wm_pid; Atom _net_wm_state; Atom _net_wm_state_fullscreen; Atom _net_wm_window_type; Atom _net_wm_window_type_dialog; Atom _net_wm_window_type_dock; Atom _net_wm_window_type_splash; Atom _net_wm_window_type_tooltip; Atom _net_wm_window_type_utility; Atom _net_supporting_wm_check; LIST_HEAD(rp_screens); rp_screen *rp_current_screen; rp_global_screen rp_glob_screen; Display *dpy; int rp_have_xrandr; LIST_HEAD(rp_children); struct rp_defaults defaults; int ignore_badwindow = 0; char **myargv; struct rp_key prefix_key; struct modifier_info rp_modifier_info; /* rudeness levels */ int rp_honour_transient_raise = 1; int rp_honour_normal_raise = 1; int rp_honour_transient_map = 1; int rp_honour_normal_map = 1; int rp_honour_vscreen_switch = 0; char *rp_error_msg = NULL; /* Global frame numset */ struct numset *rp_frame_numset; /* The X11 selection globals */ rp_xselection selection; static void x_export_selection(void) { rp_screen *screen; list_first(screen, &rp_screens, node); if (!screen) return; /* Hang the selections off screen 0's key window. */ XSetSelectionOwner(dpy, XA_PRIMARY, screen->key_window, CurrentTime); if (XGetSelectionOwner(dpy, XA_PRIMARY) != screen->key_window) warnx("can't get primary selection"); XChangeProperty(dpy, screen->root, XA_CUT_BUFFER0, xa_string, 8, PropModeReplace, (unsigned char *) selection.text, selection.len); } void set_nselection(char *txt, int len) { int i; /* Update the selection structure */ free(selection.text); /* Copy the string by hand. */ selection.text = xmalloc(len + 1); selection.len = len + 1; for (i = 0; i < len; i++) selection.text[i] = txt[i]; selection.text[len] = 0; x_export_selection(); } void set_selection(char *txt) { /* Update the selection structure */ free(selection.text); selection.text = xstrdup(txt); selection.len = strlen(txt); x_export_selection(); } static char * get_cut_buffer(void) { int nbytes; char *data; PRINT_DEBUG(("trying the cut buffer\n")); data = XFetchBytes(dpy, &nbytes); if (data) { struct sbuf *s = sbuf_new(0); sbuf_nconcat(s, data, nbytes); XFree(data); return sbuf_free_struct(s); } else return NULL; } /* Lifted the code from rxvt. */ static char * get_primary_selection(void) { long nread; unsigned long bytes_after; XTextProperty ct; struct sbuf *s = sbuf_new(0); for (nread = 0, bytes_after = 1; bytes_after > 0; nread += ct.nitems) { if ((XGetWindowProperty(dpy, rp_current_screen->input_window, rp_selection, (nread / 4), 4096, True, AnyPropertyType, &ct.encoding, &ct.format, &ct.nitems, &bytes_after, &ct.value) != Success)) { XFree(ct.value); sbuf_free(s); return NULL; } if (ct.value == NULL) continue; /* * Accumulate the data. FIXME: ct.value may not be NULL * terminated. */ sbuf_nconcat(s, (const char *) ct.value, ct.nitems); XFree(ct.value); } return sbuf_free_struct(s); } char * get_selection(void) { Atom property; XEvent ev; rp_screen *s = rp_current_screen; int loops = 1000; /* Just insert our text, if we own the selection. */ if (selection.text) { return xstrdup(selection.text); } else { /* be a good icccm citizen */ XDeleteProperty(dpy, s->input_window, rp_selection); /* * TODO: we shouldn't use CurrentTime here, use the time of the * XKeyEvent, should we fake it? */ XConvertSelection(dpy, XA_PRIMARY, xa_string, rp_selection, s->input_window, CurrentTime); /* This seems like a hack. */ while (!XCheckTypedWindowEvent(dpy, s->input_window, SelectionNotify, &ev)) { if (loops == 0) { warnx("selection request timed out"); return NULL; } usleep(10000); loops--; } PRINT_DEBUG(("SelectionNotify event\n")); property = ev.xselection.property; if (property != None) return get_primary_selection(); else return get_cut_buffer(); } } /* The hook dictionary globals. */ LIST_HEAD(rp_key_hook); LIST_HEAD(rp_switch_win_hook); LIST_HEAD(rp_switch_frame_hook); LIST_HEAD(rp_switch_screen_hook); LIST_HEAD(rp_switch_vscreen_hook); LIST_HEAD(rp_quit_hook); LIST_HEAD(rp_restart_hook); LIST_HEAD(rp_delete_window_hook); LIST_HEAD(rp_new_window_hook); LIST_HEAD(rp_title_changed_hook); struct rp_hook_db_entry rp_hook_db[] = {{"key", &rp_key_hook}, {"switchwin", &rp_switch_win_hook}, {"switchframe", &rp_switch_frame_hook}, {"switchscreen", &rp_switch_screen_hook}, {"switchvscreen", &rp_switch_vscreen_hook}, {"deletewindow", &rp_delete_window_hook}, {"quit", &rp_quit_hook}, {"restart", &rp_restart_hook}, {"newwindow", &rp_new_window_hook}, {"titlechanged", &rp_title_changed_hook}, {NULL, NULL} }; void set_rp_window_focus(rp_window *win) { PRINT_DEBUG(("Giving focus to '%s'\n", window_name(win))); XSetInputFocus(dpy, win->w, RevertToPointerRoot, CurrentTime); set_atom(win->vscreen->screen->root, _net_active_window, XA_WINDOW, &win->w, 1); } void set_window_focus(Window window) { PRINT_DEBUG(("Giving focus to %ld\n", window)); XSetInputFocus(dpy, window, RevertToPointerRoot, CurrentTime); } XftFont * rp_get_font(rp_screen *s, char *font) { XftFont *f; int fslots = sizeof(s->xft_font_cache) / sizeof(struct rp_font); int x; if (!font || font[0] == '\0') return s->xft_font; for (x = 0; x < fslots; x++) { if (!s->xft_font_cache[x].name) break; if (strcmp(s->xft_font_cache[x].name, font) == 0) return s->xft_font_cache[x].font; } /* not in the cache, make sure we can open it first */ f = XftFontOpenName(dpy, DefaultScreen(dpy), font); if (!f) { warnx("failed opening xft font \"%s\"", font); return s->xft_font; } PRINT_DEBUG(("font \"%s\" not in font cache\n", font)); /* free up the last slot if needed */ if (x == fslots) { free(s->xft_font_cache[x - 1].name); XftFontClose(dpy, s->xft_font_cache[x - 1].font); } /* shift all the cache entries to free up the first slot */ for (x = fslots - 1; x >= 1; x--) memcpy(&s->xft_font_cache[x], &s->xft_font_cache[x - 1], sizeof(struct rp_font)); s->xft_font_cache[0].name = xstrdup(font); s->xft_font_cache[0].font = f; return f; } void rp_clear_cached_fonts(rp_screen *s) { int x; for (x = 0; x < (sizeof(s->xft_font_cache) / sizeof(struct rp_font)); x++) { if (s->xft_font_cache[x].name) { free(s->xft_font_cache[x - 1].name); XftFontClose(dpy, s->xft_font_cache[x].font); } } } void rp_draw_string(rp_screen *s, Drawable d, int style, int x, int y, char *string, int length, char *font, char *color) { XftDraw *draw; XftColor xftcolor; XftFont *f = rp_get_font(s, font); if (length < 0) length = strlen(string); draw = XftDrawCreate(dpy, d, DefaultVisual(dpy, s->screen_num), DefaultColormap(dpy, s->screen_num)); if (!draw) { warnx("no Xft font available"); return; } if (color == NULL) { if (style == STYLE_NORMAL) memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor)); else memcpy(&xftcolor, &s->xft_bgcolor, sizeof(XftColor)); } else { /* * This won't actually allocate anything if the color is * already allocated. */ if (!XftColorAllocName(dpy, DefaultVisual(dpy, s->screen_num), DefaultColormap(dpy, s->screen_num), color, &xftcolor)) { warnx("couldn't XftColorAllocName \"%s\"", color); memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor)); } } XftDrawStringUtf8(draw, &xftcolor, f, x, y, (FcChar8 *)string, length); XftDrawDestroy(draw); } int rp_text_width(rp_screen *s, char *string, int count, char *font) { XGlyphInfo extents; XftFont *f = rp_get_font(s, font); if (count < 0) count = strlen(string); XftTextExtentsUtf8(dpy, f, (FcChar8 *)string, count, &extents); return extents.xOff; } /* A case insensitive strncmp. */ int str_comp(char *s1, char *s2, size_t len) { size_t i; for (i = 0; i < len; i++) if (toupper((unsigned char)s1[i]) != toupper((unsigned char)s2[i])) return 0; return 1; } /* * Check for child processes that have quit but haven't been acknowledged yet. * Update their structure. */ void check_child_procs(void) { rp_child_info *cur; int pid, status; while (1) { pid = waitpid(WAIT_ANY, &status, WNOHANG); if (pid <= 0) break; PRINT_DEBUG(("Child status: %d\n", WEXITSTATUS(status))); /* Find the child and update its structure. */ list_for_each_entry(cur, &rp_children, node) { if (cur->pid == pid) { cur->terminated = 1; cur->status = WEXITSTATUS(status); break; } } chld_signalled = 1; } } void chld_handler(int signum) { int serrno; serrno = errno; check_child_procs(); errno = serrno; } void set_sig_handler(int sig, void (*action)(int)) { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = action; sigemptyset(&act.sa_mask); if (sigaction(sig, &act, NULL)) warnx("error setting signal handler"); } void set_close_on_exec(int fd) { int flags = fcntl(fd, F_GETFD); if (flags >= 0) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } void read_rc_file(FILE *file) { char *line; size_t linesize = 256; line = xmalloc(linesize); while (getline(&line, &linesize, file) != -1) { line[strcspn(line, "\n")] = '\0'; PRINT_DEBUG(("rcfile line: %s\n", line)); if (*line != '\0' && *line != '#') { cmdret *result; result = command(0, line); /* Gobble the result. */ if (result) cmdret_free(result); } } free(line); } const char * get_homedir(void) { char *homedir; homedir = getenv("HOME"); if (homedir != NULL && homedir[0] == '\0') homedir = NULL; if (homedir == NULL) { struct passwd *pw; pw = getpwuid(getuid()); if (pw != NULL) homedir = pw->pw_dir; if (homedir != NULL && homedir[0] == '\0') homedir = NULL; } return homedir; } char * get_config_dir(void) { DIR *d; const char *homedir; char *xdg_config, *home_config; int xdg_alloc = 0; homedir = get_homedir(); if (!homedir) errx(1, "no home directory"); xdg_config = getenv("XDG_CONFIG_HOME"); if (xdg_config == NULL || !strlen(xdg_config)) { xdg_config = xsprintf("%s/.config", homedir); xdg_alloc = 1; } if (!(d = opendir(xdg_config))) { if (mkdir(xdg_config, 0755) == -1) err(1, "failed creating %s", xdg_config); if (!(d = opendir(xdg_config))) err(1, "failed opening %s", xdg_config); } closedir(d); home_config = xsprintf("%s/sdorfehs", xdg_config); if (!(d = opendir(home_config))) { if (mkdir(home_config, 0755) == -1) err(1, "failed creating %s", home_config); if (!(d = opendir(home_config))) err(1, "failed opening %s", home_config); } closedir(d); if (xdg_alloc) free(xdg_config); return home_config; } void clean_up(void) { rp_screen *cur; rp_vscreen *vcur; struct list_head *iter, *tmp, *iter2, *tmp2; history_save(); free_keymaps(); free_aliases(); free_user_commands(); free_bar(); free_window_stuff(); list_for_each_safe_entry(cur, iter, tmp, &rp_screens, node) { list_for_each_safe_entry(vcur, iter2, tmp2, &cur->vscreens, node) vscreen_del(vcur); list_del(&cur->node); screen_free(cur); free(cur); } screen_free_final(); /* Delete the undo histories */ clear_frame_undos(); /* Free the global frame numset shared by all screens. */ numset_free(rp_frame_numset); free(defaults.window_fmt); XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); XCloseDisplay(dpy); } void register_atom(Atom *a, char *name) { *a = XInternAtom(dpy, name, False); PRINT_DEBUG(("Registered Atom %ld = %s\n", (unsigned long)*a, name)); append_atom(DefaultRootWindow(dpy), _net_supported, XA_ATOM, a, 1); } int set_atom(Window w, Atom a, Atom type, unsigned long *val, unsigned long nitems) { return (XChangeProperty(dpy, w, a, type, 32, PropModeReplace, (unsigned char *)val, nitems) == Success); } int append_atom(Window w, Atom a, Atom type, unsigned long *val, unsigned long nitems) { return (XChangeProperty(dpy, w, a, type, 32, PropModeAppend, (unsigned char *)val, nitems) == Success); } unsigned long get_atom(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret, unsigned long nitems, unsigned long *left) { Atom real_type; int i, real_format = 0; unsigned long items_read = 0; unsigned long bytes_left = 0; unsigned long *p; unsigned char *data; XGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type, &real_format, &items_read, &bytes_left, &data); if (real_format == 32 && items_read) { p = (unsigned long *)data; for (i = 0; i < items_read; i++) *ret++ = *p++; XFree(data); if (left) *left = bytes_left; return items_read; } return 0; } void remove_atom(Window w, Atom a, Atom type, unsigned long remove) { unsigned long tmp, read, left, *new; int i, j = 0; read = get_atom(w, a, type, 0, &tmp, 1, &left); if (!read) return; new = malloc((read + left) * sizeof(*new)); if (read && tmp != remove) new[j++] = tmp; for (i = 1, read = left = 1; read && left; i += read) { read = get_atom(w, a, type, i, &tmp, 1, &left); if (!read) break; if (tmp != remove) new[j++] = tmp; } if (j) XChangeProperty(dpy, w, a, type, 32, PropModeReplace, (unsigned char *)new, j); else XDeleteProperty(dpy, w, a); free(new); }