libreoffice-online/wsd/FileServerUtil.cpp
Skyler Grey ff1e4eeb57 Add a ui_defaults hint for touchscreens
At the moment, we try to detect whether the browser is running with a
touchscreen, however this is very imperfect. It's possible an integrator
may have more information about whether COOL is running on a device with
a touchscreen, so this ui_defaults option allows us to specify. Touch
mode binds inputs for touchscreen devices (long press for menu, pinch to
zoom, etc.) and does not bind the normal inputs (right click for menu,
etc.), so it's crucial to get it on all touch devices and no desktop
devices, as input is severely hampered if they are the wrong way round.

The option is called TouchscreenHint. Setting it to 'true' will enable
touchscreen mode, setting it to 'false' will disable touchscreen mode.
Leaving it undefined will keep our detection active.

This option must be set at page load so we can register the right
events at creation time. Therefore, ui_defaults is perfect as a method
to override this.

This is not a long-term solution. Instead, "The right thing" is to look
specifically for touch events and specifically for mouse events, rather
than using the default hammer.js behavior which is to look for both...
that should be an eventual followup to this. However, this was a lot
faster to implement and helps with the most pressing issue: not being
able to override our detection when it goes wrong.

Change-Id: Id28a156fe352fe6565ce6b472b7aa54d0869c48e
Signed-off-by: Skyler Grey <skyler.grey@collabora.com>
2023-11-10 23:20:22 +00:00

266 lines
7.8 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*/
#include <config.h>
#include "FileServer.hpp"
#include "StringVector.hpp"
#include "Util.hpp"
#include <Poco/JSON/Object.h>
std::string FileServerRequestHandler::uiDefaultsToJSON(const std::string& uiDefaults, std::string& uiMode, std::string& uiTheme, std::string& savedUIState)
{
static std::string previousUIDefaults;
static std::string previousJSON("{}");
static std::string previousUIMode;
// early exit if we are serving the same thing
if (uiDefaults == previousUIDefaults)
{
uiMode = previousUIMode;
return previousJSON;
}
Poco::JSON::Object json;
Poco::JSON::Object textDefs;
Poco::JSON::Object spreadsheetDefs;
Poco::JSON::Object presentationDefs;
Poco::JSON::Object drawingDefs;
uiMode = "";
uiTheme = "light";
savedUIState = "true";
StringVector tokens(StringVector::tokenize(uiDefaults, ';'));
for (const auto& token : tokens)
{
StringVector keyValue(StringVector::tokenize(tokens.getParam(token), '='));
Poco::JSON::Object* currentDef = nullptr;
std::string key;
// detect the UIMode or component
if (keyValue.equals(0, "UIMode"))
{
if (keyValue.equals(1, "compact") || keyValue.equals(1, "classic"))
{
json.set("uiMode", "classic");
uiMode = "classic";
}
else if(keyValue.equals(1, "tabbed") || keyValue.equals(1, "notebookbar"))
{
json.set("uiMode", "notebookbar");
uiMode = "notebookbar";
}
else
LOG_ERR("unknown UIMode value " << keyValue[1]);
continue;
}
// detect the UITheme default, light or dark
if (keyValue.equals(0, "UITheme"))
{
json.set("darkTheme", keyValue.equals(1, "dark"));
uiTheme = keyValue[1];
continue;
}
if (keyValue.equals(0, "SavedUIState"))
{
if (keyValue.equals(1, "false"))
{
savedUIState = "false";
}
else
{
if (!keyValue.equals(1, "true"))
{
LOG_ERR("unknown SavedUIState value " << keyValue[1]);
}
savedUIState = "true";
}
}
if (keyValue.equals(0, "SaveAsMode"))
{
if (keyValue.equals(1, "group"))
{
json.set("saveAsMode", "group");
}
continue;
}
if (keyValue.equals(0, "TouchscreenHint"))
{
json.set("touchscreenHint", keyValue.equals(1, "true"));
continue;
}
if (keyValue.equals(0, "OnscreenKeyboardHint"))
{
json.set("onscreenKeyboardHint", keyValue.equals(1, "true"));
continue;
}
else if (keyValue.startsWith(0, "Text"))
{
currentDef = &textDefs;
key = keyValue[0].substr(4);
}
else if (keyValue.startsWith(0, "Spreadsheet"))
{
currentDef = &spreadsheetDefs;
key = keyValue[0].substr(11);
}
else if (keyValue.startsWith(0, "Presentation"))
{
currentDef = &presentationDefs;
key = keyValue[0].substr(12);
}
else if (Util::startsWith(keyValue[0], "Drawing"))
{
currentDef = &drawingDefs;
key = keyValue[0].substr(7);
}
else
{
LOG_ERR("unknown UI default's component " << keyValue[0]);
continue;
}
assert(currentDef);
// detect the actual UI widget we want to hide or show
if (key == "Ruler" || key == "Sidebar" || key == "Statusbar" || key == "Toolbar")
{
bool value(true);
if (keyValue.equals(1, "false") || keyValue.equals(1, "False") || keyValue.equals(1, "0"))
value = false;
currentDef->set("Show" + key, value);
}
else
{
LOG_ERR("unknown UI default " << keyValue[0]);
continue;
}
}
if (textDefs.size() > 0)
json.set("text", textDefs);
if (spreadsheetDefs.size() > 0)
json.set("spreadsheet", spreadsheetDefs);
if (presentationDefs.size() > 0)
json.set("presentation", presentationDefs);
if (drawingDefs.size() > 0)
json.set("drawing", drawingDefs);
std::ostringstream oss;
Poco::JSON::Stringifier::stringify(json, oss);
previousUIDefaults = uiDefaults;
previousJSON = oss.str();
previousUIMode = uiMode;
return previousJSON;
}
std::string FileServerRequestHandler::checkFileInfoToJSON(const std::string& checkfileInfo)
{
static std::string previousCheckFileInfo;
static std::string previousCheckFileInfoJSON("{}");
// early exit if we are serving the same thing
if (checkfileInfo == previousCheckFileInfo)
return previousCheckFileInfoJSON;
Poco::JSON::Object json;
StringVector tokens(StringVector::tokenize(checkfileInfo, ';'));
for (const auto& token : tokens)
{
StringVector keyValue(StringVector::tokenize(tokens.getParam(token), '='));
if (keyValue.equals(0, "DownloadAsPostMessage"))
{
bool value(false);
if (keyValue.equals(1, "true") || keyValue.equals(1, "True") || keyValue.equals(1, "1"))
value = true;
json.set(keyValue[0], value);
}
}
std::ostringstream oss;
Poco::JSON::Stringifier::stringify(json, oss);
previousCheckFileInfo = checkfileInfo;
previousCheckFileInfoJSON = oss.str();
return previousCheckFileInfoJSON;
}
namespace
{
bool isValidCss(const std::string& token)
{
const std::string forbidden = "<>{}&|\\\"^`'$[]";
for (auto c: token)
{
if (c < 0x20 || c >= 0x7F || forbidden.find(c) != std::string::npos)
return false;
}
return true;
}
}
std::string FileServerRequestHandler::cssVarsToStyle(const std::string& cssVars)
{
static std::string previousVars;
static std::string previousStyle;
// early exit if we are serving the same thing
if (cssVars == previousVars)
return previousStyle;
std::ostringstream styleOSS;
styleOSS << "<style>:root {";
StringVector tokens(StringVector::tokenize(cssVars, ';'));
for (const auto& token : tokens)
{
StringVector keyValue(StringVector::tokenize(tokens.getParam(token), '='));
if (keyValue.size() < 2)
{
LOG_ERR("Skipping the token [" << tokens.getParam(token) << "] since it does not have '='");
continue;
}
else if (keyValue.size() > 2)
{
LOG_ERR("Skipping the token [" << tokens.getParam(token) << "] since it has more than one '=' pair");
continue;
}
if (!isValidCss(tokens.getParam(token)))
{
LOG_WRN("Skipping the token [" << tokens.getParam(token) << "] since it contains forbidden characters");
continue;
}
styleOSS << keyValue[0] << ':' << keyValue[1] << ';';
}
styleOSS << "}</style>";
previousVars = cssVars;
previousStyle = styleOSS.str();
return previousStyle;
}
std::string FileServerRequestHandler::stringifyBoolFromConfig(
const Poco::Util::LayeredConfiguration& config,
std::string propertyName,
bool defaultValue)
{
std::string value = "false";
if (config.getBool(propertyName, defaultValue))
value = "true";
return value;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */