/* * This file is part of firk's window manager for x11 (fwm) * Copyright (C) 2016-2022 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 #include #include "main.h" #include "base.h" #include "config.h" #include "winlist.h" #include "taskbar.h" #include "xkrap.h" #include "xsupp.h" int taskbar_created = 0; Window taskbar_window; Window bg_window; #define win taskbar_window static GC gc; static int old_x0, old_y0, old_w, old_h, old_sw, old_sh; static int cur_x0, cur_y0, cur_w, cur_h; static int is_dragdrop = 0, click_num = -1, hover_num = -1, focus_num = -1; /* click_num/hover_num: -1 is "none", other negative values used for top/bot area buttons! */ static int wlist_offs = 0; static int wlist_page = 0; /* this list is independent of taskbar window drawing; do not cleanup it at all */ static WInfo *pwlist[MAX_WINDOWS]; static int numwlist = 0; static WList WL; static WInfo * next_zprev; static void taskbar_calc_size(void) { int tw; tw = TASKBAR_WIDTH(sw); cur_x0 = sw-tw; cur_y0 = 0; cur_w = tw-TASKBAR_BWIDTH*2; cur_h = sh-TASKBAR_BWIDTH*2; if(cur_w<1) cur_w=1; if(cur_h<1) cur_h=1; if(cur_htype) { case Expose: if(ev->xexpose.window!=win) return; taskbar_redraw(ev->xexpose.y, ev->xexpose.y+ev->xexpose.height); break; case ButtonPress: if(ev->xbutton.window!=win) return; if(ev->xbutton.button==Button3) { taskbar_mouse_rclick(ev->xbutton.x, ev->xbutton.y); break; } if(ev->xbutton.button!=Button1) return; taskbar_mouse_click(ev->xbutton.x, ev->xbutton.y); break; case ButtonRelease: if(ev->xbutton.window!=win) return; if(ev->xbutton.button!=Button1) return; taskbar_mouse_unclick(); break; case MotionNotify: if(ev->xmotion.window!=win) return; taskbar_mouse_move(ev->xmotion.x, ev->xmotion.y); break; case LeaveNotify: if(ev->xcrossing.window!=win) return; taskbar_mouse_move(-1, -1); break; } } /* this is macros, not inline, to make assert log proper line number */ #define GET_WINFO_BY_N(wi, n) do { (wi) = pwlist[n]; assert((wi) && (wi)->pos==(unsigned)(n)); } while(0) static void _redraw_toparea(void) { if(!taskbar_created) return; } static void _redraw_botarea(void) { if(!taskbar_created) return; } /* clickstate: 0=none 1=hovered 2=clicked */ static void _redraw_button_ll(WInfo const * wi, int x, int y, int ww, int hh, int flag_focused, int clickstate) { int mw, dw, len, yofs; unsigned char bgcolor; XSetForeground(display, gc, base_color(TASKBAR_BG)); if(hh>0 && ww>0) XFillRectangle(display, win, gc, x, y, ww, hh); if(hh<7 || ww<20 || !wi) return; /* border */ if(flag_focused) XSetForeground(display, gc, base_color(TASKBAR_BUTBORDER_FOCUSED)); else if(wi->ontop) XSetForeground(display, gc, base_color(TASKBAR_BUTBORDER_ONTOP)); else XSetForeground(display, gc, base_color(TASKBAR_BUTBORDER)); XDrawRectangle(display, win, gc, x+3, y+1, ww-7, hh-3); /* button background */ if(clickstate==2) bgcolor=TASKBAR_BUTBG_CLICK; else if(clickstate==1) bgcolor=TASKBAR_BUTBG_HOVER; else bgcolor = TASKBAR_BUTBG; if(wi->mark) bgcolor |=RED; XSetForeground(display, gc, base_color(bgcolor)); XFillRectangle(display, win, gc, x+4, y+2, ww-8, hh-4); /* button text */ if(flag_focused) XSetForeground(display, gc, base_color(TASKBAR_BUTTEXT_FOCUSED)); else if(wi->ontop) XSetForeground(display, gc, base_color(TASKBAR_BUTTEXT_ONTOP)); else XSetForeground(display, gc, base_color(TASKBAR_BUTTEXT)); mw = ww-16; dw = text_width(gc, "...",3); if(mwtitle); yofs = 14; draw_string_limwidth_utf8(display, win, gc, x+8, y+yofs, mw, wi->title, len); } static void _redraw_button(int n) { WInfo const * wi; int x, y, ww, hh; int clickstate; if(!taskbar_created) return; /* TODO: redraw system buttons on n<=-2 */ if(n=wlist_offs+wlist_page) return; y = (n-wlist_offs)*TASKBAR_WINHEIGHT+TASKBAR_TOPAREA; hh = TASKBAR_WINHEIGHT; x = 0; ww = cur_w; if(nCALENDAR_Y) taskbar_timer_update(1); ybot = cur_h-TASKBAR_BOTAREA; if(y1ybot) _redraw_botarea(); if(!wlist_page) return; if(y1ybot) y2=ybot; if(y2<=TASKBAR_TOPAREA) y2=0; else y2=(y2-TASKBAR_TOPAREA+TASKBAR_WINHEIGHT-1)/TASKBAR_WINHEIGHT; if(y2>wlist_page) y2=wlist_page; for(;y1=ybot) return -1; /* TODO */ y = y-TASKBAR_TOPAREA; yn = y/TASKBAR_WINHEIGHT; yr = y%TASKBAR_WINHEIGHT; if(x<4 || x>cur_w-5 || yr<2 || yr>TASKBAR_WINHEIGHT-4) return -1; yn += wlist_offs; if(yn<0 || yn>=numwlist) return -1; return yn; } static void _handle_button(int n) { WInfo * wi; if(n>=0 && nis_active) _focus_by_num(n); else _min_by_num(n); return; } } static void taskbar_mouse_rclick(int x, int y) { WInfo * wi; int n, old; if(click_num!=-1) return; /* ignore clicks when something already */ assert(is_dragdrop==0); n = _get_but_by_xy(x, y); if(hover_num!=n) { old = hover_num; hover_num = n; _redraw_button(old); } if(n>=0 && nmark = ~wi->mark; } _redraw_button(n); } static void taskbar_mouse_click(int x, int y) { int n, old; if(click_num!=-1) return; /* ignore clicks when something already */ assert(is_dragdrop==0); n = _get_but_by_xy(x, y); if(hover_num!=n) { old = hover_num; hover_num = n; _redraw_button(old); } click_num = n; _redraw_button(n); } static void taskbar_mouse_move(int x, int y) { int n, old; n = _get_but_by_xy(x, y); if(hover_num==n) return; old = hover_num; hover_num = n; _redraw_button(old); _redraw_button(n); if(click_num>=0) is_dragdrop = 1; if(click_num<=-2 && hover_num!=click_num) { assert(is_dragdrop==0); old = click_num; click_num = -1; _redraw_button(old); } } static void taskbar_mouse_unclick(void) { int n, old, j; WInfo *wi0, *wi; if(click_num==-1) return; /* nothing was clicked */ if(!is_dragdrop) { assert(click_num=0 && click_num=0 && hover_num!=old) _redraw_button(hover_num); return; } if(hover_num>click_num) { n = hover_num-click_num; GET_WINFO_BY_N(wi0, click_num); for(j=0; jpos = click_num+j; } (pwlist[hover_num] = wi0)->pos = hover_num; if(focus_num==click_num) focus_num=hover_num; else if(focus_num>click_num && focus_num<=hover_num) focus_num--; old = click_num; click_num = -1; for(n=old; n<=hover_num; n++) _redraw_button(n); return; } assert(hover_num>=0 && hover_num+1pos = hover_num+j+1; } (pwlist[hover_num+1] = wi0)->pos = hover_num+1; if(focus_num==click_num) focus_num=hover_num+1; else if(focus_num>=hover_num+1 && focus_numwindow, wi->newtitle, sizeof(wi->newtitle)); if(!wi->newtitle[0]) snprintf(wi->newtitle, sizeof(wi->newtitle), "(no title)"); if(XGetWindowAttributes(display, wi->window, &wa)) { wi->x0 = wa.x; wi->y0 = wa.y; wi->w = wa.width; wi->h = wa.height; #ifdef MIN_BORDERS_WIDTH if(wa.border_widthwindow, MIN_BORDERS_WIDTH); } #endif } } static void _winfo_check_newtitle(WInfo * wi, int n) { if(strcmp(wi->title, wi->newtitle)) { memcpy(wi->title, wi->newtitle, sizeof(wi->title)-1); _redraw_button(n); } } extern int taskbar_add_winfo(Window window) { int n; WInfo * wi; assert(numwlist<=MAX_WINDOWS); if(wi = WInfo_search(window)) { n=wi->pos; assert(npos = n; found: _update_winfo(wi); _winfo_check_newtitle(wi, n); return 1; } extern int taskbar_upd_winfo(Window window) { WInfo * wi; assert(numwlist<=MAX_WINDOWS); if(wi = WInfo_search(window)) goto found; return 0; found: assert((int)wi->pospos]==wi); return wi->pos; } _update_winfo(wi); _winfo_check_newtitle(wi, wi->pos); return 1; } extern int taskbar_del_winfo(Window window) { int n, m, need_focus; WInfo *wi, *wi2; assert(numwlist<=MAX_WINDOWS); if(wi = WInfo_search(window)) { n=wi->pos; assert(nwdb.to_bottom; need_focus = 0; numwlist--; if(npos = m; } } if(wi->wdb.list) WList_remove_window(wi->wdb.list, wi); WInfo_delete(wi); if(click_num==n) { click_num=-1; is_dragdrop=0; } if(click_num>n) click_num--; if(focus_num==n) { need_focus = 1; focus_num = -1; } if(focus_num>n) focus_num--; if(hover_numpos; assert(m>=0 && m=0) _focus_by_num(m); } set_atom_state(window, WithdrawnState); return 1; } // TODO: get rid of this wrapper extern WInfo * taskbar_search_winfo(Window window) { return WInfo_search(window); } extern void taskbar_fix_focus(void) { WInfo * wi; if(!(wi = taskbar_get_focuswin())) XSetInputFocus(display, PointerRoot, RevertToPointerRoot, CurrentTime); else XSetInputFocus(display, wi->window, RevertToPointerRoot, CurrentTime); } static void grabbut(unsigned int button, unsigned int modif, Window window) { unsigned int n, m, k, mod; m = sizeof(ign_mods)/sizeof(ign_mods[0]); if(m>8) m=8; for(n=0; n<(1U<=0 && n=0) { assert(focus_numis_active); if(n==focus_num) goto do_focus; wi->is_active = 0; if(wi->raised && !wi->ontop) place_window_below(display, wi->window, win); XGrabButton(display, AnyButton, AnyModifier, wi->window, False, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); } nwi->is_active = 1; WList_raise_window(&WL, nwi); old = focus_num; focus_num = n; _redraw_button(old); _redraw_button(n); do_focus: w = nwi->window; if(nwi->raised || nwi->ontop) place_window_ontop(display, w); else place_window_below(display, w, win); XSetInputFocus(display, w, RevertToPointerRoot, CurrentTime); XUngrabButton(display, AnyButton, AnyModifier, w); grabbut(Button1, SPECMASK, w); grabbut(Button3, SPECMASK, w); set_atom_state(w, NormalState); send_atom_message(w, atom_TAKEFOCUS); } extern int taskbar_focus_window(Window window) { WInfo * wi; assert(numwlist<=MAX_WINDOWS); if(wi=WInfo_search(window)) { assert((int)wi->pospos]==wi); _focus_by_num(wi->pos); return 1; } return 0; } static void _min_by_num(int n) { Window w; WInfo * wi; int m, old; XWindowChanges wchg; assert(n>=0 && nwindow; place_window_below(display, w, bg_window); if(wi->wdb.list!=&WL) logprint("BUG: try to minimize already minimized window!"); else WList_remove_window(&WL, wi); set_atom_state(w, IconicState); if(n!=focus_num) return; assert(wi->is_active); wi->is_active = 0; old = focus_num; focus_num = -1; _redraw_button(old); m = -1; if(WL.top) { m = WL.top->pos; assert(m>=0 && mpospos]==wi); _min_by_num(wi->pos); return 1; } return 0; } static void _togglemax_by_num(int n) { Window w; WInfo * wi; int m, k, old; assert(n>=0 && nwindow; if(wi->is_maximized) { wi->x0 = wi->save_x0; wi->y0 = wi->save_y0; wi->w = wi->save_w; wi->h = wi->save_h; wi->is_maximized = 0; XMoveResizeWindow(display, w, wi->x0, wi->y0, wi->w, wi->h); } else { wi->save_x0 = wi->x0; wi->save_y0 = wi->y0; wi->save_w = wi->w; wi->save_h = wi->h; wi->is_maximized = 1; wi->x0 = 0; wi->y0 = 0; wi->w = cur_x0; wi->h = sh; //cur_h; if(wi->raised) wi->w = sw; XMoveResizeWindow(display, w, wi->x0, wi->y0, wi->w, wi->h); } } extern int taskbar_toggle_maximize(Window window) { WInfo * wi; assert(numwlist<=MAX_WINDOWS); if(wi=WInfo_search(window)) { assert((int)wi->pospos]==wi); _togglemax_by_num(wi->pos); return 1; } return 0; } extern WInfo * taskbar_get_focuswin(void) { if(focus_num<0) return NULL; assert(focus_num>=0 && focus_numis_active); return pwlist[focus_num]; } extern void taskbar_toggle_fullscreen_focus(void) { WInfo * wi; if(!(wi = taskbar_get_focuswin())) return; if(!wi->raised) { wi->raised = 1; place_window_ontop(display, wi->window); } else { wi->raised = 0; if(!wi->ontop) place_window_below(display, wi->window, win); } } extern void taskbar_toggle_ontop_focus(void) { WInfo * wi; if(!(wi = taskbar_get_focuswin())) return; if(!wi->ontop) { wi->ontop = 1; place_window_ontop(display, wi->window); } else { wi->ontop = 0; if(!wi->raised) place_window_below(display, wi->window, win); } } extern void taskbar_next_window(void) { int n, n0; if(numwlist<1) return; if(focus_num<0 || focus_num>=numwlist-1) n=n0=0; else n=n0=focus_num+1; while(pwlist[n]->ontop) { n = (n+1)%numwlist; if(n==n0) return; } _focus_by_num(n); } extern void taskbar_prev_window(void) { int n, n0; if(numwlist<1) return; if(focus_num<=0 || focus_num>numwlist-1) n=n0=numwlist-1; else n=n0=focus_num-1; while(pwlist[n]->ontop) { if(n) n--; else n=numwlist-1; if(n==n0) return; } _focus_by_num(n); } extern void taskbar_zprev_window(void) { WInfo * cur; unsigned int pos; if(!next_zprev) { if(!(cur = taskbar_get_focuswin())) return; if(!(next_zprev = cur->wdb.to_bottom)) return; } pos = next_zprev->pos; assert(numwlist>0 && pos<(unsigned)numwlist && pwlist[pos]==next_zprev); next_zprev = next_zprev->wdb.to_bottom; _focus_by_num(pos); } extern void taskbar_zprev_window_finish(void) { next_zprev = NULL; } /************************************ extra features ************************************/ static unsigned mdays(unsigned Y, unsigned M) { /* support M=0 (prev. year December and M=13 (next year January) */ static unsigned const md[14] = {31,31,28,31,30,31,30,31,31,30,31,30,31,31}; if(M==2 && (Y%4)==0 && ((Y%400)==0 || (Y%100)!=0)) return 29; return md[M]; } extern void taskbar_timer_update(int force_all) { static char const * wdays[7] = {"Sun","Mon","Tue","Wed","Thu", "Fri", "Sat"}; static time_t oldnow = 0; static unsigned oldday = 0, force_redraw = 0; struct tm tm; char dst[30], tmps[30]; unsigned CAL_X, CAL_Y, CAL_SX, CAL_SY, CELL_SX, CELL_SY, MAXX, dx, dy; unsigned W, Y, M, D, h, m, s; unsigned i, j, k, w, j0; int hd; if(force_all) { force_redraw = 1; return; } if(!taskbar_created) return; if(now==oldnow) return; oldnow = now; localtime_r(&now, &tm); W = tm.tm_wday%7; Y = tm.tm_year+1900; M = (tm.tm_mon%12)+1; D = tm.tm_mday%100; h = tm.tm_hour%100; m = tm.tm_min%100; s = tm.tm_sec%100; MAXX = cur_w; snprintf(dst, sizeof(dst), "%s %4u-%02u-%02u %02u:%02u:%02u ", wdays[W], Y, M, D, h, m, s); XSetForeground(display, gc, base_color(TASKBAR_BUTTEXT_FOCUSED)); CAL_X = CALENDAR_RAWX; CAL_Y = CALENDAR_RAWY; dx = text_width(gc, dst, strlen(dst)-5); if(dx>=MAXX) CAL_X = 0; else if(CAL_X>(MAXX-dx)/2) CAL_X = (MAXX-dx)/2; XDrawImageString(display, win, gc, CAL_X, CAL_Y, dst, strlen(dst)); if(oldday!=D) check_holidays_file(0); else if(!force_redraw) return; oldday = D; force_redraw = 0; XSetForeground(display, gc, base_color(CALENDAR_BG)); CAL_X = CALENDAR_X; CAL_Y = CALENDAR_Y; CELL_SX = CALENDAR_CELLSX; CELL_SY = CALENDAR_CELLSY; if(MAXX<40) return; while(CAL_X*2+CELL_SX*7>=MAXX) { if(CAL_X*4>=CELL_SX*3 || CAL_X*3>=CELL_SX*2 && CELL_SX<=10) CAL_X--; else CELL_SX--; } CAL_SX = CELL_SX*7; CAL_SY = CELL_SY*6; XFillRectangle(display, win, gc, CAL_X, CAL_Y, CAL_SX, CAL_SY); if(W==0) W=7; W--; assert(M>=1 && M<=12); j = (W+7-(D%7)+1)%7; w = 0; if(j) { k = mdays(Y,M-1); i = k-(j-1); for(j0=0; j00 || hd==-1 && j0>=5) XSetForeground(display, gc, base_color(CALENDAR_OTHERHDAY)); else XSetForeground(display, gc, base_color(CALENDAR_OTHERDAY)); snprintf(tmps, sizeof(tmps), "%u", i); dx = (i>=10)?10:6; if(dx>=CELL_SX) dx = 0; else dx = (CELL_SX-dx)/2; dy = 10; if(dy>=CELL_SY) dy = CELL_SY; else dy = (CELL_SY*4+dy*6)/10; XDrawImageString(display, win, gc, CAL_X+CELL_SX*j0+dx, CAL_Y+CELL_SY*w+dy, tmps, strlen(tmps)); } } i = 1; k = mdays(Y,M); while(1) { if(i==D) { XSetForeground(display, gc, base_color(CALENDAR_CURDAYBG)); XFillRectangle(display, win, gc, CAL_X+CELL_SX*j, CAL_Y+CELL_SY*w, CELL_SX, CELL_SY); } hd = get_holiday_state(Y,M,i); if(hd>0 || hd==-1 && j>=5) XSetForeground(display, gc, base_color(CALENDAR_HDAY)); else XSetForeground(display, gc, base_color(CALENDAR_DAY)); snprintf(tmps, sizeof(tmps), "%u", i); dx = (i>=10)?10:6; if(dx>=CELL_SX) dx = 0; else dx = (CELL_SX-dx)/2; dy = 10; if(dy>=CELL_SY) dy = CELL_SY; else dy = (CELL_SY*4+dy*6)/10; XDrawImageString(display, win, gc, CAL_X+CELL_SX*j+dx, CAL_Y+CELL_SY*w+dy, tmps, strlen(tmps)); if(i==k) break; i++; j = (j+1)%7; if(!j) w++; } if(j<6) { j++; k = mdays(Y,M+1); i = 1; for( ; j<7; j++,i++) { hd = get_holiday_state(Y,M+1,i); if(hd>0 || hd==-1 && j>=5) XSetForeground(display, gc, base_color(CALENDAR_OTHERHDAY)); else XSetForeground(display, gc, base_color(CALENDAR_OTHERDAY)); snprintf(tmps, sizeof(tmps), "%u", i); dx = (i>=10)?10:6; if(dx>=CELL_SX) dx = 0; else dx = (CELL_SX-dx)/2; dy = 10; if(dy>=CELL_SY) dy = CELL_SY; else dy = (CELL_SY*4+dy*6)/10; XDrawImageString(display, win, gc, CAL_X+CELL_SX*j+dx, CAL_Y+CELL_SY*w+dy, tmps, strlen(tmps)); } } } static void taskbar_status_update(void) { } extern void taskbar_periodic(void) { taskbar_timer_update(0); if(startup_errors) { XSetForeground(display, gc, base_color(STERR_COLOR)); XDrawImageString(display, win, gc, STERR_X, STERR_Y, STERR_MSG, strlen(STERR_MSG)); } }