From 59f2aef1020c69910bcf0b3c3785c5b28879c9f8 Mon Sep 17 00:00:00 2001 From: maelstrom Date: Thu, 15 May 2025 21:51:27 +0200 Subject: [PATCH] refactor: use wine's parsing function for better parity --- run-applet/run-applet.pro | 7 +- run-applet/runapplet.cpp | 30 +++- run-applet/runapplet.h | 2 +- run-applet/utils.c | 284 ++++++++++++++++++++++++++++++++++++++ run-applet/utils.h | 8 ++ run-applet/utils.hpp | 9 ++ 6 files changed, 333 insertions(+), 7 deletions(-) create mode 100644 run-applet/utils.c create mode 100644 run-applet/utils.h create mode 100644 run-applet/utils.hpp diff --git a/run-applet/run-applet.pro b/run-applet/run-applet.pro index 86cbf6b..4803d56 100644 --- a/run-applet/run-applet.pro +++ b/run-applet/run-applet.pro @@ -10,10 +10,13 @@ CONFIG += c++17 SOURCES += \ main.cpp \ - runapplet.cpp + runapplet.cpp \ + utils.c HEADERS += \ - runapplet.h + runapplet.h \ + utils.h \ + utils.hpp FORMS += \ runapplet.ui diff --git a/run-applet/runapplet.cpp b/run-applet/runapplet.cpp index 4607218..36b656c 100644 --- a/run-applet/runapplet.cpp +++ b/run-applet/runapplet.cpp @@ -1,10 +1,12 @@ #include "runapplet.h" #include "ui_runapplet.h" +#include "utils.hpp" #include #include #include #include +#include RunApplet::RunApplet(QWidget *parent) : QDialog(parent) @@ -25,7 +27,19 @@ RunApplet::RunApplet(QWidget *parent) qApp->quit(); }); - connect(ui->okBtn, &QPushButton::clicked, this, &RunApplet::runApp); + connect(ui->okBtn, &QPushButton::clicked, this, [&]() { + runApp(ui->comboBox->currentText()); + }); + + connect(ui->browseBtn, &QPushButton::clicked, this, [&]() { + QString file = QFileDialog::getOpenFileName(this, tr("Browse"), "", tr("All Files (*)")); + if (file == "") return; + ui->comboBox->lineEdit()->setText(file); + }); + + connect(ui->comboBox->lineEdit(), &QLineEdit::textChanged, this, [&]() { + ui->okBtn->setDisabled(ui->comboBox->lineEdit()->text() == ""); + }); } RunApplet::~RunApplet() @@ -33,9 +47,17 @@ RunApplet::~RunApplet() delete ui; } -void RunApplet::runApp() { - QString command = ui->comboBox->currentText(); - QStringList args = command.split(" "); +void RunApplet::runApp(QString command) { + QStringList args; + + int p_argc; + uint32_t p_err; + wchar_t** p_args = wine_parse_command_line(command.toStdWString().c_str(), &p_argc, &p_err); + + for (int i = 0; i < p_argc; i++) { + args.push_back(QString::fromWCharArray(p_args[i])); + } + QProcess *process = new QProcess; process->setProgram(args[0]); args.remove(0); diff --git a/run-applet/runapplet.h b/run-applet/runapplet.h index 1c19c74..460d293 100644 --- a/run-applet/runapplet.h +++ b/run-applet/runapplet.h @@ -20,7 +20,7 @@ public: private: Ui::RunApplet *ui; - void runApp(); + void runApp(QString command); void saveState(); void restoreState(); diff --git a/run-applet/utils.c b/run-applet/utils.c new file mode 100644 index 0000000..20a7d59 --- /dev/null +++ b/run-applet/utils.c @@ -0,0 +1,284 @@ +#include "utils.h" + +#include + +/* + * Copyright 2002 Jon Griffiths + * Copyright 2016 Sebastian Lackner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define MAX_PATH 1024 + +/************************************************************************* + * CommandLineToArgvW [SHCORE.@] + * + * We must interpret the quotes in the command line to rebuild the argv + * array correctly: + * - arguments are separated by spaces or tabs + * - quotes serve as optional argument delimiters + * '"a b"' -> 'a b' + * - escaped quotes must be converted back to '"' + * '\"' -> '"' + * - consecutive backslashes preceding a quote see their number halved with + * the remainder escaping the quote: + * 2n backslashes + quote -> n backslashes + quote as an argument delimiter + * 2n+1 backslashes + quote -> n backslashes + literal quote + * - backslashes that are not followed by a quote are copied literally: + * 'a\b' -> 'a\b' + * 'a\\b' -> 'a\\b' + * - in quoted strings, consecutive quotes see their number divided by three + * with the remainder modulo 3 deciding whether to close the string or not. + * Note that the opening quote must be counted in the consecutive quotes, + * that's the (1+) below: + * (1+) 3n quotes -> n quotes + * (1+) 3n+1 quotes -> n quotes plus closes the quoted string + * (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string + * - in unquoted strings, the first quote opens the quoted string and the + * remaining consecutive quotes follow the above rule. + */ + +wchar_t** wine_parse_command_line(const wchar_t *cmdline, int *numargs, uint32_t* error) +{ + int qcount, bcount; + const wchar_t *s; + wchar_t **argv; + uint32_t argc; + wchar_t *d; + + *error = 0; + if (!numargs) + { + *error = ERROR_INVALID_PARAMETER; + return NULL; + } + + // This functionality is not used and requires passing in an extra argument + // if (*cmdline == 0) + // { + // /* Return the path to the executable */ + // uint32_t len, deslen = MAX_PATH, size; + + // size = sizeof(wchar_t *) * 2 + deslen * sizeof(wchar_t); + // for (;;) + // { + // if (!(argv = malloc(size))) return NULL; + // len = GetModuleFileNameW(0, (wchar_t *)(argv + 2), deslen); + // if (!len) + // { + // free(argv); + // return NULL; + // } + // if (len < deslen) break; + // deslen *= 2; + // size = sizeof(wchar_t *) * 2 + deslen * sizeof(wchar_t); + // free(argv); + // } + // argv[0] = (wchar_t *)(argv + 2); + // argv[1] = NULL; + // *numargs = 1; + + // return argv; + // } + + /* --- First count the arguments */ + argc = 1; + s = cmdline; + /* The first argument, the executable path, follows special rules */ + if (*s == '"') + { + /* The executable path ends at the next quote, no matter what */ + s++; + while (*s) + if (*s++ == '"') + break; + } + else + { + /* The executable path ends at the next space, no matter what */ + while (*s && *s != ' ' && *s != '\t') + s++; + } + /* skip to the first argument, if any */ + while (*s == ' ' || *s == '\t') + s++; + if (*s) + argc++; + + /* Analyze the remaining arguments */ + qcount = bcount = 0; + while (*s) + { + if ((*s == ' ' || *s == '\t') && qcount == 0) + { + /* skip to the next argument and count it if any */ + while (*s == ' ' || *s == '\t') + s++; + if (*s) + argc++; + bcount = 0; + } + else if (*s == '\\') + { + /* '\', count them */ + bcount++; + s++; + } + else if (*s == '"') + { + /* '"' */ + if ((bcount & 1) == 0) + qcount++; /* unescaped '"' */ + s++; + bcount = 0; + /* consecutive quotes, see comment in copying code below */ + while (*s == '"') + { + qcount++; + s++; + } + qcount = qcount % 3; + if (qcount == 2) + qcount = 0; + } + else + { + /* a regular character */ + bcount = 0; + s++; + } + } + + /* Allocate in a single lump, the string array, and the strings that go + * with it. This way the caller can make a single LocalFree() call to free + * both, as per MSDN. + */ + argv = malloc((argc + 1) * sizeof(wchar_t *) + (wcslen(cmdline) + 1) * sizeof(wchar_t)); + if (!argv) + return NULL; + + /* --- Then split and copy the arguments */ + argv[0] = d = wcscpy((wchar_t *)(argv + argc + 1), cmdline); + argc = 1; + /* The first argument, the executable path, follows special rules */ + if (*d == '"') + { + /* The executable path ends at the next quote, no matter what */ + s = d + 1; + while (*s) + { + if (*s == '"') + { + s++; + break; + } + *d++ = *s++; + } + } + else + { + /* The executable path ends at the next space, no matter what */ + while (*d && *d != ' ' && *d != '\t') + d++; + s = d; + if (*s) + s++; + } + /* close the executable path */ + *d++ = 0; + /* skip to the first argument and initialize it if any */ + while (*s == ' ' || *s == '\t') + s++; + if (!*s) + { + /* There are no parameters so we are all done */ + argv[argc] = NULL; + *numargs = argc; + return argv; + } + + /* Split and copy the remaining arguments */ + argv[argc++] = d; + qcount = bcount = 0; + while (*s) + { + if ((*s == ' ' || *s == '\t') && qcount == 0) + { + /* close the argument */ + *d++ = 0; + bcount = 0; + + /* skip to the next one and initialize it if any */ + do { + s++; + } while (*s == ' ' || *s == '\t'); + if (*s) + argv[argc++] = d; + } + else if (*s=='\\') + { + *d++ = *s++; + bcount++; + } + else if (*s == '"') + { + if ((bcount & 1) == 0) + { + /* Preceded by an even number of '\', this is half that + * number of '\', plus a quote which we erase. + */ + d -= bcount / 2; + qcount++; + } + else + { + /* Preceded by an odd number of '\', this is half that + * number of '\' followed by a '"' + */ + d = d - bcount / 2 - 1; + *d++ = '"'; + } + s++; + bcount = 0; + /* Now count the number of consecutive quotes. Note that qcount + * already takes into account the opening quote if any, as well as + * the quote that lead us here. + */ + while (*s == '"') + { + if (++qcount == 3) + { + *d++ = '"'; + qcount = 0; + } + s++; + } + if (qcount == 2) + qcount = 0; + } + else + { + /* a regular character */ + *d++ = *s++; + bcount = 0; + } + } + *d = '\0'; + argv[argc] = NULL; + *numargs = argc; + + return argv; +} diff --git a/run-applet/utils.h b/run-applet/utils.h new file mode 100644 index 0000000..e04eaca --- /dev/null +++ b/run-applet/utils.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +#define ERROR_INVALID_PARAMETER -1 + +wchar_t** wine_parse_command_line(const wchar_t *cmdline, int *numargs, uint32_t* error); diff --git a/run-applet/utils.hpp b/run-applet/utils.hpp new file mode 100644 index 0000000..461ae1a --- /dev/null +++ b/run-applet/utils.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define ERROR_INVALID_PARAMETER -1 + +extern "C" { +wchar_t** wine_parse_command_line(const wchar_t *cmdline, int *numargs, uint32_t* error); +}