/* -*-c-*- */ /* 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 */ /* ---------------------------- included header files ---------------------- */ #include "config.h" #include #include "libs/fvwmlib.h" #include "libs/FScreen.h" #include "libs/Grab.h" #include "libs/Parse.h" #include "libs/XResource.h" #include "fvwm.h" #include "externs.h" #include "execcontext.h" #include "cursor.h" #include "bindings.h" #include "misc.h" #include "screen.h" #include "placement.h" #include "geometry.h" #include "update.h" #include "style.h" #include "move_resize.h" #include "virtual.h" #include "stack.h" #include "ewmh.h" #include "icons.h" #include "add_window.h" /* ---------------------------- local definitions -------------------------- */ #define MAX_NUM_PLACEMENT_ALGOS 31 #define CP_GET_NEXT_STEP 5 /* ---------------------------- local macros ------------------------------- */ #ifndef MIN #define MIN(A,B) ((A)<(B)? (A):(B)) #endif #ifndef MAX #define MAX(A,B) ((A)>(B)? (A):(B)) #endif #define NORMAL_PLACEMENT_PENALTY(p) (p->normal) #define ONTOP_PLACEMENT_PENALTY(p) (p->ontop) #define ICON_PLACEMENT_PENALTY(p) (p->icon) #define STICKY_PLACEMENT_PENALTY(p) (p->sticky) #define BELOW_PLACEMENT_PENALTY(p) (p->below) #define EWMH_STRUT_PLACEMENT_PENALTY(p) (p->strut) #define PERCENTAGE_99_PENALTY(p) (p->p99) #define PERCENTAGE_95_PENALTY(p) (p->p95) #define PERCENTAGE_85_PENALTY(p) (p->p85) #define PERCENTAGE_75_PENALTY(p) (p->p75) /* ---------------------------- imports ------------------------------------ */ /* ---------------------------- included code files ------------------------ */ /* ---------------------------- local types -------------------------------- */ typedef enum { PR_POS_NORMAL = 0, PR_POS_IGNORE_PPOS, PR_POS_USE_PPOS, PR_POS_IGNORE_USPOS, PR_POS_USE_USPOS, PR_POS_PLACE_AGAIN, PR_POS_CAPTURE, PR_POS_USPOS_OVERRIDE_SOS } preason_pos_t; typedef enum { PR_SCREEN_CURRENT = 0, PR_SCREEN_STYLE, PR_SCREEN_X_RESOURCE_FVWMSCREEN, PR_SCREEN_IGNORE_CAPTURE } preason_screen_t; typedef enum { PR_PAGE_CURRENT = 0, PR_PAGE_STYLE, PR_PAGE_X_RESOURCE_PAGE, PR_PAGE_IGNORE_CAPTURE, PR_PAGE_IGNORE_INVALID, PR_PAGE_STICKY } preason_page_t; typedef enum { PR_DESK_CURRENT = 0, PR_DESK_STYLE, PR_DESK_X_RESOURCE_DESK, PR_DESK_X_RESOURCE_PAGE, PR_DESK_CAPTURE, PR_DESK_STICKY, PR_DESK_WINDOW_GROUP_LEADER, PR_DESK_WINDOW_GROUP_MEMBER, PR_DESK_TRANSIENT, PR_DESK_XPROP_XA_WM_DESKTOP } preason_desk_t; typedef struct { struct { preason_pos_t reason; int x; int y; int algo; char *pl_position_string; unsigned do_not_manual_icon_placement : 1; unsigned do_adjust_off_screen : 1; unsigned do_adjust_off_page : 1; unsigned is_pl_position_string_invalid : 1; unsigned has_tile_failed : 1; unsigned has_manual_failed : 1; unsigned has_placement_failed : 1; } pos; struct { preason_screen_t reason; int screen; rectangle g; unsigned was_modified_by_ewmh_workingarea : 1; } screen; struct { preason_page_t reason; int px; int py; unsigned do_switch_page : 1; unsigned do_honor_starts_on_page : 1; unsigned do_ignore_starts_on_page : 1; } page; struct { preason_desk_t reason; preason_desk_t sod_reason; int desk; unsigned do_switch_desk : 1; } desk; } pl_reason_t; typedef struct { int desk; int page_x; int page_y; int screen; } pl_start_style_t; typedef struct { unsigned do_forbid_manual_placement : 1; unsigned do_honor_starts_on_page : 1; unsigned do_honor_starts_on_screen : 1; unsigned do_not_use_wm_placement : 1; } pl_flags_t; typedef float pl_penalty_t; typedef enum { PL_LOOP_END, PL_LOOP_CONT } pl_loop_rc_t; struct pl_arg_t; struct pl_ret_t; typedef struct { /* If this funtion pointer is not NULL, use this function to return * the desired position in a single call */ pl_penalty_t (*get_pos_simple)( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); /* otherwise use these three in a loop */ pl_loop_rc_t (*get_first_pos)( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); pl_loop_rc_t (*get_next_pos)( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg, position hint_p); pl_penalty_t (*get_pos_penalty)( position *ret_hint_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); } pl_algo_t; typedef struct pl_scratch_t { const pl_penalty_struct *pp; const pl_percent_penalty_struct *ppp; } pl_scratch_t; typedef struct pl_arg_t { const pl_algo_t *algo; const exec_context_t *exc; const window_style *style; pl_reason_t *reason; FvwmWindow *place_fw; pl_scratch_t *scratch; rectangle place_g; position place_p2; rectangle screen_g; position page_p1; position page_p2; position pdelta_p; struct { unsigned use_percent : 1; unsigned use_ewmh_dynamic_working_areapercent : 1; unsigned do_honor_starts_on_page : 1; } flags; } pl_arg_t; typedef struct pl_ret_t { position best_p; pl_penalty_t best_penalty; struct { unsigned do_resize_too : 1; } flags; } pl_ret_t; /* ---------------------------- forward declarations ----------------------- */ static pl_loop_rc_t __pl_minoverlap_get_first_pos( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); static pl_loop_rc_t __pl_minoverlap_get_next_pos( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg, position hint_p); static pl_penalty_t __pl_minoverlap_get_pos_penalty( position *ret_hint_p, struct pl_ret_t *ret, const pl_arg_t *arg); static pl_penalty_t __pl_smart_get_pos_penalty( position *ret_hint_p, struct pl_ret_t *ret, const pl_arg_t *arg); static pl_penalty_t __pl_position_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); static pl_penalty_t __pl_cascade_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); static pl_penalty_t __pl_manual_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg); /* ---------------------------- local variables ---------------------------- */ const pl_algo_t minoverlap_placement_algo = { NULL, __pl_minoverlap_get_first_pos, __pl_minoverlap_get_next_pos, __pl_minoverlap_get_pos_penalty }; const pl_algo_t smart_placement_algo = { NULL, __pl_minoverlap_get_first_pos, __pl_minoverlap_get_next_pos, __pl_smart_get_pos_penalty }; const pl_algo_t position_placement_algo = { __pl_position_get_pos_simple }; const pl_algo_t cascade_placement_algo = { __pl_cascade_get_pos_simple }; const pl_algo_t manual_placement_algo = { __pl_manual_get_pos_simple }; /* ---------------------------- exported variables (globals) --------------- */ const pl_penalty_struct default_pl_penalty = { 1, PLACEMENT_AVOID_ONTOP, PLACEMENT_AVOID_ICON, PLACEMENT_AVOID_STICKY, PLACEMENT_AVOID_BELOW, PLACEMENT_AVOID_EWMH_STRUT }; const pl_percent_penalty_struct default_pl_percent_penalty = { PLACEMENT_AVOID_COVER_99, PLACEMENT_AVOID_COVER_95, PLACEMENT_AVOID_COVER_85, PLACEMENT_AVOID_COVER_75 }; /* ---------------------------- local functions (PositionPlacement) -------- */ static pl_penalty_t __pl_position_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg) { char *spos; Bool fPointer; int n; int i; Bool is_under_mouse; is_under_mouse = False; spos = SGET_PLACEMENT_POSITION_STRING(*arg->style); if (spos == NULL || *spos == 0) { spos = DEFAULT_PLACEMENT_POSITION_STRING; i = 1; } else if (StrEquals(spos, "Center")) { spos = DEFAULT_PLACEMENT_POS_CENTER_STRING; i = 1; } else if (StrEquals(spos, "UnderMouse")) { spos = DEFAULT_PLACEMENT_POS_MOUSE_STRING; i = 1; is_under_mouse = True; } else { i = 0; } arg->reason->pos.pl_position_string = spos; for (n = -1; i < 2 && n < 2; i++) { fPointer = False; ret_p->x = 0; ret_p->y = 0; n = GetMoveArguments( &spos, arg->place_g.width, arg->place_g.height, &ret_p->x, &ret_p->y, NULL, &fPointer, False); spos = DEFAULT_PLACEMENT_POSITION_STRING; if (n < 2) { arg->reason->pos.is_pl_position_string_invalid = 1; } } if (n < 2) { /* bug */ abort(); } if (is_under_mouse) { /* TA: 20090218: Try and keep the window on-screen if we * can. */ if (ret_p->x + arg->place_fw->g.frame.width > arg->screen_g.x + arg->screen_g.width) { ret_p->x = (arg->screen_g.x + arg->screen_g.width) - arg->place_fw->g.frame.width; } if (ret_p->y + arg->place_fw->g.frame.height > arg->screen_g.y + arg->screen_g.height) { ret_p->y = (arg->screen_g.y + arg->screen_g.height) - arg->place_fw->g.frame.height; } } /* Don't let the upper left corner be offscreen. */ if (ret_p->x < arg->screen_g.x) { ret_p->x = arg->screen_g.x; } if (ret_p->y < arg->screen_g.y) { ret_p->y = arg->screen_g.y; } if (arg->flags.do_honor_starts_on_page) { ret_p->x -= arg->pdelta_p.x; ret_p->y -= arg->pdelta_p.y; } return 0; } /* ---------------------------- local functions (CascadePlacement)---------- */ static pl_penalty_t __pl_cascade_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg) { size_borders b; int w; FvwmWindow *t; t = (Scr.cascade_window != NULL) ? Scr.cascade_window : arg->place_fw; w = t->title_thickness; if (w == 0) { w = t->boundary_width; } if (w == 0) { w = PLACEMENT_FALLBACK_CASCADE_STEP; } if (Scr.cascade_window != NULL) { Scr.cascade_x += w; Scr.cascade_y += w; switch (GET_TITLE_DIR(t)) { case DIR_S: case DIR_N: Scr.cascade_y += w; break; case DIR_E: case DIR_W: Scr.cascade_x += w; break; default: break; } } Scr.cascade_window = arg->place_fw; if (Scr.cascade_x > arg->screen_g.width / 2) { Scr.cascade_x = arg->place_fw->title_thickness; } if (Scr.cascade_y > arg->screen_g.height / 2) { Scr.cascade_y = 2 * arg->place_fw->title_thickness; } ret_p->x = Scr.cascade_x + arg->page_p1.x; ret_p->y = Scr.cascade_y + arg->page_p1.y; /* try to keep the window on the screen */ get_window_borders(arg->place_fw, &b); if (ret_p->x + arg->place_g.width >= arg->page_p2.x) { ret_p->x = arg->page_p2.x - arg->place_g.width - b.total_size.width; Scr.cascade_x = arg->place_fw->title_thickness; switch (GET_TITLE_DIR(t)) { case DIR_E: case DIR_W: Scr.cascade_x += arg->place_fw->title_thickness; default: break; } } if (ret_p->y + arg->place_g.height >= arg->page_p2.y) { ret_p->y = arg->page_p2.y - arg->place_g.height - b.total_size.height; Scr.cascade_y = arg->place_fw->title_thickness; switch (GET_TITLE_DIR(t)) { case DIR_N: case DIR_S: Scr.cascade_y += arg->place_fw->title_thickness; default: break; } } /* the left and top sides are more important in huge windows */ if (ret_p->x < arg->page_p1.x) { ret_p->x = arg->page_p1.x; } if (ret_p->y < arg->page_p1.y) { ret_p->y = arg->page_p1.y; } return 0; } /* ---------------------------- local functions (ManualPlacement)----------- */ static pl_penalty_t __pl_manual_get_pos_simple( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg) { ret_p->x = 0; ret_p->y = 0; if (GrabEm(CRS_POSITION, GRAB_NORMAL)) { int DragWidth; int DragHeight; int mx; int my; /* Grabbed the pointer - continue */ MyXGrabServer(dpy); if ( XGetGeometry( dpy, FW_W(arg->place_fw), &JunkRoot, &JunkX, &JunkY, (unsigned int*)&DragWidth, (unsigned int*)&DragHeight, (unsigned int*)&JunkBW, (unsigned int*)&JunkDepth) == 0) { MyXUngrabServer(dpy); UngrabEm(GRAB_NORMAL); return -1; } SET_PLACED_BY_FVWM(arg->place_fw, 0); MyXGrabKeyboard(dpy); DragWidth = arg->place_g.width; DragHeight = arg->place_g.height; if (Scr.SizeWindow != None) { XMapRaised(dpy, Scr.SizeWindow); } FScreenGetScrRect( NULL, FSCREEN_GLOBAL, &mx, &my, NULL, NULL); if (__move_loop( arg->exc, mx, my, DragWidth, DragHeight, &ret_p->x, &ret_p->y, False, CRS_POSITION)) { ret->flags.do_resize_too = 1; } if (Scr.SizeWindow != None) { XUnmapWindow(dpy, Scr.SizeWindow); } MyXUngrabKeyboard(dpy); MyXUngrabServer(dpy); UngrabEm(GRAB_NORMAL); } else { /* couldn't grab the pointer - better do something */ XBell(dpy, 0); ret_p->x = 0; ret_p->y = 0; arg->reason->pos.has_manual_failed = 1; } if (arg->flags.do_honor_starts_on_page) { ret_p->x -= arg->pdelta_p.x; ret_p->y -= arg->pdelta_p.y; } return 0; } /* ---------------------------- local functions (MinoverlapPlacement) ------ */ /* MinoverlapPlacement by Anthony Martin * This algorithm places a new window such that there is a minimum amount of * interference with other windows. If it can place a window without any * interference, fine. Otherwise, it places it so that the area of of * interference between the new window and the other windows is minimized */ static int __pl_minoverlap_get_next_x(const pl_arg_t *arg) { FvwmWindow *other_fw; int xnew; int xtest; int stickyx; int stickyy; int start,i; int win_left; rectangle g; Bool rc; int x; int y; x = arg->place_g.x; y = arg->place_g.y; if (arg->flags.use_percent == 1) { start = 0; } else { start = CP_GET_NEXT_STEP; } /* Test window at far right of screen */ xnew = arg->page_p2.x; xtest = arg->page_p2.x - arg->place_g.width; if (xtest > x) { xnew = xtest; } /* test the borders of the working area */ xtest = arg->page_p1.x + Scr.Desktops->ewmh_working_area.x; if (xtest > x) { xnew = MIN(xnew, xtest); } xtest = arg->page_p1.x + (Scr.Desktops->ewmh_working_area.x + Scr.Desktops->ewmh_working_area.width) - arg->place_g.width; if (xtest > x) { xnew = MIN(xnew, xtest); } /* Test the values of the right edges of every window */ for ( other_fw = Scr.FvwmRoot.next; other_fw != NULL; other_fw = other_fw->next) { if ( other_fw == arg->place_fw || (other_fw->Desk != arg->place_fw->Desk && !IS_STICKY_ACROSS_DESKS(other_fw)) || IS_EWMH_DESKTOP(FW_W(other_fw))) { continue; } if (IS_STICKY_ACROSS_PAGES(other_fw)) { stickyx = arg->pdelta_p.x; stickyy = arg->pdelta_p.y; } else { stickyx = 0; stickyy = 0; } if (IS_ICONIFIED(other_fw)) { rc = get_visible_icon_geometry(other_fw, &g); if (rc == True && y < g.y + g.height - stickyy && g.y - stickyy < arg->place_g.height + y) { win_left = arg->page_p1.x + g.x - stickyx - arg->place_g.width; for (i = start; i <= CP_GET_NEXT_STEP; i++) { xtest = win_left + g.width * (CP_GET_NEXT_STEP - i) / CP_GET_NEXT_STEP; if (xtest > x) { xnew = MIN(xnew, xtest); } } win_left = arg->page_p1.x + g.x - stickyx; for (i = start; i <= CP_GET_NEXT_STEP; i++) { xtest = (win_left) + g.width * i / CP_GET_NEXT_STEP; if (xtest > x) { xnew = MIN(xnew, xtest); } } } } else if ( y < other_fw->g.frame.height + other_fw->g.frame.y - stickyy && other_fw->g.frame.y - stickyy < arg->place_g.height + y && arg->page_p1.x < other_fw->g.frame.width + other_fw->g.frame.x - stickyx && other_fw->g.frame.x - stickyx < arg->page_p2.x) { win_left = other_fw->g.frame.x - stickyx - arg->place_g.width; for (i = start; i <= CP_GET_NEXT_STEP; i++) { xtest = win_left + other_fw->g.frame.width * (CP_GET_NEXT_STEP - i) / CP_GET_NEXT_STEP; if (xtest > x) { xnew = MIN(xnew, xtest); } } win_left = other_fw->g.frame.x - stickyx; for (i = start; i <= CP_GET_NEXT_STEP; i++) { xtest = win_left + other_fw->g.frame.width * i / CP_GET_NEXT_STEP; if (xtest > x) { xnew = MIN(xnew, xtest); } } } } return xnew; } static int __pl_minoverlap_get_next_y(const pl_arg_t *arg) { FvwmWindow *other_fw; int ynew; int ytest; int stickyy; int win_top; int start; int i; rectangle g; int y; y = arg->place_g.y; if (arg->flags.use_percent == 1) { start = 0; } else { start = CP_GET_NEXT_STEP; } /* Test window at far bottom of screen */ ynew = arg->page_p2.y; ytest = arg->page_p2.y - arg->place_g.height; if (ytest > y) { ynew = ytest; } /* test the borders of the working area */ ytest = arg->page_p1.y + Scr.Desktops->ewmh_working_area.y; if (ytest > y) { ynew = MIN(ynew, ytest); } ytest = arg->screen_g.y + (Scr.Desktops->ewmh_working_area.y + Scr.Desktops->ewmh_working_area.height) - arg->place_g.height; if (ytest > y) { ynew = MIN(ynew, ytest); } /* Test the values of the bottom edge of every window */ for ( other_fw = Scr.FvwmRoot.next; other_fw != NULL; other_fw = other_fw->next) { if ( other_fw == arg->place_fw || ( other_fw->Desk != arg->place_fw->Desk && !IS_STICKY_ACROSS_DESKS(other_fw)) || IS_EWMH_DESKTOP(FW_W(other_fw))) { continue; } if (IS_STICKY_ACROSS_PAGES(other_fw)) { stickyy = arg->pdelta_p.y; } else { stickyy = 0; } if (IS_ICONIFIED(other_fw)) { get_visible_icon_geometry(other_fw, &g); win_top = g.y - stickyy; for (i = start; i <= CP_GET_NEXT_STEP; i++) { ytest = win_top + g.height * i / CP_GET_NEXT_STEP; if (ytest > y) { ynew = MIN(ynew, ytest); } } win_top = g.y - stickyy - arg->place_g.height; for (i = start; i <= CP_GET_NEXT_STEP; i++) { ytest = win_top + g.height * (CP_GET_NEXT_STEP - i) / CP_GET_NEXT_STEP; if (ytest > y) { ynew = MIN(ynew, ytest); } } } else { win_top = other_fw->g.frame.y - stickyy; for (i = start; i <= CP_GET_NEXT_STEP; i++) { ytest = win_top + other_fw->g.frame.height * i / CP_GET_NEXT_STEP; if (ytest > y) { ynew = MIN(ynew, ytest); } } win_top = other_fw->g.frame.y - stickyy - arg->place_g.height; for (i = start; i <= CP_GET_NEXT_STEP; i++) { ytest = win_top + other_fw->g.frame.height * (CP_GET_NEXT_STEP - i) / CP_GET_NEXT_STEP; if (ytest > y) { ynew = MIN(ynew, ytest); } } } } return ynew; } static pl_loop_rc_t __pl_minoverlap_get_first_pos( position *ret_p, struct pl_ret_t *ret, const pl_arg_t *arg) { /* top left corner of page */ ret_p->x = arg->page_p1.x; ret_p->y = arg->page_p1.y; return PL_LOOP_CONT; } static pl_loop_rc_t __pl_minoverlap_get_next_pos( position *ret_p, struct pl_ret_t *ret, const struct pl_arg_t *arg, position hint_p) { ret_p->x = arg->place_g.x; ret_p->y = arg->place_g.y; if (ret_p->x + arg->place_g.width <= arg->page_p2.x) { /* try next x */ ret_p->x = __pl_minoverlap_get_next_x(arg); ret_p->y = arg->place_g.y; } if (ret_p->x + arg->place_g.width > arg->page_p2.x) { /* out of room in x direction. Try next y. Reset x.*/ ret_p->x = arg->page_p1.x; ret_p->y = __pl_minoverlap_get_next_y(arg); } if (ret_p->y + arg->place_g.height > arg->page_p2.y) { /* PageBottom */ return PL_LOOP_END; } return PL_LOOP_CONT; } static pl_penalty_t __pl_minoverlap_get_avoidance_penalty( const pl_arg_t *arg, FvwmWindow *other_fw, const rectangle *other_g) { pl_penalty_t anew; pl_penalty_t avoidance_factor; position other_p2; const pl_penalty_struct *opp; const pl_percent_penalty_struct *oppp; opp = (arg->scratch->pp != 0 && 0) ? arg->scratch->pp : &other_fw->pl_penalty; oppp = (arg->scratch->ppp != 0 && 0) ? arg->scratch->ppp : &other_fw->pl_percent_penalty; other_p2.x = other_g->x + other_g->width; other_p2.y = other_g->y + other_g->height; { long x1 = MAX(arg->place_g.x, other_g->x); long x2 = MIN(arg->place_p2.x, other_p2.x); long y1 = MAX(arg->place_g.y, other_g->y); long y2 = MIN(arg->place_p2.y, other_p2.y); /* overlapping area in pixels (windows are guaranteed to * overlap when this function is called) */ anew = (x2 - x1) * (y2 - y1); } if (IS_ICONIFIED(other_fw)) { avoidance_factor = ICON_PLACEMENT_PENALTY(opp); } else if (compare_window_layers(other_fw, arg->place_fw) > 0) { avoidance_factor = ONTOP_PLACEMENT_PENALTY(opp); } else if (compare_window_layers(other_fw, arg->place_fw) < 0) { avoidance_factor = BELOW_PLACEMENT_PENALTY(opp); } else if ( IS_STICKY_ACROSS_PAGES(other_fw) || IS_STICKY_ACROSS_DESKS(other_fw)) { avoidance_factor = STICKY_PLACEMENT_PENALTY(opp); } else { avoidance_factor = NORMAL_PLACEMENT_PENALTY(opp); } if (arg->flags.use_percent == 1) { pl_penalty_t cover_factor; long other_area; long place_area; other_area = other_g->width * other_g->height; place_area = arg->place_g.width * arg->place_g.height; cover_factor = 0; if (other_area != 0 && place_area != 0) { anew = 100 * MAX(anew / other_area, anew / place_area); if (anew >= 99) { cover_factor = PERCENTAGE_99_PENALTY(oppp); } else if (anew > 94) { cover_factor = PERCENTAGE_95_PENALTY(oppp); } else if (anew > 84) { cover_factor = PERCENTAGE_85_PENALTY(oppp); } else if (anew > 74) { cover_factor = PERCENTAGE_75_PENALTY(oppp); } } if (avoidance_factor >= 1) { avoidance_factor += cover_factor; } } if ( arg->flags.use_ewmh_dynamic_working_areapercent == 1 && DO_EWMH_IGNORE_STRUT_HINTS(other_fw) == 0 && ( other_fw->dyn_strut.left > 0 || other_fw->dyn_strut.right > 0 || other_fw->dyn_strut.top > 0 || other_fw->dyn_strut.bottom > 0)) { const pl_penalty_struct *mypp; mypp = (arg->scratch->pp != 0 && 0) ? arg->scratch->pp : &arg->place_fw->pl_penalty; /* if we intersect a window which reserves space */ avoidance_factor += (avoidance_factor >= 1) ? EWMH_STRUT_PLACEMENT_PENALTY(mypp) : 0; } anew *= avoidance_factor; return anew; } static pl_penalty_t __pl_minoverlap_get_pos_penalty( position *ret_hint_p, struct pl_ret_t *ret, const struct pl_arg_t *arg) { FvwmWindow *other_fw; pl_penalty_t penalty; size_borders b; penalty = 0; for ( other_fw = Scr.FvwmRoot.next; other_fw != NULL; other_fw = other_fw->next) { rectangle other_g; Bool rc; get_window_borders(other_fw, &b); if ( arg->place_fw == other_fw || IS_EWMH_DESKTOP(FW_W(other_fw))) { continue; } /* RBW - account for sticky windows... */ if ( other_fw->Desk != arg->place_fw->Desk && IS_STICKY_ACROSS_DESKS(other_fw) == 0) { continue; } rc = get_visible_window_or_icon_geometry(other_fw, &other_g); if (IS_STICKY_ACROSS_PAGES(other_fw)) { other_g.x -= arg->pdelta_p.x; other_g.y -= arg->pdelta_p.y; } if ( arg->place_g.x < other_g.x + other_g.width && arg->place_p2.x > other_g.x && arg->place_g.y < other_g.y + other_g.height && arg->place_p2.y > other_g.y) { pl_penalty_t anew; anew = __pl_minoverlap_get_avoidance_penalty( arg, other_fw, &other_g); penalty += anew; if ( penalty > ret->best_penalty && ret->best_penalty != -1) { /* TA: 20091230: Fix over-zealous penalties * by explicitly forcing the window on-screen * here. The y-axis is only affected here, * due to how the xoffset calculations happen * prior to setting the x-axis. When we get * penalties which are "over-zealous" -- and * by not taking into account the size of the * window borders, the window was being placed * off screen. */ if (ret->best_p.y + arg->place_g.height > arg->page_p2.y) { ret->best_p.y = (arg->page_p2.y - arg->place_g.height - b.total_size.height); ret->best_penalty = 0; penalty = 0; } /* stop looking; the penalty is too high */ return penalty; } } } /* now handle the working area */ { const pl_penalty_struct *mypp; mypp = (arg->scratch->pp != 0 && 0) ? arg->scratch->pp : &arg->place_fw->pl_penalty; if (arg->flags.use_ewmh_dynamic_working_areapercent == 1) { penalty += EWMH_STRUT_PLACEMENT_PENALTY(mypp) * EWMH_GetStrutIntersection( arg->place_g.x, arg->place_g.y, arg->place_p2.x, arg->place_p2.y, arg->flags.use_percent); } else { /* EWMH_USE_DYNAMIC_WORKING_AREA, count the base strut */ penalty += EWMH_STRUT_PLACEMENT_PENALTY(mypp) * EWMH_GetBaseStrutIntersection( arg->place_g.x, arg->place_g.y, arg->place_p2.x, arg->place_p2.y, arg->flags.use_percent); } } return penalty; } /* ---------------------------- local functions (SmartPlacement) ----------- */ static pl_penalty_t __pl_smart_get_pos_penalty( position *ret_hint_p, struct pl_ret_t *ret, const struct pl_arg_t *arg) { pl_penalty_t p; arg->scratch->pp = &default_pl_penalty; arg->scratch->ppp = &default_pl_percent_penalty; p = __pl_minoverlap_get_pos_penalty(ret_hint_p, ret, arg); if (p != 0) { p = -1; } return p; } /* ---------------------------- local functions ---------------------------- */ static int placement_loop(pl_ret_t *ret, pl_arg_t *arg) { position next_p; pl_penalty_t penalty; pl_loop_rc_t loop_rc; if (arg->algo->get_pos_simple != NULL) { position pos; penalty = arg->algo->get_pos_simple(&pos, ret, arg); arg->place_g.x = pos.x; arg->place_g.y = pos.y; ret->best_penalty = penalty; ret->best_p.x = pos.x; ret->best_p.y = pos.y; loop_rc = PL_LOOP_END; } else { loop_rc = arg->algo->get_first_pos(&next_p, ret, arg); arg->place_g.x = next_p.x; arg->place_g.y = next_p.y; ret->best_p.x = next_p.x; ret->best_p.y = next_p.y; } while (loop_rc != PL_LOOP_END) { position hint_p; pl_scratch_t scratch; memset(&scratch, 0, sizeof(scratch)); arg->scratch = &scratch; arg->place_p2.x = arg->place_g.x + arg->place_g.width; arg->place_p2.y = arg->place_g.y + arg->place_g.height; hint_p.x = arg->place_g.x; hint_p.y = arg->place_g.y; penalty = arg->algo->get_pos_penalty(&hint_p, ret, arg); /* I've added +0.0001 because with my machine the < test fail * with certain *equal* float numbers! */ if ( penalty >= 0 && ( ret->best_penalty < 0 || penalty + 0.0001 < ret->best_penalty)) { ret->best_p.x = arg->place_g.x; ret->best_p.y = arg->place_g.y; ret->best_penalty = penalty; } if (penalty == 0) { break; } loop_rc = arg->algo->get_next_pos(&next_p, ret, arg, hint_p); arg->place_g.x = next_p.x; arg->place_g.y = next_p.y; } if (ret->best_penalty < 0) { ret->best_penalty = -1; } return (ret->best_penalty == -1) ? -1 : 0; } static void __place_get_placement_flags( pl_flags_t *ret_flags, FvwmWindow *fw, window_style *pstyle, initial_window_options_t *win_opts, int mode, pl_reason_t *reason) { Bool override_ppos; Bool override_uspos; Bool has_ppos = False; Bool has_uspos = False; /* Windows use the position hint if these conditions are met: * * The program specified a USPosition hint and it is not overridden * with the No(Transient)USPosition style. * * OR * * The program specified a PPosition hint and it is not overridden * with the No(Transient)PPosition style. * * Windows without a position hint are placed using wm placement. */ if (IS_TRANSIENT(fw)) { override_ppos = SUSE_NO_TRANSIENT_PPOSITION(&pstyle->flags); override_uspos = SUSE_NO_TRANSIENT_USPOSITION(&pstyle->flags); } else { override_ppos = SUSE_NO_PPOSITION(&pstyle->flags); override_uspos = SUSE_NO_USPOSITION(&pstyle->flags); } if (fw->hints.flags & PPosition) { if (!override_ppos) { has_ppos = True; reason->pos.reason = PR_POS_USE_PPOS; } else { reason->pos.reason = PR_POS_IGNORE_PPOS; } } if (fw->hints.flags & USPosition) { if (!override_uspos) { has_uspos = True; reason->pos.reason = PR_POS_USE_USPOS; } else if (reason->pos.reason != PR_POS_USE_PPOS) { reason->pos.reason = PR_POS_IGNORE_USPOS; } } if (mode == PLACE_AGAIN) { ret_flags->do_not_use_wm_placement = 0; reason->pos.reason = PR_POS_PLACE_AGAIN; } else if (has_ppos || has_uspos) { ret_flags->do_not_use_wm_placement = 1; } else if (win_opts->flags.do_override_ppos) { ret_flags->do_not_use_wm_placement = 1; reason->pos.reason = PR_POS_CAPTURE; } else if (!ret_flags->do_honor_starts_on_page && fw->wmhints && (fw->wmhints->flags & StateHint) && fw->wmhints->initial_state == IconicState) { ret_flags->do_forbid_manual_placement = 1; reason->pos.do_not_manual_icon_placement = 1; } return; } static int __add_algo( const pl_algo_t **algos, int num_algos, const pl_algo_t *new_algo) { if (num_algos >= MAX_NUM_PLACEMENT_ALGOS) { return MAX_NUM_PLACEMENT_ALGOS; } algos[num_algos] = new_algo; num_algos++; return num_algos; } static int __place_get_wm_pos( const exec_context_t *exc, window_style *pstyle, rectangle *attr_g, pl_flags_t flags, rectangle screen_g, pl_start_style_t start_style, int mode, initial_window_options_t *win_opts, pl_reason_t *reason, int pdeltax, int pdeltay) { const pl_algo_t *algos[MAX_NUM_PLACEMENT_ALGOS + 1]; int num_algos; unsigned int placement_mode = SPLACEMENT_MODE(&pstyle->flags); pl_arg_t arg; pl_ret_t ret; int i; /* BEGIN init placement agrs and ret */ memset(&arg, 0, sizeof(arg)); arg.exc = exc; arg.style = pstyle; arg.reason = reason; arg.place_fw = exc->w.fw; arg.place_g = arg.place_fw->g.frame; arg.screen_g = screen_g; arg.page_p1.x = arg.screen_g.x - pdeltax; arg.page_p1.y = arg.screen_g.y - pdeltay; arg.page_p2.x = arg.page_p1.x + screen_g.width; arg.page_p2.y = arg.page_p1.y + screen_g.height; arg.pdelta_p.x = pdeltax; arg.pdelta_p.y = pdeltay; arg.flags.use_percent = 0; arg.flags.do_honor_starts_on_page = flags.do_honor_starts_on_page; if (SEWMH_PLACEMENT_MODE(&pstyle->flags) == EWMH_USE_WORKING_AREA) { arg.flags.use_ewmh_dynamic_working_areapercent = 1; } memset(&ret, 0, sizeof(ret)); ret.best_penalty = -1.0; /* END init placement agrs and ret */ /* override if manual placement happens */ SET_PLACED_BY_FVWM(arg.place_fw, 1); if (flags.do_forbid_manual_placement) { switch (placement_mode) { case PLACE_MANUAL: case PLACE_MANUAL_B: placement_mode = PLACE_CASCADE; break; case PLACE_TILEMANUAL: placement_mode = PLACE_TILECASCADE; break; default: break; } } reason->pos.algo = placement_mode; /* first, try various "smart" placement */ num_algos = 0; switch (placement_mode) { case PLACE_POSITION: num_algos = __add_algo( algos, num_algos, &position_placement_algo); break; case PLACE_TILEMANUAL: num_algos = __add_algo( algos, num_algos, &smart_placement_algo); num_algos = __add_algo( algos, num_algos, &manual_placement_algo); break; case PLACE_MINOVERLAPPERCENT: arg.flags.use_percent = 1; /* fall through */ case PLACE_MINOVERLAP: num_algos = __add_algo( algos, num_algos, &minoverlap_placement_algo); break; case PLACE_TILECASCADE: num_algos = __add_algo( algos, num_algos, &smart_placement_algo); num_algos = __add_algo( algos, num_algos, &cascade_placement_algo); break; case PLACE_MANUAL: case PLACE_MANUAL_B: num_algos = __add_algo( algos, num_algos, &manual_placement_algo); break; case PLACE_CASCADE: case PLACE_CASCADE_B: num_algos = __add_algo( algos, num_algos, &cascade_placement_algo); break; default: /* can't happen */ break; } /* try all the placement algorithms */ for (i = 0 ; ret.best_penalty < 0 && i < num_algos; i++) { arg.algo = algos[i]; placement_loop(&ret, &arg); } if (ret.best_penalty >= 0) { /* placement succed */ attr_g->x = ret.best_p.x; attr_g->y = ret.best_p.y; } else { /* fall back to default position */ attr_g->x = 0; attr_g->y = 0; reason->pos.has_placement_failed = 1; } return ret.flags.do_resize_too; } static int __place_get_nowm_pos( const exec_context_t *exc, window_style *pstyle, rectangle *attr_g, pl_flags_t flags, rectangle screen_g, pl_start_style_t start_style, int mode, initial_window_options_t *win_opts, pl_reason_t *reason, int pdeltax, int pdeltay) { FvwmWindow *fw = exc->w.fw; size_borders b; if (!win_opts->flags.do_override_ppos) { SET_PLACED_BY_FVWM(fw, False); } /* the USPosition was specified, or the window is a transient, or it * starts iconic so place it automatically */ if ( SUSE_START_ON_SCREEN(&pstyle->flags) && flags.do_honor_starts_on_screen) { fscreen_scr_t mangle_screen; /* If StartsOnScreen has been given for a window, translate its * USPosition so that it is relative to that particular screen. * If we don't do this, then a geometry would completely * cancel the effect of the StartsOnScreen style. However, some * applications try to remember their position. This would * break if these were translated to screen coordinates. There * is no reliable way to do it. Currently, if the desired * place does not intersect the target screen, we assume the * window position must be adjusted to the screen origin. So * there are two ways to get a window to pop up on a particular * Xinerama screen. 1: The intuitive way giving a geometry * hint relative to the desired screen's 0,0 along with the * appropriate StartsOnScreen style (or *wmscreen resource), or * 2: Do NOT specify a Xinerama screen (or specify it to be * 'g') and give the geometry hint in terms of the global * screen. */ mangle_screen = FScreenFetchMangledScreenFromUSPosHints( &(fw->hints)); if (mangle_screen != FSCREEN_GLOBAL) { /* whoever set this hint knew exactly what he was * doing; so ignore the StartsOnScreen style */ flags.do_honor_starts_on_screen = 0; reason->pos.reason = PR_POS_USPOS_OVERRIDE_SOS; } else if (attr_g->x + attr_g->width < screen_g.x || attr_g->x >= screen_g.x + screen_g.width || attr_g->y + attr_g->height < screen_g.y || attr_g->y >= screen_g.y + screen_g.height) { /* desired coordinates do not intersect the target * screen. Let's assume the application specified * global coordinates and translate them to the screen. */ FScreenTranslateCoordinates( NULL, start_style.screen, NULL, FSCREEN_GLOBAL, &attr_g->x, &attr_g->y); reason->pos.do_adjust_off_screen = 1; } } /* If SkipMapping, and other legalities are observed, adjust for * StartsOnPage. */ if (DO_NOT_SHOW_ON_MAP(fw) && flags.do_honor_starts_on_page && (!IS_TRANSIENT(fw) || SUSE_START_ON_PAGE_FOR_TRANSIENT(&pstyle->flags)) #if 0 /* dv 08-Jul-2003: Do not use this. Instead, force the window on * the requested page even if the application requested a different * position. */ && (SUSE_NO_PPOSITION(&pstyle->flags) || !(fw->hints.flags & PPosition)) /* dv 08-Jul-2003: This condition is always true because we * already checked for flags.do_honor_starts_on_page above. */ /* RBW - allow StartsOnPage to go through, even if iconic. */ && ((!(fw->wmhints && (fw->wmhints->flags & StateHint) && fw->wmhints->initial_state == IconicState)) || flags.do_honor_starts_on_page) #endif ) { int old_x; int old_y; old_x = attr_g->x; old_y = attr_g->y; /* We're placing a SkipMapping window - either capturing one * that's previously been mapped, or overriding USPosition - so * what we have here is its actual untouched coordinates. In * case it was a StartsOnPage window, we have to 1) convert the * existing x,y offsets relative to the requested page (i.e., * as though there were only one page, no virtual desktop), * then 2) readjust relative to the current page. */ if (attr_g->x < 0) { attr_g->x += Scr.MyDisplayWidth; } attr_g->x %= Scr.MyDisplayWidth; attr_g->x -= pdeltax; /* Noticed a quirk here. With some apps (e.g., xman), we find * the placement has moved 1 pixel away from where we * originally put it when we come through here. Why is this * happening? Probably attr_backup.border_width, try xclock * -borderwidth 100 */ if (attr_g->y < 0) { attr_g->y += Scr.MyDisplayHeight; } attr_g->y %= Scr.MyDisplayHeight; attr_g->y -= pdeltay; if (attr_g->x != old_x || attr_g->y != old_y) { reason->pos.do_adjust_off_page = 1; } } /* put it where asked, mod title bar */ /* if the gravity is towards the top, move it by the title height */ { rectangle final_g; int gravx; int gravy; gravity_get_offsets(fw->hints.win_gravity, &gravx, &gravy); final_g.x = attr_g->x + gravx * fw->attr_backup.border_width; final_g.y = attr_g->y + gravy * fw->attr_backup.border_width; /* Virtually all applications seem to share a common bug: they * request the top left pixel of their *border* as their origin * instead of the top left pixel of their client window, e.g. * 'xterm -g +0+0' creates an xterm that tries to map at (0 0) * although its border (width 1) would not be visible if it ran * under plain X. It should have tried to map at (1 1) * instead. This clearly violates the ICCCM, but trying to * change this is like tilting at windmills. So we have to add * the border width here. */ final_g.x += fw->attr_backup.border_width; final_g.y += fw->attr_backup.border_width; final_g.width = 0; final_g.height = 0; if (mode == PLACE_INITIAL) { get_window_borders(fw, &b); gravity_resize( fw->hints.win_gravity, &final_g, b.total_size.width, b.total_size.height); } attr_g->x = final_g.x; attr_g->y = final_g.y; } return 0; } /* Handles initial placement and sizing of a new window * * Return value: * * 0 = window lost * 1 = OK * 2 = OK, window must be resized too */ static int __place_window( const exec_context_t *exc, window_style *pstyle, rectangle *attr_g, pl_start_style_t start_style, int mode, initial_window_options_t *win_opts, pl_reason_t *reason) { FvwmWindow *t; int is_skipmapping_forbidden; int px = 0; int py = 0; int pdeltax = 0; int pdeltay = 0; rectangle screen_g; int rc = 0; pl_flags_t flags; extern Bool Restarting; FvwmWindow *fw = exc->w.fw; memset(&flags, 0, sizeof(flags)); /* Select a desk to put the window on (in list of priority): * 1. Sticky Windows stay on the current desk. * 2. Windows specified with StartsOnDesk go where specified * 3. Put it on the desk it was on before the restart. * 4. Transients go on the same desk as their parents. * 5. Window groups stay together (if the KeepWindowGroupsOnDesk style * is used). */ /* Let's get the StartsOnDesk/Page tests out of the way first. */ if ( SUSE_START_ON_DESK(&pstyle->flags) || SUSE_START_ON_SCREEN(&pstyle->flags)) { flags.do_honor_starts_on_page = 1; flags.do_honor_starts_on_screen = 1; /* * Honor the flag unless... * it's a restart or recapture, and that option's disallowed... */ if (win_opts->flags.do_override_ppos && (Restarting || (Scr.flags.are_windows_captured)) && !SRECAPTURE_HONORS_STARTS_ON_PAGE(&pstyle->flags)) { flags.do_honor_starts_on_page = 0; flags.do_honor_starts_on_screen = 0; reason->page.reason = PR_PAGE_IGNORE_CAPTURE; reason->page.do_ignore_starts_on_page = 1; reason->screen.reason = PR_PAGE_IGNORE_CAPTURE; } /* * it's a cold start window capture, and that's disallowed... */ if (win_opts->flags.do_override_ppos && (!Restarting && !(Scr.flags.are_windows_captured)) && !SCAPTURE_HONORS_STARTS_ON_PAGE(&pstyle->flags)) { flags.do_honor_starts_on_page = 0; flags.do_honor_starts_on_screen = 0; reason->page.reason = PR_PAGE_IGNORE_CAPTURE; reason->page.do_ignore_starts_on_page = 1; reason->screen.reason = PR_PAGE_IGNORE_CAPTURE; } /* * it's ActivePlacement and SkipMapping, and that's disallowed. */ switch (SPLACEMENT_MODE(&pstyle->flags)) { case PLACE_MANUAL: case PLACE_MANUAL_B: case PLACE_TILEMANUAL: is_skipmapping_forbidden = !SMANUAL_PLACEMENT_HONORS_STARTS_ON_PAGE( &pstyle->flags); break; default: is_skipmapping_forbidden = 0; break; } if (win_opts->flags.do_override_ppos || !DO_NOT_SHOW_ON_MAP(fw)) { is_skipmapping_forbidden = 0; } if (is_skipmapping_forbidden == 1) { flags.do_honor_starts_on_page = 0; reason->page.reason = PR_PAGE_IGNORE_INVALID; reason->page.do_ignore_starts_on_page = 1; fvwm_msg( WARN, "__place_window", "invalid style combination used: StartsOnPage" "/StartsOnDesk and SkipMapping don't work with" " ManualPlacement and TileManualPlacement." " Putting window on current page, please use" " another placement style or" " ActivePlacementHonorsStartsOnPage."); } } /* get the screen coordinates to place window on */ if (SUSE_START_ON_SCREEN(&pstyle->flags)) { if (flags.do_honor_starts_on_screen) { /* use screen from style */ FScreenGetScrRect( NULL, start_style.screen, &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height); reason->screen.screen = start_style.screen; } else { /* use global screen */ FScreenGetScrRect( NULL, FSCREEN_GLOBAL, &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height); reason->screen.screen = FSCREEN_GLOBAL; } } else { /* use current screen */ FScreenGetScrRect( NULL, FSCREEN_CURRENT, &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height); reason->screen.screen = FSCREEN_CURRENT; } if (SPLACEMENT_MODE(&pstyle->flags) != PLACE_MINOVERLAPPERCENT && SPLACEMENT_MODE(&pstyle->flags) != PLACE_MINOVERLAP) { EWMH_GetWorkAreaIntersection( fw, &screen_g.x, &screen_g.y, &screen_g.width, &screen_g.height, SEWMH_PLACEMENT_MODE(&pstyle->flags)); reason->screen.was_modified_by_ewmh_workingarea = 1; } reason->screen.g = screen_g; /* Don't alter the existing desk location during Capture/Recapture. */ if (!win_opts->flags.do_override_ppos) { fw->Desk = Scr.CurrentDesk; reason->desk.reason = PR_DESK_CURRENT; } else { reason->desk.reason = PR_DESK_CAPTURE; } if (S_IS_STICKY_ACROSS_DESKS(SFC(pstyle->flags))) { fw->Desk = Scr.CurrentDesk; reason->desk.reason = PR_DESK_STICKY; } else if (SUSE_START_ON_DESK(&pstyle->flags) && start_style.desk && flags.do_honor_starts_on_page) { fw->Desk = (start_style.desk > -1) ? start_style.desk - 1 : start_style.desk; reason->desk.reason = reason->desk.sod_reason; } else { if ((DO_USE_WINDOW_GROUP_HINT(fw)) && (fw->wmhints) && (fw->wmhints->flags & WindowGroupHint)&& (fw->wmhints->window_group != None) && (fw->wmhints->window_group != Scr.Root)) { /* Try to find the group leader or another window in * the group */ for (t = Scr.FvwmRoot.next; t != NULL; t = t->next) { if (FW_W(t) == fw->wmhints->window_group) { /* found the group leader, break out */ fw->Desk = t->Desk; reason->desk.reason = PR_DESK_WINDOW_GROUP_LEADER; break; } else if (t->wmhints && (t->wmhints->flags & WindowGroupHint) && (t->wmhints->window_group == fw->wmhints->window_group)) { /* found a window from the same group, * but keep looking for the group * leader */ fw->Desk = t->Desk; reason->desk.reason = PR_DESK_WINDOW_GROUP_MEMBER; } } } if ((IS_TRANSIENT(fw))&&(FW_W_TRANSIENTFOR(fw)!=None)&& (FW_W_TRANSIENTFOR(fw) != Scr.Root)) { /* Try to find the parent's desktop */ for (t = Scr.FvwmRoot.next; t != NULL; t = t->next) { if (FW_W(t) == FW_W_TRANSIENTFOR(fw)) { fw->Desk = t->Desk; reason->desk.reason = PR_DESK_TRANSIENT; break; } } } { /* migo - I am not sure this block is ever needed */ Atom atype; int aformat; unsigned long nitems, bytes_remain; unsigned char *prop; if ( XGetWindowProperty( dpy, FW_W(fw), _XA_WM_DESKTOP, 0L, 1L, True, _XA_WM_DESKTOP, &atype, &aformat, &nitems, &bytes_remain, &prop) == Success) { if (prop != NULL) { fw->Desk = *(unsigned long *)prop; XFree(prop); reason->desk.reason = PR_DESK_XPROP_XA_WM_DESKTOP; } } } } reason->desk.desk = fw->Desk; /* I think it would be good to switch to the selected desk * whenever a new window pops up, except during initialization */ /* RBW - 11/02/1998 -- I dont. */ if (!win_opts->flags.do_override_ppos && !DO_NOT_SHOW_ON_MAP(fw)) { if (Scr.CurrentDesk != fw->Desk) { reason->desk.do_switch_desk = 1; } goto_desk(fw->Desk); } /* Don't move viewport if SkipMapping, or if recapturing the window, * adjust the coordinates later. Otherwise, just switch to the target * page - it's ever so much simpler. */ if (S_IS_STICKY_ACROSS_PAGES(SFC(pstyle->flags))) { reason->page.reason = PR_PAGE_STICKY; } else if (SUSE_START_ON_DESK(&pstyle->flags)) { if (start_style.page_x != 0 && start_style.page_y != 0) { px = start_style.page_x - 1; py = start_style.page_y - 1; reason->page.reason = PR_PAGE_STYLE; px *= Scr.MyDisplayWidth; py *= Scr.MyDisplayHeight; if (!win_opts->flags.do_override_ppos && !DO_NOT_SHOW_ON_MAP(fw)) { MoveViewport(px,py,True); reason->page.do_switch_page = 1; } else if (flags.do_honor_starts_on_page) { /* Save the delta from current page */ pdeltax = Scr.Vx - px; pdeltay = Scr.Vy - py; reason->page.do_honor_starts_on_page = 1; } } } /* pick a location for the window. */ __place_get_placement_flags( &flags, fw, pstyle, win_opts, mode, reason); if (flags.do_not_use_wm_placement) { rc = __place_get_nowm_pos( exc, pstyle, attr_g, flags, screen_g, start_style, mode, win_opts, reason, pdeltax, pdeltay); } else { rc = __place_get_wm_pos( exc, pstyle, attr_g, flags, screen_g, start_style, mode, win_opts, reason, pdeltax, pdeltay); } reason->pos.x = attr_g->x; reason->pos.y = attr_g->y; return rc; } static void __place_handle_x_resources( FvwmWindow *fw, window_style *pstyle, pl_reason_t *reason) { int client_argc = 0; char **client_argv = NULL; XrmValue rm_value; /* Used to parse command line of clients for specific desk requests. */ /* Todo: check for multiple desks. */ XrmDatabase db = NULL; static XrmOptionDescRec table [] = { /* Want to accept "-workspace N" or -xrm "fvwm*desk:N" as * options to specify the desktop. I have to include dummy * options that are meaningless since Xrm seems to allow -w to * match -workspace if there would be no ambiguity. */ {"-workspacf", "*junk", XrmoptionSepArg, (caddr_t) NULL}, {"-workspace", "*desk", XrmoptionSepArg, (caddr_t) NULL}, {"-xrn", NULL, XrmoptionResArg, (caddr_t) NULL}, {"-xrm", NULL, XrmoptionResArg, (caddr_t) NULL}, }; int t1 = -1, t2 = -1, t3 = -1, spargs = 0; /* Find out if the client requested a specific desk on the command * line. * RBW - 11/20/1998 - allow a desk of -1 to work. */ if (XGetCommand(dpy, FW_W(fw), &client_argv, &client_argc) == 0) { return; } if (client_argc <= 0 || client_argv == NULL) { return; } /* Get global X resources */ MergeXResources(dpy, &db, False); /* command line takes precedence over all */ MergeCmdLineResources( &db, table, 4, client_argv[0], &client_argc, client_argv, True); /* parse the database values */ if (GetResourceString(db, "desk", client_argv[0], &rm_value) && rm_value.size != 0) { SGET_START_DESK(*pstyle) = atoi(rm_value.addr); /* RBW - 11/20/1998 */ if (SGET_START_DESK(*pstyle) > -1) { SSET_START_DESK( *pstyle, SGET_START_DESK(*pstyle) + 1); } reason->desk.sod_reason = PR_DESK_X_RESOURCE_DESK; pstyle->flags.use_start_on_desk = 1; } if (GetResourceString(db, "fvwmscreen", client_argv[0], &rm_value) && rm_value.size != 0) { SSET_START_SCREEN( *pstyle, FScreenGetScreenArgument(rm_value.addr, 'c')); reason->screen.reason = PR_SCREEN_X_RESOURCE_FVWMSCREEN; reason->screen.screen = SGET_START_SCREEN(*pstyle); pstyle->flags.use_start_on_screen = 1; } if (GetResourceString(db, "page", client_argv[0], &rm_value) && rm_value.size != 0) { spargs = sscanf( rm_value.addr, "%d %d %d", &t1, &t2, &t3); switch (spargs) { case 1: pstyle->flags.use_start_on_desk = 1; SSET_START_DESK(*pstyle, (t1 > -1) ? t1 + 1 : t1); reason->desk.sod_reason = PR_DESK_X_RESOURCE_PAGE; break; case 2: pstyle->flags.use_start_on_desk = 1; SSET_START_PAGE_X(*pstyle, (t1 > -1) ? t1 + 1 : t1); SSET_START_PAGE_Y(*pstyle, (t2 > -1) ? t2 + 1 : t2); reason->page.reason = PR_PAGE_X_RESOURCE_PAGE; reason->page.px = SGET_START_PAGE_X(*pstyle); reason->page.py = SGET_START_PAGE_Y(*pstyle); break; case 3: pstyle->flags.use_start_on_desk = 1; SSET_START_DESK(*pstyle, (t1 > -1) ? t1 + 1 : t1); reason->desk.sod_reason = PR_DESK_X_RESOURCE_PAGE; SSET_START_PAGE_X(*pstyle, (t2 > -1) ? t2 + 1 : t2); SSET_START_PAGE_Y(*pstyle, (t3 > -1) ? t3 + 1 : t3); reason->page.reason = PR_PAGE_X_RESOURCE_PAGE; reason->page.px = SGET_START_PAGE_X(*pstyle); reason->page.py = SGET_START_PAGE_Y(*pstyle); break; default: break; } } XFreeStringList(client_argv); XrmDestroyDatabase(db); return; } static void __explain_placement(FvwmWindow *fw, pl_reason_t *reason) { char explanation[2048]; char *r; char *s; char t[32]; int do_show_page; int is_placed_by_algo; *explanation = 0; s = explanation; strcat(s, "placed new window 0x%x '%s':\n"); s += strlen(s); sprintf( s, " initial size %dx%d\n", fw->g.frame.width, fw->g.frame.height); s += strlen(s); switch (reason->desk.reason) { case PR_DESK_CURRENT: r = "current desk"; break; case PR_DESK_STYLE: r = "specified by style"; break; case PR_DESK_X_RESOURCE_DESK: r = "specified by 'desk' X resource"; break; case PR_DESK_X_RESOURCE_PAGE: r = "specified by 'page' X resource"; break; case PR_DESK_CAPTURE: r = "window was (re)captured"; break; case PR_DESK_STICKY: r = "window is sticky"; break; case PR_DESK_WINDOW_GROUP_LEADER: r = "same desk as window group leader"; break; case PR_DESK_WINDOW_GROUP_MEMBER: r = "same desk as window group member"; break; case PR_DESK_TRANSIENT: r = "transient window placed on same desk as parent"; break; case PR_DESK_XPROP_XA_WM_DESKTOP: r = "specified by _XA_WM_DESKTOP property"; break; default: r = "bug"; break; } sprintf(s, " desk %d (%s)\n", reason->desk.desk, r); s += strlen(s); if (reason->desk.do_switch_desk == 1) { sprintf(s, " (switched to desk)\n"); s += strlen(s); } /* page */ do_show_page = 1; switch (reason->page.reason) { case PR_PAGE_CURRENT: do_show_page = 0; r = "current page"; break; case PR_PAGE_STYLE: r = "specified by style"; break; case PR_PAGE_X_RESOURCE_PAGE: r = "specified by 'page' X resource"; break; case PR_PAGE_IGNORE_CAPTURE: r = "window was (re)captured"; break; case PR_PAGE_IGNORE_INVALID: r = "requested page ignored because of invalid style" " combination"; break; case PR_PAGE_STICKY: do_show_page = 0; r = "current page (window is sticky)"; break; default: r = "bug"; break; } if (do_show_page == 0) { sprintf(s, " %s\n", r); } else { sprintf( s, " page %d %d (%s)\n", reason->page.px - 1, reason->page.py - 1, r); } s += strlen(s); if (reason->page.do_switch_page == 1) { sprintf(s, " (switched to page)\n"); s += strlen(s); } if (reason->page.do_ignore_starts_on_page == 1) { sprintf(s, " (possibly ignored StartsOnPage)\n"); s += strlen(s); } /* screen */ if (FScreenIsEnabled() == True || FScreenIsSLSEnabled() == True) { switch (reason->screen.reason) { case PR_SCREEN_CURRENT: r = "current screen"; break; case PR_SCREEN_STYLE: r = "specified by style"; break; case PR_SCREEN_X_RESOURCE_FVWMSCREEN: r = "specified by 'fvwmscreen' X resource"; break; case PR_SCREEN_IGNORE_CAPTURE: r = "window was (re)captured"; break; default: r = "bug"; break; } FScreenSpecToString(t, 32, reason->screen.screen); sprintf( s, " screen: %s: %d %d %dx%d (%s)\n", t, reason->screen.g.x, reason->screen.g.y, reason->screen.g.width, reason->screen.g.height, r); s += strlen(s); if (reason->screen.was_modified_by_ewmh_workingarea == 1) { sprintf( s, " (screen area modified by EWMH working" " area)\n"); s += strlen(s); } } /* position */ is_placed_by_algo = 0; switch (reason->pos.reason) { case PR_POS_NORMAL: is_placed_by_algo = 1; r = "normal placement"; break; case PR_POS_IGNORE_PPOS: is_placed_by_algo = 1; r = "ignored program specified position"; break; case PR_POS_USE_PPOS: r = "used program specified position"; break; case PR_POS_IGNORE_USPOS: is_placed_by_algo = 1; r = "ignored user specified position"; break; case PR_POS_USE_USPOS: r = "used user specified position"; break; case PR_POS_PLACE_AGAIN: is_placed_by_algo = 1; r = "by PlaceAgain command"; break; case PR_POS_CAPTURE: r = "window was (re)captured"; break; case PR_POS_USPOS_OVERRIDE_SOS: r = "StartsOnPage style overridden by application via USPos"; break; default: r = "bug"; break; } sprintf(s, " position %d %d", reason->pos.x, reason->pos.y); s += strlen(s); if (is_placed_by_algo == 1) { char *a; char *b; b = ""; switch (reason->pos.algo) { case PLACE_POSITION: a = "Position args: "; b = reason->pos.pl_position_string; break; case PLACE_TILEMANUAL: a = "TileManual"; break; case PLACE_MANUAL: case PLACE_MANUAL_B: a = "Manual"; break; case PLACE_MINOVERLAPPERCENT: a = "MinOverlapPercent"; break; case PLACE_TILECASCADE: a = "TileCascade"; break; case PLACE_CASCADE: case PLACE_CASCADE_B: a = "Cascade"; break; case PLACE_MINOVERLAP: a = "MinOverlap"; break; default: a = "bug"; break; } sprintf(s, ", placed by fvwm (%s)\n", r); s += strlen(s); sprintf(s, " placement method: %s%s\n", a, b); s += strlen(s); if (reason->pos.do_not_manual_icon_placement == 1) { sprintf(s, " (icon not placed manually)\n"); s += strlen(s); } if (reason->pos.is_pl_position_string_invalid == 1) { sprintf(s, " (invalid position string)\n"); s += strlen(s); } if (reason->pos.has_tile_failed == 1) { sprintf(s, " (tile placement failed)\n"); s += strlen(s); } if (reason->pos.has_manual_failed == 1) { sprintf(s, " (manual placement failed)\n"); s += strlen(s); } if (reason->pos.has_placement_failed == 1) { sprintf(s, " (placement failed default pos 0 0)\n"); s += strlen(s); } } else { sprintf(s, " (%s)\n", r); s += strlen(s); } if (reason->pos.do_adjust_off_screen == 1) { sprintf(s, " (adjusted to force window on screen)\n"); s += strlen(s); } if (reason->pos.do_adjust_off_page == 1) { sprintf(s, " (adjusted to force window on page)\n"); s += strlen(s); } fvwm_msg( INFO, "__explain_placement", explanation, (int)FW_W(fw), fw->name.name); return; } /* ---------------------------- interface functions ------------------------ */ Bool setup_window_placement( FvwmWindow *fw, window_style *pstyle, rectangle *attr_g, initial_window_options_t *win_opts, placement_mode_t mode) { int rc; const exec_context_t *exc; exec_context_changes_t ecc; pl_reason_t reason; pl_start_style_t start_style; memset(&reason, 0, sizeof(reason)); if (pstyle->flags.use_start_on_desk) { reason.desk.sod_reason = PR_DESK_STYLE; reason.page.px = SGET_START_PAGE_X(*pstyle); reason.page.py = SGET_START_PAGE_Y(*pstyle); } if (pstyle->flags.use_start_on_screen) { reason.screen.reason = PR_SCREEN_STYLE; reason.screen.screen = SGET_START_SCREEN(*pstyle); } __place_handle_x_resources(fw, pstyle, &reason); if (pstyle->flags.do_start_iconic) { win_opts->initial_state = IconicState; } ecc.type = EXCT_NULL; ecc.w.fw = fw; exc = exc_create_context(&ecc, ECC_TYPE | ECC_FW); start_style.desk = SGET_START_DESK(*pstyle); start_style.page_x = SGET_START_PAGE_X(*pstyle); start_style.page_y = SGET_START_PAGE_Y(*pstyle); start_style.screen = SGET_START_SCREEN(*pstyle); rc = __place_window( exc, pstyle, attr_g, start_style, mode, win_opts, &reason); exc_destroy_context(exc); if (Scr.bo.do_explain_window_placement == 1) { __explain_placement(fw, &reason); } return (rc == 0) ? False : True; } /* ---------------------------- builtin commands --------------------------- */ void CMD_PlaceAgain(F_CMD_ARGS) { int old_desk; char *token; float noMovement[1] = {1.0}; float *ppctMovement = noMovement; rectangle attr_g; XWindowAttributes attr; Bool do_move_animated = False; Bool do_place_icon = False; FvwmWindow * const fw = exc->w.fw; if (!XGetWindowAttributes(dpy, FW_W(fw), &attr)) { return; } while ((token = PeekToken(action, &action)) != NULL) { if (StrEquals("Anim", token)) { ppctMovement = NULL; do_move_animated = True; } else if (StrEquals("icon", token)) { do_place_icon = True; } } old_desk = fw->Desk; if (IS_ICONIFIED(fw) && !do_place_icon) { return; } if (IS_ICONIFIED(fw) && do_place_icon) { rectangle new_g; rectangle old_g; if (IS_ICON_SUPPRESSED(fw)) { return; } fw->Desk = Scr.CurrentDesk; get_icon_geometry(fw, &old_g); SET_ICON_MOVED(fw, 0); AutoPlaceIcon(fw, NULL, False); get_icon_geometry(fw, &new_g); __move_icon( fw, new_g.x, new_g.y, old_g.x, old_g.y, do_move_animated, False); } else { window_style style; initial_window_options_t win_opts; memset(&win_opts, 0, sizeof(win_opts)); lookup_style(fw, &style); attr_g.x = attr.x; attr_g.y = attr.y; attr_g.width = attr.width; attr_g.height = attr.height; setup_window_placement( exc->w.fw, &style, &attr_g, &win_opts, PLACE_AGAIN); AnimatedMoveFvwmWindow( fw, FW_W_FRAME(fw), -1, -1, attr_g.x, attr_g.y, False, -1, ppctMovement); } if (fw->Desk != old_desk) { int new_desk = fw->Desk; fw->Desk = old_desk; do_move_window_to_desk(fw, new_desk); } return; }