feat: printer UX improvements

This commit is contained in:
2026-04-01 22:47:27 +02:00
parent 550d4d2a19
commit 045f3ee0a5
12 changed files with 1138 additions and 269 deletions
+2 -1
View File
@@ -47,6 +47,7 @@ CXXFLAGS := \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-I ../../lib/bearssl/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
@@ -63,7 +64,7 @@ LDFLAGS := \
# ---- Source files ----
SRCS := main.cpp render.cpp diskdetail.cpp stb_truetype_impl.cpp
SRCS := main.cpp render.cpp diskdetail.cpp printers.cpp stb_truetype_impl.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ----
+44 -21
View File
@@ -29,11 +29,27 @@ static constexpr int TOOLBAR_H = 36;
static constexpr int CAT_H = 28;
static constexpr int ITEM_H = 24;
static constexpr int MAX_DEVS = 64;
static constexpr int MAX_VIRTUAL_DEVS = 4;
static constexpr int MAX_TOTAL_DEVS = MAX_DEVS + MAX_VIRTUAL_DEVS;
static constexpr int POLL_MS = 2000;
static constexpr int INDENT = 28;
static constexpr int FONT_SIZE = 18;
static constexpr int NUM_CATEGORIES = 9;
enum DeviceCategory {
CAT_CPU = 0,
CAT_INTERRUPT,
CAT_TIMER,
CAT_INPUT,
CAT_USB,
CAT_NETWORK,
CAT_DISPLAY,
CAT_STORAGE,
CAT_PCI,
CAT_AUDIO,
CAT_ACPI,
CAT_PRINTER,
NUM_CATEGORIES
};
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
@@ -48,27 +64,33 @@ static constexpr Color ACCENT_COLOR = Color::from_rgb(0x33, 0x66, 0xCC);
// ============================================================================
static const char* category_names[] = {
"CPU", // 0
"Interrupt", // 1
"Timer", // 2
"Input", // 3
"USB", // 4
"Network", // 5
"Display", // 6
"Storage", // 7
"PCI", // 8
"CPU",
"Interrupt",
"Timer",
"Input",
"USB",
"Network",
"Display",
"Storage",
"PCI",
"Audio",
"ACPI",
"Printers",
};
static const Color category_colors[] = {
Color::from_rgb(0x33, 0x66, 0xCC), // CPU - blue
Color::from_rgb(0x88, 0x44, 0xAA), // Interrupt - purple
Color::from_rgb(0x22, 0x88, 0x22), // Timer - green
Color::from_rgb(0xCC, 0x88, 0x00), // Input - amber
Color::from_rgb(0x00, 0x88, 0x88), // USB - teal
Color::from_rgb(0xCC, 0x55, 0x22), // Network - orange
Color::from_rgb(0x44, 0x66, 0xCC), // Display - indigo
Color::from_rgb(0x99, 0x55, 0x00), // Storage - brown
Color::from_rgb(0x66, 0x66, 0x66), // PCI - gray
Color::from_rgb(0x33, 0x66, 0xCC),
Color::from_rgb(0x88, 0x44, 0xAA),
Color::from_rgb(0x22, 0x88, 0x22),
Color::from_rgb(0xCC, 0x88, 0x00),
Color::from_rgb(0x00, 0x88, 0x88),
Color::from_rgb(0xCC, 0x55, 0x22),
Color::from_rgb(0x44, 0x66, 0xCC),
Color::from_rgb(0x99, 0x55, 0x00),
Color::from_rgb(0x66, 0x66, 0x66),
Color::from_rgb(0x8A, 0x5A, 0xCC),
Color::from_rgb(0x55, 0x77, 0x88),
Color::from_rgb(0x2F, 0x8B, 0x57),
};
// ============================================================================
@@ -83,7 +105,7 @@ struct DisplayRow {
int dev_index;
};
static constexpr int MAX_DISPLAY_ROWS = MAX_DEVS + NUM_CATEGORIES;
static constexpr int MAX_DISPLAY_ROWS = MAX_TOTAL_DEVS + NUM_CATEGORIES;
// ============================================================================
// Disk detail window
@@ -110,7 +132,7 @@ struct DiskDetailState {
// ============================================================================
struct DevExplorerState {
Montauk::DevInfo devs[MAX_DEVS];
Montauk::DevInfo devs[MAX_TOTAL_DEVS];
int dev_count;
bool collapsed[NUM_CATEGORIES];
int selected_row;
@@ -142,6 +164,7 @@ void render(uint32_t* pixels);
// ============================================================================
int build_display_rows(DevExplorerState* de, DisplayRow* rows);
int append_printer_devices(Montauk::DevInfo* out, int max_count);
// ============================================================================
// Function declarations — diskdetail.cpp
+18 -5
View File
@@ -17,6 +17,21 @@ int g_win_h = INIT_H;
DevExplorerState g_state;
TrueTypeFont* g_font = nullptr;
static void refresh_devices(DevExplorerState* de) {
if (de == nullptr) return;
int kernel_count = montauk::devlist(de->devs, MAX_DEVS);
if (kernel_count < 0) kernel_count = 0;
if (kernel_count > MAX_DEVS) kernel_count = MAX_DEVS;
int printer_count = append_printer_devices(
de->devs + kernel_count, MAX_TOTAL_DEVS - kernel_count);
if (printer_count < 0) printer_count = 0;
de->dev_count = kernel_count + printer_count;
de->last_poll_ms = montauk::get_milliseconds();
}
// ============================================================================
// Display row building
// ============================================================================
@@ -92,7 +107,7 @@ static bool handle_list_click(int mx, int my) {
bool is_double = (de.last_click_row == i)
&& (now - de.last_click_ms < 400);
if (is_double && de.devs[di].category == 7) {
if (is_double && de.devs[di].category == CAT_STORAGE) {
int port = (int)de.devs[di]._pad[0];
open_disk_detail(port, de.devs[di].name);
de.last_click_row = -1;
@@ -201,8 +216,7 @@ extern "C" void _start() {
}
// Initial device poll
g_state.dev_count = montauk::devlist(g_state.devs, MAX_DEVS);
g_state.last_poll_ms = montauk::get_milliseconds();
refresh_devices(&g_state);
WsWindow win;
if (!win.create("Devices", INIT_W, INIT_H))
@@ -221,8 +235,7 @@ extern "C" void _start() {
// Poll for device refresh
uint64_t now = montauk::get_milliseconds();
if (now - g_state.last_poll_ms >= POLL_MS) {
g_state.dev_count = montauk::devlist(g_state.devs, MAX_DEVS);
g_state.last_poll_ms = now;
refresh_devices(&g_state);
redraw_main = true;
}
+90
View File
@@ -0,0 +1,90 @@
/*
* printers.cpp
* Virtual printer-device registration for the MontaukOS Device Explorer
* Copyright (c) 2026 Daniel Hammer
*/
#include "devexplorer.h"
#include <print/print.hpp>
using namespace print;
static void format_printer_detail(const ProbeState& probe,
const IppUri* normalized,
char* out, int out_len) {
if (out == nullptr || out_len <= 0) return;
out[0] = '\0';
const char* host = nullptr;
if (probe.caps.host[0]) host = probe.caps.host;
else if (normalized && normalized->host[0]) host = normalized->host;
if (probe.ok) {
if (host && *host)
snprintf(out, (size_t)out_len, "Ready on %s", host);
else
snprintf(out, (size_t)out_len, "Ready");
return;
}
if (probe.message[0]) {
if (strstr(probe.message, "resolve"))
snprintf(out, (size_t)out_len, "Host resolution failed");
else if (strstr(probe.message, "mDNS"))
snprintf(out, (size_t)out_len, "mDNS host not supported");
else if (strstr(probe.message, "Unsupported format"))
snprintf(out, (size_t)out_len, "Unsupported format");
else
safe_copy(out, out_len, probe.message);
return;
}
if (host && *host)
snprintf(out, (size_t)out_len, "Awaiting probe on %s", host);
else
snprintf(out, (size_t)out_len, "Awaiting spooler probe");
}
int append_printer_devices(Montauk::DevInfo* out, int max_count) {
if (out == nullptr || max_count <= 0) return 0;
char current_uri[MAX_PATH_LEN] = {};
bool has_current_uri = read_default_printer_uri(current_uri, sizeof(current_uri));
ProbeState probe = {};
bool has_probe = load_printer_probe_state(&probe);
if (has_probe && has_current_uri && !montauk::streq(probe.printer_uri, current_uri))
has_probe = false;
const char* effective_uri = nullptr;
if (has_current_uri) effective_uri = current_uri;
else if (has_probe && probe.printer_uri[0]) effective_uri = probe.printer_uri;
else return 0;
IppUri normalized = {};
char uri_err[128] = {};
bool normalized_ok = normalize_ipp_uri(effective_uri, &normalized, uri_err, sizeof(uri_err));
Montauk::DevInfo dev = {};
dev.category = CAT_PRINTER;
if (has_probe && probe.caps.printer_name[0]) {
safe_copy(dev.name, sizeof(dev.name), probe.caps.printer_name);
} else if (normalized_ok && normalized.host[0]) {
safe_copy(dev.name, sizeof(dev.name), normalized.host);
} else {
safe_copy(dev.name, sizeof(dev.name), "Configured Printer");
}
if (has_probe) {
format_printer_detail(probe, normalized_ok ? &normalized : nullptr,
dev.detail, sizeof(dev.detail));
} else if (normalized_ok && normalized.host[0]) {
snprintf(dev.detail, sizeof(dev.detail), "Awaiting probe on %s", normalized.host);
} else {
safe_copy(dev.detail, sizeof(dev.detail), "Awaiting spooler probe");
}
out[0] = dev;
return 1;
}
+55 -29
View File
@@ -37,6 +37,31 @@ static void print_host_details(const IppUri& uri) {
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);
@@ -94,7 +119,7 @@ static int cmd_status(const ArgList& args) {
}
}
char uri[MAX_PATH_LEN];
char uri[MAX_PATH_LEN] = {};
if (read_default_printer_uri(uri, sizeof(uri))) {
printf("Default printer: %s\n", uri);
if (verbose) {
@@ -110,6 +135,12 @@ static int cmd_status(const ArgList& args) {
} 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);
@@ -278,44 +309,39 @@ static int cmd_probe(const char* printer_uri) {
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);
uint32_t ip = 0;
if (!resolve_host(normalized.host, &ip)) {
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;
}
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);
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;
}
+60
View File
@@ -15,6 +15,9 @@ extern "C" {
using namespace print;
static constexpr uint64_t PROBE_REFRESH_OK_MS = 60000;
static constexpr uint64_t PROBE_REFRESH_FAIL_MS = 15000;
static void log_msg(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
@@ -65,6 +68,56 @@ static bool write_pid_file() {
return write_text_file_atomic(SPOOL_DAEMON_PID_PATH, pid);
}
static void auto_probe_configured_printer(char* last_uri, int last_uri_len,
uint64_t* last_probe_ms, bool* last_probe_ok,
bool force = false) {
if (last_uri == nullptr || last_probe_ms == nullptr || last_probe_ok == nullptr) return;
char current_uri[MAX_PATH_LEN] = {};
bool have_uri = read_default_printer_uri(current_uri, sizeof(current_uri));
uint64_t now = montauk::get_milliseconds();
if (!have_uri || current_uri[0] == '\0') {
bool had_cached_uri = last_uri[0] != '\0';
if (had_cached_uri || file_exists(SPOOL_PROBE_STATE_PATH)) {
clear_printer_probe_state();
if (had_cached_uri) log_msg("cleared printer probe state (no default printer configured)");
}
last_uri[0] = '\0';
*last_probe_ms = 0;
*last_probe_ok = false;
return;
}
bool uri_changed = strcmp(last_uri, current_uri) != 0;
uint64_t refresh_ms = *last_probe_ok ? PROBE_REFRESH_OK_MS : PROBE_REFRESH_FAIL_MS;
if (!force && !uri_changed && *last_probe_ms != 0 && now - *last_probe_ms < refresh_ms)
return;
ProbeState state = {};
char err[128] = {};
bool ok = probe_printer_uri(current_uri, &state, err, sizeof(err));
if (!save_printer_probe_state(&state))
log_msg("warning: failed to persist printer probe state for %s", current_uri);
if (ok) {
if (state.caps.printer_name[0])
log_msg("auto-probed printer %s (%s)", state.printer_uri, state.caps.printer_name);
else
log_msg("auto-probed printer %s", state.printer_uri);
if (state.detail[0]) log_msg("probe detail: %s", state.detail);
} else {
log_msg("auto-probe failed for %s: %s",
state.printer_uri[0] ? state.printer_uri : current_uri,
state.message[0] ? state.message : (err[0] ? err : "probe failed"));
if (state.detail[0]) log_msg("probe detail: %s", state.detail);
}
safe_copy(last_uri, last_uri_len, state.printer_uri[0] ? state.printer_uri : current_uri);
*last_probe_ms = now;
*last_probe_ok = ok;
}
static void requeue_stale_active_jobs() {
DIR* dir = opendir(SPOOL_ACTIVE_DIR);
if (!dir) return;
@@ -260,9 +313,16 @@ extern "C" void _start() {
}
requeue_stale_active_jobs();
char last_probed_uri[MAX_PATH_LEN] = {};
uint64_t last_probe_ms = 0;
bool last_probe_ok = false;
auto_probe_configured_printer(last_probed_uri, sizeof(last_probed_uri),
&last_probe_ms, &last_probe_ok, true);
log_msg("ready");
for (;;) {
auto_probe_configured_printer(last_probed_uri, sizeof(last_probed_uri),
&last_probe_ms, &last_probe_ok);
char active_path[MAX_PATH_LEN];
if (claim_next_job(active_path, sizeof(active_path))) {
process_job(active_path);
File diff suppressed because it is too large Load Diff