feat: support for IPP printing + test page

This commit is contained in:
2026-04-01 21:15:27 +02:00
parent 868d0fd4f5
commit 550d4d2a19
28 changed files with 4121 additions and 62 deletions
+77
View File
@@ -0,0 +1,77 @@
# Makefile for printctl on MontaukOS
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
PROG_INC := ../../include
LIBC_LIB := ../../lib/libc
BEARSSL := ../../lib/bearssl
TLS_LIB := ../../lib/tls
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-mno-80387 \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-I $(BEARSSL)/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
TARGET := $(BINDIR)/os/printctl.elf
DEPS := $(OBJDIR)/main.d
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJDIR)/main.o $(LINK_LD) Makefile
mkdir -p $(BINDIR)/os
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJDIR)/main.o $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a -o $@
$(OBJDIR)/main.o: main.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
-include $(DEPS)
clean:
rm -rf $(OBJDIR) $(TARGET)
+360
View File
@@ -0,0 +1,360 @@
/*
* main.cpp
* MontaukOS print control utility
* Copyright (c) 2026 Daniel Hammer
*/
#include <print/print.hpp>
extern "C" {
#include <dirent.h>
#include <stdio.h>
#include <string.h>
}
using namespace print;
static void usage() {
printf("Usage:\n");
printf(" printctl set-printer <ipp://host[:port]/path>\n");
printf(" printctl show-printer\n");
printf(" printctl print <file> [--printer <uri>] [--name <job>] [--wait]\n");
printf(" printctl test-page [--printer <uri>] [--wait] [--no-wait]\n");
printf(" printctl status [--verbose]\n");
printf(" printctl inspect <job-id>\n");
printf(" printctl probe [uri]\n");
}
static void print_host_details(const IppUri& uri) {
char ip[20];
if (uri.ip) format_ipv4(ip, sizeof(ip), uri.ip);
else safe_copy(ip, sizeof(ip), "unresolved");
printf("Host: %s\n", uri.host);
printf("Resolved IP: %s\n", ip);
printf("Transport: %s\n", uri.use_tls ? "TLS" : "Plain TCP");
printf("Port: %u\n", (unsigned)uri.port);
printf("Path: %s\n", uri.path);
}
static void print_job_detail(const JobMeta& job, const char* state, bool verbose) {
printf(" %s %s\n", job.id, job.job_name);
printf(" state: %s", state ? state : job.state);
if (job.updated_at[0]) printf(" updated: %s", job.updated_at);
printf("\n");
if (job.printer_uri[0]) printf(" printer: %s\n", job.printer_uri);
if (job.doc_format[0]) printf(" format: %s\n", job.doc_format);
if (job.source_name[0]) printf(" source: %s\n", job.source_name);
if (job.remote_job_id > 0) printf(" printer job id: %d\n", job.remote_job_id);
if (job.status_message[0]) printf(" status: %s\n", job.status_message);
if ((verbose || strcmp(state, "failed") == 0 || strcmp(state, "printing") == 0) &&
job.debug_info[0]) {
printf(" debug: %s\n", job.debug_info);
}
}
static void print_job_line(const char* dir, const char* label, bool verbose) {
DIR* d = opendir(dir);
if (!d) {
printf("%s: unavailable\n", label);
return;
}
printf("%s:\n", label);
int count = 0;
struct dirent* ent = nullptr;
while ((ent = readdir(d)) != nullptr) {
if (ent->d_name[0] == '.') continue;
char path[MAX_PATH_LEN];
path_join(path, sizeof(path), dir, ent->d_name);
JobMeta job = {};
if (!load_job_from_path(path, &job)) continue;
const char* state = strcmp(label, "Queued") == 0 ? "queued"
: strcmp(label, "Active") == 0 ? "printing"
: strcmp(label, "Done") == 0 ? "completed"
: strcmp(label, "Failed") == 0 ? "failed"
: job.state;
print_job_detail(job, state, verbose);
count++;
}
closedir(d);
if (count == 0) printf(" empty\n");
}
static int cmd_status(const ArgList& args) {
ensure_spool_dirs();
bool verbose = false;
for (int i = 1; i < args.count; i++) {
if (strcmp(args.argv[i], "--verbose") == 0) verbose = true;
else {
usage();
return 1;
}
}
char uri[MAX_PATH_LEN];
if (read_default_printer_uri(uri, sizeof(uri))) {
printf("Default printer: %s\n", uri);
if (verbose) {
char err[128] = {};
IppUri parsed = {};
if (normalize_ipp_uri(uri, &parsed, err, sizeof(err))) {
resolve_host(parsed.host, &parsed.ip);
print_host_details(parsed);
} else {
printf("Printer URI error: %s\n", err);
}
}
} else
printf("Default printer: not configured\n");
int pid = -1;
if (daemon_is_running(&pid))
printf("Daemon: running (pid %d)\n", pid);
else
printf("Daemon: stopped\n");
print_job_line(SPOOL_QUEUE_DIR, "Queued", verbose);
print_job_line(SPOOL_ACTIVE_DIR, "Active", verbose);
print_job_line(SPOOL_DONE_DIR, "Done", verbose);
print_job_line(SPOOL_FAILED_DIR, "Failed", verbose);
return 0;
}
static int cmd_set_printer(const char* uri) {
char err[128];
IppUri parsed = {};
if (!normalize_ipp_uri(uri, &parsed, err, sizeof(err))) {
printf("printctl: %s\n", err);
return 1;
}
if (!write_default_printer_uri(parsed.normalized)) {
printf("printctl: failed to write default printer\n");
return 1;
}
printf("%s\n", parsed.normalized);
return 0;
}
static int print_submission_result(const char* job_id, bool wait) {
printf("queued job %s\n", job_id);
if (!wait) return 0;
JobMeta job = {};
char err[128] = {};
bool ok = wait_for_job(job_id, &job, DEFAULT_WAIT_TIMEOUT_MS, err, sizeof(err));
if (job.id[0] != '\0') {
if (ok) {
printf("completed: %s\n", job.status_message[0] ? job.status_message : "Printed");
if (job.remote_job_id > 0) printf("printer job id: %d\n", job.remote_job_id);
if (job.debug_info[0]) printf("detail: %s\n", job.debug_info);
return 0;
}
printf("failed: %s\n", job.status_message[0] ? job.status_message : "Print failed");
if (job.debug_info[0]) printf("detail: %s\n", job.debug_info);
return 1;
}
printf("printctl: %s\n", err[0] ? err : "timed out waiting for job");
char path[MAX_PATH_LEN];
char state[16];
if (find_job(job_id, path, sizeof(path), state, sizeof(state))) {
JobMeta current = {};
if (load_job_from_path(path, &current)) {
printf("current state: %s\n", state);
if (current.status_message[0]) printf("status: %s\n", current.status_message);
if (current.debug_info[0]) printf("detail: %s\n", current.debug_info);
}
}
return 1;
}
static int cmd_print(const ArgList& args) {
const char* file = nullptr;
const char* printer = nullptr;
const char* name = nullptr;
bool wait = false;
for (int i = 1; i < args.count; i++) {
if (strcmp(args.argv[i], "--printer") == 0 && i + 1 < args.count) {
printer = args.argv[++i];
} else if (strcmp(args.argv[i], "--name") == 0 && i + 1 < args.count) {
name = args.argv[++i];
} else if (strcmp(args.argv[i], "--wait") == 0) {
wait = true;
} else if (file == nullptr) {
file = args.argv[i];
} else {
usage();
return 1;
}
}
if (file == nullptr) {
usage();
return 1;
}
char job_id[48];
char err[128] = {};
if (!submit_document_job(file, printer, name, job_id, sizeof(job_id), err, sizeof(err))) {
printf("printctl: %s\n", err);
return 1;
}
return print_submission_result(job_id, wait);
}
static int cmd_test_page(const ArgList& args) {
const char* printer = nullptr;
bool wait = true;
for (int i = 1; i < args.count; i++) {
if (strcmp(args.argv[i], "--printer") == 0 && i + 1 < args.count) {
printer = args.argv[++i];
} else if (strcmp(args.argv[i], "--wait") == 0) {
wait = true;
} else if (strcmp(args.argv[i], "--no-wait") == 0) {
wait = false;
} else {
usage();
return 1;
}
}
char job_id[48];
char err[128] = {};
if (!submit_test_page_job(printer, job_id, sizeof(job_id), err, sizeof(err))) {
printf("printctl: %s\n", err);
return 1;
}
return print_submission_result(job_id, wait);
}
static int cmd_inspect(const char* job_id) {
char path[MAX_PATH_LEN];
char state[16];
if (!find_job(job_id, path, sizeof(path), state, sizeof(state))) {
printf("printctl: job %s not found\n", job_id);
return 1;
}
JobMeta job = {};
if (!load_job_from_path(path, &job)) {
printf("printctl: failed to load job %s\n", job_id);
return 1;
}
printf("Job ID: %s\n", job.id);
printf("State: %s\n", state);
printf("Name: %s\n", job.job_name);
if (job.user_name[0]) printf("User: %s\n", job.user_name);
if (job.printer_uri[0]) printf("Printer: %s\n", job.printer_uri);
if (job.doc_format[0]) printf("Format: %s\n", job.doc_format);
if (job.source_name[0]) printf("Source: %s\n", job.source_name);
if (job.created_at[0]) printf("Created: %s\n", job.created_at);
if (job.updated_at[0]) printf("Updated: %s\n", job.updated_at);
if (job.remote_job_id > 0) printf("Printer job id: %d\n", job.remote_job_id);
if (job.status_message[0]) printf("Status: %s\n", job.status_message);
if (job.debug_info[0]) printf("Debug: %s\n", job.debug_info);
return 0;
}
static int cmd_probe(const char* printer_uri) {
char uri_buf[MAX_PATH_LEN];
if (printer_uri && *printer_uri) {
safe_copy(uri_buf, sizeof(uri_buf), printer_uri);
} else if (!read_default_printer_uri(uri_buf, sizeof(uri_buf))) {
printf("printctl: no default printer is configured\n");
return 1;
}
char err[128] = {};
IppUri normalized = {};
if (!normalize_ipp_uri(uri_buf, &normalized, err, sizeof(err))) {
printf("printctl: %s\n", err);
return 1;
}
printf("Printer URI: %s\n", normalized.normalized);
print_host_details(normalized);
uint32_t ip = 0;
if (!resolve_host(normalized.host, &ip)) {
if (host_looks_like_mdns(normalized.host))
printf("Resolution: unresolved (.local/mDNS not supported yet)\n");
else
printf("Resolution: unresolved\n");
return 1;
}
normalized.ip = ip;
char ip_text[20];
format_ipv4(ip_text, sizeof(ip_text), ip);
printf("Resolution: ok (%s)\n", ip_text);
IppCapabilities caps = {};
if (!ipp_get_printer_capabilities(normalized.normalized, &caps, err, sizeof(err))) {
char detail[256] = {};
summarize_ipp_capabilities(&caps, detail, sizeof(detail));
printf("Probe failed: %s\n", err[0] ? err : "IPP probe failed");
if (detail[0]) printf("Detail: %s\n", detail);
return 1;
}
char detail[256] = {};
summarize_ipp_capabilities(&caps, detail, sizeof(detail));
printf("Probe: ok\n");
if (caps.printer_name[0]) printf("Printer name: %s\n", caps.printer_name);
printf("Supports PDF: %s\n", caps.supports_pdf ? "yes" : "no");
printf("Supports JPEG: %s\n", caps.supports_jpeg ? "yes" : "no");
printf("Supports text: %s\n", caps.supports_text ? "yes" : "no");
if (caps.preferred_format[0]) printf("Preferred format: %s\n", caps.preferred_format);
if (caps.default_format[0]) printf("Default format: %s\n", caps.default_format);
if (caps.supported_formats[0]) printf("Supported formats: %s\n", caps.supported_formats);
if (caps.status_message[0]) printf("Printer status: %s\n", caps.status_message);
if (detail[0]) printf("Detail: %s\n", detail);
return 0;
}
extern "C" void _start() {
ArgList args = {};
parse_args(&args);
if (args.count <= 0) {
usage();
montauk::exit(1);
}
int rc = 1;
if (strcmp(args.argv[0], "set-printer") == 0) {
if (args.count < 2) usage();
else rc = cmd_set_printer(args.argv[1]);
} else if (strcmp(args.argv[0], "show-printer") == 0) {
char uri[MAX_PATH_LEN];
if (read_default_printer_uri(uri, sizeof(uri))) {
printf("%s\n", uri);
rc = 0;
} else {
printf("printctl: no default printer is configured\n");
rc = 1;
}
} else if (strcmp(args.argv[0], "print") == 0) {
rc = cmd_print(args);
} else if (strcmp(args.argv[0], "test-page") == 0) {
rc = cmd_test_page(args);
} else if (strcmp(args.argv[0], "status") == 0) {
rc = cmd_status(args);
} else if (strcmp(args.argv[0], "inspect") == 0) {
if (args.count < 2) usage();
else rc = cmd_inspect(args.argv[1]);
} else if (strcmp(args.argv[0], "probe") == 0) {
rc = cmd_probe(args.count >= 2 ? args.argv[1] : nullptr);
} else {
usage();
rc = 1;
}
montauk::exit(rc);
}