<commit_before>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
////////////////////////////////////////////////////////////////////////////////

#include "ConsoleFeature.h"

#include "Basics/StringUtils.h"
#include "Basics/messages.h"
#include "Basics/shell-colors.h"
#include "Basics/terminal-utils.h"
#include "Logger/Logger.h"
#include "ProgramOptions/ProgramOptions.h"
#include "ProgramOptions/Section.h"
#include "Shell/ClientFeature.h"

using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::options;

#ifdef _WIN32
static const int FOREGROUND_WHITE =
    FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
static const int BACKGROUND_WHITE =
    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
#endif

ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server)
    : ApplicationFeature(server, "Console"),
#ifdef _WIN32
      _codePage(-1),
      _cygwinShell(false),
#endif
      _quiet(false),
      _colors(true),
      _autoComplete(true),
      _prettyPrint(true),
      _auditFile(),
      _pager(false),
      _pagerCommand("less -X -R -F -L"),
      _prompt("%E@%d> "),
      _promptError(false),
      _supportsColors(isatty(STDIN_FILENO) != 0),
      _toPager(stdout),
      _toAuditFile(nullptr) {
  setOptional(false);
  requiresElevatedPrivileges(false);
  startsAfter("Logger");

  if (!_supportsColors) {
    _colors = false;
  }

#if _WIN32
  _codePage = GetConsoleOutputCP();

  CONSOLE_SCREEN_BUFFER_INFO info;
  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);

  _defaultAttribute = info.wAttributes & INTENSITY;
  _defaultColor = info.wAttributes & FOREGROUND_WHITE;
  _defaultBackground = info.wAttributes & BACKGROUND_WHITE;

  _consoleAttribute = _defaultAttribute;
  _consoleColor = _defaultColor | _defaultBackground;
#endif
}

void ConsoleFeature::collectOptions(std::shared_ptr<ProgramOptions> options) {
  options->addOption("--quiet", "silent startup",
                     new BooleanParameter(&_quiet));

  options->addSection("console", "Configure the console");

  options->addOption("--console.colors", "enable color support",
                     new BooleanParameter(&_colors));

  options->addOption("--console.auto-complete", "enable auto completion",
                     new BooleanParameter(&_autoComplete));

  options->addOption("--console.pretty-print", "enable pretty printing",
                     new BooleanParameter(&_prettyPrint));

  options->addOption("--console.audit-file",
                     "audit log file to save commands and results",
                     new StringParameter(&_auditFile));

  options->addOption("--console.pager", "enable paging",
                     new BooleanParameter(&_pager));

  options->addHiddenOption("--console.pager-command", "pager command",
                           new StringParameter(&_pagerCommand));

  options->addOption("--console.prompt", "prompt used in REPL",
                     new StringParameter(&_prompt));

#if _WIN32
  options->addHiddenOption("--console.code-page", "Windows code page to use",
                           new Int16Parameter(&_codePage));
#endif
}

void ConsoleFeature::prepare() {
#if _WIN32
  if (getenv("SHELL") != nullptr) {
    _cygwinShell = true;
  }
#endif
}

void ConsoleFeature::start() {
  openLog();

#if _WIN32
  if (_codePage != -1) {
    SetConsoleOutputCP(_codePage);
  }
#endif
}

void ConsoleFeature::stop() {
  closeLog();
}

#ifdef _WIN32
static void _newLine() { fprintf(stdout, "\n"); }

void ConsoleFeature::_print2(std::string const& s) {
  size_t sLen = s.size();

  if (sLen == 0) {
    return;
  }

  LPWSTR wBuf = new WCHAR[sLen + 1];
  int wLen = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)sLen, wBuf,
                                 (int)((sizeof WCHAR) * (sLen + 1)));

  if (wLen) {
    auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(handle, _consoleAttribute | _consoleColor);

    DWORD n;
    WriteConsoleW(handle, wBuf, (DWORD)wLen, &n, NULL);
  } else {
    fprintf(stdout, "window error: '%d' \r\n", GetLastError());
    fprintf(stdout, "%s\r\n", s.c_str());
  }

  if (wBuf) {
    delete[] wBuf;
  }
}

void ConsoleFeature::_print(std::string const& s) {
  auto pos = s.find_first_of("\x1b");

  if (pos == std::string::npos) {
    _print2(s);
  } else {
    std::vector<std::string> lines = StringUtils::split(s, '\x1b', '\0');

    int i = 0;

    for (auto line : lines) {
      size_t pos = 0;

      if (i++ != 0 && !line.empty()) {
        char c = line[0];

        if (c == '[') {
          int code = 0;

          for (++pos; pos < line.size(); ++pos) {
            c = line[pos];

            if ('0' <= c && c <= '9') {
              code = code * 10 + (c - '0');
            } else if (c == 'm' || c == ';') {
              switch (code) {
                case 0:
                  _consoleAttribute = _defaultAttribute;
                  _consoleColor = _defaultColor | _defaultBackground;
                  break;

                case 1:  // BOLD
                case 5:  // BLINK
                  _consoleAttribute =
                      (_defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
                  break;

                case 30:
                  _consoleColor = BACKGROUND_WHITE;
                  break;

                case 31:
                  _consoleColor = FOREGROUND_RED | _defaultBackground;
                  break;

                case 32:
                  _consoleColor = FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 33:
                  _consoleColor =
                      FOREGROUND_RED | FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 34:
                  _consoleColor = FOREGROUND_BLUE | _defaultBackground;
                  break;

                case 35:
                  _consoleColor =
                      FOREGROUND_BLUE | FOREGROUND_RED | _defaultBackground;
                  break;

                case 36:
                  _consoleColor =
                      FOREGROUND_BLUE | FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 37:
                  _consoleColor = FOREGROUND_GREEN | FOREGROUND_RED |
                                  FOREGROUND_BLUE | _defaultBackground;
                  break;
              }

              code = 0;
            }

            if (c == 'm') {
              ++pos;
              break;
            }
          }
        }
      }

      if (line.size() > pos) {
        _print2(line.substr(pos));
      }
    }
  }

  auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(handle, _consoleAttribute | _consoleColor);
}

#endif

// prints a string to stdout, without a newline
void ConsoleFeature::printContinuous(std::string const& s) {
  if (s.empty()) {
    return;
  }

#ifdef _WIN32
  // no, we cannot use std::cout as this doesn't support UTF-8 on Windows

  if (!_cygwinShell) {
    // no, we cannot use std::cout as this doesn't support UTF-8 on Windows
    // fprintf(stdout, "%s\r\n", s.c_str());

    std::vector<std::string> lines = StringUtils::split(s, '\n', '\0');

    auto last = lines.back();
    lines.pop_back();

    for (auto& line : lines) {
      _print(line);
      _newLine();
    }

    _print(last);
  } else
#endif
  {
    fprintf(stdout, "%s", s.c_str());
    fflush(stdout);
  }
}

void ConsoleFeature::printLine(std::string const& s) {
#ifdef _WIN32
  // no, we cannot use std::cout as this doesn't support UTF-8 on Windows

  if (s.empty()) {
    _newLine();
    return;
  }

  if (!_cygwinShell) {
    std::vector<std::string> lines = StringUtils::split(s, '\n', '\0');

    for (auto& line : lines) {
      _print(line);
      _newLine();
    }
  } else
#endif
  {
    fprintf(stdout, "%s\n", s.c_str());
    fflush(stdout);
  }
}

void ConsoleFeature::printErrorLine(std::string const& s) { printLine(s); }

std::string ConsoleFeature::readPassword(std::string const& message) {
  std::string password;

  printContinuous(message);

#ifdef TRI_HAVE_TERMIOS_H
  TRI_SetStdinVisibility(false);
  std::getline(std::cin, password);

  TRI_SetStdinVisibility(true);
#else
  std::getline(std::cin, password);
#endif
  ConsoleFeature::printLine("");

  return password;
}

void ConsoleFeature::printWelcomeInfo() {
  if (!_quiet) {
    if (_pager) {
      std::ostringstream s;

      s << "Using pager '" << _pagerCommand << "' for output buffering.";

      printLine(s.str());
    }

    if (_prettyPrint) {
      printLine("Pretty printing values.");
    }
  }
}

void ConsoleFeature::printByeBye() {
  if (!_quiet) {
    printLine("<ctrl-D>");
    printLine(TRI_BYE_MESSAGE);
  }
}

static std::string StripBinary(std::string const& value) {
  std::string result;

  bool inBinary = false;

  for (char c : value) {
    if (inBinary) {
      if (c == 'm') {
        inBinary = false;
      }
    } else {
      if (c == '\x1b') {
        inBinary = true;
      } else {
        result.push_back(c);
      }
    }
  }

  return result;
}

void ConsoleFeature::print(std::string const& message) {
  if (_toPager == stdout) {
    printContinuous(message);
  } else {
    std::string sanitized = StripBinary(message);
    fprintf(_toPager, "%s", sanitized.c_str());
  }

  log(message);
}

void ConsoleFeature::openLog() {
  if (!_auditFile.empty()) {
    _toAuditFile = fopen(_auditFile.c_str(), "w");

    std::ostringstream s;

    if (_toAuditFile == nullptr) {
      s << "Cannot open file '" << _auditFile << "' for logging.";
      printErrorLine(s.str());
    } else {
      s << "Logging input and output to '" << _auditFile << "'.";
      printLine(s.str());
    }
  }
}

void ConsoleFeature::closeLog() {
  if (_toAuditFile != nullptr) {
    fclose(_toAuditFile);
    _toAuditFile = nullptr;
  }
}

void ConsoleFeature::log(std::string const& message) {
  if (_toAuditFile != nullptr) {
    std::string sanitized = StripBinary(message);

    if (!sanitized.empty()) {
      // do not print terminal escape sequences into log
      fprintf(_toAuditFile, "%s", sanitized.c_str());
    }
  }
}

void ConsoleFeature::flushLog() {
  if (_toAuditFile) {
    fflush(_toAuditFile);
  }
}

ConsoleFeature::Prompt ConsoleFeature::buildPrompt(ClientFeature* client) {
  std::string result;
  bool esc = false;

  for (char c : _prompt) {
    if (c == '\0') {
      break;
    }

    if (esc) {
      if (c == '%') {
        result.push_back(c);
      } else if (c == 'd') {
        if (client != nullptr) {
          result.append(client->databaseName());
        } else {
          result.append("[database]");
        }
      } else if (c == 'e' || c == 'E') {
        std::string ep;

        if (client == nullptr) {
          ep = "none";
        } else {
          ep = client->endpoint();
        }

        if (c == 'E') {
          // replace protocol
          if (ep.find("tcp://") == 0) {
            ep = ep.substr(6);
          } else if (ep.find("ssl://") == 0) {
            ep = ep.substr(6);
          } else if (ep.find("unix://") == 0) {
            ep = ep.substr(7);
          }
        }

        result.append(ep);
      } else if (c == 'u') {
        if (client == nullptr) {
          result.append("[user]");
        } else {
          result.append(client->username());
        }
      }

      esc = false;
    } else {
      if (c == '%') {
        esc = true;
      } else {
        result.push_back(c);
      }
    }
  }

  std::string colored;

  if (_supportsColors && _colors) {
    if (_promptError) {
      colored = TRI_SHELL_COLOR_BOLD_RED + result + TRI_SHELL_COLOR_RESET;
    } else {
      colored = TRI_SHELL_COLOR_BOLD_GREEN + result + TRI_SHELL_COLOR_RESET;
    }
  } else {
    colored = result;
  }

  return {result, colored};
}

void ConsoleFeature::startPager() {
#ifndef _WIN32
  if (!_pager || _pagerCommand.empty() || _pagerCommand == "stdout" ||
      _pagerCommand == "-") {
    _toPager = stdout;
  } else {
    _toPager = popen(_pagerCommand.c_str(), "w");

    if (_toPager == nullptr) {
      LOG(ERR) << "popen() for pager failed! Using stdout instead!";
      _toPager = stdout;
      _pager = false;
    }
  }
#endif
}

void ConsoleFeature::stopPager() {
#ifndef _WIN32
  if (_toPager != stdout) {
    pclose(_toPager);
    _toPager = stdout;
  }
#endif
}
<commit_msg>added http+tcp<commit_after>////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
////////////////////////////////////////////////////////////////////////////////

#include "ConsoleFeature.h"

#include "Basics/StringUtils.h"
#include "Basics/messages.h"
#include "Basics/shell-colors.h"
#include "Basics/terminal-utils.h"
#include "Logger/Logger.h"
#include "ProgramOptions/ProgramOptions.h"
#include "ProgramOptions/Section.h"
#include "Shell/ClientFeature.h"

using namespace arangodb;
using namespace arangodb::basics;
using namespace arangodb::options;

#ifdef _WIN32
static const int FOREGROUND_WHITE =
    FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
static const int BACKGROUND_WHITE =
    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;
#endif

ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server)
    : ApplicationFeature(server, "Console"),
#ifdef _WIN32
      _codePage(-1),
      _cygwinShell(false),
#endif
      _quiet(false),
      _colors(true),
      _autoComplete(true),
      _prettyPrint(true),
      _auditFile(),
      _pager(false),
      _pagerCommand("less -X -R -F -L"),
      _prompt("%E@%d> "),
      _promptError(false),
      _supportsColors(isatty(STDIN_FILENO) != 0),
      _toPager(stdout),
      _toAuditFile(nullptr) {
  setOptional(false);
  requiresElevatedPrivileges(false);
  startsAfter("Logger");

  if (!_supportsColors) {
    _colors = false;
  }

#if _WIN32
  _codePage = GetConsoleOutputCP();

  CONSOLE_SCREEN_BUFFER_INFO info;
  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);

  _defaultAttribute = info.wAttributes & INTENSITY;
  _defaultColor = info.wAttributes & FOREGROUND_WHITE;
  _defaultBackground = info.wAttributes & BACKGROUND_WHITE;

  _consoleAttribute = _defaultAttribute;
  _consoleColor = _defaultColor | _defaultBackground;
#endif
}

void ConsoleFeature::collectOptions(std::shared_ptr<ProgramOptions> options) {
  options->addOption("--quiet", "silent startup",
                     new BooleanParameter(&_quiet));

  options->addSection("console", "Configure the console");

  options->addOption("--console.colors", "enable color support",
                     new BooleanParameter(&_colors));

  options->addOption("--console.auto-complete", "enable auto completion",
                     new BooleanParameter(&_autoComplete));

  options->addOption("--console.pretty-print", "enable pretty printing",
                     new BooleanParameter(&_prettyPrint));

  options->addOption("--console.audit-file",
                     "audit log file to save commands and results",
                     new StringParameter(&_auditFile));

  options->addOption("--console.pager", "enable paging",
                     new BooleanParameter(&_pager));

  options->addHiddenOption("--console.pager-command", "pager command",
                           new StringParameter(&_pagerCommand));

  options->addOption("--console.prompt", "prompt used in REPL",
                     new StringParameter(&_prompt));

#if _WIN32
  options->addHiddenOption("--console.code-page", "Windows code page to use",
                           new Int16Parameter(&_codePage));
#endif
}

void ConsoleFeature::prepare() {
#if _WIN32
  if (getenv("SHELL") != nullptr) {
    _cygwinShell = true;
  }
#endif
}

void ConsoleFeature::start() {
  openLog();

#if _WIN32
  if (_codePage != -1) {
    SetConsoleOutputCP(_codePage);
  }
#endif
}

void ConsoleFeature::stop() {
  closeLog();
}

#ifdef _WIN32
static void _newLine() { fprintf(stdout, "\n"); }

void ConsoleFeature::_print2(std::string const& s) {
  size_t sLen = s.size();

  if (sLen == 0) {
    return;
  }

  LPWSTR wBuf = new WCHAR[sLen + 1];
  int wLen = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)sLen, wBuf,
                                 (int)((sizeof WCHAR) * (sLen + 1)));

  if (wLen) {
    auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(handle, _consoleAttribute | _consoleColor);

    DWORD n;
    WriteConsoleW(handle, wBuf, (DWORD)wLen, &n, NULL);
  } else {
    fprintf(stdout, "window error: '%d' \r\n", GetLastError());
    fprintf(stdout, "%s\r\n", s.c_str());
  }

  if (wBuf) {
    delete[] wBuf;
  }
}

void ConsoleFeature::_print(std::string const& s) {
  auto pos = s.find_first_of("\x1b");

  if (pos == std::string::npos) {
    _print2(s);
  } else {
    std::vector<std::string> lines = StringUtils::split(s, '\x1b', '\0');

    int i = 0;

    for (auto line : lines) {
      size_t pos = 0;

      if (i++ != 0 && !line.empty()) {
        char c = line[0];

        if (c == '[') {
          int code = 0;

          for (++pos; pos < line.size(); ++pos) {
            c = line[pos];

            if ('0' <= c && c <= '9') {
              code = code * 10 + (c - '0');
            } else if (c == 'm' || c == ';') {
              switch (code) {
                case 0:
                  _consoleAttribute = _defaultAttribute;
                  _consoleColor = _defaultColor | _defaultBackground;
                  break;

                case 1:  // BOLD
                case 5:  // BLINK
                  _consoleAttribute =
                      (_defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY;
                  break;

                case 30:
                  _consoleColor = BACKGROUND_WHITE;
                  break;

                case 31:
                  _consoleColor = FOREGROUND_RED | _defaultBackground;
                  break;

                case 32:
                  _consoleColor = FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 33:
                  _consoleColor =
                      FOREGROUND_RED | FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 34:
                  _consoleColor = FOREGROUND_BLUE | _defaultBackground;
                  break;

                case 35:
                  _consoleColor =
                      FOREGROUND_BLUE | FOREGROUND_RED | _defaultBackground;
                  break;

                case 36:
                  _consoleColor =
                      FOREGROUND_BLUE | FOREGROUND_GREEN | _defaultBackground;
                  break;

                case 37:
                  _consoleColor = FOREGROUND_GREEN | FOREGROUND_RED |
                                  FOREGROUND_BLUE | _defaultBackground;
                  break;
              }

              code = 0;
            }

            if (c == 'm') {
              ++pos;
              break;
            }
          }
        }
      }

      if (line.size() > pos) {
        _print2(line.substr(pos));
      }
    }
  }

  auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
  SetConsoleTextAttribute(handle, _consoleAttribute | _consoleColor);
}

#endif

// prints a string to stdout, without a newline
void ConsoleFeature::printContinuous(std::string const& s) {
  if (s.empty()) {
    return;
  }

#ifdef _WIN32
  // no, we cannot use std::cout as this doesn't support UTF-8 on Windows

  if (!_cygwinShell) {
    // no, we cannot use std::cout as this doesn't support UTF-8 on Windows
    // fprintf(stdout, "%s\r\n", s.c_str());

    std::vector<std::string> lines = StringUtils::split(s, '\n', '\0');

    auto last = lines.back();
    lines.pop_back();

    for (auto& line : lines) {
      _print(line);
      _newLine();
    }

    _print(last);
  } else
#endif
  {
    fprintf(stdout, "%s", s.c_str());
    fflush(stdout);
  }
}

void ConsoleFeature::printLine(std::string const& s) {
#ifdef _WIN32
  // no, we cannot use std::cout as this doesn't support UTF-8 on Windows

  if (s.empty()) {
    _newLine();
    return;
  }

  if (!_cygwinShell) {
    std::vector<std::string> lines = StringUtils::split(s, '\n', '\0');

    for (auto& line : lines) {
      _print(line);
      _newLine();
    }
  } else
#endif
  {
    fprintf(stdout, "%s\n", s.c_str());
    fflush(stdout);
  }
}

void ConsoleFeature::printErrorLine(std::string const& s) { printLine(s); }

std::string ConsoleFeature::readPassword(std::string const& message) {
  std::string password;

  printContinuous(message);

#ifdef TRI_HAVE_TERMIOS_H
  TRI_SetStdinVisibility(false);
  std::getline(std::cin, password);

  TRI_SetStdinVisibility(true);
#else
  std::getline(std::cin, password);
#endif
  ConsoleFeature::printLine("");

  return password;
}

void ConsoleFeature::printWelcomeInfo() {
  if (!_quiet) {
    if (_pager) {
      std::ostringstream s;

      s << "Using pager '" << _pagerCommand << "' for output buffering.";

      printLine(s.str());
    }

    if (_prettyPrint) {
      printLine("Pretty printing values.");
    }
  }
}

void ConsoleFeature::printByeBye() {
  if (!_quiet) {
    printLine("<ctrl-D>");
    printLine(TRI_BYE_MESSAGE);
  }
}

static std::string StripBinary(std::string const& value) {
  std::string result;

  bool inBinary = false;

  for (char c : value) {
    if (inBinary) {
      if (c == 'm') {
        inBinary = false;
      }
    } else {
      if (c == '\x1b') {
        inBinary = true;
      } else {
        result.push_back(c);
      }
    }
  }

  return result;
}

void ConsoleFeature::print(std::string const& message) {
  if (_toPager == stdout) {
    printContinuous(message);
  } else {
    std::string sanitized = StripBinary(message);
    fprintf(_toPager, "%s", sanitized.c_str());
  }

  log(message);
}

void ConsoleFeature::openLog() {
  if (!_auditFile.empty()) {
    _toAuditFile = fopen(_auditFile.c_str(), "w");

    std::ostringstream s;

    if (_toAuditFile == nullptr) {
      s << "Cannot open file '" << _auditFile << "' for logging.";
      printErrorLine(s.str());
    } else {
      s << "Logging input and output to '" << _auditFile << "'.";
      printLine(s.str());
    }
  }
}

void ConsoleFeature::closeLog() {
  if (_toAuditFile != nullptr) {
    fclose(_toAuditFile);
    _toAuditFile = nullptr;
  }
}

void ConsoleFeature::log(std::string const& message) {
  if (_toAuditFile != nullptr) {
    std::string sanitized = StripBinary(message);

    if (!sanitized.empty()) {
      // do not print terminal escape sequences into log
      fprintf(_toAuditFile, "%s", sanitized.c_str());
    }
  }
}

void ConsoleFeature::flushLog() {
  if (_toAuditFile) {
    fflush(_toAuditFile);
  }
}

ConsoleFeature::Prompt ConsoleFeature::buildPrompt(ClientFeature* client) {
  std::string result;
  bool esc = false;

  for (char c : _prompt) {
    if (c == '\0') {
      break;
    }

    if (esc) {
      if (c == '%') {
        result.push_back(c);
      } else if (c == 'd') {
        if (client != nullptr) {
          result.append(client->databaseName());
        } else {
          result.append("[database]");
        }
      } else if (c == 'e' || c == 'E') {
        std::string ep;

        if (client == nullptr) {
          ep = "none";
        } else {
          ep = client->endpoint();
        }

        if (c == 'E') {
          // replace protocol
          if (ep.find("tcp://") == 0) {
            ep = ep.substr(strlen("tcp://"));
          } else if (ep.find("http+tcp://") == 0) {
            ep = ep.substr(strlen("http+tcp://"));
          } else if (ep.find("ssl://") == 0) {
            ep = ep.substr(strlen("ssl://"));
          } else if (ep.find("unix://") == 0) {
            ep = ep.substr(strlen("unix://"));
          }
        }

        result.append(ep);
      } else if (c == 'u') {
        if (client == nullptr) {
          result.append("[user]");
        } else {
          result.append(client->username());
        }
      }

      esc = false;
    } else {
      if (c == '%') {
        esc = true;
      } else {
        result.push_back(c);
      }
    }
  }

  std::string colored;

  if (_supportsColors && _colors) {
    if (_promptError) {
      colored = TRI_SHELL_COLOR_BOLD_RED + result + TRI_SHELL_COLOR_RESET;
    } else {
      colored = TRI_SHELL_COLOR_BOLD_GREEN + result + TRI_SHELL_COLOR_RESET;
    }
  } else {
    colored = result;
  }

  return {result, colored};
}

void ConsoleFeature::startPager() {
#ifndef _WIN32
  if (!_pager || _pagerCommand.empty() || _pagerCommand == "stdout" ||
      _pagerCommand == "-") {
    _toPager = stdout;
  } else {
    _toPager = popen(_pagerCommand.c_str(), "w");

    if (_toPager == nullptr) {
      LOG(ERR) << "popen() for pager failed! Using stdout instead!";
      _toPager = stdout;
      _pager = false;
    }
  }
#endif
}

void ConsoleFeature::stopPager() {
#ifndef _WIN32
  if (_toPager != stdout) {
    pclose(_toPager);
    _toPager = stdout;
  }
#endif
}
<|endoftext|>


<commit_before>/******************************************************************************
 * Copyright 2017 The Apollo Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include "modules/planning/common/planning_gflags.h"
DEFINE_int32(planning_loop_rate, 5, "Loop rate for planning node");

DEFINE_string(rtk_trajectory_filename, "modules/planning/data/garage.csv",
              "Loop rate for planning node");

DEFINE_uint64(backward_trajectory_point_num, 10,
              "The number of points to be included in planning trajectory "
              "before the matched point");

DEFINE_uint64(rtk_trajectory_forward, 800,
              "The number of points to be included in RTK trajectory "
              "after the matched point");

DEFINE_double(trajectory_resolution, 0.01,
              "The time resolution of "
              "output trajectory.");

DEFINE_double(
    look_backward_distance, 10,
    "look backward this distance when creating reference line from routing");

DEFINE_double(
    look_forward_distance, 70,
    "look forward this distance when creating reference line from routing");
DEFINE_bool(enable_smooth_reference_line, true,
            "enable smooth the map reference line");

DEFINE_int32(max_history_frame_num, 5, "The maximum history frame number");

DEFINE_double(max_collision_distance, 0.1,
              "considered as collision if distance (meters) is smaller than or "
              "equal to this (meters)");

DEFINE_double(replan_distance_threshold, 5.0,
              "The distance threshold of replan");

DEFINE_bool(enable_reference_line_provider_thread, false,
            "Enable reference line provider thread.");

DEFINE_double(default_reference_line_width, 4.0,
              "Default reference line width");

DEFINE_double(planning_upper_speed_limit, 7.5,
              "Maximum speed (m/s) in planning.");

DEFINE_double(planning_distance, 100, "Planning distance");

DEFINE_double(trajectory_time_length, 8.0, "Trajectory time length");
DEFINE_double(trajectory_time_resolution, 0.1,
              "Trajectory time resolution in planning");
DEFINE_double(output_trajectory_time_resolution, 0.05,
              "Trajectory time resolution when publish");

DEFINE_bool(enable_trajectory_check, false,
            "Enable sanity check for planning trajectory.");

DEFINE_double(speed_lower_bound, -0.02, "The lowest speed allowed.");
DEFINE_double(speed_upper_bound, 40.0, "The highest speed allowed.");

DEFINE_double(longitudinal_acceleration_lower_bound, -4.5,
              "The lowest longitudinal acceleration allowed.");
DEFINE_double(longitudinal_acceleration_upper_bound, 4.0,
              "The highest longitudinal acceleration allowed.");

DEFINE_double(lateral_acceleration_bound, 4.5,
              "Bound of lateral acceleration; symmetric for left and right");
DEFINE_double(lateral_jerk_bound, 4.0,
              "Bound of lateral jerk; symmetric for left and right");

DEFINE_double(longitudinal_jerk_lower_bound, -4.0,
              "The lower bound of longitudinal jerk.");
DEFINE_double(longitudinal_jerk_upper_bound, 4.0,
              "The upper bound of longitudinal jerk.");

DEFINE_double(kappa_bound, 1.00, "The bound for vehicle curvature");

// ST Boundary
DEFINE_double(st_max_s, 80, "the maximum s of st boundary");
DEFINE_double(st_max_t, 10, "the maximum t of st boundary");

// Decision Part
DEFINE_double(static_obstacle_speed_threshold, 1.0,
              "obstacles are considered as static obstacle if its speed is "
              "less than this value (m/s)");
DEFINE_bool(enable_nudge_decision, false, "enable nudge decision");
DEFINE_double(static_decision_ignore_s_range, 3.0,
              "threshold for judging nudge in dp path computing decision");
DEFINE_double(static_decision_nudge_l_buffer, 0.5, "l buffer for nudge");
DEFINE_double(stop_distance_obstacle, 10.0,
              "stop distance from in-lane obstacle (meters)");
DEFINE_double(stop_distance_destination, 3.0,
              "stop distance from destination line");
DEFINE_double(min_driving_width, 2.5,
              "minimum road width(meters) for adc to drive through");
DEFINE_double(nudge_distance_obstacle, 0.3,
              "minimum distance to nudge a obstacle (meters)");
DEFINE_double(follow_min_distance, 10,
              "min follow distance for vehicles/bicycles/moving objects");
DEFINE_double(stop_line_min_distance, 0.0,
              "min distance (meters) to stop line for a valid stop");

DEFINE_string(destination_obstacle_id, "DEST",
              "obstacle id for converting destination to an obstacle");
DEFINE_int32(virtual_obstacle_perception_id, -1,
             "virtual obstacle perception id(a negative int)");
DEFINE_double(virtual_stop_wall_length, 0.1,
              "virtual stop wall length (meters)");
DEFINE_double(virtual_stop_wall_width, 3.7, "virtual stop wall width (meters)");
DEFINE_double(virtual_stop_wall_height, 2.0,
              "virtual stop wall height (meters)");

// Prediction Part
DEFINE_double(prediction_total_time, 5.0, "Total prediction time");
DEFINE_bool(align_prediction_time, true,
            "enable align prediction data based planning time");

// Trajectory
DEFINE_bool(enable_rule_layer, true,
            "enable rule for trajectory before model computation");

// Traffic decision

DEFINE_string(planning_config_file,
              "modules/planning/conf/planning_config.pb.txt",
              "planning config file");

DEFINE_int32(trajectory_point_num_for_debug, 10,
             "number of output trajectory points for debugging");

DEFINE_double(decision_valid_stop_range, 0.5,
              "The valid stop range in decision.");
DEFINE_bool(enable_record_debug, true,
            "True to enable record debug into debug protobuf.");
DEFINE_bool(enable_prediction, true, "True to enable prediction input.");

// QpSt optimizer
DEFINE_bool(enable_slowdown_profile_generator, true,
            "True to enable slowdown speed profile generator.");
DEFINE_double(slowdown_speed_threshold, 8.0,
              "Only generator slowdown profile when adc speed is lower than "
              "this threshold. unit : m/s.");
DEFINE_double(slowdown_profile_deceleration, -1.0,
              "The deceleration to generate slowdown profile. unit: m/s^2.");
<commit_msg>planning: disable align_prediction_time to allow rosbag replay works for planning.<commit_after>/******************************************************************************
 * Copyright 2017 The Apollo Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include "modules/planning/common/planning_gflags.h"
DEFINE_int32(planning_loop_rate, 5, "Loop rate for planning node");

DEFINE_string(rtk_trajectory_filename, "modules/planning/data/garage.csv",
              "Loop rate for planning node");

DEFINE_uint64(backward_trajectory_point_num, 10,
              "The number of points to be included in planning trajectory "
              "before the matched point");

DEFINE_uint64(rtk_trajectory_forward, 800,
              "The number of points to be included in RTK trajectory "
              "after the matched point");

DEFINE_double(trajectory_resolution, 0.01,
              "The time resolution of "
              "output trajectory.");

DEFINE_double(
    look_backward_distance, 10,
    "look backward this distance when creating reference line from routing");

DEFINE_double(
    look_forward_distance, 70,
    "look forward this distance when creating reference line from routing");
DEFINE_bool(enable_smooth_reference_line, true,
            "enable smooth the map reference line");

DEFINE_int32(max_history_frame_num, 5, "The maximum history frame number");

DEFINE_double(max_collision_distance, 0.1,
              "considered as collision if distance (meters) is smaller than or "
              "equal to this (meters)");

DEFINE_double(replan_distance_threshold, 5.0,
              "The distance threshold of replan");

DEFINE_bool(enable_reference_line_provider_thread, false,
            "Enable reference line provider thread.");

DEFINE_double(default_reference_line_width, 4.0,
              "Default reference line width");

DEFINE_double(planning_upper_speed_limit, 7.5,
              "Maximum speed (m/s) in planning.");

DEFINE_double(planning_distance, 100, "Planning distance");

DEFINE_double(trajectory_time_length, 8.0, "Trajectory time length");
DEFINE_double(trajectory_time_resolution, 0.1,
              "Trajectory time resolution in planning");
DEFINE_double(output_trajectory_time_resolution, 0.05,
              "Trajectory time resolution when publish");

DEFINE_bool(enable_trajectory_check, false,
            "Enable sanity check for planning trajectory.");

DEFINE_double(speed_lower_bound, -0.02, "The lowest speed allowed.");
DEFINE_double(speed_upper_bound, 40.0, "The highest speed allowed.");

DEFINE_double(longitudinal_acceleration_lower_bound, -4.5,
              "The lowest longitudinal acceleration allowed.");
DEFINE_double(longitudinal_acceleration_upper_bound, 4.0,
              "The highest longitudinal acceleration allowed.");

DEFINE_double(lateral_acceleration_bound, 4.5,
              "Bound of lateral acceleration; symmetric for left and right");
DEFINE_double(lateral_jerk_bound, 4.0,
              "Bound of lateral jerk; symmetric for left and right");

DEFINE_double(longitudinal_jerk_lower_bound, -4.0,
              "The lower bound of longitudinal jerk.");
DEFINE_double(longitudinal_jerk_upper_bound, 4.0,
              "The upper bound of longitudinal jerk.");

DEFINE_double(kappa_bound, 1.00, "The bound for vehicle curvature");

// ST Boundary
DEFINE_double(st_max_s, 80, "the maximum s of st boundary");
DEFINE_double(st_max_t, 10, "the maximum t of st boundary");

// Decision Part
DEFINE_double(static_obstacle_speed_threshold, 1.0,
              "obstacles are considered as static obstacle if its speed is "
              "less than this value (m/s)");
DEFINE_bool(enable_nudge_decision, false, "enable nudge decision");
DEFINE_double(static_decision_ignore_s_range, 3.0,
              "threshold for judging nudge in dp path computing decision");
DEFINE_double(static_decision_nudge_l_buffer, 0.5, "l buffer for nudge");
DEFINE_double(stop_distance_obstacle, 10.0,
              "stop distance from in-lane obstacle (meters)");
DEFINE_double(stop_distance_destination, 3.0,
              "stop distance from destination line");
DEFINE_double(min_driving_width, 2.5,
              "minimum road width(meters) for adc to drive through");
DEFINE_double(nudge_distance_obstacle, 0.3,
              "minimum distance to nudge a obstacle (meters)");
DEFINE_double(follow_min_distance, 10,
              "min follow distance for vehicles/bicycles/moving objects");
DEFINE_double(stop_line_min_distance, 0.0,
              "min distance (meters) to stop line for a valid stop");

DEFINE_string(destination_obstacle_id, "DEST",
              "obstacle id for converting destination to an obstacle");
DEFINE_int32(virtual_obstacle_perception_id, -1,
             "virtual obstacle perception id(a negative int)");
DEFINE_double(virtual_stop_wall_length, 0.1,
              "virtual stop wall length (meters)");
DEFINE_double(virtual_stop_wall_width, 3.7, "virtual stop wall width (meters)");
DEFINE_double(virtual_stop_wall_height, 2.0,
              "virtual stop wall height (meters)");

// Prediction Part
DEFINE_double(prediction_total_time, 5.0, "Total prediction time");
DEFINE_bool(align_prediction_time, false,
            "enable align prediction data based planning time");

// Trajectory
DEFINE_bool(enable_rule_layer, true,
            "enable rule for trajectory before model computation");

// Traffic decision

DEFINE_string(planning_config_file,
              "modules/planning/conf/planning_config.pb.txt",
              "planning config file");

DEFINE_int32(trajectory_point_num_for_debug, 10,
             "number of output trajectory points for debugging");

DEFINE_double(decision_valid_stop_range, 0.5,
              "The valid stop range in decision.");
DEFINE_bool(enable_record_debug, true,
            "True to enable record debug into debug protobuf.");
DEFINE_bool(enable_prediction, true, "True to enable prediction input.");

// QpSt optimizer
DEFINE_bool(enable_slowdown_profile_generator, true,
            "True to enable slowdown speed profile generator.");
DEFINE_double(slowdown_speed_threshold, 8.0,
              "Only generator slowdown profile when adc speed is lower than "
              "this threshold. unit : m/s.");
DEFINE_double(slowdown_profile_deceleration, -1.0,
              "The deceleration to generate slowdown profile. unit: m/s^2.");
<|endoftext|>


<commit_before>/*
  Copyright:    © 2021 SIL International.
  Description:  Tests for the rust_mock interfaces.
*/
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>

#include <keyman/keyboardprocessor.h>
#include "path.hpp"

#include "state.hpp"

#define   try_status(expr) \
{auto __s = (expr); if (__s != KM_KBP_STATUS_OK) std::exit(100*__LINE__+__s);}

namespace
{
  std::string get_json_doc(km_kbp_state const& state)
  {
    size_t sz = 0;
    try_status(km_kbp_state_to_json(&state, nullptr, &sz));
    std::string buf(sz-1, 0);
    try_status(km_kbp_state_to_json(&state, &buf[0], &sz));

    return buf;
  }

  km_kbp_option_item test_env_opts[] =
  {
    {u"hello",     u"world", 0},
    KM_KBP_OPTIONS_END
  };

constexpr char const *doc1_expected = u8"\
{\n\
    \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\
    \"keyboard\" : {\n\
        \"id\" : \"dummy\",\n\
        \"folder\" : \"\",\n\
        \"version\" : \"3.145\",\n\
        \"rules\" : []\n\
    },\n\
    \"context\" : [\n\
        \"H\",\n\
        \"e\",\n\
        \"l\",\n\
        \"l\",\n\
        \"o\",\n\
        \" \",\n\
        \"😁\",\n\
        \"S\",\n\
        \"I\",\n\
        \"L\"\n\
    ],\n\
    \"actions\" : [\n\
        { \"persist\" : { \"keyboard\" : { \"__test_point\" : \"F2 pressed test save.\" } } }\n\
    ]\n\
}\n";

constexpr char const *doc2_expected = u8"\
{\n\
    \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\
    \"keyboard\" : {\n\
        \"id\" : \"dummy\",\n\
        \"folder\" : \"\",\n\
        \"version\" : \"3.145\",\n\
        \"rules\" : []\n\
    },\n\
    \"context\" : [],\n\
    \"actions\" : []\n\
}\n";

constexpr km_kbp_option_item const expected_persist_opt = {
  u"__test_point",
  u"F2 pressed test save.",
  KM_KBP_OPT_KEYBOARD
};

inline
bool operator==(km_kbp_option_item const & lhs, km_kbp_option_item const & rhs)
{
  return lhs.scope == rhs.scope
      && std::u16string(lhs.key) == rhs.key
      && std::u16string(lhs.value) == rhs.value;
}

bool operator==(km_kbp_action_item const & lhs,
                km_kbp_action_item const & rhs)
{
  if (lhs.type != rhs.type) return false;
  switch(lhs.type)
  {
    case KM_KBP_IT_CHAR:        return lhs.character == rhs.character;
    case KM_KBP_IT_MARKER:      return lhs.marker == rhs.marker;
    case KM_KBP_IT_PERSIST_OPT: return *lhs.option == *rhs.option;
    default: break;
  }

  return true;
}

#ifdef assert
#undef assert
#endif
#define assert(expr) {if (!(expr)) return __LINE__; }
bool action_items(km_kbp_state const * state,
                  std::initializer_list<km_kbp_action_item> const & expected)
{
  size_t n = 0;
  auto act = km_kbp_state_action_items(state, &n);

  for (auto &rhs: expected)
    if (!(*act++ == rhs)) return false;

  return true;
}

} // namespace

int main(int, char * [])
{
  km_kbp_keyboard * test_kb = nullptr;
  km_kbp_state * test_state = nullptr,
               * test_clone = nullptr;
  try_status(km_kbp_keyboard_load(km::kbp::path("dummy.rust_mock").c_str(), &test_kb));

  // Simple sanity tests.
  try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state));
  try_status(km_kbp_state_clone(test_state, &test_clone));
  // Check sub objects have been copied and not shared.
  if (km_kbp_state_context(test_state) == km_kbp_state_context(test_clone))
    return __LINE__;
  size_t n_actions = 0;
  if (km_kbp_state_action_items(test_state, &n_actions) == nullptr
      && n_actions != 0)
    return __LINE__;

  // Lets add data and do some basic checks of options and km_kbp_context
  km_kbp_context_item *citems = nullptr;
  try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems));
  try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems));
  km_kbp_context_items_dispose(citems);
  if(km_kbp_context_length(km_kbp_state_context(test_state)) != 7)
    return __LINE__;
  if(km_kbp_context_length(km_kbp_state_context(test_clone)) != 0)
    return __LINE__;

  // Overwrite some data.
  km_kbp_option_item new_opt[] = {
    {u"hello", u"globe", KM_KBP_OPT_ENVIRONMENT},
    KM_KBP_OPTIONS_END};
  try_status(km_kbp_state_options_update(test_clone, new_opt));

  // Test the engine
  auto attrs = km_kbp_get_engine_attrs(test_state);
  // Check the lib supplies our required interface.
  if (attrs->current - attrs->age > KM_KBP_LIB_CURRENT
      || attrs->current < KM_KBP_LIB_CURRENT) return __LINE__;
  if (attrs->max_context < 16) return __LINE__;

  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S,
                                  KM_KBP_MODIFIER_SHIFT));
#if 0
  // TODO: enable these tests once we have the data propagating to Rust
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_I,
                                  KM_KBP_MODIFIER_SHIFT));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('I')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, 0));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('l')}}}));

  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0));
  assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {0}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L,
                                  KM_KBP_MODIFIER_SHIFT));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('L')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2,0));
  assert(action_items(test_state, {{KM_KBP_IT_PERSIST_OPT, {0,},
                      {uintptr_t(&expected_persist_opt)}}}));

  // Test debug dump
  auto doc1 = get_json_doc(*test_state),
       doc2 = get_json_doc(*test_clone);

  std::cout << doc1 << std::endl;
  std::cout << doc2 << std::endl;

  // These should not be equal.
  if (doc1 == doc2)           return __LINE__;
  // These should be.
  if (doc1 != doc1_expected)  return __LINE__;
  if (doc2 != doc2_expected)  return __LINE__;
#endif

  // Destroy them
  km_kbp_state_dispose(test_state);
  km_kbp_state_dispose(test_clone);
  km_kbp_keyboard_dispose(test_kb);

  return 0;
}
<commit_msg>chore(common): stub out tests to prevent build errors<commit_after>/*
  Copyright:    © 2021 SIL International.
  Description:  Tests for the rust_mock interfaces.
*/
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>

#include <keyman/keyboardprocessor.h>
#include "path.hpp"

#include "state.hpp"

#define   try_status(expr) \
{auto __s = (expr); if (__s != KM_KBP_STATUS_OK) std::exit(100*__LINE__+__s);}

namespace
{
  std::string get_json_doc(km_kbp_state const& state)
  {
    size_t sz = 0;
    try_status(km_kbp_state_to_json(&state, nullptr, &sz));
    std::string buf(sz-1, 0);
    try_status(km_kbp_state_to_json(&state, &buf[0], &sz));

    return buf;
  }

  km_kbp_option_item test_env_opts[] =
  {
    {u"hello",     u"world", 0},
    KM_KBP_OPTIONS_END
  };

constexpr char const *doc1_expected = u8"\
{\n\
    \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\
    \"keyboard\" : {\n\
        \"id\" : \"dummy\",\n\
        \"folder\" : \"\",\n\
        \"version\" : \"3.145\",\n\
        \"rules\" : []\n\
    },\n\
    \"context\" : [\n\
        \"H\",\n\
        \"e\",\n\
        \"l\",\n\
        \"l\",\n\
        \"o\",\n\
        \" \",\n\
        \"😁\",\n\
        \"S\",\n\
        \"I\",\n\
        \"L\"\n\
    ],\n\
    \"actions\" : [\n\
        { \"persist\" : { \"keyboard\" : { \"__test_point\" : \"F2 pressed test save.\" } } }\n\
    ]\n\
}\n";

constexpr char const *doc2_expected = u8"\
{\n\
    \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\
    \"keyboard\" : {\n\
        \"id\" : \"dummy\",\n\
        \"folder\" : \"\",\n\
        \"version\" : \"3.145\",\n\
        \"rules\" : []\n\
    },\n\
    \"context\" : [],\n\
    \"actions\" : []\n\
}\n";

constexpr km_kbp_option_item const expected_persist_opt = {
  u"__test_point",
  u"F2 pressed test save.",
  KM_KBP_OPT_KEYBOARD
};

inline
bool operator==(km_kbp_option_item const & lhs, km_kbp_option_item const & rhs)
{
  return lhs.scope == rhs.scope
      && std::u16string(lhs.key) == rhs.key
      && std::u16string(lhs.value) == rhs.value;
}

bool operator==(km_kbp_action_item const & lhs,
                km_kbp_action_item const & rhs)
{
  if (lhs.type != rhs.type) return false;
  switch(lhs.type)
  {
    case KM_KBP_IT_CHAR:        return lhs.character == rhs.character;
    case KM_KBP_IT_MARKER:      return lhs.marker == rhs.marker;
    case KM_KBP_IT_PERSIST_OPT: return *lhs.option == *rhs.option;
    default: break;
  }

  return true;
}

#ifdef assert
#undef assert
#endif
#define assert(expr) {if (!(expr)) return __LINE__; }
bool action_items(km_kbp_state const * state,
                  std::initializer_list<km_kbp_action_item> const & expected)
{
  size_t n = 0;
  auto act = km_kbp_state_action_items(state, &n);

  for (auto &rhs: expected)
    if (!(*act++ == rhs)) return false;

  return true;
}

} // namespace

int main(int, char * [])
{
  km_kbp_keyboard * test_kb = nullptr;
  km_kbp_state * test_state = nullptr,
               * test_clone = nullptr;
  try_status(km_kbp_keyboard_load(km::kbp::path("dummy.rust_mock").c_str(), &test_kb));

  // Simple sanity tests.
  try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state));
  try_status(km_kbp_state_clone(test_state, &test_clone));
  // Check sub objects have been copied and not shared.
  if (km_kbp_state_context(test_state) == km_kbp_state_context(test_clone))
    return __LINE__;
  size_t n_actions = 0;
  if (km_kbp_state_action_items(test_state, &n_actions) == nullptr
      && n_actions != 0)
    return __LINE__;

  // Lets add data and do some basic checks of options and km_kbp_context
  km_kbp_context_item *citems = nullptr;
  try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems));
  try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems));
  km_kbp_context_items_dispose(citems);
  if(km_kbp_context_length(km_kbp_state_context(test_state)) != 7)
    return __LINE__;
  if(km_kbp_context_length(km_kbp_state_context(test_clone)) != 0)
    return __LINE__;

  // Overwrite some data.
  km_kbp_option_item new_opt[] = {
    {u"hello", u"globe", KM_KBP_OPT_ENVIRONMENT},
    KM_KBP_OPTIONS_END};
  try_status(km_kbp_state_options_update(test_clone, new_opt));

  // Test the engine
  auto attrs = km_kbp_get_engine_attrs(test_state);
  // Check the lib supplies our required interface.
  if (attrs->current - attrs->age > KM_KBP_LIB_CURRENT
      || attrs->current < KM_KBP_LIB_CURRENT) return __LINE__;
  if (attrs->max_context < 16) return __LINE__;

  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S,
                                  KM_KBP_MODIFIER_SHIFT));

  //TEMP
  action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}});
  auto doc1 = get_json_doc(*test_state);
  std::cout << doc1 << std::endl;
#if 0
  // TODO: enable these tests once we have the data propagating to Rust
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_I,
                                  KM_KBP_MODIFIER_SHIFT));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('I')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, 0));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('l')}}}));

  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0));
  assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {0}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L,
                                  KM_KBP_MODIFIER_SHIFT));
  assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('L')}}}));
  try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2,0));
  assert(action_items(test_state, {{KM_KBP_IT_PERSIST_OPT, {0,},
                      {uintptr_t(&expected_persist_opt)}}}));

  // Test debug dump
  auto doc1 = get_json_doc(*test_state),
       doc2 = get_json_doc(*test_clone);

  std::cout << doc1 << std::endl;
  std::cout << doc2 << std::endl;

  // These should not be equal.
  if (doc1 == doc2)           return __LINE__;
  // These should be.
  if (doc1 != doc1_expected)  return __LINE__;
  if (doc2 != doc2_expected)  return __LINE__;
#endif

  // Destroy them
  km_kbp_state_dispose(test_state);
  km_kbp_state_dispose(test_clone);
  km_kbp_keyboard_dispose(test_kb);

  return 0;
}
<|endoftext|>


<commit_before>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_FEATURE_HPP
#define MAPNIK_FEATURE_HPP

// mapnik
#include <mapnik/value.hpp>
#include <mapnik/geometry.hpp>
#include <mapnik/raster.hpp>
#include <mapnik/feature_kv_iterator.hpp>
// boost
#include <boost/version.hpp>
#if BOOST_VERSION >= 104000
#include <boost/property_map/property_map.hpp>
#else
#include <boost/property_map.hpp>
#endif
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>

// stl
#include <vector>
#include <map>
#include <stdexcept>

namespace mapnik {

typedef boost::shared_ptr<raster> raster_ptr;

class feature_impl;

template <typename T>
class context : private boost::noncopyable,
                public boost::associative_property_map<T>

{
    friend class feature_impl;
public:
    typedef T map_type;
    typedef typename boost::associative_property_map<map_type> base_type;
    typedef typename map_type::value_type value_type;
    typedef typename map_type::key_type key_type;
    typedef typename map_type::size_type size_type;
    typedef typename map_type::difference_type difference_type;
    typedef typename map_type::iterator iterator;
    typedef typename map_type::const_iterator const_iterator;

    context()
        : base_type(mapping_) {}

    size_type push(key_type const& name)
    {
        size_type index = mapping_.size();
        mapping_.insert(std::make_pair(name, index));
        return index;
    }

    size_type size() const { return mapping_.size(); }
    const_iterator begin() const { return mapping_.begin();}
    const_iterator end() const { return mapping_.end();}

private:
    map_type mapping_;
};

typedef context<std::map<std::string,std::size_t> > context_type;
typedef boost::shared_ptr<context_type> context_ptr;

class feature_impl : private boost::noncopyable
{
    friend class feature_kv_iterator;
public:

    typedef mapnik::value value_type;
    typedef std::vector<value_type> cont_type;
    typedef feature_kv_iterator iterator;

    feature_impl(context_ptr const& ctx, int id)
        : id_(id),
          ctx_(ctx),
          data_(ctx_->mapping_.size())
    {}

    inline int id() const { return id_;}

    inline void set_id(int id) { id_ = id;}

    template <typename T>
    void put(context_type::key_type const& key, T const& val)
    {
        put(key,value(val));
    }

    template <typename T>
    void put_new(context_type::key_type const& key, T const& val)
    {
        put_new(key,value(val));
    }

    void put(context_type::key_type const& key, value const& val)
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end()
            && itr->second < data_.size())
        {
            data_[itr->second] = val;
        }
        else
        {
            throw std::out_of_range(std::string("Key does not exist: '") + key + "'");
        }
    }

    void put_new(context_type::key_type const& key, value const& val)
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end()
            && itr->second < data_.size())
        {
            data_[itr->second] = val;
        }
        else
        {
            cont_type::size_type index = ctx_->push(key);
            if (index == data_.size())
                data_.push_back(val);
        }
    }

    bool has_key(context_type::key_type const& key) const
    {
        return (ctx_->mapping_.find(key) != ctx_->mapping_.end());
    }

    value_type const& get(context_type::key_type const& key) const
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end())
            return get(itr->second);
        else
            throw std::out_of_range(std::string("Key does not exist: '") + key + "'");
    }

    value_type const& get(std::size_t index) const
    {
        if (index < data_.size())
            return data_[index];
        throw std::out_of_range("Index out of range");
    }

    boost::optional<value_type const&> get_optional(std::size_t index) const
    {
        if (index < data_.size())
            return boost::optional<value_type const&>(data_[index]);
        return boost::optional<value_type const&>();
    }

    std::size_t size() const
    {
        return data_.size();
    }

    context_ptr context()
    {
        return ctx_;
    }

    boost::ptr_vector<geometry_type> const& paths() const
    {
        return geom_cont_;
    }

    boost::ptr_vector<geometry_type> & paths()
    {
        return geom_cont_;
    }

    void add_geometry(geometry_type * geom)
    {
        geom_cont_.push_back(geom);
    }

    unsigned num_geometries() const
    {
        return geom_cont_.size();
    }

    geometry_type const& get_geometry(unsigned index) const
    {
        return geom_cont_[index];
    }

    geometry_type& get_geometry(unsigned index)
    {
        return geom_cont_[index];
    }

    box2d<double> envelope() const
    {
        box2d<double> result;
        for (unsigned i=0;i<num_geometries();++i)
        {
            geometry_type const& geom = get_geometry(i);
            if (i==0)
            {
                box2d<double> box = geom.envelope();
                result.init(box.minx(),box.miny(),box.maxx(),box.maxy());
            }
            else
            {
                result.expand_to_include(geom.envelope());
            }
        }
        return result;
    }

    const raster_ptr& get_raster() const
    {
        return raster_;
    }

    void set_raster(raster_ptr const& raster)
    {
        raster_ = raster;
    }

    feature_kv_iterator begin() const
    {
        return feature_kv_iterator(*this,true);
    }

    feature_kv_iterator end() const
    {
        return feature_kv_iterator(*this);
    }

    std::string to_string() const
    {
        std::stringstream ss;
        ss << "Feature ( id=" << id_ << std::endl;
        context_type::map_type::const_iterator itr = ctx_->mapping_.begin();
        context_type::map_type::const_iterator end = ctx_->mapping_.end();
        for ( ;itr!=end; ++itr)
        {
            std::size_t index = itr->second;
            if (index < data_.size())
            {
                ss << "  " << itr->first  << ":" <<  data_[itr->second] << std::endl;
            }
        }
        ss << ")" << std::endl;
        return ss.str();
    }

private:
    int id_;
    context_ptr ctx_;
    boost::ptr_vector<geometry_type> geom_cont_;
    raster_ptr raster_;
    cont_type data_;
};

inline std::ostream& operator<< (std::ostream & out,feature_impl const& f)
{
    out << f.to_string();
    return out;
}

typedef feature_impl Feature;

}

#endif // MAPNIK_FEATURE_HPP
<commit_msg>declare feature_impl and context_type for windows symbol visibility<commit_after>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2011 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_FEATURE_HPP
#define MAPNIK_FEATURE_HPP

// mapnik
#include <mapnik/config.hpp>
#include <mapnik/value.hpp>
#include <mapnik/geometry.hpp>
#include <mapnik/raster.hpp>
#include <mapnik/feature_kv_iterator.hpp>
// boost
#include <boost/version.hpp>
#if BOOST_VERSION >= 104000
#include <boost/property_map/property_map.hpp>
#else
#include <boost/property_map.hpp>
#endif
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>

// stl
#include <vector>
#include <map>
#include <stdexcept>

namespace mapnik {

typedef boost::shared_ptr<raster> raster_ptr;

class feature_impl;

template <typename T>
class context : private boost::noncopyable,
                public boost::associative_property_map<T>

{
    friend class feature_impl;
public:
    typedef T map_type;
    typedef typename boost::associative_property_map<map_type> base_type;
    typedef typename map_type::value_type value_type;
    typedef typename map_type::key_type key_type;
    typedef typename map_type::size_type size_type;
    typedef typename map_type::difference_type difference_type;
    typedef typename map_type::iterator iterator;
    typedef typename map_type::const_iterator const_iterator;

    context()
        : base_type(mapping_) {}

    size_type push(key_type const& name)
    {
        size_type index = mapping_.size();
        mapping_.insert(std::make_pair(name, index));
        return index;
    }

    size_type size() const { return mapping_.size(); }
    const_iterator begin() const { return mapping_.begin();}
    const_iterator end() const { return mapping_.end();}

private:
    map_type mapping_;
};

typedef MAPNIK_DECL context<std::map<std::string,std::size_t> > context_type;
typedef MAPNIK_DECL boost::shared_ptr<context_type> context_ptr;

class MAPNIK_DECL feature_impl : private boost::noncopyable
{
    friend class feature_kv_iterator;
public:

    typedef mapnik::value value_type;
    typedef std::vector<value_type> cont_type;
    typedef feature_kv_iterator iterator;

    feature_impl(context_ptr const& ctx, int id)
        : id_(id),
          ctx_(ctx),
          data_(ctx_->mapping_.size())
    {}

    inline int id() const { return id_;}

    inline void set_id(int id) { id_ = id;}

    template <typename T>
    void put(context_type::key_type const& key, T const& val)
    {
        put(key,value(val));
    }

    template <typename T>
    void put_new(context_type::key_type const& key, T const& val)
    {
        put_new(key,value(val));
    }

    void put(context_type::key_type const& key, value const& val)
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end()
            && itr->second < data_.size())
        {
            data_[itr->second] = val;
        }
        else
        {
            throw std::out_of_range(std::string("Key does not exist: '") + key + "'");
        }
    }

    void put_new(context_type::key_type const& key, value const& val)
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end()
            && itr->second < data_.size())
        {
            data_[itr->second] = val;
        }
        else
        {
            cont_type::size_type index = ctx_->push(key);
            if (index == data_.size())
                data_.push_back(val);
        }
    }

    bool has_key(context_type::key_type const& key) const
    {
        return (ctx_->mapping_.find(key) != ctx_->mapping_.end());
    }

    value_type const& get(context_type::key_type const& key) const
    {
        context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
        if (itr != ctx_->mapping_.end())
            return get(itr->second);
        else
            throw std::out_of_range(std::string("Key does not exist: '") + key + "'");
    }

    value_type const& get(std::size_t index) const
    {
        if (index < data_.size())
            return data_[index];
        throw std::out_of_range("Index out of range");
    }

    boost::optional<value_type const&> get_optional(std::size_t index) const
    {
        if (index < data_.size())
            return boost::optional<value_type const&>(data_[index]);
        return boost::optional<value_type const&>();
    }

    std::size_t size() const
    {
        return data_.size();
    }

    context_ptr context()
    {
        return ctx_;
    }

    boost::ptr_vector<geometry_type> const& paths() const
    {
        return geom_cont_;
    }

    boost::ptr_vector<geometry_type> & paths()
    {
        return geom_cont_;
    }

    void add_geometry(geometry_type * geom)
    {
        geom_cont_.push_back(geom);
    }

    unsigned num_geometries() const
    {
        return geom_cont_.size();
    }

    geometry_type const& get_geometry(unsigned index) const
    {
        return geom_cont_[index];
    }

    geometry_type& get_geometry(unsigned index)
    {
        return geom_cont_[index];
    }

    box2d<double> envelope() const
    {
        box2d<double> result;
        for (unsigned i=0;i<num_geometries();++i)
        {
            geometry_type const& geom = get_geometry(i);
            if (i==0)
            {
                box2d<double> box = geom.envelope();
                result.init(box.minx(),box.miny(),box.maxx(),box.maxy());
            }
            else
            {
                result.expand_to_include(geom.envelope());
            }
        }
        return result;
    }

    const raster_ptr& get_raster() const
    {
        return raster_;
    }

    void set_raster(raster_ptr const& raster)
    {
        raster_ = raster;
    }

    feature_kv_iterator begin() const
    {
        return feature_kv_iterator(*this,true);
    }

    feature_kv_iterator end() const
    {
        return feature_kv_iterator(*this);
    }

    std::string to_string() const
    {
        std::stringstream ss;
        ss << "Feature ( id=" << id_ << std::endl;
        context_type::map_type::const_iterator itr = ctx_->mapping_.begin();
        context_type::map_type::const_iterator end = ctx_->mapping_.end();
        for ( ;itr!=end; ++itr)
        {
            std::size_t index = itr->second;
            if (index < data_.size())
            {
                ss << "  " << itr->first  << ":" <<  data_[itr->second] << std::endl;
            }
        }
        ss << ")" << std::endl;
        return ss.str();
    }

private:
    int id_;
    context_ptr ctx_;
    boost::ptr_vector<geometry_type> geom_cont_;
    raster_ptr raster_;
    cont_type data_;
};

inline std::ostream& operator<< (std::ostream & out,feature_impl const& f)
{
    out << f.to_string();
    return out;
}

typedef feature_impl Feature;

}

#endif // MAPNIK_FEATURE_HPP
<|endoftext|>


<commit_before>#pragma once

#include "adapters/alsa.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"

LEMONBUDDY_NS

namespace modules {
  class volume_module : public event_module<volume_module> {
   public:
    using event_module::event_module;

    void setup() {
      // Load configuration values {{{

      auto master_mixer = m_conf.get<string>(name(), "master-mixer", "Master");
      auto speaker_mixer = m_conf.get<string>(name(), "speaker-mixer", "");
      auto headphone_mixer = m_conf.get<string>(name(), "headphone-mixer", "");

      m_headphone_ctrl_numid = m_conf.get<int>(name(), "headphone-id", -1);

      if (!headphone_mixer.empty() && m_headphone_ctrl_numid == -1)
        throw module_error(
            "volume_module: Missing required property value for \"headphone-id\"...");
      else if (headphone_mixer.empty() && m_headphone_ctrl_numid != -1)
        throw module_error(
            "volume_module: Missing required property value for \"headphone-mixer\"...");

      if (string_util::lower(speaker_mixer) == "master")
        throw module_error(
            "volume_module: The \"Master\" mixer is already processed internally. Specify another "
            "mixer or comment out the \"speaker-mixer\" parameter...");
      if (string_util::lower(headphone_mixer) == "master")
        throw module_error(
            "volume_module: The \"Master\" mixer is already processed internally. Specify another "
            "mixer or comment out the \"headphone-mixer\" parameter...");

      // }}}
      // Setup mixers {{{

      auto create_mixer = [](const logger& log, string mixer_name) {
        unique_ptr<alsa_mixer> mixer;

        try {
          mixer = make_unique<alsa_mixer>(mixer_name);
        } catch (const alsa_mixer_error& e) {
          log.err("volume_module: Failed to open '%s' mixer => %s", mixer_name, e.what());
          mixer.reset();
        }

        return mixer;
      };

      m_master_mixer = create_mixer(m_log, master_mixer);

      if (!speaker_mixer.empty())
        m_speaker_mixer = create_mixer(m_log, speaker_mixer);
      if (!headphone_mixer.empty())
        m_headphone_mixer = create_mixer(m_log, headphone_mixer);

      if (!m_master_mixer && !m_speaker_mixer && !m_headphone_mixer) {
        stop();
        return;
      }

      if (m_headphone_mixer && m_headphone_ctrl_numid > -1) {
        try {
          m_headphone_ctrl = make_unique<alsa_ctl_interface>(m_headphone_ctrl_numid);
        } catch (const alsa_ctl_interface_error& e) {
          m_log.err("%s: Failed to open headphone control interface => %s", name(), e.what());
          m_headphone_ctrl.reset();
        }
      }

      // }}}
      // Add formats and elements {{{

      m_formatter->add(
          FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
      m_formatter->add(
          FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});

      if (m_formatter->has(TAG_BAR_VOLUME)) {
        m_bar_volume = get_config_bar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
      }
      if (m_formatter->has(TAG_RAMP_VOLUME)) {
        m_ramp_volume = get_config_ramp(m_conf, name(), TAG_RAMP_VOLUME);
        m_ramp_headphones = get_config_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
      }
      if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
        m_label_volume =
            get_optional_config_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%");
        m_label_volume_tokenized = m_label_volume->clone();
      }
      if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
        m_label_muted = get_optional_config_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%");
        m_label_muted_tokenized = m_label_muted->clone();
      }

      // }}}
    }

    void stop() {
      std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
      m_master_mixer.reset();
      m_speaker_mixer.reset();
      m_headphone_mixer.reset();
      m_headphone_ctrl.reset();
      event_module::stop();
    }

    bool has_event() {
      try {
        bool has_event = false;
        if (m_master_mixer)
          has_event |= m_master_mixer->wait(25);
        if (m_speaker_mixer)
          has_event |= m_speaker_mixer->wait(25);
        if (m_headphone_mixer)
          has_event |= m_headphone_mixer->wait(25);
        if (m_headphone_ctrl)
          has_event |= m_headphone_ctrl->wait(25);
        return has_event;
      } catch (const alsa_exception& e) {
        m_log.err("%s: %s", name(), e.what());
        return false;
      }
    }

    bool update() {
      // Consume any other pending events
      if (m_master_mixer)
        m_master_mixer->process_events();
      if (m_speaker_mixer)
        m_speaker_mixer->process_events();
      if (m_headphone_mixer)
        m_headphone_mixer->process_events();
      if (m_headphone_ctrl)
        m_headphone_ctrl->wait(0);

      int volume = 100;
      bool muted = false;

      if (m_master_mixer) {
        volume *= m_master_mixer->get_volume() / 100.0f;
        muted |= m_master_mixer->is_muted();
      }

      if (m_headphone_mixer && m_headphone_ctrl && m_headphone_ctrl->test_device_plugged()) {
        m_headphones = true;
        volume *= m_headphone_mixer->get_volume() / 100.0f;
        muted |= m_headphone_mixer->is_muted();
      } else if (m_speaker_mixer) {
        m_headphones = false;
        volume *= m_speaker_mixer->get_volume() / 100.0f;
        muted |= m_speaker_mixer->is_muted();
      }

      m_volume = volume;
      m_muted = muted;

      if (m_label_volume_tokenized) {
        m_label_volume_tokenized->m_text = m_label_volume->m_text;
        m_label_volume_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
      }

      if (m_label_muted_tokenized) {
        m_label_muted_tokenized->m_text = m_label_muted->m_text;
        m_label_muted_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
      }

      return true;
    }

    string get_format() {
      return m_muted == true ? FORMAT_MUTED : FORMAT_VOLUME;
    }

    string get_output() {
      m_builder->cmd(mousebtn::LEFT, EVENT_TOGGLE_MUTE);

      if (!m_muted && m_volume < 100)
        m_builder->cmd(mousebtn::SCROLL_UP, EVENT_VOLUME_UP);
      else
        m_log.trace("%s: Not adding scroll up handler (muted or volume = 100)", name());

      if (!m_muted && m_volume > 0)
        m_builder->cmd(mousebtn::SCROLL_DOWN, EVENT_VOLUME_DOWN);
      else
        m_log.trace("%s: Not adding scroll down handler (muted or volume = 0)", name());

      m_builder->node(module::get_output());

      return m_builder->flush();
    }

    bool build(builder* builder, string tag) {
      if (tag == TAG_BAR_VOLUME)
        builder->node(m_bar_volume->output(m_volume));
      else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones))
        builder->node(m_ramp_volume->get_by_percentage(m_volume));
      else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones)
        builder->node(m_ramp_headphones->get_by_percentage(m_volume));
      else if (tag == TAG_LABEL_VOLUME)
        builder->node(m_label_volume_tokenized);
      else if (tag == TAG_LABEL_MUTED)
        builder->node(m_label_muted_tokenized);
      else
        return false;
      return true;
    }

    bool handle_event(string cmd) {
      if (cmd.compare(0, 3, EVENT_PREFIX) != 0)
        return false;

      if (!m_master_mixer)
        return false;

      alsa_mixer* master_mixer = m_master_mixer.get();
      alsa_mixer* other_mixer = nullptr;

      if (m_headphone_mixer && m_headphones)
        other_mixer = m_headphone_mixer.get();
      else if (m_speaker_mixer)
        other_mixer = m_speaker_mixer.get();
      else
        return false;

      if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) {
        master_mixer->set_mute(m_muted);
        if (other_mixer != nullptr)
          other_mixer->set_mute(m_muted);
      } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) {
        master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() + 5, 0, 100));
        if (other_mixer != nullptr)
          other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() + 5, 0, 100));
      } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) {
        master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() - 5, 0, 100));
        if (other_mixer != nullptr)
          other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() - 5, 0, 100));
      } else {
        return false;
      }

      return true;
    }

    bool receive_events() const {
      return true;
    }

   private:
    static constexpr auto FORMAT_VOLUME = "format-volume";
    static constexpr auto FORMAT_MUTED = "format-muted";

    static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
    static constexpr auto TAG_RAMP_HEADPHONES = "<ramp-headphones>";
    static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
    static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
    static constexpr auto TAG_LABEL_MUTED = "<label-muted>";

    static constexpr auto EVENT_PREFIX = "vol";
    static constexpr auto EVENT_VOLUME_UP = "volup";
    static constexpr auto EVENT_VOLUME_DOWN = "voldown";
    static constexpr auto EVENT_TOGGLE_MUTE = "volmute";

    progressbar_t m_bar_volume;
    ramp_t m_ramp_volume;
    ramp_t m_ramp_headphones;
    label_t m_label_volume;
    label_t m_label_volume_tokenized;
    label_t m_label_muted;
    label_t m_label_muted_tokenized;

    unique_ptr<alsa_mixer> m_master_mixer;
    unique_ptr<alsa_mixer> m_speaker_mixer;
    unique_ptr<alsa_mixer> m_headphone_mixer;
    unique_ptr<alsa_ctl_interface> m_headphone_ctrl;
    int m_headphone_ctrl_numid = -1;
    int m_volume = 0;

    stateflag m_muted{false};
    stateflag m_headphones{false};
  };
}

LEMONBUDDY_NS_END
<commit_msg>fix(volume): Do not require speaker/headphone for events<commit_after>#pragma once

#include "adapters/alsa.hpp"
#include "config.hpp"
#include "drawtypes/label.hpp"
#include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp"
#include "modules/meta.hpp"

LEMONBUDDY_NS

namespace modules {
  class volume_module : public event_module<volume_module> {
   public:
    using event_module::event_module;

    void setup() {
      // Load configuration values {{{

      auto master_mixer = m_conf.get<string>(name(), "master-mixer", "Master");
      auto speaker_mixer = m_conf.get<string>(name(), "speaker-mixer", "");
      auto headphone_mixer = m_conf.get<string>(name(), "headphone-mixer", "");

      m_headphone_id = m_conf.get<int>(name(), "headphone-id", -1);

      if (!headphone_mixer.empty() && m_headphone_id == -1)
        throw module_error(
            "volume_module: Missing required property value for \"headphone-id\"...");
      else if (headphone_mixer.empty() && m_headphone_id != -1)
        throw module_error(
            "volume_module: Missing required property value for \"headphone-mixer\"...");

      if (string_util::lower(speaker_mixer) == "master")
        throw module_error(
            "volume_module: The \"Master\" mixer is already processed internally. Specify another "
            "mixer or comment out the \"speaker-mixer\" parameter...");
      if (string_util::lower(headphone_mixer) == "master")
        throw module_error(
            "volume_module: The \"Master\" mixer is already processed internally. Specify another "
            "mixer or comment out the \"headphone-mixer\" parameter...");

      // }}}
      // Setup mixers {{{

      auto create_mixer = [](const logger& log, string mixer_name) {
        unique_ptr<alsa_mixer> mixer;

        try {
          mixer = make_unique<alsa_mixer>(mixer_name);
        } catch (const alsa_mixer_error& e) {
          log.err("volume_module: Failed to open '%s' mixer => %s", mixer_name, e.what());
          mixer.reset();
        }

        return mixer;
      };

      m_master_mixer = create_mixer(m_log, master_mixer);

      if (!speaker_mixer.empty())
        m_speaker_mixer = create_mixer(m_log, speaker_mixer);
      if (!headphone_mixer.empty())
        m_headphone_mixer = create_mixer(m_log, headphone_mixer);

      if (!m_master_mixer && !m_speaker_mixer && !m_headphone_mixer) {
        m_log.err("%s: No configured mixers, stopping module...", name());
        stop();
        return;
      }

      if (m_headphone_mixer && m_headphone_id > -1) {
        try {
          m_headphone_ctrl = make_unique<alsa_ctl_interface>(m_headphone_id);
        } catch (const alsa_ctl_interface_error& e) {
          m_log.err("%s: Failed to open headphone control interface => %s", name(), e.what());
          m_headphone_ctrl.reset();
        }
      }

      // }}}
      // Add formats and elements {{{

      m_formatter->add(
          FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
      m_formatter->add(
          FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});

      if (m_formatter->has(TAG_BAR_VOLUME)) {
        m_bar_volume = get_config_bar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
      }
      if (m_formatter->has(TAG_RAMP_VOLUME)) {
        m_ramp_volume = get_config_ramp(m_conf, name(), TAG_RAMP_VOLUME);
        m_ramp_headphones = get_config_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
      }
      if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
        m_label_volume =
            get_optional_config_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%");
        m_label_volume_tokenized = m_label_volume->clone();
      }
      if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
        m_label_muted = get_optional_config_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%");
        m_label_muted_tokenized = m_label_muted->clone();
      }

      // }}}
    }

    void stop() {
      std::lock_guard<threading_util::spin_lock> lck(this->update_lock);
      m_master_mixer.reset();
      m_speaker_mixer.reset();
      m_headphone_mixer.reset();
      m_headphone_ctrl.reset();
      event_module::stop();
    }

    bool has_event() {
      try {
        bool has_event = false;
        if (m_master_mixer)
          has_event |= m_master_mixer->wait(25);
        if (m_speaker_mixer)
          has_event |= m_speaker_mixer->wait(25);
        if (m_headphone_mixer)
          has_event |= m_headphone_mixer->wait(25);
        if (m_headphone_ctrl)
          has_event |= m_headphone_ctrl->wait(25);
        return has_event;
      } catch (const alsa_exception& e) {
        m_log.err("%s: %s", name(), e.what());
        return false;
      }
    }

    bool update() {
      // Consume any other pending events
      if (m_master_mixer)
        m_master_mixer->process_events();
      if (m_speaker_mixer)
        m_speaker_mixer->process_events();
      if (m_headphone_mixer)
        m_headphone_mixer->process_events();
      if (m_headphone_ctrl)
        m_headphone_ctrl->wait(0);

      int volume = 100;
      bool muted = false;

      if (m_master_mixer) {
        volume *= m_master_mixer->get_volume() / 100.0f;
        muted |= m_master_mixer->is_muted();
      }

      if (m_headphone_mixer && m_headphone_ctrl && m_headphone_ctrl->test_device_plugged()) {
        m_headphones = true;
        volume *= m_headphone_mixer->get_volume() / 100.0f;
        muted |= m_headphone_mixer->is_muted();
      } else if (m_speaker_mixer) {
        m_headphones = false;
        volume *= m_speaker_mixer->get_volume() / 100.0f;
        muted |= m_speaker_mixer->is_muted();
      }

      m_volume = volume;
      m_muted = muted;

      if (m_label_volume_tokenized) {
        m_label_volume_tokenized->m_text = m_label_volume->m_text;
        m_label_volume_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
      }

      if (m_label_muted_tokenized) {
        m_label_muted_tokenized->m_text = m_label_muted->m_text;
        m_label_muted_tokenized->replace_token("%percentage%", to_string(m_volume) + "%");
      }

      return true;
    }

    string get_format() {
      return m_muted == true ? FORMAT_MUTED : FORMAT_VOLUME;
    }

    string get_output() {
      m_builder->cmd(mousebtn::LEFT, EVENT_TOGGLE_MUTE);

      if (!m_muted && m_volume < 100)
        m_builder->cmd(mousebtn::SCROLL_UP, EVENT_VOLUME_UP);
      else
        m_log.trace("%s: Not adding scroll up handler (muted or volume = 100)", name());

      if (!m_muted && m_volume > 0)
        m_builder->cmd(mousebtn::SCROLL_DOWN, EVENT_VOLUME_DOWN);
      else
        m_log.trace("%s: Not adding scroll down handler (muted or volume = 0)", name());

      m_builder->node(module::get_output());

      return m_builder->flush();
    }

    bool build(builder* builder, string tag) {
      if (tag == TAG_BAR_VOLUME)
        builder->node(m_bar_volume->output(m_volume));
      else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones))
        builder->node(m_ramp_volume->get_by_percentage(m_volume));
      else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones)
        builder->node(m_ramp_headphones->get_by_percentage(m_volume));
      else if (tag == TAG_LABEL_VOLUME)
        builder->node(m_label_volume_tokenized);
      else if (tag == TAG_LABEL_MUTED)
        builder->node(m_label_muted_tokenized);
      else
        return false;
      return true;
    }

    bool handle_event(string cmd) {
      if (cmd.compare(0, 3, EVENT_PREFIX) != 0)
        return false;

      if (!m_master_mixer)
        return false;

      alsa_mixer* master_mixer = m_master_mixer.get();
      alsa_mixer* other_mixer = nullptr;

      if (m_headphone_mixer && m_headphones)
        other_mixer = m_headphone_mixer.get();
      else if (m_speaker_mixer)
        other_mixer = m_speaker_mixer.get();

      if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) {
        master_mixer->set_mute(m_muted);
        if (other_mixer != nullptr)
          other_mixer->set_mute(m_muted);
      } else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) {
        master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() + 5, 0, 100));
        if (other_mixer != nullptr)
          other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() + 5, 0, 100));
      } else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) {
        master_mixer->set_volume(math_util::cap<float>(master_mixer->get_volume() - 5, 0, 100));
        if (other_mixer != nullptr)
          other_mixer->set_volume(math_util::cap<float>(other_mixer->get_volume() - 5, 0, 100));
      } else {
        return false;
      }

      return true;
    }

    bool receive_events() const {
      return true;
    }

   private:
    static constexpr auto FORMAT_VOLUME = "format-volume";
    static constexpr auto FORMAT_MUTED = "format-muted";

    static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
    static constexpr auto TAG_RAMP_HEADPHONES = "<ramp-headphones>";
    static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
    static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
    static constexpr auto TAG_LABEL_MUTED = "<label-muted>";

    static constexpr auto EVENT_PREFIX = "vol";
    static constexpr auto EVENT_VOLUME_UP = "volup";
    static constexpr auto EVENT_VOLUME_DOWN = "voldown";
    static constexpr auto EVENT_TOGGLE_MUTE = "volmute";

    progressbar_t m_bar_volume;
    ramp_t m_ramp_volume;
    ramp_t m_ramp_headphones;
    label_t m_label_volume;
    label_t m_label_volume_tokenized;
    label_t m_label_muted;
    label_t m_label_muted_tokenized;

    unique_ptr<alsa_mixer> m_master_mixer;
    unique_ptr<alsa_mixer> m_speaker_mixer;
    unique_ptr<alsa_mixer> m_headphone_mixer;
    unique_ptr<alsa_ctl_interface> m_headphone_ctrl;
    int m_headphone_id = -1;
    int m_volume = 0;

    stateflag m_muted{false};
    stateflag m_headphones{false};
  };
}

LEMONBUDDY_NS_END
<|endoftext|>