summaryrefslogtreecommitdiffstats
path: root/unix
diff options
context:
space:
mode:
authorEmiliano Gavilán <Emiliano Gavilán>2024-05-27 23:31:19 (GMT)
committerEmiliano Gavilán <Emiliano Gavilán>2024-05-27 23:31:19 (GMT)
commit8940f0d3fee47a62a1b4ad9a744bacc418a7bde7 (patch)
treef2db0d52e714d3a398afeb3d1756bcb2e7e82230 /unix
parentb4c28d714f7ef525a86a5001b63f4be785090b88 (diff)
downloadtk-8940f0d3fee47a62a1b4ad9a744bacc418a7bde7.zip
tk-8940f0d3fee47a62a1b4ad9a744bacc418a7bde7.tar.gz
tk-8940f0d3fee47a62a1b4ad9a744bacc418a7bde7.tar.bz2
Printing on *nix/X11 using libcups API. Initial commit
Diffstat (limited to 'unix')
-rw-r--r--unix/Makefile.in6
-rw-r--r--unix/tkUnixInit.c1
-rw-r--r--unix/tkUnixInt.h1
-rw-r--r--unix/tkUnixPrint.c520
4 files changed, 526 insertions, 2 deletions
diff --git a/unix/Makefile.in b/unix/Makefile.in
index a157615..806a770 100644
--- a/unix/Makefile.in
+++ b/unix/Makefile.in
@@ -394,7 +394,8 @@ X11_OBJS = tkUnix.o tkUnix3d.o tkUnixButton.o tkUnixColor.o tkUnixConfig.o \
tkUnixCursor.o tkUnixDraw.o tkUnixEmbed.o tkUnixEvent.o tkIcu.o \
tkUnixFocus.o $(FONT_OBJS) tkUnixInit.o tkUnixKey.o tkUnixMenu.o \
tkUnixMenubu.o tkUnixScale.o tkUnixScrlbr.o tkUnixSelect.o \
- tkUnixSend.o tkUnixSysNotify.o tkUnixSysTray.o tkUnixWm.o tkUnixXId.o
+ tkUnixSend.o tkUnixSysNotify.o tkUnixSysTray.o tkUnixWm.o tkUnixXId.o \
+ tkUnixPrint.o
AQUA_OBJS = tkMacOSXBitmap.o tkMacOSXButton.o tkMacOSXClipboard.o \
tkMacOSXColor.o tkMacOSXConfig.o tkMacOSXCursor.o tkMacOSXDebug.o \
@@ -516,7 +517,8 @@ X11_SRCS = \
$(UNIX_DIR)/tkUnixScale.c $(UNIX_DIR)/tkUnixScrlbr.c \
$(UNIX_DIR)/tkUnixSelect.c $(UNIX_DIR)/tkUnixSend.c \
$(UNIX_DIR)/tkUnixSysNotify $(UNIX_DIR)/tkUnixSysTray.c \
- $(UNIX_DIR)/tkUnixWm.c $(UNIX_DIR)/tkUnixXId.c
+ $(UNIX_DIR)/tkUnixWm.c $(UNIX_DIR)/tkUnixXId.c \
+ $(UNIX_DIR)/tkUnixPrint.c
AQUA_SRCS = \
$(MAC_OSX_DIR)/tkMacOSXBitmap.c $(MAC_OSX_DIR)/tkMacOSXButton.c \
diff --git a/unix/tkUnixInit.c b/unix/tkUnixInit.c
index 34b67fc..c8acf22 100644
--- a/unix/tkUnixInit.c
+++ b/unix/tkUnixInit.c
@@ -44,6 +44,7 @@ TkpInit(
Tktray_Init(interp);
(void)SysNotify_Init (interp);
Icu_Init(interp);
+ Cups_Init(interp);
return TCL_OK;
}
diff --git a/unix/tkUnixInt.h b/unix/tkUnixInt.h
index 5429236..f46212e 100644
--- a/unix/tkUnixInt.h
+++ b/unix/tkUnixInt.h
@@ -26,6 +26,7 @@
MODULE_SCOPE int Tktray_Init (Tcl_Interp* interp);
MODULE_SCOPE int SysNotify_Init (Tcl_Interp* interp);
+MODULE_SCOPE int Cups_Init (Tcl_Interp* interp);
#endif /* _TKUNIXINT */
diff --git a/unix/tkUnixPrint.c b/unix/tkUnixPrint.c
new file mode 100644
index 0000000..f8640ea
--- /dev/null
+++ b/unix/tkUnixPrint.c
@@ -0,0 +1,520 @@
+/*
+ * tkUnixPrint.c --
+ *
+ * tkUnixPrint.c implements a "::tk::print::cups" Tcl command which
+ * interfaces the libcups2 API with the [tk print] command.
+ *
+ * Copyright © 2024 Emiliano Gavilán.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkUnixInt.h"
+
+#ifdef HAVE_CUPS
+#include <cups/cups.h>
+
+typedef int (CupsSubCmdOp)(Tcl_Interp *, int, Tcl_Obj *const []);
+
+static Tcl_ObjCmdProc Cups_Cmd;
+static CupsSubCmdOp DefaultPrinterOp;
+static CupsSubCmdOp GetPrintersOp;
+static CupsSubCmdOp PrintOp;
+static Tcl_ArgvGenFuncProc ParseEnumOptions;
+static Tcl_ArgvGenFuncProc ParseOptions;
+static Tcl_ArgvGenFuncProc ParseMargins;
+static Tcl_ArgvGenFuncProc ParseNup;
+static cups_dest_t* GetPrinterFromObj(Tcl_Obj *);
+
+static cups_dest_t *
+GetPrinterFromObj(Tcl_Obj *nameObj)
+{
+ cups_dest_t *printer;
+ int len;
+ const char *nameStr = Tcl_GetStringFromObj(nameObj, &len);
+ char *p;
+ char *name, *instance = NULL;
+ Tcl_DString ds;
+
+ Tcl_DStringInit(&ds);
+ name = Tcl_DStringAppend(&ds, nameStr, len);
+ p = strchr(name, '/');
+ if (p) {
+ *p = '\0';
+ instance = p+1;
+ }
+
+ printer = cupsGetNamedDest(CUPS_HTTP_DEFAULT, name, instance);
+ Tcl_DStringFree(&ds);
+
+ return printer;
+}
+
+static int
+Cups_Cmd(
+ TCL_UNUSED(ClientData),
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ static const struct CupsCmds {
+ const char *subcmd;
+ CupsSubCmdOp *subCmd;
+ } cupsCmds[] = {
+ {"defaultprinter" , DefaultPrinterOp},
+ {"getprinters" , GetPrintersOp},
+ {"print" , PrintOp},
+ {NULL, NULL}
+ };
+ int index;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?arg ...?");
+ return TCL_ERROR;
+ }
+
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], cupsCmds,
+ sizeof(struct CupsCmds), "subcommand", 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ return cupsCmds[index].subCmd(interp, objc, objv);
+}
+
+static int
+DefaultPrinterOp(
+ Tcl_Interp *interp,
+ TCL_UNUSED(int),
+ TCL_UNUSED(Tcl_Obj *const *))
+{
+ cups_dest_t *printer;
+ Tcl_Obj *resultObj;
+
+ printer = cupsGetNamedDest(CUPS_HTTP_DEFAULT, NULL, NULL);
+ if (printer) {
+ if (printer->instance) {
+ resultObj = Tcl_ObjPrintf("%s/%s", printer->name,
+ printer->instance);
+ } else {
+ resultObj = Tcl_NewStringObj(printer->name, -1);
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ }
+
+ cupsFreeDests(1, printer);
+ return TCL_OK;
+}
+
+static int
+GetPrintersOp(
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ cups_dest_t *dests;
+ cups_option_t *option;
+ int num_dests, i, j;
+ Tcl_Obj *keyPtr, *optPtr, *resultObj;
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ return TCL_ERROR;
+ }
+
+ num_dests = cupsGetDests2(CUPS_HTTP_DEFAULT, &dests);
+ resultObj = Tcl_NewObj();
+
+ for (i = 0; i < num_dests; i++) {
+ if (dests[i].instance)
+ keyPtr = Tcl_ObjPrintf("%s/%s", dests[i].name, dests[i].instance);
+ else
+ keyPtr = Tcl_NewStringObj(dests[i].name, -1);
+
+ option = dests[i].options;
+ optPtr = Tcl_NewObj();
+ for(j = 0; j < dests[i].num_options; j++) {
+ Tcl_DictObjPut(NULL, optPtr,
+ Tcl_NewStringObj(option[j].name, -1),
+ Tcl_NewStringObj(option[j].value, -1));
+ }
+
+ Tcl_DictObjPut(NULL, resultObj, keyPtr, optPtr);
+ }
+
+ cupsFreeDests(num_dests, dests);
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+}
+
+/* Information needed for parsing */
+struct CupsOptions {
+ const char *name;
+ const char *cupsName;
+};
+
+static const struct CupsOptions colormodeOpts[] = {
+ {"auto", CUPS_PRINT_COLOR_MODE_AUTO},
+ {"color", CUPS_PRINT_COLOR_MODE_COLOR},
+ {"monochrome", CUPS_PRINT_COLOR_MODE_MONOCHROME},
+ {NULL, NULL}
+};
+
+static const struct CupsOptions formatOpts[] = {
+ {"auto", CUPS_FORMAT_AUTO},
+ {"pdf", CUPS_FORMAT_PDF},
+ {"postscript", CUPS_FORMAT_POSTSCRIPT},
+ {"text", CUPS_FORMAT_TEXT},
+ {NULL, NULL}
+};
+
+static const struct CupsOptions mediaOpts[] = {
+ {"a4", CUPS_MEDIA_A4},
+ {"legal", CUPS_MEDIA_LEGAL},
+ {"letter", CUPS_MEDIA_LETTER},
+ {NULL, NULL}
+};
+
+static const struct CupsOptions orientationOpts[] = {
+ {"portrait", CUPS_ORIENTATION_PORTRAIT},
+ {"landscape", CUPS_ORIENTATION_LANDSCAPE},
+ {NULL, NULL}
+};
+
+enum {PARSECOLORMODE, PARSEFORMAT, PARSEMEDIA, PARSEORIENTATION};
+
+static const struct ParseData {
+ const char *message;
+ const struct CupsOptions *optionTable;
+} parseData[] = {
+ {"colormode", colormodeOpts},
+ {"format", formatOpts},
+ {"media", mediaOpts},
+ {"orientation", orientationOpts},
+ {NULL, NULL}
+};
+
+static int
+PrintOp(
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ cups_dest_t *printer;
+ cups_dinfo_t *info;
+ int result = TCL_OK;
+ int job_id;
+
+ /* variables for Tcl_ParseArgsObjv */
+ Tcl_Obj *const *parseObjv;
+ Tcl_Size count;
+
+ /* options related vaiables */
+ cups_option_t *options = NULL;
+ int num_options = 0;
+ int copies = 0, pprint = 0;
+ const char *media = NULL, *color = NULL, *orient = NULL, *format = NULL,
+ *nup = NULL, *title = NULL;
+ Tcl_Obj *marginsObj = NULL, *optionsObj = NULL;
+ double tzoom = 1.0;
+
+ /* Data to print
+ * this is a binary buffer, since it can contain data such as
+ * jpg or compressed pdf which might contain any bytes.
+ * USE [encoding convertto] with a proper encoding when passing
+ * text data to print.
+ */
+ const unsigned char *buffer; int buflen;
+
+ const Tcl_ArgvInfo argTable[] = {
+ {TCL_ARGV_GENFUNC, "-colormode", ParseEnumOptions, &color,
+ "color mode", (ClientData) &parseData[PARSECOLORMODE]},
+ {TCL_ARGV_INT , "-copies", NULL, &copies,
+ "number of copies", NULL},
+ {TCL_ARGV_GENFUNC, "-format", ParseEnumOptions, &format,
+ "data format", (ClientData) &parseData[PARSEFORMAT]},
+ {TCL_ARGV_GENFUNC, "-margins", ParseMargins, &marginsObj,
+ "media page size", NULL},
+ {TCL_ARGV_GENFUNC, "-media", ParseEnumOptions, &media,
+ "media page size", (ClientData) &parseData[PARSEMEDIA]},
+ {TCL_ARGV_GENFUNC, "-nup", ParseNup, &nup,
+ "pages per sheet", NULL},
+ {TCL_ARGV_GENFUNC, "-options", ParseOptions, &optionsObj,
+ "generic options", NULL},
+ {TCL_ARGV_GENFUNC, "-orientation", ParseEnumOptions, &orient,
+ "page orientation", (ClientData) &parseData[PARSEORIENTATION]},
+ {TCL_ARGV_CONSTANT, "-prettyprint", (void *)1, &pprint,
+ "print header", NULL},
+ {TCL_ARGV_STRING, "-title", NULL, &title,
+ "job title", NULL},
+ {TCL_ARGV_FLOAT, "-tzoom", NULL, &tzoom,
+ "text zoom", NULL},
+ TCL_ARGV_TABLE_END
+ };
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "printer data ?-opt arg ...?");
+ return TCL_ERROR;
+ }
+
+ printer = GetPrinterFromObj(objv[2]);
+ if (!printer) {
+ Tcl_SetObjResult(interp,
+ Tcl_ObjPrintf("unknown printer or class \"%s\"",
+ Tcl_GetString(objv[2])));
+ return TCL_ERROR;
+ }
+
+ /* T_PAO discards the first arg, but we have 4 before the options */
+ parseObjv = objv+3;
+ count = objc-3;
+
+ if (Tcl_ParseArgsObjv(interp, argTable, &count, parseObjv, NULL)!=TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ if (copies < 0 || copies > 100) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("copies must be an integer"
+ "between 0 and 100", -1));
+ cupsFreeDests(1, printer);
+ return TCL_ERROR;
+ }
+ if (tzoom < 0.5 || tzoom > 2.0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("tzoom must be a number"
+ "between 0.5 and 2.0", -1));
+ cupsFreeDests(1, printer);
+ return TCL_ERROR;
+ }
+
+/* Add options */
+ if (copies != 0) {
+ char copiesbuf[4];
+
+ snprintf(copiesbuf, 4, "%d", copies);
+ num_options = cupsAddOption(CUPS_COPIES, copiesbuf,
+ num_options, &options);
+ }
+ if (color) {
+ num_options = cupsAddOption(CUPS_PRINT_COLOR_MODE, color,
+ num_options, &options);
+ }
+ if (media) {
+ num_options = cupsAddOption(CUPS_MEDIA, media,
+ num_options, &options);
+ }
+ if (nup) {
+ num_options = cupsAddOption(CUPS_NUMBER_UP, nup,
+ num_options, &options);
+ }
+ if (orient) {
+ num_options = cupsAddOption(CUPS_ORIENTATION, orient,
+ num_options, &options);
+ }
+ if (pprint) {
+ num_options = cupsAddOption("prettyprint", "yes",
+ num_options, &options);
+ }
+ if (marginsObj) {
+ int n;
+ Tcl_Obj **listArr;
+
+ Tcl_ListObjGetElements(interp, marginsObj, &n, &listArr);
+ num_options = cupsAddOption("page-top", Tcl_GetString(listArr[0]),
+ num_options, &options);
+ num_options = cupsAddOption("page-left", Tcl_GetString(listArr[1]),
+ num_options, &options);
+ num_options = cupsAddOption("page-bottom", Tcl_GetString(listArr[2]),
+ num_options, &options);
+ num_options = cupsAddOption("page-right", Tcl_GetString(listArr[3]),
+ num_options, &options);
+ }
+ if (optionsObj) {
+ Tcl_DictSearch search;
+ int done = 0;
+ Tcl_Obj *key, *value;
+
+ for (Tcl_DictObjFirst(interp, optionsObj, &search, &key, &value, &done)
+ ; !done ; Tcl_DictObjNext(&search, &key, &value, &done))
+ {
+ num_options = cupsAddOption(Tcl_GetString(key),
+ Tcl_GetString(value), num_options, &options);
+ }
+ }
+ if (tzoom != 1.0) {
+ char cpibuf[TCL_DOUBLE_SPACE + 1];
+ char lpibuf[TCL_DOUBLE_SPACE + 1];
+
+ Tcl_PrintDouble(interp, 10.0 / tzoom, cpibuf);
+ Tcl_PrintDouble(interp, 6.0 / tzoom, lpibuf);
+ num_options = cupsAddOption("cpi", cpibuf,
+ num_options, &options);
+ num_options = cupsAddOption("lpi", lpibuf,
+ num_options, &options);
+ }
+
+ /* set title and format */
+ if (!title) {
+ title = "Tk print job";
+ }
+ if (!format) {
+ format = CUPS_FORMAT_AUTO;
+ }
+
+ info = cupsCopyDestInfo(CUPS_HTTP_DEFAULT, printer);
+
+ if (cupsCreateDestJob(CUPS_HTTP_DEFAULT, printer, info, &job_id,
+ title, num_options, options) != IPP_STATUS_OK) {
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Error creating job: \"%s\"",
+ cupsLastErrorString()));
+ result = TCL_ERROR;
+ goto cleanup;
+ }
+
+ buffer = Tcl_GetByteArrayFromObj(objv[3], &buflen);
+
+ if (cupsStartDestDocument(CUPS_HTTP_DEFAULT, printer, info, job_id,
+ "(stdin)", format, 0, NULL, 1) != HTTP_STATUS_CONTINUE) {
+ // Can't start document
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Error starting document: \"%s\"",
+ cupsLastErrorString()));
+ result = TCL_ERROR;
+ goto cleanup;
+ }
+
+ if (cupsWriteRequestData(CUPS_HTTP_DEFAULT,(char *) buffer, buflen) !=
+ HTTP_STATUS_CONTINUE) {
+ // some error ocurred
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Error writing data: \"%s\"",
+ cupsLastErrorString()));
+ result = TCL_ERROR;
+ goto cleanup;
+ }
+
+ if (cupsFinishDestDocument(CUPS_HTTP_DEFAULT, printer, info) ==
+ IPP_STATUS_OK) {
+ // all OK
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(job_id));
+ } else {
+ // some error ocurred
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Error finishing document: \"%s\"",
+ cupsLastErrorString()));
+ result = TCL_ERROR;
+ goto cleanup;
+ }
+
+cleanup:
+ cupsFreeDestInfo(info);
+ cupsFreeOptions(num_options, options);
+ cupsFreeDests(1, printer);
+ return result;
+}
+
+static int
+ParseEnumOptions(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ TCL_UNUSED(int),
+ Tcl_Obj *const *objv,
+ void *dstPtr)
+{
+ int index;
+ const char **dest = (const char **) dstPtr;
+ struct ParseData *pdata = (struct ParseData *)clientData;
+
+ if (Tcl_GetIndexFromObjStruct(interp, objv[0], pdata->optionTable,
+ sizeof(struct CupsOptions), pdata->message, 0, &index) != TCL_OK) {
+ return -1;
+ }
+
+ *dest = pdata->optionTable[index].cupsName;
+ return 1;
+}
+
+static int
+ParseOptions(
+ TCL_UNUSED(ClientData),
+ Tcl_Interp *interp,
+ TCL_UNUSED(int),
+ Tcl_Obj *const *objv,
+ void *dstPtr)
+{
+ Tcl_Obj **objPtr = (Tcl_Obj **) dstPtr;
+ int n;
+
+ /* check for a valid dictionary */
+ if (Tcl_DictObjSize(NULL, objv[0], &n) != TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("options must be a proper"
+ "dictionary", -1));
+ return -1;
+ }
+
+ *objPtr = objv[0];
+ return 1;
+}
+
+static int
+ParseMargins(
+ TCL_UNUSED(ClientData),
+ Tcl_Interp *interp,
+ TCL_UNUSED(int),
+ Tcl_Obj *const *objv,
+ void *dstPtr)
+{
+ Tcl_Obj **objPtr = (Tcl_Obj **) dstPtr;
+ Tcl_Obj **listArr;
+ int n;
+
+ if (Tcl_ListObjGetElements(NULL, objv[0], &n, &listArr) != TCL_OK ||
+ n != 4 ||
+ Tcl_GetIntFromObj(interp, listArr[0], &n) != TCL_OK ||
+ Tcl_GetIntFromObj(interp, listArr[1], &n) != TCL_OK ||
+ Tcl_GetIntFromObj(interp, listArr[2], &n) != TCL_OK ||
+ Tcl_GetIntFromObj(interp, listArr[3], &n) != TCL_OK
+ ) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("margins must be a list "
+ "of four integers: top left bottom right" , -1));
+ return -1;
+ }
+
+ *objPtr = objv[0];
+ return 1;
+}
+
+static int
+ParseNup(
+ TCL_UNUSED(ClientData),
+ Tcl_Interp *interp,
+ TCL_UNUSED(int),
+ Tcl_Obj *const *objv,
+ void *dstPtr)
+{
+ const char **nup = (const char **) dstPtr;
+ int n;
+
+ if (Tcl_GetIntFromObj(NULL, objv[0], &n) != TCL_OK ||
+ (n != 1 && n != 2 && n != 4 && n != 6 && n != 9 && n != 16)
+ ) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong number-up value: "
+ "should be 1, 2, 4, 6, 9 or 16", -1));
+ return -1;
+ }
+
+ *nup = Tcl_GetString(objv[0]);
+ return 1;
+}
+#endif /*HAVE_CUPS*/
+
+int
+Cups_Init(Tcl_Interp *interp)
+{
+#ifdef HAVE_CUPS
+ Tcl_Namespace *ns;
+ ns = Tcl_FindNamespace(interp, "::tk::print", NULL, TCL_GLOBAL_ONLY);
+ if (!ns)
+ ns = Tcl_CreateNamespace(interp, "::tk::print", NULL, NULL);
+ Tcl_CreateObjCommand(interp, "::tk::print::cups", Cups_Cmd, NULL, NULL);
+ Tcl_Export(interp, ns, "cups", 0);
+#endif
+ return TCL_OK;
+}