diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/args.cc | 389 | ||||
| -rw-r--r-- | src/args.hh | 64 | ||||
| -rw-r--r-- | src/cfg.cc | 199 | ||||
| -rw-r--r-- | src/cfg.hh | 45 | ||||
| -rw-r--r-- | src/check.hh | 39 | ||||
| -rw-r--r-- | src/colour.cc | 15 | ||||
| -rw-r--r-- | src/colour.hh | 20 | ||||
| -rw-r--r-- | src/config.h.in | 6 | ||||
| -rw-r--r-- | src/image.cc | 15 | ||||
| -rw-r--r-- | src/image.hh | 62 | ||||
| -rw-r--r-- | src/image_loader.hh | 15 | ||||
| -rw-r--r-- | src/image_processor.cc | 947 | ||||
| -rw-r--r-- | src/image_processor.hh | 29 | ||||
| -rw-r--r-- | src/io.cc | 358 | ||||
| -rw-r--r-- | src/io.hh | 109 | ||||
| -rw-r--r-- | src/line.cc | 127 | ||||
| -rw-r--r-- | src/line.hh | 37 | ||||
| -rw-r--r-- | src/logger.cc | 125 | ||||
| -rw-r--r-- | src/logger.hh | 41 | ||||
| -rw-r--r-- | src/main.cc | 47 | ||||
| -rw-r--r-- | src/paths.cc | 100 | ||||
| -rw-r--r-- | src/paths.hh | 20 | ||||
| -rw-r--r-- | src/size.hh | 30 | ||||
| -rw-r--r-- | src/spawner.cc | 236 | ||||
| -rw-r--r-- | src/spawner.hh | 49 | ||||
| -rw-r--r-- | src/str.cc | 94 | ||||
| -rw-r--r-- | src/str.hh | 34 | ||||
| -rw-r--r-- | src/u.hh | 21 | ||||
| -rw-r--r-- | src/u8.hh | 196 | ||||
| -rw-r--r-- | src/unique_fd.cc | 9 | ||||
| -rw-r--r-- | src/unique_fd.hh | 36 | ||||
| -rw-r--r-- | src/xpm/.clang-tidy | 2 | ||||
| -rw-r--r-- | src/xpm/color.c | 882 | ||||
| -rw-r--r-- | src/xpm/dix-config.h | 1 | ||||
| -rw-r--r-- | src/xpm/dix/dix_priv.h | 14 | ||||
| -rw-r--r-- | src/xpm/include/dix.h | 14 |
36 files changed, 4427 insertions, 0 deletions
diff --git a/src/args.cc b/src/args.cc new file mode 100644 index 0000000..1794941 --- /dev/null +++ b/src/args.cc @@ -0,0 +1,389 @@ +#include "args.hh" + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <format> +#include <iostream> +#include <map> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +std::string kEmpty; + +class OptionImpl : public Args::OptionArgument { + public: + enum Type : uint8_t { + NoArgument, + RequiredArgument, + OptionalArgument, + }; + + OptionImpl(Type type, char shortname, std::string longname, std::string arg, + std::string help) + : type(type), + shortname(shortname), + longname(std::move(longname)), + arg(std::move(arg)), + help(std::move(help)) {} + + const Type type; + const char shortname; + const std::string longname; + const std::string arg; + const std::string help; + + [[nodiscard]] bool is_set() const override { return is_set_; } + + [[nodiscard]] bool has_argument() const override { + return value_.has_value(); + } + + [[nodiscard]] const std::string& argument() const override { + if (value_.has_value()) + return value_.value(); + assert(false); + return kEmpty; + } + + void clear() { + is_set_ = false; + value_.reset(); + } + + void set_argument(std::string value) { + assert(type == Type::RequiredArgument || type == Type::OptionalArgument); + is_set_ = true; + value_ = std::move(value); + } + + void set_no_argument() { + assert(type == Type::NoArgument || type == Type::OptionalArgument); + is_set_ = true; + value_.reset(); + } + + private: + bool is_set_{false}; + std::optional<std::string> value_; +}; + +class ArgsImpl : public Args { + public: + explicit ArgsImpl(std::string prgname) : prgname_(std::move(prgname)) {} + + std::shared_ptr<Option> option(char shortname, std::string longname, + std::string help) override { + auto opt = std::make_shared<OptionImpl>( + OptionImpl::Type::NoArgument, shortname, std::move(longname), + /* arg */ std::string(), std::move(help)); + add(opt); + return opt; + } + + std::shared_ptr<OptionArgument> option_argument(char shortname, + std::string longname, + std::string arg, + std::string help, + bool required) override { + auto opt = std::make_shared<OptionImpl>( + required ? OptionImpl::Type::RequiredArgument + : OptionImpl::Type::OptionalArgument, + shortname, std::move(longname), std::move(arg), std::move(help)); + add(opt); + return opt; + } + + bool run(int argc, char** argv, + std::vector<std::string_view>* arguments = nullptr) override { + last_error_.clear(); + for (auto& opt : options_) { + opt->clear(); + } + + std::string_view prgname; + if (prgname_.empty()) { + if (argc > 0) + prgname = argv[0]; + } else { + prgname = prgname_; + } + + for (int a = 1; a < argc; ++a) { + assert(argv[a]); + if (argv[a][0] == '-' && argv[a][1] != '\0') { + if (argv[a][1] == '-') { + // long option + size_t eq = 2; + while (argv[a][eq] != '=' && argv[a][eq] != '\0') + ++eq; + size_t end = eq; + while (argv[a][end] != '\0') + ++end; + + if (end == 2) { + // "--", no more options signal + if (arguments) { + for (++a; a < argc; ++a) + arguments->emplace_back(argv[a]); + } + break; + } + + auto name = std::string_view(argv[a] + 2, eq - 2); + auto it = long_.find(name); + if (it == long_.end()) { + last_error_ = + std::format("{}: unrecognized option '--{}'", prgname, name); + return false; + } + auto& opt = options_[it->second]; + + if (eq < end) { + // long option with argument after equal sign + switch (opt->type) { + case OptionImpl::Type::NoArgument: + last_error_ = + std::format("{}: option '--{}' doesn't allow an argument", + prgname, name); + return false; + case OptionImpl::Type::RequiredArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_argument( + std::string(argv[a] + eq + 1, end - (eq + 1))); + break; + } + } else { + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_no_argument(); + break; + case OptionImpl::Type::RequiredArgument: + if (++a >= argc) { + last_error_ = std::format( + "{}: option '--{}' requires an argument", prgname, name); + return false; + } + opt->set_argument(argv[a]); + break; + } + } + } else { + // short options + char* current = argv[a] + 1; + for (; *current; ++current) { + auto it = short_.find(*current); + if (it == short_.end()) { + last_error_ = + std::format("{}: invalid option -- '{}'", prgname, *current); + return false; + } + + auto& opt = options_[it->second]; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + opt->set_no_argument(); + break; + case OptionImpl::Type::RequiredArgument: + if (++a >= argc) { + last_error_ = + std::format("{}: option requires an argument -- '{}'", + prgname, *current); + return false; + } + opt->set_argument(argv[a]); + break; + } + } + } + } else { + if (arguments) + arguments->emplace_back(argv[a]); + } + } + return true; + } + + void print_error(std::ostream& out) const override { + if (last_error_.empty()) + return; + + out << last_error_ << '\n'; + } + + void print_help(std::ostream& out, uint32_t width = 79) const override { + if (options_.empty()) + return; + + uint32_t indent = 0; + const uint32_t max_need = width / 2; + std::vector<uint32_t> option_need; + for (auto const& opt : options_) { + uint32_t need; + if (opt->longname.empty()) { + need = 4; // -O + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + break; + case OptionImpl::Type::RequiredArgument: + need += 1 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + } + } else { + need = 8 + opt->longname.size(); // -O, --option + switch (opt->type) { + case OptionImpl::Type::NoArgument: + break; + case OptionImpl::Type::RequiredArgument: + // =ARG + need += 1 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + case OptionImpl::Type::OptionalArgument: + // [=ARG] + need += 3 + (opt->arg.empty() ? 3 : opt->arg.size()); + break; + } + } + need += 2; // margin + + option_need.emplace_back(need); + if (need <= max_need) { + indent = std::max(indent, need); + } + } + + print_wrap(out, width, /* indent */ 0, + "Mandatory arguments to long options" + " are mandatory for short options too."); + auto need_it = option_need.begin(); + for (auto const& opt : options_) { + if (opt->longname.empty()) { + out << " -" << opt->shortname; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + case OptionImpl::Type::OptionalArgument: + break; + case OptionImpl::Type::RequiredArgument: + out << " " << (opt->arg.empty() ? "ARG" : opt->arg); + break; + } + } else { + if (opt->shortname != '\0') { + out << " -" << opt->shortname << ", --"; + } else { + out << " --"; + } + out << opt->longname; + switch (opt->type) { + case OptionImpl::Type::NoArgument: + break; + case OptionImpl::Type::RequiredArgument: + out << "=" << (opt->arg.empty() ? "ARG" : opt->arg); + break; + case OptionImpl::Type::OptionalArgument: + out << "=[" << (opt->arg.empty() ? "ARG" : opt->arg) << ']'; + break; + } + } + + auto need = *need_it++; + if (need > max_need) { + out << '\n'; + if (!opt->help.empty()) { + print_wrap(out, width, 0, opt->help); + } + } else { + if (opt->help.empty()) { + out << '\n'; + } else { + out << " "; // add margin, already included in need + while (need++ < indent) + out << ' '; + print_wrap(out, width, indent, opt->help); + } + } + } + } + + private: + void add(std::shared_ptr<OptionImpl> opt) { + if (opt->shortname == '\0' && opt->longname.empty()) { + assert(false); + } else { + auto idx = options_.size(); + if (opt->shortname != '\0') + short_.emplace(opt->shortname, idx); + if (!opt->longname.empty()) + long_.emplace(opt->longname, idx); + } + options_.emplace_back(std::move(opt)); + } + + static inline bool is_whitespace(char c) { return c == ' ' || c == '\t'; } + + static void print_wrap(std::ostream& out, uint32_t width, uint32_t indent, + const std::string& str) { + if (indent + str.size() <= width) { + out << str << '\n'; + return; + } + if (width <= indent || indent + 10 > width) { + out << '\n'; + out << str << '\n'; + return; + } + const std::string indent_str(indent, ' '); + const uint32_t avail = width - indent; + size_t offset = 0; + while (offset + avail < str.size()) { + uint32_t i = avail; + while (i > 0 && !is_whitespace(str[offset + i])) + --i; + if (i == 0) { + out << str.substr(offset, avail - 1); + out << "-\n"; + offset += avail - 1; + } else { + out << str.substr(offset, i); + out << '\n'; + offset += i; + } + out << indent_str; + } + out << str.substr(offset); + out << '\n'; + } + + const std::string prgname_; + std::vector<std::shared_ptr<OptionImpl>> options_; + std::map<char, size_t> short_; + std::map<std::string_view, size_t> long_; + std::string last_error_; +}; + +} // namespace + +std::shared_ptr<Args::Option> Args::option(std::string longname, + std::string help) { + return option(/* shortname */ '\0', std::move(longname), std::move(help)); +} + +std::shared_ptr<Args::OptionArgument> Args::option_argument( + std::string longname, std::string arg, std::string help, bool required) { + return option_argument(/* shortname */ '\0', std::move(longname), + std::move(arg), std::move(help), required); +} + +std::unique_ptr<Args> Args::create(std::string prgname) { + return std::make_unique<ArgsImpl>(std::move(prgname)); +} diff --git a/src/args.hh b/src/args.hh new file mode 100644 index 0000000..14f3716 --- /dev/null +++ b/src/args.hh @@ -0,0 +1,64 @@ +#ifndef ARGS_HH +#define ARGS_HH + +#include <cstdint> +#include <iosfwd> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +class Args { + public: + virtual ~Args() = default; + + class Option { + public: + virtual ~Option() = default; + + [[nodiscard]] virtual bool is_set() const = 0; + + protected: + Option() = default; + Option(Option const&) = delete; + Option& operator=(Option const&) = delete; + }; + + class OptionArgument : public Option { + public: + [[nodiscard]] virtual bool has_argument() const = 0; + [[nodiscard]] virtual const std::string& argument() const = 0; + }; + + static std::unique_ptr<Args> create(std::string prgname = std::string()); + + virtual std::shared_ptr<Option> option(char shortname, + std::string longname = std::string(), + std::string help = std::string()) = 0; + + std::shared_ptr<Option> option(std::string longname, + std::string help = std::string()); + + virtual std::shared_ptr<OptionArgument> option_argument( + char shortname, std::string longname = std::string(), + std::string arg = std::string(), std::string help = std::string(), + bool required = true) = 0; + + std::shared_ptr<OptionArgument> option_argument( + std::string longname, std::string arg = std::string(), + std::string help = std::string(), bool required = true); + + virtual bool run(int argc, char** argv, + std::vector<std::string_view>* arguments = nullptr) = 0; + + virtual void print_error(std::ostream& out) const = 0; + + virtual void print_help(std::ostream& out, uint32_t width = 79) const = 0; + + protected: + Args() = default; + Args(Args const&) = delete; + Args& operator=(Args const&) = delete; +}; + +#endif // ARGS_HH diff --git a/src/cfg.cc b/src/cfg.cc new file mode 100644 index 0000000..b65246f --- /dev/null +++ b/src/cfg.cc @@ -0,0 +1,199 @@ +#include "cfg.hh" + +#include "io.hh" +#include "line.hh" +#include "paths.hh" +#include "str.hh" + +#include <charconv> +#include <cstdint> +#include <format> +#include <functional> +#include <map> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <system_error> +#include <utility> +#include <vector> + +namespace cfg { + +namespace { + +inline char ascii_lowercase(char c) { + if (c >= 'A' && c <= 'Z') { + // NOLINTNEXTLINE(bugprone-narrowing-conversions) + return c | 0x20; + } + return c; +} + +bool ascii_lowercase_eq(std::string_view a, std::string_view b) { + if (a.size() != b.size()) + return false; + auto it_a = a.begin(); + auto it_b = b.begin(); + for (; it_a != a.end(); ++it_a, ++it_b) { + if (ascii_lowercase(*it_a) != *it_b) + return false; + } + return true; +} + +class ConfigSingleImpl : public Config { + public: + ConfigSingleImpl() = default; + + bool load(std::filesystem::path const& path, + std::vector<std::string>& errors) { + auto io_reader = io::open(std::string(path)); + if (!io_reader.has_value()) { + errors.push_back( + std::format("Unable to open {} for reading", path.string())); + return false; + } + bool all_ok = true; + auto line_reader = line::open(std::move(io_reader.value())); + while (true) { + auto line = line_reader->read(); + if (line.has_value()) { + auto trimmed = str::trim(line.value()); + if (trimmed.empty() || trimmed.front() == '#') + continue; + auto eq = trimmed.find('='); + if (eq == std::string_view::npos) { + errors.push_back( + std::format("{}:{}: Invalid line, expected key = value.", + path.string(), line_reader->number())); + all_ok = false; + continue; + } + auto key = str::trim(trimmed.substr(0, eq)); + auto value = str::trim(trimmed.substr(eq + 1)); + auto ret = values_.emplace(key, value); + if (!ret.second) { + errors.push_back(std::format("{}:{}: Duplicate key {} ignored.", + path.string(), line_reader->number(), + key)); + all_ok = false; + continue; + } + } else { + switch (line.error()) { + case io::ReadError::Eof: + break; + default: + errors.push_back(std::format("{}: Read error", path.string())); + all_ok = false; + break; + } + break; + } + } + return all_ok; + } + + [[nodiscard]] + std::optional<std::string_view> get(std::string_view name) const override { + auto it = values_.find(name); + if (it == values_.end()) + return std::nullopt; + return it->second; + } + + private: + std::map<std::string, std::string, std::less<>> values_; +}; + +class ConfigXdgImpl : public Config { + public: + ConfigXdgImpl() = default; + + bool load(std::string_view name, std::vector<std::string>& errors) { + bool all_ok = true; + for (auto const& dir : paths::config_dirs()) { + auto file = dir / name; + if (std::filesystem::exists(file)) { + auto cfg = std::make_unique<ConfigSingleImpl>(); + if (!cfg->load(file, errors)) + all_ok = false; + configs_.push_back(std::move(cfg)); + } + } + return all_ok; + } + + [[nodiscard]] + std::optional<std::string_view> get(std::string_view name) const override { + for (auto const& config : configs_) { + auto ret = config->get(name); + if (ret.has_value()) + return ret; + } + return std::nullopt; + } + + private: + std::vector<std::unique_ptr<ConfigSingleImpl>> configs_; +}; + +} // namespace + +bool Config::has(std::string_view name) const { return get(name).has_value(); } + +std::optional<int64_t> Config::get_int64(std::string_view name) const { + auto str = get(name); + if (str.has_value()) { + auto* const end = str->data() + str->size(); + int64_t ret; + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + auto [ptr, ec] = std::from_chars(str->data(), end, ret); + if (ec == std::errc() && ptr == end) + return ret; + } + return std::nullopt; +} + +std::optional<uint64_t> Config::get_uint64(std::string_view name) const { + auto str = get(name); + if (str.has_value()) { + auto* const end = str->data() + str->size(); + uint64_t ret; + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + auto [ptr, ec] = std::from_chars(str->data(), end, ret); + if (ec == std::errc() && ptr == end) + return ret; + } + return std::nullopt; +} + +std::optional<bool> Config::get_bool(std::string_view name) const { + auto str = get(name); + if (str.has_value()) { + if (ascii_lowercase_eq(str.value(), "true") || + ascii_lowercase_eq(str.value(), "yes")) + return true; + if (ascii_lowercase_eq(str.value(), "false") || + ascii_lowercase_eq(str.value(), "no")) + return false; + } + return std::nullopt; +} + +std::unique_ptr<Config> load_all(std::string_view name, + std::vector<std::string>& errors) { + auto ret = std::make_unique<ConfigXdgImpl>(); + ret->load(name, errors); + return ret; +} + +std::unique_ptr<Config> load_one(std::filesystem::path const& path, + std::vector<std::string>& errors) { + auto ret = std::make_unique<ConfigSingleImpl>(); + ret->load(path, errors); + return ret; +} + +} // namespace cfg diff --git a/src/cfg.hh b/src/cfg.hh new file mode 100644 index 0000000..3be9a00 --- /dev/null +++ b/src/cfg.hh @@ -0,0 +1,45 @@ +#ifndef CFG_HH +#define CFG_HH + +#include <filesystem> +#include <memory> +#include <optional> +#include <string_view> +#include <vector> + +namespace cfg { + +class Config { + public: + virtual ~Config() = default; + + [[nodiscard]] + virtual std::optional<std::string_view> get(std::string_view name) const = 0; + + [[nodiscard]] + bool has(std::string_view name) const; + [[nodiscard]] + std::optional<int64_t> get_int64(std::string_view name) const; + [[nodiscard]] + std::optional<uint64_t> get_uint64(std::string_view name) const; + [[nodiscard]] + std::optional<bool> get_bool(std::string_view name) const; + + protected: + Config() = default; + + Config(Config const&) = delete; + Config& operator=(Config const&) = delete; +}; + +[[nodiscard]] +std::unique_ptr<Config> load_all(std::string_view name, + std::vector<std::string>& errors); + +[[nodiscard]] +std::unique_ptr<Config> load_one(std::filesystem::path const& path, + std::vector<std::string>& errors); + +} // namespace cfg + +#endif // CFG_HH diff --git a/src/check.hh b/src/check.hh new file mode 100644 index 0000000..91c1717 --- /dev/null +++ b/src/check.hh @@ -0,0 +1,39 @@ +#ifndef CHECK_HH +#define CHECK_HH + +#include <cstdlib> +#include <stdckdint.h> +#include <type_traits> + +namespace check { + +template <typename T> + requires std::is_arithmetic_v<T> +T add(T a, T b) { + T ret; + if (ckd_add(&ret, a, b)) + abort(); + return ret; +} + +template <typename T> + requires std::is_arithmetic_v<T> +T sub(T a, T b) { + T ret; + if (ckd_sub(&ret, a, b)) + abort(); + return ret; +} + +template <typename T> + requires std::is_arithmetic_v<T> +T mul(T a, T b) { + T ret; + if (ckd_mul(&ret, a, b)) + abort(); + return ret; +} + +} // namespace check + +#endif // CHECK_HH diff --git a/src/colour.cc b/src/colour.cc new file mode 100644 index 0000000..e9df340 --- /dev/null +++ b/src/colour.cc @@ -0,0 +1,15 @@ +#include "colour.hh" + +#include <cstdint> + +Colour::Colour() : argb(0) {} + +Colour::Colour(uint32_t argb) : argb(argb) {} + +Colour::Colour(uint8_t r, uint8_t g, uint8_t b) + : argb(0xff000000 | (static_cast<uint32_t>(r) << 16) | + (static_cast<uint32_t>(g) << 8) | b) {} + +Colour::Colour(uint8_t a, uint8_t r, uint8_t g, uint8_t b) + : argb((static_cast<uint32_t>(a) << 24) | (static_cast<uint32_t>(r) << 16) | + (static_cast<uint32_t>(g) << 8) | b) {} diff --git a/src/colour.hh b/src/colour.hh new file mode 100644 index 0000000..8362bb3 --- /dev/null +++ b/src/colour.hh @@ -0,0 +1,20 @@ +#ifndef COLOUR_HH +#define COLOUR_HH + +#include <cstdint> + +struct Colour { + Colour(); + explicit Colour(uint32_t argb); + Colour(uint8_t r, uint8_t g, uint8_t b); + Colour(uint8_t a, uint8_t r, uint8_t g, uint8_t b); + + uint8_t alpha() const { return argb >> 24; }; + uint8_t red() const { return (argb >> 16) & 0xff; }; + uint8_t green() const { return (argb >> 8) & 0xff; }; + uint8_t blue() const { return argb & 0xff; }; + + uint32_t argb; +}; + +#endif // COLOUR_HH diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..5280a3d --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,6 @@ +#define VERSION "@version@" + +#define HAVE_JPEG @have_jpeg@ +#define HAVE_PNG @have_png@ +#define HAVE_RSVG @have_rsvg@ +#define HAVE_XPM @have_xpm@ diff --git a/src/image.cc b/src/image.cc new file mode 100644 index 0000000..3e3b485 --- /dev/null +++ b/src/image.cc @@ -0,0 +1,15 @@ +#include "image.hh" + +#include "size.hh" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <utility> + +Image::Image(Format format, Size size, size_t scanline, + std::unique_ptr<uint8_t[]> pixels) + : format_(format), + size_(size), + scanline_(scanline), + pixels_(std::move(pixels)) {} diff --git a/src/image.hh b/src/image.hh new file mode 100644 index 0000000..8191393 --- /dev/null +++ b/src/image.hh @@ -0,0 +1,62 @@ +#ifndef IMAGE_HH +#define IMAGE_HH + +#include "size.hh" + +#include <cstddef> +#include <cstdint> +#include <memory> + +class Image { + public: + enum class Format : uint8_t { + RGBA_8888, + ARGB_8888, + BGRA_8888, + ABGR_8888, + }; + + [[nodiscard]] + Size const& size() const { + return size_; + } + + [[nodiscard]] + uint32_t width() const { + return size_.width; + } + + [[nodiscard]] + uint32_t height() const { + return size_.height; + } + + [[nodiscard]] + Format format() const { + return format_; + } + + [[nodiscard]] + size_t scanline() const { + return scanline_; + } + + [[nodiscard]] + uint8_t const* data() const { + return pixels_.get(); + } + + Image(Format format, Size size, size_t scanline, + std::unique_ptr<uint8_t[]> pixels); + + private: + Image(Image const&) = delete; + Image& operator=(Image const&) = delete; + + Format const format_; + Size const size_; + size_t const scanline_; + std::unique_ptr<uint8_t[]> pixels_; +}; + +#endif // IMAGE_HH diff --git a/src/image_loader.hh b/src/image_loader.hh new file mode 100644 index 0000000..d283624 --- /dev/null +++ b/src/image_loader.hh @@ -0,0 +1,15 @@ +#ifndef IMAGE_LOADER_HH +#define IMAGE_LOADER_HH + +#include "image.hh" // IWYU pragma: export + +#include <cstdint> + +enum class ImageLoadError : uint8_t { + kNoSuchFile, + kUnsupportedFormat, + kProcessError, + kError, +}; + +#endif // IMAGE_LOADER_HH diff --git a/src/image_processor.cc b/src/image_processor.cc new file mode 100644 index 0000000..55e3cac --- /dev/null +++ b/src/image_processor.cc @@ -0,0 +1,947 @@ +#include "image_processor.hh" + +#include "colour.hh" +#include "config.h" +#include "image.hh" +#include "image_loader.hh" +#include "io.hh" +#include "size.hh" +#include "spawner.hh" + +#include <algorithm> +#include <bit> +#include <cassert> +#include <cctype> +#include <charconv> +#include <cstdint> +#include <cstring> +#include <expected> +#include <filesystem> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <system_error> +#include <utility> + +#if HAVE_JPEG || HAVE_PNG +# include <cerrno> +# include <csetjmp> +# include <cstddef> +# include <cstdio> +#endif + +#if HAVE_JPEG +# include <jpeglib.h> +#endif + +#if HAVE_PNG +# include <png.h> +#endif + +#if HAVE_RSVG +# include <cairo.h> +# include <cmath> +# include <gio/gio.h> +# include <glib-object.h> +# include <librsvg/rsvg.h> +#endif + +#if HAVE_XPM +# include <X11/xpm.h> +extern "C" { +# include "xpm/include/dix.h" +} +#endif + +namespace { + +struct Request { + bool head; + Image::Format format; + uint32_t max_width; + uint32_t max_height; + uint32_t background; + size_t path_len; +}; + +struct Response { + uint32_t width; + uint32_t height; + uint8_t error; + size_t scanline; +}; + +struct Result { + Response response; + Image::Format format; + std::unique_ptr<uint8_t[]> pixels; +}; + +std::expected<void, ImageLoadError> write_request( + io::Writer& writer, std::filesystem::path const& path, bool head, + Image::Format format, uint32_t max_width, uint32_t max_height, + Colour background) { + auto path_str = path.native(); + Request request{.head = head, .format = format, .max_width = max_width, + .max_height = max_height, .background = background.argb, + .path_len = path_str.size()}; + auto ret = writer.repeat_write(&request, sizeof(request)); + if (!ret.has_value() || ret.value() != sizeof(request)) { + return std::unexpected(ImageLoadError::kProcessError); + } + ret = writer.repeat_write(path_str.data(), path_str.size()); + if (!ret.has_value() || ret.value() != path_str.size()) { + return std::unexpected(ImageLoadError::kProcessError); + } + return {}; +} + +std::expected<Response, ImageLoadError> read_response(io::Reader& reader) { + Response response; + auto ret = reader.repeat_read(&response, sizeof(response)); + if (!ret.has_value() || ret.value() != sizeof(response)) { + return std::unexpected(ImageLoadError::kProcessError); + } + if (response.width == 0) { + return std::unexpected(static_cast<ImageLoadError>(response.error)); + } + return response; +} + +Size rescale(Size const& size, uint32_t max_width, uint32_t max_height) { + if (max_width > 0 && size.width > max_height) { + if (max_height > 0 && size.height > max_height) { + auto sx = static_cast<float>(size.width) / static_cast<float>(max_width); + auto sy = static_cast<float>(size.height) / static_cast<float>(max_height); + if (sx >= sy) { + return Size{max_width, (size.height * max_width) / size.width}; + } + return Size{(size.width * max_height) / size.height, max_height}; + } + return Size{max_width, (size.height * max_width) / size.width}; + } + if (max_height > 0 && size.height > max_height) { + return Size{(size.width * max_height) / size.height, max_height}; + } + return size; +} + +void swap_four_bytes(std::span<uint8_t> pixels, uint32_t width, uint32_t height, + size_t scanline) { + assert(height * scanline <= pixels.size()); + auto* row = pixels.data(); + while (height--) { + auto* const pixel = reinterpret_cast<uint32_t*>(row); + for (uint32_t x = 0; x < width; ++x) { + pixel[x] = std::byteswap(pixel[x]); + } + row += scanline; + } +} + +void swap_two_bytes(std::span<uint8_t> pixels, uint32_t width, uint32_t height, + size_t scanline, size_t offset) { + assert(offset < 4); + assert(height * scanline <= pixels.size()); + auto* row = pixels.data(); + while (height--) { + auto* pixel = row + offset; + for (uint32_t x = 0; x < width; ++x, pixel += 4) { + std::swap(pixel[0], pixel[2]); + } + row += scanline; + } +} + +void shift_left_bytes(std::span<uint8_t> pixels, uint32_t width, + uint32_t height, size_t scanline) { + assert(height * scanline <= pixels.size()); + auto* row = pixels.data(); + while (height--) { + auto* const pixel = reinterpret_cast<uint32_t*>(row); + for (uint32_t x = 0; x < width; ++x) { + pixel[x] = std::rotl(pixel[x], 8); + } + row += scanline; + } +} + +void shift_right_bytes(std::span<uint8_t> pixels, uint32_t width, + uint32_t height, size_t scanline) { + assert(height * scanline <= pixels.size()); + auto* row = pixels.data(); + while (height--) { + auto* const pixel = reinterpret_cast<uint32_t*>(row); + for (uint32_t x = 0; x < width; ++x) { + pixel[x] = std::rotr(pixel[x], 8); + } + row += scanline; + } +} + +void set_alpha_bytes(std::span<uint8_t> pixels, uint32_t width, uint32_t height, + size_t scanline, size_t offset) { + assert(height * scanline <= pixels.size()); + assert(offset < 4); + auto* row = pixels.data(); + while (height--) { + auto* pixel = row + offset; + for (uint32_t x = 0; x < width; ++x, pixel += 4) { + *pixel = 0xff; + } + row += scanline; + } +} + +void insert_alpha_bytes(std::span<uint8_t> pixels, uint32_t width, + uint32_t height, size_t scanline, size_t offset) { + assert(static_cast<size_t>(width) * 4 <= scanline); + assert(height * scanline <= pixels.size()); + + if (offset == 0) { + auto* row = pixels.data(); + while (height--) { + auto* read_pixel = row + static_cast<size_t>((width - 1) * 3); + auto* write_pixel = row + static_cast<size_t>((width - 1) * 4); + for (uint32_t x = 0; x < width; ++x, read_pixel -= 3, write_pixel -= 4) { + write_pixel[0] = 0xff; + std::copy_n(read_pixel, 3, write_pixel + 1); + } + row += scanline; + } + } else { + assert(offset == 3); + + auto* row = pixels.data(); + while (height--) { + auto* read_pixel = row + static_cast<size_t>((width - 1) * 3); + auto* write_pixel = row + static_cast<size_t>((width - 1) * 4); + for (uint32_t x = 0; x < width; ++x, read_pixel -= 3, write_pixel -= 4) { + std::copy_n(read_pixel, 3, write_pixel); + write_pixel[3] = 0xff; + } + row += scanline; + } + } +} + +void rearrange_bytes(std::span<uint8_t> pixels, uint32_t width, uint32_t height, + size_t scanline, Image::Format source, + Image::Format target) { + switch (source) { + case Image::Format::RGBA_8888: + switch (target) { + case Image::Format::RGBA_8888: + return; + case Image::Format::ARGB_8888: + shift_right_bytes(pixels, width, height, scanline); + return; + case Image::Format::BGRA_8888: + swap_two_bytes(pixels, width, height, scanline, 0); + return; + case Image::Format::ABGR_8888: + swap_four_bytes(pixels, width, height, scanline); + return; + } + break; + case Image::Format::ARGB_8888: + switch (target) { + case Image::Format::RGBA_8888: + shift_left_bytes(pixels, width, height, scanline); + return; + case Image::Format::ARGB_8888: + return; + case Image::Format::BGRA_8888: + swap_four_bytes(pixels, width, height, scanline); + return; + case Image::Format::ABGR_8888: + swap_two_bytes(pixels, width, height, scanline, 1); + return; + } + break; + case Image::Format::BGRA_8888: + switch (target) { + case Image::Format::RGBA_8888: + swap_two_bytes(pixels, width, height, scanline, 0); + return; + case Image::Format::ARGB_8888: + swap_four_bytes(pixels, width, height, scanline); + return; + case Image::Format::BGRA_8888: + return; + case Image::Format::ABGR_8888: + shift_left_bytes(pixels, width, height, scanline); + return; + } + break; + case Image::Format::ABGR_8888: + switch (target) { + case Image::Format::RGBA_8888: + swap_four_bytes(pixels, width, height, scanline); + return; + case Image::Format::ARGB_8888: + swap_two_bytes(pixels, width, height, scanline, 1); + return; + case Image::Format::BGRA_8888: + shift_right_bytes(pixels, width, height, scanline); + return; + case Image::Format::ABGR_8888: + return; + } + break; + } +} + +#if HAVE_JPEG +struct jpeg_extended_error_mgr { + struct jpeg_error_mgr err; + jmp_buf jmpbuf; +}; + +void jpeg_error_exit(j_common_ptr info) { + auto* err = reinterpret_cast<jpeg_extended_error_mgr*>(info->err); + jpeg_destroy(info); + longjmp(err->jmpbuf, 1); +} + +void jpeg_error_output_nothing(j_common_ptr /* info */) { + // be silent, do nothing +} + +# if BITS_IN_JSAMPLE != 8 +# error Unsupported libjpeg setup +# endif + +std::expected<Result, ImageLoadError> load_jpeg( + std::filesystem::path const& path, Request const& request) { + jpeg_extended_error_mgr err; + FILE* fh = nullptr; + if (setjmp(err.jmpbuf)) { + // This is better than exit() which is the default behavior, + // but almost guaranteed to leak some memory here. + if (fh) + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + fh = fopen(path.c_str(), "rb"); + if (fh == nullptr) { + if (errno == ENOENT) { + return std::unexpected(ImageLoadError::kNoSuchFile); + } + return std::unexpected(ImageLoadError::kError); + } + + jpeg_decompress_struct info; + info.err = jpeg_std_error(&err.err); + info.err->error_exit = jpeg_error_exit; + info.err->output_message = jpeg_error_output_nothing; + + jpeg_create_decompress(&info); + + jpeg_stdio_src(&info, fh); + if (jpeg_read_header(&info, TRUE) != JPEG_HEADER_OK) { + jpeg_destroy_decompress(&info); + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + if (request.head) { + Response resp{.width = info.image_width, .height = info.image_height, .error = 0, .scanline = 0}; + jpeg_destroy_decompress(&info); + fclose(fh); + return Result{.response=resp, .format=Image::Format::RGBA_8888, .pixels=nullptr}; + } + + enum class Conversion : uint8_t { + kNone, + kSetAlphaFirst, + kSetAlphaLast, + kAddAlphaFirst, + kAddAlphaLast, + } conversion = Conversion::kNone; + Image::Format output_format = request.format; + +# if JCS_EXTENSIONS +# if JCS_ALPHA_EXTENSIONS + switch (request.format) { + case Image::Format::RGBA_8888: + info.out_color_space = JCS_EXT_RGBA; + break; + case Image::Format::BGRA_8888: + info.out_color_space = JCS_EXT_BGRA; + break; + case Image::Format::ABGR_8888: + info.out_color_space = JCS_EXT_ABGR; + break; + case Image::Format::ARGB_8888: + info.out_color_space = JCS_EXT_ARGB; + break; + } +# else + switch (request.format) { + case Image::Format::RGBA_8888: + info.out_color_space = JCS_EXT_RGBX; + conversion = Conversion::kSetAlphaLast; + break; + case Image::Format::BGRA_8888: + info.out_color_space = JCS_EXT_BGRX; + conversion = Conversion::kSetAlphaLast; + break; + case Image::Format::ABGR_8888: + info.out_color_space = JCS_EXT_XBGR; + conversion = Conversion::kSetAlphaFirst; + break; + case Image::Format::ARGB_8888: + info.out_color_space = JCS_EXT_XRGB; + conversion = Conversion::kSetAlphaFirst; + break; + } +# endif +# else +# if RGB_RED != 0 || RGB_GREEN != 1 || RGB_BLUE != 2 || RGB_PIXELSIZE != 3 +# error Unsupported libjpeg setup +# endif + info.out_color_space = JCS_RGB; + switch (request.format) { + case Image::Format::RGBA_8888: + case Image::Format::BGRA_8888: + format = Image::Format::RGBA_8888; + conversion = Conversion::kAddAlphaLast; + break; + case Image::Format::ABGR_8888: + case Image::Format::ARGB_8888: + format = Image::Format::ARGB_8888; + conversion = Conversion::kAddAlphaFirst; + break; + } +# endif + + if (request.max_width || request.max_height) { + auto new_size = rescale(Size{info.image_width, info.image_height}, + request.max_width, request.max_height); + unsigned int denom = 2; + while (info.image_width / denom >= new_size.width && + info.image_height / denom >= new_size.height) { + info.scale_denom = denom; + denom *= 2; + } + } + + jpeg_start_decompress(&info); + + size_t scanline = static_cast<size_t>(info.output_width) * 4; + auto pixels = std::make_unique<uint8_t[]>(scanline * info.output_height); + + auto buffer = std::make_unique<JSAMPROW[]>(info.rec_outbuf_height); + + while (info.output_scanline < info.output_height) { + int buf_height = 1; + buffer[0] = reinterpret_cast<JSAMPROW>(pixels.get() + + (info.output_scanline * scanline)); + while (buf_height < info.rec_outbuf_height && + info.output_scanline + buf_height < info.output_height) { + buffer[buf_height] = reinterpret_cast<JSAMPROW>( + pixels.get() + ((info.output_scanline + buf_height) * scanline)); + ++buf_height; + } + jpeg_read_scanlines(&info, buffer.get(), buf_height); + } + + switch (conversion) { + case Conversion::kNone: + break; + case Conversion::kAddAlphaFirst: + insert_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, + info.output_width, info.output_height, scanline, 0); + break; + case Conversion::kAddAlphaLast: + insert_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, + info.output_width, info.output_height, scanline, 3); + break; + case Conversion::kSetAlphaFirst: + set_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, + info.output_width, info.output_height, scanline, 0); + break; + case Conversion::kSetAlphaLast: + set_alpha_bytes(std::span{pixels.get(), scanline * info.output_height}, + info.output_width, info.output_height, scanline, 3); + break; + } + + Response resp{.width = info.output_width, .height = info.output_height, .error = 0, .scanline = scanline}; + jpeg_destroy_decompress(&info); + fclose(fh); + return Result{.response = resp, .format = output_format, .pixels = std::move(pixels)}; +} +#endif + +#if HAVE_PNG +// NOLINTBEGIN(misc-include-cleaner) +void png_error(png_structp png_ptr, png_const_charp /* message */) { + // Don't write anything + longjmp(png_jmpbuf(png_ptr), 1); +} + +void png_warning(png_structp /* png_ptr */, png_const_charp /* message */) { + // Do nothing +} + +std::expected<Result, ImageLoadError> load_png( + std::filesystem::path const& path, Request const& request) { + FILE* fh = fopen(path.c_str(), "rb"); + if (fh == nullptr) { + if (errno == ENOENT) { + return std::unexpected(ImageLoadError::kNoSuchFile); + } + return std::unexpected(ImageLoadError::kError); + } + + uint8_t header[8]; + if (fread(header, 1, sizeof(header), fh) != sizeof(header)) { + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + if (png_sig_cmp(header, 0, sizeof(header))) { + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, + png_error, png_warning); + + if (!png_ptr) { + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fh); + return std::unexpected(ImageLoadError::kError); + } + + png_init_io(png_ptr, fh); + png_set_sig_bytes(png_ptr, sizeof(header)); + + png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + + png_read_info(png_ptr, info_ptr); + + png_uint_32 width; + png_uint_32 height; + int bit_depth; + int color_type; + + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + nullptr, nullptr, nullptr); + + if (request.head) { + Response resp{.width =width, .height = height, .error = 0, .scanline = 0}; + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fh); + return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; + } + + if (request.background != 0) { + png_color_16 background_color; + Colour colour{request.background}; + background_color.red = colour.red() << 8 | colour.red(); + background_color.green = colour.green() << 8 | colour.green(); + background_color.blue = colour.blue() << 8 | colour.blue(); + png_set_background(png_ptr, &background_color, PNG_BACKGROUND_GAMMA_SCREEN, + 0, 1); + } else { + png_color_16p background_color; + if (png_get_bKGD(png_ptr, info_ptr, &background_color)) + png_set_background(png_ptr, background_color, PNG_BACKGROUND_GAMMA_FILE, + 1, 1); + } + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + if (bit_depth == 16) + png_set_scale_16(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + switch (request.format) { + case Image::Format::RGBA_8888: + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + break; + case Image::Format::ARGB_8888: + png_set_swap_alpha(png_ptr); + png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); + break; + case Image::Format::BGRA_8888: + png_set_bgr(png_ptr); + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + break; + case Image::Format::ABGR_8888: + png_set_bgr(png_ptr); + png_set_swap_alpha(png_ptr); + png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE); + break; + } + + png_read_update_info(png_ptr, info_ptr); + + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + size_t scanline = png_get_rowbytes(png_ptr, info_ptr); + + /* Guard against integer overflow */ + if (height > PNG_SIZE_MAX / scanline) + png_error(png_ptr, "image_data buffer would be too large"); + + auto pixels = std::make_unique<uint8_t[]>(scanline * height); + auto rows = std::make_unique<png_bytep[]>(height); + + for (png_uint_32 y = 0; y < height; ++y) + rows[y] = pixels.get() + (y * scanline); + + png_read_image(png_ptr, rows.get()); + + png_read_end(png_ptr, nullptr); + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + fclose(fh); + + Response resp{.width = width, .height = height, .error = 0, .scanline = scanline}; + return Result{.response = resp, .format = request.format, .pixels = std::move(pixels)}; +} +// NOLINTEND(misc-include-cleaner) +#endif + +#if HAVE_RSVG +std::expected<Result, ImageLoadError> load_svg( + std::filesystem::path const& path, Request const& request) { + GFile* file = g_file_new_for_path(path.c_str()); + if (!file) { + return std::unexpected(ImageLoadError::kNoSuchFile); + } + RsvgHandle* handle = + rsvg_handle_new_from_gfile_sync(file, RSVG_HANDLE_FLAGS_NONE, nullptr, nullptr); + + if (!handle) { + g_object_unref(file); + return std::unexpected(ImageLoadError::kError); + } + g_object_unref(file); + + // TODO: Get this as part of the request? + rsvg_handle_set_dpi(handle, 96.0); + + Size size; + { + gdouble width_double; // NOLINT(misc-include-cleaner) + gdouble height_double; // NOLINT(misc-include-cleaner) + if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &width_double, + &height_double)) { + g_object_unref(handle); + return std::unexpected(ImageLoadError::kError); + } + size.width = static_cast<uint32_t>(ceil(width_double)); + size.height = static_cast<uint32_t>(ceil(height_double)); + } + + if (request.head) { + Response resp{.width = size.width, .height = size.height, .error = 0, .scanline = 0}; + g_object_unref(handle); + return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; + } + + if (request.max_width || request.max_height) { + size = rescale(size, request.max_width, request.max_height); + } + + size_t scanline = static_cast<size_t>(size.width) * 4; + auto pixels = std::make_unique<uint8_t[]>(scanline * size.height); + + cairo_surface_t* surface = cairo_image_surface_create_for_data( + reinterpret_cast<unsigned char*>(pixels.get()), CAIRO_FORMAT_ARGB32, + static_cast<int>(size.width), static_cast<int>(size.height), + static_cast<int>(scanline)); + + if (!surface) { + g_object_unref(handle); + return std::unexpected(ImageLoadError::kError); + } + + cairo_t* cr = cairo_create(surface); + + if (request.background) { + cairo_rectangle(cr, 0, 0, size.width, size.height); + Colour colour{request.background}; + cairo_set_source_rgba(cr, colour.red() / 255.0, colour.green() / 255.0, + colour.blue() / 255.0, colour.alpha() / 255.0); + cairo_fill(cr); + } + + RsvgRectangle viewport = { + .x = 0.0, + .y = 0.0, + .width = static_cast<double>(size.width), + .height = static_cast<double>(size.height), + }; + + if (!rsvg_handle_render_document(handle, cr, &viewport, nullptr)) { + cairo_destroy(cr); + cairo_surface_destroy(surface); + g_object_unref(handle); + return std::unexpected(ImageLoadError::kError); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); + g_object_unref(handle); + + // Cairo uses premultiplied pixels, we don't + { + uint8_t* row = pixels.get(); + for (uint32_t y = 0; y < size.height; ++y) { + uint8_t* pixel = row; + for (uint32_t x = 0; x < size.width; ++x, pixel += 4) { + if constexpr (std::endian::native == std::endian::big) { + if (pixel[0] != 0xff && pixel[0] > 0) { + pixel[1] = (static_cast<uint16_t>(pixel[1]) * 255) / pixel[0]; + pixel[2] = (static_cast<uint16_t>(pixel[2]) * 255) / pixel[0]; + pixel[3] = (static_cast<uint16_t>(pixel[3]) * 255) / pixel[0]; + } + } else { + if (pixel[3] != 0xff && pixel[3] > 0) { + pixel[0] = (static_cast<uint16_t>(pixel[0]) * 255) / pixel[3]; + pixel[1] = (static_cast<uint16_t>(pixel[1]) * 255) / pixel[3]; + pixel[2] = (static_cast<uint16_t>(pixel[2]) * 255) / pixel[3]; + } + } + } + row += scanline; + } + } + + return Result{ + .response = Response{.width = size.width, .height = size.height, .error = 0, .scanline = scanline}, + .format = std::endian::native == std::endian::big ? Image::Format::ARGB_8888 + : Image::Format::BGRA_8888, + .pixels = std::move(pixels) + }; +} +#endif + +#if HAVE_XPM + +std::optional<uint32_t> xpm_parse_color(XpmColor const& color) { + if (color.c_color) { + auto const len = strlen(color.c_color); + if (color.c_color[0] == '#' && len == 7) { + uint32_t rgb; + if (std::from_chars(color.c_color + 1, color.c_color + 7, rgb, 16).ec == std::errc()) { + return 0xff000000 | rgb; + } + } + if (len == 4 && (strcmp(color.c_color, "None") == 0 || + strcmp(color.c_color, "none") == 0)) + return 0; + unsigned short red; + unsigned short green; + unsigned short blue; + if (dixLookupBuiltinColor(0, color.c_color, len, + &red, &green, &blue)) { + return 0xff000000 | (static_cast<uint32_t>(red & 0xff) << 16) | + (green & 0xff00) | (blue & 0xff); + } + } + if (color.m_color) { + if (strcmp(color.m_color, "white") == 0) { + return 0xffffffff; + } + if (strcmp(color.m_color, "black") == 0) { + return 0xff000000; + } + } + return std::nullopt; +} + +std::expected<Result, ImageLoadError> load_xpm( + std::filesystem::path const& path, Request const& request) { + XpmImage image; + XpmInfo info; + auto ret = XpmReadFileToXpmImage(path.c_str(), &image, &info); + if (ret != XpmSuccess) { + if (ret == XpmOpenFailed) + return std::unexpected(ImageLoadError::kNoSuchFile); + return std::unexpected(ImageLoadError::kError); + } + + if (request.head) { + Response resp{.width = image.width, .height = image.height, .error = 0, .scanline = 0}; + XpmFreeXpmImage(&image); + XpmFreeXpmInfo(&info); + return Result{.response = resp, .format = Image::Format::RGBA_8888, .pixels = nullptr}; + } + + size_t scanline = static_cast<size_t>(image.width) * 4; + auto pixels = std::make_unique<uint8_t[]>(scanline * image.height); + + auto colors = std::make_unique<uint32_t[]>(image.ncolors); + for (unsigned int i = 0; i < image.ncolors; ++i) { + auto ret = xpm_parse_color(image.colorTable[i]); + if (ret.has_value()) { + colors[i] = ret.value(); + } else { + return std::unexpected(ImageLoadError::kUnsupportedFormat); + } + } + + auto* out_row = pixels.get(); + auto* in = image.data; + for (unsigned int y = 0; y < image.height; ++y) { + auto* out_pixel = reinterpret_cast<uint32_t*>(out_row); + for (unsigned int x = 0; x < image.width; ++x) { + out_pixel[x] = colors[*in++]; + } + out_row += scanline; + } + + XpmFreeXpmImage(&image); + XpmFreeXpmInfo(&info); + + return Result{ + .response = Response{.width = image.width, .height = image.height, .error = 0, .scanline = scanline}, + .format = std::endian::native == std::endian::big ? Image::Format::ARGB_8888 : Image::Format::BGRA_8888, + .pixels = std::move(pixels), + }; +} +#endif + +} // namespace + +namespace image_processor { + +std::expected<Size, ImageLoadError> peek(Process& process, + std::filesystem::path const& path) { + auto ret = write_request(process.writer(), path, true, + Image::Format::ARGB_8888, 0, 0, {}); + if (!ret) { + return std::unexpected(ret.error()); + } + auto ret2 = read_response(process.reader()); + if (!ret2) + return std::unexpected(ret2.error()); + return Size{ret2->width, ret2->height}; +} + +std::expected<std::unique_ptr<Image>, ImageLoadError> load( + Process& process, std::filesystem::path const& path, Image::Format format, + uint32_t max_width, uint32_t max_height, std::optional<Colour> background) { + auto ret = write_request(process.writer(), path, false, format, max_width, + max_height, background.value_or({})); + if (!ret) { + return std::unexpected(ret.error()); + } + auto ret2 = read_response(process.reader()); + if (!ret2) + return std::unexpected(ret2.error()); + size_t size = static_cast<size_t>(ret2->height) * ret2->scanline; + auto pixels = std::make_unique<uint8_t[]>(size); + auto ret3 = process.reader().repeat_read(pixels.get(), size); + if (!ret3.has_value() || ret3.value() != size) { + return std::unexpected(ret.error()); + } + return std::make_unique<Image>(format, Size{ret2->width, ret2->height}, + ret2->scanline, std::move(pixels)); +} + +int run(std::unique_ptr<io::Reader> reader, + std::unique_ptr<io::Writer> writer) { + while (true) { + Request request; + auto ret = reader->repeat_read(&request, sizeof(request)); + if (!ret.has_value() || ret.value() != sizeof(request)) + break; + std::string path_str(request.path_len, ' '); + ret = reader->repeat_read(path_str.data(), path_str.size()); + if (!ret.has_value() || ret.value() != path_str.size()) + break; + std::filesystem::path path(std::move(path_str)); + + auto extension = path.extension().native(); + std::ranges::transform(extension, extension.begin(), + [](unsigned char c){ return std::tolower(c); }); + std::expected<Result, ImageLoadError> result{ + std::unexpected(ImageLoadError::kUnsupportedFormat)}; +#if HAVE_JPEG + if (extension == ".jpeg" || extension == ".jpg" || extension == ".jpe" || + extension == ".jfif" || extension == ".jfi" || extension == ".jif") { + result = load_jpeg(path, request); + } +#endif +#if HAVE_PNG + if (path.extension() == ".png") { + result = load_png(path, request); + } +#endif +#if HAVE_RSVG + if (path.extension() == ".svg" || path.extension() == ".svgz") { + result = load_svg(path, request); + } +#endif +#if HAVE_XPM + if (path.extension() == ".xpm") { + result = load_xpm(path, request); + } +#endif + if (!request.head && result.has_value() && + result->format != request.format) { + auto size = static_cast<size_t>(result->response.height) * + result->response.scanline; + rearrange_bytes(std::span(result->pixels.get(), size), + result->response.width, result->response.height, + result->response.scanline, result->format, + request.format); + } + if (result.has_value()) { + auto ret2 = + writer->repeat_write(&result->response, sizeof(result->response)); + if (!ret2.has_value() || ret2.value() != sizeof(result->response)) + break; + if (!request.head) { + auto size = static_cast<size_t>(result->response.height) * + result->response.scanline; + ret2 = writer->repeat_write(result->pixels.get(), size); + if (!ret2.has_value() || ret2.value() != size) + break; + } + } else { + Response response{.width = 0, .height = 0, .error = static_cast<uint8_t>(result.error()), .scanline = 0}; + auto ret2 = writer->repeat_write(&response, sizeof(response)); + if (!ret2.has_value() || ret2.value() != sizeof(response)) + break; + } + } + return -1; +} + +} // namespace image_processor diff --git a/src/image_processor.hh b/src/image_processor.hh new file mode 100644 index 0000000..551ac4f --- /dev/null +++ b/src/image_processor.hh @@ -0,0 +1,29 @@ +#ifndef IMAGE_PROCESSOR_HH +#define IMAGE_PROCESSOR_HH + +#include "colour.hh" +#include "image_loader.hh" +#include "io.hh" +#include "spawner.hh" + +#include <filesystem> +#include <memory> +#include <optional> + +namespace image_processor { + +[[nodiscard]] +std::expected<Size, ImageLoadError> peek(Process& process, + std::filesystem::path const& path); + +[[nodiscard]] +std::expected<std::unique_ptr<Image>, ImageLoadError> load( + Process& process, std::filesystem::path const& path, Image::Format format, + uint32_t max_width = 0, uint32_t max_height = 0, + std::optional<Colour> background = std::nullopt); + +int run(std::unique_ptr<io::Reader> reader, std::unique_ptr<io::Writer> writer); + +} // namespace image_processor + +#endif // IMAGE_PROCESSOR_HH diff --git a/src/io.cc b/src/io.cc new file mode 100644 index 0000000..660c5f7 --- /dev/null +++ b/src/io.cc @@ -0,0 +1,358 @@ +#include "io.hh" + +#include "unique_fd.hh" + +#include <algorithm> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <expected> +#include <fcntl.h> +#include <limits> +#include <memory> +#include <optional> +#include <string> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <utility> + +namespace io { + +namespace { + +class BasicReader : public Reader { + public: + explicit BasicReader(unique_fd fd) : fd_(std::move(fd)) {} + + [[nodiscard]] + std::expected<size_t, ReadError> read(void* dst, size_t max) override { + ssize_t ret = ::read( + fd_.get(), dst, + std::min(static_cast<size_t>(std::numeric_limits<ssize_t>::max()), + max)); + if (ret < 0) { + switch (errno) { + case EINTR: + return read(dst, max); + default: + return std::unexpected(ReadError::Error); + } + } else if (ret == 0 && max > 0) { + return std::unexpected(ReadError::Eof); + } + offset_ += ret; + return ret; + } + + [[nodiscard]] + std::expected<size_t, ReadError> skip(size_t max) override { + off_t ret; + if (sizeof(size_t) > sizeof(off_t)) { + ret = lseek( + fd_.get(), + // NOLINTNEXTLINE(bugprone-narrowing-conversions) + std::min(static_cast<size_t>(std::numeric_limits<off_t>::max()), max), + SEEK_CUR); + } else { + ret = lseek(fd_.get(), static_cast<off_t>(max), SEEK_CUR); + } + if (ret < 0) { + return std::unexpected(ReadError::Error); + } + // Don't want skip to go past (cached) file end. + if (!size_.has_value() || ret >= size_.value()) { + // When going past end, double check that it still is the end. + off_t ret2 = lseek(fd_.get(), 0, SEEK_END); + if (ret2 < 0) { + // We're screwed, but try to go back to original position and then + // return error. + size_.reset(); + lseek(fd_.get(), offset_, SEEK_SET); + return std::unexpected(ReadError::Error); + } + size_ = ret2; + if (ret >= ret2) { + auto distance = ret2 - offset_; + offset_ = ret2; + if (distance == 0 && max > 0) + return std::unexpected(ReadError::Eof); + return distance; + } + // Seek back to where we should be + if (lseek(fd_.get(), ret, SEEK_SET) < 0) { + return std::unexpected(ReadError::Error); + } + } + auto distance = ret - offset_; + offset_ = ret; + return distance; + } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; + off_t offset_{0}; + std::optional<off_t> size_; +}; + +class MemoryReader : public Reader { + public: + MemoryReader(void* ptr, size_t size) : ptr_(ptr), size_(size) {} + + [[nodiscard]] + std::expected<size_t, ReadError> read(void* dst, size_t max) override { + size_t avail = size_ - offset_; + if (avail == 0 && max > 0) + return std::unexpected(io::ReadError::Eof); + size_t ret = std::min(max, avail); + memcpy(dst, reinterpret_cast<char*>(ptr_) + offset_, ret); + offset_ += ret; + return ret; + } + + [[nodiscard]] + std::expected<size_t, ReadError> skip(size_t max) override { + size_t avail = size_ - offset_; + size_t ret = std::min(max, avail); + offset_ += ret; + return ret; + } + + [[nodiscard]] + int raw_fd() const override { + return -1; + } + + protected: + void* ptr_; + size_t const size_; + + private: + size_t offset_{0}; +}; + +class MmapReader : public MemoryReader { + public: + MmapReader(unique_fd fd, void* ptr, size_t size) + : MemoryReader(ptr, size), fd_(std::move(fd)) {} + + ~MmapReader() override { munmap(ptr_, size_); } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; +}; + +class StringReader : public MemoryReader { + public: + explicit StringReader(std::string data) + : MemoryReader(nullptr, data.size()), data_(std::move(data)) { + ptr_ = data_.data(); + } + + private: + std::string data_; +}; + +class BasicWriter : public Writer { + public: + explicit BasicWriter(unique_fd fd) : fd_(std::move(fd)) {} + + [[nodiscard]] + std::expected<size_t, WriteError> write(void const* dst, + size_t size) override { + ssize_t ret = ::write( + fd_.get(), dst, + std::min(static_cast<size_t>(std::numeric_limits<ssize_t>::max()), + size)); + if (ret < 0) { + switch (errno) { + case EINTR: + return write(dst, size); + default: + return std::unexpected(WriteError::Error); + } + } else if (ret == 0 && size > 0) { + return std::unexpected(WriteError::Error); + } + return ret; + } + + [[nodiscard]] + std::expected<void, WriteError> close() override { + if (::close(fd_.release()) == 0) + return {}; + return std::unexpected(WriteError::Error); + } + + [[nodiscard]] + int raw_fd() const override { + return fd_.get(); + } + + private: + unique_fd fd_; +}; + +} // namespace + +std::expected<size_t, ReadError> Reader::repeat_read(void* dst, size_t max) { + auto ret = read(dst, max); + if (!ret.has_value() || ret.value() == max) + return ret; + + char* d = reinterpret_cast<char*>(dst); + size_t offset = ret.value(); + while (true) { + ret = read(d + offset, max - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == max) + break; + } + return offset; +} + +std::expected<size_t, ReadError> Reader::repeat_skip(size_t max) { + auto ret = skip(max); + if (!ret.has_value() || ret.value() == max) + return ret; + + size_t offset = ret.value(); + while (true) { + ret = skip(max - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == max) + break; + } + return offset; +} + +std::expected<size_t, WriteError> Writer::repeat_write(void const* dst, + size_t size) { + auto ret = write(dst, size); + if (!ret.has_value() || ret.value() == size) + return ret; + + char const* d = reinterpret_cast<char const*>(dst); + size_t offset = ret.value(); + while (true) { + ret = write(d + offset, size - offset); + if (!ret.has_value()) + break; + offset += ret.value(); + if (offset == size) + break; + } + return offset; +} + +std::expected<std::unique_ptr<Reader>, OpenError> open( + const std::string& file_path) { + return openat(AT_FDCWD, file_path); +} + +std::expected<std::unique_ptr<Reader>, OpenError> openat( + int dirfd, const std::string& file_path) { + unique_fd fd(::openat(dirfd, file_path.c_str(), O_RDONLY)); + if (fd) { + struct stat buf; + if (fstat(fd.get(), &buf) == 0) { + if (std::cmp_less_equal(buf.st_size, + std::numeric_limits<size_t>::max())) { + auto size = static_cast<size_t>(buf.st_size); + void* ptr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd.get(), 0); + if (ptr != MAP_FAILED) { + return std::make_unique<MmapReader>(std::move(fd), ptr, size); + } + } + } + return std::make_unique<BasicReader>(std::move(fd)); + } + OpenError err; + switch (errno) { + case EINTR: + return openat(dirfd, file_path); + case EACCES: + err = OpenError::NoAccess; + break; + case ENOENT: + err = OpenError::NoSuchFile; + break; + default: + err = OpenError::Error; + break; + } + return std::unexpected(err); +} + +std::unique_ptr<Reader> memory(std::string data) { + return std::make_unique<StringReader>(std::move(data)); +} + +std::expected<std::unique_ptr<Writer>, CreateError> create( + const std::string& file_path, bool replace_existing) { + return createat(AT_FDCWD, file_path, replace_existing); +} + +std::expected<std::unique_ptr<Writer>, CreateError> createat( + int dirfd, const std::string& file_path, bool replace_existing) { + int flags = O_WRONLY | O_CREAT; + if (replace_existing) { + flags |= O_TRUNC; + } else { + flags |= O_EXCL; + } + unique_fd fd(::openat(dirfd, file_path.c_str(), flags, 0666)); + if (fd) { + return std::make_unique<BasicWriter>(std::move(fd)); + } + CreateError err; + switch (errno) { + case EINTR: + return createat(dirfd, file_path, replace_existing); + case EACCES: + err = CreateError::NoAccess; + break; + case EEXIST: + err = CreateError::Exists; + break; + default: + err = CreateError::Error; + break; + } + return std::unexpected(err); +} + +std::expected<std::pair<std::unique_ptr<Reader>, std::unique_ptr<Writer>>, + PipeError> +pipe() { + int fds[2]; + if (::pipe(fds) == 0) { + return std::make_pair(reader_from_raw(fds[0]), writer_from_raw(fds[1])); + } + return std::unexpected(PipeError::Error); +} + +std::unique_ptr<Reader> reader_from_raw(int fd) { + return std::make_unique<BasicReader>(unique_fd{fd}); +} + +std::unique_ptr<Writer> writer_from_raw(int fd) { + return std::make_unique<BasicWriter>(unique_fd{fd}); +} + +} // namespace io diff --git a/src/io.hh b/src/io.hh new file mode 100644 index 0000000..90ad52e --- /dev/null +++ b/src/io.hh @@ -0,0 +1,109 @@ +#ifndef IO_HH +#define IO_HH + +#include <cstddef> +#include <expected> +#include <memory> +#include <string> +#include <utility> + +namespace io { + +enum class ReadError : uint8_t { + Error, + Eof, + InvalidData, // invalid data read (not used by raw file) + MaxTooSmall, // max argument needs to be bigger (not used by raw file) +}; + +enum class WriteError : uint8_t { + Error, +}; + +enum class OpenError : uint8_t { + NoSuchFile, + NoAccess, + Error, +}; + +enum class CreateError : uint8_t { + Exists, + NoAccess, + Error, +}; + +enum class PipeError : uint8_t { + Error, +}; + +class Reader { + public: + virtual ~Reader() = default; + + [[nodiscard]] virtual std::expected<size_t, ReadError> read(void* dst, + size_t max) = 0; + [[nodiscard]] virtual std::expected<size_t, ReadError> skip(size_t max) = 0; + + [[nodiscard]] std::expected<size_t, ReadError> repeat_read(void* dst, + size_t max); + [[nodiscard]] std::expected<size_t, ReadError> repeat_skip(size_t max); + + [[nodiscard]] + virtual int raw_fd() const = 0; + + protected: + Reader() = default; + + Reader(Reader const&) = delete; + Reader& operator=(Reader const&) = delete; +}; + +class Writer { + public: + virtual ~Writer() = default; + + [[nodiscard]] + virtual std::expected<size_t, WriteError> write(void const* dst, + size_t size) = 0; + + // Use this instead of the destructor if you care if the call returned an + // error or not. Regardless of returned value, after this method is called + // write will always fail. + [[nodiscard]] + virtual std::expected<void, WriteError> close() = 0; + + [[nodiscard]] + virtual int raw_fd() const = 0; + + [[nodiscard]] std::expected<size_t, WriteError> repeat_write(void const* dst, + size_t size); + + protected: + Writer() = default; + + Writer(Writer const&) = delete; + Writer& operator=(Writer const&) = delete; +}; + +[[nodiscard]] std::expected<std::unique_ptr<Reader>, OpenError> open( + const std::string& file_path); +[[nodiscard]] std::expected<std::unique_ptr<Reader>, OpenError> openat( + int dirfd, const std::string& file_path); +[[nodiscard]] std::unique_ptr<Reader> memory(std::string data); + +[[nodiscard]] std::expected<std::unique_ptr<Writer>, CreateError> create( + const std::string& file_path, bool replace_existing); +[[nodiscard]] std::expected<std::unique_ptr<Writer>, CreateError> createat( + int dirfd, const std::string& file_path, bool replace_existing); + +[[nodiscard]] std::expected< + std::pair<std::unique_ptr<Reader>, std::unique_ptr<Writer>>, PipeError> +pipe(); + +// Takes ownership of the fd. +[[nodiscard]] std::unique_ptr<Reader> reader_from_raw(int fd); +[[nodiscard]] std::unique_ptr<Writer> writer_from_raw(int fd); + +} // namespace io + +#endif // IO_HH diff --git a/src/line.cc b/src/line.cc new file mode 100644 index 0000000..23370fc --- /dev/null +++ b/src/line.cc @@ -0,0 +1,127 @@ +#include "line.hh" + +#include "check.hh" + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> +#include <expected> +#include <memory> +#include <string_view> +#include <utility> + +namespace line { + +namespace { + +const char kLineTerminators[] = "\r\n"; + +class ReaderImpl : public Reader { + public: + ReaderImpl(std::unique_ptr<io::Reader> reader, size_t max_len) + : reader_(std::move(reader)), + max_len_(max_len), + buffer_(std::make_unique_for_overwrite<char[]>( + check::add(max_len, static_cast<size_t>(2)))), + rptr_(buffer_.get()), + wptr_(buffer_.get()), + search_(rptr_), + end_(buffer_.get() + check::add(max_len, static_cast<size_t>(2))) {} + + [[nodiscard]] std::expected<std::string_view, io::ReadError> read() override { + while (true) { + search_ = std::find_first_of(search_, wptr_, kLineTerminators, + kLineTerminators + 2); + if (search_ < wptr_) { + if (std::cmp_greater(search_ - rptr_, max_len_)) { + return line(max_len_, 0); + } + + size_t tlen; + if (*search_ == '\n') { + tlen = 1; + } else { + if (search_ + 1 == wptr_) { + make_space_if_needed(); + auto got = fill(); + if (!got.has_value()) { + if (got.error() == io::ReadError::Eof) { + return line(search_ - rptr_, 1); + } + return std::unexpected(got.error()); + } + } + if (search_[1] == '\n') { + tlen = 2; + } else { + tlen = 1; + } + } + return line(search_ - rptr_, tlen); + } + if (std::cmp_greater_equal(wptr_ - rptr_, max_len_)) { + return line(max_len_, 0); + } + + make_space_if_needed(); + auto got = fill(); + if (!got.has_value()) { + if (got.error() == io::ReadError::Eof && rptr_ != wptr_) { + return line(wptr_ - rptr_, 0); + } + return std::unexpected(got.error()); + } + } + } + + [[nodiscard]] uint64_t number() const override { return number_; } + + private: + std::string_view line(size_t len, size_t terminator_len) { + assert(len <= max_len_); + auto ret = std::string_view(rptr_, len); + rptr_ += len + terminator_len; + search_ = rptr_; + ++number_; + return ret; + } + + void make_space_if_needed() { + size_t free = rptr_ - buffer_.get(); + if (free == 0) + return; + size_t avail = end_ - wptr_; + if (avail > 1024) + return; + memmove(buffer_.get(), rptr_, wptr_ - rptr_); + search_ -= free; + wptr_ -= free; + rptr_ = buffer_.get(); + } + + std::expected<size_t, io::ReadError> fill() { + auto ret = reader_->read(wptr_, end_ - wptr_); + if (ret.has_value()) + wptr_ += ret.value(); + return ret; + } + + std::unique_ptr<io::Reader> reader_; + size_t const max_len_; + uint64_t number_{0}; + std::unique_ptr<char[]> buffer_; + char* rptr_; + char* wptr_; + char* search_; + char* const end_; +}; + +} // namespace + +std::unique_ptr<Reader> open(std::unique_ptr<io::Reader> reader, + size_t max_len) { + return std::make_unique<ReaderImpl>(std::move(reader), max_len); +} + +} // namespace line diff --git a/src/line.hh b/src/line.hh new file mode 100644 index 0000000..a8eeea8 --- /dev/null +++ b/src/line.hh @@ -0,0 +1,37 @@ +#ifndef LINE_HH +#define LINE_HH + +#include "io.hh" // IWYU pragma: export + +#include <cstddef> +#include <expected> +#include <memory> +#include <optional> +#include <string_view> + +namespace line { + +class Reader { + public: + virtual ~Reader() = default; + + // Returned view is only valid until next call to read. + [[nodiscard]] + virtual std::expected<std::string_view, io::ReadError> read() = 0; + // Starts at zero. Returns next line. + // So, before first read it is zero, after first read it is one. + [[nodiscard]] virtual uint64_t number() const = 0; + + protected: + Reader() = default; + + Reader(Reader const&) = delete; + Reader& operator=(Reader const&) = delete; +}; + +[[nodiscard]] std::unique_ptr<Reader> open(std::unique_ptr<io::Reader> reader, + size_t max_len = 8192); + +} // namespace line + +#endif // LINE_HH diff --git a/src/logger.cc b/src/logger.cc new file mode 100644 index 0000000..e6198da --- /dev/null +++ b/src/logger.cc @@ -0,0 +1,125 @@ +#include "logger.hh" + +#include <cstdint> +#include <format> +#include <iostream> +#include <memory> +#include <string> +#include <string_view> +#include <utility> + +namespace logger { + +namespace { + +class BaseLogger : public Logger { + protected: + enum class Level : uint8_t { + kError, + kWarning, + kInfo, + kDebug, + }; + + public: + void err(std::string_view message) final { write(Level::kError, message); } + + void warn(std::string_view message) final { write(Level::kWarning, message); } + + void info(std::string_view message) final { write(Level::kInfo, message); } + +#ifndef NDEBUG + void dbg(std::string_view message) final { write(Level::kDebug, message); } +#endif + + protected: + BaseLogger() = default; + + virtual void write(Level level, std::string_view message) = 0; +}; + +class NoopLogger : public BaseLogger { + public: + NoopLogger() = default; + + protected: + void write(Level /* level */, std::string_view /* message */) override {} +}; + +class StderrLogger : public BaseLogger { + public: + explicit StderrLogger(bool verbose) : verbose_(verbose) {} + + protected: + void write(Level level, std::string_view message) override { + switch (level) { + case Level::kError: + std::cerr << "Error: "; + break; + case Level::kWarning: + std::cerr << "Warning: "; + break; + case Level::kInfo: + if (!verbose_) + return; + std::cerr << "Info: "; + break; + case Level::kDebug: + if (!verbose_) + return; + std::cerr << "Debug: "; + break; + } + std::cerr << message << '\n'; + } + + private: + bool const verbose_; +}; + +class PrefixLogger : public Logger { + public: + PrefixLogger(Logger& logger, std::string prefix) + : logger_(logger), prefix_(std::move(prefix)) {} + + void err(std::string_view message) override { + logger_.err(std::format("{}: {}", prefix_, message)); + } + + void warn(std::string_view message) override { + logger_.warn(std::format("{}: {}", prefix_, message)); + } + + void info(std::string_view message) override { + logger_.info(std::format("{}: {}", prefix_, message)); + } + +#ifndef NDEBUG + void dbg(std::string_view message) override { + logger_.dbg(std::format("{}: {}", prefix_, message)); + } +#endif + + private: + Logger& logger_; + std::string prefix_; +}; + +} // namespace + +[[nodiscard]] +std::unique_ptr<Logger> noop() { + return std::make_unique<NoopLogger>(); +} + +[[nodiscard]] +std::unique_ptr<Logger> stderr(bool verbose) { + return std::make_unique<StderrLogger>(verbose); +} + +[[nodiscard]] +std::unique_ptr<Logger> prefix(Logger& logger, std::string prefix) { + return std::make_unique<PrefixLogger>(logger, std::move(prefix)); +} + +} // namespace logger diff --git a/src/logger.hh b/src/logger.hh new file mode 100644 index 0000000..df6879a --- /dev/null +++ b/src/logger.hh @@ -0,0 +1,41 @@ +#ifndef LOGGER_HH +#define LOGGER_HH + +#include <memory> +#include <string> +#include <string_view> + +namespace logger { + +class Logger { + public: + virtual ~Logger() = default; + + virtual void err(std::string_view message) = 0; + virtual void warn(std::string_view message) = 0; + virtual void info(std::string_view message) = 0; + +#if defined(NDEBUG) + void dbg(std::string_view) {} +#else + virtual void dbg(std::string_view message) = 0; +#endif + + protected: + Logger() = default; + Logger(Logger const&) = delete; + Logger& operator=(Logger const&) = delete; +}; + +[[nodiscard]] +std::unique_ptr<Logger> noop(); + +[[nodiscard]] +std::unique_ptr<Logger> stderr(bool verbose = false); + +[[nodiscard]] +std::unique_ptr<Logger> prefix(Logger& logger, std::string prefix); + +} // namespace logger + +#endif // LOGGER_HH diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..9756c87 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,47 @@ +#include "args.hh" +#include "config.h" +#include "image_processor.hh" +#include "spawner.hh" + +#include <stdlib.h> // NOLINT(modernize-deprecated-headers) +#include <iostream> +#include <string_view> +#include <vector> + +int main(int argc, char** argv) { + auto args = Args::create(); + auto opt_help = args->option('h', "help", "display this text and exit"); + auto opt_version = args->option('V', "version", "display version and exit"); + std::vector<std::string_view> arguments; + if (!args->run(argc, argv, &arguments)) { + args->print_error(std::cerr); + std::cerr << "Try `sawmill --help` for usage.\n"; + return EXIT_FAILURE; + } + if (opt_help->is_set()) { + std::cout << "Usage: `sawmill [OPTION]...`\n" + << "This is a window manager for Wayland.\n" + << "\n"; + args->print_help(std::cout); + return EXIT_SUCCESS; + } + if (opt_version->is_set()) { + std::cout << "sawmill " << VERSION << " written by Joel Klinghed.\n"; + return EXIT_SUCCESS; + } + auto spawner = Spawner::create(); + if (spawner.has_value()) { + auto processor = spawner.value()->run(Spawner::Exec::kImageProcessor); + if (processor.has_value()) { + auto ret = image_processor::peek(**processor, "/home/the_jk/Downloads/image2vector.svg"); + if (ret.has_value()) { + std::cout << ret->width << "x" << ret->height << '\n'; + } else { + std::cerr << "Bad file\n"; + } + return EXIT_SUCCESS; + } + } + std::cerr << "Unable to launch spawner.\n"; + return EXIT_FAILURE; +} diff --git a/src/paths.cc b/src/paths.cc new file mode 100644 index 0000000..091be6e --- /dev/null +++ b/src/paths.cc @@ -0,0 +1,100 @@ +#include "paths.hh" + +#include "str.hh" + +#include <algorithm> +#include <cerrno> +#include <cstddef> +#include <cstdlib> +#include <iterator> +#include <memory> +#include <pwd.h> +#include <string_view> +#include <unistd.h> +#include <unordered_set> +#include <vector> + +namespace paths { + +namespace { + +std::vector<std::filesystem::path> xdg_read_dirs( + const char* userdir_env_name, std::string_view userdir_home_default, + const char* dirs_env_name, + std::vector<std::filesystem::path> const& dirs_default_value) { + std::vector<std::filesystem::path> ret; + std::unordered_set<std::filesystem::path> tmp; + auto* env_userdir = getenv(userdir_env_name); + if (env_userdir != nullptr && env_userdir[0] != '\0') { + ret.emplace_back(env_userdir); + } else { + ret.emplace_back(home() / userdir_home_default); + } + tmp.insert(ret.back()); + auto* env_dirs = getenv(dirs_env_name); + if (env_dirs != nullptr && env_dirs[0] != '\0') { + for (auto dir : str::split(env_dirs, ':')) { + if (tmp.emplace(dir).second) { + ret.emplace_back(dir); + } + } + } else { + std::ranges::copy_if( + dirs_default_value, std::back_inserter(ret), + [&tmp](auto const& dir) { return tmp.emplace(dir).second; }); + } + return ret; +} + +} // namespace + +std::filesystem::path home() { + { + auto* str = getenv("HOME"); + if (str != nullptr && str[0] != '\0') + return str; + } + + { + auto maybe_size = sysconf(_SC_GETPW_R_SIZE_MAX); + size_t size = maybe_size > 0 ? static_cast<size_t>(maybe_size) : 1024; + auto buffer = std::make_unique<char[]>(size); + struct passwd pwd; + struct passwd* ret; + int err; + while (true) { + err = getpwuid_r(geteuid(), &pwd, buffer.get(), size, &ret); + if (err == 0) + break; + if (err != ERANGE) + break; + auto new_size = size * 2; + if (new_size < size) + break; + buffer = std::make_unique<char[]>(new_size); + size = new_size; + } + if (err == 0 && ret) { + if (ret->pw_dir != nullptr && ret->pw_dir[0] != '\0') { + return ret->pw_dir; + } + } + } + + return "/"; +} + +std::vector<std::filesystem::path> config_dirs() { + static const std::vector<std::filesystem::path> fallback{"/etc/xdg"}; + return xdg_read_dirs("XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS", + fallback); +} + +std::vector<std::filesystem::path> data_dirs() { + static const std::vector<std::filesystem::path> fallback{"/usr/local/share/", + "/usr/share/"}; + return xdg_read_dirs("XDG_DATA_HOME", ".local/share", "XDG_DATA_DIRS", + fallback); +} + +} // namespace paths diff --git a/src/paths.hh b/src/paths.hh new file mode 100644 index 0000000..8bd4c75 --- /dev/null +++ b/src/paths.hh @@ -0,0 +1,20 @@ +#ifndef PATHS_HH +#define PATHS_HH + +#include <filesystem> // IWYU pragma: export +#include <vector> + +namespace paths { + +// Return home directory, goes HOME, /etc/passwd entry, / in that order. +std::filesystem::path home(); + +// Return config directories for reading, in order of priority. +std::vector<std::filesystem::path> config_dirs(); + +// Return data directories for reading, in order of priority. +std::vector<std::filesystem::path> data_dirs(); + +} // namespace paths + +#endif // PATHS_HH diff --git a/src/size.hh b/src/size.hh new file mode 100644 index 0000000..8fcc53f --- /dev/null +++ b/src/size.hh @@ -0,0 +1,30 @@ +#ifndef SIZE_HH +#define SIZE_HH + +#include <compare> +#include <cstdint> + +struct Size final { + Size() : width(0), height(0) {} + Size(uint32_t width, uint32_t height) : width(width), height(height) {} + + bool empty() const { return width == 0 || height == 0; } + + operator bool() const { return !empty(); } + + std::strong_ordering operator<=>(Size const& other) const { + if (empty()) { + return other.empty() ? std::strong_ordering::equal + : std::strong_ordering::less; + } + if (other.empty()) + return std::strong_ordering::greater; + auto ret = width <=> other.width; + return ret == std::strong_ordering::equal ? height <=> other.height : ret; + } + + uint32_t width; + uint32_t height; +}; + +#endif // SIZE_HH diff --git a/src/spawner.cc b/src/spawner.cc new file mode 100644 index 0000000..e49ac34 --- /dev/null +++ b/src/spawner.cc @@ -0,0 +1,236 @@ +#include "spawner.hh" + +#include "image_processor.hh" +#include "io.hh" +#include "unique_fd.hh" + +#include <cassert> +#include <csignal> +#include <expected> +#include <linux/prctl.h> +#include <memory> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <utility> + +namespace { + +int pidfd_getfd(int pidfd, int targetfd, unsigned int flags) { + return static_cast<int>(syscall(SYS_pidfd_getfd, pidfd, targetfd, flags)); +} + +// NOLINTNEXTLINE(misc-include-cleaner) +int pidfd_open(pid_t pid, unsigned int flags) { + return static_cast<int>(syscall(SYS_pidfd_open, pid, flags)); +} + +class ProcessImpl : public Process { + public: + ProcessImpl( + pid_t pid, + std::pair<std::unique_ptr<io::Reader>, std::unique_ptr<io::Writer>> pipe) + : pid_(pid), pipe_(std::move(pipe)) { + assert(pid_); + } + + [[nodiscard]] + pid_t pid() const { return pid_; } + + [[nodiscard]] + io::Reader& reader() const override { return *pipe_.first; } + + [[nodiscard]] + io::Writer& writer() const override { return *pipe_.second; } + + void kill() { + if (pid_ != 0) { + ::kill(pid_, SIGTERM); // NOLINT(misc-include-cleaner) + pid_ = 0; + } + } + + private: + pid_t pid_; + std::pair<std::unique_ptr<io::Reader>, std::unique_ptr<io::Writer>> pipe_; +}; + +struct Request { + Spawner::Exec exec; + int reader; + int writer; +}; + +struct Response { + pid_t pid; +}; + +void spawner_runner(unique_fd parent, std::unique_ptr<io::Reader> reader, + std::unique_ptr<io::Writer> writer) { + while (true) { + Request request; + { + auto ret = reader->repeat_read(&request, sizeof(request)); + if (!ret.has_value() || ret.value() != sizeof(request)) { + break; + } + } + + // Need to not return a response before child has duped fds. + // So use a short-lived pipe to sync child start. + auto sync_pipe = io::pipe(); + pid_t child_pid = 0; + + if (sync_pipe.has_value()) { + child_pid = fork(); + if (child_pid == 0) { + // Child process + sync_pipe->first.reset(); + reader.reset(); + writer.reset(); + + unique_fd child_reader_fd{pidfd_getfd(parent.get(), request.reader, 0)}; + unique_fd child_writer_fd{pidfd_getfd(parent.get(), request.writer, 0)}; + + parent.reset(); + + if (child_reader_fd && child_writer_fd) { + char c = 1; + if (sync_pipe->second->write(&c, sizeof(c)).has_value()) { + auto child_reader = io::reader_from_raw(child_reader_fd.release()); + auto child_writer = io::writer_from_raw(child_writer_fd.release()); + + switch (request.exec) { + case Spawner::Exec::kImageProcessor: + _exit(image_processor::run(std::move(child_reader), + std::move(child_writer))); + } + } + } + + _exit(1); + // _exit obviously never returns but to help tools and compilers… + return; + } + + // Parent process + if (child_pid == -1) { + // fork() failed, we use zero as error value. + child_pid = 0; + } else { + sync_pipe->second.reset(); + + char c = 1; + std::ignore = sync_pipe->first->read(&c, sizeof(c)); + } + } + + Response response{child_pid}; + { + auto ret = writer->repeat_write(&response, sizeof(response)); + if (!ret.has_value() || ret.value() != sizeof(response)) { + break; + } + } + } +} + +class SpawnerImpl : public Spawner { + public: + explicit SpawnerImpl(std::unique_ptr<ProcessImpl> process) + : process_(std::move(process)) {} + + ~SpawnerImpl() override { + if (process_) { + process_->kill(); + } + } + + [[nodiscard]] + std::expected<std::unique_ptr<Process>, Error> run(Exec exec) override { + if (!process_) { + return std::unexpected(Error::kError); + } + + auto reader_pipe = io::pipe(); + auto writer_pipe = io::pipe(); + if (!reader_pipe.has_value() || !writer_pipe.has_value()) { + return std::unexpected(Error::kError); + } + + Request req{ + .exec = exec, + .reader = writer_pipe->first->raw_fd(), + .writer = reader_pipe->second->raw_fd(), + }; + { + auto ret = process_->writer().repeat_write(&req, sizeof(req)); + if (!ret.has_value() || ret.value() != sizeof(req)) { + process_->kill(); + process_.reset(); + return std::unexpected(Error::kError); + } + } + + Response resp; + { + auto ret = process_->reader().repeat_read(&resp, sizeof(resp)); + if (!ret.has_value() || ret.value() != sizeof(resp)) { + process_->kill(); + process_.reset(); + return std::unexpected(Error::kError); + } + } + + if (resp.pid == 0) + return std::unexpected(Error::kError); + + return std::make_unique<ProcessImpl>( + resp.pid, std::make_pair(std::move(reader_pipe->first), + std::move(writer_pipe->second))); + } + + private: + std::unique_ptr<ProcessImpl> process_; +}; + +} // namespace + +std::expected<std::unique_ptr<Spawner>, Spawner::Error> Spawner::create() { + auto reader_pipe = io::pipe(); + auto writer_pipe = io::pipe(); + if (!reader_pipe.has_value() || !writer_pipe.has_value()) { + return std::unexpected(Error::kError); + } + + auto pid = getpid(); + unique_fd pidfd{pidfd_open(pid, 0)}; + if (!pidfd) { + return std::unexpected(Error::kError); + } + + // Needed for pidfd_getfd to work in child. + if (prctl(PR_SET_PTRACER, pid) != 0) { + return std::unexpected(Error::kError); + } + + pid = fork(); + if (pid == 0) { + // Child process + reader_pipe->first.reset(); + writer_pipe->second.reset(); + + spawner_runner(std::move(pidfd), std::move(writer_pipe->first), + std::move(reader_pipe->second)); + _exit(0); + } + + // Parent process + if (pid == -1) { + return std::unexpected(Error::kError); + } + + return std::make_unique<SpawnerImpl>(std::make_unique<ProcessImpl>( + pid, std::make_pair(std::move(reader_pipe->first), + std::move(writer_pipe->second)))); +} diff --git a/src/spawner.hh b/src/spawner.hh new file mode 100644 index 0000000..c4f511b --- /dev/null +++ b/src/spawner.hh @@ -0,0 +1,49 @@ +#ifndef SPAWNER_HH +#define SPAWNER_HH + +#include <cstdint> +#include <expected> +#include <memory> + +namespace io { +class Reader; +class Writer; +}; // namespace io + +class Process { + public: + virtual ~Process() = default; + + virtual io::Reader& reader() const = 0; + virtual io::Writer& writer() const = 0; + + protected: + Process() = default; + Process(Process const&) = delete; + Process& operator=(Process const&) = delete; +}; + +class Spawner { + public: + enum class Exec : uint8_t { + kImageProcessor, + }; + + enum class Error : uint8_t { + kError, + }; + + virtual ~Spawner() = default; + + [[nodiscard]] + virtual std::expected<std::unique_ptr<Process>, Error> run(Exec exec) = 0; + + static std::expected<std::unique_ptr<Spawner>, Error> create(); + + protected: + Spawner() = default; + Spawner(Spawner const&) = delete; + Spawner& operator=(Spawner const&) = delete; +}; + +#endif // SPAWNER_HH diff --git a/src/str.cc b/src/str.cc new file mode 100644 index 0000000..129e0f3 --- /dev/null +++ b/src/str.cc @@ -0,0 +1,94 @@ +#include "str.hh" + +#include <cstddef> +#include <string_view> +#include <vector> + +namespace str { + +namespace { + +[[nodiscard]] +inline bool is_space(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +} // namespace + +void split(std::string_view str, std::vector<std::string_view>& out, + char separator, bool keep_empty) { + out.clear(); + + size_t offset = 0; + while (true) { + auto next = str.find(separator, offset); + if (next == std::string_view::npos) { + if (keep_empty || offset < str.size()) + out.push_back(str.substr(offset)); + break; + } + if (keep_empty || offset < next) + out.push_back(str.substr(offset, next - offset)); + offset = next + 1; + } +} + +std::vector<std::string_view> split(std::string_view str, char separator, + bool keep_empty) { + std::vector<std::string_view> vec; + split(str, vec, separator, keep_empty); + return vec; +} + +void split(std::string_view str, std::vector<std::string_view>& out, + std::string_view separator, bool keep_empty) { + out.clear(); + + size_t offset = 0; + while (true) { + auto next = str.find(separator, offset); + if (next == std::string_view::npos) { + if (keep_empty || offset < str.size()) + out.push_back(str.substr(offset)); + break; + } + if (keep_empty || offset < next) + out.push_back(str.substr(offset, next - offset)); + offset = next + separator.size(); + } +} + +std::vector<std::string_view> split(std::string_view str, + std::string_view separator, + bool keep_empty) { + std::vector<std::string_view> vec; + split(str, vec, separator, keep_empty); + return vec; +} + +std::string_view trim(std::string_view str) { + size_t s = 0; + size_t e = str.size(); + while (s < e && is_space(str[s])) + ++s; + while (e > s && is_space(str[e - 1])) + --e; + return str.substr(s, e - s); +} + +std::string_view ltrim(std::string_view str) { + size_t s = 0; + size_t const e = str.size(); + while (s < e && is_space(str[s])) + ++s; + return str.substr(s, e - s); +} + +std::string_view rtrim(std::string_view str) { + size_t e = str.size(); + while (e > 0 && is_space(str[e - 1])) + --e; + return str.substr(0, e); +} + +} // namespace str diff --git a/src/str.hh b/src/str.hh new file mode 100644 index 0000000..c736f4b --- /dev/null +++ b/src/str.hh @@ -0,0 +1,34 @@ +#ifndef STR_HH +#define STR_HH + +#include <string_view> +#include <vector> + +namespace str { + +void split(std::string_view str, std::vector<std::string_view>& out, + char separator = ' ', bool keep_empty = false); + +[[nodiscard]] std::vector<std::string_view> split(std::string_view str, + char separator = ' ', + bool keep_empty = false); + +void split(std::string_view str, std::vector<std::string_view>& out, + std::string_view separator, bool keep_empty = false); + +[[nodiscard]] std::vector<std::string_view> split(std::string_view str, + std::string_view separator, + bool keep_empty = false); + +[[nodiscard]] +std::string_view trim(std::string_view str); + +[[nodiscard]] +std::string_view ltrim(std::string_view str); + +[[nodiscard]] +std::string_view rtrim(std::string_view str); + +} // namespace str + +#endif // STR_HH diff --git a/src/u.hh b/src/u.hh new file mode 100644 index 0000000..439d6dc --- /dev/null +++ b/src/u.hh @@ -0,0 +1,21 @@ +#ifndef U_HH +#define U_HH + +#include <cstdint> + +namespace u { + +enum class ReadError : uint8_t { + Invalid, // Invalid sequence + End, // At end (it == end) + Incomplete, // Too few bytes +}; + +enum class ReadErrorReplace : uint8_t { + End, // At end (it == end) + Incomplete, // Too few bytes +}; + +} // namespace u + +#endif // U_HH diff --git a/src/u8.hh b/src/u8.hh new file mode 100644 index 0000000..d673caa --- /dev/null +++ b/src/u8.hh @@ -0,0 +1,196 @@ +#ifndef U8_HH +#define U8_HH + +#include "u.hh" // IWYU pragma: export + +#include <cstdint> // IWYU pragma: export +#include <expected> +#include <iterator> +#include <type_traits> +#include <utility> + +namespace u8 { + +template <std::forward_iterator T> + requires std::is_same_v<std::iter_value_t<T>, uint8_t> +std::expected<uint32_t, u::ReadError> read(T& start, T const& end) { + if (start == end) + return std::unexpected(u::ReadError::End); + uint32_t u; + switch (*start >> 4) { + case 0xf: + // 11110uvv 10vvwwww 10xxxxyy 10yyzzzz + if (std::distance(start, end) < 4) { + return std::unexpected(u::ReadError::Incomplete); + } + u = (*start & 0x07) << 18; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 3); + return std::unexpected(u::ReadError::Invalid); + } + u |= (*start & 0x3f) << 12; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 2); + return std::unexpected(u::ReadError::Invalid); + } + u |= (*start & 0x3f) << 6; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + u |= *start & 0x3f; + if (u < 0x10000 || u > 0x10ffff) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + break; + case 0xe: + // 1110wwww 10xxxxyy 10yyzzzz + if (std::distance(start, end) < 3) { + return std::unexpected(u::ReadError::Incomplete); + } + u = (*start & 0x0f) << 12; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 2); + return std::unexpected(u::ReadError::Invalid); + } + u |= (*start & 0x3f) << 6; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + u |= *start & 0x3f; + if (u < 0x800 || (u >= 0xd800 && u <= 0xdfff)) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + break; + case 0xd: + case 0xc: + // 110xxxyy 10yyzzzz + if (std::distance(start, end) < 2) { + return std::unexpected(u::ReadError::Incomplete); + } + u = (*start & 0x1f) << 6; + std::advance(start, 1); + if ((*start & 0xc0) != 0x80) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + u |= *start & 0x3f; + if (u < 0x80) { + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + } + break; + case 0xb: + case 0xa: + case 0x9: + case 0x8: + std::advance(start, 1); + return std::unexpected(u::ReadError::Invalid); + default: + // 0yyyzzzz + u = *start; + break; + } + std::advance(start, 1); + return u; +} + +template <std::forward_iterator T> + requires std::is_same_v<std::iter_value_t<T>, uint8_t> +std::expected<uint32_t, u::ReadErrorReplace> read_replace(T& start, + T const& end, + bool eof) { + auto const tmp = start; + auto ret = read(start, end); + if (ret.has_value()) + return *ret; + switch (ret.error()) { + case u::ReadError::Incomplete: + if (eof) + break; + return std::unexpected(u::ReadErrorReplace::Incomplete); + case u::ReadError::End: + return std::unexpected(u::ReadErrorReplace::End); + case u::ReadError::Invalid: + break; + } + start = tmp + 1; + return 0xfffd; +} + +template <std::forward_iterator T> + requires std::is_same_v<std::iter_value_t<T>, uint8_t> +bool write(T& start, T const& end, uint32_t code) { + if (code < 0x80) { + if (start == end) + return false; + *start = static_cast<uint8_t>(code); + } else if (code < 0x800) { + if (std::distance(start, end) < 2) + return false; + *start = 0xc0 | static_cast<uint8_t>(code >> 6); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>(code & 0x3f); + } else if (code < 0x10000) { + if (std::distance(start, end) < 3) + return false; + *start = 0xe0 | static_cast<uint8_t>(code >> 12); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>((code >> 6) & 0x3f); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>(code & 0x3f); + } else { + if (std::distance(start, end) < 4) + return false; + *start = 0xf0 | static_cast<uint8_t>(code >> 18); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>((code >> 12) & 0x3f); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>((code >> 6) & 0x3f); + std::advance(start, 1); + *start = 0x80 | static_cast<uint8_t>(code & 0x3f); + } + std::advance(start, 1); + return true; +} + +template <std::forward_iterator T> + requires std::is_same_v<std::iter_value_t<T>, uint8_t> +bool skip(T& start, T const& end) { + if (start == end) + return false; + switch (*start >> 4) { + case 0xf: + if (std::distance(start, end) < 4) + return false; + std::advance(start, 4); + break; + case 0xe: + if (std::distance(start, end) < 3) + return false; + std::advance(start, 3); + break; + case 0xc: + case 0xd: + if (std::distance(start, end) < 2) + return false; + std::advance(start, 2); + break; + default: + std::advance(start, 1); + break; + } + return true; +} + +} // namespace u8 + +#endif // U8_HH diff --git a/src/unique_fd.cc b/src/unique_fd.cc new file mode 100644 index 0000000..135a449 --- /dev/null +++ b/src/unique_fd.cc @@ -0,0 +1,9 @@ +#include "unique_fd.hh" + +#include <unistd.h> + +void unique_fd::reset(int fd) { + if (fd_ != -1) + close(fd_); + fd_ = fd; +} diff --git a/src/unique_fd.hh b/src/unique_fd.hh new file mode 100644 index 0000000..2950905 --- /dev/null +++ b/src/unique_fd.hh @@ -0,0 +1,36 @@ +#ifndef UNIQUE_FD_HH +#define UNIQUE_FD_HH + +class unique_fd { + public: + constexpr unique_fd() : fd_(-1) {} + explicit constexpr unique_fd(int fd) : fd_(fd) {} + unique_fd(unique_fd& fd) = delete; + unique_fd& operator=(unique_fd& fd) = delete; + unique_fd(unique_fd&& fd) : fd_(fd.release()) {} + unique_fd& operator=(unique_fd&& fd) { + reset(fd.release()); + return *this; + } + ~unique_fd() { reset(); } + + bool operator==(unique_fd const& fd) const { return get() == fd.get(); } + bool operator!=(unique_fd const& fd) const { return get() != fd.get(); } + + int get() const { return fd_; } + explicit operator bool() const { return fd_ != -1; } + int operator*() const { return fd_; } + + int release() { + int ret = fd_; + fd_ = -1; + return ret; + } + + void reset(int fd = -1); + + private: + int fd_; +}; + +#endif // UNIQUE_FD_HH diff --git a/src/xpm/.clang-tidy b/src/xpm/.clang-tidy new file mode 100644 index 0000000..8f60b95 --- /dev/null +++ b/src/xpm/.clang-tidy @@ -0,0 +1,2 @@ +--- +Checks: '' diff --git a/src/xpm/color.c b/src/xpm/color.c new file mode 100644 index 0000000..ead7073 --- /dev/null +++ b/src/xpm/color.c @@ -0,0 +1,882 @@ +/*********************************************************** + +Copyright 1987, 1998 The Open Group + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from The Open Group. + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +******************************************************************/ + +#include <dix-config.h> + +#include <X11/keysym.h> + +#include "dix/dix_priv.h" +#include "include/dix.h" + +typedef struct _builtinColor { + unsigned char red; + unsigned char green; + unsigned char blue; + const char *name; +} BuiltinColor; + +static const BuiltinColor BuiltinColors[] = { +/* R G B name */ + { 240, 248, 255, "alice blue" }, + { 240, 248, 255, "AliceBlue" }, + { 250, 235, 215, "antique white" }, + { 250, 235, 215, "AntiqueWhite" }, + { 255, 239, 219, "AntiqueWhite1" }, + { 238, 223, 204, "AntiqueWhite2" }, + { 205, 192, 176, "AntiqueWhite3" }, + { 139, 131, 120, "AntiqueWhite4" }, + { 0, 255, 255, "aqua" }, + { 127, 255, 212, "aquamarine" }, + { 127, 255, 212, "aquamarine1" }, + { 118, 238, 198, "aquamarine2" }, + { 102, 205, 170, "aquamarine3" }, + { 69, 139, 116, "aquamarine4" }, + { 240, 255, 255, "azure" }, + { 240, 255, 255, "azure1" }, + { 224, 238, 238, "azure2" }, + { 193, 205, 205, "azure3" }, + { 131, 139, 139, "azure4" }, + { 245, 245, 220, "beige" }, + { 255, 228, 196, "bisque" }, + { 255, 228, 196, "bisque1" }, + { 238, 213, 183, "bisque2" }, + { 205, 183, 158, "bisque3" }, + { 139, 125, 107, "bisque4" }, + { 0, 0, 0, "black" }, + { 255, 235, 205, "blanched almond" }, + { 255, 235, 205, "BlanchedAlmond" }, + { 0, 0, 255, "blue" }, + { 138, 43, 226, "blue violet" }, + { 0, 0, 255, "blue1" }, + { 0, 0, 238, "blue2" }, + { 0, 0, 205, "blue3" }, + { 0, 0, 139, "blue4" }, + { 138, 43, 226, "BlueViolet" }, + { 165, 42, 42, "brown" }, + { 255, 64, 64, "brown1" }, + { 238, 59, 59, "brown2" }, + { 205, 51, 51, "brown3" }, + { 139, 35, 35, "brown4" }, + { 222, 184, 135, "burlywood" }, + { 255, 211, 155, "burlywood1" }, + { 238, 197, 145, "burlywood2" }, + { 205, 170, 125, "burlywood3" }, + { 139, 115, 85, "burlywood4" }, + { 95, 158, 160, "cadet blue" }, + { 95, 158, 160, "CadetBlue" }, + { 152, 245, 255, "CadetBlue1" }, + { 142, 229, 238, "CadetBlue2" }, + { 122, 197, 205, "CadetBlue3" }, + { 83, 134, 139, "CadetBlue4" }, + { 127, 255, 0, "chartreuse" }, + { 127, 255, 0, "chartreuse1" }, + { 118, 238, 0, "chartreuse2" }, + { 102, 205, 0, "chartreuse3" }, + { 69, 139, 0, "chartreuse4" }, + { 210, 105, 30, "chocolate" }, + { 255, 127, 36, "chocolate1" }, + { 238, 118, 33, "chocolate2" }, + { 205, 102, 29, "chocolate3" }, + { 139, 69, 19, "chocolate4" }, + { 255, 127, 80, "coral" }, + { 255, 114, 86, "coral1" }, + { 238, 106, 80, "coral2" }, + { 205, 91, 69, "coral3" }, + { 139, 62, 47, "coral4" }, + { 100, 149, 237, "cornflower blue" }, + { 100, 149, 237, "CornflowerBlue" }, + { 255, 248, 220, "cornsilk" }, + { 255, 248, 220, "cornsilk1" }, + { 238, 232, 205, "cornsilk2" }, + { 205, 200, 177, "cornsilk3" }, + { 139, 136, 120, "cornsilk4" }, + { 220, 20, 60, "crimson" }, + { 0, 255, 255, "cyan" }, + { 0, 255, 255, "cyan1" }, + { 0, 238, 238, "cyan2" }, + { 0, 205, 205, "cyan3" }, + { 0, 139, 139, "cyan4" }, + { 0, 0, 139, "dark blue" }, + { 0, 139, 139, "dark cyan" }, + { 184, 134, 11, "dark goldenrod" }, + { 169, 169, 169, "dark gray" }, + { 0, 100, 0, "dark green" }, + { 169, 169, 169, "dark grey" }, + { 189, 183, 107, "dark khaki" }, + { 139, 0, 139, "dark magenta" }, + { 85, 107, 47, "dark olive green" }, + { 255, 140, 0, "dark orange" }, + { 153, 50, 204, "dark orchid" }, + { 139, 0, 0, "dark red" }, + { 233, 150, 122, "dark salmon" }, + { 143, 188, 143, "dark sea green" }, + { 72, 61, 139, "dark slate blue" }, + { 47, 79, 79, "dark slate gray" }, + { 47, 79, 79, "dark slate grey" }, + { 0, 206, 209, "dark turquoise" }, + { 148, 0, 211, "dark violet" }, + { 0, 0, 139, "DarkBlue" }, + { 0, 139, 139, "DarkCyan" }, + { 184, 134, 11, "DarkGoldenrod" }, + { 255, 185, 15, "DarkGoldenrod1" }, + { 238, 173, 14, "DarkGoldenrod2" }, + { 205, 149, 12, "DarkGoldenrod3" }, + { 139, 101, 8, "DarkGoldenrod4" }, + { 169, 169, 169, "DarkGray" }, + { 0, 100, 0, "DarkGreen" }, + { 169, 169, 169, "DarkGrey" }, + { 189, 183, 107, "DarkKhaki" }, + { 139, 0, 139, "DarkMagenta" }, + { 85, 107, 47, "DarkOliveGreen" }, + { 202, 255, 112, "DarkOliveGreen1" }, + { 188, 238, 104, "DarkOliveGreen2" }, + { 162, 205, 90, "DarkOliveGreen3" }, + { 110, 139, 61, "DarkOliveGreen4" }, + { 255, 140, 0, "DarkOrange" }, + { 255, 127, 0, "DarkOrange1" }, + { 238, 118, 0, "DarkOrange2" }, + { 205, 102, 0, "DarkOrange3" }, + { 139, 69, 0, "DarkOrange4" }, + { 153, 50, 204, "DarkOrchid" }, + { 191, 62, 255, "DarkOrchid1" }, + { 178, 58, 238, "DarkOrchid2" }, + { 154, 50, 205, "DarkOrchid3" }, + { 104, 34, 139, "DarkOrchid4" }, + { 139, 0, 0, "DarkRed" }, + { 233, 150, 122, "DarkSalmon" }, + { 143, 188, 143, "DarkSeaGreen" }, + { 193, 255, 193, "DarkSeaGreen1" }, + { 180, 238, 180, "DarkSeaGreen2" }, + { 155, 205, 155, "DarkSeaGreen3" }, + { 105, 139, 105, "DarkSeaGreen4" }, + { 72, 61, 139, "DarkSlateBlue" }, + { 47, 79, 79, "DarkSlateGray" }, + { 151, 255, 255, "DarkSlateGray1" }, + { 141, 238, 238, "DarkSlateGray2" }, + { 121, 205, 205, "DarkSlateGray3" }, + { 82, 139, 139, "DarkSlateGray4" }, + { 47, 79, 79, "DarkSlateGrey" }, + { 0, 206, 209, "DarkTurquoise" }, + { 148, 0, 211, "DarkViolet" }, + { 255, 20, 147, "deep pink" }, + { 0, 191, 255, "deep sky blue" }, + { 255, 20, 147, "DeepPink" }, + { 255, 20, 147, "DeepPink1" }, + { 238, 18, 137, "DeepPink2" }, + { 205, 16, 118, "DeepPink3" }, + { 139, 10, 80, "DeepPink4" }, + { 0, 191, 255, "DeepSkyBlue" }, + { 0, 191, 255, "DeepSkyBlue1" }, + { 0, 178, 238, "DeepSkyBlue2" }, + { 0, 154, 205, "DeepSkyBlue3" }, + { 0, 104, 139, "DeepSkyBlue4" }, + { 105, 105, 105, "dim gray" }, + { 105, 105, 105, "dim grey" }, + { 105, 105, 105, "DimGray" }, + { 105, 105, 105, "DimGrey" }, + { 30, 144, 255, "dodger blue" }, + { 30, 144, 255, "DodgerBlue" }, + { 30, 144, 255, "DodgerBlue1" }, + { 28, 134, 238, "DodgerBlue2" }, + { 24, 116, 205, "DodgerBlue3" }, + { 16, 78, 139, "DodgerBlue4" }, + { 178, 34, 34, "firebrick" }, + { 255, 48, 48, "firebrick1" }, + { 238, 44, 44, "firebrick2" }, + { 205, 38, 38, "firebrick3" }, + { 139, 26, 26, "firebrick4" }, + { 255, 250, 240, "floral white" }, + { 255, 250, 240, "FloralWhite" }, + { 34, 139, 34, "forest green" }, + { 34, 139, 34, "ForestGreen" }, + { 255, 0, 255, "fuchsia" }, + { 220, 220, 220, "gainsboro" }, + { 248, 248, 255, "ghost white" }, + { 248, 248, 255, "GhostWhite" }, + { 255, 215, 0, "gold" }, + { 255, 215, 0, "gold1" }, + { 238, 201, 0, "gold2" }, + { 205, 173, 0, "gold3" }, + { 139, 117, 0, "gold4" }, + { 218, 165, 32, "goldenrod" }, + { 255, 193, 37, "goldenrod1" }, + { 238, 180, 34, "goldenrod2" }, + { 205, 155, 29, "goldenrod3" }, + { 139, 105, 20, "goldenrod4" }, + { 190, 190, 190, "gray" }, + { 0, 0, 0, "gray0" }, + { 3, 3, 3, "gray1" }, + { 26, 26, 26, "gray10" }, + { 255, 255, 255, "gray100" }, + { 28, 28, 28, "gray11" }, + { 31, 31, 31, "gray12" }, + { 33, 33, 33, "gray13" }, + { 36, 36, 36, "gray14" }, + { 38, 38, 38, "gray15" }, + { 41, 41, 41, "gray16" }, + { 43, 43, 43, "gray17" }, + { 46, 46, 46, "gray18" }, + { 48, 48, 48, "gray19" }, + { 5, 5, 5, "gray2" }, + { 51, 51, 51, "gray20" }, + { 54, 54, 54, "gray21" }, + { 56, 56, 56, "gray22" }, + { 59, 59, 59, "gray23" }, + { 61, 61, 61, "gray24" }, + { 64, 64, 64, "gray25" }, + { 66, 66, 66, "gray26" }, + { 69, 69, 69, "gray27" }, + { 71, 71, 71, "gray28" }, + { 74, 74, 74, "gray29" }, + { 8, 8, 8, "gray3" }, + { 77, 77, 77, "gray30" }, + { 79, 79, 79, "gray31" }, + { 82, 82, 82, "gray32" }, + { 84, 84, 84, "gray33" }, + { 87, 87, 87, "gray34" }, + { 89, 89, 89, "gray35" }, + { 92, 92, 92, "gray36" }, + { 94, 94, 94, "gray37" }, + { 97, 97, 97, "gray38" }, + { 99, 99, 99, "gray39" }, + { 10, 10, 10, "gray4" }, + { 102, 102, 102, "gray40" }, + { 105, 105, 105, "gray41" }, + { 107, 107, 107, "gray42" }, + { 110, 110, 110, "gray43" }, + { 112, 112, 112, "gray44" }, + { 115, 115, 115, "gray45" }, + { 117, 117, 117, "gray46" }, + { 120, 120, 120, "gray47" }, + { 122, 122, 122, "gray48" }, + { 125, 125, 125, "gray49" }, + { 13, 13, 13, "gray5" }, + { 127, 127, 127, "gray50" }, + { 130, 130, 130, "gray51" }, + { 133, 133, 133, "gray52" }, + { 135, 135, 135, "gray53" }, + { 138, 138, 138, "gray54" }, + { 140, 140, 140, "gray55" }, + { 143, 143, 143, "gray56" }, + { 145, 145, 145, "gray57" }, + { 148, 148, 148, "gray58" }, + { 150, 150, 150, "gray59" }, + { 15, 15, 15, "gray6" }, + { 153, 153, 153, "gray60" }, + { 156, 156, 156, "gray61" }, + { 158, 158, 158, "gray62" }, + { 161, 161, 161, "gray63" }, + { 163, 163, 163, "gray64" }, + { 166, 166, 166, "gray65" }, + { 168, 168, 168, "gray66" }, + { 171, 171, 171, "gray67" }, + { 173, 173, 173, "gray68" }, + { 176, 176, 176, "gray69" }, + { 18, 18, 18, "gray7" }, + { 179, 179, 179, "gray70" }, + { 181, 181, 181, "gray71" }, + { 184, 184, 184, "gray72" }, + { 186, 186, 186, "gray73" }, + { 189, 189, 189, "gray74" }, + { 191, 191, 191, "gray75" }, + { 194, 194, 194, "gray76" }, + { 196, 196, 196, "gray77" }, + { 199, 199, 199, "gray78" }, + { 201, 201, 201, "gray79" }, + { 20, 20, 20, "gray8" }, + { 204, 204, 204, "gray80" }, + { 207, 207, 207, "gray81" }, + { 209, 209, 209, "gray82" }, + { 212, 212, 212, "gray83" }, + { 214, 214, 214, "gray84" }, + { 217, 217, 217, "gray85" }, + { 219, 219, 219, "gray86" }, + { 222, 222, 222, "gray87" }, + { 224, 224, 224, "gray88" }, + { 227, 227, 227, "gray89" }, + { 23, 23, 23, "gray9" }, + { 229, 229, 229, "gray90" }, + { 232, 232, 232, "gray91" }, + { 235, 235, 235, "gray92" }, + { 237, 237, 237, "gray93" }, + { 240, 240, 240, "gray94" }, + { 242, 242, 242, "gray95" }, + { 245, 245, 245, "gray96" }, + { 247, 247, 247, "gray97" }, + { 250, 250, 250, "gray98" }, + { 252, 252, 252, "gray99" }, + { 0, 255, 0, "green" }, + { 173, 255, 47, "green yellow" }, + { 0, 255, 0, "green1" }, + { 0, 238, 0, "green2" }, + { 0, 205, 0, "green3" }, + { 0, 139, 0, "green4" }, + { 173, 255, 47, "GreenYellow" }, + { 190, 190, 190, "grey" }, + { 0, 0, 0, "grey0" }, + { 3, 3, 3, "grey1" }, + { 26, 26, 26, "grey10" }, + { 255, 255, 255, "grey100" }, + { 28, 28, 28, "grey11" }, + { 31, 31, 31, "grey12" }, + { 33, 33, 33, "grey13" }, + { 36, 36, 36, "grey14" }, + { 38, 38, 38, "grey15" }, + { 41, 41, 41, "grey16" }, + { 43, 43, 43, "grey17" }, + { 46, 46, 46, "grey18" }, + { 48, 48, 48, "grey19" }, + { 5, 5, 5, "grey2" }, + { 51, 51, 51, "grey20" }, + { 54, 54, 54, "grey21" }, + { 56, 56, 56, "grey22" }, + { 59, 59, 59, "grey23" }, + { 61, 61, 61, "grey24" }, + { 64, 64, 64, "grey25" }, + { 66, 66, 66, "grey26" }, + { 69, 69, 69, "grey27" }, + { 71, 71, 71, "grey28" }, + { 74, 74, 74, "grey29" }, + { 8, 8, 8, "grey3" }, + { 77, 77, 77, "grey30" }, + { 79, 79, 79, "grey31" }, + { 82, 82, 82, "grey32" }, + { 84, 84, 84, "grey33" }, + { 87, 87, 87, "grey34" }, + { 89, 89, 89, "grey35" }, + { 92, 92, 92, "grey36" }, + { 94, 94, 94, "grey37" }, + { 97, 97, 97, "grey38" }, + { 99, 99, 99, "grey39" }, + { 10, 10, 10, "grey4" }, + { 102, 102, 102, "grey40" }, + { 105, 105, 105, "grey41" }, + { 107, 107, 107, "grey42" }, + { 110, 110, 110, "grey43" }, + { 112, 112, 112, "grey44" }, + { 115, 115, 115, "grey45" }, + { 117, 117, 117, "grey46" }, + { 120, 120, 120, "grey47" }, + { 122, 122, 122, "grey48" }, + { 125, 125, 125, "grey49" }, + { 13, 13, 13, "grey5" }, + { 127, 127, 127, "grey50" }, + { 130, 130, 130, "grey51" }, + { 133, 133, 133, "grey52" }, + { 135, 135, 135, "grey53" }, + { 138, 138, 138, "grey54" }, + { 140, 140, 140, "grey55" }, + { 143, 143, 143, "grey56" }, + { 145, 145, 145, "grey57" }, + { 148, 148, 148, "grey58" }, + { 150, 150, 150, "grey59" }, + { 15, 15, 15, "grey6" }, + { 153, 153, 153, "grey60" }, + { 156, 156, 156, "grey61" }, + { 158, 158, 158, "grey62" }, + { 161, 161, 161, "grey63" }, + { 163, 163, 163, "grey64" }, + { 166, 166, 166, "grey65" }, + { 168, 168, 168, "grey66" }, + { 171, 171, 171, "grey67" }, + { 173, 173, 173, "grey68" }, + { 176, 176, 176, "grey69" }, + { 18, 18, 18, "grey7" }, + { 179, 179, 179, "grey70" }, + { 181, 181, 181, "grey71" }, + { 184, 184, 184, "grey72" }, + { 186, 186, 186, "grey73" }, + { 189, 189, 189, "grey74" }, + { 191, 191, 191, "grey75" }, + { 194, 194, 194, "grey76" }, + { 196, 196, 196, "grey77" }, + { 199, 199, 199, "grey78" }, + { 201, 201, 201, "grey79" }, + { 20, 20, 20, "grey8" }, + { 204, 204, 204, "grey80" }, + { 207, 207, 207, "grey81" }, + { 209, 209, 209, "grey82" }, + { 212, 212, 212, "grey83" }, + { 214, 214, 214, "grey84" }, + { 217, 217, 217, "grey85" }, + { 219, 219, 219, "grey86" }, + { 222, 222, 222, "grey87" }, + { 224, 224, 224, "grey88" }, + { 227, 227, 227, "grey89" }, + { 23, 23, 23, "grey9" }, + { 229, 229, 229, "grey90" }, + { 232, 232, 232, "grey91" }, + { 235, 235, 235, "grey92" }, + { 237, 237, 237, "grey93" }, + { 240, 240, 240, "grey94" }, + { 242, 242, 242, "grey95" }, + { 245, 245, 245, "grey96" }, + { 247, 247, 247, "grey97" }, + { 250, 250, 250, "grey98" }, + { 252, 252, 252, "grey99" }, + { 240, 255, 240, "honeydew" }, + { 240, 255, 240, "honeydew1" }, + { 224, 238, 224, "honeydew2" }, + { 193, 205, 193, "honeydew3" }, + { 131, 139, 131, "honeydew4" }, + { 255, 105, 180, "hot pink" }, + { 255, 105, 180, "HotPink" }, + { 255, 110, 180, "HotPink1" }, + { 238, 106, 167, "HotPink2" }, + { 205, 96, 144, "HotPink3" }, + { 139, 58, 98, "HotPink4" }, + { 205, 92, 92, "indian red" }, + { 205, 92, 92, "IndianRed" }, + { 255, 106, 106, "IndianRed1" }, + { 238, 99, 99, "IndianRed2" }, + { 205, 85, 85, "IndianRed3" }, + { 139, 58, 58, "IndianRed4" }, + { 75, 0, 130, "indigo" }, + { 255, 255, 240, "ivory" }, + { 255, 255, 240, "ivory1" }, + { 238, 238, 224, "ivory2" }, + { 205, 205, 193, "ivory3" }, + { 139, 139, 131, "ivory4" }, + { 240, 230, 140, "khaki" }, + { 255, 246, 143, "khaki1" }, + { 238, 230, 133, "khaki2" }, + { 205, 198, 115, "khaki3" }, + { 139, 134, 78, "khaki4" }, + { 230, 230, 250, "lavender" }, + { 255, 240, 245, "lavender blush" }, + { 255, 240, 245, "LavenderBlush" }, + { 255, 240, 245, "LavenderBlush1" }, + { 238, 224, 229, "LavenderBlush2" }, + { 205, 193, 197, "LavenderBlush3" }, + { 139, 131, 134, "LavenderBlush4" }, + { 124, 252, 0, "lawn green" }, + { 124, 252, 0, "LawnGreen" }, + { 255, 250, 205, "lemon chiffon" }, + { 255, 250, 205, "LemonChiffon" }, + { 255, 250, 205, "LemonChiffon1" }, + { 238, 233, 191, "LemonChiffon2" }, + { 205, 201, 165, "LemonChiffon3" }, + { 139, 137, 112, "LemonChiffon4" }, + { 173, 216, 230, "light blue" }, + { 240, 128, 128, "light coral" }, + { 224, 255, 255, "light cyan" }, + { 238, 221, 130, "light goldenrod" }, + { 250, 250, 210, "light goldenrod yellow" }, + { 211, 211, 211, "light gray" }, + { 144, 238, 144, "light green" }, + { 211, 211, 211, "light grey" }, + { 255, 182, 193, "light pink" }, + { 255, 160, 122, "light salmon" }, + { 32, 178, 170, "light sea green" }, + { 135, 206, 250, "light sky blue" }, + { 132, 112, 255, "light slate blue" }, + { 119, 136, 153, "light slate gray" }, + { 119, 136, 153, "light slate grey" }, + { 176, 196, 222, "light steel blue" }, + { 255, 255, 224, "light yellow" }, + { 173, 216, 230, "LightBlue" }, + { 191, 239, 255, "LightBlue1" }, + { 178, 223, 238, "LightBlue2" }, + { 154, 192, 205, "LightBlue3" }, + { 104, 131, 139, "LightBlue4" }, + { 240, 128, 128, "LightCoral" }, + { 224, 255, 255, "LightCyan" }, + { 224, 255, 255, "LightCyan1" }, + { 209, 238, 238, "LightCyan2" }, + { 180, 205, 205, "LightCyan3" }, + { 122, 139, 139, "LightCyan4" }, + { 238, 221, 130, "LightGoldenrod" }, + { 255, 236, 139, "LightGoldenrod1" }, + { 238, 220, 130, "LightGoldenrod2" }, + { 205, 190, 112, "LightGoldenrod3" }, + { 139, 129, 76, "LightGoldenrod4" }, + { 250, 250, 210, "LightGoldenrodYellow" }, + { 211, 211, 211, "LightGray" }, + { 144, 238, 144, "LightGreen" }, + { 211, 211, 211, "LightGrey" }, + { 255, 182, 193, "LightPink" }, + { 255, 174, 185, "LightPink1" }, + { 238, 162, 173, "LightPink2" }, + { 205, 140, 149, "LightPink3" }, + { 139, 95, 101, "LightPink4" }, + { 255, 160, 122, "LightSalmon" }, + { 255, 160, 122, "LightSalmon1" }, + { 238, 149, 114, "LightSalmon2" }, + { 205, 129, 98, "LightSalmon3" }, + { 139, 87, 66, "LightSalmon4" }, + { 32, 178, 170, "LightSeaGreen" }, + { 135, 206, 250, "LightSkyBlue" }, + { 176, 226, 255, "LightSkyBlue1" }, + { 164, 211, 238, "LightSkyBlue2" }, + { 141, 182, 205, "LightSkyBlue3" }, + { 96, 123, 139, "LightSkyBlue4" }, + { 132, 112, 255, "LightSlateBlue" }, + { 119, 136, 153, "LightSlateGray" }, + { 119, 136, 153, "LightSlateGrey" }, + { 176, 196, 222, "LightSteelBlue" }, + { 202, 225, 255, "LightSteelBlue1" }, + { 188, 210, 238, "LightSteelBlue2" }, + { 162, 181, 205, "LightSteelBlue3" }, + { 110, 123, 139, "LightSteelBlue4" }, + { 255, 255, 224, "LightYellow" }, + { 255, 255, 224, "LightYellow1" }, + { 238, 238, 209, "LightYellow2" }, + { 205, 205, 180, "LightYellow3" }, + { 139, 139, 122, "LightYellow4" }, + { 0, 255, 0, "lime" }, + { 50, 205, 50, "lime green" }, + { 50, 205, 50, "LimeGreen" }, + { 250, 240, 230, "linen" }, + { 255, 0, 255, "magenta" }, + { 255, 0, 255, "magenta1" }, + { 238, 0, 238, "magenta2" }, + { 205, 0, 205, "magenta3" }, + { 139, 0, 139, "magenta4" }, + { 176, 48, 96, "maroon" }, + { 255, 52, 179, "maroon1" }, + { 238, 48, 167, "maroon2" }, + { 205, 41, 144, "maroon3" }, + { 139, 28, 98, "maroon4" }, + { 102, 205, 170, "medium aquamarine" }, + { 0, 0, 205, "medium blue" }, + { 186, 85, 211, "medium orchid" }, + { 147, 112, 219, "medium purple" }, + { 60, 179, 113, "medium sea green" }, + { 123, 104, 238, "medium slate blue" }, + { 0, 250, 154, "medium spring green" }, + { 72, 209, 204, "medium turquoise" }, + { 199, 21, 133, "medium violet red" }, + { 102, 205, 170, "MediumAquamarine" }, + { 0, 0, 205, "MediumBlue" }, + { 186, 85, 211, "MediumOrchid" }, + { 224, 102, 255, "MediumOrchid1" }, + { 209, 95, 238, "MediumOrchid2" }, + { 180, 82, 205, "MediumOrchid3" }, + { 122, 55, 139, "MediumOrchid4" }, + { 147, 112, 219, "MediumPurple" }, + { 171, 130, 255, "MediumPurple1" }, + { 159, 121, 238, "MediumPurple2" }, + { 137, 104, 205, "MediumPurple3" }, + { 93, 71, 139, "MediumPurple4" }, + { 60, 179, 113, "MediumSeaGreen" }, + { 123, 104, 238, "MediumSlateBlue" }, + { 0, 250, 154, "MediumSpringGreen" }, + { 72, 209, 204, "MediumTurquoise" }, + { 199, 21, 133, "MediumVioletRed" }, + { 25, 25, 112, "midnight blue" }, + { 25, 25, 112, "MidnightBlue" }, + { 245, 255, 250, "mint cream" }, + { 245, 255, 250, "MintCream" }, + { 255, 228, 225, "misty rose" }, + { 255, 228, 225, "MistyRose" }, + { 255, 228, 225, "MistyRose1" }, + { 238, 213, 210, "MistyRose2" }, + { 205, 183, 181, "MistyRose3" }, + { 139, 125, 123, "MistyRose4" }, + { 255, 228, 181, "moccasin" }, + { 255, 222, 173, "navajo white" }, + { 255, 222, 173, "NavajoWhite" }, + { 255, 222, 173, "NavajoWhite1" }, + { 238, 207, 161, "NavajoWhite2" }, + { 205, 179, 139, "NavajoWhite3" }, + { 139, 121, 94, "NavajoWhite4" }, + { 0, 0, 128, "navy" }, + { 0, 0, 128, "navy blue" }, + { 0, 0, 128, "NavyBlue" }, + { 253, 245, 230, "old lace" }, + { 253, 245, 230, "OldLace" }, + { 128, 128, 0, "olive" }, + { 107, 142, 35, "olive drab" }, + { 107, 142, 35, "OliveDrab" }, + { 192, 255, 62, "OliveDrab1" }, + { 179, 238, 58, "OliveDrab2" }, + { 154, 205, 50, "OliveDrab3" }, + { 105, 139, 34, "OliveDrab4" }, + { 255, 165, 0, "orange" }, + { 255, 69, 0, "orange red" }, + { 255, 165, 0, "orange1" }, + { 238, 154, 0, "orange2" }, + { 205, 133, 0, "orange3" }, + { 139, 90, 0, "orange4" }, + { 255, 69, 0, "OrangeRed" }, + { 255, 69, 0, "OrangeRed1" }, + { 238, 64, 0, "OrangeRed2" }, + { 205, 55, 0, "OrangeRed3" }, + { 139, 37, 0, "OrangeRed4" }, + { 218, 112, 214, "orchid" }, + { 255, 131, 250, "orchid1" }, + { 238, 122, 233, "orchid2" }, + { 205, 105, 201, "orchid3" }, + { 139, 71, 137, "orchid4" }, + { 238, 232, 170, "pale goldenrod" }, + { 152, 251, 152, "pale green" }, + { 175, 238, 238, "pale turquoise" }, + { 219, 112, 147, "pale violet red" }, + { 238, 232, 170, "PaleGoldenrod" }, + { 152, 251, 152, "PaleGreen" }, + { 154, 255, 154, "PaleGreen1" }, + { 144, 238, 144, "PaleGreen2" }, + { 124, 205, 124, "PaleGreen3" }, + { 84, 139, 84, "PaleGreen4" }, + { 175, 238, 238, "PaleTurquoise" }, + { 187, 255, 255, "PaleTurquoise1" }, + { 174, 238, 238, "PaleTurquoise2" }, + { 150, 205, 205, "PaleTurquoise3" }, + { 102, 139, 139, "PaleTurquoise4" }, + { 219, 112, 147, "PaleVioletRed" }, + { 255, 130, 171, "PaleVioletRed1" }, + { 238, 121, 159, "PaleVioletRed2" }, + { 205, 104, 137, "PaleVioletRed3" }, + { 139, 71, 93, "PaleVioletRed4" }, + { 255, 239, 213, "papaya whip" }, + { 255, 239, 213, "PapayaWhip" }, + { 255, 218, 185, "peach puff" }, + { 255, 218, 185, "PeachPuff" }, + { 255, 218, 185, "PeachPuff1" }, + { 238, 203, 173, "PeachPuff2" }, + { 205, 175, 149, "PeachPuff3" }, + { 139, 119, 101, "PeachPuff4" }, + { 205, 133, 63, "peru" }, + { 255, 192, 203, "pink" }, + { 255, 181, 197, "pink1" }, + { 238, 169, 184, "pink2" }, + { 205, 145, 158, "pink3" }, + { 139, 99, 108, "pink4" }, + { 221, 160, 221, "plum" }, + { 255, 187, 255, "plum1" }, + { 238, 174, 238, "plum2" }, + { 205, 150, 205, "plum3" }, + { 139, 102, 139, "plum4" }, + { 176, 224, 230, "powder blue" }, + { 176, 224, 230, "PowderBlue" }, + { 160, 32, 240, "purple" }, + { 155, 48, 255, "purple1" }, + { 145, 44, 238, "purple2" }, + { 125, 38, 205, "purple3" }, + { 85, 26, 139, "purple4" }, + { 102, 51, 153, "rebecca purple" }, + { 102, 51, 153, "RebeccaPurple" }, + { 255, 0, 0, "red" }, + { 255, 0, 0, "red1" }, + { 238, 0, 0, "red2" }, + { 205, 0, 0, "red3" }, + { 139, 0, 0, "red4" }, + { 188, 143, 143, "rosy brown" }, + { 188, 143, 143, "RosyBrown" }, + { 255, 193, 193, "RosyBrown1" }, + { 238, 180, 180, "RosyBrown2" }, + { 205, 155, 155, "RosyBrown3" }, + { 139, 105, 105, "RosyBrown4" }, + { 65, 105, 225, "royal blue" }, + { 65, 105, 225, "RoyalBlue" }, + { 72, 118, 255, "RoyalBlue1" }, + { 67, 110, 238, "RoyalBlue2" }, + { 58, 95, 205, "RoyalBlue3" }, + { 39, 64, 139, "RoyalBlue4" }, + { 139, 69, 19, "saddle brown" }, + { 139, 69, 19, "SaddleBrown" }, + { 250, 128, 114, "salmon" }, + { 255, 140, 105, "salmon1" }, + { 238, 130, 98, "salmon2" }, + { 205, 112, 84, "salmon3" }, + { 139, 76, 57, "salmon4" }, + { 244, 164, 96, "sandy brown" }, + { 244, 164, 96, "SandyBrown" }, + { 46, 139, 87, "sea green" }, + { 46, 139, 87, "SeaGreen" }, + { 84, 255, 159, "SeaGreen1" }, + { 78, 238, 148, "SeaGreen2" }, + { 67, 205, 128, "SeaGreen3" }, + { 46, 139, 87, "SeaGreen4" }, + { 255, 245, 238, "seashell" }, + { 255, 245, 238, "seashell1" }, + { 238, 229, 222, "seashell2" }, + { 205, 197, 191, "seashell3" }, + { 139, 134, 130, "seashell4" }, + { 160, 82, 45, "sienna" }, + { 255, 130, 71, "sienna1" }, + { 238, 121, 66, "sienna2" }, + { 205, 104, 57, "sienna3" }, + { 139, 71, 38, "sienna4" }, + { 192, 192, 192, "silver" }, + { 135, 206, 235, "sky blue" }, + { 135, 206, 235, "SkyBlue" }, + { 135, 206, 255, "SkyBlue1" }, + { 126, 192, 238, "SkyBlue2" }, + { 108, 166, 205, "SkyBlue3" }, + { 74, 112, 139, "SkyBlue4" }, + { 106, 90, 205, "slate blue" }, + { 112, 128, 144, "slate gray" }, + { 112, 128, 144, "slate grey" }, + { 106, 90, 205, "SlateBlue" }, + { 131, 111, 255, "SlateBlue1" }, + { 122, 103, 238, "SlateBlue2" }, + { 105, 89, 205, "SlateBlue3" }, + { 71, 60, 139, "SlateBlue4" }, + { 112, 128, 144, "SlateGray" }, + { 198, 226, 255, "SlateGray1" }, + { 185, 211, 238, "SlateGray2" }, + { 159, 182, 205, "SlateGray3" }, + { 108, 123, 139, "SlateGray4" }, + { 112, 128, 144, "SlateGrey" }, + { 255, 250, 250, "snow" }, + { 255, 250, 250, "snow1" }, + { 238, 233, 233, "snow2" }, + { 205, 201, 201, "snow3" }, + { 139, 137, 137, "snow4" }, + { 0, 255, 127, "spring green" }, + { 0, 255, 127, "SpringGreen" }, + { 0, 255, 127, "SpringGreen1" }, + { 0, 238, 118, "SpringGreen2" }, + { 0, 205, 102, "SpringGreen3" }, + { 0, 139, 69, "SpringGreen4" }, + { 70, 130, 180, "steel blue" }, + { 70, 130, 180, "SteelBlue" }, + { 99, 184, 255, "SteelBlue1" }, + { 92, 172, 238, "SteelBlue2" }, + { 79, 148, 205, "SteelBlue3" }, + { 54, 100, 139, "SteelBlue4" }, + { 210, 180, 140, "tan" }, + { 255, 165, 79, "tan1" }, + { 238, 154, 73, "tan2" }, + { 205, 133, 63, "tan3" }, + { 139, 90, 43, "tan4" }, + { 0, 128, 128, "teal" }, + { 216, 191, 216, "thistle" }, + { 255, 225, 255, "thistle1" }, + { 238, 210, 238, "thistle2" }, + { 205, 181, 205, "thistle3" }, + { 139, 123, 139, "thistle4" }, + { 255, 99, 71, "tomato" }, + { 255, 99, 71, "tomato1" }, + { 238, 92, 66, "tomato2" }, + { 205, 79, 57, "tomato3" }, + { 139, 54, 38, "tomato4" }, + { 64, 224, 208, "turquoise" }, + { 0, 245, 255, "turquoise1" }, + { 0, 229, 238, "turquoise2" }, + { 0, 197, 205, "turquoise3" }, + { 0, 134, 139, "turquoise4" }, + { 238, 130, 238, "violet" }, + { 208, 32, 144, "violet red" }, + { 208, 32, 144, "VioletRed" }, + { 255, 62, 150, "VioletRed1" }, + { 238, 58, 140, "VioletRed2" }, + { 205, 50, 120, "VioletRed3" }, + { 139, 34, 82, "VioletRed4" }, + { 128, 128, 128, "web gray" }, + { 0, 128, 0, "web green" }, + { 128, 128, 128, "web grey" }, + { 128, 0, 0, "web maroon" }, + { 128, 0, 128, "web purple" }, + { 128, 128, 128, "WebGray" }, + { 0, 128, 0, "WebGreen" }, + { 128, 128, 128, "WebGrey" }, + { 128, 0, 0, "WebMaroon" }, + { 128, 0, 128, "WebPurple" }, + { 245, 222, 179, "wheat" }, + { 255, 231, 186, "wheat1" }, + { 238, 216, 174, "wheat2" }, + { 205, 186, 150, "wheat3" }, + { 139, 126, 102, "wheat4" }, + { 255, 255, 255, "white" }, + { 245, 245, 245, "white smoke" }, + { 245, 245, 245, "WhiteSmoke" }, + { 190, 190, 190, "x11 gray" }, + { 0, 255, 0, "x11 green" }, + { 190, 190, 190, "x11 grey" }, + { 176, 48, 96, "x11 maroon" }, + { 160, 32, 240, "x11 purple" }, + { 190, 190, 190, "X11Gray" }, + { 0, 255, 0, "X11Green" }, + { 190, 190, 190, "X11Grey" }, + { 176, 48, 96, "X11Maroon" }, + { 160, 32, 240, "X11Purple" }, + { 255, 255, 0, "yellow" }, + { 154, 205, 50, "yellow green" }, + { 255, 255, 0, "yellow1" }, + { 238, 238, 0, "yellow2" }, + { 205, 205, 0, "yellow3" }, + { 139, 139, 0, "yellow4" }, + { 154, 205, 50, "YellowGreen" }, +}; + +Bool +dixLookupBuiltinColor(int screen, + char *name, + unsigned int len, + unsigned short *pred, + unsigned short *pgreen, + unsigned short *pblue) +{ + int low = 0; + int high = ARRAY_SIZE(BuiltinColors) - 1; + + while (high >= low) { + int mid = (low + high) / 2; + const BuiltinColor *c = &BuiltinColors[mid]; + const int currentLen = strlen(c->name); + const int r = strncasecmp(c->name, name, min(len, currentLen)); + + if (r == 0) { + if (len == currentLen) { + *pred = c->red * 0x101; + *pgreen = c->green * 0x101; + *pblue = c->blue * 0x101; + return TRUE; + } else if (len > currentLen) { + low = mid + 1; + } else { + high = mid - 1; + } + } else if (r > 0) { + high = mid - 1; + } else { + low = mid + 1; + } + } + return FALSE; +} diff --git a/src/xpm/dix-config.h b/src/xpm/dix-config.h new file mode 100644 index 0000000..5137e8a --- /dev/null +++ b/src/xpm/dix-config.h @@ -0,0 +1 @@ +// Empty file making color.c able to compile without modifications. diff --git a/src/xpm/dix/dix_priv.h b/src/xpm/dix/dix_priv.h new file mode 100644 index 0000000..005da83 --- /dev/null +++ b/src/xpm/dix/dix_priv.h @@ -0,0 +1,14 @@ +#ifndef DIX_PRIV_H +#define DIX_PRIV_H + +#define TRUE 1 +#define FALSE 0 + +#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a)[0])) + +#include <string.h> +#include <strings.h> + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +#endif // DIX_PRIV_H diff --git a/src/xpm/include/dix.h b/src/xpm/include/dix.h new file mode 100644 index 0000000..2a7125a --- /dev/null +++ b/src/xpm/include/dix.h @@ -0,0 +1,14 @@ +#ifndef DIX_H +#define DIX_H + +#include <X11/Xlib.h> + +Bool +dixLookupBuiltinColor(int screen, + char *name, + unsigned int len, + unsigned short *pred, + unsigned short *pgreen, + unsigned short *pblue); + +#endif // DIX_H |
