From 69810e1c5c28cfc758ece59c7b7eab459e1e3f2b Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Mon, 13 Oct 2025 20:29:14 +0200 Subject: [PATCH] Refactor cli setup and printer selection logic into separate classes --- .vscode/settings.json | 3 + src/PtouchPrint.cpp | 347 +++++++++++------------------------- src/PtouchPrint.hpp | 69 ++++--- src/cli/CliParser.cpp | 93 ++++++++++ src/cli/CliParser.hpp | 94 ++++++++++ src/core/PrinterService.cpp | 95 ++++++++++ src/core/PrinterService.hpp | 87 +++++++++ src/main.cpp | 5 +- src/meson.build | 6 +- 9 files changed, 527 insertions(+), 272 deletions(-) create mode 100644 src/cli/CliParser.cpp create mode 100644 src/cli/CliParser.hpp create mode 100644 src/core/PrinterService.cpp create mode 100644 src/core/PrinterService.hpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 351793d..8fd89e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -88,6 +88,9 @@ }, "clangd.onConfigChanged": "restart", "cSpell.words": [ + "fontsize", + "halign", + "libusb", "ptrnt" ], } \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 36d0079..e9e7eb3 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -1,4 +1,4 @@ -/* +/* ptrnt - print labels on linux Copyright (C) 2023-2025 Moritz Martinius @@ -18,191 +18,154 @@ */ #include "PtouchPrint.hpp" -#include -#include #include -#include -#include -#include -#include #include #include #include -#include -#include -#include -#include -#include - -#include "CLI/Option.hpp" #include "PrinterDriverFactory.hpp" +#include "cli/CliParser.hpp" +#include "constants.hpp" +#include "core/PrinterService.hpp" #include "graphics/LabelBuilder.hpp" -#include "graphics/interface/ILabel.hpp" -#include "libusbwrap/UsbDeviceFactory.hpp" namespace ptprnt { -PtouchPrint::PtouchPrint(const char* versionString) : mVersionString{versionString} {} +PtouchPrint::PtouchPrint(const char* versionString) + : mVersionString(versionString), + mCliParser(std::make_unique(ptprnt::APP_DESC, versionString)), + mPrinterService(std::make_unique()) {} + +PtouchPrint::~PtouchPrint() = default; int PtouchPrint::init(int argc, char** argv) { - setupCliParser(); + // Parse CLI arguments + int parseResult = mCliParser->parse(argc, argv); + if (parseResult != 0) { + // Pass through: positive = clean exit (help/version), negative = error + return parseResult; + } - try { - mApp.parse(argc, argv); - } catch (const CLI::ParseError& e) { - mApp.exit(e); + // Setup logging based on CLI flags + setupLogger(); + + // Initialize printer service + if (!mPrinterService->initialize()) { return -1; } - // Set log level based on flags - if (mTraceFlag) { - setupLogger(spdlog::level::trace); - } else if (mVerboseFlag) { - setupLogger(spdlog::level::debug); - } else { - setupLogger(spdlog::level::warn); - } - - if (!mUsbDeviceFactory.init()) { - spdlog::error("Could not initialize libusb"); - return -1; - } return 0; } int PtouchPrint::run() { spdlog::info("ptprnt version {}", mVersionString); - // Handle --list-all-drivers flag - if (mListDriversFlag) { - auto driverFactory = std::make_unique(); - auto drivers = driverFactory->listAllDrivers(); + const auto& options = mCliParser->getOptions(); - fmt::print("Available printer drivers:\n"); - for (const auto& driver : drivers) { - fmt::print(" - {}\n", driver); - } - fmt::print("\nUse with: -p or --printer \n"); - return 0; + // Handle --list-all-drivers + if (options.listDrivers) { + return handleListDrivers() ? 0 : -1; } - // Determine which printer to use - std::shared_ptr printer = nullptr; + // Handle printing + return handlePrinting() ? 0 : -1; +} - if (mPrinterSelection != "auto") { - // Explicit printer selection by name - auto driverFactory = std::make_unique(); - printer = driverFactory->createByName(mPrinterSelection); +void PtouchPrint::setupLogger() { + const auto& options = mCliParser->getOptions(); - if (!printer) { - spdlog::error("Failed to create printer driver '{}'", mPrinterSelection); - spdlog::info("Use --list-all-drivers to see available drivers"); - return -1; - } - - spdlog::info("Using explicitly selected printer: {}", mPrinterSelection); - - // FakePrinter doesn't need USB device attachment - if (mPrinterSelection == "FakePrinter" || mPrinterSelection == "fake") { - printer->attachUsbDevice(nullptr); - } else { - // Real printer needs USB device - const auto printerUsbId = printer->getUsbId(); - auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); - - if (devices.empty()) { - spdlog::error("No USB device found for printer {}. Is it connected and powered on?", mPrinterSelection); - return -1; - } - - if (devices.size() > 1) { - spdlog::warn("Found more than one device of the same printer on bus. Using first one."); - } - - if (!printer->attachUsbDevice(std::move(devices[0]))) { - spdlog::error("Failed to attach USB device to printer"); - return -1; - } - } - } else { - // Auto-detect printer from USB devices - mDetectedPrinters = getCompatiblePrinters(); - auto numFoundPrinters = mDetectedPrinters.size(); - - if (numFoundPrinters == 0) { - spdlog::error("No compatible printers found, please make sure that they are turned on and connected"); - spdlog::info("Tip: Use -p FakePrinter for testing without hardware"); - return -1; - } else if (numFoundPrinters > 1) { - spdlog::warn("Found more than one compatible printer. Use -p to select explicitly."); - return -1; - } - - printer = mDetectedPrinters[0]; - const auto printerUsbId = printer->getUsbId(); - auto devices = mUsbDeviceFactory.findDevices(printerUsbId.first, printerUsbId.second); - - if (devices.size() != 1) { - spdlog::warn("Found more than one device of the same printer on bus. Currently not supported"); - return -1; - } - - if (!printer->attachUsbDevice(std::move(devices[0]))) { - spdlog::error("Failed to attach USB device to printer"); - return -1; - } + spdlog::level::level_enum level = spdlog::level::warn; + if (options.trace) { + level = spdlog::level::trace; + } else if (options.verbose) { + level = spdlog::level::debug; } + auto consoleSink = std::make_shared(); + consoleSink->set_level(level); + consoleSink->set_pattern("%^%L:%$ %v"); + + auto fileSink = std::make_shared("ptprnt.log", true); + fileSink->set_level(spdlog::level::trace); + fileSink->set_pattern("%Y-%m-%d %H:%m:%S:%e [pid:%P tid:%t] [%^%l%$] %v (%@)"); + + std::vector sinks{consoleSink, fileSink}; + auto logger = std::make_shared("default_logger", sinks.begin(), sinks.end()); + logger->set_level(spdlog::level::trace); + spdlog::set_default_logger(logger); +} + +bool PtouchPrint::handleListDrivers() { + auto driverFactory = std::make_unique(); + auto drivers = driverFactory->listAllDrivers(); + + fmt::print("Available printer drivers:\n"); + for (const auto& driver : drivers) { + fmt::print(" - {}\n", driver); + } + fmt::print("\nUse with: -p or --printer \n"); + return true; +} + +bool PtouchPrint::handlePrinting() { + const auto& options = mCliParser->getOptions(); + + // Select printer + auto printer = mPrinterService->selectPrinter(options.printerSelection); + if (!printer) { + spdlog::error("Failed to select printer"); + return false; + } + + // Get printer status auto status = printer->getPrinterStatus(); spdlog::info("Detected tape width is {}mm", status.tapeWidthMm); - if (0 == mCommands.size()) { + // Check if there are any commands + if (options.commands.empty()) { spdlog::warn("No command specified, nothing to do..."); - return 0; + return true; } - // Use LabelBuilder to construct the label + // Build label using LabelBuilder graphics::LabelBuilder labelBuilder(printer->getPrinterInfo().pixelLines); - for (const auto& [cmd, value] : mCommands) { - switch (cmd) { - case CliCmdType::Text: - labelBuilder.addText(value); + for (const auto& [cmdType, value] : options.commands) { + switch (cmdType) { + case cli::CommandType::Text: + labelBuilder.addText(value + "\n"); break; - case CliCmdType::Font: + case cli::CommandType::Font: spdlog::debug("Setting font to {}", value); labelBuilder.setFontFamily(value); break; - case CliCmdType::FontSize: + case cli::CommandType::FontSize: spdlog::debug("Setting font size to {}", std::stod(value)); labelBuilder.setFontSize(std::stod(value)); break; - case CliCmdType::HAlign: + case cli::CommandType::HAlign: { spdlog::debug("Setting text horizontal alignment to {}", value); - { - auto hPos = HALignPositionMap.find(value); - if (hPos == HALignPositionMap.end()) { - spdlog::warn("Invalid horizontal alignment specified!"); - labelBuilder.setHAlign(HAlignPosition::UNKNOWN); - } else { - labelBuilder.setHAlign(hPos->second); - } + auto hPos = HALignPositionMap.find(value); + if (hPos == HALignPositionMap.end()) { + spdlog::warn("Invalid horizontal alignment specified!"); + labelBuilder.setHAlign(HAlignPosition::UNKNOWN); + } else { + labelBuilder.setHAlign(hPos->second); } break; - case CliCmdType::VAlign: + } + case cli::CommandType::VAlign: { spdlog::debug("Setting text vertical alignment to {}", value); - { - auto vPos = VALignPositionMap.find(value); - if (vPos == VALignPositionMap.end()) { - spdlog::warn("Invalid vertical alignment specified!"); - labelBuilder.setVAlign(VAlignPosition::UNKNOWN); - } else { - labelBuilder.setVAlign(vPos->second); - } + auto vPos = VALignPositionMap.find(value); + if (vPos == VALignPositionMap.end()) { + spdlog::warn("Invalid vertical alignment specified!"); + labelBuilder.setVAlign(VAlignPosition::UNKNOWN); + } else { + labelBuilder.setVAlign(vPos->second); } break; - case CliCmdType::None: + } + case cli::CommandType::None: [[fallthrough]]; default: spdlog::warn("This command is currently not supported."); @@ -210,114 +173,14 @@ int PtouchPrint::run() { } } + // Build and print the label auto label = labelBuilder.build(); - if (!printer->printLabel(std::move(label))) { - spdlog::error("An error occured while printing"); - return -1; + if (!mPrinterService->printLabel(std::move(label))) { + spdlog::error("An error occurred while printing"); + return false; } - return 0; + return true; } -std::vector> PtouchPrint::getCompatiblePrinters() { - - auto usbDevs = mUsbDeviceFactory.findAllDevices(); - auto driverFactory = std::make_unique(); - std::vector> foundPrinterDrivers{}; - - for (auto& usbDev : usbDevs) { - auto driver = driverFactory->create(usbDev->getUsbId()); - if (driver != nullptr) { - foundPrinterDrivers.push_back(driver); - } - } - return foundPrinterDrivers; -} - -void PtouchPrint::setupLogger(spdlog::level::level_enum lvl) { - auto consoleSink = std::make_shared(); - consoleSink->set_level(lvl); - if (spdlog::level::level_enum::debug == lvl || spdlog::level::level_enum::trace == lvl) { - // This will enable file and line number for debug and trace macros - // TODO: line number and functions only work with macros - consoleSink->set_pattern("%^%L:%$ %v"); - } else { - consoleSink->set_pattern("%^%L:%$ %v"); - } - - auto fileSink = std::make_shared("ptprnt.log", true); - fileSink->set_level(spdlog::level::trace); - fileSink->set_pattern("%Y-%m-%d %H:%m:%S:%e [pid:%P tid:%t] [%^%l%$] %v (%@)"); - std::vector sinks{consoleSink, fileSink}; - auto logger = std::make_shared("default_logger", sinks.begin(), sinks.end()); - logger->set_level(spdlog::level::trace); - spdlog::set_default_logger(logger); -} - -// TODO: CLI parsing should be a seperate class/file -void PtouchPrint::setupCliParser() { - auto printVersion = [this](std::size_t) { - fmt::print("ptprnt version: {}\n", mVersionString); - }; - - // General options - mApp.add_flag("-v,--verbose", mVerboseFlag, "Enable verbose output"); - mApp.add_flag("--trace", mTraceFlag, "Enable trace output (shows USB communication)"); - mApp.add_flag("-V,--version", printVersion, "Prints the ptprnt's version"); - - // Printer selection - mApp.add_option("-p,--printer", mPrinterSelection, - "Select printer driver (default: auto). Use --list-all-drivers to see available options") - ->default_val("auto"); - - mApp.add_flag("--list-all-drivers", mListDriversFlag, "List all available printer drivers and exit"); - - // Text printing options - mApp.add_option("-t,--text", - "Text to print (can be used multple times, use formatting options before to " - "influence text layout)") - ->group("Printing") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) - ->trigger_on_parse() - ->each([this](std::string text) { mCommands.emplace_back(CliCmdType::Text, text); }); - mApp.add_option("-f,--font", "Font used for the following text occurences") - ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) - ->trigger_on_parse() - ->each([this](std::string font) { mCommands.emplace_back(CliCmdType::Font, font); }); - mApp.add_option("-s,--fontsize", "Font size of the following text occurences") - ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) - ->trigger_on_parse() - ->each([this](std::string size) { mCommands.emplace_back(CliCmdType::FontSize, size); }); - mApp.add_option("--valign", "Vertical alignment of the following text occurences") - ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) - ->trigger_on_parse() - ->transform([](std::string in) -> std::string { - std::unordered_set validValignOptions{"top", "middle", "bottom"}; - std::ranges::transform(in, in.begin(), [](unsigned char c) { return std::tolower(c); }); - if (validValignOptions.find(in) == validValignOptions.end()) { - return {""}; - } - return in; - }) - ->each([this](std::string valign) { mCommands.emplace_back(CliCmdType::VAlign, valign); }); - mApp.add_option("--halign", "Vertical alignment of the following text occurences") - ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) - ->trigger_on_parse() - ->transform([](std::string in) -> std::string { - std::unordered_set validValignOptions{"left", "center", "right", "justify"}; - std::transform(in.begin(), in.end(), in.begin(), [](unsigned char c) { return std::tolower(c); }); - if (validValignOptions.find(in) == validValignOptions.end()) { - return {""}; - } - return in; - }) - ->each([this](std::string halign) { mCommands.emplace_back(CliCmdType::HAlign, halign); }); - - // Image options - mApp.add_option("-i,--image", "Image to print. Excludes all text printing ")->group("Image printing"); -} -} // namespace ptprnt \ No newline at end of file +} // namespace ptprnt diff --git a/src/PtouchPrint.hpp b/src/PtouchPrint.hpp index 911eea8..2c4b420 100644 --- a/src/PtouchPrint.hpp +++ b/src/PtouchPrint.hpp @@ -1,4 +1,4 @@ -/* +/* ptrnt - print labels on linux Copyright (C) 2023-2025 Moritz Martinius @@ -19,49 +19,62 @@ #pragma once -#include -#include -#include +#include +#include -#include "constants.hpp" -#include "interface/IPrinterDriver.hpp" -#include "libusbwrap/UsbDeviceFactory.hpp" +namespace ptprnt::cli { +class CliParser; +} + +namespace ptprnt::core { +class PrinterService; +} namespace ptprnt { -enum class CliCmdType { None = 0, Text = 1, FontSize = 2, Font = 3, VAlign = 4, HAlign = 5 }; -using CliCmd = std::pair; +/** + * @brief Main application class for ptprnt + * + * Acts as a thin glue layer coordinating CLI parsing and core printer functionality. + * Separates CLI frontend concerns from the core library. + */ class PtouchPrint { public: + /** + * @brief Construct the application + * @param versionString Version string to display + */ PtouchPrint(const char* versionString); - ~PtouchPrint() = default; + ~PtouchPrint(); // Must be defined in .cpp where complete types are visible - // This is basically a singelton application class, no need to copy or move + // This is basically a singleton application class, no need to copy or move PtouchPrint(const PtouchPrint&) = delete; PtouchPrint& operator=(const PtouchPrint&) = delete; PtouchPrint(PtouchPrint&&) = delete; PtouchPrint& operator=(PtouchPrint&&) = delete; + /** + * @brief Initialize the application + * @param argc Argument count + * @param argv Argument values + * @return 0 on success, non-zero on error + */ int init(int argc, char** argv); + + /** + * @brief Run the application + * @return 0 on success, non-zero on error + */ int run(); private: - // methods - void setupLogger(spdlog::level::level_enum lvl); - void setupCliParser(); - std::vector> getCompatiblePrinters(); + void setupLogger(); + bool handleListDrivers(); + bool handlePrinting(); - // member variables - CLI::App mApp{ptprnt::APP_DESC}; - libusbwrap::UsbDeviceFactory mUsbDeviceFactory{}; - std::vector> mDetectedPrinters{}; - std::vector mCommands{}; - std::string mVersionString = ""; - - // CLI flags and options - bool mVerboseFlag = false; - bool mTraceFlag = false; - std::string mPrinterSelection = "auto"; - bool mListDriversFlag = false; + std::string mVersionString; + std::unique_ptr mCliParser; + std::unique_ptr mPrinterService; }; -} // namespace ptprnt \ No newline at end of file + +} // namespace ptprnt diff --git a/src/cli/CliParser.cpp b/src/cli/CliParser.cpp new file mode 100644 index 0000000..318f567 --- /dev/null +++ b/src/cli/CliParser.cpp @@ -0,0 +1,93 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#include "CliParser.hpp" + +#include + +namespace ptprnt::cli { + +CliParser::CliParser(const std::string& appDescription, std::string versionString) + : mApp(appDescription), mVersionString(std::move(versionString)) { + setupParser(); +} + +int CliParser::parse(int argc, char** argv) { + try { + mApp.parse(argc, argv); + } catch (const CLI::CallForHelp& e) { + // User requested help - display it and signal clean exit + mApp.exit(e); + return 1; // Signal: exit cleanly + } catch (const CLI::CallForVersion&) { + // User requested version - already displayed by callback + return 1; // Signal: exit cleanly + } catch (const CLI::ParseError& e) { + // Parse error - display error message + mApp.exit(e); + return -1; // Signal: error + } + return 0; +} + +void CliParser::setupParser() { + // Version callback + auto printVersion = [this](std::size_t) { + fmt::print("ptprnt version: {}\n", mVersionString); + throw CLI::CallForVersion(); + }; + + // General options + mApp.add_flag("-v,--verbose", mOptions.verbose, "Enable verbose output"); + mApp.add_flag("--trace", mOptions.trace, "Enable trace output (shows USB communication)"); + mApp.add_flag("-V,--version", printVersion, "Prints the ptprnt's version"); + + // Printer selection + mApp.add_option("-p,--printer", mOptions.printerSelection, + "Select printer driver (default: auto). Use --list-all-drivers to see available options") + ->default_val("auto"); + + mApp.add_flag("--list-all-drivers", mOptions.listDrivers, "List all available printer drivers and exit"); + + // Text printing options + // Note: CLI11 options are processed in order when using ->each() with callbacks + mApp.add_option("-t,--text", + "Text to print (can be used multiple times, use formatting options before to influence text layout)") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->each([this](const std::string& text) { mOptions.commands.emplace_back(CommandType::Text, text); }); + + // Text formatting options + mApp.add_option("-f,--font", "Font used for the following text occurrences") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->each([this](const std::string& font) { mOptions.commands.emplace_back(CommandType::Font, font); }); + + mApp.add_option("-s,--fontsize", "Font size of the following text occurrences") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->each([this](const std::string& size) { mOptions.commands.emplace_back(CommandType::FontSize, size); }); + + mApp.add_option("--valign", "Vertical alignment of the following text occurrences") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->each([this](const std::string& align) { mOptions.commands.emplace_back(CommandType::VAlign, align); }); + + mApp.add_option("--halign", "Horizontal alignment of the following text occurrences") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->each([this](const std::string& align) { mOptions.commands.emplace_back(CommandType::HAlign, align); }); +} + +} // namespace ptprnt::cli diff --git a/src/cli/CliParser.hpp b/src/cli/CliParser.hpp new file mode 100644 index 0000000..148b4a3 --- /dev/null +++ b/src/cli/CliParser.hpp @@ -0,0 +1,94 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#pragma once + +#include + +#include +#include + +namespace ptprnt::cli { + +/** + * @brief Types of CLI commands that can be issued + */ +enum class CommandType { None = 0, Text = 1, FontSize = 2, Font = 3, VAlign = 4, HAlign = 5 }; + +/** + * @brief A command with its type and value + */ +using Command = std::pair; + +/** + * @brief Parsed CLI options and commands + */ +struct CliOptions { + bool verbose{false}; + bool trace{false}; + bool listDrivers{false}; + std::string printerSelection{"auto"}; + std::vector commands{}; +}; + +/** + * @brief CLI argument parser for ptprnt + * + * Handles all command-line argument parsing using CLI11. + * Separates CLI concerns from core library functionality. + */ +class CliParser { + public: + /** + * @brief Construct a CLI parser + * @param appDescription Application description for help text + * @param versionString Version string to display + */ + CliParser(const std::string& appDescription, std::string versionString); + + ~CliParser() = default; + + CliParser(const CliParser&) = delete; + CliParser& operator=(const CliParser&) = delete; + CliParser(CliParser&&) = delete; + CliParser& operator=(CliParser&&) = delete; + + /** + * @brief Parse command line arguments + * @param argc Argument count + * @param argv Argument values + * @return 0 on success, positive value if should exit immediately (help/version), negative on error + */ + int parse(int argc, char** argv); + + /** + * @brief Get the parsed options + * @return Reference to parsed options + */ + [[nodiscard]] const CliOptions& getOptions() const { return mOptions; } + + private: + void setupParser(); + + CLI::App mApp; + std::string mVersionString; + CliOptions mOptions; +}; + +} // namespace ptprnt::cli diff --git a/src/core/PrinterService.cpp b/src/core/PrinterService.cpp new file mode 100644 index 0000000..a4b226f --- /dev/null +++ b/src/core/PrinterService.cpp @@ -0,0 +1,95 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#include "PrinterService.hpp" + +#include + +#include "PrinterDriverFactory.hpp" + +namespace ptprnt::core { + +PrinterService::PrinterService() = default; + +bool PrinterService::initialize() { + if (!mUsbDeviceFactory.init()) { + spdlog::error("Could not initialize libusb"); + return false; + } + return true; +} + +std::vector> PrinterService::detectPrinters() { + spdlog::debug("Detecting printers..."); + + auto usbDevs = mUsbDeviceFactory.findAllDevices(); + auto driverFactory = std::make_unique(); + mDetectedPrinters.clear(); + + for (auto& usbDev : usbDevs) { + auto driver = driverFactory->create(usbDev->getUsbId()); + if (driver != nullptr) { + mDetectedPrinters.push_back(driver); + } + } + + spdlog::debug("Found {} compatible printer(s)", mDetectedPrinters.size()); + return mDetectedPrinters; +} + +std::shared_ptr PrinterService::selectPrinter(const std::string& printerName) { + if (mDetectedPrinters.empty()) { + detectPrinters(); + } + + if (mDetectedPrinters.empty()) { + spdlog::error("No compatible printers detected"); + return nullptr; + } + + // Auto-select first printer + if (printerName == "auto") { + mCurrentPrinter = mDetectedPrinters.front(); + spdlog::info("Auto-selected printer: {}", mCurrentPrinter->getName()); + return mCurrentPrinter; + } + + // Select printer by name + for (auto& printer : mDetectedPrinters) { + if (printer->getDriverName() == printerName) { + mCurrentPrinter = printer; + spdlog::info("Using explicitly selected printer: {}", printerName); + return mCurrentPrinter; + } + } + + spdlog::error("Printer '{}' not found", printerName); + return nullptr; +} + +bool PrinterService::printLabel(std::unique_ptr label) { + if (!mCurrentPrinter) { + spdlog::error("No printer selected"); + return false; + } + + return mCurrentPrinter->printLabel(std::move(label)); +} + +} // namespace ptprnt::core diff --git a/src/core/PrinterService.hpp b/src/core/PrinterService.hpp new file mode 100644 index 0000000..142e82a --- /dev/null +++ b/src/core/PrinterService.hpp @@ -0,0 +1,87 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2025 Moritz Martinius + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + */ + +#pragma once + +#include +#include +#include + +#include "interface/IPrinterDriver.hpp" +#include "libusbwrap/UsbDeviceFactory.hpp" + +namespace ptprnt::core { + +/** + * @brief Core service for printer operations + * + * Provides the core library functionality for: + * - Detecting printers + * - Selecting printers + * - Building and printing labels + */ +class PrinterService { + public: + PrinterService(); + ~PrinterService() = default; + + PrinterService(const PrinterService&) = delete; + PrinterService& operator=(const PrinterService&) = delete; + PrinterService(PrinterService&&) = delete; + PrinterService& operator=(PrinterService&&) = delete; + + /** + * @brief Initialize USB device factory + * @return true on success, false on failure + */ + bool initialize(); + + /** + * @brief Detect all compatible printers + * @return Vector of detected printers + */ + std::vector> detectPrinters(); + + /** + * @brief Select a printer by name or auto-detect + * @param printerName Printer driver name, or "auto" for first detected + * @return Printer driver, or nullptr if not found + */ + std::shared_ptr selectPrinter(const std::string& printerName); + + /** + * @brief Get the currently selected printer + * @return Current printer, or nullptr if none selected + */ + [[nodiscard]] std::shared_ptr getCurrentPrinter() const { return mCurrentPrinter; } + + /** + * @brief Print a label + * @param label The label to print + * @return true on success, false on failure + */ + bool printLabel(std::unique_ptr label); + + private: + libusbwrap::UsbDeviceFactory mUsbDeviceFactory; + std::vector> mDetectedPrinters; + std::shared_ptr mCurrentPrinter; +}; + +} // namespace ptprnt::core diff --git a/src/main.cpp b/src/main.cpp index 19a160a..8c6cd55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,10 @@ int main(int argc, char** argv) { ptprnt::PtouchPrint ptouchprnt(PROJ_VERSION); int ret = ptouchprnt.init(argc, argv); if (ret != 0) { - return ret; + // Non-zero from init means don't continue + // Positive values = clean exit (help/version) + // Negative values = error + return ret > 0 ? 0 : ret; } return ptouchprnt.run(); } diff --git a/src/meson.build b/src/meson.build index 70420c1..79a255f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,7 +13,9 @@ ptprnt_hpps = files ( 'graphics/Bitmap.hpp', 'graphics/Label.hpp', 'graphics/LabelBuilder.hpp', - 'graphics/Monochrome.hpp' + 'graphics/Monochrome.hpp', + 'cli/CliParser.hpp', + 'core/PrinterService.hpp' ) ptprnt_srcs = files ( @@ -27,4 +29,6 @@ ptprnt_srcs = files ( 'graphics/Monochrome.cpp', 'libusbwrap/UsbDeviceFactory.cpp', 'libusbwrap/UsbDevice.cpp', + 'cli/CliParser.cpp', + 'core/PrinterService.cpp' ) \ No newline at end of file