r_layout.c revision b18c2d1e
1/* 2 * Copyright notice... 3 */ 4 5#include "ctwm.h" 6 7#include <stdlib.h> 8#include <stdio.h> 9#include <string.h> 10 11#include "r_layout.h" 12#include "r_area_list.h" 13#include "r_area.h" 14#include "util.h" 15 16 17/* 18 * Prototype internal funcs 19 */ 20static void _RLayoutFreeNames(RLayout *self); 21static RAreaList *_RLayoutRecenterVertically(const RLayout *self, 22 const RArea *far_area); 23static RAreaList *_RLayoutRecenterHorizontally(const RLayout *self, 24 const RArea *far_area); 25static RAreaList *_RLayoutVerticalIntersect(const RLayout *self, 26 const RArea *area); 27static RAreaList *_RLayoutHorizontalIntersect(const RLayout *self, 28 const RArea *area); 29 30/* Foreach() callbacks used in various lookups */ 31static bool _findMonitorByXY(const RArea *cur, void *vdata); 32static bool _findMonitorBottomEdge(const RArea *cur, void *vdata); 33static bool _findMonitorTopEdge(const RArea *cur, void *vdata); 34static bool _findMonitorLeftEdge(const RArea *cur, void *vdata); 35static bool _findMonitorRightEdge(const RArea *cur, void *vdata); 36 37 38 39 40/************************ 41 * 42 * First, some funcs for creating and destroying RLayout's in various 43 * ways. 44 * 45 ************************/ 46 47 48/** 49 * Create an RLayout for a given set of monitors. 50 * 51 * This stashes up the list of monitors, and precalculates the 52 * horizontal/vertical stripes that compose it. 53 */ 54RLayout * 55RLayoutNew(RAreaList *monitors) 56{ 57 RLayout *layout = malloc(sizeof(RLayout)); 58 if(layout == NULL) { 59 abort(); 60 } 61 62 layout->monitors = monitors; 63 layout->horiz = RAreaListHorizontalUnion(monitors); 64 layout->vert = RAreaListVerticalUnion(monitors); 65 layout->names = NULL; 66 67 return layout; 68} 69 70 71/** 72 * Create a copy of an RLayout with given amounts cropped off the sides. 73 * This is used anywhere we need to pretend our display area is smaller 74 * than it actually is (e.g., via the BorderBottom/Top/Left/Right config 75 * params) 76 */ 77RLayout * 78RLayoutCopyCropped(const RLayout *self, int left_margin, int right_margin, 79 int top_margin, int bottom_margin) 80{ 81 RAreaList *cropped_monitors = RAreaListCopyCropped(self->monitors, 82 left_margin, right_margin, 83 top_margin, bottom_margin); 84 if(cropped_monitors == NULL) { 85 return NULL; // nothing to crop, same layout as passed 86 } 87 88 return RLayoutNew(cropped_monitors); 89} 90 91 92/** 93 * Clean up and free any RLayout.names there might be in an RLayout. 94 */ 95static void 96_RLayoutFreeNames(RLayout *self) 97{ 98 if(self == NULL) { 99 return; 100 } 101 if(self->names != NULL) { 102 free(self->names); 103 self->names = NULL; 104 } 105} 106 107 108/** 109 * Clean up and free an RLayout. 110 */ 111void 112RLayoutFree(RLayout *self) 113{ 114 if(self == NULL) { 115 return; 116 } 117 118 RAreaListFree(self->monitors); 119 RAreaListFree(self->horiz); 120 RAreaListFree(self->vert); 121 _RLayoutFreeNames(self); 122 free(self); 123} 124 125 126/** 127 * Set the names for our monitors in an RLayout. This is only used for 128 * the RLayout that describes our complete monitor layout, which fills in 129 * the RANDR names for each output. 130 */ 131RLayout * 132RLayoutSetMonitorsNames(RLayout *self, char **names) 133{ 134 _RLayoutFreeNames(self); 135 self->names = names; 136 return self; 137} 138 139 140 141/************************ 142 * 143 * Next, a few util funcs for dealing with RArea's that are outside our 144 * RLayout, but we want to find the nearest way to move them inside, then 145 * return a list of which RArea's they'd be intersecting with. 146 * 147 ************************/ 148 149 150/** 151 * Given an RArea that doesn't reside in any of the areas in our RLayout, 152 * create a list of maximally-tall RArea slices out of our layout where 153 * it would wind up if we brought it onto the nearest screen edge. This 154 * yields a RAreaList as tall as the slice[es] the window would touch if 155 * we moved it in. 156 * 157 * If we had the move the window horizontally (it was off-screen to the 158 * right or left), it results in a 1-pixel-wide slice of the right- or 159 * left-most self->vert. 160 * 161 * If we had to move it vertically (it was off to the top or bottom), it 162 * winds up being whatever horizontal intersection with self->vert would 163 * result from the window's x and width, with the full height of the 164 * involved slices. 165 * 166 * This is the vertical-stripe-returning counterpart of 167 * _RLayoutRecenterHorizontally(). 168 * 169 * This is called only by \_RLayoutVerticalIntersect() when given an RArea 170 * that doesn't already intersect the RLayout. Will probably not tell 171 * you something useful if given a far_area that already _does_ intersect 172 * self. 173 * 174 * \param self Our current monitor layout 175 * \param far_area The area to act on 176 */ 177static RAreaList * 178_RLayoutRecenterVertically(const RLayout *self, const RArea *far_area) 179{ 180 RArea big = RAreaListBigArea(self->monitors), tmp; 181 182 // We assume far_area is outside of self. So it's in one of the 183 // three labelled areas: 184 // 185 // |_V_| ___ tmp.top 186 // | | | 187 // L| |R | 188 // |___| ___ tmp.bottom 189 // | V | 190 // 191 // So we'll create an RArea that's the y and height of big (a giant 192 // rectangle covering all the monitors), so that its intersection 193 // with self->vert will always cover a full vertical stripe. Then 194 // we'll set its x/width so that it's shifted to be at least 195 // minimally inside big somehow. 196 197 // Where did it wind up? 198 if((far_area->x >= big.x && far_area->x <= RAreaX2(&big)) 199 || (RAreaX2(far_area) >= big.x && RAreaX2(far_area) <= RAreaX2(&big))) { 200 // In one of the V areas. It's already in a horizontal position 201 // that would fit, so we just keep x/width. 202 tmp = RAreaNew(far_area->x, big.y, 203 far_area->width, big.height); 204 } 205 else if(RAreaX2(far_area) < big.x) { 206 // Off to the left side in L, so move it over just far enough 207 // that 1 pixel of it protrudes into the left side. 208 tmp = RAreaNew(big.x - far_area->width + 1, big.y, 209 far_area->width, big.height); 210 } 211 else { 212 // Off to the right side in R, so move it over just far enough 213 // that 1 pixel of it protrudes into the right side. 214 tmp = RAreaNew(RAreaX2(&big), big.y, 215 far_area->width, big.height); 216 } 217 218 // Then intersect that (full height, at least 1 pixel horizontally 219 // somewhere) with our collection of vertical stripes, to yield an 220 // answer. If the window was off to the left or right, this will 221 // yield a 1-pixel-wide slice of either the left- or right-most 222 // ->vert of our layout. If it were off the top of bottom, though, 223 // it'll yield some slice of 1 (or more) of our ->vert's, as wide as 224 // the window itself was. 225 return RAreaListIntersect(self->vert, &tmp); 226 227 // n.b.; _RLayoutRecenterHorizontally() is the counterpart to this 228 // with horizontal slices. The comments in the two have been written 229 // independently with somewhat different explanatory styles, so if 230 // the description here was confusing, try reading the other one and 231 // transposing. 232} 233 234 235/** 236 * Given an RArea that doesn't reside in any of the areas in our RLayout, 237 * create a list of maximally-wide RArea slices out of our layout where 238 * it would wind up if we brought it onto the nearest screen edge. This 239 * yields a RAreaList as wide as the slice[es] the window would touch if 240 * we moved it in. 241 * 242 * If we had the move the window vertically (it was off-screen to the top 243 * or bottom), it results in a 1-pixel-wide slice of the top- or 244 * bottom-most self->horiz. 245 * 246 * If we had to move it horizontally (it was off to the left or right), 247 * it winds up being whatever vertical intersection with self->horiz 248 * would result from the window's y and height, with the full width of 249 * the involved slices. 250 * 251 * This is the horizontal-stripe-returning counterpart of 252 * _RLayoutRecenterVertically(). 253 * 254 * This is called only by \_RLayoutVerticalIntersect() when given an RArea 255 * that doesn't already intersect the RLayout. Will probably not tell 256 * you something useful if given a far_area that already _does_ intersect 257 * self. 258 * 259 * \param self Our current monitor layout 260 * \param far_area The area to act on 261 */ 262static RAreaList * 263_RLayoutRecenterHorizontally(const RLayout *self, const RArea *far_area) 264{ 265 RArea big = RAreaListBigArea(self->monitors), tmp; 266 267 // far_area is outside self, so it's in one of the 3 labelled areas: 268 // 269 // ___T___ 270 // | | 271 // H| |H 272 // _|___|_ 273 // B 274 // 275 // We create an RArea that's the x and width of big, so it always 276 // covers the entire width of any member of ->horiz. Then we move 277 // the far_area in to the nearest edge to figure the y/height to set. 278 279 if((far_area->y >= big.y && far_area->y <= RAreaY2(&big)) 280 || (RAreaY2(far_area) >= big.y && RAreaY2(far_area) <= RAreaY2(&big))) { 281 // In one of the H areas. Already in a valid place vertically, 282 // so make a horizontal strip that position/tall. 283 tmp = RAreaNew(big.x, far_area->y, 284 big.width, far_area->height); 285 } 286 else if(RAreaY2(far_area) < big.y) { 287 // Off the top (T); move it down just far enough that it's bottom 288 // protrudes 1 pixel into the top. 289 tmp = RAreaNew(big.x, big.y - far_area->height + 1, 290 big.width, far_area->height); 291 } 292 else { 293 // Off the bottom (B); move it up just enough that it's top 294 // protrudes 1 pixel into the bottom. 295 tmp = RAreaNew(big.x, RAreaY2(&big), 296 big.width, far_area->height); 297 } 298 299 // And intersect that RArea with self->horiz. This results in a 300 // full-width overlap with 1 pixel at the bottom of the bottom-most, 301 // 1 pixel at the top of the top-most, or 1..(far_area->height) 302 // overlap somewhere. In that last case (far_area was in H), the 303 // intersection may yield multiple areas. 304 return RAreaListIntersect(self->horiz, &tmp); 305 306 // n.b.; _RLayoutRecenterVertically() is the counterpart to this with 307 // vertical slices. The comments in the two have been written 308 // independently with somewhat different explanatory styles, so if 309 // the description here was confusing, try reading the other one and 310 // transposing. 311} 312 313 314 315/************************ 316 * 317 * Some wrappers called when we need to Insersect an RArea with our 318 * RLayout, but also handle the case (using the above funcs) when the 319 * RArea doesn't Intersect our layout by finding the nearest border we 320 * could shuffle it over. 321 * 322 ************************/ 323 324 325/** 326 * Find which vertical regions of our monitor layout a given RArea (often 327 * a window) is in. If it's completely off the screen, we move it until 328 * it's just over the nearest edge, and return the vertical stripe(s) it 329 * would be in then. 330 * 331 * This function is used only by RLayoutFindTopBottomEdges() 332 */ 333static RAreaList * 334_RLayoutVerticalIntersect(const RLayout *self, const RArea *area) 335{ 336 RAreaList *mit = RAreaListIntersect(self->vert, area); 337 338 if(mit->len == 0) { 339 // Not on screen. Move it to just over the nearest edge so it 340 // is, and give the slices it's in then. 341 RAreaListFree(mit); 342 mit = _RLayoutRecenterVertically(self, area); 343 } 344 return mit; 345} 346 347 348/** 349 * Find which horizontal regions of our monitor layout a given RArea 350 * (often a window) is in. If it's completely off the screen, we move it 351 * until it's just over the nearest edge, and return the horizontal 352 * stripe(s) it would be in then. 353 * 354 * This function is used only by RLayoutFindLeftRightEdges() 355 */ 356static RAreaList * 357_RLayoutHorizontalIntersect(const RLayout *self, const RArea *area) 358{ 359 RAreaList *mit = RAreaListIntersect(self->horiz, area); 360 361 if(mit->len == 0) { 362 // Not on screen. Move it to just over the nearest edge so it 363 // is, and give the slices it's in then. 364 RAreaListFree(mit); 365 mit = _RLayoutRecenterHorizontally(self, area); 366 } 367 368 return mit; 369} 370 371 372 373/************************ 374 * 375 * Some funcs using the above (layers of) utils to find info about which 376 * stripes of the RLayout an Area appears in. These are used mostly as 377 * backend utils for figuring various f.*zoom's. 378 * 379 ************************/ 380 381 382/** 383 * Figure the position (or nearest practical position) of an area in our 384 * screen layout, and return info about the bottom/top stripes it fits 385 * into. 386 * 387 * Note that the return values (params) are slightly counterintuitive; 388 * top tells you where the top of the lowest stripe that area intersects 389 * with is, and bottom tells you the bottom of the highest. 390 * 391 * This is used as a backend piece of various calculations trying to be 392 * sure something winds up on-screen and when figuring out how to zoom 393 * it. 394 * 395 * \param[in] self The monitor layout to work from 396 * \param[in] area The area to be fit into the monitors 397 * \param[out] top The top of the lowest stripe area fits into. 398 * \param[out] bottom The bottom of the highest stripe area fits into. 399 */ 400void 401RLayoutFindTopBottomEdges(const RLayout *self, const RArea *area, int *top, 402 int *bottom) 403{ 404 RAreaList *mit = _RLayoutVerticalIntersect(self, area); 405 406 if(top != NULL) { 407 *top = RAreaListMaxY(mit); 408 } 409 410 if(bottom != NULL) { 411 *bottom = RAreaListMinY2(mit); 412 } 413 414 RAreaListFree(mit); 415} 416 417 418/** 419 * Find the bottom of the top stripe of self that area fits into. A 420 * shortcut to get only the second return value of 421 * RLayoutFindTopBottomEdges(). 422 */ 423int 424RLayoutFindBottomEdge(const RLayout *self, const RArea *area) 425{ 426 int min_y2; 427 RLayoutFindTopBottomEdges(self, area, NULL, &min_y2); 428 return min_y2; 429} 430 431 432/** 433 * Find the top of the bottom stripe of self that area fits into. A 434 * shortcut to get only the first return value of 435 * RLayoutFindTopBottomEdges(). 436 */ 437int 438RLayoutFindTopEdge(const RLayout *self, const RArea *area) 439{ 440 int max_y; 441 RLayoutFindTopBottomEdges(self, area, &max_y, NULL); 442 return max_y; 443} 444 445 446/** 447 * Figure the position (or nearest practical position) of an area in our 448 * screen layout, and return info about the left/rightmost stripes it fits 449 * into. 450 * 451 * As with RLayoutFindTopBottomEdges(), the return values (params) are 452 * slightly counterintuitive. left tells you where the left-side of the 453 * right-most stripe that area intersects with is, and right tells you 454 * the right side of the left-most. 455 * 456 * This is used as a backend piece of various calculations trying to be 457 * sure something winds up on-screen and when figuring out how to zoom 458 * it. 459 * 460 * \param[in] self The monitor layout to work from 461 * \param[in] area The area to be fit into the monitors 462 * \param[out] left The left edge of the right-most stripe area fits into. 463 * \param[out] right The right edge of the left-most stripe area fits into. 464 */ 465void 466RLayoutFindLeftRightEdges(const RLayout *self, const RArea *area, int *left, 467 int *right) 468{ 469 RAreaList *mit = _RLayoutHorizontalIntersect(self, area); 470 471 if(left != NULL) { 472 *left = RAreaListMaxX(mit); 473 } 474 475 if(right != NULL) { 476 *right = RAreaListMinX2(mit); 477 } 478 479 RAreaListFree(mit); 480} 481 482 483/** 484 * Find the left edge of the right-most stripe of self that area fits 485 * into. A shortcut to get only the first return value of 486 * RLayoutFindLeftRightEdges(). 487 */ 488int 489RLayoutFindLeftEdge(const RLayout *self, const RArea *area) 490{ 491 int max_x; 492 RLayoutFindLeftRightEdges(self, area, &max_x, NULL); 493 return max_x; 494} 495 496 497/** 498 * Find the right edge of the left-most stripe of self that area fits 499 * into. A shortcut to get only the second return value of 500 * RLayoutFindLeftRightEdges(). 501 */ 502int 503RLayoutFindRightEdge(const RLayout *self, const RArea *area) 504{ 505 int min_x2; 506 RLayoutFindLeftRightEdges(self, area, NULL, &min_x2); 507 return min_x2; 508} 509 510 511 512/************************ 513 * 514 * Lookups to find areas in an RLayout by various means. 515 * 516 ************************/ 517 518 519/// Internal structure for callback in RLayoutGetAreaAtXY(). 520struct monitor_finder_xy { 521 const RArea *area; 522 int x, y; 523}; 524 525/// Callback util for RLayoutGetAreaAtXY(). 526static bool 527_findMonitorByXY(const RArea *cur, void *vdata) 528{ 529 struct monitor_finder_xy *data = (struct monitor_finder_xy *)vdata; 530 531 if(RAreaContainsXY(cur, data->x, data->y)) { 532 data->area = cur; 533 return true; 534 } 535 return false; 536} 537 538/** 539 * Find the RArea in a RLayout that a given coordinate falls into. In 540 * practice, the RArea's in self are the monitors of the desktop, so this 541 * answers "Which monitor is this position on?" 542 */ 543RArea 544RLayoutGetAreaAtXY(const RLayout *self, int x, int y) 545{ 546 struct monitor_finder_xy data = { .area = NULL, .x = x, .y = y }; 547 548 RAreaListForeach(self->monitors, _findMonitorByXY, &data); 549 550 return data.area == NULL ? self->monitors->areas[0] : *data.area; 551} 552 553 554/** 555 * Return the index'th RArea in an RLayout, or RAreaInvalid() with an out 556 * of range index. 557 */ 558RArea 559RLayoutGetAreaIndex(const RLayout *self, int index) 560{ 561 if(index >= self->monitors->len || index < 0) { 562 return RAreaInvalid(); 563 } 564 565 return self->monitors->areas[index]; 566} 567 568 569/** 570 * Return the RArea in self with the name given by the string of length 571 * len at name. This is only used in RLayoutXParseGeometry() to parse a 572 * fragment of a larger string, hence the need for len. It's used to 573 * find the monitor with a given name (RANDR output name). 574 */ 575RArea 576RLayoutGetAreaByName(const RLayout *self, const char *name, int len) 577{ 578 if(self->names != NULL) { 579 if(len < 0) { 580 len = strlen(name); 581 } 582 583 for(int i = 0; i < self->monitors->len 584 && self->names[i] != NULL; i++) { 585 if(strncmp(self->names[i], name, len) == 0) { 586 return self->monitors->areas[i]; 587 } 588 } 589 } 590 591 return RAreaInvalid(); 592} 593 594 595 596/************************ 597 * 598 * Now some utils for finding various edges of the monitors a given RArea 599 * intersects with. 600 * 601 ************************/ 602 603 604/// Internal struct for use in FindMonitor*Edge() callbacks. 605struct monitor_edge_finder { 606 const RArea *area; 607 union { 608 int max_x; 609 int max_y; 610 int min_x2; 611 int min_y2; 612 } u; 613 bool found; 614}; 615 616/// Callback util for RLayoutFindMonitorBottomEdge() 617static bool 618_findMonitorBottomEdge(const RArea *cur, void *vdata) 619{ 620 struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata; 621 622 // Does the area we're looking for intersect this piece of the 623 // RLayout, is the bottom of the area shown on it, and is the bottom 624 // of this piece the highest we've yet found that satisfies those 625 // conditions? 626 if(RAreaIsIntersect(cur, data->area) 627 && RAreaY2(cur) > RAreaY2(data->area) 628 && (!data->found || RAreaY2(cur) < data->u.min_y2)) { 629 data->u.min_y2 = RAreaY2(cur); 630 data->found = true; 631 } 632 return false; 633} 634 635/** 636 * Find the bottom edge of the top-most monitor that contains the most of 637 * a given RArea. Generally, the area would be a window. 638 * 639 * That is, we find the monitor whose bottom is the highest up, but that 640 * still shows the bottom edge of the window, and return that monitor's 641 * bottom. If the bottom of the window is off all the monitors, that's 642 * just the highest-ending monitor that contains the window. 643 */ 644int 645RLayoutFindMonitorBottomEdge(const RLayout *self, const RArea *area) 646{ 647 struct monitor_edge_finder data = { .area = area }; 648 649 RAreaListForeach(self->monitors, _findMonitorBottomEdge, &data); 650 651 return data.found ? data.u.min_y2 : RLayoutFindBottomEdge(self, area); 652} 653 654 655/// Callback util for RLayoutFindMonitorTopEdge() 656static bool 657_findMonitorTopEdge(const RArea *cur, void *vdata) 658{ 659 struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata; 660 661 // Does the area we're looking for intersect this piece of the 662 // RLayout, is the top of the area shown on it, and is the top 663 // of this piece the lowest we've yet found that satisfies those 664 // conditions? 665 if(RAreaIsIntersect(cur, data->area) 666 && cur->y < data->area->y 667 && (!data->found || cur->y > data->u.max_y)) { 668 data->u.max_y = cur->y; 669 data->found = true; 670 } 671 return false; 672} 673 674/** 675 * Find the top edge of the bottom-most monitor that contains the most of 676 * a given RArea. Generally, the area would be a window. 677 * 678 * That is, we find the monitor whose top is the lowest down, but that 679 * still shows the top edge of the window, and return that monitor's top. 680 * If the top of the window is off all the monitors, that's just the 681 * lowest-ending monitor that contains part of the window. 682 */ 683int 684RLayoutFindMonitorTopEdge(const RLayout *self, const RArea *area) 685{ 686 struct monitor_edge_finder data = { .area = area }; 687 688 RAreaListForeach(self->monitors, _findMonitorTopEdge, &data); 689 690 return data.found ? data.u.max_y : RLayoutFindTopEdge(self, area); 691} 692 693 694/// Callback util for RLayoutFindMonitorLeftEdge() 695static bool 696_findMonitorLeftEdge(const RArea *cur, void *vdata) 697{ 698 struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata; 699 700 // Does the area we're looking for intersect this piece of the 701 // RLayout, is the left of the area shown on it, and is the left of 702 // this piece the right-most we've yet found that satisfies those 703 // conditions? 704 if(RAreaIsIntersect(cur, data->area) 705 && cur->x < data->area->x 706 && (!data->found || cur->x > data->u.max_x)) { 707 data->u.max_x = cur->x; 708 data->found = true; 709 } 710 return false; 711} 712 713/** 714 * Find the left edge of the right-most monitor that contains the most of 715 * a given RArea. Generally, the area would be a window. 716 * 717 * That is, we find the monitor whose left is the furthest right, but 718 * that still shows the left edge of the window, and return that 719 * monitor's left. If the left edge of the window is off all the 720 * monitors, that's just the right-most-ending monitor that contains the 721 * window. 722 */ 723int 724RLayoutFindMonitorLeftEdge(const RLayout *self, const RArea *area) 725{ 726 struct monitor_edge_finder data = { .area = area }; 727 728 RAreaListForeach(self->monitors, _findMonitorLeftEdge, &data); 729 730 return data.found ? data.u.max_x : RLayoutFindLeftEdge(self, area); 731} 732 733 734/// Callback util for RLayoutFindMonitorRightEdge() 735static bool 736_findMonitorRightEdge(const RArea *cur, void *vdata) 737{ 738 struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata; 739 740 // Does the area we're looking for intersect this piece of the 741 // RLayout, is the right of the area shown on it, and is the right of 742 // this piece the left-most we've yet found that satisfies those 743 // conditions? 744 if(RAreaIsIntersect(cur, data->area) 745 && RAreaX2(cur) > RAreaX2(data->area) 746 && (!data->found || RAreaX2(cur) < data->u.min_x2)) { 747 data->u.min_x2 = RAreaX2(cur); 748 data->found = true; 749 } 750 return false; 751} 752 753/** 754 * Find the right edge of the left-most monitor that contains the most of 755 * a given RArea. Generally, the area would be a window. 756 * 757 * That is, we find the monitor whose right is the furthest left, but 758 * that still shows the right edge of the window, and return that 759 * monitor's right. If the right edge of the window is off all the 760 * monitors, that's just the left-most-ending monitor that contains the 761 * window. 762 */ 763int 764RLayoutFindMonitorRightEdge(const RLayout *self, const RArea *area) 765{ 766 struct monitor_edge_finder data = { .area = area }; 767 768 RAreaListForeach(self->monitors, _findMonitorRightEdge, &data); 769 770 return data.found ? data.u.min_x2 : RLayoutFindRightEdge(self, area); 771} 772 773 774 775/************************ 776 * 777 * Backend funcs called by the f.*zoom handlers to figure the area we 778 * should zoom into. 779 * 780 ************************/ 781 782 783/** 784 * Figure the best way to stretch an area across the full horizontal 785 * width of an RLayout. This is the backend for the f.xhorizoom ctwm 786 * function, zooming a window to the full width of all monitors. 787 */ 788RArea 789RLayoutFullHoriz(const RLayout *self, const RArea *area) 790{ 791 int max_x, min_x2; 792 793 RLayoutFindLeftRightEdges(self, area, &max_x, &min_x2); 794 795 return RAreaNew(max_x, area->y, min_x2 - max_x + 1, area->height); 796 797 /** 798 * This yields an area: 799 * ~~~ 800 * TL W 801 * *-----* 802 * | | 803 * H| | 804 * | | 805 * *-----* 806 * ~~~ 807 * 808 * The precise construction of the area can be tricky. 809 * 810 * In the simplest case, the area is entirely in one horizontal 811 * stripe to start with. In that case, max_x is the left side of 812 * that box, min_x2 is the right side, so the resulting area starts 813 * at (left margin, area y), with the height of y and the width of 814 * the whole stripe. Easy. 815 * 816 * When it spans multiple, it's more convoluted. Let's consider an 817 * example layout (of horizontal stripes, so that top stripe may be 818 * across 2 monitors) to make it a little clearer: 819 * 820 * ~~~ 821 * *--------------------------* 822 * | |......2.....| 823 * | | <-----. 824 * | 1 ========= | . 825 * *-------------*-=========--*-* >-- 2 horiz stripes 826 * | ========= | ' 827 * | / | <---' 828 * area --+-' | 829 * *--------------* 830 * ~~~ 831 * 832 * So in this case, we're trying to stretch area out as far 833 * horizontal as it can go, crossing monitors if possible. 834 * 835 * So, the top-left corner of our box (TL) has the X coordinate of 836 * the right-most strip we started with (the lower), and the Y 837 * coordinate of the top of the area, yielding point (1) above (not 838 * the asterisk; specifically where (1) sits). 839 * 840 * The width W is the difference between the right of the 841 * left-most-ending (in this case, the top) stripe, and the left of 842 * the right-most-starting (the bottom) (plus 1 because math). 843 * That's the width of the intersecting horizontal area (2) above. 844 * 845 * And the height H is just the height of the original area. And so, 846 * our resulting area is the height of that original area (in ='s), 847 * and stretched to the left and right until it runs into one or the 848 * other monitor edge (1 space to the left, 2 to the right, in our 849 * diagram). 850 */ 851} 852 853 854/** 855 * Figure the best way to stretch an area across the full vertical height 856 * of an RLayout. This is the backend for the f.xzoom ctwm function, 857 * zooming a window to the full height of all monitors. 858 */ 859RArea 860RLayoutFullVert(const RLayout *self, const RArea *area) 861{ 862 int max_y, min_y2; 863 864 RLayoutFindTopBottomEdges(self, area, &max_y, &min_y2); 865 866 return RAreaNew(area->x, max_y, area->width, min_y2 - max_y + 1); 867 868 // X-ref long comment above in RLayoutFullHoriz() for worked example. 869 // This is just rotated 90 degrees, but the logic works out about the 870 // same. 871} 872 873 874/** 875 * Figure the best way to stretch an area across the largest horizontal 876 * and vertical space it can from its current position. Essentially, 877 * stretch it in all directions until it hits the edge of our available 878 * space. 879 * 880 * This is the backend for the f.xfullzoom function. 881 */ 882RArea 883RLayoutFull(const RLayout *self, const RArea *area) 884{ 885 RArea full_horiz, full_vert, full1, full2; 886 887 // Get the boxes for full horizontal and vertical zooms, using the 888 // above functions. 889 full_horiz = RLayoutFullHoriz(self, area); 890 full_vert = RLayoutFullVert(self, area); 891 892 // Now stretch each of those in the other direction... 893 full1 = RLayoutFullVert(self, &full_horiz); 894 full2 = RLayoutFullHoriz(self, &full_vert); 895 896 // And return whichever was bigger. 897 return RAreaArea(&full1) > RAreaArea(&full2) ? full1 : full2; 898} 899 900 901 902/** 903 * Figure the best way to stretch an area horizontally without crossing 904 * monitors. 905 * 906 * This is the backend for the f.horizoom ctwm function. 907 */ 908RArea 909RLayoutFullHoriz1(const RLayout *self, const RArea *area) 910{ 911 // Cheat by using RLayoutFull1() to find the RArea for the monitor 912 // it's most on. 913 RArea target = RLayoutFull1(self, area); 914 int max_y, min_y2; 915 916 // We're stretching horizontally, so the x and width of target (that 917 // monitor) are already right. But we have to figure the y and 918 // height... 919 920 // Generally, the y is the window's original y, unless we had to move 921 // it down to get onto the target monitor. XXX Wait, what if we 922 // moved it _up_? 923 max_y = max(area->y, target.y); 924 target.y = max_y; 925 926 // The bottom would be the bottom of the area, clipped to the bottom 927 // of the monitor. So the height is the diff. 928 min_y2 = min(RAreaY2(area), RAreaY2(&target)); 929 target.height = min_y2 - max_y + 1; 930 931 return target; 932} 933 934 935/** 936 * Figure the best way to stretch an area vertically without crossing 937 * monitors. 938 * 939 * This is the backend for the f.zoom ctwm function. 940 */ 941RArea 942RLayoutFullVert1(const RLayout *self, const RArea *area) 943{ 944 // Let RLayoutFull1() find the right monitor. 945 RArea target = RLayoutFull1(self, area); 946 int max_x, min_x2; 947 948 // Stretching vertically, so the y/height of the monitor are already 949 // right. 950 951 // x is where the window was, unless we had to move it right to get 952 // onto the monitor. XXX What if we moved it left? 953 max_x = max(area->x, target.x); 954 target.x = max_x; 955 956 // Right side is where it was, unless we have to clip to the monitor. 957 min_x2 = min(RAreaX2(area), RAreaX2(&target)); 958 target.width = min_x2 - max_x + 1; 959 960 return target; 961} 962 963 964/** 965 * Figure the best way to resize an area to fill one monitor. 966 * 967 * This is the backend for the f.fullzoom ctwm function. 968 * 969 * \param self Monitor layout 970 * \param area Area (window) to zoom out 971 */ 972RArea 973RLayoutFull1(const RLayout *self, const RArea *area) 974{ 975 RArea target; 976 RAreaList *mit = RAreaListIntersect(self->monitors, area); 977 // Start with a list of all the monitors the window is on now. 978 979 if(mit->len == 0) { 980 // Not on any screens. Find the "nearest" place it would wind 981 // up. 982 RAreaListFree(mit); 983 mit = _RLayoutRecenterHorizontally(self, area); 984 } 985 986 // Of the monitors it's on, find the one that it's "most" on, and 987 // return the RArea of it. 988 target = RAreaListBestTarget(mit, area); 989 RAreaListFree(mit); 990 return target; 991} 992 993 994 995/************************ 996 * 997 * Finally, some small misc utils. 998 * 999 ************************/ 1000 1001 1002/** 1003 * Generate maximal spanning RArea. 1004 * 1005 * This is a trivial wrapper of RAreaListBigArea() to hide knowledge of 1006 * RLayout internals. Currently only used once; maybe should just be 1007 * deref'd there... 1008 */ 1009RArea 1010RLayoutBigArea(const RLayout *self) 1011{ 1012 return RAreaListBigArea(self->monitors); 1013} 1014 1015 1016/** 1017 * How many monitors does a given RLayout contain? 1018 */ 1019int 1020RLayoutNumMonitors(const RLayout *self) 1021{ 1022 return self->monitors->len; 1023} 1024 1025 1026/** 1027 * Pretty-print an RLayout. 1028 * 1029 * Used for dev/debug. 1030 */ 1031void 1032RLayoutPrint(const RLayout *self) 1033{ 1034 fprintf(stderr, "[monitors="); 1035 RAreaListPrint(self->monitors); 1036 fprintf(stderr, "\n horiz="); 1037 RAreaListPrint(self->horiz); 1038 fprintf(stderr, "\n vert="); 1039 RAreaListPrint(self->vert); 1040 fprintf(stderr, "]\n"); 1041} 1042