/* * 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 #include #include "main.h" #include "base.h" #include "config.h" #include "winlist.h" #include "fsysd.h" #include "taskbar.h" #include "taskbart.h" #include "xkrap.h" #include "xsupp.h" int taskbar_created = 0; Window taskbar_window; Window bg_window; GC taskbar_gc; int taskbar_x0, taskbar_y0, taskbar_width, taskbar_height; #define win taskbar_window #define gc taskbar_gc #define cur_x0 taskbar_x0 #define cur_y0 taskbar_y0 #define cur_w taskbar_width #define cur_h taskbar_height static int old_x0, old_y0, old_w, old_h, old_sw, old_sh; 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! */ /* TODO: wlist_page, ws.o, ws.n should be unsigned; buttons ids may still be signed? */ static int wlist_page = 0; /* number of taskbar winbutton slots */ typedef struct { int o; /* offset of window list displayed part */ int n; /* total number of windows in list */ WInfo *p[MAX_WINDOWS]; WList z_order; /* z-order linked list of non-minimized non-ontop windows */ WInfo *zprev; } workspace; static workspace ws; 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; x = ev->xbutton.x; y = ev->xbutton.y; switch(ev->xbutton.button) { case Button4: if(yxbutton.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) = ws.p[n]; assert((wi) && (wi)->cat==0 && (wi)->cpos==(unsigned)(n)); } while(0) static void validate_taskbar_winfo(WInfo *wi) { unsigned int pos; assert(wi); assert(wi->cat==0); pos = wi->cpos; assert(ws.n>0 && ws.n<=MAX_WINDOWS); assert(pos<(unsigned)ws.n && ws.p[pos]==wi); } 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==ws.zprev) XSetForeground(display, gc, base_color(TASKBAR_BUTBORDER_ZPREV)); 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_butsep(int n) { /* redraw button separator after button #n; -1 is separator before #0 */ int x, y, w; if(!taskbar_created) return; if(n=wlist_page) return; y = (n+1-ws.o)*TASKBAR_WINHEIGHT+TASKBAR_TOPAREA-1; x = 0; w = cur_w; if(w<1) return; XSetForeground(display, gc, base_color(TASKBAR_BG)); XDrawLine(display, win, gc, x, y, x+w-1, y); if(is_dragdrop && hover_num>=0 && (hover_numclick_num && hover_num==n)) { XSetForeground(display, gc, base_color(TASKBAR_BUTBREAKLINE)); XDrawLine(display, win, gc, x+1, y, x+w-3, y); } } #define HC_TO_SEP(h,c) ( (h) - (((h)<(c))?1:0) ) /* (h>=0 && c>=0 && h!=c) should be pre-checked by caller */ static void _redraw_button(int n) { WInfo const * wi; int x, y, w, h; int clickstate; if(!taskbar_created) return; /* TODO: redraw system buttons on n<=-2 */ if(n=wlist_page) return; y = (n-ws.o)*TASKBAR_WINHEIGHT+TASKBAR_TOPAREA; h = TASKBAR_WINHEIGHT; x = 0; w = cur_w; if(n=5 && h>=3) _redraw_button_ll(wi, x, y, w, h-1, (n==focus_num), clickstate); } static void taskbar_redraw(int y1, int y2) { int ybot; if(!taskbar_created) return; if(y2<=y1) return; if(y1ybot) _redraw_botarea(); if(!wlist_page) return; if(y1=TASKBAR_TOPAREA) _redraw_butsep(ws.o-1); 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 += ws.o; if(yn<0 || yn>=ws.n) return -1; return yn; } static void _handle_button(int n) { WInfo * wi; if(n>=0 && nis_active) taskbar_focus_window(wi); else taskbar_minimize_window(wi); 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); if(is_dragdrop && old>=0 && old!=click_num) _redraw_butsep(HC_TO_SEP(old,click_num)); _redraw_button(n); if(click_num>=0) is_dragdrop = 1; if(is_dragdrop && n>=0 && n!=click_num) _redraw_butsep(HC_TO_SEP(n,click_num)); if(click_num<=-2 && hover_num!=click_num) { assert(is_dragdrop==0); old = click_num; click_num = -1; _redraw_button(old); } } static void taskbar_move_button(int old_pos, int new_pos) { int n, j; WInfo *wi0, *wi; assert(old_pos>=0 && new_pos>=0); if(new_pos>old_pos) { n = new_pos-old_pos; GET_WINFO_BY_N(wi0, old_pos); for(j=old_pos; jcpos = j; } (ws.p[new_pos] = wi0)->cpos = new_pos; if(focus_num==old_pos) focus_num = new_pos; else if(focus_num>old_pos && focus_num<=new_pos) focus_num--; for(n=old_pos; n<=new_pos; n++) _redraw_button(n); } else if(new_posnew_pos; j--) { GET_WINFO_BY_N(wi, j-1); (ws.p[j] = wi)->cpos = j; } (ws.p[new_pos] = wi0)->cpos = new_pos; if(focus_num==old_pos) focus_num=new_pos; else if(focus_num>=new_pos && focus_num=0 && click_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; wi->bw = wa.border_width; #ifdef MIN_BORDERS_WIDTH if(wa.border_widthwindow, MIN_BORDERS_WIDTH); wi->bw = 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 WInfo * taskbar_add_winfo(Window window) { int n; WInfo * wi; assert(ws.n<=MAX_WINDOWS); if(ws.n==MAX_WINDOWS) return NULL; if(!(wi = WInfo_new(window))) return NULL; ws.p[n=ws.n++] = wi; wi->cat = 0; wi->cpos = n; _update_winfo(wi); _winfo_check_newtitle(wi, n); return wi; } extern int taskbar_upd_winfo(WInfo * wi) { assert(ws.n<=MAX_WINDOWS); assert(wi->cat==0); /* TODO */ if(wi->cat!=0) return 0; /* TODO: 1+ = other workspace, 50000=tray icon, 50001=widget */ assert((int)wi->cposcpos]==wi); _update_winfo(wi); _winfo_check_newtitle(wi, wi->cpos); return 1; } static void taskbar_zprev_deleted(void); extern void taskbar_del_winfo(WInfo * wi) { int n, m, need_focus; WInfo *wi2; Window window; window = wi->window; assert(ws.n<=MAX_WINDOWS); assert(wi->cat==0); /* TODO */ n = wi->cpos; assert(ncpos = m; } } if(wi->wdb.list) { assert(wi->wdb.list==&ws.z_order); WList_remove_window(wi->wdb.list, wi); } if(click_num==n) { click_num = -1; /* remove active click and remove drag&drop if it was active */ if(is_dragdrop) { is_dragdrop=0; if(hover_num>=0 && hover_num!=n) _redraw_butsep(HC_TO_SEP(hover_num,n)); } } if(click_num>n) { click_num--; if(is_dragdrop) { if(hover_num==click_num+1) _redraw_butsep(HC_TO_SEP(hover_num,click_num)); /* new butsep apprears */ if(hover_num==click_num) _redraw_butsep(HC_TO_SEP(hover_num,click_num+1)); /* old butsep disappears */ } } if(focus_num==n) { need_focus = 1; focus_num = -1; } if(focus_num>n) focus_num--; for(;n<=ws.n;n++) _redraw_button(n); if(need_focus) { if(ws.z_order.top) { assert(ws.z_order.top->cat==0); m = ws.z_order.top->cpos; assert(m>=0 && mwindow, 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<is_active = 0; if(owi->raised && !owi->ontop) place_window_below(display, owi->window, win); XGrabButton(display, AnyButton, AnyModifier, owi->window, False, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); } wi->is_active = 1; if(!wi->ontop) WList_raise_window(&ws.z_order, wi); focus_num = wi->cpos; if(owi) _redraw_button(owi->cpos); _redraw_button(wi->cpos); } w = wi->window; if(wi->raised || wi->ontop) place_window_ontop(display, w); else place_window_below(display, w, win); XSetInputFocus(display, w, RevertToPointerRoot, CurrentTime); /* TODO: skip this is WM_HINTS have input=false */ XUngrabButton(display, AnyButton, AnyModifier, w); grabbut(Button1, SPECMASK, w); grabbut(Button3, SPECMASK, w); set_atom_state(w, NormalState); send_atom_message(w, atom_WM_TAKEFOCUS, 0); return 1; } extern int taskbar_minimize_window(WInfo * wi) { Window w; validate_taskbar_winfo(wi); w = wi->window; place_window_below(display, w, bg_window); if(wi->ontop) assert(!wi->wdb.list); else if(!wi->wdb.list) logprint("BUG: try to minimize already minimized window!"); else { assert(wi->wdb.list==&ws.z_order); if(wi==ws.zprev) taskbar_zprev_deleted(); WList_remove_window(&ws.z_order, wi); } set_atom_state(w, IconicState); if(wi->is_active) { assert(focus_num==(int)wi->cpos); wi->is_active = 0; focus_num = -1; _redraw_button(wi->cpos); if(ws.z_order.top) taskbar_focus_window(ws.z_order.top); else XSetInputFocus(display, PointerRoot, RevertToPointerRoot, CurrentTime); } else { assert(focus_num!=(int)wi->cpos); } return 1; } extern int taskbar_toggle_maximize(WInfo * wi) { Window w; validate_taskbar_winfo(wi); w = wi->window; 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 = -wi->bw; wi->y0 = -wi->bw; 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); } return 1; } extern void taskbar_save_maximized(WInfo * wi, int x0, int y0, int w, int h) { validate_taskbar_winfo(wi); if(!wi->is_maximized) { wi->save_x0 = x0; wi->save_y0 = y0; wi->save_w = w; wi->save_h = h; wi->is_maximized = 1; } } extern WInfo * taskbar_get_focuswin(void) { WInfo *wi; if(focus_num<0) return NULL; assert(focus_num>=0 && focus_numis_active); return wi; } 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) { /* This is the only function that allowed to alter wi->ontop, and there is no way to alter 'ontop' flag for non-focused window. We should be careful about not setting ws.zprev->ontop, even if zprev sequence starts with focuswin->to_bottom, but zprev may become focused after that. To be safe we just forbid changing wi->ontop when zprev sequence active. */ WInfo * wi; if(ws.zprev || !(wi = taskbar_get_focuswin())) return; if(!wi->ontop) { wi->ontop = 1; place_window_ontop(display, wi->window); assert(wi->wdb.list==&ws.z_order); WList_remove_window(&ws.z_order, wi); } else { wi->ontop = 0; if(!wi->raised) place_window_below(display, wi->window, win); assert(!wi->wdb.list); WList_raise_window(&ws.z_order, wi); } } extern void taskbar_next_window(void) { int n, n0; if(ws.n<1) return; if(focus_num<0 || focus_num>=ws.n-1) n=n0=0; else n=n0=focus_num+1; while(ws.p[n]->ontop) { n = (n+1)%ws.n; if(n==n0) return; } taskbar_focus_window(ws.p[n]); } extern void taskbar_prev_window(void) { int n, n0; if(ws.n<1) return; if(focus_num<=0 || focus_num>ws.n-1) n=n0=ws.n-1; else n=n0=focus_num-1; while(ws.p[n]->ontop) { if(n) n--; else n=ws.n-1; if(n==n0) return; } taskbar_focus_window(ws.p[n]); } /* during active zprev, real z-order is (note fullscreen windows are ignored): [ontop_windows...] taskbar zprev_window [other_non_minimized...] remember not to break it during other actions */ static void zprev_start(void) { WInfo *cur, *new; assert(!ws.zprev); if(!(cur = taskbar_get_focuswin())) return; if(cur->ontop) { assert(!cur->wdb.list); new = ws.z_order.top; } else { assert(cur==ws.z_order.top); assert(!cur->wdb.to_top); new = cur->wdb.to_bottom; } if(!new) return; validate_taskbar_winfo(new); assert(!new->ontop); if(cur->raised && !cur->ontop) place_window_below(display, cur->window, win); place_window_below(display, new->window, win); ws.zprev = new; _redraw_button(new->cpos); } static WInfo * current_zprev(void) { WInfo *cur; cur = ws.zprev; validate_taskbar_winfo(cur); assert(!cur->ontop); /* see comment in taskbar_toggle_ontop_focus() */ assert(cur->wdb.list==&ws.z_order); /* all WList_remove_window() calls should be protected from removing ws.zprev */ return cur; } static void zprev_proceed(void) { WInfo *cur, *new; cur = current_zprev(); if(!(new = cur->wdb.to_bottom)) { #ifdef ZPREV_SEQ_ALLOW_WRAP new = ws.z_order_top; assert(new); /* we have at least 'cur' in z-order list */ #else ws.zprev = NULL; place_window_above(display, cur->window, bg_window); _redraw_button(cur->cpos); cur = taskbar_get_focuswin(); /* we already have 'cur' in z-order list, so there should be focuswin */ validate_taskbar_winfo(cur); if(cur->raised || cur->ontop) place_window_ontop(display, cur->window); else place_window_below(display, cur->window, win); return; #endif } validate_taskbar_winfo(new); place_window_above(display, cur->window, new->window); place_window_below(display, new->window, win); ws.zprev = new; _redraw_button(cur->cpos); _redraw_button(new->cpos); } static void zprev_cancel(void) { WInfo *cur, *new; cur = current_zprev(); new = cur->wdb.to_bottom; ws.zprev = NULL; place_window_above(display, cur->window, new?new->window:bg_window); _redraw_button(cur->cpos); cur = taskbar_get_focuswin(); /* we already have 'cur' in z-order list, so there should be focuswin */ validate_taskbar_winfo(cur); if(cur->raised || cur->ontop) place_window_ontop(display, cur->window); else place_window_below(display, cur->window, win); } static void zprev_accept(void) { WInfo *wi; wi = current_zprev(); ws.zprev = NULL; /* no need to restore back previous focused window place, and no need to restore back current zprev place, and no need to redraw current zprev button because taskbar_focus_window() will do that */ #ifdef ZPREV_SEQ_ALLOW_WRAP if(wi==taskbar_get_focuswin()) _redraw_button(focus_num); #endif taskbar_focus_window(wi); } extern void taskbar_zprev_window(void) { if(!ws.zprev) zprev_start(); else zprev_proceed(); } extern void taskbar_zprev_window_finish(void) { if(ws.zprev) zprev_accept(); } static void taskbar_zprev_deleted(void) { WInfo *new, *cur, *ff; cur = current_zprev(); if(!(new = cur->wdb.to_bottom)) { #ifdef ZPREV_SEQ_ALLOW_WRAP new = ws.z_order_top; assert(new); /* we have at least 'cur' in z-order list */ if(new==cur) #endif { ws.zprev = NULL; if((ff = taskbar_get_focuswin())!=cur) { validate_taskbar_winfo(ff); if(ff->raised || ff->ontop) place_window_ontop(display, ff->window); else place_window_below(display, ff->window, win); } return; } } validate_taskbar_winfo(new); place_window_below(display, new->window, win); ws.zprev = new; _redraw_button(new->cpos); }