~nickbp/gridmgr

91b1847c0ba49a43af475ae3f02cdef8908be905 — Nick Parker 9 years ago 93ef83b
Tab to 4sp. No actual code changes.
M src/config.cpp => src/config.cpp +28 -28
@@ 21,32 21,32 @@
#include <stdarg.h>

namespace config {
	FILE *fout = stdout, *ferr = stderr;
	bool debug_enabled = false;

	void _debug(const char* format, ...) {
		if (debug_enabled) {
			va_list args;
			va_start(args, format);
			vfprintf(fout, format, args);
			va_end(args);
			fprintf(fout, "\n");
		}
	}

	void _log(const char* format, ...) {
		va_list args;
		va_start(args, format);
		vfprintf(fout, format, args);
		va_end(args);
		fprintf(fout, "\n");
	}

	void _error(const char* format, ...) {
		va_list args;
		va_start(args, format);
		vfprintf(ferr, format, args);
		va_end(args);
		fprintf(ferr, "\n");
	}
    FILE *fout = stdout, *ferr = stderr;
    bool debug_enabled = false;

    void _debug(const char* format, ...) {
        if (debug_enabled) {
            va_list args;
            va_start(args, format);
            vfprintf(fout, format, args);
            va_end(args);
            fprintf(fout, "\n");
        }
    }

    void _log(const char* format, ...) {
        va_list args;
        va_start(args, format);
        vfprintf(fout, format, args);
        va_end(args);
        fprintf(fout, "\n");
    }

    void _error(const char* format, ...) {
        va_list args;
        va_start(args, format);
        vfprintf(ferr, format, args);
        va_end(args);
        fprintf(ferr, "\n");
    }
}

M src/config.in.h => src/config.in.h +13 -13
@@ 49,24 49,24 @@
#cmakedefine USE_XINERAMA

namespace config {
	static const int
		VERSION_MAJOR = @gridmgr_VERSION_MAJOR@,
		VERSION_MINOR = @gridmgr_VERSION_MINOR@,
		VERSION_PATCH = @gridmgr_VERSION_PATCH@;
    static const int
        VERSION_MAJOR = @gridmgr_VERSION_MAJOR@,
        VERSION_MINOR = @gridmgr_VERSION_MINOR@,
        VERSION_PATCH = @gridmgr_VERSION_PATCH@;

	static const char VERSION_STRING[] = "@gridmgr_VERSION_MAJOR@.@gridmgr_VERSION_MINOR@.@gridmgr_VERSION_PATCH@";
	static const char BUILD_DATE[] = __TIMESTAMP__;
    static const char VERSION_STRING[] = "@gridmgr_VERSION_MAJOR@.@gridmgr_VERSION_MINOR@.@gridmgr_VERSION_PATCH@";
    static const char BUILD_DATE[] = __TIMESTAMP__;

	/* DONT USE THESE, use DEBUG()/LOG()/ERROR() instead: */
    /* DONT USE THESE, use DEBUG()/LOG()/ERROR() instead: */

	extern FILE *fout;
	extern FILE *ferr;
    extern FILE *fout;
    extern FILE *ferr;

	extern bool debug_enabled;
	void _debug(const char* format, ...);
    extern bool debug_enabled;
    void _debug(const char* format, ...);

	void _log(const char* format, ...);
	void _error(const char* format, ...);
    void _log(const char* format, ...);
    void _error(const char* format, ...);
}

#endif

M src/dimensions.h => src/dimensions.h +6 -6
@@ 20,12 20,12 @@
*/

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;
    //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 +52 -52
@@ 23,67 23,67 @@
#include "window.h"

bool grid::set_active(POS window) {
	return window::select_activate(window);
    return window::select_activate(window);
}

bool grid::set_position(POS gridpos, POS monitor) {
	// initializes to the currently active window
	ActiveWindow win;
    // initializes to the currently active window
    ActiveWindow win;

	// get current window's dimensions
	Dimensions cur_window;
	if (!win.Size(cur_window)) {
		return false;
	}
    // get current window's dimensions
    Dimensions cur_window;
    if (!win.Size(cur_window)) {
        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. */
	win.DeFullscreen();// disregard failure
    /* 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. */
    win.DeFullscreen();// disregard failure

	Dimensions cur_viewport, next_viewport;
	{
		ViewportCalc vcalc(cur_window);
		// cur_window + monitor -> cur_viewport + next_viewport
		if (!vcalc.Viewports(monitor, cur_viewport, next_viewport)) {
			return false;
		}
	}
    Dimensions cur_viewport, next_viewport;
    {
        ViewportCalc vcalc(cur_window);
        // cur_window + monitor -> cur_viewport + next_viewport
        if (!vcalc.Viewports(monitor, cur_viewport, next_viewport)) {
            return false;
        }
    }

	/* using the requested position and current dimensions,
	   calculate and set new dimensions */
	PositionCalc pcalc(cur_window);
    /* using the requested position and current dimensions,
       calculate and set new dimensions */
    PositionCalc pcalc(cur_window);

	State cur_state, next_state;
	if (/* cur_viewport + cur_window -> cur_state */
			!pcalc.CurState(cur_viewport, cur_state) ||
			/* cur_state + pos -> next_state */
			!pcalc.NextState(cur_state, gridpos, next_state)) {
		return false;
	}
    State cur_state, next_state;
    if (/* cur_viewport + cur_window -> cur_state */
            !pcalc.CurState(cur_viewport, cur_state) ||
            /* cur_state + pos -> next_state */
            !pcalc.NextState(cur_state, gridpos, next_state)) {
        return false;
    }

	Dimensions next_dim;
	if (next_state.pos == POS_UNKNOWN && next_state.mode == MODE_UNKNOWN) {
		// window doesnt currently have a state, and user didn't specify one
		pcalc.ViewportToDim(cur_viewport, next_viewport, next_dim);
	} else {
		// next_viewport + next_state -> next_dim
		if (!pcalc.StateToDim(next_viewport, next_state, next_dim)) {
			return false;
		}
	}
    Dimensions next_dim;
    if (next_state.pos == POS_UNKNOWN && next_state.mode == MODE_UNKNOWN) {
        // window doesnt currently have a state, and user didn't specify one
        pcalc.ViewportToDim(cur_viewport, next_viewport, next_dim);
    } else {
        // next_viewport + next_state -> next_dim
        if (!pcalc.StateToDim(next_viewport, next_state, next_dim)) {
            return false;
        }
    }

	// move the window to next_dim
	if (!win.DeShade() || !win.MoveResize(next_dim)) {
		return false;
	}
    // move the window to next_dim
    if (!win.DeShade() || !win.MoveResize(next_dim)) {
        return false;
    }

	if (next_state.pos == POS_CENTER &&
		next_state.mode == MODE_THREE_COL_L) {
		/* if we're going to be filling the screen anyway, just maximize
		   (do this after MoveResize so that viewport changes are respected) */
		win.Maximize();// disregard failure
	}
	return true;
    if (next_state.pos == POS_CENTER &&
        next_state.mode == MODE_THREE_COL_L) {
        /* if we're going to be filling the screen anyway, just maximize
           (do this after MoveResize so that viewport changes are respected) */
        win.Maximize();// disregard failure
    }
    return true;
}

M src/grid.h => src/grid.h +8 -8
@@ 22,14 22,14 @@
#include "pos.h"

namespace grid {
	/* Selects and makes active the window in the specified direction relative
	 * to the currently active window. */
	bool set_active(POS window);

	/* Selects the active window and moves/resizes it to the requested
	 * position/monitor, according to its current state.
	 * Returns true if successful, false otherwise. */
	bool set_position(POS gridpos, POS monitor);
    /* Selects and makes active the window in the specified direction relative
     * to the currently active window. */
    bool set_active(POS window);

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

#endif

M src/main.cpp => src/main.cpp +197 -197
@@ 28,220 28,220 @@
#define TIMESTR_MAX 128 // arbitrarily large

static void syntax(char* appname) {
	ERROR_RAWDIR("");
	ERROR_RAWDIR("gridmgr v%s (built %s)",
		  config::VERSION_STRING,
		  config::BUILD_DATE);
	ERROR_RAWDIR("");
	ERROR_RAWDIR("Performs one or more of the following, in this order:");
	ERROR_RAWDIR("- Activate a window adjacent to the current window.");
    ERROR_RAWDIR("");
    ERROR_RAWDIR("gridmgr v%s (built %s)",
          config::VERSION_STRING,
          config::BUILD_DATE);
    ERROR_RAWDIR("");
    ERROR_RAWDIR("Performs one or more of the following, in this order:");
    ERROR_RAWDIR("- Activate a window adjacent to the current window.");
#ifdef USE_XINERAMA
	ERROR_RAWDIR("- Move active window to an adjacent monitor.");
    ERROR_RAWDIR("- Move active window to an adjacent monitor.");
#endif
	ERROR_RAWDIR("- Position active window on a grid layout.");
	ERROR_RAWDIR("");
    ERROR_RAWDIR("- Position active window on a grid layout.");
    ERROR_RAWDIR("");
#ifdef USE_XINERAMA
	ERROR_RAWDIR("Usage: %s [options] <window/monitor/position> [w/m/p] [w/m/p]", appname);
    ERROR_RAWDIR("Usage: %s [options] <window/monitor/position> [w/m/p] [w/m/p]", appname);
#else
	ERROR_RAWDIR("Usage: %s [options] <window/position> [w/p] [w/p]", appname);
    ERROR_RAWDIR("Usage: %s [options] <window/position> [w/p] [w/p]", appname);
#endif
	ERROR_RAWDIR("");
	ERROR_RAWDIR("Windows (activate adjacent (w)indow):");
	ERROR_RAWDIR("  -------- --------- ---------  ");
	ERROR_RAWDIR(" | wuleft |   wup   | wuright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | wleft  |         |  wright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | wdleft |  wdown  | wdright | ");
	ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR("");
    ERROR_RAWDIR("Windows (activate adjacent (w)indow):");
    ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR(" | wuleft |   wup   | wuright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | wleft  |         |  wright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | wdleft |  wdown  | wdright | ");
    ERROR_RAWDIR("  -------- --------- ---------  ");
#ifdef USE_XINERAMA
	ERROR_RAWDIR("");
	ERROR_RAWDIR("Monitors (move window to adjacent (m)onitor):");
	ERROR_RAWDIR("  -------- --------- ---------  ");
	ERROR_RAWDIR(" | muleft |   mup   | muright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | mleft  |         |  mright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | mdleft |  mdown  | mdright | ");
	ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR("");
    ERROR_RAWDIR("Monitors (move window to adjacent (m)onitor):");
    ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR(" | muleft |   mup   | muright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | mleft  |         |  mright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | mdleft |  mdown  | mdright | ");
    ERROR_RAWDIR("  -------- --------- ---------  ");
#endif
	ERROR_RAWDIR("");
	ERROR_RAWDIR("Positions (position window on (g)rid):");
	ERROR_RAWDIR("  -------- --------- ---------  ");
	ERROR_RAWDIR(" | guleft |   gup   | guright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | gleft  | gcenter |  gright | ");
	ERROR_RAWDIR(" |------- + ------- + --------| ");
	ERROR_RAWDIR(" | gdleft |  gdown  | gdright | ");
	ERROR_RAWDIR("  -------- --------- ---------  ");
	ERROR_RAWDIR("");
	ERROR_RAWDIR("Options:");
	ERROR_RAWDIR("  -h/--help        This help text.");
	ERROR_RAWDIR("  -v/--verbose     Show verbose output.");
	ERROR_RAWDIR("  --log <file>     Append any output to <file>.");
	ERROR_RAWDIR("");
    ERROR_RAWDIR("");
    ERROR_RAWDIR("Positions (position window on (g)rid):");
    ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR(" | guleft |   gup   | guright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | gleft  | gcenter |  gright | ");
    ERROR_RAWDIR(" |------- + ------- + --------| ");
    ERROR_RAWDIR(" | gdleft |  gdown  | gdright | ");
    ERROR_RAWDIR("  -------- --------- ---------  ");
    ERROR_RAWDIR("");
    ERROR_RAWDIR("Options:");
    ERROR_RAWDIR("  -h/--help        This help text.");
    ERROR_RAWDIR("  -v/--verbose     Show verbose output.");
    ERROR_RAWDIR("  --log <file>     Append any output to <file>.");
    ERROR_RAWDIR("");
}

static bool strsub_to_pos(const char* arg, grid::POS& out, bool center_is_valid) {
	if (strcmp(arg, "uleft") == 0) {
		out = grid::POS_UP_LEFT;
	} else if (strcmp(arg, "up") == 0) {
		out = grid::POS_UP_CENTER;
	} else if (strcmp(arg, "uright") == 0) {
		out = grid::POS_UP_RIGHT;

	} else if (strcmp(arg, "left") == 0) {
		out = grid::POS_LEFT;
	} else if (center_is_valid && strcmp(arg, "center") == 0) {
		out = grid::POS_CENTER;
	} else if (strcmp(arg, "right") == 0) {
		out = grid::POS_RIGHT;

	} else if (strcmp(arg, "dleft") == 0) {
		out = grid::POS_DOWN_LEFT;
	} else if (strcmp(arg, "down") == 0) {
		out = grid::POS_DOWN_CENTER;
	} else if (strcmp(arg, "dright") == 0) {
		out = grid::POS_DOWN_RIGHT;

	} else {
		return false;
	}
	return true;
    if (strcmp(arg, "uleft") == 0) {
        out = grid::POS_UP_LEFT;
    } else if (strcmp(arg, "up") == 0) {
        out = grid::POS_UP_CENTER;
    } else if (strcmp(arg, "uright") == 0) {
        out = grid::POS_UP_RIGHT;

    } else if (strcmp(arg, "left") == 0) {
        out = grid::POS_LEFT;
    } else if (center_is_valid && strcmp(arg, "center") == 0) {
        out = grid::POS_CENTER;
    } else if (strcmp(arg, "right") == 0) {
        out = grid::POS_RIGHT;

    } else if (strcmp(arg, "dleft") == 0) {
        out = grid::POS_DOWN_LEFT;
    } else if (strcmp(arg, "down") == 0) {
        out = grid::POS_DOWN_CENTER;
    } else if (strcmp(arg, "dright") == 0) {
        out = grid::POS_DOWN_RIGHT;

    } else {
        return false;
    }
    return true;
}

namespace {
	enum CMD { CMD_UNKNOWN, CMD_HELP, CMD_POSITION };
	CMD run_cmd = CMD_UNKNOWN;
	grid::POS gridpos = grid::POS_CURRENT,
		monitor = grid::POS_CURRENT, window = grid::POS_CURRENT;
    enum CMD { CMD_UNKNOWN, CMD_HELP, CMD_POSITION };
    CMD run_cmd = CMD_UNKNOWN;
    grid::POS gridpos = grid::POS_CURRENT,
        monitor = grid::POS_CURRENT, window = grid::POS_CURRENT;
}

static bool parse_config(int argc, char* argv[]) {
	if (argc == 1) {
		syntax(argv[0]);
		return false;
	}
	int c;
	while (1) {
		static struct option long_options[] = {
			{"help", 0, NULL, 'h'},
			{"verbose", 0, NULL, 'v'},
			{"log", required_argument, NULL, 'l'},
			{0,0,0,0}
		};

		int option_index = 0;
		c = getopt_long(argc, argv, "hvl",
				long_options, &option_index);
		if (c == -1) {//unknown arg (doesnt match -x/--x format)
			if (optind >= argc) {
				//at end of successful parse
				break;
			}
			//getopt refuses to continue, so handle position manually:
			for (int i = optind; i < argc; ++i) {
				const char* arg = argv[i];
				//DEBUG("%d %d %s", argc, i, arg);
				grid::POS tmp_pos;
				if (arg[0] == 'g' && strsub_to_pos(arg+1, tmp_pos, true)) {
					if (gridpos != grid::POS_CURRENT) {
						ERROR("%s: Multiple positions specified: '%s'", argv[0], argv[i]);
						syntax(argv[0]);
						return false;
					}
					gridpos = tmp_pos;
				} else if (arg[0] == 'w' && strsub_to_pos(arg+1, tmp_pos, false)) {
					if (window != grid::POS_CURRENT) {
						ERROR("%s: Multiple windows specified: '%s'", argv[0], argv[i]);
						syntax(argv[0]);
						return false;
					}
					window = tmp_pos;
				} else if (arg[0] == 'm' && strsub_to_pos(arg+1, tmp_pos, false)) {
					if (monitor != grid::POS_CURRENT) {
						ERROR("%s: Multiple monitors specified: '%s'", argv[0], argv[i]);
						syntax(argv[0]);
						return false;
					}
					monitor = tmp_pos;
				} else {
					ERROR("%s: Unknown argument: '%s'", argv[0], argv[i]);
					syntax(argv[0]);
					return false;
				}
				run_cmd = CMD_POSITION;
			}
			break;
		}

		switch (c) {
		case 'h':
			run_cmd = CMD_HELP;
			return true;
		case 'v':
			config::debug_enabled = true;
			break;
		case 'l':
			{
				FILE* logfile = fopen(optarg, "a");
				if (logfile == NULL) {
					ERROR("Unable to open log file %s: %s", optarg, strerror(errno));
					return false;
				}
				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:
			syntax(argv[0]);
			return false;
		}
	}

	return true;
    if (argc == 1) {
        syntax(argv[0]);
        return false;
    }
    int c;
    while (1) {
        static struct option long_options[] = {
            {"help", 0, NULL, 'h'},
            {"verbose", 0, NULL, 'v'},
            {"log", required_argument, NULL, 'l'},
            {0,0,0,0}
        };

        int option_index = 0;
        c = getopt_long(argc, argv, "hvl",
                long_options, &option_index);
        if (c == -1) {//unknown arg (doesnt match -x/--x format)
            if (optind >= argc) {
                //at end of successful parse
                break;
            }
            //getopt refuses to continue, so handle position manually:
            for (int i = optind; i < argc; ++i) {
                const char* arg = argv[i];
                //DEBUG("%d %d %s", argc, i, arg);
                grid::POS tmp_pos;
                if (arg[0] == 'g' && strsub_to_pos(arg+1, tmp_pos, true)) {
                    if (gridpos != grid::POS_CURRENT) {
                        ERROR("%s: Multiple positions specified: '%s'", argv[0], argv[i]);
                        syntax(argv[0]);
                        return false;
                    }
                    gridpos = tmp_pos;
                } else if (arg[0] == 'w' && strsub_to_pos(arg+1, tmp_pos, false)) {
                    if (window != grid::POS_CURRENT) {
                        ERROR("%s: Multiple windows specified: '%s'", argv[0], argv[i]);
                        syntax(argv[0]);
                        return false;
                    }
                    window = tmp_pos;
                } else if (arg[0] == 'm' && strsub_to_pos(arg+1, tmp_pos, false)) {
                    if (monitor != grid::POS_CURRENT) {
                        ERROR("%s: Multiple monitors specified: '%s'", argv[0], argv[i]);
                        syntax(argv[0]);
                        return false;
                    }
                    monitor = tmp_pos;
                } else {
                    ERROR("%s: Unknown argument: '%s'", argv[0], argv[i]);
                    syntax(argv[0]);
                    return false;
                }
                run_cmd = CMD_POSITION;
            }
            break;
        }

        switch (c) {
        case 'h':
            run_cmd = CMD_HELP;
            return true;
        case 'v':
            config::debug_enabled = true;
            break;
        case 'l':
            {
                FILE* logfile = fopen(optarg, "a");
                if (logfile == NULL) {
                    ERROR("Unable to open log file %s: %s", optarg, strerror(errno));
                    return false;
                }
                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:
            syntax(argv[0]);
            return false;
        }
    }

    return true;
}

int main(int argc, char* argv[]) {
	if (!parse_config(argc, argv)) {
		return EXIT_FAILURE;
	}
	switch (run_cmd) {
	case CMD_HELP:
		syntax(argv[0]);
		return EXIT_SUCCESS;
	case CMD_POSITION:
		// activate window (if specified)
		if (window != grid::POS_CURRENT && !grid::set_active(window)) {
			return EXIT_FAILURE;
		}
		// move window (if specified)
		if (gridpos != grid::POS_CURRENT || monitor != grid::POS_CURRENT) {
			return grid::set_position(gridpos, monitor) ?
				EXIT_SUCCESS : EXIT_FAILURE;
		}
		return EXIT_SUCCESS;
	default:
		ERROR("%s: no command specified", argv[0]);
		syntax(argv[0]);
		return EXIT_FAILURE;
	}
    if (!parse_config(argc, argv)) {
        return EXIT_FAILURE;
    }
    switch (run_cmd) {
    case CMD_HELP:
        syntax(argv[0]);
        return EXIT_SUCCESS;
    case CMD_POSITION:
        // activate window (if specified)
        if (window != grid::POS_CURRENT && !grid::set_active(window)) {
            return EXIT_FAILURE;
        }
        // move window (if specified)
        if (gridpos != grid::POS_CURRENT || monitor != grid::POS_CURRENT) {
            return grid::set_position(gridpos, monitor) ?
                EXIT_SUCCESS : EXIT_FAILURE;
        }
        return EXIT_SUCCESS;
    default:
        ERROR("%s: no command specified", argv[0]);
        syntax(argv[0]);
        return EXIT_FAILURE;
    }
}

M src/neighbor.cpp => src/neighbor.cpp +225 -225
@@ 25,256 25,256 @@
#define DISTANCE(a,b) ((a > b) ? (a - b) : (b - a))

namespace {
	class point {
	public:
		point() : x(0), y(0) { }
		point(const Dimensions& d)
			: x(MIDPOINT(d.x, d.width)),
			  y(MIDPOINT(d.y, d.height)) { }
    class point {
    public:
        point() : x(0), y(0) { }
        point(const Dimensions& d)
            : x(MIDPOINT(d.x, d.width)),
              y(MIDPOINT(d.y, d.height)) { }

		/* Returns whether 'p' is in the specified direction relative to this
		   point. Eg POS_UP -> "is p above this?" */
		bool direction(grid::POS dir, const point& p) const {
			// note: M_PI_4 == pi/4, from math.h.
			if (y == p.y && x == p.x) {
				// p is on top of us, avoid getting stuck
				return false;
			}
			//DEBUG("%s: %f vs %f", grid::dir_str(dir), abs_atan(p), (M_PI_4));
			switch (dir) {
			case grid::POS_UP_LEFT:
				return (y > p.y && x > p.x);
			case grid::POS_UP_RIGHT:
				return (y > p.y && x < p.x);
			case grid::POS_DOWN_LEFT:
				return (y < p.y && x > p.x);
			case grid::POS_DOWN_RIGHT:
				return (y < p.y && x < p.x);
        /* Returns whether 'p' is in the specified direction relative to this
           point. Eg POS_UP -> "is p above this?" */
        bool direction(grid::POS dir, const point& p) const {
            // note: M_PI_4 == pi/4, from math.h.
            if (y == p.y && x == p.x) {
                // p is on top of us, avoid getting stuck
                return false;
            }
            //DEBUG("%s: %f vs %f", grid::dir_str(dir), abs_atan(p), (M_PI_4));
            switch (dir) {
            case grid::POS_UP_LEFT:
                return (y > p.y && x > p.x);
            case grid::POS_UP_RIGHT:
                return (y > p.y && x < p.x);
            case grid::POS_DOWN_LEFT:
                return (y < p.y && x > p.x);
            case grid::POS_DOWN_RIGHT:
                return (y < p.y && x < p.x);

			case grid::POS_UP_CENTER:
				if (y < p.y) { return false; } // p is below us
				return abs_atan(p) >= M_PI_4; // p is too far right/left of us
			case grid::POS_DOWN_CENTER:
				if (y > p.y) { return false; } // p is above us
				return abs_atan(p) >= M_PI_4; // p is too far right/left of us
            case grid::POS_UP_CENTER:
                if (y < p.y) { return false; } // p is below us
                return abs_atan(p) >= M_PI_4; // p is too far right/left of us
            case grid::POS_DOWN_CENTER:
                if (y > p.y) { return false; } // p is above us
                return abs_atan(p) >= M_PI_4; // p is too far right/left of us

			case grid::POS_LEFT:
				if (x < p.x) { return false; } // p is right of us
				return abs_atan(p) <= M_PI_4; // p is too far above/below us
			case grid::POS_RIGHT:
				if (x > p.x) { return false; } // p is left of us
				return abs_atan(p) <= M_PI_4; // p is too far above/below us
            case grid::POS_LEFT:
                if (x < p.x) { return false; } // p is right of us
                return abs_atan(p) <= M_PI_4; // p is too far above/below us
            case grid::POS_RIGHT:
                if (x > p.x) { return false; } // p is left of us
                return abs_atan(p) <= M_PI_4; // p is too far above/below us

			case grid::POS_CENTER:
			case grid::POS_UNKNOWN:
			case grid::POS_CURRENT:
				ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
				break;
			}
			return false;//???
		}
            case grid::POS_CENTER:
            case grid::POS_UNKNOWN:
            case grid::POS_CURRENT:
                ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
                break;
            }
            return false;//???
        }

		/* Not a true 'distance', more of a weighting where smaller is better. */
		double distance(grid::POS dir, const point& p) const {
			long w = DISTANCE(p.x, x), h = DISTANCE(p.y, y);
			switch (dir) {
			case grid::POS_UP_LEFT:
			case grid::POS_UP_RIGHT:
			case grid::POS_DOWN_LEFT:
			case grid::POS_DOWN_RIGHT:
				//use a weighting that favors diagonals (where w == h)
			{
				double dist = sqrt((w * w) + (h * h));
				//sqrt again to further decrease dist's importance
				return sqrt(dist) * DISTANCE(w, h);
			}
        /* Not a true 'distance', more of a weighting where smaller is better. */
        double distance(grid::POS dir, const point& p) const {
            long w = DISTANCE(p.x, x), h = DISTANCE(p.y, y);
            switch (dir) {
            case grid::POS_UP_LEFT:
            case grid::POS_UP_RIGHT:
            case grid::POS_DOWN_LEFT:
            case grid::POS_DOWN_RIGHT:
                //use a weighting that favors diagonals (where w == h)
            {
                double dist = sqrt((w * w) + (h * h));
                //sqrt again to further decrease dist's importance
                return sqrt(dist) * DISTANCE(w, h);
            }

			case grid::POS_UP_CENTER:
			case grid::POS_DOWN_CENTER:
			case grid::POS_RIGHT:
			case grid::POS_LEFT:
				//use actual distance, diagonals are automatically farther
				return sqrt((w * w) + (h * h));
            case grid::POS_UP_CENTER:
            case grid::POS_DOWN_CENTER:
            case grid::POS_RIGHT:
            case grid::POS_LEFT:
                //use actual distance, diagonals are automatically farther
                return sqrt((w * w) + (h * h));

			case grid::POS_CENTER:
			case grid::POS_UNKNOWN:
			case grid::POS_CURRENT:
				ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
				break;
			}
			return 0;//???
		}
            case grid::POS_CENTER:
            case grid::POS_UNKNOWN:
            case grid::POS_CURRENT:
                ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
                break;
            }
            return 0;//???
        }

		void shift_pos(grid::POS dir, long max_x, long max_y) {
			switch (dir) {
			case grid::POS_UP_LEFT:
				// we're looking up left, so move the point down right
				y += max_y;
				x += max_x;
				break;
			case grid::POS_DOWN_RIGHT:
				// opposite of UP_LEFT
				shift_pos(grid::POS_UP_LEFT, -1 * max_x, -1 * max_y);
				break;
        void shift_pos(grid::POS dir, long max_x, long max_y) {
            switch (dir) {
            case grid::POS_UP_LEFT:
                // we're looking up left, so move the point down right
                y += max_y;
                x += max_x;
                break;
            case grid::POS_DOWN_RIGHT:
                // opposite of UP_LEFT
                shift_pos(grid::POS_UP_LEFT, -1 * max_x, -1 * max_y);
                break;

			case grid::POS_UP_RIGHT:
				// we're looking up right, so move the point down left
				y += max_y;
				x -= max_x;
				break;
			case grid::POS_DOWN_LEFT:
				// opposite of UP_RIGHT
				shift_pos(grid::POS_UP_RIGHT, -1 * max_x, -1 * max_y);
				break;
            case grid::POS_UP_RIGHT:
                // we're looking up right, so move the point down left
                y += max_y;
                x -= max_x;
                break;
            case grid::POS_DOWN_LEFT:
                // opposite of UP_RIGHT
                shift_pos(grid::POS_UP_RIGHT, -1 * max_x, -1 * max_y);
                break;

			case grid::POS_UP_CENTER:
				// we're looking up, so move the point down
				y += max_y;
				break;
			case grid::POS_DOWN_CENTER:
				// opposite of UP_CENTER
				shift_pos(grid::POS_UP_CENTER, -1 * max_x, -1 * max_y);
				break;
            case grid::POS_UP_CENTER:
                // we're looking up, so move the point down
                y += max_y;
                break;
            case grid::POS_DOWN_CENTER:
                // opposite of UP_CENTER
                shift_pos(grid::POS_UP_CENTER, -1 * max_x, -1 * max_y);
                break;

			case grid::POS_LEFT:
				// we're looking left, so move the point right
				x += max_x;
				break;
			case grid::POS_RIGHT:
				// opposite of LEFT
				shift_pos(grid::POS_LEFT, -1 * max_x, -1 * max_y);
				break;
            case grid::POS_LEFT:
                // we're looking left, so move the point right
                x += max_x;
                break;
            case grid::POS_RIGHT:
                // opposite of LEFT
                shift_pos(grid::POS_LEFT, -1 * max_x, -1 * max_y);
                break;

			case grid::POS_CENTER:
			case grid::POS_UNKNOWN:
			case grid::POS_CURRENT:
				ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
				break;//???
			}
		}
            case grid::POS_CENTER:
            case grid::POS_UNKNOWN:
            case grid::POS_CURRENT:
                ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
                break;//???
            }
        }

		long x;
		long y;
        long x;
        long y;

	private:
		inline double abs_atan(const point& p2) const {
			long denom = DISTANCE(x, p2.x);
			if (denom == 0) {
				return M_PI_2;// avoid div0 (pi/2, from math.h)
			} else {
				return atan(DISTANCE(y, p2.y) / (double)denom);
			}
		}
	};
    private:
        inline double abs_atan(const point& p2) const {
            long denom = DISTANCE(x, p2.x);
            if (denom == 0) {
                return M_PI_2;// avoid div0 (pi/2, from math.h)
            } else {
                return atan(DISTANCE(y, p2.y) / (double)denom);
            }
        }
    };

	/* Finds and selects the nearest non-active point in the given direction and
	   returns true, or returns false if none was found. */
	bool select_nearest_in_direction(grid::POS dir, const std::vector<point>& pts,
			size_t active, size_t& select) {
		// find nearst point that matches the given direction
		double nearest_dist = 0;
		long nearest_i = -1;
		const point& active_pt = pts[active];
		DEBUG("search %lu for points %s of %ld,%ld:",
				pts.size()-1, grid::pos_str(dir), active_pt.x, active_pt.y);
		for (size_t i = 0; i < pts.size(); ++i) {
			if (i == active) {
				DEBUG("skip: %ld,%ld", pts[i].x, pts[i].y);
				continue;
			}
    /* Finds and selects the nearest non-active point in the given direction and
       returns true, or returns false if none was found. */
    bool select_nearest_in_direction(grid::POS dir, const std::vector<point>& pts,
            size_t active, size_t& select) {
        // find nearst point that matches the given direction
        double nearest_dist = 0;
        long nearest_i = -1;
        const point& active_pt = pts[active];
        DEBUG("search %lu for points %s of %ld,%ld:",
                pts.size()-1, grid::pos_str(dir), active_pt.x, active_pt.y);
        for (size_t i = 0; i < pts.size(); ++i) {
            if (i == active) {
                DEBUG("skip: %ld,%ld", pts[i].x, pts[i].y);
                continue;
            }

			const point& pt = pts[i];
			if (active_pt.direction(dir, pt)) {
				double dist = active_pt.distance(dir, pt);
				DEBUG("match!: %ld,%ld (dist %.02f)", pts[i].x, pts[i].y, dist);
				if (nearest_i < 0 || dist < nearest_dist) {
					nearest_i = i;
					nearest_dist = dist;
				}
			} else {
				DEBUG("miss: %ld,%ld", pts[i].x, pts[i].y);
			}
		}
            const point& pt = pts[i];
            if (active_pt.direction(dir, pt)) {
                double dist = active_pt.distance(dir, pt);
                DEBUG("match!: %ld,%ld (dist %.02f)", pts[i].x, pts[i].y, dist);
                if (nearest_i < 0 || dist < nearest_dist) {
                    nearest_i = i;
                    nearest_dist = dist;
                }
            } else {
                DEBUG("miss: %ld,%ld", pts[i].x, pts[i].y);
            }
        }

		if (nearest_i >= 0) {
			// found!
			select = nearest_i;
			return true;
		}
		return false;
	}
        if (nearest_i >= 0) {
            // found!
            select = nearest_i;
            return true;
        }
        return false;
    }

	/* When nothing is found in a given direction, this function determines what
	   the fallback direction ordering should be */
	grid::POS fallback_direction(grid::POS dir) {
		// go clockwise
		switch (dir) {
		case grid::POS_UP_CENTER:
			return grid::POS_UP_RIGHT;
		case grid::POS_UP_RIGHT:
			return grid::POS_RIGHT;
		case grid::POS_RIGHT:
			return grid::POS_DOWN_RIGHT;
		case grid::POS_DOWN_RIGHT:
			return grid::POS_DOWN_CENTER;
		case grid::POS_DOWN_CENTER:
			return grid::POS_DOWN_LEFT;
		case grid::POS_DOWN_LEFT:
			return grid::POS_LEFT;
		case grid::POS_LEFT:
			return grid::POS_UP_LEFT;
		case grid::POS_UP_LEFT:
			return grid::POS_UP_CENTER;
		case grid::POS_CENTER:
		case grid::POS_UNKNOWN:
		case grid::POS_CURRENT:
			ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
			break;
		}
		return grid::POS_UP_CENTER;//???
	}
    /* When nothing is found in a given direction, this function determines what
       the fallback direction ordering should be */
    grid::POS fallback_direction(grid::POS dir) {
        // go clockwise
        switch (dir) {
        case grid::POS_UP_CENTER:
            return grid::POS_UP_RIGHT;
        case grid::POS_UP_RIGHT:
            return grid::POS_RIGHT;
        case grid::POS_RIGHT:
            return grid::POS_DOWN_RIGHT;
        case grid::POS_DOWN_RIGHT:
            return grid::POS_DOWN_CENTER;
        case grid::POS_DOWN_CENTER:
            return grid::POS_DOWN_LEFT;
        case grid::POS_DOWN_LEFT:
            return grid::POS_LEFT;
        case grid::POS_LEFT:
            return grid::POS_UP_LEFT;
        case grid::POS_UP_LEFT:
            return grid::POS_UP_CENTER;
        case grid::POS_CENTER:
        case grid::POS_UNKNOWN:
        case grid::POS_CURRENT:
            ERROR("Internal error: invalid dir %s", grid::pos_str(dir));
            break;
        }
        return grid::POS_UP_CENTER;//???
    }
}

#define MAX_X(dim) (dim.x + dim.width)
#define MAX_Y(dim) (dim.y + dim.height)

void neighbor::select(grid::POS dir, const dim_list_t& all, size_t active, size_t& select) {
	if (all.size() <= 1) {
		select = 0;
		return;
	}
	if (dir == grid::POS_CURRENT) {
		select = active;
		return;
	}
    if (all.size() <= 1) {
        select = 0;
        return;
    }
    if (dir == grid::POS_CURRENT) {
        select = active;
        return;
    }

	// dimension -> center point and max window dimensions
	size_t bound_x = MAX_X(all[0]), bound_y = MAX_Y(all[0]);
	std::vector<point> pts;
	pts.reserve(all.size());
	for (dim_list_t::const_iterator iter = all.begin();
		 iter != all.end(); ++iter) {
		const Dimensions& d = *iter;
		size_t d_x = MAX_X(d), d_y = MAX_Y(d);
		if (d_x > bound_x) { bound_x = d_x; }
		if (d_y > bound_y) { bound_y = d_y; }
		pts.push_back(point(d));
	}
    // dimension -> center point and max window dimensions
    size_t bound_x = MAX_X(all[0]), bound_y = MAX_Y(all[0]);
    std::vector<point> pts;
    pts.reserve(all.size());
    for (dim_list_t::const_iterator iter = all.begin();
         iter != all.end(); ++iter) {
        const Dimensions& d = *iter;
        size_t d_x = MAX_X(d), d_y = MAX_Y(d);
        if (d_x > bound_x) { bound_x = d_x; }
        if (d_y > bound_y) { bound_y = d_y; }
        pts.push_back(point(d));
    }

	for (size_t i = 0; i < 4; ++i) {//try each of the four directions
		if (select_nearest_in_direction(dir, pts, active, select)) {
			return;
		}
		// not found. try shifting active pt for a wraparound search
		pts[active].shift_pos(dir, bound_x, bound_y);
		if (select_nearest_in_direction(dir, pts, active, select)) {
			return;
		}
		// still not found. undo shift and try next direction
		pts[active].shift_pos(dir, -1 * bound_x, -1 * bound_y);
		dir = fallback_direction(dir);
	}
    for (size_t i = 0; i < 4; ++i) {//try each of the four directions
        if (select_nearest_in_direction(dir, pts, active, select)) {
            return;
        }
        // not found. try shifting active pt for a wraparound search
        pts[active].shift_pos(dir, bound_x, bound_y);
        if (select_nearest_in_direction(dir, pts, active, select)) {
            return;
        }
        // still not found. undo shift and try next direction
        pts[active].shift_pos(dir, -1 * bound_x, -1 * bound_y);
        dir = fallback_direction(dir);
    }

	// STILL not found. just give up!
	select = active;
    // STILL not found. just give up!
    select = active;
}

M src/neighbor.h => src/neighbor.h +1 -1
@@ 27,7 27,7 @@
typedef std::vector<Dimensions> dim_list_t;

namespace neighbor {
	void select(grid::POS dir, const dim_list_t& all, size_t active, size_t& select);
    void select(grid::POS dir, const dim_list_t& all, size_t active, size_t& select);
}

#endif

M src/pos.h => src/pos.h +35 -35
@@ 20,42 20,42 @@
*/

namespace grid {
	/* These are the various available positions which may be set.
	 * CURRENT is "use the current position", useful when only switching monitors */
	enum POS {
		POS_UNKNOWN, POS_CURRENT,
		POS_UP_LEFT, POS_UP_CENTER, POS_UP_RIGHT,
		POS_LEFT, POS_CENTER, POS_RIGHT,
		POS_DOWN_LEFT, POS_DOWN_CENTER, POS_DOWN_RIGHT
	};
    /* These are the various available positions which may be set.
     * CURRENT is "use the current position", useful when only switching monitors */
    enum POS {
        POS_UNKNOWN, POS_CURRENT,
        POS_UP_LEFT, POS_UP_CENTER, POS_UP_RIGHT,
        POS_LEFT, POS_CENTER, POS_RIGHT,
        POS_DOWN_LEFT, POS_DOWN_CENTER, POS_DOWN_RIGHT
    };

	inline const char* pos_str(POS pos) {
		switch (pos) {
		case POS_UNKNOWN:
			return "UNKNOWN";
		case POS_CURRENT:
			return "CURRENT";
		case POS_UP_LEFT:
			return "UP_LEFT";
		case POS_UP_CENTER:
			return "UP_CENTER";
		case POS_UP_RIGHT:
			return "UP_RIGHT";
		case POS_LEFT:
			return "LEFT";
		case POS_CENTER:
			return "CENTER";
		case POS_RIGHT:
			return "RIGHT";
		case POS_DOWN_LEFT:
			return "DOWN_LEFT";
		case POS_DOWN_CENTER:
			return "DOWN_CENTER";
		case POS_DOWN_RIGHT:
			return "DOWN_RIGHT";
		}
		return "???";
	}
    inline const char* pos_str(POS pos) {
        switch (pos) {
        case POS_UNKNOWN:
            return "UNKNOWN";
        case POS_CURRENT:
            return "CURRENT";
        case POS_UP_LEFT:
            return "UP_LEFT";
        case POS_UP_CENTER:
            return "UP_CENTER";
        case POS_UP_RIGHT:
            return "UP_RIGHT";
        case POS_LEFT:
            return "LEFT";
        case POS_CENTER:
            return "CENTER";
        case POS_RIGHT:
            return "RIGHT";
        case POS_DOWN_LEFT:
            return "DOWN_LEFT";
        case POS_DOWN_CENTER:
            return "DOWN_CENTER";
        case POS_DOWN_RIGHT:
            return "DOWN_RIGHT";
        }
        return "???";
    }
}

#endif

M src/position.cpp => src/position.cpp +430 -430
@@ 22,456 22,456 @@
#include "position.h"

namespace {
	inline const char* mode_str(grid::MODE mode) {
		switch (mode) {
		case grid::MODE_UNKNOWN:
			return "UNKNOWN";
		case grid::MODE_TWO_COL:
			return "TWO_COL";
		case grid::MODE_THREE_COL_S:
			return "THREE_COL_S";
		case grid::MODE_THREE_COL_L:
			return "THREE_COL_L";
		default:
			break;
		}
		return "???";
	}
    inline const char* mode_str(grid::MODE mode) {
        switch (mode) {
        case grid::MODE_UNKNOWN:
            return "UNKNOWN";
        case grid::MODE_TWO_COL:
            return "TWO_COL";
        case grid::MODE_THREE_COL_S:
            return "THREE_COL_S";
        case grid::MODE_THREE_COL_L:
            return "THREE_COL_L";
        default:
            break;
        }
        return "???";
    }

#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;
	}
    // 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 StateToDim) */
bool PositionCalc::CurState(const Dimensions& viewport, State& out) const {
	//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 = grid::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_UP_LEFT;
					out.mode = grid::MODE_TWO_COL;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom left quadrant
					out.pos = grid::POS_DOWN_LEFT;
					out.mode = grid::MODE_TWO_COL;
				}
			} else if (NEAR(rel_x, viewport.width / 2.)) {
				if (NEAR(rel_y, 0)) {
					// top right quadrant
					out.pos = grid::POS_UP_RIGHT;
					out.mode = grid::MODE_TWO_COL;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom right quadrant
					out.pos = grid::POS_DOWN_RIGHT;
					out.mode = grid::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 = grid::MODE_TWO_COL;
			} else if (NEAR(rel_x, viewport.width / 2.)) {
				// right half
				out.pos = grid::POS_RIGHT;
				out.mode = grid::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_UP_LEFT;
					out.mode = grid::MODE_THREE_COL_S;
				} if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom left col
					out.pos = grid::POS_DOWN_LEFT;
					out.mode = grid::MODE_THREE_COL_S;
				}
			} else if (NEAR(rel_x, viewport.width / 3.)) {
				if (NEAR(rel_y, 0)) {
					// top center col
					out.pos = grid::POS_UP_CENTER;
					out.mode = grid::MODE_THREE_COL_S;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom center col
					out.pos = grid::POS_DOWN_CENTER;
					out.mode = grid::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_UP_RIGHT;
					out.mode = grid::MODE_THREE_COL_S;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom right col
					out.pos = grid::POS_DOWN_RIGHT;
					out.mode = grid::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 = grid::MODE_THREE_COL_S;
			} else if (NEAR(rel_x, viewport.width / 3.)) {
				// center col
				out.pos = grid::POS_CENTER;
				out.mode = grid::MODE_THREE_COL_S;
			} else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
				// right col
				out.pos = grid::POS_RIGHT;
				out.mode = grid::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_UP_LEFT;
					out.mode = grid::MODE_THREE_COL_L;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom left two cols
					out.pos = grid::POS_DOWN_LEFT;
					out.mode = grid::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_UP_RIGHT;
					out.mode = grid::MODE_THREE_COL_L;
				} else if (NEAR(rel_y, viewport.height / 2.)) {
					// bottom right two cols
					out.pos = grid::POS_DOWN_RIGHT;
					out.mode = grid::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 = grid::MODE_THREE_COL_L;
			} else if (NEAR(rel_x, viewport.width / 3.)) {
				// right two cols
				out.pos = grid::POS_RIGHT;
				out.mode = grid::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_UP_CENTER;
				out.mode = grid::MODE_THREE_COL_L;
			} else if (NEAR(rel_y, viewport.height / 2.)) {
				// bottom half
				out.pos = grid::POS_DOWN_CENTER;
				out.mode = grid::MODE_THREE_COL_L;
			}
		} else if (NEAR(window.height, viewport.height) &&
				NEAR(rel_y, 0)) {
			// full screen
			out.pos = grid::POS_CENTER;
			out.mode = grid::MODE_THREE_COL_L;
		}
	}
    //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 = grid::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_UP_LEFT;
                    out.mode = grid::MODE_TWO_COL;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom left quadrant
                    out.pos = grid::POS_DOWN_LEFT;
                    out.mode = grid::MODE_TWO_COL;
                }
            } else if (NEAR(rel_x, viewport.width / 2.)) {
                if (NEAR(rel_y, 0)) {
                    // top right quadrant
                    out.pos = grid::POS_UP_RIGHT;
                    out.mode = grid::MODE_TWO_COL;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom right quadrant
                    out.pos = grid::POS_DOWN_RIGHT;
                    out.mode = grid::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 = grid::MODE_TWO_COL;
            } else if (NEAR(rel_x, viewport.width / 2.)) {
                // right half
                out.pos = grid::POS_RIGHT;
                out.mode = grid::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_UP_LEFT;
                    out.mode = grid::MODE_THREE_COL_S;
                } if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom left col
                    out.pos = grid::POS_DOWN_LEFT;
                    out.mode = grid::MODE_THREE_COL_S;
                }
            } else if (NEAR(rel_x, viewport.width / 3.)) {
                if (NEAR(rel_y, 0)) {
                    // top center col
                    out.pos = grid::POS_UP_CENTER;
                    out.mode = grid::MODE_THREE_COL_S;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom center col
                    out.pos = grid::POS_DOWN_CENTER;
                    out.mode = grid::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_UP_RIGHT;
                    out.mode = grid::MODE_THREE_COL_S;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom right col
                    out.pos = grid::POS_DOWN_RIGHT;
                    out.mode = grid::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 = grid::MODE_THREE_COL_S;
            } else if (NEAR(rel_x, viewport.width / 3.)) {
                // center col
                out.pos = grid::POS_CENTER;
                out.mode = grid::MODE_THREE_COL_S;
            } else if (NEAR(rel_x, 2 * viewport.width / 3.)) {
                // right col
                out.pos = grid::POS_RIGHT;
                out.mode = grid::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_UP_LEFT;
                    out.mode = grid::MODE_THREE_COL_L;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom left two cols
                    out.pos = grid::POS_DOWN_LEFT;
                    out.mode = grid::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_UP_RIGHT;
                    out.mode = grid::MODE_THREE_COL_L;
                } else if (NEAR(rel_y, viewport.height / 2.)) {
                    // bottom right two cols
                    out.pos = grid::POS_DOWN_RIGHT;
                    out.mode = grid::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 = grid::MODE_THREE_COL_L;
            } else if (NEAR(rel_x, viewport.width / 3.)) {
                // right two cols
                out.pos = grid::POS_RIGHT;
                out.mode = grid::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_UP_CENTER;
                out.mode = grid::MODE_THREE_COL_L;
            } else if (NEAR(rel_y, viewport.height / 2.)) {
                // bottom half
                out.pos = grid::POS_DOWN_CENTER;
                out.mode = grid::MODE_THREE_COL_L;
            }
        } else if (NEAR(window.height, viewport.height) &&
                NEAR(rel_y, 0)) {
            // full screen
            out.pos = grid::POS_CENTER;
            out.mode = grid::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;
    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 PositionCalc::NextState(const State& cur, grid::POS req_pos, State& out) const {
	if (req_pos == grid::POS_UNKNOWN) {// nice to have
		ERROR("Position '%s' was requested. Internal error?", pos_str(req_pos));
		return false;
	}
	if (req_pos == grid::POS_CURRENT) {
		out = cur;
	} else if (cur.pos == req_pos) {
		// position is same, so rotate mode
		out.pos = cur.pos;
		switch (cur.pos) {
		case grid::POS_UP_CENTER:
		case grid::POS_CENTER:
		case grid::POS_DOWN_CENTER:
			// for center col: 3x2L -> 3x2S (-> 3x2L) (no 2x2)
			switch (cur.mode) {
			case grid::MODE_THREE_COL_L:
				out.mode = grid::MODE_THREE_COL_S;
				break;
			case grid::MODE_THREE_COL_S:
			default:
				out.mode = grid::MODE_THREE_COL_L;
			}
			break;
		default:
			// for everything else: 2x2 -> 3x2L -> 3x2S (-> 2x2)
			switch (cur.mode) {
			case grid::MODE_UNKNOWN:
			case grid::MODE_THREE_COL_L:
				out.mode = grid::MODE_THREE_COL_S;
				break;
			case grid::MODE_TWO_COL:
				out.mode = grid::MODE_THREE_COL_L;
				break;
			case grid::MODE_THREE_COL_S:
			default:
				out.mode = grid::MODE_TWO_COL;
			}
		}
	} else {
		// new position, so start with initial mode
		out.pos = req_pos;
		switch (req_pos) {
		case grid::POS_UP_CENTER:
		case grid::POS_CENTER:
		case grid::POS_DOWN_CENTER:
			// for center col: start with 3x2L
			out.mode = grid::MODE_THREE_COL_L;
			break;
		default:
			// for everything else: start with 2x2
			out.mode = grid::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;
    if (req_pos == grid::POS_UNKNOWN) {// nice to have
        ERROR("Position '%s' was requested. Internal error?", pos_str(req_pos));
        return false;
    }
    if (req_pos == grid::POS_CURRENT) {
        out = cur;
    } else if (cur.pos == req_pos) {
        // position is same, so rotate mode
        out.pos = cur.pos;
        switch (cur.pos) {
        case grid::POS_UP_CENTER:
        case grid::POS_CENTER:
        case grid::POS_DOWN_CENTER:
            // for center col: 3x2L -> 3x2S (-> 3x2L) (no 2x2)
            switch (cur.mode) {
            case grid::MODE_THREE_COL_L:
                out.mode = grid::MODE_THREE_COL_S;
                break;
            case grid::MODE_THREE_COL_S:
            default:
                out.mode = grid::MODE_THREE_COL_L;
            }
            break;
        default:
            // for everything else: 2x2 -> 3x2L -> 3x2S (-> 2x2)
            switch (cur.mode) {
            case grid::MODE_UNKNOWN:
            case grid::MODE_THREE_COL_L:
                out.mode = grid::MODE_THREE_COL_S;
                break;
            case grid::MODE_TWO_COL:
                out.mode = grid::MODE_THREE_COL_L;
                break;
            case grid::MODE_THREE_COL_S:
            default:
                out.mode = grid::MODE_TWO_COL;
            }
        }
    } else {
        // new position, so start with initial mode
        out.pos = req_pos;
        switch (req_pos) {
        case grid::POS_UP_CENTER:
        case grid::POS_CENTER:
        case grid::POS_DOWN_CENTER:
            // for center col: start with 3x2L
            out.mode = grid::MODE_THREE_COL_L;
            break;
        default:
            // for everything else: start with 2x2
            out.mode = grid::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;
}

/* 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 PositionCalc::StateToDim(const Dimensions& viewport, const State& state,
		Dimensions& out) const {
	bool ret = true;
	long rel_x = 0, rel_y = 0;//coordinates relative to viewport
	switch (state.mode) {
	case grid::MODE_TWO_COL:
		switch (state.pos) {
		case grid::POS_UP_LEFT:// top left quadrant
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width / 2.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_UP_CENTER:// invalid, use mode THREE_COL_S/L
			return false;
		case grid::POS_UP_RIGHT:// top right quadrant
			rel_x = viewport.width / 2.;
			rel_y = 0;
			out.width = viewport.width / 2.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_LEFT:// left half
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width / 2.;
			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;
			out.width = viewport.width / 2.;
			out.height = viewport.height;
			break;
		case grid::POS_DOWN_LEFT:// bottom left quadrant
			rel_x = 0;
			rel_y = viewport.height / 2.;
			out.width = viewport.width / 2.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_DOWN_CENTER:// invalid, use mode THREE_COL_S/L
			return false;
		case grid::POS_DOWN_RIGHT:// bottom right quadrant
			rel_x = viewport.width / 2.;
			rel_y = viewport.height / 2.;
			out.width = viewport.width / 2.;
			out.height = viewport.height / 2.;
			break;
		default:
			ret = false;
			break;
		}
		break;
	case grid::MODE_THREE_COL_S:
		switch (state.pos) {
		case grid::POS_UP_LEFT:// top left col
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_UP_CENTER:// top center col
			rel_x = viewport.width / 3.;
			rel_y = 0;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_UP_RIGHT:// top right col
			rel_x = 2 * viewport.width / 3.;
			rel_y = 0;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_LEFT:// left third
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width / 3.;
			out.height = viewport.height;
			break;
		case grid::POS_CENTER:// center col
			rel_x = viewport.width / 3.;
			rel_y = 0;
			out.width = viewport.width / 3.;
			out.height = viewport.height;
			break;
		case grid::POS_RIGHT:// right third
			rel_x = 2 * viewport.width / 3.;
			rel_y = 0;
			out.width = viewport.width / 3;
			out.height = viewport.height;
			break;
		case grid::POS_DOWN_LEFT:// bottom left col
			rel_x = 0;
			rel_y = viewport.height / 2.;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_DOWN_CENTER:// bottom center col
			rel_x = viewport.width / 3.;
			rel_y = viewport.height / 2.;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_DOWN_RIGHT:// bottom right col
			rel_x = 2 * viewport.width / 3.;
			rel_y = viewport.height / 2.;
			out.width = viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		default:
			ret = false;
		}
		break;
	case grid::MODE_THREE_COL_L:
		switch (state.pos) {
		case grid::POS_UP_LEFT:// top left two cols
			rel_x = 0;
			rel_y = 0;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_UP_CENTER:// top half
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_UP_RIGHT:// top right two cols
			rel_x = viewport.width / 3.;
			rel_y = 0;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_LEFT:// left two thirds
			rel_x = 0;
			rel_y = 0;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height;
			break;
		case grid::POS_CENTER:// full screen
			rel_x = 0;
			rel_y = 0;
			out.width = viewport.width;
			out.height = viewport.height;
			break;
		case grid::POS_RIGHT:// right two thirds
			rel_x = viewport.width / 3.;
			rel_y = 0;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height;
			break;
		case grid::POS_DOWN_LEFT:// bottom left two cols
			rel_x = 0;
			rel_y = viewport.height / 2.;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_DOWN_CENTER:// bottom half
			rel_x = 0;
			rel_y = viewport.height / 2.;
			out.width = viewport.width;
			out.height = viewport.height / 2.;
			break;
		case grid::POS_DOWN_RIGHT:// bottom right two cols
			rel_x = viewport.width / 3.;
			rel_y = viewport.height / 2.;
			out.width = 2 * viewport.width / 3.;
			out.height = viewport.height / 2.;
			break;
		default:
			ret = false;
		}
		break;
	default:
		ret = false;
	}
	if (ret) {
		//convert relative pos to absolute:
		out.x = rel_x + viewport.x;
		out.y = rel_y + viewport.y;
		DEBUG("pos=%s mode=%s -> %ldx %ldy %luw %luh",
				pos_str(state.pos), mode_str(state.mode),
				out.x, out.y, out.width, out.height);
	} else {
		ERROR("Bad pos=%s + mode=%s", pos_str(state.pos), mode_str(state.mode));
	}
	return ret;
        Dimensions& out) const {
    bool ret = true;
    long rel_x = 0, rel_y = 0;//coordinates relative to viewport
    switch (state.mode) {
    case grid::MODE_TWO_COL:
        switch (state.pos) {
        case grid::POS_UP_LEFT:// top left quadrant
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width / 2.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_UP_CENTER:// invalid, use mode THREE_COL_S/L
            return false;
        case grid::POS_UP_RIGHT:// top right quadrant
            rel_x = viewport.width / 2.;
            rel_y = 0;
            out.width = viewport.width / 2.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_LEFT:// left half
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width / 2.;
            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;
            out.width = viewport.width / 2.;
            out.height = viewport.height;
            break;
        case grid::POS_DOWN_LEFT:// bottom left quadrant
            rel_x = 0;
            rel_y = viewport.height / 2.;
            out.width = viewport.width / 2.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_DOWN_CENTER:// invalid, use mode THREE_COL_S/L
            return false;
        case grid::POS_DOWN_RIGHT:// bottom right quadrant
            rel_x = viewport.width / 2.;
            rel_y = viewport.height / 2.;
            out.width = viewport.width / 2.;
            out.height = viewport.height / 2.;
            break;
        default:
            ret = false;
            break;
        }
        break;
    case grid::MODE_THREE_COL_S:
        switch (state.pos) {
        case grid::POS_UP_LEFT:// top left col
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_UP_CENTER:// top center col
            rel_x = viewport.width / 3.;
            rel_y = 0;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_UP_RIGHT:// top right col
            rel_x = 2 * viewport.width / 3.;
            rel_y = 0;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_LEFT:// left third
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width / 3.;
            out.height = viewport.height;
            break;
        case grid::POS_CENTER:// center col
            rel_x = viewport.width / 3.;
            rel_y = 0;
            out.width = viewport.width / 3.;
            out.height = viewport.height;
            break;
        case grid::POS_RIGHT:// right third
            rel_x = 2 * viewport.width / 3.;
            rel_y = 0;
            out.width = viewport.width / 3;
            out.height = viewport.height;
            break;
        case grid::POS_DOWN_LEFT:// bottom left col
            rel_x = 0;
            rel_y = viewport.height / 2.;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_DOWN_CENTER:// bottom center col
            rel_x = viewport.width / 3.;
            rel_y = viewport.height / 2.;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_DOWN_RIGHT:// bottom right col
            rel_x = 2 * viewport.width / 3.;
            rel_y = viewport.height / 2.;
            out.width = viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        default:
            ret = false;
        }
        break;
    case grid::MODE_THREE_COL_L:
        switch (state.pos) {
        case grid::POS_UP_LEFT:// top left two cols
            rel_x = 0;
            rel_y = 0;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_UP_CENTER:// top half
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_UP_RIGHT:// top right two cols
            rel_x = viewport.width / 3.;
            rel_y = 0;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_LEFT:// left two thirds
            rel_x = 0;
            rel_y = 0;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height;
            break;
        case grid::POS_CENTER:// full screen
            rel_x = 0;
            rel_y = 0;
            out.width = viewport.width;
            out.height = viewport.height;
            break;
        case grid::POS_RIGHT:// right two thirds
            rel_x = viewport.width / 3.;
            rel_y = 0;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height;
            break;
        case grid::POS_DOWN_LEFT:// bottom left two cols
            rel_x = 0;
            rel_y = viewport.height / 2.;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_DOWN_CENTER:// bottom half
            rel_x = 0;
            rel_y = viewport.height / 2.;
            out.width = viewport.width;
            out.height = viewport.height / 2.;
            break;
        case grid::POS_DOWN_RIGHT:// bottom right two cols
            rel_x = viewport.width / 3.;
            rel_y = viewport.height / 2.;
            out.width = 2 * viewport.width / 3.;
            out.height = viewport.height / 2.;
            break;
        default:
            ret = false;
        }
        break;
    default:
        ret = false;
    }
    if (ret) {
        //convert relative pos to absolute:
        out.x = rel_x + viewport.x;
        out.y = rel_y + viewport.y;
        DEBUG("pos=%s mode=%s -> %ldx %ldy %luw %luh",
                pos_str(state.pos), mode_str(state.mode),
                out.x, out.y, out.width, out.height);
    } else {
        ERROR("Bad pos=%s + mode=%s", pos_str(state.pos), mode_str(state.mode));
    }
    return ret;
}

void PositionCalc::ViewportToDim(const Dimensions& cur_viewport,
		const Dimensions& next_viewport, Dimensions& out) const {
	// just do an exact scaling to the new viewport
	if (cur_viewport.width == 0 || cur_viewport.height == 0) {//nice to have, avoid div0
		out = next_viewport;//just throw something together and get out
		return;
	}
	const double ratio_x = next_viewport.width / (double)cur_viewport.width,
		ratio_y = next_viewport.height / (double)cur_viewport.height;
        const Dimensions& next_viewport, Dimensions& out) const {
    // just do an exact scaling to the new viewport
    if (cur_viewport.width == 0 || cur_viewport.height == 0) {//nice to have, avoid div0
        out = next_viewport;//just throw something together and get out
        return;
    }
    const double ratio_x = next_viewport.width / (double)cur_viewport.width,
        ratio_y = next_viewport.height / (double)cur_viewport.height;

	// avoid implicit floor to avoid cumulative rounding error:
	out.x = round((window.x - cur_viewport.x) * ratio_x + next_viewport.x);
	out.y = round((window.y - cur_viewport.y) * ratio_y + next_viewport.y);
	out.width = round(window.width * ratio_x);
	out.height = round(window.height * ratio_y);
    // avoid implicit floor to avoid cumulative rounding error:
    out.x = round((window.x - cur_viewport.x) * ratio_x + next_viewport.x);
    out.y = round((window.y - cur_viewport.y) * ratio_y + next_viewport.y);
    out.width = round(window.width * ratio_x);
    out.height = round(window.height * ratio_y);
}

M src/position.h => src/position.h +28 -28
@@ 23,48 23,48 @@
#include "dimensions.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
	};
    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
    };
}

struct State {
	State() : pos(grid::POS_UNKNOWN), mode(grid::MODE_UNKNOWN) { }
    State() : pos(grid::POS_UNKNOWN), mode(grid::MODE_UNKNOWN) { }

	grid::POS pos;
	grid::MODE mode;
    grid::POS pos;
    grid::MODE mode;
};

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

	/* Produces an autodetected state of this window using its current
	 * coordinates. Returns true on success, else false. */
	bool CurState(const Dimensions& viewport, State& cur_state) const;
    /* Produces an autodetected state of this window using its current
     * coordinates. Returns true on success, else false. */
    bool CurState(const Dimensions& viewport, State& cur_state) const;

	/* Given a current state and requested position for the window, calculates
	 * its next state. Returns true on success, else false. */
	bool NextState(const State& cur_state, grid::POS req_pos,
			State& next_state) const;
    /* Given a current state and requested position for the window, calculates
     * its next state. Returns true on success, else false. */
    bool NextState(const State& cur_state, grid::POS req_pos,
            State& next_state) const;

	/* Given a state for the window, calculates the dimensions of that position.
	 * Returns true on success, else false. */
	bool StateToDim(const Dimensions& viewport, const State& state,
			Dimensions& out) const;
    /* Given a state for the window, calculates the dimensions of that position.
     * Returns true on success, else false. */
    bool StateToDim(const Dimensions& viewport, const State& state,
            Dimensions& out) const;

	/* Special case of StateToDim: Given the current viewport and next viewport
	 * for a window with no autodetected state, calculates a reasonable location
	 * in the new viewport. */
	void ViewportToDim(const Dimensions& cur_viewport,
			const Dimensions& next_viewport, Dimensions& out) const;
    /* Special case of StateToDim: Given the current viewport and next viewport
     * for a window with no autodetected state, calculates a reasonable location
     * in the new viewport. */
    void ViewportToDim(const Dimensions& cur_viewport,
            const Dimensions& next_viewport, Dimensions& out) const;

private:
	const Dimensions window;
    const Dimensions window;
};

#endif

M src/viewport-imp-ewmh.cpp => src/viewport-imp-ewmh.cpp +57 -57
@@ 21,66 21,66 @@
#include "x11-util.h"

bool viewport::ewmh::get_viewports(Display* disp, const Dimensions& /*activewin*/,
		dim_list_t& viewports_out, size_t& active_out) {
	//get current workspace
	unsigned long cur_workspace;
	{
		unsigned long* cur_ptr;
		static Atom curdesk_msg = XInternAtom(disp, "_NET_CURRENT_DESKTOP", False);
		if (!(cur_ptr = (unsigned long *)x11_util::get_property(disp, DefaultRootWindow(disp),
								XA_CARDINAL, curdesk_msg, NULL))) {
			ERROR_DIR("unable to retrieve current desktop");
			return false;
		}
		cur_workspace = *cur_ptr;
		x11_util::free_property(cur_ptr);
	}
        dim_list_t& viewports_out, size_t& active_out) {
    //get current workspace
    unsigned long cur_workspace;
    {
        unsigned long* cur_ptr;
        static Atom curdesk_msg = XInternAtom(disp, "_NET_CURRENT_DESKTOP", False);
        if (!(cur_ptr = (unsigned long *)x11_util::get_property(disp, DefaultRootWindow(disp),
                                XA_CARDINAL, curdesk_msg, NULL))) {
            ERROR_DIR("unable to retrieve current desktop");
            return false;
        }
        cur_workspace = *cur_ptr;
        x11_util::free_property(cur_ptr);
    }

	unsigned long* area;
	size_t area_count = 0;//number of areas returned, one per workspace. each area contains 4 ulongs.
	static Atom workarea_msg = XInternAtom(disp, "_NET_WORKAREA", False);
	if (!(area = (unsigned long*)x11_util::get_property(disp, DefaultRootWindow(disp),
							XA_CARDINAL, workarea_msg, &area_count))) {
		ERROR_DIR("unable to retrieve spanning workarea");
		return false;
	}
	if (area_count == 0) {
		ERROR_DIR("unable to retrieve spanning workarea.");
		x11_util::free_property(area);
		return false;
	}
	if (cur_workspace >= (area_count * 4) || area_count % 4 != 0) {//nice to have
		ERROR("got invalid workarea count: %d (cur workspace: %d)",
				area_count, cur_workspace);
		x11_util::free_property(area);
		return false;
	}
    unsigned long* area;
    size_t area_count = 0;//number of areas returned, one per workspace. each area contains 4 ulongs.
    static Atom workarea_msg = XInternAtom(disp, "_NET_WORKAREA", False);
    if (!(area = (unsigned long*)x11_util::get_property(disp, DefaultRootWindow(disp),
                            XA_CARDINAL, workarea_msg, &area_count))) {
        ERROR_DIR("unable to retrieve spanning workarea");
        return false;
    }
    if (area_count == 0) {
        ERROR_DIR("unable to retrieve spanning workarea.");
        x11_util::free_property(area);
        return false;
    }
    if (cur_workspace >= (area_count * 4) || area_count % 4 != 0) {//nice to have
        ERROR("got invalid workarea count: %d (cur workspace: %d)",
                area_count, cur_workspace);
        x11_util::free_property(area);
        return false;
    }

	if (config::debug_enabled) {
		for (size_t i = 0; i < area_count/4; ++i) {
			if (i == cur_workspace) {
				DEBUG("active workspace %lu of %lu: %lux %luy %luw %luh",
						i+1, area_count/4,
						area[i*4], area[i*4+1], area[i*4+2], area[i*4+3]);
			} else {
				DEBUG("inactive workspace %lu of %lu: %lux %luy %luw %luh",
						i+1, area_count/4,
						area[i*4], area[i*4+1], area[i*4+2], area[i*4+3]);
			}
		}
	}
    if (config::debug_enabled) {
        for (size_t i = 0; i < area_count/4; ++i) {
            if (i == cur_workspace) {
                DEBUG("active workspace %lu of %lu: %lux %luy %luw %luh",
                        i+1, area_count/4,
                        area[i*4], area[i*4+1], area[i*4+2], area[i*4+3]);
            } else {
                DEBUG("inactive workspace %lu of %lu: %lux %luy %luw %luh",
                        i+1, area_count/4,
                        area[i*4], area[i*4+1], area[i*4+2], area[i*4+3]);
            }
        }
    }

	//set current workspace as viewport
	viewports_out.clear();//nice to have
	viewports_out.push_back(Dimensions());
	active_out = 0;
    //set current workspace as viewport
    viewports_out.clear();//nice to have
    viewports_out.push_back(Dimensions());
    active_out = 0;

	Dimensions& v = viewports_out.back();
	v.x = area[cur_workspace*4];
	v.y = area[(cur_workspace*4)+1];
	v.width = area[(cur_workspace*4)+2];
	v.height = area[(cur_workspace*4)+3];
    Dimensions& v = viewports_out.back();
    v.x = area[cur_workspace*4];
    v.y = area[(cur_workspace*4)+1];
    v.width = area[(cur_workspace*4)+2];
    v.height = area[(cur_workspace*4)+3];

	x11_util::free_property(area);
	return true;
    x11_util::free_property(area);
    return true;
}

M src/viewport-imp-ewmh.h => src/viewport-imp-ewmh.h +4 -4
@@ 27,10 27,10 @@
typedef std::vector<Dimensions> dim_list_t;

namespace viewport {
	namespace ewmh {
		bool get_viewports(Display* disp, const Dimensions& activewin,
				dim_list_t& viewports_out, size_t& active_out);
	}
    namespace ewmh {
        bool get_viewports(Display* disp, const Dimensions& activewin,
                dim_list_t& viewports_out, size_t& active_out);
    }
}

#endif

M src/viewport-imp-xinerama.cpp => src/viewport-imp-xinerama.cpp +206 -206
@@ 26,216 26,216 @@
#define MAX(a,b) (((a) > (b)) ? (a) : (b))

static int INTERSECTION(int a1, int a2, int b1, int b2) {
	int ret = (a2 < b1 || b2 < a1) ? 0 :
		(a1 >= b1) ?
		((a2 >= b2) ? (b2 - a1) : (a2 - a1)) :
		((a2 >= b2) ? (b2 - b1) : (a2 - b1));
	DEBUG("%d-%d x %d-%d = %d", a1, a2, b1, b2, ret);
	return ret;
    int ret = (a2 < b1 || b2 < a1) ? 0 :
        (a1 >= b1) ?
        ((a2 >= b2) ? (b2 - a1) : (a2 - a1)) :
        ((a2 >= b2) ? (b2 - b1) : (a2 - b1));
    DEBUG("%d-%d x %d-%d = %d", a1, a2, b1, b2, ret);
    return ret;
}

namespace {
	bool get_screens(Display* disp, const Dimensions& activewin,
			Dimensions& bounding_box, dim_list_t& viewports,
			size_t& active_viewport) {
		int screen_count = 0;
		XineramaScreenInfo* screens = XineramaQueryScreens(disp, &screen_count);
		if (screens == NULL || screen_count == 0) {
			DEBUG_DIR("xinerama not loaded or unavailable");
			if (screens != NULL) {
				XFree(screens);
			}
			return false;
		}

		//initialize bounding box to something
		bounding_box.x = screens[0].x_org;
		bounding_box.y = screens[0].y_org;
		int bound_max_x = screens[0].x_org + screens[0].width,
			bound_max_y = screens[0].y_org + screens[0].height;

		// search for largest overlap between active window and xinerama screen.
		// the screen with the most overlap is the 'active screen'
		int active_overlap = 0;

		for (int i = 0; i < screen_count; ++i) {
			const XineramaScreenInfo& screen = screens[i];

			// grow bounding box
			bounding_box.x = MIN(bounding_box.x, screen.x_org);
			bounding_box.y = MIN(bounding_box.y, screen.y_org);
			bound_max_x = MAX(bound_max_x, screen.x_org + screen.width);
			bound_max_y = MAX(bound_max_y, screen.y_org + screen.height);

			// add viewport
			viewports.push_back(Dimensions());
			Dimensions& v = viewports.back();
			v.x = screen.x_org;
			v.y = screen.y_org;
			v.width = screen.width;
			v.height = screen.height;

			// check active overlap
			int overlap =
				INTERSECTION(screen.x_org, screen.x_org + screen.width,
						activewin.x, activewin.x + activewin.width) *
				INTERSECTION(screen.y_org, screen.y_org + screen.height,
						activewin.y, activewin.y + activewin.height);

			DEBUG("screen %d of %d: %dx %dy %dw %dh (overlap %d)",
					i+1, screen_count,
					screen.x_org, screen.y_org, screen.width, screen.height,
					overlap);

			if (overlap > active_overlap) {
				// overlap is bigger, update active counters
				active_overlap = overlap;
				active_viewport = i;
			}
		};

		DEBUG("active screen is %lu of %d", active_viewport+1, screen_count);

		bounding_box.width = bound_max_x - bounding_box.x;
		bounding_box.height = bound_max_y - bounding_box.y;
		DEBUG("desktop bounding box: %ldx %ldy %ldw %ldh",
				bounding_box.x, bounding_box.y,
				bounding_box.width, bounding_box.height);

		XFree(screens);
		return true;
	}

	struct strut {
		enum TYPE {
			LEFT, RIGHT, TOP, BOTTOM
		};

		strut(TYPE type, size_t width, size_t min, size_t max)
			: type(type), width(width), min(min), max(max) { }

		TYPE type;
		size_t width, min, max;
	};

	bool get_struts(Display* disp, std::vector<strut>& out) {
		Window* clients;
		size_t client_count = 0;
		static Atom clientlist_msg = XInternAtom(disp, "_NET_CLIENT_LIST", False);
		if (!(clients = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
								XA_WINDOW, clientlist_msg, &client_count))) {
			ERROR_DIR("unable to retrieve list of clients");
			return false;
		}
		for (size_t i = 0; i < client_count; ++i) {
			unsigned long* xstrut;
			size_t xstrut_count = 0;//number of strut values for this client (should always be 12)
			static Atom strut_msg = XInternAtom(disp, "_NET_WM_STRUT_PARTIAL", False);
			if (!(xstrut = (unsigned long*)x11_util::get_property(disp, clients[i],
									XA_CARDINAL, strut_msg, &xstrut_count))) {
				//DEBUG("client %lu of %lu lacks struts", i+1, client_count);
				continue;
			}
			if (xstrut_count != 12) {//nice to have
				ERROR_DIR("incorrect number of strut values: got %lu, expected 12", xstrut_count);
				x11_util::free_property(clients);
				x11_util::free_property(xstrut);
				return false;
			}

			DEBUG("client %lu of %lu struts: left:%lu@%lu-%lu right:%lu@%lu-%lu top:%lu@%lu-%lu bot:%lu@%lu-%lu",
					i+1, client_count,
					xstrut[0], xstrut[4], xstrut[5],
					xstrut[1], xstrut[6], xstrut[7],
					xstrut[2], xstrut[8], xstrut[9],
					xstrut[3], xstrut[10], xstrut[11]);

			//left
			if (xstrut[0] > 0) {
				out.push_back(strut(strut::LEFT, xstrut[0], xstrut[4], xstrut[5]));
			}
			//right
			if (xstrut[1] > 0) {
				out.push_back(strut(strut::RIGHT, xstrut[1], xstrut[6], xstrut[7]));
			}
			//top
			if (xstrut[2] > 0) {
				out.push_back(strut(strut::TOP, xstrut[2], xstrut[8], xstrut[9]));
			}
			//bot
			if (xstrut[3] > 0) {
				out.push_back(strut(strut::BOTTOM, xstrut[3], xstrut[10], xstrut[11]));
			}

			x11_util::free_property(xstrut);
		}
		x11_util::free_property(clients);
		return true;
	}

	void trim_screen(const Dimensions& bound, const std::vector<strut>& struts,
			Dimensions& screen) {
		//for simpler math, operate on things in terms of min/max
		long screen_max_x = screen.x + screen.width,
			screen_max_y = screen.y + screen.height;

		for (std::vector<strut>::const_iterator iter = struts.begin();
			 iter != struts.end(); ++iter) {
			switch (iter->type) {
			case strut::LEFT:
				// first check if it intersects our screen's min/max y
				if (INTERSECTION(screen.y, screen_max_y, iter->min, iter->max) != 0) {
					//then check if the strut (relative to the bounding box) actually exceeds our min x
					screen.x = MAX(screen.x, (long)iter->width - bound.x);
				}
				break;
			case strut::RIGHT:
				if (INTERSECTION(screen.y, screen_max_y, iter->min, iter->max) != 0) {
					long bound_max_x = bound.x + bound.width;
					screen_max_x = MIN(screen_max_x, bound_max_x - iter->width);
				}
				break;
			case strut::TOP:
				if (INTERSECTION(screen.x, screen_max_x, iter->min, iter->max) != 0) {
					screen.y = MAX(screen.y, (long)iter->width - bound.y);
				}
				break;
			case strut::BOTTOM:
				if (INTERSECTION(screen.x, screen_max_x, iter->min, iter->max) != 0) {
					long bound_max_y = bound.y + bound.height;
					screen_max_y = MIN(screen_max_y, bound_max_y - iter->width);
				}
				break;
			}
		}

		screen.width = screen_max_x - screen.x;
		screen.height = screen_max_y - screen.y;

		DEBUG("trimmed: %ldx %ldy %ldw %ldh",
				screen.x, screen.y, screen.width, screen.height);
	}
    bool get_screens(Display* disp, const Dimensions& activewin,
            Dimensions& bounding_box, dim_list_t& viewports,
            size_t& active_viewport) {
        int screen_count = 0;
        XineramaScreenInfo* screens = XineramaQueryScreens(disp, &screen_count);
        if (screens == NULL || screen_count == 0) {
            DEBUG_DIR("xinerama not loaded or unavailable");
            if (screens != NULL) {
                XFree(screens);
            }
            return false;
        }

        //initialize bounding box to something
        bounding_box.x = screens[0].x_org;
        bounding_box.y = screens[0].y_org;
        int bound_max_x = screens[0].x_org + screens[0].width,
            bound_max_y = screens[0].y_org + screens[0].height;

        // search for largest overlap between active window and xinerama screen.
        // the screen with the most overlap is the 'active screen'
        int active_overlap = 0;

        for (int i = 0; i < screen_count; ++i) {
            const XineramaScreenInfo& screen = screens[i];

            // grow bounding box
            bounding_box.x = MIN(bounding_box.x, screen.x_org);
            bounding_box.y = MIN(bounding_box.y, screen.y_org);
            bound_max_x = MAX(bound_max_x, screen.x_org + screen.width);
            bound_max_y = MAX(bound_max_y, screen.y_org + screen.height);

            // add viewport
            viewports.push_back(Dimensions());
            Dimensions& v = viewports.back();
            v.x = screen.x_org;
            v.y = screen.y_org;
            v.width = screen.width;
            v.height = screen.height;

            // check active overlap
            int overlap =
                INTERSECTION(screen.x_org, screen.x_org + screen.width,
                        activewin.x, activewin.x + activewin.width) *
                INTERSECTION(screen.y_org, screen.y_org + screen.height,
                        activewin.y, activewin.y + activewin.height);

            DEBUG("screen %d of %d: %dx %dy %dw %dh (overlap %d)",
                    i+1, screen_count,
                    screen.x_org, screen.y_org, screen.width, screen.height,
                    overlap);

            if (overlap > active_overlap) {
                // overlap is bigger, update active counters
                active_overlap = overlap;
                active_viewport = i;
            }
        };

        DEBUG("active screen is %lu of %d", active_viewport+1, screen_count);

        bounding_box.width = bound_max_x - bounding_box.x;
        bounding_box.height = bound_max_y - bounding_box.y;
        DEBUG("desktop bounding box: %ldx %ldy %ldw %ldh",
                bounding_box.x, bounding_box.y,
                bounding_box.width, bounding_box.height);

        XFree(screens);
        return true;
    }

    struct strut {
        enum TYPE {
            LEFT, RIGHT, TOP, BOTTOM
        };

        strut(TYPE type, size_t width, size_t min, size_t max)
            : type(type), width(width), min(min), max(max) { }

        TYPE type;
        size_t width, min, max;
    };

    bool get_struts(Display* disp, std::vector<strut>& out) {
        Window* clients;
        size_t client_count = 0;
        static Atom clientlist_msg = XInternAtom(disp, "_NET_CLIENT_LIST", False);
        if (!(clients = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
                                XA_WINDOW, clientlist_msg, &client_count))) {
            ERROR_DIR("unable to retrieve list of clients");
            return false;
        }
        for (size_t i = 0; i < client_count; ++i) {
            unsigned long* xstrut;
            size_t xstrut_count = 0;//number of strut values for this client (should always be 12)
            static Atom strut_msg = XInternAtom(disp, "_NET_WM_STRUT_PARTIAL", False);
            if (!(xstrut = (unsigned long*)x11_util::get_property(disp, clients[i],
                                    XA_CARDINAL, strut_msg, &xstrut_count))) {
                //DEBUG("client %lu of %lu lacks struts", i+1, client_count);
                continue;
            }
            if (xstrut_count != 12) {//nice to have
                ERROR_DIR("incorrect number of strut values: got %lu, expected 12", xstrut_count);
                x11_util::free_property(clients);
                x11_util::free_property(xstrut);
                return false;
            }

            DEBUG("client %lu of %lu struts: left:%lu@%lu-%lu right:%lu@%lu-%lu top:%lu@%lu-%lu bot:%lu@%lu-%lu",
                    i+1, client_count,
                    xstrut[0], xstrut[4], xstrut[5],
                    xstrut[1], xstrut[6], xstrut[7],
                    xstrut[2], xstrut[8], xstrut[9],
                    xstrut[3], xstrut[10], xstrut[11]);

            //left
            if (xstrut[0] > 0) {
                out.push_back(strut(strut::LEFT, xstrut[0], xstrut[4], xstrut[5]));
            }
            //right
            if (xstrut[1] > 0) {
                out.push_back(strut(strut::RIGHT, xstrut[1], xstrut[6], xstrut[7]));
            }
            //top
            if (xstrut[2] > 0) {
                out.push_back(strut(strut::TOP, xstrut[2], xstrut[8], xstrut[9]));
            }
            //bot
            if (xstrut[3] > 0) {
                out.push_back(strut(strut::BOTTOM, xstrut[3], xstrut[10], xstrut[11]));
            }

            x11_util::free_property(xstrut);
        }
        x11_util::free_property(clients);
        return true;
    }

    void trim_screen(const Dimensions& bound, const std::vector<strut>& struts,
            Dimensions& screen) {
        //for simpler math, operate on things in terms of min/max
        long screen_max_x = screen.x + screen.width,
            screen_max_y = screen.y + screen.height;

        for (std::vector<strut>::const_iterator iter = struts.begin();
             iter != struts.end(); ++iter) {
            switch (iter->type) {
            case strut::LEFT:
                // first check if it intersects our screen's min/max y
                if (INTERSECTION(screen.y, screen_max_y, iter->min, iter->max) != 0) {
                    //then check if the strut (relative to the bounding box) actually exceeds our min x
                    screen.x = MAX(screen.x, (long)iter->width - bound.x);
                }
                break;
            case strut::RIGHT:
                if (INTERSECTION(screen.y, screen_max_y, iter->min, iter->max) != 0) {
                    long bound_max_x = bound.x + bound.width;
                    screen_max_x = MIN(screen_max_x, bound_max_x - iter->width);
                }
                break;
            case strut::TOP:
                if (INTERSECTION(screen.x, screen_max_x, iter->min, iter->max) != 0) {
                    screen.y = MAX(screen.y, (long)iter->width - bound.y);
                }
                break;
            case strut::BOTTOM:
                if (INTERSECTION(screen.x, screen_max_x, iter->min, iter->max) != 0) {
                    long bound_max_y = bound.y + bound.height;
                    screen_max_y = MIN(screen_max_y, bound_max_y - iter->width);
                }
                break;
            }
        }

        screen.width = screen_max_x - screen.x;
        screen.height = screen_max_y - screen.y;

        DEBUG("trimmed: %ldx %ldy %ldw %ldh",
                screen.x, screen.y, screen.width, screen.height);
    }
}

bool viewport::xinerama::get_viewports(Display* disp, const Dimensions& activewin,
		dim_list_t& viewports_out, size_t& active_out) {
	viewports_out.clear();

	Dimensions bounding_box;
	if (!get_screens(disp, activewin, bounding_box, viewports_out, active_out)) {
		return false;
	}

	std::vector<strut> struts;
	if (!get_struts(disp, struts)) {
		return false;
	}

	// trim struts from viewports
	for (dim_list_t::iterator iter = viewports_out.begin();
		 iter != viewports_out.end(); ++iter) {
		trim_screen(bounding_box, struts, *iter);
	}

	return true;
        dim_list_t& viewports_out, size_t& active_out) {
    viewports_out.clear();

    Dimensions bounding_box;
    if (!get_screens(disp, activewin, bounding_box, viewports_out, active_out)) {
        return false;
    }

    std::vector<strut> struts;
    if (!get_struts(disp, struts)) {
        return false;
    }

    // trim struts from viewports
    for (dim_list_t::iterator iter = viewports_out.begin();
         iter != viewports_out.end(); ++iter) {
        trim_screen(bounding_box, struts, *iter);
    }

    return true;
}

M src/viewport-imp-xinerama.h => src/viewport-imp-xinerama.h +4 -4
@@ 33,10 33,10 @@
typedef std::vector<Dimensions> dim_list_t;

namespace viewport {
	namespace xinerama {
		bool get_viewports(Display* disp, const Dimensions& activewin,
				dim_list_t& viewports_out, size_t& active_out);
	}
    namespace xinerama {
        bool get_viewports(Display* disp, const Dimensions& activewin,
                dim_list_t& viewports_out, size_t& active_out);
    }
}

#endif

M src/viewport.cpp => src/viewport.cpp +36 -36
@@ 28,52 28,52 @@
#endif

namespace {
	bool get_all(const Dimensions& activewin,
			dim_list_t& viewports, size_t& active) {
		Display* disp = XOpenDisplay(NULL);
		if (disp == NULL) {
			ERROR_DIR("unable to get display");
			return false;
		}
    bool get_all(const Dimensions& activewin,
            dim_list_t& viewports, size_t& active) {
        Display* disp = XOpenDisplay(NULL);
        if (disp == NULL) {
            ERROR_DIR("unable to get display");
            return false;
        }

#ifdef USE_XINERAMA
		//try xinerama, fall back to ewmh if xinerama is unavailable
		bool ok = viewport::xinerama::get_viewports(disp, activewin, viewports, active) ||
			viewport::ewmh::get_viewports(disp, activewin, viewports, active);
        //try xinerama, fall back to ewmh if xinerama is unavailable
        bool ok = viewport::xinerama::get_viewports(disp, activewin, viewports, active) ||
            viewport::ewmh::get_viewports(disp, activewin, viewports, active);
#else
		//xinerama disabled; just do ewmh
		bool ok = viewport::ewmh::get_viewports(disp, activewin, viewports, active);
        //xinerama disabled; just do ewmh
        bool ok = viewport::ewmh::get_viewports(disp, activewin, viewports, active);
#endif

		if (config::debug_enabled) {
			for (size_t i = 0; i < viewports.size(); ++i) {
				const Dimensions& v = viewports[i];
				DEBUG("viewport %lu: %dx %dy %luw %luh",
						i, v.x, v.y, v.width, v.height);
			}
		}
        if (config::debug_enabled) {
            for (size_t i = 0; i < viewports.size(); ++i) {
                const Dimensions& v = viewports[i];
                DEBUG("viewport %lu: %dx %dy %luw %luh",
                        i, v.x, v.y, v.width, v.height);
            }
        }

		XCloseDisplay(disp);
		return ok;
	}
        XCloseDisplay(disp);
        return ok;
    }
}

bool ViewportCalc::Viewports(grid::POS monitor,
		Dimensions& cur_viewport, Dimensions& next_viewport) const {
	dim_list_t viewports;
	size_t active, neighbor;
	if (!get_all(activewin, viewports, active)) {
		return false;
	}
        Dimensions& cur_viewport, Dimensions& next_viewport) const {
    dim_list_t viewports;
    size_t active, neighbor;
    if (!get_all(activewin, viewports, active)) {
        return false;
    }

	neighbor::select(monitor, viewports, active, neighbor);
    neighbor::select(monitor, viewports, active, neighbor);

	cur_viewport = viewports[active];
	next_viewport = viewports[neighbor];
    cur_viewport = viewports[active];
    next_viewport = viewports[neighbor];

	DEBUG("cur viewport: %dx %dy %dw %dh",
			cur_viewport.x, cur_viewport.y, cur_viewport.width, cur_viewport.height);
	DEBUG("next viewport: %dx %dy %dw %dh",
			next_viewport.x, next_viewport.y, next_viewport.width, next_viewport.height);
	return true;
    DEBUG("cur viewport: %dx %dy %dw %dh",
            cur_viewport.x, cur_viewport.y, cur_viewport.width, cur_viewport.height);
    DEBUG("next viewport: %dx %dy %dw %dh",
            next_viewport.x, next_viewport.y, next_viewport.width, next_viewport.height);
    return true;
}

M src/viewport.h => src/viewport.h +5 -5
@@ 24,14 24,14 @@

class ViewportCalc {
public:
	ViewportCalc(const Dimensions& activewin)
		: activewin(activewin) { }
    ViewportCalc(const Dimensions& activewin)
        : activewin(activewin) { }

	bool Viewports(grid::POS monitor,
			Dimensions& cur_viewport, Dimensions& next_viewport) const;
    bool Viewports(grid::POS monitor,
            Dimensions& cur_viewport, Dimensions& next_viewport) const;

private:
	const Dimensions activewin;
    const Dimensions activewin;
};

#endif

M src/window.cpp => src/window.cpp +368 -368
@@ 24,398 24,398 @@
#define SOURCE_INDICATION 2 //say that we're a pager or taskbar

namespace {
	int _client_msg(Display* disp, Window win, Atom msg,
			unsigned long data0, unsigned long data1,
			unsigned long data2, unsigned long data3,
			unsigned long data4) {
		XEvent event;
		long mask = SubstructureRedirectMask | SubstructureNotifyMask;

		event.xclient.type = ClientMessage;
		event.xclient.serial = 0;
		event.xclient.send_event = True;
		event.xclient.message_type = msg;
		event.xclient.window = win;
		event.xclient.format = 32;
		event.xclient.data.l[0] = data0;
		event.xclient.data.l[1] = data1;
		event.xclient.data.l[2] = data2;
		event.xclient.data.l[3] = data3;
		event.xclient.data.l[4] = data4;

		DEBUG("send message_type=%s, data=(%lu,%lu,%lu,%lu,%lu)",
				XGetAtomName(disp, msg), data0, data1, data2, data3, data4);

		if (XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
			return true;
		} else {
			ERROR("Cannot send %s event.", msg);
			return false;
		}
	}

	inline void print_window(Display* disp, Window win) {
		if (!config::debug_enabled) { return; }
		Window root;
		int x, y;
		unsigned int width, height, border, depth;
		if (XGetGeometry(disp, win, &root, &x, &y, &width,
						&height, &border, &depth) == 0) {
			ERROR_DIR("get geometry failed");
			return;
		}
		DEBUG("  %dx %dy %uw %uh %ub", x, y, width, height, border);
	}

	Window* get_active_window(Display* disp) {
		static Atom actwin_msg = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
		Window* ret = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
				XA_WINDOW, actwin_msg, NULL);
		if (ret == NULL) {
			ERROR_DIR("unable to get active window");
		}
		return ret;
	}

	bool is_dock_window(Display* disp, Window win) {
		/* disallow moving/selecting this window if it has type DESKTOP or DOCK.
		   (avoid messing with the user's desktop components, eg taskbars) */
		bool ret = false;
		size_t count = 0;
		static Atom wintype_msg = XInternAtom(disp, "_NET_WM_WINDOW_TYPE", False);
		Atom* types = (Atom*)x11_util::get_property(disp, win, XA_ATOM, wintype_msg, &count);
		if (types == NULL) {
			ERROR_DIR("couldn't get window types");
			//assume window types are fine, keep going
		} else {
			static Atom desktop_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DESKTOP", False),
				dock_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DOCK", False);
			for (size_t i = 0; i < count; ++i) {
				DEBUG("%d type %lu: %d %s",
						win, i, types[i], XGetAtomName(disp, types[i]));
				if (types[i] == desktop_type || types[i] == dock_type) {
					ret = true;
					if (!config::debug_enabled) {
						break;
					}
				}
			}
			x11_util::free_property(types);
		}
		return ret;
	}

	bool is_menu_window(Display* disp, Window win) {
		/* also disallow moving/selecting this window if it has SKIP_PAGER or SKIP_TASKBAR.
		   (avoid messing with auxiliary panels and menus) */
		bool ret = false;
		size_t count = 0;
		static Atom state_msg = XInternAtom(disp, "_NET_WM_STATE", False);
		Atom* states = (Atom*)x11_util::get_property(disp, win, XA_ATOM, state_msg, &count);
		if (states == NULL) {
			ERROR_DIR("couldn't get window states");
			//assume window states are fine, keep going
		} else {
			static Atom skip_pager = XInternAtom(disp, "_NET_WM_STATE_SKIP_PAGER", False),
				skip_taskbar = XInternAtom(disp, "_NET_WM_STATE_SKIP_TASKBAR", False);
			for (size_t i = 0; i < count; ++i) {
				DEBUG("%d state %lu: %d %s",
						win, i, states[i], XGetAtomName(disp, states[i]));
				if (states[i] == skip_pager || states[i] == skip_taskbar) {
					ret = true;
					if (!config::debug_enabled) {
						break;
					}
				}
			}
			x11_util::free_property(states);
		}
		return ret;
	}

	bool get_window_size(Display* disp, Window win,
			Dimensions* out_exterior = NULL,
			unsigned int* out_margin_width = NULL,
			unsigned int* out_margin_height = NULL) {
		Window root;
		unsigned int internal_width, internal_height;
		{
			/* first, get the interior width/height from this window
			   (so that we can calculate margins) */
			int x, y;
			unsigned int border, depth;
			if (XGetGeometry(disp, win, &root, &x, &y, &internal_width,
							&internal_height, &border, &depth) == 0) {
				ERROR_DIR("get geometry failed");
				return false;
			}
		}

		if (win == root) {
			ERROR_DIR("this window is root! treating this as an error.");
			return false;
		}

		/* now traverse up the parents until we reach the one JUST BEFORE root,
		   and get the external width/height and x/y from that. */
		Window just_before_root;
		{
			int count = 1;
			Window parent = win;
			Window* children;
			unsigned int children_count;
			do {
				just_before_root = parent;
				if (XQueryTree(disp, just_before_root, &root,
								&parent, &children, &children_count) == 0) {
					ERROR_DIR("get query tree failed");
					return false;
				}
				if (children != NULL) {
					XFree(children);
				}
				DEBUG("%d window=%lu, parent=%lu, root=%lu",
						count, just_before_root, parent, root);
				print_window(disp, just_before_root);
			} while (++count < 50 && parent != root);
		}

		int x, y;
		unsigned int external_width, external_height;
		{
			unsigned int border, depth;
			if (XGetGeometry(disp, just_before_root, &root, &x, &y, &external_width,
							&external_height, &border, &depth) == 0) {
				ERROR_DIR("get geometry failed");
				return false;
			}
		}

		if (out_exterior != NULL) {
			out_exterior->x = x;
			out_exterior->y = y;
			out_exterior->width = external_width;
			out_exterior->height = external_height;
		}

		if (out_margin_width != NULL) {
			*out_margin_width = external_width - internal_width;
		}
		if (out_margin_height != NULL) {
			*out_margin_height = external_height - internal_height;
		}

		DEBUG("size: exterior %uw %uh - interior %uw %uh = margins %dw %dh",
				external_width, external_height,
				internal_width, internal_height,
				external_width - internal_width,
				external_height - internal_height);
		return true;
	}

	bool activate_window(Display* disp, Window curactive, Window newactive) {
		static Atom active_msg = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
		if (!_client_msg(disp, newactive, active_msg,
						SOURCE_INDICATION, CurrentTime, curactive, 0, 0)) {
			ERROR_DIR("couldn't activate");
			return false;
		}
		return true;
	}

	bool set_window_state(Display* disp, Window win, Atom state1, Atom state2, bool enable) {
		/*
		  this disagrees with docs, which say that we should be using a
		  _NET_WM_STATE_DISABLE/_ENABLE atom in data[0]. That apparently doesn't
		  work in practice, but '0'/'1' do.
		*/

		int val = (enable) ? 1 : 0;// just to be explicit
		static Atom state_msg = XInternAtom(disp, "_NET_WM_STATE", False);
		return _client_msg(disp, win, state_msg,
				val, state1, state2, SOURCE_INDICATION, 0);
	}

	bool maximize_window(Display* disp, Window win, bool enable) {
		static Atom max_vert = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_VERT", False),
			max_horiz = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
		return set_window_state(disp, win, max_vert, max_horiz, enable);
	}
    int _client_msg(Display* disp, Window win, Atom msg,
            unsigned long data0, unsigned long data1,
            unsigned long data2, unsigned long data3,
            unsigned long data4) {
        XEvent event;
        long mask = SubstructureRedirectMask | SubstructureNotifyMask;

        event.xclient.type = ClientMessage;
        event.xclient.serial = 0;
        event.xclient.send_event = True;
        event.xclient.message_type = msg;
        event.xclient.window = win;
        event.xclient.format = 32;
        event.xclient.data.l[0] = data0;
        event.xclient.data.l[1] = data1;
        event.xclient.data.l[2] = data2;
        event.xclient.data.l[3] = data3;
        event.xclient.data.l[4] = data4;

        DEBUG("send message_type=%s, data=(%lu,%lu,%lu,%lu,%lu)",
                XGetAtomName(disp, msg), data0, data1, data2, data3, data4);

        if (XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
            return true;
        } else {
            ERROR("Cannot send %s event.", msg);
            return false;
        }
    }

    inline void print_window(Display* disp, Window win) {
        if (!config::debug_enabled) { return; }
        Window root;
        int x, y;
        unsigned int width, height, border, depth;
        if (XGetGeometry(disp, win, &root, &x, &y, &width,
                        &height, &border, &depth) == 0) {
            ERROR_DIR("get geometry failed");
            return;
        }
        DEBUG("  %dx %dy %uw %uh %ub", x, y, width, height, border);
    }

    Window* get_active_window(Display* disp) {
        static Atom actwin_msg = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
        Window* ret = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
                XA_WINDOW, actwin_msg, NULL);
        if (ret == NULL) {
            ERROR_DIR("unable to get active window");
        }
        return ret;
    }

    bool is_dock_window(Display* disp, Window win) {
        /* disallow moving/selecting this window if it has type DESKTOP or DOCK.
           (avoid messing with the user's desktop components, eg taskbars) */
        bool ret = false;
        size_t count = 0;
        static Atom wintype_msg = XInternAtom(disp, "_NET_WM_WINDOW_TYPE", False);
        Atom* types = (Atom*)x11_util::get_property(disp, win, XA_ATOM, wintype_msg, &count);
        if (types == NULL) {
            ERROR_DIR("couldn't get window types");
            //assume window types are fine, keep going
        } else {
            static Atom desktop_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DESKTOP", False),
                dock_type = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DOCK", False);
            for (size_t i = 0; i < count; ++i) {
                DEBUG("%d type %lu: %d %s",
                        win, i, types[i], XGetAtomName(disp, types[i]));
                if (types[i] == desktop_type || types[i] == dock_type) {
                    ret = true;
                    if (!config::debug_enabled) {
                        break;
                    }
                }
            }
            x11_util::free_property(types);
        }
        return ret;
    }

    bool is_menu_window(Display* disp, Window win) {
        /* also disallow moving/selecting this window if it has SKIP_PAGER or SKIP_TASKBAR.
           (avoid messing with auxiliary panels and menus) */
        bool ret = false;
        size_t count = 0;
        static Atom state_msg = XInternAtom(disp, "_NET_WM_STATE", False);
        Atom* states = (Atom*)x11_util::get_property(disp, win, XA_ATOM, state_msg, &count);
        if (states == NULL) {
            ERROR_DIR("couldn't get window states");
            //assume window states are fine, keep going
        } else {
            static Atom skip_pager = XInternAtom(disp, "_NET_WM_STATE_SKIP_PAGER", False),
                skip_taskbar = XInternAtom(disp, "_NET_WM_STATE_SKIP_TASKBAR", False);
            for (size_t i = 0; i < count; ++i) {
                DEBUG("%d state %lu: %d %s",
                        win, i, states[i], XGetAtomName(disp, states[i]));
                if (states[i] == skip_pager || states[i] == skip_taskbar) {
                    ret = true;
                    if (!config::debug_enabled) {
                        break;
                    }
                }
            }
            x11_util::free_property(states);
        }
        return ret;
    }

    bool get_window_size(Display* disp, Window win,
            Dimensions* out_exterior = NULL,
            unsigned int* out_margin_width = NULL,
            unsigned int* out_margin_height = NULL) {
        Window root;
        unsigned int internal_width, internal_height;
        {
            /* first, get the interior width/height from this window
               (so that we can calculate margins) */
            int x, y;
            unsigned int border, depth;
            if (XGetGeometry(disp, win, &root, &x, &y, &internal_width,
                            &internal_height, &border, &depth) == 0) {
                ERROR_DIR("get geometry failed");
                return false;
            }
        }

        if (win == root) {
            ERROR_DIR("this window is root! treating this as an error.");
            return false;
        }

        /* now traverse up the parents until we reach the one JUST BEFORE root,
           and get the external width/height and x/y from that. */
        Window just_before_root;
        {
            int count = 1;
            Window parent = win;
            Window* children;
            unsigned int children_count;
            do {
                just_before_root = parent;
                if (XQueryTree(disp, just_before_root, &root,
                                &parent, &children, &children_count) == 0) {
                    ERROR_DIR("get query tree failed");
                    return false;
                }
                if (children != NULL) {
                    XFree(children);
                }
                DEBUG("%d window=%lu, parent=%lu, root=%lu",
                        count, just_before_root, parent, root);
                print_window(disp, just_before_root);
            } while (++count < 50 && parent != root);
        }

        int x, y;
        unsigned int external_width, external_height;
        {
            unsigned int border, depth;
            if (XGetGeometry(disp, just_before_root, &root, &x, &y, &external_width,
                            &external_height, &border, &depth) == 0) {
                ERROR_DIR("get geometry failed");
                return false;
            }
        }

        if (out_exterior != NULL) {
            out_exterior->x = x;
            out_exterior->y = y;
            out_exterior->width = external_width;
            out_exterior->height = external_height;
        }

        if (out_margin_width != NULL) {
            *out_margin_width = external_width - internal_width;
        }
        if (out_margin_height != NULL) {
            *out_margin_height = external_height - internal_height;
        }

        DEBUG("size: exterior %uw %uh - interior %uw %uh = margins %dw %dh",
                external_width, external_height,
                internal_width, internal_height,
                external_width - internal_width,
                external_height - internal_height);
        return true;
    }

    bool activate_window(Display* disp, Window curactive, Window newactive) {
        static Atom active_msg = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
        if (!_client_msg(disp, newactive, active_msg,
                        SOURCE_INDICATION, CurrentTime, curactive, 0, 0)) {
            ERROR_DIR("couldn't activate");
            return false;
        }
        return true;
    }

    bool set_window_state(Display* disp, Window win, Atom state1, Atom state2, bool enable) {
        /*
          this disagrees with docs, which say that we should be using a
          _NET_WM_STATE_DISABLE/_ENABLE atom in data[0]. That apparently doesn't
          work in practice, but '0'/'1' do.
        */

        int val = (enable) ? 1 : 0;// just to be explicit
        static Atom state_msg = XInternAtom(disp, "_NET_WM_STATE", False);
        return _client_msg(disp, win, state_msg,
                val, state1, state2, SOURCE_INDICATION, 0);
    }

    bool maximize_window(Display* disp, Window win, bool enable) {
        static Atom max_vert = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_VERT", False),
            max_horiz = XInternAtom(disp, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
        return set_window_state(disp, win, max_vert, max_horiz, enable);
    }
}

bool window::select_activate(grid::POS dir) {
	Display* disp = XOpenDisplay(NULL);
	if (disp == NULL) {
		ERROR_DIR("unable to get display");
		return false;
	}

	std::vector<Window> wins;

	{
		size_t win_count = 0;
		static Atom clientlist_msg = XInternAtom(disp, "_NET_CLIENT_LIST", False);
		Window* all_wins = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
				XA_WINDOW, clientlist_msg, &win_count);
		if (all_wins == NULL || win_count == 0) {
			ERROR_DIR("unable to get list of windows");
			if (all_wins != NULL) {
				x11_util::free_property(all_wins);
			}
			XCloseDisplay(disp);
			return false;
		}
		// only select normal windows, ignore docks and menus
		for (size_t i = 0; i < win_count; ++i) {
			if (!is_dock_window(disp, all_wins[i]) && !is_menu_window(disp, all_wins[i])) {
				wins.push_back(all_wins[i]);
			}
		}
		x11_util::free_property(all_wins);
	}

	size_t active_window = 0;
	dim_list_t all_windows;
	{
		Window* active = get_active_window(disp);
		if (active == NULL) {
			XCloseDisplay(disp);
			return false;
		}
		for (size_t i = 0; i < wins.size(); ++i) {
			if (wins[i] == *active) {
				active_window = i;
				DEBUG_DIR("ACTIVE:");
			}
			all_windows.push_back(Dimensions());
			get_window_size(disp, wins[i], &all_windows.back(), NULL, NULL);
		}
		x11_util::free_property(active);
	}

	size_t next_window;
	neighbor::select(dir, all_windows, active_window, next_window);

	bool ok = activate_window(disp, wins[active_window], wins[next_window]);

	XCloseDisplay(disp);
	return ok;
    Display* disp = XOpenDisplay(NULL);
    if (disp == NULL) {
        ERROR_DIR("unable to get display");
        return false;
    }

    std::vector<Window> wins;

    {
        size_t win_count = 0;
        static Atom clientlist_msg = XInternAtom(disp, "_NET_CLIENT_LIST", False);
        Window* all_wins = (Window*)x11_util::get_property(disp, DefaultRootWindow(disp),
                XA_WINDOW, clientlist_msg, &win_count);
        if (all_wins == NULL || win_count == 0) {
            ERROR_DIR("unable to get list of windows");
            if (all_wins != NULL) {
                x11_util::free_property(all_wins);
            }
            XCloseDisplay(disp);
            return false;
        }
        // only select normal windows, ignore docks and menus
        for (size_t i = 0; i < win_count; ++i) {
            if (!is_dock_window(disp, all_wins[i]) && !is_menu_window(disp, all_wins[i])) {
                wins.push_back(all_wins[i]);
            }
        }
        x11_util::free_property(all_wins);
    }

    size_t active_window = 0;
    dim_list_t all_windows;
    {
        Window* active = get_active_window(disp);
        if (active == NULL) {
            XCloseDisplay(disp);
            return false;
        }
        for (size_t i = 0; i < wins.size(); ++i) {
            if (wins[i] == *active) {
                active_window = i;
                DEBUG_DIR("ACTIVE:");
            }
            all_windows.push_back(Dimensions());
            get_window_size(disp, wins[i], &all_windows.back(), NULL, NULL);
        }
        x11_util::free_property(active);
    }

    size_t next_window;
    neighbor::select(dir, all_windows, active_window, next_window);

    bool ok = activate_window(disp, wins[active_window], wins[next_window]);

    XCloseDisplay(disp);
    return ok;
}

ActiveWindow::~ActiveWindow() {
	if (disp != NULL) {
		XCloseDisplay(disp);
	}
	if (win != NULL) {
		x11_util::free_property(win);
	}
    if (disp != NULL) {
        XCloseDisplay(disp);
    }
    if (win != NULL) {
        x11_util::free_property(win);
    }
}

bool ActiveWindow::init() {
	if (disp == NULL) {
		disp = XOpenDisplay(NULL);
		if (disp == NULL) {
			ERROR_DIR("unable to get display");
			return false;
		}
	}

	if (win == NULL) {
		win = get_active_window(disp);
		if (win == NULL) {
			return false;
		}
	}
	return true;
    if (disp == NULL) {
        disp = XOpenDisplay(NULL);
        if (disp == NULL) {
            ERROR_DIR("unable to get display");
            return false;
        }
    }

    if (win == NULL) {
        win = get_active_window(disp);
        if (win == NULL) {
            return false;
        }
    }
    return true;
}

bool ActiveWindow::Size(Dimensions& activewin) {
	if (!init()) {
		return false;
	}
    if (!init()) {
        return false;
    }

	if (is_dock_window(disp, *win) || is_menu_window(disp, *win)) {
		LOG_DIR("Active window is a desktop or dock. Ignoring move request.");
		return false;
	}
    if (is_dock_window(disp, *win) || is_menu_window(disp, *win)) {
        LOG_DIR("Active window is a desktop or dock. Ignoring move request.");
        return false;
    }

	if (!get_window_size(disp, *win, &activewin, NULL, NULL)) {
		ERROR_DIR("couldn't get window size");
		return false;
	}
    if (!get_window_size(disp, *win, &activewin, NULL, NULL)) {
        ERROR_DIR("couldn't get window size");
        return false;
    }

	DEBUG("activewin %dx %dy %luw %luh",
			activewin.x, activewin.y, activewin.width, activewin.height);
    DEBUG("activewin %dx %dy %luw %luh",
            activewin.x, activewin.y, activewin.width, activewin.height);

	return true;
    return true;
}

bool ActiveWindow::MoveResize(const Dimensions& activewin) {
	if (!init()) {
		return false;
	}

	unsigned int margin_width, margin_height;
	if (!get_window_size(disp, *win, NULL, &margin_width, &margin_height)) {
		return false;
	}

	//demaximize the window before attempting to move it
	if (!maximize_window(disp, *win, false)) {
		ERROR_DIR("couldn't demaximize");
		//disregard failure
	}

	unsigned long new_interior_width = activewin.width - margin_width,
		new_interior_height = activewin.height - margin_height;

	//moveresize uses exterior for position, but interior for width/height
	DEBUG("%ldx %ldy %luw %luh - margins %dw %dh = %ldx %ldy %luw %luh",
			activewin.x, activewin.y, activewin.width, activewin.height,
			margin_width, margin_height,
			activewin.x, activewin.y, new_interior_width, new_interior_height);

	if (XMoveResizeWindow(disp, *win, activewin.x, activewin.y,
					new_interior_width, new_interior_height) == 0) {
		ERROR("MoveResize to %ldx %ldy %luw %luh failed.",
				activewin.x, activewin.y, new_interior_width, new_interior_height);
		return false;
	}
	return true;
    if (!init()) {
        return false;
    }

    unsigned int margin_width, margin_height;
    if (!get_window_size(disp, *win, NULL, &margin_width, &margin_height)) {
        return false;
    }

    //demaximize the window before attempting to move it
    if (!maximize_window(disp, *win, false)) {
        ERROR_DIR("couldn't demaximize");
        //disregard failure
    }

    unsigned long new_interior_width = activewin.width - margin_width,
        new_interior_height = activewin.height - margin_height;

    //moveresize uses exterior for position, but interior for width/height
    DEBUG("%ldx %ldy %luw %luh - margins %dw %dh = %ldx %ldy %luw %luh",
            activewin.x, activewin.y, activewin.width, activewin.height,
            margin_width, margin_height,
            activewin.x, activewin.y, new_interior_width, new_interior_height);

    if (XMoveResizeWindow(disp, *win, activewin.x, activewin.y,
                    new_interior_width, new_interior_height) == 0) {
        ERROR("MoveResize to %ldx %ldy %luw %luh failed.",
                activewin.x, activewin.y, new_interior_width, new_interior_height);
        return false;
    }
    return true;
}

bool ActiveWindow::Maximize() {
	if (!init()) {
		return false;
	}

	if (!maximize_window(disp, *win, true)) {
		ERROR_DIR("couldn't maximize");
		return false;
	}
	return true;
    if (!init()) {
        return false;
    }

    if (!maximize_window(disp, *win, true)) {
        ERROR_DIR("couldn't maximize");
        return false;
    }
    return true;
}
bool ActiveWindow::DeFullscreen() {
	if (!init()) {
		return false;
	}

	static Atom fs = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
	if (!set_window_state(disp, *win, fs, 0, false)) {
		ERROR_DIR("couldn't defullscreen");
		return false;
	}
	return true;
    if (!init()) {
        return false;
    }

    static Atom fs = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
    if (!set_window_state(disp, *win, fs, 0, false)) {
        ERROR_DIR("couldn't defullscreen");
        return false;
    }
    return true;
}
bool ActiveWindow::DeShade() {
	if (!init()) {
		return false;
	}

	static Atom shade = XInternAtom(disp, "_NET_WM_STATE_SHADED", False);
	if (!set_window_state(disp, *win, shade, 0, false)) {
		ERROR_DIR("couldn't deshade");
		return false;
	}
	return true;
    if (!init()) {
        return false;
    }

    static Atom shade = XInternAtom(disp, "_NET_WM_STATE_SHADED", False);
    if (!set_window_state(disp, *win, shade, 0, false)) {
        ERROR_DIR("couldn't deshade");
        return false;
    }
    return true;
}

M src/window.h => src/window.h +12 -12
@@ 25,28 25,28 @@
#include "dimensions.h"

namespace window {
	/* Finds the nearest window in the given direction and activates it. */
	bool select_activate(grid::POS dir);
    /* Finds the nearest window in the given direction and activates it. */
    bool select_activate(grid::POS dir);
}

class ActiveWindow {
public:
	ActiveWindow() : disp(NULL), win(NULL) { }
	virtual ~ActiveWindow();
    ActiveWindow() : disp(NULL), win(NULL) { }
    virtual ~ActiveWindow();

	bool Size(Dimensions& activewin);
    bool Size(Dimensions& activewin);

	bool MoveResize(const Dimensions& activewin);
    bool MoveResize(const Dimensions& activewin);

	bool Maximize();
	bool DeFullscreen();
	bool DeShade();
    bool Maximize();
    bool DeFullscreen();
    bool DeShade();

private:
	bool init();
    bool init();

	Display* disp;
	Window* win;
    Display* disp;
    Window* win;
};

#endif

M src/x11-util.cpp => src/x11-util.cpp +41 -41
@@ 22,51 22,51 @@
#define MAX_PROPERTY_VALUE_LEN 4096

unsigned char* x11_util::get_property(Display *disp, Window win,
		Atom xa_prop_type, Atom xa_prop_name, size_t* out_count) {
	Atom xa_ret_type;
	int ret_format;
	unsigned long ret_nitems, ret_bytes_after;
	unsigned char* ret_prop;
        Atom xa_prop_type, Atom xa_prop_name, size_t* out_count) {
    Atom xa_ret_type;
    int ret_format;
    unsigned long ret_nitems, ret_bytes_after;
    unsigned char* ret_prop;

	/* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
	 *
	 * long_length = Specifies the length in 32-bit multiples of the
	 *               data to be retrieved.
	 */
	if (XGetWindowProperty(disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, false,
					xa_prop_type, &xa_ret_type, &ret_format,
					&ret_nitems, &ret_bytes_after, &ret_prop) != Success) {
		ERROR("Cannot get property %d/%s.", xa_prop_name, XGetAtomName(disp, xa_prop_name));
		return NULL;
	} else {
		DEBUG("Property %d/%s -> %lu items", xa_prop_name, XGetAtomName(disp, xa_prop_name), ret_nitems);
	}
    /* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
     *
     * long_length = Specifies the length in 32-bit multiples of the
     *               data to be retrieved.
     */
    if (XGetWindowProperty(disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, false,
                    xa_prop_type, &xa_ret_type, &ret_format,
                    &ret_nitems, &ret_bytes_after, &ret_prop) != Success) {
        ERROR("Cannot get property %d/%s.", xa_prop_name, XGetAtomName(disp, xa_prop_name));
        return NULL;
    } else {
        DEBUG("Property %d/%s -> %lu items", xa_prop_name, XGetAtomName(disp, xa_prop_name), ret_nitems);
    }

	if (xa_ret_type != xa_prop_type) {
		if (xa_ret_type == None) {
			// avoid crash on XGetAtomName(None)
			char *req = XGetAtomName(disp, xa_prop_type);
			//not necessarily an error, can happen if the window in question just lacks the requested property
			//DEBUG("Unsupported or invalid request %s: requested type %s, got <none>", prop_name, req);
			XFree(req);
		} else {
			char *req = XGetAtomName(disp, xa_prop_type),
				*got = XGetAtomName(disp, xa_ret_type);
			ERROR("Invalid type of property %d/%s: req %s, got %s",
					xa_prop_name, XGetAtomName(disp, xa_prop_name), req, got);
			XFree(req);
			XFree(got);
		}
		XFree(ret_prop);
		return NULL;
	}
    if (xa_ret_type != xa_prop_type) {
        if (xa_ret_type == None) {
            // avoid crash on XGetAtomName(None)
            char *req = XGetAtomName(disp, xa_prop_type);
            //not necessarily an error, can happen if the window in question just lacks the requested property
            //DEBUG("Unsupported or invalid request %s: requested type %s, got <none>", prop_name, req);
            XFree(req);
        } else {
            char *req = XGetAtomName(disp, xa_prop_type),
                *got = XGetAtomName(disp, xa_ret_type);
            ERROR("Invalid type of property %d/%s: req %s, got %s",
                    xa_prop_name, XGetAtomName(disp, xa_prop_name), req, got);
            XFree(req);
            XFree(got);
        }
        XFree(ret_prop);
        return NULL;
    }

	if (out_count != NULL) {
		*out_count = ret_nitems;
	}
	return ret_prop;
    if (out_count != NULL) {
        *out_count = ret_nitems;
    }
    return ret_prop;
}

void x11_util::free_property(void* prop) {
	XFree(prop);
    XFree(prop);
}

M src/x11-util.h => src/x11-util.h +3 -3
@@ 24,9 24,9 @@
#include <stdint.h>

namespace x11_util {
	unsigned char* get_property(Display *disp, Window win,
			Atom xa_prop_type, Atom xa_prop_name, size_t* out_count);
	void free_property(void* prop);
    unsigned char* get_property(Display *disp, Window win,
            Atom xa_prop_type, Atom xa_prop_name, size_t* out_count);
    void free_property(void* prop);
}

#endif