~nickbp/gridmgr

e3707153368088cc4583d0e646868b758aa4d01c — Nick Parker 10 years ago f50998c
move position calculations out of grid.cpp into a separate module. refrain from moving windows with a _DOCK and/or _DESKTOP type flag enabled. add a stub with timestamp/cmdline to the top of logfile output.
M src/CMakeLists.txt => src/CMakeLists.txt +2 -0
@@ 28,6 28,8 @@ SET(SRCS
  grid.h
  grid.cpp
  main.cpp
  position.h
  position.cpp
  viewport.h
  viewport-ewmh.cpp
  window.h

A src/dimensions.h => src/dimensions.h +31 -0
@@ 0,0 1,31 @@
#ifndef GRIDMGR_DIMENSIONS_H
#define GRIDMGR_DIMENSIONS_H

/*
  gridmgr - Organizes windows according to a grid.
  Copyright (C) 2011  Nicholas Parker

  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 3 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, see <http://www.gnu.org/licenses/>.
*/

struct Dimensions {
	//position of the top left corner of the screen
	long x;
	long y;
	//width,height are 'exterior' size, including any borders/decorations
	unsigned long width;
	unsigned long height;
};

#endif

M src/grid.cpp => src/grid.cpp +15 -455
@@ 17,464 17,24 @@
*/

#include "grid.h"
#include "config.h"
#include "window.h"

namespace grid {
	enum MODE {
		MODE_UNKNOWN,
		MODE_TWO_COL,// 2x2 (not applicable for center col positions)
		MODE_THREE_COL_S,// 3x2 small: each position filling 1 column
		MODE_THREE_COL_L// 3x2 large: sides filling 2 columns and center filling full width
	};
	inline const char* mode_str(MODE mode) {
		switch (mode) {
		case MODE_UNKNOWN:
			return "UNKNOWN";
		case MODE_TWO_COL:
			return "TWO_COL";
		case MODE_THREE_COL_S:
			return "THREE_COL_S";
		case MODE_THREE_COL_L:
			return "THREE_COL_L";
		default:
			break;
		}
		return "???";
	}

	struct Position {
		POS pos;
		MODE mode;
	};

	// given window's position/mode, calculate its dimensions.
	// could have some kind of fancy autogeneration here,
	// but there's a very finite number of possible positions (for now)
	bool calculate_pos(const ActiveWindow::Dimensions& viewport,
			const Position& pos, ActiveWindow::Dimensions& window_out) {
		bool ret = true;
		long rel_x = 0, rel_y = 0;//coordinates relative to viewport
		switch (pos.mode) {
		case MODE_TWO_COL:
			switch (pos.pos) {
			case POS_TOP_LEFT:// top left quadrant
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_TOP_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case POS_TOP_RIGHT:// top right quadrant
				rel_x = viewport.width / 2.;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_LEFT:// left half
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height;
				break;
			case POS_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case POS_RIGHT:// right half
				rel_x = viewport.width / 2.;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height;
				break;
			case POS_BOT_LEFT:// bottom left quadrant
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_BOT_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case POS_BOT_RIGHT:// bottom right quadrant
				rel_x = viewport.width / 2.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
				break;
			}
			break;
		case MODE_THREE_COL_S:
			switch (pos.pos) {
			case POS_TOP_LEFT:// top left col
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_TOP_CENTER:// top center col
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_TOP_RIGHT:// top right col
				rel_x = 2 * viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_LEFT:// left third
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case POS_CENTER:// center col
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case POS_RIGHT:// right third
				rel_x = 2 * viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3;
				window_out.height = viewport.height;
				break;
			case POS_BOT_LEFT:// bottom left col
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_BOT_CENTER:// bottom center col
				rel_x = viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_BOT_RIGHT:// bottom right col
				rel_x = 2 * viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
			}
			break;
		case MODE_THREE_COL_L:
			switch (pos.pos) {
			case POS_TOP_LEFT:// top left two cols
				rel_x = 0;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_TOP_CENTER:// top half
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width;
				window_out.height = viewport.height / 2.;
				break;
			case POS_TOP_RIGHT:// top right two cols
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_LEFT:// left two thirds
				rel_x = 0;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case POS_CENTER:// full screen
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width;
				window_out.height = viewport.height;
				break;
			case POS_RIGHT:// right two thirds
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case POS_BOT_LEFT:// bottom left two cols
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case POS_BOT_CENTER:// bottom half
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width;
				window_out.height = viewport.height / 2.;
				break;
			case POS_BOT_RIGHT:// bottom right two cols
				rel_x = viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
			}
			break;
		default:
			ret = false;
		}
		if (ret) {
			//convert relative pos to absolute:
			window_out.x = rel_x + viewport.x;
			window_out.y = rel_y + viewport.y;
			DEBUG("pos=%s mode=%s -> %ldx %ldy %luw %luh",
					pos_str(pos.pos), mode_str(pos.mode),
					window_out.x, window_out.y,
					window_out.width, window_out.height);
		} else {
			ERROR("bad pos %s + mode %s", pos_str(pos.pos), mode_str(pos.mode));
		}
		return ret;
	}

#define MAX(a,b) (((a) > (b)) ? (a) : (b))
	// whether two numbers are 'near' one another, according to a fudge factor.
	template <typename A, typename B>
	inline bool _near(A a, B b, const char* a_desc, const char* b_desc) {
		// fudge factor: the greater of 5px or 5% of the greater number.
		double fudge = MAX(5, 0.05 * MAX(a,b));
		bool ret = (a + fudge) >= b && (a - fudge) <= b;
		DEBUG("%s(%d) vs %s(%d): %s",
				a_desc, (int)a, b_desc, (int)b,
				(ret) ? "true" : "false");
		return ret;
	}
#define NEAR(a,b) _near(a, b, #a, #b)

	// given window's dimensions, estimate its position/mode (or unknown+unknown)
	// (inverse of calculate_pos)
	bool get_current_pos(const ActiveWindow::Dimensions& window,
			const ActiveWindow::Dimensions& viewport, Position& out) {
		//get window x/y relative to viewport x/y
		int rel_x = window.x - viewport.x,
			rel_y = window.y - viewport.y;
		out.pos = POS_UNKNOWN;
		out.mode = MODE_UNKNOWN;
		if (NEAR(window.width, viewport.width / 2.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left quadrant
						out.pos = POS_TOP_LEFT;
						out.mode = MODE_TWO_COL;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left quadrant
						out.pos = POS_BOT_LEFT;
						out.mode = MODE_TWO_COL;
					}
				} else if (NEAR(rel_x, viewport.width / 2.)) {
					if (NEAR(rel_y, 0)) {
						// top right quadrant
						out.pos = POS_TOP_RIGHT;
						out.mode = MODE_TWO_COL;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right quadrant
						out.pos = POS_BOT_RIGHT;
						out.mode = MODE_TWO_COL;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left half
					out.pos = POS_LEFT;
					out.mode = MODE_TWO_COL;
				} else if (NEAR(rel_x, viewport.width / 2.)) {
					// right half
					out.pos = POS_RIGHT;
					out.mode = MODE_TWO_COL;
				}
			}
		} else if (NEAR(window.width, viewport.width / 3.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left col
						out.pos = POS_TOP_LEFT;
						out.mode = MODE_THREE_COL_S;
					} if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left col
						out.pos = POS_BOT_LEFT;
						out.mode = MODE_THREE_COL_S;
					}
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top center col
						out.pos = POS_TOP_CENTER;
						out.mode = MODE_THREE_COL_S;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom center col
						out.pos = POS_BOT_CENTER;
						out.mode = MODE_THREE_COL_S;
					}
				} else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top right col
						out.pos = POS_TOP_RIGHT;
						out.mode = MODE_THREE_COL_S;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right col
						out.pos = POS_BOT_RIGHT;
						out.mode = MODE_THREE_COL_S;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left col
					out.pos = POS_LEFT;
					out.mode = MODE_THREE_COL_S;
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					// center col
					out.pos = POS_CENTER;
					out.mode = MODE_THREE_COL_S;
				} else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
					// right col
					out.pos = POS_RIGHT;
					out.mode = MODE_THREE_COL_S;
				}
			}
		} else if (NEAR(window.width, 2 * viewport.width / 3.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left two cols
						out.pos = POS_TOP_LEFT;
						out.mode = MODE_THREE_COL_L;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left two cols
						out.pos = POS_BOT_LEFT;
						out.mode = MODE_THREE_COL_L;
					}
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top right two cols
						out.pos = POS_TOP_RIGHT;
						out.mode = MODE_THREE_COL_L;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right two cols
						out.pos = POS_BOT_RIGHT;
						out.mode = MODE_THREE_COL_L;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left two cols
					out.pos = POS_LEFT;
					out.mode = MODE_THREE_COL_L;
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					// right two cols
					out.pos = POS_RIGHT;
					out.mode = MODE_THREE_COL_L;
				}
			}
		} else if (NEAR(window.width, viewport.width) &&
				NEAR(rel_x, 0)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_y, 0)) {
					// top half
					out.pos = POS_TOP_CENTER;
					out.mode = MODE_THREE_COL_L;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom half
					out.pos = POS_BOT_CENTER;
					out.mode = MODE_THREE_COL_L;
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				// full screen
				out.pos = POS_CENTER;
				out.mode = MODE_THREE_COL_L;
			}
		}
#include "position.h"
#include "window.h"

		DEBUG("%ldx %ldy %luw %luh -> pos=%s mode=%s",
				rel_x, rel_y, window.width, window.height,
				pos_str(out.pos), mode_str(out.mode));
		return true;
	}
bool grid::set_position(POS pos) {
	//initializes to the currently active window
	ActiveWindow win;

	bool get_next_pos(const Position& cur, POS req_pos, Position& out) {
		if (req_pos == POS_UNKNOWN) {
			return false;// nice to have
		}
		if (cur.pos == req_pos) {
			// position is same, so rotate mode
			out.pos = cur.pos;
			switch (cur.pos) {
			case POS_TOP_CENTER:
			case POS_CENTER:
			case POS_BOT_CENTER:
				// for center col: 3x2L -> 3x2S (-> 3x2L) (no 2x2)
				switch (cur.mode) {
				case MODE_THREE_COL_L:
					out.mode = MODE_THREE_COL_S;
					break;
				case MODE_THREE_COL_S:
				default:
					out.mode = MODE_THREE_COL_L;
				}
				break;
			default:
				// for everything else: 2x2 -> 3x2L -> 3x2S (-> 2x2)
				switch (cur.mode) {
				case MODE_UNKNOWN:
				case MODE_THREE_COL_L:
					out.mode = MODE_THREE_COL_S;
					break;
				case MODE_TWO_COL:
					out.mode = MODE_THREE_COL_L;
					break;
				case MODE_THREE_COL_S:
				default:
					out.mode = MODE_TWO_COL;
				}
			}
		} else {
			// new position, so start with initial mode
			out.pos = req_pos;
			switch (req_pos) {
			case POS_TOP_CENTER:
			case POS_CENTER:
			case POS_BOT_CENTER:
				// for center col: start with 3x2L
				out.mode = MODE_THREE_COL_L;
				break;
			default:
				// for everything else: start with 2x2
				out.mode = MODE_TWO_COL;
			}
		}
		DEBUG("curpos=%s curmode=%s + reqpos=%s -> pos=%s mode=%s",
				pos_str(cur.pos), mode_str(cur.mode), pos_str(req_pos),
				pos_str(out.pos), mode_str(out.mode));
		return true;
	//get current window's dimensions
	Dimensions viewport, cur_dim;
	if (!win.Sizes(viewport, cur_dim)) {
		return false;
	}
}

bool grid::set_position(POS req_pos) {
	ActiveWindow win;
	Position cur_pos, next_pos;
	ActiveWindow::Dimensions viewport, cur_dim, next_dim;
	return win.Sizes(viewport, cur_dim) &&
		get_current_pos(cur_dim, viewport, cur_pos) &&// cur_dim+viewport -> cur_pos
		get_next_pos(cur_pos, req_pos, next_pos) &&// cur_pos+req_pos -> next_pos
		calculate_pos(viewport, next_pos, next_dim) &&// next_pos+viewport -> next_dim
		win.MoveResize(next_dim);
	//using the requested position and current dimensions,
	//calculate and set new dimensions
	PositionCalc calc(viewport, cur_dim);
	
	Dimensions next_dim;
	return calc.NextPos(pos, next_dim) && win.MoveResize(next_dim);
}

M src/grid.h => src/grid.h +5 -34
@@ 19,41 19,12 @@
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

namespace grid {
	enum POS {
		POS_UNKNOWN,
		POS_TOP_LEFT, POS_TOP_CENTER, POS_TOP_RIGHT,
		POS_LEFT, POS_CENTER, POS_RIGHT,
		POS_BOT_LEFT, POS_BOT_CENTER, POS_BOT_RIGHT
	};
	inline const char* pos_str(POS pos) {
		switch (pos) {
		case POS_UNKNOWN:
			return "UNKNOWN";
		case POS_TOP_LEFT:
			return "TOP_LEFT";
		case POS_TOP_CENTER:
			return "TOP_CENTER";
		case POS_TOP_RIGHT:
			return "TOP_RIGHT";
		case POS_LEFT:
			return "LEFT";
		case POS_CENTER:
			return "CENTER";
		case POS_RIGHT:
			return "RIGHT";
		case POS_BOT_LEFT:
			return "BOT_LEFT";
		case POS_BOT_CENTER:
			return "BOT_CENTER";
		case POS_BOT_RIGHT:
			return "BOT_RIGHT";
		default:
			break;
		}
		return "???";
	}
#include "pos.h"

namespace grid {
	/* Selects the active window and moves/resizes it to the requested
	   position, according to its current state.
	   Returns true if successful, false otherwise. */
	bool set_position(POS position);
}


M src/main.cpp => src/main.cpp +21 -0
@@ 19,10 19,13 @@
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "config.h"
#include "grid.h"

#define TIMESTR_MAX 128 // arbitrarily large

void syntax(char* appname) {
	ERROR_RAWDIR("");
	ERROR_RAWDIR("gridmgr v%s (built %s)",


@@ 129,6 132,24 @@ bool parse_config(int argc, char* argv[]) {
				}
				config::fout = logfile;
				config::ferr = logfile;
				char now_s[TIMESTR_MAX];
				{
					time_t now = time(NULL);
					struct tm now_tm;
					localtime_r(&now, &now_tm);
					if (strftime(now_s, TIMESTR_MAX, "%a, %d %b %Y %T %z", &now_tm) == 0) {
						ERROR_DIR("strftime failed");
						return false;
					}
				}
				fprintf(logfile, "--- %s ---\n", now_s);
				for (int i = 0; i < argc && argc < 100;) {
					fprintf(logfile, "%s", argv[i]);
					if (++i < argc) {
						fprintf(logfile, " ");
					}
				}
				fprintf(logfile, "\n");
			}
			break;
		default:

A src/pos.h => src/pos.h +61 -0
@@ 0,0 1,61 @@
#ifndef GRIDMGR_POS_H
#define GRIDMGR_POS_H

/*
  gridmgr - Organizes windows according to a grid.
  Copyright (C) 2011  Nicholas Parker

  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 3 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, see <http://www.gnu.org/licenses/>.
*/

namespace grid {
	/* These are the various available positions which may be set.
	   UNKNOWN is considered an error state and should not be requested. */
	enum POS {
		POS_UNKNOWN,
		POS_TOP_LEFT, POS_TOP_CENTER, POS_TOP_RIGHT,
		POS_LEFT, POS_CENTER, POS_RIGHT,
		POS_BOT_LEFT, POS_BOT_CENTER, POS_BOT_RIGHT
	};

	inline const char* pos_str(POS pos) {
		switch (pos) {
		case POS_UNKNOWN:
			return "UNKNOWN";
		case POS_TOP_LEFT:
			return "TOP_LEFT";
		case POS_TOP_CENTER:
			return "TOP_CENTER";
		case POS_TOP_RIGHT:
			return "TOP_RIGHT";
		case POS_LEFT:
			return "LEFT";
		case POS_CENTER:
			return "CENTER";
		case POS_RIGHT:
			return "RIGHT";
		case POS_BOT_LEFT:
			return "BOT_LEFT";
		case POS_BOT_CENTER:
			return "BOT_CENTER";
		case POS_BOT_RIGHT:
			return "BOT_RIGHT";
		default:
			break;
		}
		return "???";
	}
}

#endif

A src/position.cpp => src/position.cpp +476 -0
@@ 0,0 1,476 @@
/*
  gridmgr - Organizes windows according to a grid.
  Copyright (C) 2011  Nicholas Parker

  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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "position.h"
#include "config.h"

namespace {
	enum MODE {
		MODE_UNKNOWN,
		MODE_TWO_COL,// 2x2 (not applicable for center col positions)
		MODE_THREE_COL_S,// 3x2 small: each position filling 1 column
		MODE_THREE_COL_L// 3x2 large: sides filling 2 columns and center filling full width
	};
	inline const char* mode_str(MODE mode) {
		switch (mode) {
		case MODE_UNKNOWN:
			return "UNKNOWN";
		case MODE_TWO_COL:
			return "TWO_COL";
		case MODE_THREE_COL_S:
			return "THREE_COL_S";
		case MODE_THREE_COL_L:
			return "THREE_COL_L";
		default:
			break;
		}
		return "???";
	}

	struct State {
		grid::POS pos;
		MODE mode;
	};

	// given window's state, calculate its dimensions.
	// could have some kind of fancy autogeneration here,
	// but there's a very finite number of possible positions (for now?)
	bool calculate_state(const Dimensions& viewport,
			const State& state, Dimensions& window_out) {
		bool ret = true;
		long rel_x = 0, rel_y = 0;//coordinates relative to viewport
		switch (state.mode) {
		case MODE_TWO_COL:
			switch (state.pos) {
			case grid::POS_TOP_LEFT:// top left quadrant
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_TOP_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case grid::POS_TOP_RIGHT:// top right quadrant
				rel_x = viewport.width / 2.;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_LEFT:// left half
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height;
				break;
			case grid::POS_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case grid::POS_RIGHT:// right half
				rel_x = viewport.width / 2.;
				rel_y = 0;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height;
				break;
			case grid::POS_BOT_LEFT:// bottom left quadrant
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_BOT_CENTER:// invalid, use mode THREE_COL_S/L
				return false;
			case grid::POS_BOT_RIGHT:// bottom right quadrant
				rel_x = viewport.width / 2.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 2.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
				break;
			}
			break;
		case MODE_THREE_COL_S:
			switch (state.pos) {
			case grid::POS_TOP_LEFT:// top left col
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_TOP_CENTER:// top center col
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_TOP_RIGHT:// top right col
				rel_x = 2 * viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_LEFT:// left third
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case grid::POS_CENTER:// center col
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case grid::POS_RIGHT:// right third
				rel_x = 2 * viewport.width / 3.;
				rel_y = 0;
				window_out.width = viewport.width / 3;
				window_out.height = viewport.height;
				break;
			case grid::POS_BOT_LEFT:// bottom left col
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_BOT_CENTER:// bottom center col
				rel_x = viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_BOT_RIGHT:// bottom right col
				rel_x = 2 * viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
			}
			break;
		case MODE_THREE_COL_L:
			switch (state.pos) {
			case grid::POS_TOP_LEFT:// top left two cols
				rel_x = 0;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_TOP_CENTER:// top half
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_TOP_RIGHT:// top right two cols
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_LEFT:// left two thirds
				rel_x = 0;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case grid::POS_CENTER:// full screen
				rel_x = 0;
				rel_y = 0;
				window_out.width = viewport.width;
				window_out.height = viewport.height;
				break;
			case grid::POS_RIGHT:// right two thirds
				rel_x = viewport.width / 3.;
				rel_y = 0;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height;
				break;
			case grid::POS_BOT_LEFT:// bottom left two cols
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_BOT_CENTER:// bottom half
				rel_x = 0;
				rel_y = viewport.height / 2.;
				window_out.width = viewport.width;
				window_out.height = viewport.height / 2.;
				break;
			case grid::POS_BOT_RIGHT:// bottom right two cols
				rel_x = viewport.width / 3.;
				rel_y = viewport.height / 2.;
				window_out.width = 2 * viewport.width / 3.;
				window_out.height = viewport.height / 2.;
				break;
			default:
				ret = false;
			}
			break;
		default:
			ret = false;
		}
		if (ret) {
			//convert relative pos to absolute:
			window_out.x = rel_x + viewport.x;
			window_out.y = rel_y + viewport.y;
			DEBUG("pos=%s mode=%s -> %ldx %ldy %luw %luh",
					pos_str(state.pos), mode_str(state.mode),
					window_out.x, window_out.y,
					window_out.width, window_out.height);
		} else {
			ERROR("Bad pos=%s + mode=%s", pos_str(state.pos), mode_str(state.mode));
		}
		return ret;
	}

#define MAX(a,b) (((a) > (b)) ? (a) : (b))
	// whether two numbers are 'near' one another, according to a fudge factor.
	template <typename A, typename B>
	inline bool _near(A a, B b, const char* a_desc, const char* b_desc) {
		// fudge factor: the greater of 5px or 5% of the greater number.
		double fudge = MAX(5, 0.05 * MAX(a,b));
		bool ret = (a + fudge) >= b && (a - fudge) <= b;
		DEBUG("%s(%d) vs %s(%d): %s",
				a_desc, (int)a, b_desc, (int)b,
				(ret) ? "true" : "false");
		return ret;
	}
#define NEAR(a,b) _near(a, b, #a, #b)

	// given window's dimensions, estimate its state (or unknown+unknown)
	// (inverse of calculate_state)
	bool get_current_state(const Dimensions& window,
			const Dimensions& viewport, State& out) {
		//get window x/y relative to viewport x/y
		int rel_x = window.x - viewport.x,
			rel_y = window.y - viewport.y;
		out.pos = grid::POS_UNKNOWN;
		out.mode = MODE_UNKNOWN;
		if (NEAR(window.width, viewport.width / 2.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left quadrant
						out.pos = grid::POS_TOP_LEFT;
						out.mode = MODE_TWO_COL;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left quadrant
						out.pos = grid::POS_BOT_LEFT;
						out.mode = MODE_TWO_COL;
					}
				} else if (NEAR(rel_x, viewport.width / 2.)) {
					if (NEAR(rel_y, 0)) {
						// top right quadrant
						out.pos = grid::POS_TOP_RIGHT;
						out.mode = MODE_TWO_COL;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right quadrant
						out.pos = grid::POS_BOT_RIGHT;
						out.mode = MODE_TWO_COL;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left half
					out.pos = grid::POS_LEFT;
					out.mode = MODE_TWO_COL;
				} else if (NEAR(rel_x, viewport.width / 2.)) {
					// right half
					out.pos = grid::POS_RIGHT;
					out.mode = MODE_TWO_COL;
				}
			}
		} else if (NEAR(window.width, viewport.width / 3.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left col
						out.pos = grid::POS_TOP_LEFT;
						out.mode = MODE_THREE_COL_S;
					} if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left col
						out.pos = grid::POS_BOT_LEFT;
						out.mode = MODE_THREE_COL_S;
					}
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top center col
						out.pos = grid::POS_TOP_CENTER;
						out.mode = MODE_THREE_COL_S;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom center col
						out.pos = grid::POS_BOT_CENTER;
						out.mode = MODE_THREE_COL_S;
					}
				} else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top right col
						out.pos = grid::POS_TOP_RIGHT;
						out.mode = MODE_THREE_COL_S;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right col
						out.pos = grid::POS_BOT_RIGHT;
						out.mode = MODE_THREE_COL_S;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left col
					out.pos = grid::POS_LEFT;
					out.mode = MODE_THREE_COL_S;
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					// center col
					out.pos = grid::POS_CENTER;
					out.mode = MODE_THREE_COL_S;
				} else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
					// right col
					out.pos = grid::POS_RIGHT;
					out.mode = MODE_THREE_COL_S;
				}
			}
		} else if (NEAR(window.width, 2 * viewport.width / 3.)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_x, 0)) {
					if (NEAR(rel_y, 0)) {
						// top left two cols
						out.pos = grid::POS_TOP_LEFT;
						out.mode = MODE_THREE_COL_L;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom left two cols
						out.pos = grid::POS_BOT_LEFT;
						out.mode = MODE_THREE_COL_L;
					}
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					if (NEAR(rel_y, 0)) {
						// top right two cols
						out.pos = grid::POS_TOP_RIGHT;
						out.mode = MODE_THREE_COL_L;
					} else if (NEAR(rel_y, viewport.height / 2.)) {
						// bottom right two cols
						out.pos = grid::POS_BOT_RIGHT;
						out.mode = MODE_THREE_COL_L;
					}
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				if (NEAR(rel_x, 0)) {
					// left two cols
					out.pos = grid::POS_LEFT;
					out.mode = MODE_THREE_COL_L;
				} else if (NEAR(rel_x, viewport.width / 3.)) {
					// right two cols
					out.pos = grid::POS_RIGHT;
					out.mode = MODE_THREE_COL_L;
				}
			}
		} else if (NEAR(window.width, viewport.width) &&
				NEAR(rel_x, 0)) {
			if (NEAR(window.height, viewport.height / 2.)) {
				if (NEAR(rel_y, 0)) {
					// top half
					out.pos = grid::POS_TOP_CENTER;
					out.mode = MODE_THREE_COL_L;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom half
					out.pos = grid::POS_BOT_CENTER;
					out.mode = MODE_THREE_COL_L;
				}
			} else if (NEAR(window.height, viewport.height) &&
					NEAR(rel_y, 0)) {
				// full screen
				out.pos = grid::POS_CENTER;
				out.mode = MODE_THREE_COL_L;
			}
		}

		DEBUG("%ldx %ldy %luw %luh -> pos=%s mode=%s",
				rel_x, rel_y, window.width, window.height,
				pos_str(out.pos), mode_str(out.mode));
		return true;
	}

	bool get_next_state(const State& cur, grid::POS req_pos, State& out) {
		if (req_pos == grid::POS_UNKNOWN) {
			ERROR("Position '%s' was requested. Internal error?", pos_str(req_pos));
			return false;// nice to have
		}
		if (cur.pos == req_pos) {
			// position is same, so rotate mode
			out.pos = cur.pos;
			switch (cur.pos) {
			case grid::POS_TOP_CENTER:
			case grid::POS_CENTER:
			case grid::POS_BOT_CENTER:
				// for center col: 3x2L -> 3x2S (-> 3x2L) (no 2x2)
				switch (cur.mode) {
				case MODE_THREE_COL_L:
					out.mode = MODE_THREE_COL_S;
					break;
				case MODE_THREE_COL_S:
				default:
					out.mode = MODE_THREE_COL_L;
				}
				break;
			default:
				// for everything else: 2x2 -> 3x2L -> 3x2S (-> 2x2)
				switch (cur.mode) {
				case MODE_UNKNOWN:
				case MODE_THREE_COL_L:
					out.mode = MODE_THREE_COL_S;
					break;
				case MODE_TWO_COL:
					out.mode = MODE_THREE_COL_L;
					break;
				case MODE_THREE_COL_S:
				default:
					out.mode = MODE_TWO_COL;
				}
			}
		} else {
			// new position, so start with initial mode
			out.pos = req_pos;
			switch (req_pos) {
			case grid::POS_TOP_CENTER:
			case grid::POS_CENTER:
			case grid::POS_BOT_CENTER:
				// for center col: start with 3x2L
				out.mode = MODE_THREE_COL_L;
				break;
			default:
				// for everything else: start with 2x2
				out.mode = MODE_TWO_COL;
			}
		}
		DEBUG("curpos=%s curmode=%s + reqpos=%s -> pos=%s mode=%s",
				pos_str(cur.pos), mode_str(cur.mode), pos_str(req_pos),
				pos_str(out.pos), mode_str(out.mode));
		return true;
	}
}

bool PositionCalc::NextPos(grid::POS request, Dimensions& out) {
	State cur_state, next_state;
	return get_current_state(window, viewport, cur_state) &&// window + viewport -> cur_state
		get_next_state(cur_state, request, next_state) &&// cur_state + request -> next_state
		calculate_state(viewport, next_state, out);// next_state + viewport -> next_dim
}

A src/position.h => src/position.h +37 -0
@@ 0,0 1,37 @@
#ifndef GRIDMGR_POSITION_H
#define GRIDMGR_POSITION_H

/*
  gridmgr - Organizes windows according to a grid.
  Copyright (C) 2011  Nicholas Parker

  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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "pos.h"
#include "dimensions.h"

class PositionCalc {
 public:
	PositionCalc(const Dimensions& viewport,
		 const Dimensions& window)
	 : viewport(viewport), window(window) { }

	bool NextPos(grid::POS request, Dimensions& out);

 private:
	const Dimensions viewport, window;
};

#endif

M src/viewport-ewmh.cpp => src/viewport-ewmh.cpp +1 -1
@@ 21,7 21,7 @@
#include "config.h"

bool viewport::get_viewport_ewmh(Display* disp,
		ActiveWindow::Dimensions& viewport_out) {
		Dimensions& viewport_out) {
	//get current workspace
	unsigned long cur_workspace;
	{

M src/viewport-xinerama.cpp => src/viewport-xinerama.cpp +2 -2
@@ 39,8 39,8 @@ static int INTERSECTION(int a1, int a2, int b1, int b2) {
}

bool viewport::get_viewport_xinerama(Display* disp,
		const ActiveWindow::Dimensions& activewin,
		ActiveWindow::Dimensions& viewport_out) {
		const Dimensions& activewin,
		Dimensions& viewport_out) {
	//pick the xinerama screen which the active window 'belongs' to (= 'active screen')
	//also calculate a bounding box across all screens (needed for strut math)
	long bound_xmin = 0, bound_xmax = 0, bound_ymin = 0, bound_ymax = 0,//bounding box of all screens

M src/viewport.h => src/viewport.h +6 -4
@@ 19,18 19,20 @@
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "window.h"
#include <X11/Xlib.h>

#include "dimensions.h"
#include "config.h"

namespace viewport {

	bool get_viewport_ewmh(Display* disp,
			ActiveWindow::Dimensions& viewport_out);
			Dimensions& viewport_out);

#ifdef USE_XINERAMA
	bool get_viewport_xinerama(Display* disp,
			const ActiveWindow::Dimensions& activewin,
			ActiveWindow::Dimensions& viewport_out);
			const Dimensions& activewin,
			Dimensions& viewport_out);
#endif

}

M src/window.cpp => src/window.cpp +63 -14
@@ 67,8 67,47 @@ namespace {
		DEBUG("  %dx %dy %uw %uh %ub", x, y, width, height, border);
	}

	bool check_window_allowed(Display* disp, Window win) {
		/*
		  check if this is a DESKTOP or DOCK type window. disallow moving it if so.
		  (avoid messing with the user's desktop itself)
		*/
		bool ret = true;
		{
			size_t count = 0;
			Atom* types;
			static Atom desktop_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DESKTOP", False),
				dock_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DOCK", False);

			if (!(types = (Atom*)x11_util::get_property(disp, win,
									XA_ATOM, "_NET_WM_WINDOW_TYPE", &count))) {
				ERROR_DIR("couldnt get window types");
				//we're just doing this to check that the window is allowed,
				//no need to abort the whole thing.
				return true;
			}

			for (size_t i = 0; i < count; ++i) {
				DEBUG("type %lu: %d %s",
						i, types[i], XGetAtomName(disp, types[i]));
				if (types[i] == desktop_type || types[i] == dock_type) {
					ret = false;
					if (!config::debug_enabled) {
						break;
					}
				}
			}
			x11_util::free_property(types);
		}

		if (!ret) {
			LOG_DIR("Active window is a desktop or dock. Ignoring move request.");
		}
		return ret;
	}

	bool get_window_size(Display* disp, Window win,
			ActiveWindow::Dimensions* out_exterior = NULL,
			Dimensions* out_exterior = NULL,
			unsigned int* out_margin_width = NULL,
			unsigned int* out_margin_height = NULL) {
		Window root;


@@ 148,9 187,11 @@ namespace {
	}

	bool defullscreen_deshade_window(Display* disp, Window win) {
		//this disagrees with docs, which say that we should be using a
		//_NET_WM_STATE_DISABLE atom in data[0]. but that apparently doesn't
		//work in practice, but '0' does. (and '1' works for ENABLE)
		/*
		  this disagrees with docs, which say that we should be using a
		  _NET_WM_STATE_DISABLE atom in data[0]. but that apparently doesn't
		  work in practice, but '0' does. (and '1' works for ENABLE)
		*/

		if (!client_msg(disp, win, "_NET_WM_STATE",
						0,//1 = enable state(s), 0 = disable state(s)


@@ 164,9 205,11 @@ namespace {
	}

	bool demaximize_window(Display* disp, Window win) {
		//this disagrees with docs, which say that we should be using a
		//_NET_WM_STATE_DISABLE atom in data[0]. but that apparently doesn't
		//work in practice, but '0' does. (and '1' works for ENABLE)
		/*
		  this disagrees with docs, which say that we should be using a
		  _NET_WM_STATE_DISABLE atom in data[0]. but that apparently doesn't
		  work in practice, but '0' does. (and '1' works for ENABLE)
		*/

		if (!client_msg(disp, win, "_NET_WM_STATE",
						0,//1 = enable state(s), 0 = disable state(s)


@@ 180,8 223,8 @@ namespace {
		return true;
	}

	bool get_viewport(Display* disp, const ActiveWindow::Dimensions& activewin,
			ActiveWindow::Dimensions& viewport_out) {
	bool get_viewport(Display* disp, const Dimensions& activewin,
			Dimensions& viewport_out) {
#ifdef USE_XINERAMA
		//try xinerama, fall back to ewmh if xinerama is unavailable
		return viewport::get_viewport_xinerama(disp, activewin, viewport_out) ||


@@ 222,6 265,10 @@ ActiveWindow::~ActiveWindow() {
bool ActiveWindow::Sizes(Dimensions& viewport, Dimensions& activewin) const {
	CHECK_STATE();

	if (!check_window_allowed(disp, *win)) {
		return false;
	}

	if (config::debug_enabled) {
		size_t count = 0;
		Atom* states;


@@ 230,7 277,7 @@ bool ActiveWindow::Sizes(Dimensions& viewport, Dimensions& activewin) const {
			ERROR_DIR("couldnt get window states");
		} else {
			for (size_t i = 0; i < count; ++i) {
				LOG("state %lu: %d %s",
				DEBUG("state %lu: %d %s",
						i, states[i], XGetAtomName(disp, states[i]));
			}
			x11_util::free_property(states);


@@ 242,10 289,12 @@ bool ActiveWindow::Sizes(Dimensions& viewport, Dimensions& activewin) const {
		return false;
	}

	//special case: if the active window is fullscreen, the desktop workarea is
	//wrong (ie the obscured taskbar extents aren't included). get around this
	//by unfullscreening the window first (if applicable). but return the
	//window's dimensions from when it was fullscreened/shaded.
	/*
	  special case: if the active window is fullscreen, the desktop workarea is
	  wrong (ie the obscured taskbar extents aren't included). get around this
	  by unfullscreening the window first (if applicable). but return the
	  window's dimensions from when it was fullscreened/shaded.
	*/
	defullscreen_deshade_window(disp, *win);//disregard failure

	if (!get_viewport(disp, activewin, viewport)) {

M src/window.h => src/window.h +2 -9
@@ 21,6 21,8 @@

#include <X11/Xlib.h>

#include "dimensions.h"

/*
  NOTE: This class internally assumes that a new active window won't be
  selected for the lifetime of the constructed object. Any changes in


@@ 32,15 34,6 @@ class ActiveWindow {
	ActiveWindow();
	virtual ~ActiveWindow();

	struct Dimensions {
		//relative to the top left corner of the screen
		long x;
		long y;
		//width,height include any applicable borders/decorations ('exterior' size)
		unsigned long width;
		unsigned long height;
	};

	bool Sizes(Dimensions& viewport, Dimensions& activewin) const;
	bool MoveResize(const Dimensions& activewin);