/* * This file is part of firk's window manager for x11 (fwm) * Copyright (C) 2016-2023 firk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "main.h" #define BASE_INTERNAL #include "base.h" #include "config.h" #include "winlist.h" #include "taskbar.h" #include "taskbart.h" #include #include #include #include "tick.h" #include "runprog.h" Display * display; Screen * screen; int sw, sh; Window rootwin; Cursor cur_left_ptr; Atom atoms[atoms_length]; char current_theme[64]; static unsigned long colors[16]; static unsigned int super_keycode[2]; static unsigned int br_keycode[2]; extern int send_atom_message(Window window, Atom msg, int param) { Atom * protos; int nprotos, n; XEvent ev; if(msg!=atom_FWM_THEMESET) { if(!XGetWMProtocols(display, window, &protos, &nprotos)) return 0; for(n=0;n8) m=8; for(n=0; n<(1U<keycode = XKeysymToKeycode(display, hk->keysym); if(hk->keycode) grabkey(hk->keycode, SPECMASK|hk->shift); } if(super_keycode[0]=XKeysymToKeycode(display, XK_Super_L)) { grabkey(super_keycode[0], 0); grabkey(super_keycode[0], ControlMask); grabkey(super_keycode[0], ShiftMask); grabkey(super_keycode[0], ControlMask|ShiftMask); } if(super_keycode[1]=XKeysymToKeycode(display, XK_Super_R)) { grabkey(super_keycode[1], 0); grabkey(super_keycode[1], ControlMask); grabkey(super_keycode[1], ShiftMask); grabkey(super_keycode[1], ControlMask|ShiftMask); } if(HOOK_BRIGHTNESS_KEYS) { if(br_keycode[0] = XKeysymToKeycode(display,XF86XK_MonBrightnessDown)) XGrabKey(display, br_keycode[0], AnyModifier, rootwin, False, GrabModeAsync, GrabModeAsync); if(br_keycode[1] = XKeysymToKeycode(display,XF86XK_MonBrightnessUp)) XGrabKey(display, br_keycode[1], AnyModifier, rootwin, False, GrabModeAsync, GrabModeAsync); } { short val[4]; val[0] = 16; /* width */ val[1] = 16; /* height */ val[2] = 40; /* wide width expected to be (width*2.5) */ val[3] = 0; /* reserved */ XChangeProperty(display, rootwin, atom_FWM_TRAYSIZE, atom_FWM_TRAYSIZE, 16, PropModeReplace, (unsigned char*)val, 4); /* TODO: unsure if this needed, because fwm anyway will resize tray windows to this sizes and clients may look them there */ } XChangeProperty(display, rootwin, atom_FWM_THEMESET, atom_FWM_THEMESET, 8, PropModeReplace, (unsigned char*)current_theme, strlen(current_theme+1)+2); } static int update_themeset(WInfo *wi, void *u) { if(wi->flags & WI_NEED_THEMESET) send_atom_message(wi->window, atom_FWM_THEMESET, current_theme[0]); return 0; } extern void base_change_theme(char const *theme) { size_t j; char c; bzero(current_theme, sizeof(current_theme)); if(theme) { current_theme[0] = theme[0]; for(j=1; j=0 && c<=31 || c==127) break; current_theme[j] = c; } } XChangeProperty(display, rootwin, atom_FWM_THEMESET, atom_FWM_THEMESET, 8, PropModeReplace, (unsigned char*)current_theme, strlen(current_theme+1)+2); WInfo_foreach(&update_themeset, NULL); } extern int base_get_event(XEvent * ev) { if(XCheckMaskEvent(display, -1, ev)==False) return 0; return 1; } extern void base_wait_event(XEvent * ev) { XNextEvent(display, ev); } static int on_window_map(Window window) { Atom * protos; int nprotos, n; unsigned int flags; char wcl[32]; char *tmp; WInfo * wi; if(window==taskbar_window || window==bg_window) { logprint("ERROR! someone sent MapRequest for our windows, ignoring"); return 0; } if(WInfo_search(window)) { logprint("ERROR! duplicate MapRequest, ignoring"); return 0; } flags = 0; if(XGetWMProtocols(display, window, &protos, &nprotos)) { for(n=0;n0) { strncpy(wcl, tmp, 32); XFree(tmp); if(wcl[0]<1 || wcl[0]>WCLASS_MAX || wcl[31]) bzero(wcl, sizeof(wcl)); else for(n=1; n<31 && wcl[n]; n++) if(wcl[n]<32 || wcl[n]>126) { bzero(wcl, sizeof(wcl)); break; } } switch(wcl[0]) { case 0: wcl[0] = WCLASS_NORMAL; case WCLASS_NORMAL: if(!(wi = taskbar_add_winfo(window))) { logprint("ERROR! too many windows! destroying new one!"); XDestroyWindow(display, window); return 1; } break; case WCLASS_TRAYICON: case WCLASS_TRAYWIDE: if(!(wi = tray_add_winfo(window, wcl))) { logprint("ERROR! too many windows! destroying new one!"); XDestroyWindow(display, window); return 1; } break; case WCLASS_WIDGET: if(!(wi = widgets_add_winfo(window, wcl))) { logprint("ERROR! too many windows! destroying new one!"); XDestroyWindow(display, window); return 1; } break; default: logprint("ERROR! unknown window class %d, destroying it", (int)wcl[0]); XDestroyWindow(display, window); return 1; } wi->flags = flags; strncpy(wi->fwm_wclass, wcl, sizeof(wi->fwm_wclass)-1); if(flags & WI_NEED_THEMESET) send_atom_message(window, atom_FWM_THEMESET, current_theme[0]); XSelectInput(display, window, FocusChangeMask|PropertyChangeMask); XMapWindow(display, window); if(wcl[0]==WCLASS_NORMAL) taskbar_focus_window(wi); return 1; } static int on_window_unmap(Window window) { WInfo * wi; if(window==taskbar_window || window==bg_window) { logprint("ERROR! someone sent UnmapRequest for our windows, ignoring"); return 0; } if(!(wi = WInfo_search(window))) return 0; if(wi->cat==0) taskbar_del_winfo(wi); else if(wi->cat==50000) tray_del_winfo(wi); else if(wi->cat==50001) widgets_del_winfo(wi); else logprint("ERROR! unmap for unknown wi->cat=%u",wi->cat); /* TODO: 1+=taskbar 50000=tray 50001=widget */ WInfo_delete(wi); return 1; } /* window creation: got CreateNotify (parent=rootwin) got ConfigureRequest (parent=rootwin) call XConfigureWindow got ConfigureNotify (event=rootwin) got ConfigureRequest call XConfigureWindow got ConfigureNotify (event=rootwin) got MapRequest (parent=rootwin) call XMapWindow got MapNotify (event=rootwin) on destroying: got UnmapNotify (event=rootwin) got DestroyNotify (event=rootwin) */ /* return 0 to display warning about unhandled event */ extern int base_handle_event(XEvent * ev) { WInfo * wi; XWindowChanges wchg; unsigned long t; char const * cmd; unsigned int shift; switch(ev->type) { /* just say we handled these useless events */ case CreateNotify: if(ev->xcreatewindow.parent!=rootwin) return 0; return 1; case ConfigureNotify: if(ev->xconfigure.window==rootwin) { #ifndef WATCH_ROOTWIN_RESIZE return 0; #else sw = ev->xconfigure.width; if(sw<0) sw=0; sh = ev->xconfigure.height; if(sh<0) sh=0; return 1; #endif } if(ev->xconfigure.event!=rootwin) return 0; if(wi = WInfo_search(ev->xconfigure.window)) if(wi->cat==0) taskbar_upd_winfo(wi); return 1; case MapNotify: if(ev->xmap.event!=rootwin) return 0; return 1; /* real useful events */ case ConfigureRequest: if(ev->xconfigurerequest.parent!=rootwin) return 0; /* ignore configure requests for system windows from other clients */ if(ev->xconfigurerequest.window==taskbar_window || ev->xconfigurerequest.window==bg_window) return 1; /* this event can be for both mapped and not mapped window just retranslate it */ wchg.x = ev->xconfigurerequest.x; wchg.y = ev->xconfigurerequest.y; wchg.width = ev->xconfigurerequest.width; wchg.height = ev->xconfigurerequest.height; /* taskbart.c uses w=-1 as a mark for still unhandled window; anyway, negative sizes are not useful, deny them */ if(wchg.width<0) wchg.width = 0; if(wchg.height<0) wchg.height = 0; #ifndef FORCE_BORDERS_WIDTH wchg.border_width = ev->xconfigurerequest.border_width; #else wchg.border_width = FORCE_BORDERS_WIDTH; ev->xconfigurerequest.value_mask |= CWBorderWidth; #endif #ifdef MIN_BORDERS_WIDTH if(wchg.border_widthxconfigurerequest.above; wchg.stack_mode = ev->xconfigurerequest.detail;*/ if(wi = WInfo_search(ev->xconfigurerequest.window)) { /* disallow move/resize/borderchange for tray and widget windows; this allows us to add tiny patch (setting FWM_WCLASS before window map) to some app to convert it to fwm-tray compatible one */ if(wi->cat>=50000) return 1; } XConfigureWindow(display, ev->xconfigurerequest.window, ev->xconfigurerequest.value_mask & ~CWSibling & ~CWStackMode , &wchg); return 1; case MapRequest: if(ev->xmaprequest.parent!=rootwin) return 0; return on_window_map(ev->xmaprequest.window); case UnmapNotify: if(ev->xunmap.event!=rootwin) return 0; return on_window_unmap(ev->xunmap.window); case DestroyNotify: if(ev->xdestroywindow.event!=rootwin) return 0; return on_window_unmap(ev->xdestroywindow.window); case FocusIn: if(!(wi = WInfo_search(ev->xfocus.window))) { return 1; /* TODO */ logprint("warn: FocusIn for unknown window"); return 0; } if(!wi->is_active) taskbar_fix_focus(); /* prevent focus stealing */ return 1; case FocusOut: if(!(wi = WInfo_search(ev->xfocus.window))) { return 1; /* TODO */ logprint("warn: FocusOut for unknown window"); return 0; } return 1; case PropertyNotify: if(ev->xproperty.atom==atom_WM_NAME) { if(!(wi = WInfo_search(ev->xproperty.window))) return 0; if(wi->cat!=0) return 1; if(!taskbar_upd_winfo(wi)) return 0; } else if(ev->xproperty.atom==atom_WM_HINTS) { if(!(wi = WInfo_search(ev->xproperty.window))) return 0; if(wi->cat!=0) return 1; if(!taskbar_upd_winfo(wi)) return 0; } else if(ev->xproperty.atom==atom_WM_TRANSFOR) { if(!(wi = WInfo_search(ev->xproperty.window))) return 0; if(wi->cat!=0) return 1; if(!taskbar_upd_winfo(wi)) return 0; /* TODO: upd_winfo should known what property to reread */ /* TODO: WM_HINTS contains window-alarm flag */ /* TODO: WM_HINTS have option to start-minimized */ /* TODO: WM_TRANSIENT_FOR to avoid hiding popup by its "parent" */ /* TODO: WM_CHANGE_STATE event support to allow apps to minimize/deminimize their windows; should be disabled by default */ } return 1; case ButtonPress: wi = WInfo_search(ev->xbutton.window); if(!wi) return 0; if(!wi->is_active) taskbar_focus_window(wi); if(CLRMOD(ev->xbutton.state)==SPECMASK) { t = get_ticks(); if(ev->xbutton.button==Button1) { if(wi->specstate==2 && t-wi->spec1ticksspecstate = 3; } else if(wi->specstate==0 || wi->specstate==2) { wi->specstate=1; wi->spec1ticks = t; wi->spec1x=ev->xbutton.x_root; wi->spec1y=ev->xbutton.y_root; wi->spec2x=wi->x0; wi->spec2y=wi->y0; } } else if(ev->xbutton.button==Button3) { wi->specstate=5; wi->spec1x=ev->xbutton.x_root; wi->spec1y=ev->xbutton.y_root; wi->spec2x=wi->w; wi->spec2y=wi->h; } } return 1; case ButtonRelease: wi = WInfo_search(ev->xbutton.window); if(!wi) return 0; if(wi->specstate==1) { wi->specstate=2; } else if(wi->specstate==3) { wi->specstate = 0; taskbar_toggle_maximize(wi); } else wi->specstate = 0; return 1; case MotionNotify: wi = WInfo_search(ev->xmotion.window); if(!wi) return 0; if(wi->specstate==1) { int dx, dy; dx = ev->xmotion.x_root-wi->spec1x; dy = ev->xmotion.y_root-wi->spec1y; wi->spec2x += dx; wi->spec2y += dy; wi->x0 = wi->spec2x; wi->y0 = wi->spec2y; XMoveWindow(display, wi->window, wi->x0, wi->y0); wi->spec1x += dx; wi->spec1y += dy; } else if(wi->specstate==5) { int dx, dy; dx = ev->xmotion.x_root-wi->spec1x; dy = ev->xmotion.y_root-wi->spec1y; wi->spec2x += dx; wi->spec2y += dy; if(wi->spec2x<1) wi->spec2x=1; if(wi->spec2y<1) wi->spec2y=1; wi->w = wi->spec2x; wi->h = wi->spec2y; XResizeWindow(display, wi->window, wi->w, wi->h); wi->spec1x += dx; wi->spec1y += dy; } return 1; case KeyPress: if(ev->xkey.window!=rootwin) return 0; if(!ev->xkey.keycode) return 0; if(HOOK_BRIGHTNESS_KEYS) { if(ev->xkey.keycode==br_keycode[0]) { taskbart_brightness(-1); return 1; } if(ev->xkey.keycode==br_keycode[1]) { taskbart_brightness(1); return 1; } } shift = CLRMOD(ev->xkey.state); if((shift & SPECMASK)!=SPECMASK) return 0; cmd = get_hotkey_by_keycode(ev->xkey.keycode, shift-SPECMASK); /* gcc does not allow switch() for pointers for some reason */ if(!cmd) return 0; else if(cmd==OP_CLOSE_WINDOW) { if(wi = taskbar_get_focuswin()) { if(!send_atom_message(wi->window, atom_WM_DELETE, 0)) { XDestroyWindow(display, wi->window); XSync(display, False); } } } else if(cmd==OP_KILL_WINDOW) { if(wi = taskbar_get_focuswin()) { XDestroyWindow(display, wi->window); XSync(display, False); } } else if(cmd==OP_TOGGLE_FULL) { taskbar_toggle_fullscreen_focus(); } else if(cmd==OP_NEXT_WINDOW) { taskbar_next_window(); } else if(cmd==OP_PREV_WINDOW) { taskbar_prev_window(); } else if(cmd==OP_ZPREV_WINDOW) { taskbar_zprev_window(); } else if(cmd==OP_TOGGLE_ONTOP) { taskbar_toggle_ontop_focus(); } else if(cmd==OP_QUIT) { exit(0); } else if(cmd==OP_BRIGHTNESS_INC) { taskbart_brightness(1); } else if(cmd==OP_BRIGHTNESS_DEC) { taskbart_brightness(-1); } else { run_command(cmd); } return 1; case KeyRelease: if(ev->xkey.window!=rootwin) return 0; if(ev->xkey.keycode==super_keycode[0] || ev->xkey.keycode==super_keycode[1]) taskbar_zprev_window_finish(); return 1; default: return 0; } } extern unsigned long base_color(unsigned char c) { return colors[c&15]; }