/* * main.cpp * MontaukOS print control utility * Copyright (c) 2026 Daniel Hammer */ #include extern "C" { #include #include #include } using namespace print; static void usage() { printf("Usage:\n"); printf(" printctl set-printer \n"); printf(" printctl show-printer\n"); printf(" printctl print [--printer ] [--name ] [--wait]\n"); printf(" printctl test-page [--printer ] [--wait] [--no-wait]\n"); printf(" printctl status [--verbose]\n"); printf(" printctl inspect \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_probe_state_summary(const ProbeState& probe, bool verbose) { if (!probe.printer_uri[0] && !probe.message[0]) return; printf("Last probe: %s", probe.ok ? "ok" : "failed"); if (probe.probed_at[0]) printf(" at %s", probe.probed_at); printf("\n"); if (!verbose) return; if (probe.ok && probe.caps.printer_name[0]) printf("Printer name: %s\n", probe.caps.printer_name); else if (probe.message[0]) printf("Probe status: %s\n", probe.message); if (probe.ok) { printf("Supports PDF: %s\n", probe.caps.supports_pdf ? "yes" : "no"); printf("Supports JPEG: %s\n", probe.caps.supports_jpeg ? "yes" : "no"); printf("Supports text: %s\n", probe.caps.supports_text ? "yes" : "no"); if (probe.caps.preferred_format[0]) printf("Preferred format: %s\n", probe.caps.preferred_format); if (probe.caps.default_format[0]) printf("Default format: %s\n", probe.caps.default_format); if (probe.caps.supported_formats[0]) printf("Supported formats: %s\n", probe.caps.supported_formats); if (probe.caps.status_message[0]) printf("Printer status: %s\n", probe.caps.status_message); } if (probe.detail[0]) printf("Probe detail: %s\n", probe.detail); } 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.copies > 1) printf(" copies: %d\n", job.copies); 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"); if (uri[0]) { ProbeState probe = {}; if (load_printer_probe_state(&probe) && strcmp(probe.printer_uri, uri) == 0) print_probe_state_summary(probe, verbose); } 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, ¤t)) { 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; } uint32_t ip = 0; if (resolve_host(normalized.host, &ip)) normalized.ip = ip; printf("Printer URI: %s\n", normalized.normalized); print_host_details(normalized); if (!normalized.ip) { if (host_looks_like_mdns(normalized.host)) printf("Resolution: unresolved (.local/mDNS not supported yet)\n"); else printf("Resolution: unresolved\n"); } else { char ip_text[20]; format_ipv4(ip_text, sizeof(ip_text), normalized.ip); printf("Resolution: ok (%s)\n", ip_text); } ProbeState probe = {}; if (!probe_printer_uri(normalized.normalized, &probe, err, sizeof(err))) { printf("Probe failed: %s\n", probe.message[0] ? probe.message : (err[0] ? err : "IPP probe failed")); if (probe.detail[0]) printf("Detail: %s\n", probe.detail); return 1; } printf("Probe: ok\n"); if (probe.caps.printer_name[0]) printf("Printer name: %s\n", probe.caps.printer_name); printf("Supports PDF: %s\n", probe.caps.supports_pdf ? "yes" : "no"); printf("Supports JPEG: %s\n", probe.caps.supports_jpeg ? "yes" : "no"); printf("Supports text: %s\n", probe.caps.supports_text ? "yes" : "no"); if (probe.caps.preferred_format[0]) printf("Preferred format: %s\n", probe.caps.preferred_format); if (probe.caps.default_format[0]) printf("Default format: %s\n", probe.caps.default_format); if (probe.caps.supported_formats[0]) printf("Supported formats: %s\n", probe.caps.supported_formats); if (probe.caps.status_message[0]) printf("Printer status: %s\n", probe.caps.status_message); if (probe.detail[0]) printf("Detail: %s\n", probe.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); }