From 6fcbbe681268f59766f305d6b75dd9808b3915e8 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Sun, 22 Feb 2026 10:50:23 +0530 Subject: [PATCH 01/17] Add GitHub Actions workflow for build and test --- .github/workflows/build.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..42c68c9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: Build and Test + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + build-linux-run-tests: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: show Ubuntu version + run: cat /etc/os-release | grep PRETTY_NAME | awk -F '=' '{print $2}' + - name: update build environment + run: sudo apt-get update --fix-missing -y + - name: install prerequisites + run: | + sudo apt-get install autoconf autopoint libglib2.0-dev libdbus-1-dev libtool -y + - name: configure + env: + CC: /usr/bin/gcc + run: ./autogen.sh && ./configure + - name: make + run: make + - name: Run Tests + run: make check || cat test/error_log* From 5241b6941fd6064d1963a99c5d1c2b0019182339 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Mon, 23 Feb 2026 21:59:35 +0530 Subject: [PATCH 02/17] Add CodeQL analysis workflow This workflow runs CodeQL analysis on pushes and pull requests to the master branch, and it is scheduled to run weekly. --- .github/workflows/codeql.yml | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..05232b1 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,51 @@ +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: '0 7 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['cpp'] + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Update build environment + run: sudo apt-get update --fix-missing -y + + - name: Install prerequisite + run: | + sudo apt-get install autoconf autopoint libglib2.0-dev libdbus-1-dev libtool -y + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # config-file: ./.github/codeql.yml + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" From 7e48b8bdba4ccf0abb76ad354123bd74bdaf8e7f Mon Sep 17 00:00:00 2001 From: Souptik De Date: Sat, 7 Mar 2026 20:07:12 +0530 Subject: [PATCH 03/17] Add workflow_dispatch event to build.yml --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42c68c9..9016ed6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,8 @@ on: pull_request: branches: - '**' - + workflow_dispatch: + jobs: build-linux-run-tests: From b9193f5bb7d470d322f293a57f6b2b1c6d5d44fe Mon Sep 17 00:00:00 2001 From: Souptik De Date: Sat, 7 Mar 2026 20:07:44 +0530 Subject: [PATCH 04/17] Add workflow_dispatch trigger to CodeQL workflow --- .github/workflows/codeql.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 05232b1..6c83a7a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,7 +7,8 @@ on: branches: [master] schedule: - cron: '0 7 * * 6' - + workflow_dispatch: + jobs: analyze: name: Analyze From e0c89266d338eafa5cde5d93c65bf49ce872a37e Mon Sep 17 00:00:00 2001 From: Souptik De Date: Sat, 7 Mar 2026 20:21:07 +0530 Subject: [PATCH 05/17] Change CodeQL language from C++ to C --- .github/workflows/codeql.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c83a7a..8cba13a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - language: ['cpp'] + language: ['c'] steps: - name: Checkout repository @@ -40,7 +40,6 @@ jobs: uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - # config-file: ./.github/codeql.yml queries: +security-and-quality - name: Autobuild From 4fabfd96b5872824d8fdc55225c76a3a37c0b384 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Mon, 16 Mar 2026 22:30:51 +0530 Subject: [PATCH 06/17] Fix installation command formatting in build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9016ed6..64ab42b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: run: sudo apt-get update --fix-missing -y - name: install prerequisites run: | - sudo apt-get install autoconf autopoint libglib2.0-dev libdbus-1-dev libtool -y + sudo apt-get install -y autoconf autopoint libglib2.0-dev libdbus-1-dev libtool - name: configure env: CC: /usr/bin/gcc From 0c28254a8d0374fd21d898a481cfc0345edddbfe Mon Sep 17 00:00:00 2001 From: Souptik De Date: Mon, 16 Mar 2026 22:31:53 +0530 Subject: [PATCH 07/17] Fix installation command for prerequisites in codeql.yml --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8cba13a..4f2093e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: - name: Install prerequisite run: | - sudo apt-get install autoconf autopoint libglib2.0-dev libdbus-1-dev libtool -y + sudo apt-get install -y autoconf autopoint libglib2.0-dev libdbus-1-dev libtool - name: Initialize CodeQL uses: github/codeql-action/init@v4 From c3efef833c09ddf0535ee983b9f74e9d9b1a92ed Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 09:41:57 +0530 Subject: [PATCH 08/17] Rewrite cpdbPrintFD to always attempt the FD-passing path first (printFd D-Bus method) --- cpdb/cpdb-frontend.c | 136 ++++++++++++++++++-- cpdb/cpdb-frontend.h | 20 ++- cpdb/interface/org.openprinting.Backend.xml | 9 ++ 3 files changed, 152 insertions(+), 13 deletions(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 7a2c115..ad2506e 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -3,6 +3,7 @@ #include #include #include +#include static void fetchPrinterListFromBackend (cpdb_frontend_obj_t * frontend_obj, const char * backend); @@ -1201,6 +1202,124 @@ char *cpdbPrintFileWithJobTitle(cpdb_printer_obj_t *p, int cpdbPrintFD(cpdb_printer_obj_t *p, char **jobid, const char *title, char **socket_path) { + *jobid = NULL; + *socket_path = NULL; + + /* + * Always attempt the FD-passing path first (printFd D-Bus method). + * + * We call the method directly via + * g_dbus_proxy_call_with_unix_fd_list_sync() rather than the + * gdbus-codegen-generated print_backend_call_print_fd_sync() wrapper + * because the generated wrapper does not expose the GUnixFDList output + * parameter needed to receive a type-'h' return value. + */ + GError *error = NULL; + GUnixFDList *fd_list = NULL; + GVariant *out_params; + + cpdbDebugPrintSettings(p->settings); + + out_params = g_dbus_proxy_call_with_unix_fd_list_sync( + G_DBUS_PROXY(p->backend_proxy), + "printFd", + g_variant_new("(sia(ss)s)", + p->id, + p->settings->count, + cpdbSerializeToGVariant(p->settings), + title), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* no fds to send to backend */ + &fd_list, /* receive fd list from backend */ + NULL, + &error); + + if (error != NULL) + { + if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) + { + /* + * Backend does not implement printFd — it predates this + * feature and only has printSocket. Fall back to the legacy + * socket-file path. + */ + loginfo("cpdbPrintFD: backend %s does not support printFd " + "(%s) — falling back to printSocket\n", + p->backend_name, error->message); + g_error_free(error); + if (fd_list) g_object_unref(fd_list); + goto use_socket_path; + } + + /* + * Any other error case + */ + logerror("cpdbPrintFD: printFd failed on %s %s: %s\n", + p->id, p->backend_name, error->message); + g_error_free(error); + if (fd_list) g_object_unref(fd_list); + return -1; + } + + /* printFd succeeded , unpack jobid and fd index from the reply */ + gint fd_index; + g_variant_get(out_params, "(sh)", jobid, &fd_index); + g_variant_unref(out_params); + + if (*jobid == NULL || **jobid == '\0') + { + logerror("cpdbPrintFD: printFd returned empty job ID " + "on %s %s\n", p->id, p->backend_name); + if (fd_list) g_object_unref(fd_list); + return -1; + } + + if (fd_list == NULL) + { + logerror("cpdbPrintFD: printFd returned no fd list " + "on %s %s\n", p->id, p->backend_name); + g_free(*jobid); + *jobid = NULL; + return -1; + } + + /* + * g_unix_fd_list_get() dups the fd out of the list. + * The caller owns the returned fd and must close it after writing + * all print data. + */ + GError *fd_error = NULL; + int fd = g_unix_fd_list_get(fd_list, fd_index, &fd_error); + g_object_unref(fd_list); + + if (fd == -1) + { + logerror("cpdbPrintFD: failed to extract fd on %s %s: %s\n", + p->id, p->backend_name, + fd_error ? fd_error->message : "unknown error"); + if (fd_error) g_error_free(fd_error); + g_free(*jobid); + *jobid = NULL; + return -1; + } + + loginfo("cpdbPrintFD: FD path — job %s on %s %s fd=%d\n", + *jobid, p->id, p->backend_name, fd); + + /* + * socket_path stays NULL , callers must guard unlink() on this. + */ + cpdbSaveSettingsToDisk(p->settings); + return fd; + + //use_socket_path: + /* + * Legacy socket-file fallback. + * Reached only when the backend returned G_DBUS_ERROR_UNKNOWN_METHOD + * for printFd. This is the original cpdbPrintFD() implementation, + * unchanged in behaviour. + */ *socket_path = cpdbPrintSocket(p, jobid, title); if (*socket_path == NULL) { logerror("Error getting socket for job on %s %s: %s\n", @@ -1208,10 +1327,9 @@ int cpdbPrintFD(cpdb_printer_obj_t *p, return -1; } - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) { - logerror("Error creating fd for job %s on %s %s with socket %s: %s\n", - *jobid, p->id, p->backend_name, *socket_path, strerror(errno)); + int sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) { + logerror("cpdbPrintFD: socket() failed: %s\n", strerror(errno)); return -1; } @@ -1220,15 +1338,15 @@ int cpdbPrintFD(cpdb_printer_obj_t *p, server_addr.sun_family = AF_UNIX; strncpy(server_addr.sun_path, *socket_path, sizeof(server_addr.sun_path) - 1); - int res = connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + int res = connect(sfd, (struct sockaddr *)&server_addr,sizeof(server_addr)); if (res == -1) { - logerror("Error connecting to socket for %s on %s %s, socket %s: %s\n", - *jobid, p->id, p->backend_name, *socket_path, strerror(errno)); - close(fd); // Close the socket in case of an error + logerror("cpdbPrintFD: connect() failed on %s %s path=%s: %s\n", + p->id, p->backend_name, *socket_path, strerror(errno)); + close(sfd); return -1; } - return fd; + return sfd; } char *cpdbPrintSocket(cpdb_printer_obj_t *p, char **jobid, const char *title) diff --git a/cpdb/cpdb-frontend.h b/cpdb/cpdb-frontend.h index c790487..5cd35e0 100644 --- a/cpdb/cpdb-frontend.h +++ b/cpdb/cpdb-frontend.h @@ -509,13 +509,25 @@ char *cpdbPrintFileWithJobTitle(cpdb_printer_obj_t *p, const char *file_path, co /** * Print using a file descriptor, using the settings set previously. - * + * + * Always tries the FD-passing path first (printFd D-Bus method). + * This works across Snap/container sandbox boundaries because no socket + * file is involved. + * + * Falls back to the legacy socket-file path (printSocket) only when + * the backend returns G_DBUS_ERROR_UNKNOWN_METHOD, meaning it is an + * older backend that does not implement printFd. + * On success: + * returns an open, writable file descriptor ready for print data + * sets *jobid — job ID string, caller must g_free() + * sets *socket_path — socket file path (if socket fallback was used), + * NULL (if FD path was used) ; caller must + * g_free() + unlink() only when non-NULL * @param p Printer object * @param jobid Job ID * @param title Job title - * @param socket_path Socket path - * - * @return File descriptor + * @param socket_path socket path (fallback) or NULL (FD path) + * @return Open fd on success, -1 on failure */ int cpdbPrintFD(cpdb_printer_obj_t *p, char **jobid, const char *title, char **socket_path); diff --git a/cpdb/interface/org.openprinting.Backend.xml b/cpdb/interface/org.openprinting.Backend.xml index 471f5c5..a690b33 100644 --- a/cpdb/interface/org.openprinting.Backend.xml +++ b/cpdb/interface/org.openprinting.Backend.xml @@ -85,6 +85,15 @@ + + + + + + + + + From 3f7c1418da8bc7909d64bae88925e7cf8ca85854 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 10:43:41 +0530 Subject: [PATCH 09/17] Guard unlink(socket_path) and g_free(socket_path) so that it only excutes for socket fallback case --- cpdb/cpdb-frontend.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index ad2506e..7c10980 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -1183,8 +1183,14 @@ char *cpdbPrintFileWithJobTitle(cpdb_printer_obj_t *p, if (write(fd, buffer, bytesRead) != bytesRead) { fclose(file); close(fd); - unlink(socket_path); - g_free(socket_path); + /* + * socket_path is NULL when cpdbPrintFD used the FD path (printFd). + * Only unlink and free when it was populated by the socket fallback. + */ + if(socket_path != NULL) { + unlink(socket_path); + g_free(socket_path); + } logerror("Error sending file %s on %s %s: %s\n", file_path, p->id, p->backend_name, strerror(errno)); return NULL; @@ -1193,9 +1199,14 @@ char *cpdbPrintFileWithJobTitle(cpdb_printer_obj_t *p, fclose(file); close(fd); - unlink(socket_path); - g_free(socket_path); - + /* + * socket_path is NULL when cpdbPrintFD used the FD path (printFd). + * Only unlink and free when it was populated by the socket fallback. + */ + if (socket_path != NULL) { + unlink(socket_path); + g_free(socket_path); + } return jobid; } @@ -1317,8 +1328,7 @@ int cpdbPrintFD(cpdb_printer_obj_t *p, /* * Legacy socket-file fallback. * Reached only when the backend returned G_DBUS_ERROR_UNKNOWN_METHOD - * for printFd. This is the original cpdbPrintFD() implementation, - * unchanged in behaviour. + * for printFd. */ *socket_path = cpdbPrintSocket(p, jobid, title); if (*socket_path == NULL) { From 5283534860411dee56db9fced922b116f014b477 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 11:33:19 +0530 Subject: [PATCH 10/17] uncommented use_socket_path --- cpdb/cpdb-frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 7c10980..473760c 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -1324,7 +1324,7 @@ int cpdbPrintFD(cpdb_printer_obj_t *p, cpdbSaveSettingsToDisk(p->settings); return fd; - //use_socket_path: + use_socket_path: /* * Legacy socket-file fallback. * Reached only when the backend returned G_DBUS_ERROR_UNKNOWN_METHOD From abdd1d2e8d0a52568df32edef570d88e9314530b Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 13:42:15 +0530 Subject: [PATCH 11/17] Fix Gvariant format string --- cpdb/cpdb-frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 473760c..babbae4 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -1234,7 +1234,7 @@ int cpdbPrintFD(cpdb_printer_obj_t *p, out_params = g_dbus_proxy_call_with_unix_fd_list_sync( G_DBUS_PROXY(p->backend_proxy), "printFd", - g_variant_new("(sia(ss)s)", + g_variant_new("(si@a(ss)s)", p->id, p->settings->count, cpdbSerializeToGVariant(p->settings), From e70af0198695ef33561c530ea3f5f6d0e5281a7c Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Thu, 16 Jul 2026 23:32:46 +0530 Subject: [PATCH 12/17] Add capabilities handling: define structures and methods for all printer capabilities --- cpdb/cpdb-frontend.c | 150 ++++++++++++++++++++ cpdb/cpdb-frontend.h | 62 ++++++++ cpdb/interface/org.openprinting.Backend.xml | 11 ++ 3 files changed, 223 insertions(+) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index babbae4..01577d1 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -20,6 +20,12 @@ static void cpdbUnpackOptions (int int num_media, GVariant * media_var, cpdb_options_t * options); + +static void cpdbUnpackCapabilities (int num_capabilities, + GVariant * var, + int num_media, + GVariant * media_var, + cpdb_capabilities_t * capabilities); static GHashTable * cpdbUnpackTranslations (GVariant * translations); static void add_to_hash_table (gpointer key, gpointer value, @@ -1086,6 +1092,39 @@ cpdb_options_t *cpdbGetAllOptions(cpdb_printer_obj_t *p) return p->options; } +cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p) +{ + if (p == NULL) + { + logwarn("Invalid params: cpdbGetAllCapabilities()\n"); + return NULL; + } + + GError *error = NULL; + int num_capabilities, num_media; + GVariant *var, *media_var; + print_backend_call_get_all_capabilities_sync(p->backend_proxy, + p->id, + &num_capabilities, + &var, + &num_media, + &media_var, + NULL, + &error); + if (error) + { + logerror("Error getting printer capabilities for %s %s : %s\n", + p->id, p->backend_name, error->message); + return NULL; + } + + loginfo("Obtained %d capabilities and %d media for %s %s\n", + num_capabilities, num_media, p->id, p->backend_name); + cpdb_capabilities_t *caps = cpdbGetNewCapabilities(); + cpdbUnpackCapabilities(num_capabilities, var, num_media, media_var, caps); + return caps; +} + cpdb_option_t *cpdbGetOption(cpdb_printer_obj_t *p, const char *name) { @@ -2405,6 +2444,117 @@ void cpdbUnpackOptions(int num_options, g_variant_iter_free(iter); } +void cpdbUnpackCapabilities(int num_capabilities, + GVariant *caps_var, + int num_media, + GVariant *media_var, + cpdb_capabilities_t *capabilities) +{ + cpdb_capability_t *cap; + cpdb_media_t *media; + int i, j, num, width, length, l, r, t, b, type, range_lower, range_upper; + GVariantIter *iter, *sub_iter; + char *str, *name, *def, *group; + + capabilities->count = num_capabilities; + g_variant_get(caps_var, "a(ssisia(s)ii)", &iter); + i = 0; + while (g_variant_iter_loop(iter, "(ssisia(s)ii)", + &name, &group, &type, &def, &num, &sub_iter, &range_lower, &range_upper)) + { + if (i >= num_capabilities) + { + logwarn("array of capabilities contains more than expected amount"); + g_free(name); + g_free(group); + g_free(def); + g_variant_iter_free(sub_iter); + break; + } + + cap = g_new0(cpdb_capability_t, 1); + logdebug("name=%s;\n", name); + cap->option_name = g_strdup(name); + logdebug("group=%s;\n", group); + cap->group_name = g_strdup(group); + logdebug("type=%d;\n", type); + cap->type = type; + logdebug("default=%s;\n", def); + cap->default_value = g_strdup(def); + logdebug("num_choices=%d;\n", num); + cap->num_supported = num; + logdebug("range_lower=%d; range_upper=%d;\n", range_lower, range_upper); + cap->range_lower = range_lower; + cap->range_upper = range_upper; + logdebug("choices:\n"); + cap->supported_values = cpdbNewCStringArray(num); + + j = 0; + while (g_variant_iter_loop(sub_iter, "(s)", &str)) + { + if (j >= num) + { + logwarn("array of values contains more than expected amount"); + g_free(str); + break; + } + + logdebug(" %s;\n", str); + cap->supported_values[j] = g_strdup(str); + j++; + } + g_hash_table_insert(capabilities->table, g_strdup(cap->option_name), cap); + i++; + } + g_variant_iter_free(iter); + + capabilities->media_count = num_media; + g_variant_get(media_var, "a(siiia(iiii))", &iter); + i = 0; + while (g_variant_iter_loop(iter, "(siiia(iiii))", + &name, &width, &length, &num, &sub_iter)) + { + if (i >= num_media) + { + logwarn("array of media contains more than expected amount"); + g_free(name); + g_variant_iter_free(sub_iter); + break; + } + + media = g_new0(cpdb_media_t, 1); + logdebug("name=%s;\n", name); + media->name = g_strdup(name); + logdebug("width=%d;\n", width); + media->width = width; + logdebug("length=%d;\n", length); + media->length = length; + logdebug("num_margins=%d;\n", num); + media->num_margins = num; + media->margins = g_new0(cpdb_margin_t, num); + + j = 0; + while (g_variant_iter_loop(sub_iter, "(iiii)", &l, &r, &t, &b)) + { + if (j >= num) + { + logwarn("array of margins contains more than expected amount"); + break; + } + + logdebug(" %d,%d,%d,%d;\n", l, r, t, b); + media->margins[j].left = l; + media->margins[j].right = r; + media->margins[j].top = t; + media->margins[j].bottom = b; + j++; + } + g_hash_table_insert(capabilities->media, g_strdup(media->name), media); + i++; + } + g_variant_iter_free(iter); +} + static GHashTable *cpdbUnpackTranslations (GVariant *variant) { GVariantIter iter; diff --git a/cpdb/cpdb-frontend.h b/cpdb/cpdb-frontend.h index 5cd35e0..0ba5f25 100644 --- a/cpdb/cpdb-frontend.h +++ b/cpdb/cpdb-frontend.h @@ -35,6 +35,8 @@ typedef struct cpdb_printer_obj_s cpdb_printer_obj_t; typedef struct cpdb_settings_s cpdb_settings_t; typedef struct cpdb_options_s cpdb_options_t; typedef struct cpdb_option_s cpdb_option_t; +typedef struct cpdb_capabilities_s cpdb_capabilities_t; +typedef struct cpdb_capability_s cpdb_capability_t; typedef struct cpdb_margin_s cpdb_margin_t; typedef struct cpdb_media_s cpdb_media_t; @@ -444,6 +446,15 @@ char *cpdbGetState(cpdb_printer_obj_t *printer_obj); */ cpdb_options_t *cpdbGetAllOptions(cpdb_printer_obj_t *printer_obj); +/** + * Get all the different capabilities with type information for a printer. + * + * @param printer_obj Printer object + * + * @return Capabilities struct + */ +cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *printer_obj); + /** * Get a single cpdb_option_t struct corresponding to an option name for a printer. * @@ -876,6 +887,14 @@ struct cpdb_options_s GHashTable *media; /**[name] --> cpdb_media_t struct**/ }; +struct cpdb_capabilities_s +{ + int count; + int media_count; + GHashTable *table; /**[name] --> cpdb_capability_t struct**/ + GHashTable *media; /**[name] --> cpdb_media_t struct**/ +}; + /** * Get an empty cpdb_options_t struct with no 'options' in it * @@ -890,6 +909,26 @@ cpdb_options_t *cpdbGetNewOptions(); */ void cpdbDeleteOptions(cpdb_options_t *); +/************************************************************************************************/ +/** +______________________________________ cpdb_capabilities_t __________________________________________ + +**/ + +/** + * Get an empty cpdb_capabilities_t struct with no 'capabilities' in it + * + * @return Capabilities object + */ +cpdb_capabilities_t *cpdbGetNewCapabilities(); + +/** + * Free up a capabilities object. + * + * @param capabilities Capabilities object + */ +void cpdbDeleteCapabilities(cpdb_capabilities_t *); + /************************************************************************************************/ /** ______________________________________ cpdb_option_t __________________________________________ @@ -911,6 +950,29 @@ void cpdbDeleteOption(cpdb_option_t *); /************************************************************************************************/ +/** +______________________________________ cpdb_capability_t __________________________________________ + +**/ +struct cpdb_capability_s +{ + char *option_name; + char *group_name; + int type; /* 0=boolean,1=integer,2=range,3=enum,4=keyword,5=resolution,6=string */ + char *default_value; + int num_supported; + char **supported_values; + int range_lower; /* only valid when type == 2 (range) */ + int range_upper; /* only valid when type == 2 (range) */ +}; + +/** + * @param cap Capability object + */ +void cpdbDeleteCapability(cpdb_capability_t *); + +/************************************************************************************************/ + /** ______________________________________ cpdb_margin_t __________________________________________ diff --git a/cpdb/interface/org.openprinting.Backend.xml b/cpdb/interface/org.openprinting.Backend.xml index a690b33..ce89d21 100644 --- a/cpdb/interface/org.openprinting.Backend.xml +++ b/cpdb/interface/org.openprinting.Backend.xml @@ -72,6 +72,17 @@ + + + + + + + + From 776bbe364547c24f3efd69aeaa892758525d2e37 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 17 Jul 2026 20:51:41 +0530 Subject: [PATCH 13/17] Add capabilities management: implement functions to create and delete printer capabilities * cpdbGetNewCapabilities() - creates new capabilities struct with hash tables * cpdbDeleteCapabilities() - frees capabilities struct and hash tables * cpdbDeleteCapability() - frees individual capability struct and its arrays --- cpdb/cpdb-frontend.c | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 01577d1..5cf0f98 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -2296,6 +2296,56 @@ void cpdbDeleteOptions(cpdb_options_t *opts) free(opts); } +/**************cpdb_capabilities_t************************************/ +cpdb_capabilities_t *cpdbGetNewCapabilities() +{ + cpdb_capabilities_t *c = g_new0(cpdb_capabilities_t, 1); + c->count = 0; + c->table = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) cpdbDeleteCapability); + c->media_count = 0; + c->media = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) cpdbDeleteMedia); + return c; +} + +void cpdbDeleteCapabilities(cpdb_capabilities_t *caps) +{ + if (caps == NULL) + return; + + if (caps->table) + g_hash_table_destroy(caps->table); + if (caps->media) + g_hash_table_destroy(caps->media); + free(caps); +} + +void cpdbDeleteCapability(cpdb_capability_t *cap) +{ + if (cap == NULL) + return; + + if (cap->option_name) + free(cap->option_name); + if (cap->group_name) + free(cap->group_name); + if (cap->supported_values) + { + for (int i = 0; i < cap->num_supported; i++) + free(cap->supported_values[i]); + free(cap->supported_values); + } + if (cap->default_value) + free(cap->default_value); + + free(cap); +} + /**************cpdb_option_t************************************/ void cpdbDeleteOption(cpdb_option_t *opt) From 0768425686709190baa99115750d9c446aad9253 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sun, 19 Jul 2026 21:57:00 +0530 Subject: [PATCH 14/17] Enhance capabilities handling: add conversion function and update capability type definitions making New frontend + old backend compatible --- cpdb/cpdb-frontend.c | 68 ++++++++++++++++++++++++++++++++++++++++++-- cpdb/cpdb-frontend.h | 11 ++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 5cf0f98..bac62fb 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -28,8 +28,9 @@ static void cpdbUnpackCapabilities (int cpdb_capabilities_t * capabilities); static GHashTable * cpdbUnpackTranslations (GVariant * translations); static void add_to_hash_table (gpointer key, - gpointer value, - gpointer user_data); + gpointer value, + gpointer user_data); +static cpdb_capabilities_t *options_to_capabilities (cpdb_options_t * opts); /** ________________________________________________ cpdb_frontend_obj_t __________________________________________ @@ -1113,8 +1114,24 @@ cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p) &error); if (error) { + if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) + { + loginfo("cpdbGetAllCapabilities: backend %s does not support GetAllCapabilities " + "(%s) — falling back to GetAllOptions\n", + p->backend_name, error->message); + g_error_free(error); + cpdb_options_t *opts = cpdbGetAllOptions(p); + if (!opts) + { + logerror("cpdbGetAllCapabilities: fallback GetAllOptions also failed for %s %s\n", + p->id, p->backend_name); + return NULL; + } + return options_to_capabilities(opts); + } logerror("Error getting printer capabilities for %s %s : %s\n", p->id, p->backend_name, error->message); + g_error_free(error); return NULL; } @@ -2384,6 +2401,53 @@ void cpdbDeleteMedia(cpdb_media_t *media) free(media); } +/**************cpdb_option_t -> cpdb_capability_t conversion********************/ + +static cpdb_capabilities_t *options_to_capabilities(cpdb_options_t *opts) +{ + cpdb_capabilities_t *caps = cpdbGetNewCapabilities(); + + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init(&iter, opts->table); + while (g_hash_table_iter_next(&iter, &key, &value)) + { + cpdb_option_t *opt = (cpdb_option_t *)value; + cpdb_capability_t *cap = g_new0(cpdb_capability_t, 1); + cap->option_name = g_strdup(opt->option_name); + cap->group_name = g_strdup(opt->group_name); + cap->type = CPDB_CAP_UNKNOWN; + cap->default_value = g_strdup(opt->default_value); + cap->num_supported = opt->num_supported; + cap->supported_values = cpdbNewCStringArray(cap->num_supported); + for (int j = 0; j < cap->num_supported; j++) + cap->supported_values[j] = g_strdup(opt->supported_values[j]); + cap->range_lower = 0; + cap->range_upper = 0; + g_hash_table_insert(caps->table, g_strdup(cap->option_name), cap); + caps->count++; + } + + g_hash_table_iter_init(&iter, opts->media); + while (g_hash_table_iter_next(&iter, &key, &value)) + { + cpdb_media_t *m = (cpdb_media_t *)value; + cpdb_media_t *cm = g_new0(cpdb_media_t, 1); + cm->name = g_strdup(m->name); + cm->width = m->width; + cm->length = m->length; + cm->num_margins = m->num_margins; + cm->margins = g_new0(cpdb_margin_t, cm->num_margins); + for (int j = 0; j < cm->num_margins; j++) + cm->margins[j] = m->margins[j]; + g_hash_table_insert(caps->media, g_strdup(cm->name), cm); + caps->media_count++; + } + + return caps; +} + /** * ________________________________utility functions__________________________ */ diff --git a/cpdb/cpdb-frontend.h b/cpdb/cpdb-frontend.h index 0ba5f25..b06f6fd 100644 --- a/cpdb/cpdb-frontend.h +++ b/cpdb/cpdb-frontend.h @@ -958,7 +958,7 @@ struct cpdb_capability_s { char *option_name; char *group_name; - int type; /* 0=boolean,1=integer,2=range,3=enum,4=keyword,5=resolution,6=string */ + int type; /* 0=boolean,1=integer,2=range,3=enum,4=keyword,5=resolution,6=string,7=unknown */ char *default_value; int num_supported; char **supported_values; @@ -966,6 +966,15 @@ struct cpdb_capability_s int range_upper; /* only valid when type == 2 (range) */ }; +#define CPDB_CAP_BOOLEAN 0 +#define CPDB_CAP_INTEGER 1 +#define CPDB_CAP_RANGE 2 +#define CPDB_CAP_ENUM 3 +#define CPDB_CAP_KEYWORD 4 +#define CPDB_CAP_RESOLUTION 5 +#define CPDB_CAP_STRING 6 +#define CPDB_CAP_UNKNOWN 7 + /** * @param cap Capability object */ From 9c1c1c6b33875861716670728a1d870cb4d305e7 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sat, 25 Jul 2026 17:35:00 +0530 Subject: [PATCH 15/17] GetAllCapabilities: add locale support, human-readable strings, and capability type enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D-Bus XML: Add locale input arg to GetAllCapabilities; change output signature from a(ssisia(s)ii) to a(sssisia(ss)ii) — inserts human-readable name after option name, human-readable group after group name, and changes supported-values array from a(s) to a(ss) (value + human-readable label pair per choice). cpdb-frontend.h: Replace #define constants with cpdb_capability_type_t enum; expand cpdb_capability_s with human_readable_name, human_readable_group, human_readable_choices fields; add locale param to cpdbGetAllCapabilities(). cpdb-frontend.c: Thread locale through to D-Bus call; update cpdbUnpackCapabilities for new (sssisia(ss)ii) variant format; update cpdbDeleteCapability to free new fields; populate human-readable fields in options_to_capabilities() fallback path. --- cpdb/cpdb-frontend.c | 46 +++++++++++++++++---- cpdb/cpdb-frontend.h | 36 ++++++++++------ cpdb/interface/org.openprinting.Backend.xml | 14 ++++--- 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index bac62fb..9cb11f8 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -1093,7 +1093,8 @@ cpdb_options_t *cpdbGetAllOptions(cpdb_printer_obj_t *p) return p->options; } -cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p) +cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p, + const char *locale) { if (p == NULL) { @@ -1101,11 +1102,13 @@ cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p) return NULL; } + const char *lang = locale ? locale : ""; GError *error = NULL; int num_capabilities, num_media; GVariant *var, *media_var; print_backend_call_get_all_capabilities_sync(p->backend_proxy, p->id, + lang, &num_capabilities, &var, &num_media, @@ -2349,14 +2352,24 @@ void cpdbDeleteCapability(cpdb_capability_t *cap) if (cap->option_name) free(cap->option_name); + if (cap->human_readable_name) + free(cap->human_readable_name); if (cap->group_name) free(cap->group_name); + if (cap->human_readable_group) + free(cap->human_readable_group); if (cap->supported_values) { for (int i = 0; i < cap->num_supported; i++) free(cap->supported_values[i]); free(cap->supported_values); } + if (cap->human_readable_choices) + { + for (int i = 0; i < cap->num_supported; i++) + free(cap->human_readable_choices[i]); + free(cap->human_readable_choices); + } if (cap->default_value) free(cap->default_value); @@ -2416,13 +2429,21 @@ static cpdb_capabilities_t *options_to_capabilities(cpdb_options_t *opts) cpdb_option_t *opt = (cpdb_option_t *)value; cpdb_capability_t *cap = g_new0(cpdb_capability_t, 1); cap->option_name = g_strdup(opt->option_name); + /* No locale-aware translation available from the old GetAllOptions + * path; echo raw name as best-effort human-readable fallback. */ + cap->human_readable_name = g_strdup(opt->option_name); cap->group_name = g_strdup(opt->group_name); + cap->human_readable_group = g_strdup(opt->group_name); cap->type = CPDB_CAP_UNKNOWN; cap->default_value = g_strdup(opt->default_value); cap->num_supported = opt->num_supported; cap->supported_values = cpdbNewCStringArray(cap->num_supported); + cap->human_readable_choices = cpdbNewCStringArray(cap->num_supported); for (int j = 0; j < cap->num_supported; j++) + { cap->supported_values[j] = g_strdup(opt->supported_values[j]); + cap->human_readable_choices[j] = g_strdup(opt->supported_values[j]); + } cap->range_lower = 0; cap->range_upper = 0; g_hash_table_insert(caps->table, g_strdup(cap->option_name), cap); @@ -2568,19 +2589,23 @@ void cpdbUnpackCapabilities(int num_capabilities, cpdb_media_t *media; int i, j, num, width, length, l, r, t, b, type, range_lower, range_upper; GVariantIter *iter, *sub_iter; - char *str, *name, *def, *group; + char *str, *name, *human_name, *group, *human_group, *def, *choice_label; capabilities->count = num_capabilities; - g_variant_get(caps_var, "a(ssisia(s)ii)", &iter); + g_variant_get(caps_var, "a(sssisia(ss)ii)", &iter); i = 0; - while (g_variant_iter_loop(iter, "(ssisia(s)ii)", - &name, &group, &type, &def, &num, &sub_iter, &range_lower, &range_upper)) + while (g_variant_iter_loop(iter, "(sssisia(ss)ii)", + &name, &human_name, &group, &human_group, + &type, &def, &num, &sub_iter, + &range_lower, &range_upper)) { if (i >= num_capabilities) { logwarn("array of capabilities contains more than expected amount"); g_free(name); + g_free(human_name); g_free(group); + g_free(human_group); g_free(def); g_variant_iter_free(sub_iter); break; @@ -2589,8 +2614,12 @@ void cpdbUnpackCapabilities(int num_capabilities, cap = g_new0(cpdb_capability_t, 1); logdebug("name=%s;\n", name); cap->option_name = g_strdup(name); + logdebug("human_name=%s;\n", human_name); + cap->human_readable_name = g_strdup(human_name); logdebug("group=%s;\n", group); cap->group_name = g_strdup(group); + logdebug("human_group=%s;\n", human_group); + cap->human_readable_group = g_strdup(human_group); logdebug("type=%d;\n", type); cap->type = type; logdebug("default=%s;\n", def); @@ -2602,19 +2631,22 @@ void cpdbUnpackCapabilities(int num_capabilities, cap->range_upper = range_upper; logdebug("choices:\n"); cap->supported_values = cpdbNewCStringArray(num); + cap->human_readable_choices = cpdbNewCStringArray(num); j = 0; - while (g_variant_iter_loop(sub_iter, "(s)", &str)) + while (g_variant_iter_loop(sub_iter, "(ss)", &str, &choice_label)) { if (j >= num) { logwarn("array of values contains more than expected amount"); g_free(str); + g_free(choice_label); break; } - logdebug(" %s;\n", str); + logdebug(" %s / %s;\n", str, choice_label); cap->supported_values[j] = g_strdup(str); + cap->human_readable_choices[j] = g_strdup(choice_label); j++; } g_hash_table_insert(capabilities->table, g_strdup(cap->option_name), cap); diff --git a/cpdb/cpdb-frontend.h b/cpdb/cpdb-frontend.h index b06f6fd..62cc82a 100644 --- a/cpdb/cpdb-frontend.h +++ b/cpdb/cpdb-frontend.h @@ -450,10 +450,13 @@ cpdb_options_t *cpdbGetAllOptions(cpdb_printer_obj_t *printer_obj); * Get all the different capabilities with type information for a printer. * * @param printer_obj Printer object + * @param locale BCP47 language tag for human-readable strings, + * or NULL to use raw option/choice names * * @return Capabilities struct */ -cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *printer_obj); +cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *printer_obj, + const char *locale); /** * Get a single cpdb_option_t struct corresponding to an option name for a printer. @@ -954,27 +957,34 @@ void cpdbDeleteOption(cpdb_option_t *); ______________________________________ cpdb_capability_t __________________________________________ **/ +typedef enum cpdb_capability_type_e { + CPDB_CAP_BOOLEAN, + CPDB_CAP_INTEGER, + CPDB_CAP_RANGE, + CPDB_CAP_ENUM, + CPDB_CAP_KEYWORD, + CPDB_CAP_RESOLUTION, + CPDB_CAP_STRING, + CPDB_CAP_UNKNOWN /* set only by the GetAllOptions fallback path, + never returned by a backend directly */ +} cpdb_capability_type_t; + struct cpdb_capability_s { char *option_name; + char *human_readable_name; char *group_name; - int type; /* 0=boolean,1=integer,2=range,3=enum,4=keyword,5=resolution,6=string,7=unknown */ + char *human_readable_group; + cpdb_capability_type_t type; char *default_value; int num_supported; char **supported_values; - int range_lower; /* only valid when type == 2 (range) */ - int range_upper; /* only valid when type == 2 (range) */ + char **human_readable_choices; /* parallel array, same length + as supported_values */ + int range_lower; + int range_upper; }; -#define CPDB_CAP_BOOLEAN 0 -#define CPDB_CAP_INTEGER 1 -#define CPDB_CAP_RANGE 2 -#define CPDB_CAP_ENUM 3 -#define CPDB_CAP_KEYWORD 4 -#define CPDB_CAP_RESOLUTION 5 -#define CPDB_CAP_STRING 6 -#define CPDB_CAP_UNKNOWN 7 - /** * @param cap Capability object */ diff --git a/cpdb/interface/org.openprinting.Backend.xml b/cpdb/interface/org.openprinting.Backend.xml index ce89d21..6d225f5 100644 --- a/cpdb/interface/org.openprinting.Backend.xml +++ b/cpdb/interface/org.openprinting.Backend.xml @@ -74,12 +74,16 @@ + - - + + From 4ecabbb43eba8d48005861a73ec416c6da92919f Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sun, 26 Jul 2026 19:06:30 +0530 Subject: [PATCH 16/17] Add English-fallback translation retry in frontend-side functions All translation-fallback logic is now in cpdb-frontend.c, requiring ZERO backend-side changes. Four paths covered: - cpdbGetOptionTranslation: if backend returns the raw option_name, retry D-Bus call with locale 'en' - cpdbGetChoiceTranslation: same pattern using choice_name - cpdbGetGroupTranslation: same pattern using group_name - cpdbGetAllTranslations: fetch English table, patch entries where value == raw key suffix and English differs - cpdbGetAllCapabilities: second GetAllCapabilities D-Bus call with 'en', patch untranslated human_readable_name/group/choices from the English result No new public API, no ABI changes, no backend opt-in needed. --- cpdb/cpdb-frontend.c | 182 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 9cb11f8..8e29049 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -5,6 +5,8 @@ #include #include +#define CPDB_FALLBACK_LOCALE "en" + static void fetchPrinterListFromBackend (cpdb_frontend_obj_t * frontend_obj, const char * backend); @@ -1142,6 +1144,73 @@ cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p, num_capabilities, num_media, p->id, p->backend_name); cpdb_capabilities_t *caps = cpdbGetNewCapabilities(); cpdbUnpackCapabilities(num_capabilities, var, num_media, media_var, caps); + + if (strcmp(lang, CPDB_FALLBACK_LOCALE) != 0) + { + int en_num_caps, en_num_media; + GVariant *en_var, *en_media_var; + GError *en_error = NULL; + print_backend_call_get_all_capabilities_sync(p->backend_proxy, + p->id, + CPDB_FALLBACK_LOCALE, + &en_num_caps, + &en_var, + &en_num_media, + &en_media_var, + NULL, + &en_error); + if (!en_error) + { + cpdb_capabilities_t *en_caps = cpdbGetNewCapabilities(); + cpdbUnpackCapabilities(en_num_caps, en_var, en_num_media, + en_media_var, en_caps); + + GHashTableIter iter; + gpointer key, cap_ptr; + g_hash_table_iter_init(&iter, caps->table); + while (g_hash_table_iter_next(&iter, &key, &cap_ptr)) + { + cpdb_capability_t *cap = (cpdb_capability_t *)cap_ptr; + cpdb_capability_t *en_cap = g_hash_table_lookup(en_caps->table, cap->option_name); + if (!en_cap) + continue; + if (cap->human_readable_name + && strcmp(cap->human_readable_name, cap->option_name) == 0 + && en_cap->human_readable_name + && strcmp(en_cap->human_readable_name, cap->option_name) != 0) + { + g_free(cap->human_readable_name); + cap->human_readable_name = g_strdup(en_cap->human_readable_name); + } + if (cap->human_readable_group + && cap->group_name + && strcmp(cap->human_readable_group, cap->group_name) == 0 + && en_cap->human_readable_group + && strcmp(en_cap->human_readable_group, cap->group_name) != 0) + { + g_free(cap->human_readable_group); + cap->human_readable_group = g_strdup(en_cap->human_readable_group); + } + for (int j = 0; j < cap->num_supported && j < en_cap->num_supported; j++) + { + if (cap->human_readable_choices[j] + && cap->supported_values[j] + && strcmp(cap->human_readable_choices[j], cap->supported_values[j]) == 0 + && en_cap->human_readable_choices[j] + && en_cap->supported_values[j] + && strcmp(en_cap->supported_values[j], cap->supported_values[j]) == 0 + && strcmp(en_cap->human_readable_choices[j], cap->supported_values[j]) != 0) + { + g_free(cap->human_readable_choices[j]); + cap->human_readable_choices[j] = g_strdup(en_cap->human_readable_choices[j]); + } + } + } + cpdbDeleteCapabilities(en_caps); + } + if (en_error) + g_error_free(en_error); + } return caps; } @@ -1693,6 +1762,31 @@ char *cpdbGetOptionTranslation(cpdb_printer_obj_t *p, p->id, p->backend_name, error->message); return NULL; } + + if (translation && strcmp(translation, option_name) == 0 + && strcmp(locale, CPDB_FALLBACK_LOCALE) != 0) + { + g_free(translation); + translation = NULL; + GError *retry_error = NULL; + char *en = NULL; + print_backend_call_get_option_translation_sync(p->backend_proxy, + p->id, + option_name, + CPDB_FALLBACK_LOCALE, + &en, + NULL, + &retry_error); + if (!retry_error && en && strcmp(en, option_name) != 0) + translation = en; + else + { + if (retry_error) + g_error_free(retry_error); + g_free(en); + translation = g_strdup(option_name); + } + } logdebug("Obtained translation=%s; for option=%s;locale=%s;printer=%s#%s;\n", translation, option_name, locale, p->id, p->backend_name); @@ -1744,6 +1838,32 @@ char *cpdbGetChoiceTranslation(cpdb_printer_obj_t *p, p->id, p->backend_name, error->message); return NULL; } + + if (translation && strcmp(translation, choice_name) == 0 + && strcmp(locale, CPDB_FALLBACK_LOCALE) != 0) + { + g_free(translation); + translation = NULL; + GError *retry_error = NULL; + char *en = NULL; + print_backend_call_get_choice_translation_sync(p->backend_proxy, + p->id, + option_name, + choice_name, + CPDB_FALLBACK_LOCALE, + &en, + NULL, + &retry_error); + if (!retry_error && en && strcmp(en, choice_name) != 0) + translation = en; + else + { + if (retry_error) + g_error_free(retry_error); + g_free(en); + translation = g_strdup(choice_name); + } + } logdebug("Obtained translation=%s; for option=%s;choice=%s;locale=%s;printer=%s#%s;\n", translation, option_name, choice_name, locale, @@ -1793,6 +1913,31 @@ char *cpdbGetGroupTranslation(cpdb_printer_obj_t *p, p->id, p->backend_name, error->message); return NULL; } + + if (translation && strcmp(translation, group_name) == 0 + && strcmp(locale, CPDB_FALLBACK_LOCALE) != 0) + { + g_free(translation); + translation = NULL; + GError *retry_error = NULL; + char *en = NULL; + print_backend_call_get_group_translation_sync(p->backend_proxy, + p->id, + group_name, + CPDB_FALLBACK_LOCALE, + &en, + NULL, + &retry_error); + if (!retry_error && en && strcmp(en, group_name) != 0) + translation = en; + else + { + if (retry_error) + g_error_free(retry_error); + g_free(en); + translation = g_strdup(group_name); + } + } logdebug("Obtained translation=%s; for group=%s;locale=%s;printer=%s#%s;\n", translation, group_name, locale, p->id, p->backend_name); @@ -1831,6 +1976,43 @@ void cpdbGetAllTranslations(cpdb_printer_obj_t *p, cpdbDeleteTranslations(p); p->locale = g_strdup(locale); p->translations = cpdbUnpackTranslations(translations); + + if (strcmp(locale, CPDB_FALLBACK_LOCALE) != 0) + { + GError *en_error = NULL; + GVariant *en_variant = NULL; + print_backend_call_get_all_translations_sync(p->backend_proxy, + p->id, + CPDB_FALLBACK_LOCALE, + &en_variant, + NULL, + &en_error); + if (!en_error && en_variant) + { + GHashTable *en_table = cpdbUnpackTranslations(en_variant); + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init(&iter, p->translations); + while (g_hash_table_iter_next(&iter, &key, &value)) + { + const char *ks = (const char *)key; + const char *vs = (const char *)value; + const char *raw = strrchr(ks, '#'); + if (!raw || raw[1] == '\0') + continue; + raw++; + if (strcmp(vs, raw) != 0) + continue; + const char *en_val = g_hash_table_lookup(en_table, ks); + if (en_val && strcmp(en_val, raw) != 0) + g_hash_table_insert(p->translations, + g_strdup(ks), g_strdup(en_val)); + } + g_hash_table_destroy(en_table); + } + if (en_error) + g_error_free(en_error); + } } //To be used with cpdbGetAllTranslations only From 48dbd8126ded27f8c45aec0960d6ab69118210df Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Thu, 30 Jul 2026 18:47:03 +0530 Subject: [PATCH 17/17] GetAllCapabilities: add human_readable_group to capabilities, add human_readable_name to media - Fix capabilities D-Bus signature: a(sssisia(ss)ii) -> a(ssssisia(ss)ii) (was missing human_readable_group as 4th leading string) - Change comment to match the 4-string field order - Add separate cpdb_capability_media_t struct with human_readable_name field so GetAllCapabilities media entries carry a display name while GetAllOptions' cpdb_media_t stays unchanged - Update cpdbUnpackCapabilities to parse a(ssiiia(iiii)) format - Wire cpdbDeleteCapabilityMedia into cpdbGetNewCapabilities hash table - Extend English-fallback retry to also patch media human readable names --- cpdb/cpdb-frontend.c | 61 +++++++++++++++++---- cpdb/cpdb-frontend.h | 25 ++++++++- cpdb/interface/org.openprinting.Backend.xml | 7 ++- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/cpdb/cpdb-frontend.c b/cpdb/cpdb-frontend.c index 8e29049..061330f 100644 --- a/cpdb/cpdb-frontend.c +++ b/cpdb/cpdb-frontend.c @@ -1206,6 +1206,25 @@ cpdb_capabilities_t *cpdbGetAllCapabilities(cpdb_printer_obj_t *p, } } } + + GHashTableIter media_iter; + gpointer media_key, media_ptr; + g_hash_table_iter_init(&media_iter, caps->media); + while (g_hash_table_iter_next(&media_iter, &media_key, &media_ptr)) + { + cpdb_capability_media_t *m = (cpdb_capability_media_t *)media_ptr; + cpdb_capability_media_t *en_m = g_hash_table_lookup(en_caps->media, m->name); + if (!en_m) + continue; + if (m->human_readable_name + && strcmp(m->human_readable_name, m->name) == 0 + && en_m->human_readable_name + && strcmp(en_m->human_readable_name, m->name) != 0) + { + g_free(m->human_readable_name); + m->human_readable_name = g_strdup(en_m->human_readable_name); + } + } cpdbDeleteCapabilities(en_caps); } if (en_error) @@ -2509,9 +2528,9 @@ cpdb_capabilities_t *cpdbGetNewCapabilities() (GDestroyNotify) cpdbDeleteCapability); c->media_count = 0; c->media = g_hash_table_new_full(g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) cpdbDeleteMedia); + g_str_equal, + g_free, + (GDestroyNotify) cpdbDeleteCapabilityMedia); return c; } @@ -2596,6 +2615,21 @@ void cpdbDeleteMedia(cpdb_media_t *media) free(media); } +void cpdbDeleteCapabilityMedia(cpdb_capability_media_t *media) +{ + if (media == NULL) + return; + + if (media->name) + free(media->name); + if (media->human_readable_name) + free(media->human_readable_name); + if (media->margins) + free(media->margins); + + free(media); +} + /**************cpdb_option_t -> cpdb_capability_t conversion********************/ static cpdb_capabilities_t *options_to_capabilities(cpdb_options_t *opts) @@ -2636,8 +2670,9 @@ static cpdb_capabilities_t *options_to_capabilities(cpdb_options_t *opts) while (g_hash_table_iter_next(&iter, &key, &value)) { cpdb_media_t *m = (cpdb_media_t *)value; - cpdb_media_t *cm = g_new0(cpdb_media_t, 1); + cpdb_capability_media_t *cm = g_new0(cpdb_capability_media_t, 1); cm->name = g_strdup(m->name); + cm->human_readable_name = g_strdup(m->name); cm->width = m->width; cm->length = m->length; cm->num_margins = m->num_margins; @@ -2768,15 +2803,16 @@ void cpdbUnpackCapabilities(int num_capabilities, cpdb_capabilities_t *capabilities) { cpdb_capability_t *cap; - cpdb_media_t *media; + cpdb_capability_media_t *media; int i, j, num, width, length, l, r, t, b, type, range_lower, range_upper; GVariantIter *iter, *sub_iter; char *str, *name, *human_name, *group, *human_group, *def, *choice_label; + char *media_human_name; capabilities->count = num_capabilities; - g_variant_get(caps_var, "a(sssisia(ss)ii)", &iter); + g_variant_get(caps_var, "a(ssssisia(ss)ii)", &iter); i = 0; - while (g_variant_iter_loop(iter, "(sssisia(ss)ii)", + while (g_variant_iter_loop(iter, "(ssssisia(ss)ii)", &name, &human_name, &group, &human_group, &type, &def, &num, &sub_iter, &range_lower, &range_upper)) @@ -2837,22 +2873,25 @@ void cpdbUnpackCapabilities(int num_capabilities, g_variant_iter_free(iter); capabilities->media_count = num_media; - g_variant_get(media_var, "a(siiia(iiii))", &iter); + g_variant_get(media_var, "a(ssiiia(iiii))", &iter); i = 0; - while (g_variant_iter_loop(iter, "(siiia(iiii))", - &name, &width, &length, &num, &sub_iter)) + while (g_variant_iter_loop(iter, "(ssiiia(iiii))", + &name, &media_human_name, &width, &length, &num, &sub_iter)) { if (i >= num_media) { logwarn("array of media contains more than expected amount"); g_free(name); + g_free(media_human_name); g_variant_iter_free(sub_iter); break; } - media = g_new0(cpdb_media_t, 1); + media = g_new0(cpdb_capability_media_t, 1); logdebug("name=%s;\n", name); media->name = g_strdup(name); + logdebug("human_name=%s;\n", media_human_name); + media->human_readable_name = g_strdup(media_human_name); logdebug("width=%d;\n", width); media->width = width; logdebug("length=%d;\n", length); diff --git a/cpdb/cpdb-frontend.h b/cpdb/cpdb-frontend.h index 62cc82a..e8abc16 100644 --- a/cpdb/cpdb-frontend.h +++ b/cpdb/cpdb-frontend.h @@ -39,6 +39,7 @@ typedef struct cpdb_capabilities_s cpdb_capabilities_t; typedef struct cpdb_capability_s cpdb_capability_t; typedef struct cpdb_margin_s cpdb_margin_t; typedef struct cpdb_media_s cpdb_media_t; +typedef struct cpdb_capability_media_s cpdb_capability_media_t; typedef enum cpdb_printer_update_e { CPDB_CHANGE_PRINTER_ADDED, @@ -895,7 +896,7 @@ struct cpdb_capabilities_s int count; int media_count; GHashTable *table; /**[name] --> cpdb_capability_t struct**/ - GHashTable *media; /**[name] --> cpdb_media_t struct**/ + GHashTable *media; /**[name] --> cpdb_capability_media_t struct**/ }; /** @@ -1027,6 +1028,28 @@ struct cpdb_media_s */ void cpdbDeleteMedia(cpdb_media_t *media); +/************************************************************************************************/ +/** +______________________________________ cpdb_capability_media_t __________________________________________ +**/ + +struct cpdb_capability_media_s +{ + char *name; + char *human_readable_name; + int width; + int length; + int num_margins; + cpdb_margin_t *margins; +}; + +/** + * Free up a capability media-size object. + * + * @param media Capability media-size object + */ +void cpdbDeleteCapabilityMedia(cpdb_capability_media_t *media); + #ifdef __cplusplus } #endif diff --git a/cpdb/interface/org.openprinting.Backend.xml b/cpdb/interface/org.openprinting.Backend.xml index 6d225f5..05966c9 100644 --- a/cpdb/interface/org.openprinting.Backend.xml +++ b/cpdb/interface/org.openprinting.Backend.xml @@ -76,7 +76,7 @@ - + - + +