QT開發GIF截屏工具的問題記錄

項目地址:https://github.com/theArcticOcean/Gifer

QT log 重定向問題。

描述:在QML中的log print成功輸出所有信息,CPP中的打印不能顯示文件名,行號,函數名

看了幫助文檔中的例子
QtMessageHandler qInstallMessageHandler(QtMessageHandler handler)
安裝我們自己的handler後可以重定向

#include <qapplication.h>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageOutput);
    QApplication app(argc, argv);
    ...
    return app.exec();
}    

我使用在自己的項目中,源代碼如下

    void LogBase
        (
        QtMsgType type,
        const QMessageLogContext &context,
        const QString &msg
        )
    {

    }
//...
int main()
{
//...
qInstallMessageHandler( LogBase );
//...
return 0;
}

結果log 重定向,QML成功打印所有信息,CPP中的打印不能顯示文件名,行號,函數名

[Debug ((null), 0, (null))] LogInit finished.
[Warning (qrc:/main.qml, 72, (null))] qrc:/main.qml:72:9: QML Button: Cannot anchor to an item that isn't a parent or sibling.  [Debug (qrc:/main.qml, 30, onPressed)] onPressed: (613.9609375, 507.06640625)

在查看源碼後,發現QMessageLogContext有兩種構造方式。

class QMessageLogContext
{
    Q_DISABLE_COPY(QMessageLogContext)
public:
    Q_DECL_CONSTEXPR QMessageLogContext()
        : version(2), line(0), file(Q_NULLPTR), function(Q_NULLPTR), category(Q_NULLPTR) {}
    Q_DECL_CONSTEXPR QMessageLogContext(const char *fileName, int lineNumber, const char *functionName, const char *categoryName)
        : version(2), line(lineNumber), file(fileName), function(functionName), category(categoryName) {}

在我們的工程中,CPP類下的QMessageLogContext構造使用了第一種方式,QML下的的QMessageLogContext構造使用了第二種方式。我想,這和應用程序的啓動相關,本工程使用QQmlApplicationEngine啓動。
我們可以通過修改message handler函數來保證文件名,行號,函數名的輸出:

    void LogBase
        (
        QtMsgType type,
        const QMessageLogContext &context = QMessageLogContext( __FILE__, __LINE__, __FUNCTION__, NULL ),
        const QString &msg = QString("")
        )
    {

Qt Quick設置icon

在pro文件中加上:

macx{
    message("compile for mac os x")
    ICON = Images/logo.icns
}

重新生成Makeifle,再make。

雙擊mac程序,程序在移動文件時提示沒有權限。

雙擊app啓動程序,我一開始猜想是Finder進程啓動app進程。使用pstree可以查看相關的進程派生關係。可以使用brew安裝pstree。
查詢Finder相關的分支:

 Images git:(master)  ps aux |grep Finder
weiyang 284 0.0 0.5 4751940 45756 ?? R 14Jul18 9:12.45 /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
weiyang 27974 0.0 0.0 4267752 576 s002 R+ 9:07AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Finder

 Images git:(master)  pstree -p 284
-+= 00001 root /sbin/launchd
 \--= 00284 weiyang /System/Library/CoreServices/Finder.app/Contents/MacOS/Find

用類似的方法,查詢雙擊Gifer.app後的進程派生關係。

 Images git:(master)  ps aux |grep Gifer
weiyang 28107 0.2 0.6 4474736 46468 ?? S 9:08AM 0:01.01 /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Gifer
weiyang 28129 0.0 0.0 4267752 532 s002 R+ 9:09AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Gifer

 Images git:(master)  pstree -p 28107
-+= 00001 root /sbin/launchd
 \--= 28107 weiyang /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Gifer

原來,是launchd啓動app的。
那麼命令行啓動的進程派生又是怎麼樣子的呢?

 Images git:(master)  ps aux |grep Gifer
weiyang 28395 0.0 0.0 4267752 640 s002 R+ 9:16AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn Gifer
weiyang 28342 0.0 0.6 4476844 46860 s001 S+ 9:15AM 0:00.70 ./Gifer
 Images git:(master)  pstree -p 28342
-+= 00001 root /sbin/launchd
 \-+= 17185 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 17249 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2 --server lo
     \-+= 17250 root login -fp weiyang
       \-+= 17251 weiyang -zsh
         \--= 28342 weiyang ./Gifer

命令行啓動的進程是由終端進程啓動的。

在源碼中,我使用了QDir::current().absolutePath()定位程序的路徑,但他返回的是/。進一步查看該函數的解釋

[static] QDir QDir::current()
Returns the application’s current directory.
The directory is constructed using the absolute path of the current directory

再看看QCoreApplication::applicationDirPath()的解釋

[static] QString QCoreApplication::applicationDirPath()
Returns the directory that contains the application executable.
For example, if you have installed Qt in the C:\Qt directory, and you run the regexp example, this function will return “C:/Qt/examples/tools/regexp”.

在程序啓動起來後QDir QDir::current()返回的application’s current directory是不確定的。
如果是雙擊app啓動,返回值是/;如果是命令行啓動,返回值是可執行文件的路徑,也就和QCoreApplication::applicationDirPath()一樣了。
後者是我們需要的函數。

使用system運行命令的問題

假設我使用system運行這樣一條命令:
ffmpeg -threads 1 -y -r 70 -i /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Pictures/out%d.png -final_delay 300 /Users/weiyang/pro/Gifer/Gifer.app/Contents/MacOS/Pictures/output.gif
再這樣判斷:

    ret = system( cmdStr.toStdString().c_str() );
    if( -1 == ret || 127 == ret )
    {
        tmp = cmdStr + strerror(errno);
        qWarning() << tmp;
        QMessageBox::information( this,
                               tr("Combine Gif Failed"),
                               tmp,
                               QMessageBox::Ok
                               );
        return false;
    }

判斷依據來自於mac上的幫助文檔

RETURN VALUES
The system() function returns the exit status of the shell as returned by
waitpid(2), or -1 if an error occurred when invoking fork(2) or
waitpid(2). A return value of 127 means the execution of the shell
failed.

但事實上並沒有這麼簡單,閱讀了博文:https://blog.csdn.net/cheyo/article/details/6595955
system的工作大致是這樣的:
父進程fork子進程
父進程等待子進程
在子進程中執行字符串所表述的命令
返回執行結果。

我將源碼改成:

    ret = system( cmdStr.toStdString().c_str() );
    // create sub process
    if (-1 == ret)
    {
        qCritical("system error!");
    }
    else
    {
        qDebug("ret is not -1, exit ret value = [0x%x]", ret);  //0x7f00

        // call shell script and finish successfully.
        if (WIFEXITED(ret))   // WIFEXITED is a macro
        {
            if (0 == WEXITSTATUS(ret))  // return value of shell script run.
            {
                qDebug("run shell script successfully.");
            }
            // 0x7f = 127
            else
            {
                qCritical("run shell script fail, script exit code: %d, reason: %s", WEXITSTATUS(ret), strerror(errno));
            }
        }
        else
        {
            qCritical("exit ret = [%d]", WEXITSTATUS(ret));
        }
    }

當我在shell終端中命令行方式啓動後,沒有問題。但如果是雙擊Gifer.app就會是這樣的結果:

[Debug (windowgrabber.cpp, 159, bool WindowGrabber::combineImages())] ret is not -1, exit ret value = [0x7f00]
[Critical (windowgrabber.cpp, 171, bool WindowGrabber::combineImages())] run shell script fail, script exit code: 127, reason: No such file or directory

一樣的命令,結果卻是不同的。
我找到了ffmpeg的具體位置,然後在system的命令中也給出ffmpeg的絕對路徑,問題就不存在了。也就是說,在雙擊啓動app的方式下,系統找不到ffmpeg。
在雙擊啓動app的方式下,PATH的值是

/usr/bin:/bin:/usr/sbin:/sbin

此結果在代碼中添加system("echo $PATH > ~/code/txt");可以檢測出來。
這和我在~/.bashrc下的設置是不一樣的。
新的PATH值剛好對應

/usr/include/paths.h
67:#define  _PATH_STDPATH   "/usr/bin:/bin:/usr/sbin:/sbin"

/usr/libexec/security-checksystem
12:export PATH=/usr/bin:/bin:/usr/sbin:/sbin

/usr/local/Homebrew/bin/brew
71: PATH="/usr/bin:/bin:/usr/sbin:/sbin"

system的源碼爲:

int system(const char * cmdstring)
{
    pid_t pid;
    int status;
    if( cmdstring == NULL )
    {
        return 1;
    }
    if( (pid = fork()) < 0 )
    {
        status = -1;
    }
    else if( pid == 0 )
    {
        // execl create new process to replace old process
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        // if above statement execute successfully, the following function would not work.
        _exit(127);
    }
    else
    {
        while( waitpid(pid, &status, 0) < 0 )
        {
            if( errno != EINTR )
            {
                status = -1;
                break;
            }
        }
    }
    return status;
}

execlp搜索目錄默認值是paths.h中的_PATH_DEFPATH。也就是前面提到過的/usr/bin:/bin:/usr/sbin:/sbin

For execlp() and
execvp(), search path is the path specified in the environment by
PATH variable. If this variable is not specified, the default path
is set according to the _PATH_DEFPATH definition in <paths.h>, which is
set to “/usr/bin:/bin”. For execvP(), the search path is specified as
an argument to the function. In addition, certain errors are treated
specially.

故,在/usr/bin/下建立一個軟連接即可。

➜ ~ sudo ln -s /usr/local/bin/ffmpeg /usr/bin/ffmpeg

怎樣使得dmg安裝包自帶Application文件夾

之前打包都是使用QT的打包功能,macdeployqt或windeployqt,但是生成的dmg有一個缺點,他沒有Application文件夾圖標生成。每次都需要拖動到Finder中的Application。
我在https://stackoverflow.com/questions/8680132/creating-nice-dmg-installer-for-mac-os-x 看到了一位大神Linus Unnebäck 的回答,他的方案簡單易懂。

brew install node
npm install -g appdmg

創建spec.json

{
    "title": "Gifer Installer",
    "icon": "Images/logo.icns",
    "background": "Images/back.png",
    "icon-size": 80,
    "contents": [
        { "x": 100, "y": 120, "type": "file", "path": "Gifer.app" },
        { "x": 400, "y": 120, "type": "link", "path": "/Applications" }
    ],
    "window": {
        "size": { "width": 500, "height": 300 }
    }
}

生成dmg:
appdmg spec.json Gifer.dmg
效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章