Set encoding for process output issue - c++

I want to set encoding for a Windows console process for Russian ouput. In C# the Process has a StandardOutputEncoding attribute, but in Qt no such functionality exists.
Here is the problem:
Any suggestion how to accomplish it?
Update:
I have tried QTextStream setCodec function:
void Test1::getData(QByteArray data)
{
QTextStream encodeStream(data);
encodeStream.setCodec("windows-1251");
dataTextBrowser->append(encodeStream.readAll());
emit dataFinished();
}
Result:

Thanks to Michael O. I have fixed the issue. Also, I included the code here, so others can find the solution.
Code:
void Test1::getData(QByteArray data)
{
QTextStream encodeStream(data);
encodeStream.setCodec("IBM 866");
dataTextBrowser->append(encodeStream.readAll());
emit dataFinished();
}

Related

Qt startDetached fails under Qt 5.15.2

I'm updating some old Qt 5.6 code to Qt 5.15.2
The Qt C++ code below opens a dos prompt, the runs a bat file, staying open when done.
cstring = "cmd /k " +QDir::currentPath()+"/cpath.bat";
QProcess::startDetached(cstring);
This code works fine under 5.6, does nothing under 5.15.2
How do I fix this for Qt 5.15.2?
You need to split the command to program and arguments. See https://doc.qt.io/qt-5/qprocess.html#startDetached-1
In your case it should be
QProcess::startDetached("cmd", {"/k", QDir::currentPath() + "/cpath.bat"});
The advantage is that now you do not need to worry for example about enclosing paths containing spaces in quotes. Consider if your current path would be "C:\my path". Then your version from Qt 5.6 would not work. While the version I presented in this answer will work out of the box. I guess it was exactly this reason that the old overload was prone to this kind of bugs that Qt decided to remove it and use only the new overload.
Ok, with my previous answer I though that the problem was with the way you are passing the arguments. But as it seems the problem is with showing the terminal... Since I am doing the same in one of my projects (HiFile file manager), I had a look how I am doing it. So here it is.
class DetachableProcess : public QProcess
{
public:
explicit DetachableProcess(QObject *parent = nullptr) : QProcess(parent)
{
}
bool detach()
{
if (!waitForStarted())
{
return false;
}
setProcessState(QProcess::NotRunning);
return true;
}
};
And then use it like this:
DetachableProcess process;
process.setCreateProcessArgumentsModifier( // this probably did the trick you need
[](QProcess::CreateProcessArguments *args)
{
args->flags |= CREATE_NEW_CONSOLE;
args->startupInfo->dwFlags &=~ STARTF_USESTDHANDLES;
});
process.start("cmd", {"/k", QDir::currentPath() + "/cpath.bat"}); // here are your params...
process.detach();
I am afraid I cannot give more explanation why this complicated method works, I do not know it. And I do not know why it was changed in Qt.
I hope it will work for you. It works for me in Qt 6.2.4, which I am currently using.

Directly executing a batch through clicked function in qt

So I'm trying to have my "button" directly execute a Batch file, important here is that I don't want it to show me a dialogue and make me chose the path, which is the problem I'm having right now with the following code
void MainWindow::on_pushButton_clicked()
{
QString filename=QFileDialog::getOpenFileName(
this,
tr("Open File"),
"C://",
"All files (*.*);;Text File (*.txt);;Music file (*.mp3)");
}
I think this is probably really simple, but i can't get it, I'm not even learning c++ at the moment but my boss asked me to create something out of my scope (wants me to create a GUI for a batch file and have them interact) and I thought of this approach, which is just creating a GUI that executes it.
I've looked at this question: asked to execute an external program with Qt
but they don't talk about how the file path can directly be added into the code, or if I should even be using Qprocess and how, and if I can pass it through "clicked" function.
I'm really inexperienced, all of the code above I got with the help of the internet, but I really don't know how to program using c++
so could someone please be kind enough to show me how a file path can be added to the code, assuming it's in C:\Users\name_goes_here\Downloads
I'd really appreciate it :D
I'd recommend using QProcess for anything "execute external program" with Qt.
You could do it like this:
void MainWindow::on_pushButton_clicked()
{
QProcess process;
process.start("C:/Users/name_goes_here/Downloads/yourfile.bat");
process.waitForFinished(); // Assuming that you do want to wait for it to finish before the code execution resumes
}
Note the "/" in the path. Only Windows uses the messed up "\" for path separation, which would require you to write "C:\\Users\\.." in any string in C++ as "\" needs to be escaped.
Luckily, Qt uses "/" as the universal separator and translates it to whatever the OS needs as required. So you should just use "/" whenever working with Qt.
This is from the Qt documentation:
Qt uses "/" as a universal directory separator in the same way that "/" is used as a path separator in URLs. If you always use "/" as a directory separator, Qt will translate your paths to conform to the underlying operating system.
And finally, if you don't know how to code in C++, shouldn't you be learning that first instead of trying to execute batch files from within a library as complex as Qt? Sounds like you're trying to do too many new things at once.
This is fairly simple merging your source and the one you linked:
void MainWindow::on_pushButton_clicked()
{
QProcess::execute(
QString::fromLatin1(
"cmd.exe /c C:\\Users\\name_goes_here\\Downloads\\file.bat"));
}
Notes:
I used QProcess::execute() instead of QProcess::start() to make things even simpler.
To achieve execution of the batch file, I pass it to cmd32.exe as this is the interpreter which is responsible.
As MCVE testQProcessBatch.cc:
// Qt header:
#include <QtWidgets>
void on_pushButton_clicked()
{
#if 0 // WORKS:
QProcess::execute(
QString::fromUtf8("cmd.exe /c C:\\Users\\Scheff\\Downloads\\testBatch.bat"));
#else // WORKS AS WELL:
QProcess::execute(
QString::fromUtf8("C:\\Users\\Scheff\\Downloads\\testBatch.bat"));
#endif // 0
}
int main(int argc, char **argv)
{
qDebug() << "Version:" << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
QMainWindow qWin;
QPushButton qBtn(QString::fromLatin1("Start cmd"));
qWin.setCentralWidget(&qBtn);
qWin.show();
QObject::connect(&qBtn, &QPushButton::clicked,
&on_pushButton_clicked);
// run application
return app.exec();
}
and the test batch file testBatch.bat:
echo "This is testBatch.bat"
pause
Tested with VS2013 on Windows 10:
Thanks for contributing guys!
I tried using the QProcess method but I think I'm too inexperienced when it comes to figuring out problems associated with it (which I did face when using this method). the CMD route is probably good but I also thought it was too difficult and both of these methods didn't work for me.
Here's what I have now (thanks to Detonar and ymoreau) and and it seems to be doing the job, this might not be the most optimal approach, but it worked for me!
I included QDesktopServices and QUrl
void MainWindow::on_pushButton_clicked()
{
QString filename="C:\\Users\\Name_goes_here\\Downloads\\test.bat";(
this);
hide(); //optional
QDesktopServices::openUrl(QUrl("file:///"+filename,QUrl::TolerantMode));
}

QML type from C++ Plugin signaling only once

I have a C++ plugin that watches for file changes with QFileSystemWatcher and connects it's fileChanged signal with a custom QML type slot like this:
//In the custom QML type constructor
QObject::connect(&this->_watcher, SIGNAL(fileChanged(QString)),
this, SLOT(fileChangedSlot(QString)));
The slot function:
void CustomQMLTypeClass::fileChangedSlot(QString file)
{
Q_UNUSED(file);
emit fileChanged();
}
In the QML side:
CustomQMLType{
fileUri: "some/file/path/file.format"
onFileChanged: console.log("File changed")
}
While running the program all goes right, but when I do, i.e.:
echo "sth" >> some/file/path/file.format
More than once, the notification is only triggered once. Why? O.o
Apparently the problem is with QFileSystemWatcher, it sometimes worked and some others don't.
As I can handle the cost, my quick solution was to alter the slot:
void CustomQMLTypeClass::fileChangedSlot(QString &file)
{
_watcher.removePath(file);
_watcher.addPath(file);
emit fileChanged();
}
Now it works as expected but don't know why and couldn't get to understand neither with QFileSystemWatcher's source. Finally I decided KDE's KDirWatch is way better.

How to debug QDomElement in QtXml?

I've got a QDomElement, and I would like to debug it, i.e. see it as plain text in debug console. In order to output it with qDebug(), it needs to be in QString format, however I don't see any conversion method from a QDomElement nor a QDomNode.
Any idea? Thanks!
There is no built-in operator for streaming DOM elements to QDebug. You could write one easily enough, something like:
QDebug operator<<(QDebug dbg, const QDomNode& node)
{
QString s;
QTextStream str(&s, QIODevice::WriteOnly);
node.save(str, 2);
dbg << qPrintable(s);
return dbg;
}
Use QTextStream:
QTextStream lTS(stdout);
lTS << lMyDomElement;
if you #include <QDebug> QDebug would act as TextStream itself.
i.e. qDebug()<< lMyDomElement; would be enough)
Well I also come across similar situations, in that case my best bet is to make use of the QDomDocument which this QDomElement is part of. So I would say you cannot get a direct way to access the QDomElement but you can achieve that using the QDomDocument.
For this you need to ensure that your QDomDocument gets updated with the recent QDomElement and then use QDomDocument::toString() which would return you the whole document as a QString.
Here is the Qt reference.
Hope this helps.

Will loading a DLL dynamically reconcile its stderr to a main application? If so, then how...?

I'm writing a GUI application, using Qt, which links to a third-party DLL that sometimes sends error messages to stderr. I'd like these error messages to be displayed in a window within my GUI.
I couldn't find an established way to redirect stderr (as opposed to std::cerr) even after much searching, so I wrote the following class myself:
class StdErrRedirect : public QObject
{
Q_OBJECT
public:
// Constructor
StdErrRedirect(QTextEdit *errorLog,
QObject *parent = NULL);
// Destructor
~StdErrRedirect();
private slots:
void fileChanged(const QString &filename);
private:
QFile tmp;
QFileSystemWatcher watcher;
QString tmpFileNameQtFormat;
QString tmpFileNameNativeFormat;
QTextEdit *m_errorLog;
QString oldContent;
};
StdErrRedirect::StdErrRedirect(QTextEdit *errorLog,
QObject *parent)
: QObject(parent)
{
// Store the pointer to the error log window
m_errorLog = errorLog;
// Create a temporary filename: first find the path:
tmpFileNameQtFormat = QDir::tempPath();
// Make sure the closing slash is present:
if (!tmpFileNameQtFormat.endsWith(QChar('/')))
tmpFileNameQtFormat.append(QChar('/'));
// Add the file name itself:
tmpFileNameQtFormat.append("nb_stderrlog");
// Obtain a version of the filename in the operating system's native format:
tmpFileNameNativeFormat = QDir::toNativeSeparators(tmpFileNameQtFormat);
// Set up redirection to this file:
freopen(tmpFileNameNativeFormat.toAscii().constData(), "a+", stderr);
// Initialise the QFileSystemWatcher:
connect(&watcher, SIGNAL(fileChanged(const QString &)),
this, SLOT(fileChanged(const QString &)));
watcher.addPath(tmpFileNameQtFormat);
tmp.setFileName(tmpFileNameQtFormat);
}
StdErrRedirect::~StdErrRedirect()
{
// Ensure the temporary file is properly deleted:
fclose(stderr);
tmp.close();
tmp.open(QIODevice::ReadWrite);
tmp.remove();
}
void StdErrRedirect::fileChanged(const QString &filename)
{
tmp.open(QIODevice::ReadOnly);
QTextStream stream(&tmp);
QString content = stream.readAll();
tmp.close();
// Identify what's new, and just send this to the window:
int newchars = content.size() - oldContent.size();
if (newchars)
{
m_errorLog -> append(content.right(newchars));
oldContent = content;
}
}
If I instantiate this from my main window using:
errorLog = new QTextEdit;
redirector = new StdErrRedirect(errorLog);
... then everything I write to stderr appears in the window.
So far, so good. The problem is that the DLL's output still does not. In a call to a DLL function which emits an error, if I put the code:
if (error != _OK)
{
error.PrintErrorTrace();
fprintf(stderr, "Should have printed an error \r\n");
fflush(stderr);
//fsync(_fileno(stderr)); Linux version
_commit(_fileno(stderr));
return;
}
...then the text "Should have printed an error" appears but the error message itself does not.
Now, I've read somewhere that this is probably because the redirection is being set up after the DLL was loaded at the beginning of the application, and so it's own stderr channel is unaffected. Therefore, I should be able to fix this by loading the DLL dynamically, after setting up the redirection, instead.
Here is my question, then: how do I do this? I can try putting the following code at the beginning of my application:
QLibrary extlib;
extlib.setFileName("libname");
extlib.setLoadHints(QLibrary::ResolveAllSymbolsHint);
extlib.load();
...but on its own it has no effect. I think this is because the linker is still setting the library up to be opened automatically. However, if I remove the DLL from the linker (I'm using VS2008, so I remove extlib.lib from the dependency list) then the application won't compile because the compiler can't find the symbols from the DLL.
So there's obviously something deeply wrong with what I'm trying to do here. Can anybody help?
Thanks,
Stephen.
Does the DLL really write to stderr? Or does it write to GetStdHandle(STD_ERROR_HANDLE) ? The first maps to the second, initially. But with freopen() you merely change the mapping. Anything written to STD_ERROR_HANDLE will still go there.
To redirect everyones error output, you would need SetStdHandle.
There is only one stderr, so my guess is that the problem is not that you need to load the dll dynamically, but somewhere in your redirection code. Your redirection code is written Linux style, where in windows things work differently.
if you could test your application on Linux, It would help to pin point the problem. If it works on Linux, that it is surly the redirection code.
Anyway, you should read some more about redirection and windows, as I don't think that what you are trying to do now will help you.