QT开发GIF截屏工具的问题记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/theArcticOcean/article/details/81155792

项目地址: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
效果:

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页