diff --git a/.github/ISSUE_TEMPLATE/00-olive_unsupported.md b/.github/ISSUE_TEMPLATE/00-olive_unsupported.md index 700c5c1cb..88e715ff8 100644 --- a/.github/ISSUE_TEMPLATE/00-olive_unsupported.md +++ b/.github/ISSUE_TEMPLATE/00-olive_unsupported.md @@ -5,7 +5,6 @@ title: "[UNSUPPORTED]" labels: "Legacy (Unsupported)" assignees: '' --- - # Olive 0.1 is unsupported Unfortunately no one is supporting Olive 0.1 at this time. Any reports pertaining to it will be diff --git a/.github/ISSUE_TEMPLATE/01-crash_issue.md b/.github/ISSUE_TEMPLATE/01-crash_issue.md index 0b68b7202..87e916dee 100644 --- a/.github/ISSUE_TEMPLATE/01-crash_issue.md +++ b/.github/ISSUE_TEMPLATE/01-crash_issue.md @@ -4,16 +4,26 @@ about: Report a fatal crash that resulted in Olive unexpectedly closing. title: "[CRASH]" labels: "Crash, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Steps to Reproduce** -**Backtrace** +1. +2. +3. + +
Backtrace

+
+
+
+
**Additional Information** diff --git a/.github/ISSUE_TEMPLATE/50-build_issue.md b/.github/ISSUE_TEMPLATE/50-build_issue.md index 8d93ad06d..155c7c750 100644 --- a/.github/ISSUE_TEMPLATE/50-build_issue.md +++ b/.github/ISSUE_TEMPLATE/50-build_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to compiling or packaging (including continuous i title: "[BUILD]" labels: "Building/Packaging, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-cache_issue.md b/.github/ISSUE_TEMPLATE/50-cache_issue.md index 0d088aa8c..f346d8245 100644 --- a/.github/ISSUE_TEMPLATE/50-cache_issue.md +++ b/.github/ISSUE_TEMPLATE/50-cache_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to the disk cache system, including failure to c title: "[CACHE]" labels: "Disk Cache, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-codec_issue.md b/.github/ISSUE_TEMPLATE/50-codec_issue.md index 04082833f..09fc991ac 100644 --- a/.github/ISSUE_TEMPLATE/50-codec_issue.md +++ b/.github/ISSUE_TEMPLATE/50-codec_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to codec handling, including importing footage or title: "[CODEC]" labels: "Codec, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-color_issue.md b/.github/ISSUE_TEMPLATE/50-color_issue.md index 99889a56b..99e4c803c 100644 --- a/.github/ISSUE_TEMPLATE/50-color_issue.md +++ b/.github/ISSUE_TEMPLATE/50-color_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to the management of pixels and color, including title: "[COLOR]" labels: "Color Management, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-editing_issue.md b/.github/ISSUE_TEMPLATE/50-editing_issue.md index 0465e88a5..88d2f16c1 100644 --- a/.github/ISSUE_TEMPLATE/50-editing_issue.md +++ b/.github/ISSUE_TEMPLATE/50-editing_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to the overall editing experience, including usa title: "[EDIT]" labels: "Timeline/Editing, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-export_issue.md b/.github/ISSUE_TEMPLATE/50-export_issue.md index 7ccef22fc..4e6e1f3a2 100644 --- a/.github/ISSUE_TEMPLATE/50-export_issue.md +++ b/.github/ISSUE_TEMPLATE/50-export_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to exporting videos from Olive, including errors title: "[EXPORT]" labels: "Export, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-node_issue.md b/.github/ISSUE_TEMPLATE/50-node_issue.md index 4d15ecebe..ec31c8989 100644 --- a/.github/ISSUE_TEMPLATE/50-node_issue.md +++ b/.github/ISSUE_TEMPLATE/50-node_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to the node-based compositing system, including title: "[NODES]" labels: "Nodes/Compositing, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-playback_issue.md b/.github/ISSUE_TEMPLATE/50-playback_issue.md index 5e35446f5..32a3cc13b 100644 --- a/.github/ISSUE_TEMPLATE/50-playback_issue.md +++ b/.github/ISSUE_TEMPLATE/50-playback_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to the playback of video or audio, including lag title: "[PLAYBACK]" labels: "Playback, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-project_issue.md b/.github/ISSUE_TEMPLATE/50-project_issue.md index fa65f393e..ff9175354 100644 --- a/.github/ISSUE_TEMPLATE/50-project_issue.md +++ b/.github/ISSUE_TEMPLATE/50-project_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to project management, including working with an title: "[PROJECT]" labels: "Project, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-renderer_issue.md b/.github/ISSUE_TEMPLATE/50-renderer_issue.md index f97a22079..e77316402 100644 --- a/.github/ISSUE_TEMPLATE/50-renderer_issue.md +++ b/.github/ISSUE_TEMPLATE/50-renderer_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to rendering, including corrupted frames, incorr title: "[RENDER]" labels: "Renderer, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/ISSUE_TEMPLATE/50-ui_issue.md b/.github/ISSUE_TEMPLATE/50-ui_issue.md index a0cfa3c02..f746024de 100644 --- a/.github/ISSUE_TEMPLATE/50-ui_issue.md +++ b/.github/ISSUE_TEMPLATE/50-ui_issue.md @@ -4,12 +4,14 @@ about: Report an issue related to general user interface usability, including b title: "[UI]" labels: "User Interface, Triage" assignees: '' - --- -**Commit Hash (8 character string of letters/numbers in title bar)** +**Commit Hash** + **Platform** + **Summary** + **Additional Information / Output** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07ebcbdd7..b4756b7f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,14 +139,21 @@ jobs: then mkdir olive-editor cp app/olive-editor.exe olive-editor + cp app/olive-crashhandler.exe olive-editor cp app/olive-editor.pdb olive-editor + windeployqt olive-editor/olive-crashhandler.exe windeployqt olive-editor/olive-editor.exe cp $(cygpath $GITHUB_WORKSPACE)/bin/*.dll olive-editor cp $(cygpath $GITHUB_WORKSPACE)/out/Default/crashpad_handler.exe olive-editor + cp $(cygpath $GITHUB_WORKSPACE)/minidump_stackwalk/* olive-editor elif [ "$MATRIX_OS" == "macos-latest" ] then export BUNDLE_NAME=Olive.app mv app/$BUNDLE_NAME . + + # HACK: OCIO doesn't link it's library properly so we need to manually correct + install_name_tool -change libOpenColorIO.1.dylib @rpath/libOpenColorIO.1.dylib $BUNDLE_NAME/Contents/MacOS/Olive + macdeployqt $BUNDLE_NAME $DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f8ab7419..06a282d31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") + set(OLIVE_DEFINITIONS -DAPPVERSION="${PROJECT_VERSION}" -DQT_DEPRECATED_WARNINGS) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") diff --git a/README.md b/README.md index 9b95c2b60..9291d1b77 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,17 @@ -# Olive Video Editor [![Build Status](https://travis-ci.org/olive-editor/olive.svg?branch=master)](https://travis-ci.org/olive-editor/olive) [![Build Status](https://ci.appveyor.com/api/projects/status/5s4jabxayg51rv95?svg=true)](https://ci.appveyor.com/project/itsmattkc/olive) +# Olive Video Editor [![Build status](https://github.com/olive-editor/olive/workflows/CI/badge.svg?branch=master)](https://github.com/olive-editor/olive/actions?query=branch%3Amaster) Olive is a free non-linear video editor for Windows, macOS, and Linux. -![screen](https://www.olivevideoeditor.org/img/screenshot.jpg) +![screen](https://olivevideoeditor.org/img/020-2.png) -**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Twitter](https://twitter.com/oliveteam) | [Discord](https://discord.gg/4Ae9KZn) | [Patreon](https://www.patreon.com/olivevideoeditor) - -## Getting Started with Olive: - -Binaries of Olive are available at: https://olivevideoeditor.org/download.php - -Some tutorials can be seen on the GitHub Wiki: https://github.com/olive-editor/olive/wiki/Overview-Guide +**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Binaries](https://olivevideoeditor.org/download.php) | [Twitter](https://twitter.com/oliveteam) | [Discord](https://discord.gg/4Ae9KZn) | [Patreon](https://www.patreon.com/olivevideoeditor) | [Tutorials](https://github.com/olive-editor/olive/wiki/Overview-Guide) **NOTE: Olive is alpha software and is considered highly unstable. While we highly appreciate users testing and providing usage information, please use at your own risk.** +## Binaries + +[Nightly binaries are available on the website.](https://olivevideoeditor.org/download.php) + ## Support Olive: Please consider supporting Olive: diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a313bb3a2..46a6ba9a3 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -185,6 +185,54 @@ if (GoogleCrashpad_FOUND) PRIVATE ${CRASHPAD_LIBRARIES} ) + + set(OLIVE_CRASH_TARGET "olive-crashhandler") + + set(OLIVE_CRASH_SOURCES + dialog/crashhandler/crashhandler.h + dialog/crashhandler/crashhandler.cpp + dialog/crashhandler/crashhandlermain.cpp + ) + + if (WIN32) + add_executable( + ${OLIVE_CRASH_TARGET} + WIN32 + ${OLIVE_CRASH_SOURCES} + ) + else() + add_executable( + ${OLIVE_CRASH_TARGET} + ${OLIVE_CRASH_SOURCES} + ) + endif() + + target_include_directories( + ${OLIVE_CRASH_TARGET} + PRIVATE + ${CRASHPAD_INCLUDE_DIRS} + ) + + target_link_libraries( + ${OLIVE_CRASH_TARGET} + PRIVATE + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Network + ${CRASHPAD_LIBRARIES} + ) + + if(UNIX AND NOT APPLE) + install(TARGETS ${OLIVE_TARGET} ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin) + endif() + + if(APPLE) + # Move crash handler program inside Mac app bundle + add_custom_command(TARGET ${OLIVE_CRASH_TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${OLIVE_CRASH_TARGET} $ + ) + endif() endif() # Set compiler definitions @@ -209,43 +257,3 @@ if(DOXYGEN_FOUND) set(DOXYGEN_EXTRACT_PRIVATE "YES") doxygen_add_docs(docs ALL ${OLIVE_SOURCES}) endif() - -set(OLIVE_CRASH_TARGET "olive-crashhandler") - -set(OLIVE_CRASH_SOURCES - dialog/crashhandler/crashhandler.h - dialog/crashhandler/crashhandler.cpp - dialog/crashhandler/crashhandlermain.cpp -) - -if (WIN32) - add_executable( - ${OLIVE_CRASH_TARGET} - WIN32 - ${OLIVE_CRASH_SOURCES} - ) -else() - add_executable( - ${OLIVE_CRASH_TARGET} - ${OLIVE_CRASH_SOURCES} - ) -endif() - -target_link_libraries( - ${OLIVE_CRASH_TARGET} - PRIVATE - Qt5::Core - Qt5::Gui - Qt5::Widgets -) - -if(UNIX AND NOT APPLE) - install(TARGETS ${OLIVE_TARGET} ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin) -endif() - -if(APPLE) - # Move crash handler program inside Mac app bundle - add_custom_command(TARGET ${OLIVE_CRASH_TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${OLIVE_CRASH_TARGET} $ - ) -endif() diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 395284c44..bfaf46960 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -68,11 +68,17 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) // Heuristically determine whether this file is part of an image sequence or not if (GetImageSequenceDigitCount(f->filename()) > 0) { + QSize dim(in->spec().width, in->spec().height); + int64_t ind = GetImageSequenceIndex(f->filename()); // Check if files around exist around it with that follow a sequence - if (QFileInfo::exists(TransformImageSequenceFileName(f->filename(), ind - 1)) - || QFileInfo::exists(TransformImageSequenceFileName(f->filename(), ind + 1))) { + QString previous_img_fn = TransformImageSequenceFileName(f->filename(), ind - 1); + QString next_img_fn = TransformImageSequenceFileName(f->filename(), ind + 1); + + // GetImageDimensions will return a 0,0 size if the file doesn't exist, so it's safe to check + // both existence and matching size with this + if (GetImageDimensions(previous_img_fn) == dim || GetImageDimensions(next_img_fn) == dim) { // We need user feedback here and since UI must occur in the UI thread (and we could be in any thread), we defer // to the Core which will definitely be in the UI thread and block here until we get an answer from the user QMetaObject::invokeMethod(Core::instance(), @@ -316,6 +322,25 @@ bool OIIODecoder::FileTypeIsSupported(const QString& fn) return true; } +QSize OIIODecoder::GetImageDimensions(const QString &fn) +{ + QSize sz; + auto in = OIIO::ImageInput::open(fn.toStdString()); + + if (in) { + sz.setWidth(in->spec().width); + sz.setHeight(in->spec().height); + + in->close(); + +#if OIIO_VERSION < 10903 + OIIO::ImageInput::destroy(in); +#endif + } + + return sz; +} + bool OIIODecoder::OpenImageHandler(const QString &fn) { image_ = OIIO::ImageInput::open(fn.toStdString()); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index ff278fa63..979923be2 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -68,6 +68,8 @@ private: static bool FileTypeIsSupported(const QString& fn); + static QSize GetImageDimensions(const QString& fn); + bool OpenImageHandler(const QString& fn); void CloseImageHandle(); diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index ee07f43c4..99521453c 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -23,10 +23,9 @@ set(OLIVE_SOURCES common/clamp.h common/commandlineparser.h common/commandlineparser.cpp - common/crashhandler.h - common/crashhandler.cpp - common/crashpadinterface.cpp common/crashpadinterface.h + common/crashpadinterface.cpp + common/crashpadutils.h common/debug.h common/debug.cpp common/define.h diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp deleted file mode 100644 index 469e45233..000000000 --- a/app/common/crashhandler.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "crashhandler.h" - -#include -#include -#include -#include -#include -#include -#include - -#if defined(Q_OS_WINDOWS) -#include -#include -#include -#include -#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX) -#include -#endif - -#include "common/filefunctions.h" - -OLIVE_NAMESPACE_ENTER - -void crash_handler(int sig) -{ - QString log_path = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("olive_crash")); - QFile output(log_path); - - output.open(QFile::WriteOnly); - QTextStream ostream(&output); - -#ifdef GITHASH - ostream << "Version: " << GITHASH << "\n"; -#endif - ostream << "Signal: " << sig << "\n\n"; - -#if defined(Q_OS_WINDOWS) - // Use Windows stackwalk API - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - - CONTEXT context; - memset(&context, 0, sizeof(CONTEXT)); - context.ContextFlags = CONTEXT_FULL; - RtlCaptureContext(&context); - - SymInitialize(process, NULL, TRUE); - - DWORD image; - STACKFRAME64 stackframe; - ZeroMemory(&stackframe, sizeof(STACKFRAME64)); - -#ifdef _M_IX86 - image = IMAGE_FILE_MACHINE_I386; - stackframe.AddrPC.Offset = context.Eip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Ebp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Esp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_X64 - image = IMAGE_FILE_MACHINE_AMD64; - stackframe.AddrPC.Offset = context.Rip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Rsp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Rsp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_IA64 - image = IMAGE_FILE_MACHINE_IA64; - stackframe.AddrPC.Offset = context.StIIP; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.IntSp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrBStore.Offset = context.RsBSP; - stackframe.AddrBStore.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.IntSp; - stackframe.AddrStack.Mode = AddrModeFlat; -#endif - - for (int i = 0; i < 50; i++) { - - BOOL result = StackWalk64( - image, process, thread, - &stackframe, &context, NULL, - SymFunctionTableAccess64, SymGetModuleBase64, NULL); - - if (!result) { break; } - - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - - ostream << "[" << i << "] "; - - if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) { - ostream << symbol->Name; - //printf("[%i] %s\n", i, symbol->Name); - } else { - ostream << "???"; - //printf("[%i] ???\n", i); - } - - ostream << "\n"; - } - - SymCleanup(process); -#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX) - void *array[10]; - size_t size; - - // get void*'s for all entries on the stack - size = backtrace(array, 10); - - // print out all the frames to stderr - backtrace_symbols_fd(array, size, output.handle()); -#endif - - output.close(); - - QString crash_handler_exe = QDir(qApp->applicationDirPath()).filePath(QStringLiteral("olive-crashhandler")); - QProcess::startDetached(crash_handler_exe, {log_path}); - - exit(1); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/common/crashpadinterface.cpp b/app/common/crashpadinterface.cpp index 770e8a84a..393cba4c6 100644 --- a/app/common/crashpadinterface.cpp +++ b/app/common/crashpadinterface.cpp @@ -23,75 +23,67 @@ #ifdef USE_CRASHPAD #include +#include #include #include #include +#include +#include "crashpadutils.h" #include "filefunctions.h" -#ifdef Q_OS_WINDOWS +#ifdef OS_WIN #include #endif -// Copied from base::FilePath to match its macro -#if defined(OS_POSIX) -// On most platforms, native pathnames are char arrays, and the encoding -// may or may not be specified. On Mac OS X, native pathnames are encoded -// in UTF-8. -#define TO_BASE_STRING_TYPE(x) x.toStdString() -#elif defined(OS_WIN) -// On Windows, for Unicode-aware applications, native pathnames are wchar_t -// arrays encoded in UTF-16. -#define TO_BASE_STRING_TYPE(x) x.toStdWString() -#endif // OS_WIN - crashpad::CrashpadClient *client; -bool ShowCrashConfirmation() +QString GenerateReportPath() { - QString msg = QCoreApplication::translate("CrashReport", - "We're sorry, Olive has crashed. " - "Would you like to send an error report to " - "help developers fix this issue?\n\n" - "Crash reports are anonymous and only send " - "non-specific details about your computer and" - "how the crash occurred."); - - return (QMessageBox::critical(nullptr, - QString(), - msg, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes); + return QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("reports")); } -#ifdef Q_OS_WINDOWS +base::FilePath GenerateReportPathForCrashpad() +{ + return base::FilePath(QSTRING_TO_BASE_STRING(GenerateReportPath())); +} + +#if defined(OS_WIN) LONG WINAPI Win32ExceptionHandler(_EXCEPTION_POINTERS *ExceptionInfo) { - if (ShowCrashConfirmation()) { - client->DumpAndCrash(ExceptionInfo); - } + QString crash_handler_exe = QDir(qApp->applicationDirPath()).filePath(QStringLiteral("olive-crashhandler")); + QProcess::startDetached(crash_handler_exe, {GenerateReportPath(), QString::number(QDateTime::currentSecsSinceEpoch())}); + + client->DumpAndCrash(ExceptionInfo); return EXCEPTION_CONTINUE_SEARCH; } +#elif defined(OS_LINUX) +bool LinuxExceptionHandler(int, siginfo_t*, ucontext_t*) +{ + QString crash_handler_exe = QDir(qApp->applicationDirPath()).filePath(QStringLiteral("olive-crashhandler")); + QProcess::startDetached(crash_handler_exe, {GenerateReportPath(), QString::number(QDateTime::currentSecsSinceEpoch())}); + + // Returning false signals to Crashpad to proceed with its own exception handling + return false; +} #endif bool InitializeCrashpad() { QString exe_dir = QCoreApplication::applicationDirPath(); +#ifdef OS_WIN + base::FilePath handler(QSTRING_TO_BASE_STRING(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler.exe")))); +#else // FIXME: On Linux, probably should put this in a subdir so that it doesn't conflict with // anything else in /usr/bin - -#ifdef Q_OS_WINDOWS - base::FilePath handler(TO_BASE_STRING_TYPE(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler.exe")))); -#else - base::FilePath handler(TO_BASE_STRING_TYPE(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler")))); + base::FilePath handler(QSTRING_TO_BASE_STRING(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler")))); #endif - base::FilePath reports_dir(TO_BASE_STRING_TYPE(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath("reports"))); + base::FilePath reports_dir = GenerateReportPathForCrashpad(); - base::FilePath metrics_dir(TO_BASE_STRING_TYPE(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath("metrics"))); - - std::string url = "https://olivevideoeditor.org/crashpad/report.php"; + base::FilePath metrics_dir(QSTRING_TO_BASE_STRING(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("metrics")))); // Metadata that will be posted to the server with the crash report map std::map annotations; @@ -105,19 +97,22 @@ bool InitializeCrashpad() std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(reports_dir); if (database == NULL) return false; - // Enable automated crash uploads + // Disable automated crash uploads crashpad::Settings *settings = database->GetSettings(); if (settings == NULL) return false; - settings->SetUploadsEnabled(true); + settings->SetUploadsEnabled(false); // Start crash handler client = new crashpad::CrashpadClient(); bool status = client->StartHandler(handler, reports_dir, metrics_dir, - url, annotations, arguments, true, true); + "https://olivevideoeditor.org/crashpad/report.php", + annotations, arguments, true, true); // Override Crashpad exception filter with our own -#ifdef Q_OS_WINDOWS +#if defined(OS_WIN) SetUnhandledExceptionFilter(Win32ExceptionHandler); +#elif defined(OS_LINUX) + crashpad::CrashpadClient::SetFirstChanceExceptionHandler(LinuxExceptionHandler); #endif return status; diff --git a/app/common/crashpadinterface.h b/app/common/crashpadinterface.h index 26bd17b65..2388ece96 100644 --- a/app/common/crashpadinterface.h +++ b/app/common/crashpadinterface.h @@ -23,7 +23,6 @@ #ifdef USE_CRASHPAD -#include #include #include diff --git a/app/common/crashhandler.h b/app/common/crashpadutils.h similarity index 50% rename from app/common/crashhandler.h rename to app/common/crashpadutils.h index 10ed30627..a2a36e49d 100644 --- a/app/common/crashhandler.h +++ b/app/common/crashpadutils.h @@ -18,15 +18,23 @@ ***/ -#ifndef CRASHHANDLER_H -#define CRASHHANDLER_H +#ifndef CRASHPADUTILS_H +#define CRASHPADUTILS_H -#include "common/define.h" +#include -OLIVE_NAMESPACE_ENTER +// Copied from base::FilePath to match its macro +#if defined(OS_POSIX) +// On most platforms, native pathnames are char arrays, and the encoding +// may or may not be specified. On Mac OS X, native pathnames are encoded +// in UTF-8. +#define QSTRING_TO_BASE_STRING(x) x.toStdString() +#define BASE_STRING_TO_QSTRING(x) QString::fromStdString(x) +#elif defined(OS_WIN) +// On Windows, for Unicode-aware applications, native pathnames are wchar_t +// arrays encoded in UTF-16. +#define QSTRING_TO_BASE_STRING(x) x.toStdWString() +#define BASE_STRING_TO_QSTRING(x) QString::fromStdWString(x) +#endif // OS_WIN -void crash_handler(int sig); - -OLIVE_NAMESPACE_EXIT - -#endif // CRASHHANDLER_H +#endif // CRASHPADUTILS_H diff --git a/app/dialog/crashhandler/crashhandler.cpp b/app/dialog/crashhandler/crashhandler.cpp index 7645eb5f9..e6120a5d9 100644 --- a/app/dialog/crashhandler/crashhandler.cpp +++ b/app/dialog/crashhandler/crashhandler.cpp @@ -20,66 +20,181 @@ #include "crashhandler.h" +#include +#include #include +#include #include -#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include "common/crashpadutils.h" + OLIVE_NAMESPACE_ENTER -CrashHandlerDialog::CrashHandlerDialog(const char *log_file) +CrashHandlerDialog::CrashHandlerDialog(const char *report_dir, const char* crash_time) { setWindowTitle(tr("Olive")); + crash_time_ = QString(crash_time).toULongLong(); + report_dir_ = report_dir; QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(new QLabel(tr("We're sorry, Olive has crashed. Please send the following log to the developers to " - "help resolve this."))); + layout->addWidget(new QLabel(tr("We're sorry, Olive has crashed. Please help us fix it by " + "sending an error report."))); - QTextEdit* edit = new QTextEdit(); - edit->setReadOnly(true); - layout->addWidget(edit); + summary_edit_ = new QTextEdit(); + summary_edit_->setPlaceholderText(tr("Describe what you were doing in as much detail as " + "possible. If you can, provide steps to reproduce this crash.")); - edit->append(QStringLiteral("Build Environment: %1 (%2)").arg(QSysInfo::buildCpuArchitecture(), QSysInfo::buildAbi())); - edit->append(QStringLiteral("Run Environment: %1").arg(QSysInfo::currentCpuArchitecture())); - edit->append(QStringLiteral("Kernel: %1 %2").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion())); - edit->append(QStringLiteral("System: %1 (%2 %3)").arg(QSysInfo::prettyProductName(), QSysInfo::productType(), QSysInfo::productVersion())); - edit->append(QString()); + layout->addWidget(summary_edit_); - QDialogButtonBox* buttons = new QDialogButtonBox(); + layout->addWidget(new QLabel(tr("Crash Report:"))); - // FIXME: Implement auto-reporting - //buttons->addButton(tr("Send Error Report"), QDialogButtonBox::AcceptRole); - //buttons->addButton(tr("Don't Send"), QDialogButtonBox::RejectRole); - buttons->addButton(QDialogButtonBox::Ok); + crash_report_ = new QTextEdit(); + crash_report_->setReadOnly(true); + crash_report_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + layout->addWidget(crash_report_); - connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); - layout->addWidget(buttons); + QHBoxLayout* btn_layout = new QHBoxLayout(); + btn_layout->setMargin(0); + btn_layout->addStretch(); - QFile log(log_file); - if (log.open(QFile::ReadOnly | QFile::Text)) { - edit->append(log.readAll()); + send_report_btn_ = new QPushButton(tr("Send Error Report")); + connect(send_report_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::SendErrorReport); + btn_layout->addWidget(send_report_btn_); - QMetaObject::invokeMethod(edit->verticalScrollBar(), - "setValue", - Qt::QueuedConnection, - Q_ARG(int, 0)); + dont_send_btn_ = new QPushButton(tr("Don't Send")); + connect(dont_send_btn_, &QPushButton::clicked, this, &CrashHandlerDialog::reject); + btn_layout->addWidget(dont_send_btn_); - log.close(); + layout->addLayout(btn_layout); + + crash_report_->setEnabled(false); + send_report_btn_->setEnabled(false); + + crash_report_->setText(tr("Waiting for crash report to be generated...")); + + AttemptToFindReport(); +} + +void CrashHandlerDialog::SetGUIObjectsEnabled(bool e) +{ + summary_edit_->setEnabled(e); + crash_report_->setEnabled(e); + send_report_btn_->setEnabled(e); + dont_send_btn_->setEnabled(e); +} + +void CrashHandlerDialog::GenerateReport() +{ + QProcess* p = new QProcess(); + + connect(p, QOverload::of(&QProcess::finished), + this, &CrashHandlerDialog::ReadProcessFinished); + connect(p, &QProcess::readyReadStandardOutput, this, &CrashHandlerDialog::ReadProcessHasData); + + QString stackwalk_filename; + +#if defined(OS_WIN) + stackwalk_filename = QStringLiteral("minidump_stackwalk.exe"); +#else + stackwalk_filename = QStringLiteral("minidump_stackwalk"); +#endif + + QString stackwalk_bin = QDir(qApp->applicationDirPath()).filePath(stackwalk_filename); + p->start(stackwalk_bin, {report_filename_}); + crash_report_->setText(QStringLiteral("Trying to run: %1").arg(stackwalk_bin)); +} + +void CrashHandlerDialog::ReplyFinished(QNetworkReply* reply) +{ + if (reply->error() == QNetworkReply::NoError) { + // Close dialog + QDialog::accept(); + } else { + QMessageBox::critical(this, tr("Upload Failed"), + tr("Failed to send error report. Please try again later."), + QMessageBox::Ok); + SetGUIObjectsEnabled(true); } } -void CrashHandlerDialog::accept() +void CrashHandlerDialog::AttemptToFindReport() { - QDialog::accept(); + // Retrieve reports from Crashpad database + std::unique_ptr database = crashpad::CrashReportDatabase::Initialize(base::FilePath(QSTRING_TO_BASE_STRING(QString(report_dir_)))); + std::vector reports; + database->GetCompletedReports(&reports); + + // Find report that was made after the crash time + foreach (const crashpad::CrashReportDatabase::Report& report, reports) { + if (report.creation_time >= crash_time_) { + report_filename_ = BASE_STRING_TO_QSTRING(report.file_path.value()); + break; + } + } + + // If we found it, use it, otherwise wait a second and try again + if (report_filename_.isEmpty()) { + // Couldn't find report, try again in one second + QTimer::singleShot(500, this, &CrashHandlerDialog::AttemptToFindReport); + } else { + GenerateReport(); + } } -void CrashHandlerDialog::reject() +void CrashHandlerDialog::ReadProcessHasData() { - QDialog::reject(); + report_data_.append(static_cast(sender())->readAllStandardOutput()); +} + +void CrashHandlerDialog::ReadProcessFinished() +{ + SetGUIObjectsEnabled(true); + crash_report_->setText(report_data_); + delete sender(); +} + +void CrashHandlerDialog::SendErrorReport() +{ + SetGUIObjectsEnabled(false); + + QNetworkAccessManager* manager = new QNetworkAccessManager(); + connect(manager, &QNetworkAccessManager::finished, this, &CrashHandlerDialog::ReplyFinished); + + QNetworkRequest request; + request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + request.setUrl(QStringLiteral("https://olivevideoeditor.org/crashpad/report.php")); + + // Create HTTP form + QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + // Create description section + QHttpPart desc_part; + desc_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("text/plain")); + desc_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"description\"")); + desc_part.setBody(summary_edit_->toPlainText().toUtf8()); + multipart->append(desc_part); + + // Create file section + QHttpPart file_part; + file_part.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/octet-stream")); + file_part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"upload_file_minidump\"; filename=\"%1\"") + .arg(QFileInfo(report_filename_).fileName())); + QFile* file = new QFile(report_filename_); + file->open(QFile::ReadOnly); + file_part.setBodyDevice(file); + file->setParent(multipart); // Delete file with multipart + multipart->append(file_part); + + manager->post(request, multipart); } OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/crashhandler/crashhandler.h b/app/dialog/crashhandler/crashhandler.h index 485f1d543..7ec91669b 100644 --- a/app/dialog/crashhandler/crashhandler.h +++ b/app/dialog/crashhandler/crashhandler.h @@ -21,7 +21,12 @@ #ifndef CRASHHANDLERDIALOG_H #define CRASHHANDLERDIALOG_H +#include #include +#include +#include +#include +#include #include "common/define.h" @@ -31,12 +36,39 @@ class CrashHandlerDialog : public QDialog { Q_OBJECT public: - CrashHandlerDialog(const char* log_file); + CrashHandlerDialog(const char* report_dir, const char* crash_time); -public slots: - virtual void accept() override; +private: + void SetGUIObjectsEnabled(bool e); - virtual void reject() override; + void GenerateReport(); + + QTextEdit* summary_edit_; + + QTextEdit* crash_report_; + + QPushButton* send_report_btn_; + + QPushButton* dont_send_btn_; + + QString report_filename_; + + time_t crash_time_; + + QString report_dir_; + + QByteArray report_data_; + +private slots: + void ReplyFinished(QNetworkReply *reply); + + void AttemptToFindReport(); + + void ReadProcessHasData(); + + void ReadProcessFinished(); + + void SendErrorReport(); }; diff --git a/app/dialog/crashhandler/crashhandlermain.cpp b/app/dialog/crashhandler/crashhandlermain.cpp index 88032fff4..d0fe76368 100644 --- a/app/dialog/crashhandler/crashhandlermain.cpp +++ b/app/dialog/crashhandler/crashhandlermain.cpp @@ -24,13 +24,13 @@ int main(int argc, char *argv[]) { - if (argc < 2) { + if (argc < 3) { return 1; } QApplication a(argc, argv); - OLIVE_NAMESPACE::CrashHandlerDialog chd(argv[1]); + OLIVE_NAMESPACE::CrashHandlerDialog chd(argv[1], argv[2]); chd.open(); return a.exec(); diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 2db075c25..28e8a50f5 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -126,7 +126,7 @@ void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, con FramePtr frame = Frame::Create(); frame->set_timestamp(time); - if (video_force_download_resolution_) { + if (video_force_download_resolution_ || texture.isNull()) { // If we're setting the resolution ourselves or we're zeroing it out, allocate the frame now frame->set_video_params(VideoParams(video_params_.width(), video_params_.height(), diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index b65234dc5..387baa12e 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -358,12 +358,17 @@ void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash) return; } + TimeRangeList ranges_to_invalidate; QMap::const_iterator i; for (i=time_hash_map_.constBegin(); i!=time_hash_map_.constEnd(); i++) { if (i.value() == hash) { - Invalidate(TimeRange(i.key(), i.key() + timebase_)); + ranges_to_invalidate.InsertTimeRange(TimeRange(i.key(), i.key() + timebase_)); } } + + foreach (const TimeRange& range, ranges_to_invalidate) { + Invalidate(range); + } } void FrameHashCache::ProjectInvalidated(Project *p) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 25f13ac58..983f422e1 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -99,11 +99,7 @@ void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e) void ViewerDisplayWidget::SetImage(FramePtr in_buffer) { - if (in_buffer && in_buffer->is_allocated()) { - last_loaded_buffer_ = in_buffer; - } else { - last_loaded_buffer_ = nullptr; - } + last_loaded_buffer_ = in_buffer; if (last_loaded_buffer_) { makeCurrent(); diff --git a/cmake/FindGoogleCrashpad.cmake b/cmake/FindGoogleCrashpad.cmake index 60ef6b849..17474a407 100644 --- a/cmake/FindGoogleCrashpad.cmake +++ b/cmake/FindGoogleCrashpad.cmake @@ -79,6 +79,10 @@ foreach (COMPONENT ${_crashpad_components}) list(APPEND CRASHPAD_LIBRARIES ${CRASHPAD_${UPPERCOMPONENT}_LIB}) endforeach() +if (UNIX AND NOT APPLE) + list(APPEND CRASHPAD_LIBRARIES ${CMAKE_DL_LIBS} Threads::Threads) +endif() + find_package_handle_standard_args(GoogleCrashpad REQUIRED_VARS CRASHPAD_LIBRARIES