mirror of
https://github.com/debauchee/barrier.git
synced 2026-02-08 04:45:03 +08:00
CServer to the primary screen when the configuration changes so it can make necessary adjustments (the win32 primary screen must tell the hook dll about the new jump zones). changed includes of some std c++ library files to go through our own include files. these wrap the include with stuff to keep vc++ quiet when compiling at warning level 4, which is what it does now. it also works around missing <istream> and <ostream> on g++2.96. added missing std:: where necessary. g++ doesn't really support namespaces so it lets references without the namespace slip through. added workaround or fix. not sure if istringstream::str(string) should reset eofbit. it does on g++ but does not on vc++. added clear() after str() so it works either way. added low-level keyboard hook to win32. if available (it's only available on NT SP3 and up) it allows us to catch and handle alt+tab, alt+esc, ctrl+esc, and windows key hot keys. i think that leaves only ctrl+alt+del and accessibility functions uncaught on those systems.
770 lines
18 KiB
C++
770 lines
18 KiB
C++
#include "CHTTPServer.h"
|
|
#include "CHTTPProtocol.h"
|
|
#include "XHTTP.h"
|
|
#include "CServer.h"
|
|
#include "CConfig.h"
|
|
#include "CLog.h"
|
|
#include "XThread.h"
|
|
#include "ISocket.h"
|
|
#include "stdset.h"
|
|
#include "stdsstream.h"
|
|
#include <assert.h>
|
|
|
|
//
|
|
// CHTTPServer
|
|
//
|
|
|
|
CHTTPServer::CHTTPServer(CServer* server) : m_server(server)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
CHTTPServer::~CHTTPServer()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void CHTTPServer::processRequest(ISocket* socket)
|
|
{
|
|
assert(socket != NULL);
|
|
|
|
CHTTPRequest* request = NULL;
|
|
try {
|
|
// parse request
|
|
request = CHTTPProtocol::readRequest(socket->getInputStream());
|
|
if (request == NULL) {
|
|
throw XHTTP(400);
|
|
}
|
|
|
|
// if absolute uri then strip off scheme and host
|
|
if (request->m_uri[0] != '/') {
|
|
CString::size_type n = request->m_uri.find('/');
|
|
if (n == CString::npos) {
|
|
throw XHTTP(404);
|
|
}
|
|
request->m_uri = request->m_uri.substr(n);
|
|
}
|
|
|
|
// process
|
|
CHTTPReply reply;
|
|
doProcessRequest(*request, reply);
|
|
|
|
// send reply
|
|
CHTTPProtocol::reply(socket->getOutputStream(), reply);
|
|
log((CLOG_INFO "HTTP reply %d for %s %s", reply.m_status, request->m_method.c_str(), request->m_uri.c_str()));
|
|
|
|
// clean up
|
|
delete request;
|
|
}
|
|
catch (XHTTP& e) {
|
|
log((CLOG_WARN "returning HTTP error %d %s for %s", e.getStatus(), e.getReason().c_str(), (request != NULL) ? request->m_uri.c_str() : "<unknown>"));
|
|
|
|
// clean up
|
|
delete request;
|
|
|
|
// return error
|
|
CHTTPReply reply;
|
|
reply.m_majorVersion = 1;
|
|
reply.m_minorVersion = 0;
|
|
reply.m_status = e.getStatus();
|
|
reply.m_reason = e.getReason();
|
|
reply.m_method = "GET";
|
|
// FIXME -- use a nicer error page
|
|
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
|
|
CString("text/plain")));
|
|
reply.m_body = e.getReason();
|
|
e.addHeaders(reply);
|
|
CHTTPProtocol::reply(socket->getOutputStream(), reply);
|
|
}
|
|
catch (...) {
|
|
// ignore other exceptions
|
|
RETHROW_XTHREAD
|
|
}
|
|
}
|
|
|
|
void CHTTPServer::doProcessRequest(
|
|
CHTTPRequest& request,
|
|
CHTTPReply& reply)
|
|
{
|
|
reply.m_majorVersion = request.m_majorVersion;
|
|
reply.m_minorVersion = request.m_minorVersion;
|
|
reply.m_status = 200;
|
|
reply.m_reason = "OK";
|
|
reply.m_method = request.m_method;
|
|
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
|
|
CString("text/html")));
|
|
|
|
// switch based on uri
|
|
if (request.m_uri == "/editmap") {
|
|
if (request.m_method == "GET" || request.m_method == "HEAD") {
|
|
doProcessGetEditMap(request, reply);
|
|
}
|
|
else if (request.m_method == "POST") {
|
|
doProcessPostEditMap(request, reply);
|
|
}
|
|
else {
|
|
throw XHTTPAllow("GET, HEAD, POST");
|
|
}
|
|
}
|
|
else {
|
|
// unknown page
|
|
throw XHTTP(404);
|
|
}
|
|
}
|
|
|
|
void CHTTPServer::doProcessGetEditMap(
|
|
CHTTPRequest& /*request*/,
|
|
CHTTPReply& reply)
|
|
{
|
|
static const char* s_editMapProlog1 =
|
|
"<html>\r\n"
|
|
"<head>\r\n"
|
|
" <title>Synergy -- Edit Screens</title>\r\n"
|
|
"</head>\r\n"
|
|
"<body>\r\n"
|
|
" <form method=\"POST\" action=\"editmap\" "
|
|
"enctype=\"multipart/form-data\">\r\n"
|
|
" <input type=hidden name=\"size\" value=\"";
|
|
static const char* s_editMapProlog2 =
|
|
"\">\r\n";
|
|
static const char* s_editMapEpilog =
|
|
" <input type=submit name=\"submit\">\r\n"
|
|
" <input type=reset>\r\n"
|
|
" <br>\r\n"
|
|
" </form>\r\n"
|
|
"</body>\r\n"
|
|
"</html>\r\n";
|
|
static const char* s_editMapTableProlog =
|
|
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\"><tr><td>"
|
|
" <table border=\"0\" cellspacing=\"2\" cellpadding=\"6\">\r\n";
|
|
static const char* s_editMapTableEpilog =
|
|
" </table>"
|
|
"</td></tr></table>\r\n";
|
|
static const char* s_editMapRowProlog =
|
|
"<tr align=\"center\" valign=\"top\">\r\n";
|
|
static const char* s_editMapRowEpilog =
|
|
"</tr>\r\n";
|
|
static const char* s_editMapScreenDummy =
|
|
"<td>";
|
|
static const char* s_editMapScreenPrimary =
|
|
"<td bgcolor=\"#2222288\"><input type=\"text\" readonly value=\"";
|
|
static const char* s_editMapScreenLive =
|
|
"<td bgcolor=\"#cccccc\"><input type=\"text\" value=\"";
|
|
static const char* s_editMapScreenDead =
|
|
"<td><input type=\"text\" value=\"";
|
|
static const char* s_editMapScreenLiveDead1 =
|
|
"\" size=\"16\" maxlength=\"64\" name=\"";
|
|
static const char* s_editMapScreenLiveDead2 =
|
|
"\">";
|
|
static const char* s_editMapScreenEnd =
|
|
"</td>\r\n";
|
|
|
|
std::ostringstream s;
|
|
|
|
// convert screen map into a temporary screen map
|
|
CScreenArray screens;
|
|
{
|
|
CConfig config;
|
|
m_server->getConfig(&config);
|
|
screens.convertFrom(config);
|
|
// FIXME -- note to user if config couldn't be exactly represented
|
|
}
|
|
|
|
// insert blank columns and rows around array (to allow the user
|
|
// to insert new screens)
|
|
screens.insertColumn(0);
|
|
screens.insertColumn(screens.getWidth());
|
|
screens.insertRow(0);
|
|
screens.insertRow(screens.getHeight());
|
|
|
|
// get array size
|
|
const SInt32 w = screens.getWidth();
|
|
const SInt32 h = screens.getHeight();
|
|
|
|
// construct reply
|
|
reply.m_body += s_editMapProlog1;
|
|
s << w << "x" << h;
|
|
reply.m_body += s.str();
|
|
reply.m_body += s_editMapProlog2;
|
|
|
|
// add screen map for editing
|
|
const CString primaryName = m_server->getPrimaryScreenName();
|
|
reply.m_body += s_editMapTableProlog;
|
|
for (SInt32 y = 0; y < h; ++y) {
|
|
reply.m_body += s_editMapRowProlog;
|
|
for (SInt32 x = 0; x < w; ++x) {
|
|
s.str("");
|
|
if (!screens.isAllowed(x, y) && screens.get(x, y) != primaryName) {
|
|
s << s_editMapScreenDummy;
|
|
}
|
|
else {
|
|
if (!screens.isSet(x, y)) {
|
|
s << s_editMapScreenDead;
|
|
}
|
|
else if (screens.get(x, y) == primaryName) {
|
|
s << s_editMapScreenPrimary;
|
|
}
|
|
else {
|
|
s << s_editMapScreenLive;
|
|
}
|
|
s << screens.get(x, y) <<
|
|
s_editMapScreenLiveDead1 <<
|
|
"n" << x << "x" << y <<
|
|
s_editMapScreenLiveDead2;
|
|
}
|
|
s << s_editMapScreenEnd;
|
|
reply.m_body += s.str();
|
|
}
|
|
reply.m_body += s_editMapRowEpilog;
|
|
}
|
|
reply.m_body += s_editMapTableEpilog;
|
|
|
|
reply.m_body += s_editMapEpilog;
|
|
}
|
|
|
|
void CHTTPServer::doProcessPostEditMap(
|
|
CHTTPRequest& request,
|
|
CHTTPReply& reply)
|
|
{
|
|
typedef std::vector<CString> ScreenArray;
|
|
typedef std::set<CString> ScreenSet;
|
|
|
|
// parse the result
|
|
CHTTPProtocol::CFormParts parts;
|
|
if (!CHTTPProtocol::parseFormData(request, parts)) {
|
|
log((CLOG_WARN "editmap: cannot parse form data"));
|
|
throw XHTTP(400);
|
|
}
|
|
|
|
try {
|
|
std::ostringstream s;
|
|
|
|
// convert post data into a temporary screen map. also check
|
|
// that no screen name is invalid or used more than once.
|
|
SInt32 w, h;
|
|
CHTTPProtocol::CFormParts::iterator index = parts.find("size");
|
|
if (index == parts.end() ||
|
|
!parseXY(index->second, w, h) ||
|
|
w <= 0 || h <= 0) {
|
|
log((CLOG_WARN "editmap: cannot parse size or size is invalid"));
|
|
throw XHTTP(400);
|
|
}
|
|
ScreenSet screenNames;
|
|
CScreenArray screens;
|
|
screens.resize(w, h);
|
|
for (SInt32 y = 0; y < h; ++y) {
|
|
for (SInt32 x = 0; x < w; ++x) {
|
|
// find part
|
|
s.str("");
|
|
s << "n" << x << "x" << y;
|
|
index = parts.find(s.str());
|
|
if (index == parts.end()) {
|
|
// FIXME -- screen is missing. error?
|
|
continue;
|
|
}
|
|
|
|
// skip blank names
|
|
const CString& name = index->second;
|
|
if (name.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// check name. name must be legal and must not have
|
|
// already been seen.
|
|
if (screenNames.count(name)) {
|
|
// FIXME -- better error message
|
|
log((CLOG_WARN "editmap: duplicate name %s", name.c_str()));
|
|
throw XHTTP(400);
|
|
}
|
|
// FIXME -- check that name is legal
|
|
|
|
// save name. if we've already seen the name then
|
|
// report an error.
|
|
screens.set(x, y, name);
|
|
screenNames.insert(name);
|
|
}
|
|
}
|
|
|
|
// if new map is invalid then return error. map is invalid if:
|
|
// there are no screens, or
|
|
// the screens are not 4-connected.
|
|
if (screenNames.empty()) {
|
|
// no screens
|
|
// FIXME -- need better no screens
|
|
log((CLOG_WARN "editmap: no screens"));
|
|
throw XHTTP(400);
|
|
}
|
|
if (!screens.isValid()) {
|
|
// FIXME -- need better unconnected screens error
|
|
log((CLOG_WARN "editmap: unconnected screens"));
|
|
throw XHTTP(400);
|
|
}
|
|
|
|
// convert temporary screen map into a regular map
|
|
CConfig config;
|
|
screens.convertTo(config);
|
|
|
|
// set new screen map on server
|
|
m_server->setConfig(config);
|
|
|
|
// now reply with current map
|
|
doProcessGetEditMap(request, reply);
|
|
}
|
|
catch (XHTTP&) {
|
|
// FIXME -- construct a more meaningful error?
|
|
throw;
|
|
}
|
|
}
|
|
|
|
bool CHTTPServer::parseXY(
|
|
const CString& xy, SInt32& x, SInt32& y)
|
|
{
|
|
std::istringstream s(xy);
|
|
char delimiter;
|
|
s >> x;
|
|
s.get(delimiter);
|
|
s >> y;
|
|
return (!!s && delimiter == 'x');
|
|
}
|
|
|
|
|
|
//
|
|
// CHTTPServer::CScreenArray
|
|
//
|
|
|
|
CHTTPServer::CScreenArray::CScreenArray() : m_w(0), m_h(0)
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
CHTTPServer::CScreenArray::~CScreenArray()
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::resize(SInt32 w, SInt32 h)
|
|
{
|
|
m_screens.clear();
|
|
m_screens.resize(w * h);
|
|
m_w = w;
|
|
m_h = h;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::insertRow(SInt32 i)
|
|
{
|
|
assert(i >= 0 && i <= m_h);
|
|
|
|
CNames newScreens;
|
|
newScreens.resize(m_w * (m_h + 1));
|
|
|
|
for (SInt32 y = 0; y < i; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
newScreens[x + y * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
for (SInt32 y = i; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
newScreens[x + (y + 1) * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
|
|
m_screens.swap(newScreens);
|
|
++m_h;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::insertColumn(SInt32 i)
|
|
{
|
|
assert(i >= 0 && i <= m_w);
|
|
|
|
CNames newScreens;
|
|
newScreens.resize((m_w + 1) * m_h);
|
|
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < i; ++x) {
|
|
newScreens[x + y * (m_w + 1)] = m_screens[x + y * m_w];
|
|
}
|
|
for (SInt32 x = i; x < m_w; ++x) {
|
|
newScreens[(x + 1) + y * (m_w + 1)] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
|
|
m_screens.swap(newScreens);
|
|
++m_w;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::eraseRow(SInt32 i)
|
|
{
|
|
assert(i >= 0 && i < m_h);
|
|
|
|
CNames newScreens;
|
|
newScreens.resize(m_w * (m_h - 1));
|
|
|
|
for (SInt32 y = 0; y < i; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
newScreens[x + y * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
for (SInt32 y = i + 1; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
newScreens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
|
|
m_screens.swap(newScreens);
|
|
--m_h;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::eraseColumn(SInt32 i)
|
|
{
|
|
assert(i >= 0 && i < m_w);
|
|
|
|
CNames newScreens;
|
|
newScreens.resize((m_w - 1) * m_h);
|
|
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
newScreens[x + y * (m_w - 1)] = m_screens[x + y * m_w];
|
|
}
|
|
for (SInt32 x = i + 1; x < m_w; ++x) {
|
|
newScreens[(x - 1) + y * (m_w - 1)] = m_screens[x + y * m_w];
|
|
}
|
|
}
|
|
|
|
m_screens.swap(newScreens);
|
|
--m_w;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::rotateRows(SInt32 i)
|
|
{
|
|
// nothing to do if no rows
|
|
if (m_h == 0) {
|
|
return;
|
|
}
|
|
|
|
// convert to canonical form
|
|
if (i < 0) {
|
|
i = m_h - ((-i) % m_h);
|
|
}
|
|
else {
|
|
i %= m_h;
|
|
}
|
|
if (i == 0 || i == m_h) {
|
|
return;
|
|
}
|
|
|
|
while (i > 0) {
|
|
// rotate one row
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
CString tmp = m_screens[x];
|
|
for (SInt32 y = 1; y < m_h; ++y) {
|
|
m_screens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
m_screens[x + (m_h - 1) * m_w] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::rotateColumns(SInt32 i)
|
|
{
|
|
// nothing to do if no columns
|
|
if (m_h == 0) {
|
|
return;
|
|
}
|
|
|
|
// convert to canonical form
|
|
if (i < 0) {
|
|
i = m_w - ((-i) % m_w);
|
|
}
|
|
else {
|
|
i %= m_w;
|
|
}
|
|
if (i == 0 || i == m_w) {
|
|
return;
|
|
}
|
|
|
|
while (i > 0) {
|
|
// rotate one column
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
CString tmp = m_screens[0 + y * m_w];
|
|
for (SInt32 x = 1; x < m_w; ++x) {
|
|
m_screens[x - 1 + y * m_w] = m_screens[x + y * m_w];
|
|
}
|
|
m_screens[m_w - 1 + y * m_w] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::remove(SInt32 x, SInt32 y)
|
|
{
|
|
set(x, y, CString());
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::set(
|
|
SInt32 x, SInt32 y, const CString& name)
|
|
{
|
|
assert(x >= 0 && x < m_w);
|
|
assert(y >= 0 && y < m_h);
|
|
|
|
m_screens[x + y * m_w] = name;
|
|
}
|
|
|
|
bool CHTTPServer::CScreenArray::isAllowed(
|
|
SInt32 x, SInt32 y) const
|
|
{
|
|
assert(x >= 0 && x < m_w);
|
|
assert(y >= 0 && y < m_h);
|
|
|
|
if (x > 0 && !m_screens[(x - 1) + y * m_w].empty()) {
|
|
return true;
|
|
}
|
|
if (x < m_w - 1 && !m_screens[(x + 1) + y * m_w].empty()) {
|
|
return true;
|
|
}
|
|
if (y > 0 && !m_screens[x + (y - 1) * m_w].empty()) {
|
|
return true;
|
|
}
|
|
if (y < m_h - 1 && !m_screens[x + (y + 1) * m_w].empty()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CHTTPServer::CScreenArray::isSet(
|
|
SInt32 x, SInt32 y) const
|
|
{
|
|
assert(x >= 0 && x < m_w);
|
|
assert(y >= 0 && y < m_h);
|
|
|
|
return !m_screens[x + y * m_w].empty();
|
|
}
|
|
|
|
CString CHTTPServer::CScreenArray::get(
|
|
SInt32 x, SInt32 y) const
|
|
{
|
|
assert(x >= 0 && x < m_w);
|
|
assert(y >= 0 && y < m_h);
|
|
|
|
return m_screens[x + y * m_w];
|
|
}
|
|
|
|
bool CHTTPServer::CScreenArray::find(
|
|
const CString& name,
|
|
SInt32& xOut, SInt32& yOut) const
|
|
{
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
if (m_screens[x + y * m_w] == name) {
|
|
xOut = x;
|
|
yOut = y;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CHTTPServer::CScreenArray::isValid() const
|
|
{
|
|
SInt32 count = 0, isolated = 0;
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
if (isSet(x, y)) {
|
|
++count;
|
|
if (!isAllowed(x, y)) {
|
|
++isolated;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return (count <= 1 || isolated == 0);
|
|
}
|
|
|
|
bool CHTTPServer::CScreenArray::convertFrom(
|
|
const CConfig& config)
|
|
{
|
|
typedef std::set<CString> ScreenSet;
|
|
|
|
// insert the first screen
|
|
CConfig::const_iterator index = config.begin();
|
|
if (index == config.end()) {
|
|
// no screens
|
|
resize(0, 0);
|
|
return true;
|
|
}
|
|
CString name = *index;
|
|
resize(1, 1);
|
|
set(0, 0, name);
|
|
|
|
// flood fill state
|
|
CNames screenStack;
|
|
ScreenSet doneSet;
|
|
|
|
// put all but the first screen on the stack
|
|
// note -- if all screens are 4-connected then we can skip this
|
|
while (++index != config.end()) {
|
|
screenStack.push_back(*index);
|
|
}
|
|
|
|
// put the first screen on the stack last so we process it first
|
|
screenStack.push_back(name);
|
|
|
|
// perform a flood fill using the stack as the seeds
|
|
while (!screenStack.empty()) {
|
|
// get next screen from stack
|
|
CString name = screenStack.back();
|
|
screenStack.pop_back();
|
|
|
|
// skip screen if we've seen it before
|
|
if (doneSet.count(name) > 0) {
|
|
continue;
|
|
}
|
|
|
|
// add this screen to doneSet so we don't process it again
|
|
doneSet.insert(name);
|
|
|
|
// find the screen. if it's not found then not all of the
|
|
// screens are 4-connected. discard disconnected screens.
|
|
SInt32 x, y;
|
|
if (!find(name, x, y)) {
|
|
continue;
|
|
}
|
|
|
|
// insert the screen's neighbors
|
|
// FIXME -- handle edge wrapping
|
|
CString neighbor;
|
|
neighbor = config.getNeighbor(name, CConfig::kLeft);
|
|
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
|
// insert left neighbor, adding a column if necessary
|
|
if (x == 0 || get(x - 1, y) != neighbor) {
|
|
++x;
|
|
insertColumn(x - 1);
|
|
set(x - 1, y, neighbor);
|
|
}
|
|
screenStack.push_back(neighbor);
|
|
}
|
|
neighbor = config.getNeighbor(name, CConfig::kRight);
|
|
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
|
// insert right neighbor, adding a column if necessary
|
|
if (x == m_w - 1 || get(x + 1, y) != neighbor) {
|
|
insertColumn(x + 1);
|
|
set(x + 1, y, neighbor);
|
|
}
|
|
screenStack.push_back(neighbor);
|
|
}
|
|
neighbor = config.getNeighbor(name, CConfig::kTop);
|
|
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
|
// insert top neighbor, adding a row if necessary
|
|
if (y == 0 || get(x, y - 1) != neighbor) {
|
|
++y;
|
|
insertRow(y - 1);
|
|
set(x, y - 1, neighbor);
|
|
}
|
|
screenStack.push_back(neighbor);
|
|
}
|
|
neighbor = config.getNeighbor(name, CConfig::kBottom);
|
|
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
|
// insert bottom neighbor, adding a row if necessary
|
|
if (y == m_h - 1 || get(x, y + 1) != neighbor) {
|
|
insertRow(y + 1);
|
|
set(x, y + 1, neighbor);
|
|
}
|
|
screenStack.push_back(neighbor);
|
|
}
|
|
}
|
|
|
|
// check symmetry
|
|
// FIXME -- handle edge wrapping
|
|
for (index = config.begin(); index != config.end(); ++index) {
|
|
const CString& name = *index;
|
|
SInt32 x, y;
|
|
if (!find(name, x, y)) {
|
|
return false;
|
|
}
|
|
|
|
CString neighbor;
|
|
neighbor = config.getNeighbor(name, CConfig::kLeft);
|
|
if ((x == 0 && !neighbor.empty()) ||
|
|
(x > 0 && get(x - 1, y) != neighbor)) {
|
|
return false;
|
|
}
|
|
|
|
neighbor = config.getNeighbor(name, CConfig::kRight);
|
|
if ((x == m_w - 1 && !neighbor.empty()) ||
|
|
(x < m_w - 1 && get(x + 1, y) != neighbor)) {
|
|
return false;
|
|
}
|
|
|
|
neighbor = config.getNeighbor(name, CConfig::kTop);
|
|
if ((y == 0 && !neighbor.empty()) ||
|
|
(y > 0 && get(x, y - 1) != neighbor)) {
|
|
return false;
|
|
}
|
|
|
|
neighbor = config.getNeighbor(name, CConfig::kBottom);
|
|
if ((y == m_h - 1 && !neighbor.empty()) ||
|
|
(y < m_h - 1 && get(x, y + 1) != neighbor)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CHTTPServer::CScreenArray::convertTo(
|
|
CConfig& config) const
|
|
{
|
|
// add screens and find smallest box containing all screens
|
|
SInt32 x0 = m_w, x1 = 0, y0 = m_h, y1 = 0;
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
if (isSet(x, y)) {
|
|
config.addScreen(get(x, y));
|
|
if (x < x0) {
|
|
x0 = x;
|
|
}
|
|
if (x > x1) {
|
|
x1 = x;
|
|
}
|
|
if (y < y0) {
|
|
y0 = y;
|
|
}
|
|
if (y > y1) {
|
|
y1 = y;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// make connections between screens
|
|
// FIXME -- add support for wrapping
|
|
// FIXME -- mark topmost and leftmost screens
|
|
for (SInt32 y = 0; y < m_h; ++y) {
|
|
for (SInt32 x = 0; x < m_w; ++x) {
|
|
if (!isSet(x, y)) {
|
|
continue;
|
|
}
|
|
if (x > x0 && isSet(x - 1, y)) {
|
|
config.connect(get(x, y),
|
|
CConfig::kLeft,
|
|
get(x - 1, y));
|
|
}
|
|
if (x < x1 && isSet(x + 1, y)) {
|
|
config.connect(get(x, y),
|
|
CConfig::kRight,
|
|
get(x + 1, y));
|
|
}
|
|
if (y > y0 && isSet(x, y - 1)) {
|
|
config.connect(get(x, y),
|
|
CConfig::kTop,
|
|
get(x, y - 1));
|
|
}
|
|
if (y < y1 && isSet(x, y + 1)) {
|
|
config.connect(get(x, y),
|
|
CConfig::kBottom,
|
|
get(x, y + 1));
|
|
}
|
|
}
|
|
}
|
|
}
|