From 327a859043d24d149691fe154e2c03a356941960 Mon Sep 17 00:00:00 2001 From: Kirill Gavrilov Date: Thu, 2 Jan 2025 22:28:30 +0300 Subject: [PATCH] StShared - avoid linking to GTK2 by default, use zenity for openfile dialog --- .github/workflows/build_ubuntu_20.04.yml | 4 +- .github/workflows/build_ubuntu_22.04.yml | 4 +- .github/workflows/build_ubuntu_24.04.yml | 7 +- CMakeLists.txt | 19 +++-- StShared/StFileNode2.cpp | 94 ++++++++++++++++++++++- StShared/StLogger.cpp | 97 ++++++++++++++++++++++-- debian/control | 4 +- docs/INSTALL.md | 20 ++--- 8 files changed, 216 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build_ubuntu_20.04.yml b/.github/workflows/build_ubuntu_20.04.yml index 116b8cd6..cecd7aad 100644 --- a/.github/workflows/build_ubuntu_20.04.yml +++ b/.github/workflows/build_ubuntu_20.04.yml @@ -19,12 +19,12 @@ jobs: sudo apt-get update sudo apt-get install -y \ g++ ninja-build \ - libgtk2.0-dev \ libopenal-dev \ libopenvr-dev \ libgl-dev \ + libfreetype-dev libfontconfig-dev \ libconfig++-dev libconfig-dev \ - libxpm-dev \ + libxrandr-dev libxpm-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev - name: Compile run: | diff --git a/.github/workflows/build_ubuntu_22.04.yml b/.github/workflows/build_ubuntu_22.04.yml index 4c2686a5..aa0ef5ba 100644 --- a/.github/workflows/build_ubuntu_22.04.yml +++ b/.github/workflows/build_ubuntu_22.04.yml @@ -19,12 +19,12 @@ jobs: sudo apt-get update sudo apt-get install -y \ g++ ninja-build \ - libgtk2.0-dev \ libopenal-dev \ libopenvr-dev \ libgl-dev \ + libfreetype-dev libfontconfig-dev \ libconfig++-dev libconfig-dev \ - libxpm-dev \ + libxrandr-dev libxpm-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev - name: Compile run: | diff --git a/.github/workflows/build_ubuntu_24.04.yml b/.github/workflows/build_ubuntu_24.04.yml index 45e68ba7..e3e8c672 100644 --- a/.github/workflows/build_ubuntu_24.04.yml +++ b/.github/workflows/build_ubuntu_24.04.yml @@ -19,12 +19,13 @@ jobs: sudo apt-get update sudo apt-get install -y \ g++ ninja-build \ - libgtk2.0-dev \ + zenity \ libopenal-dev \ libopenvr-dev \ libgl-dev \ + libfreetype-dev libfontconfig-dev \ libconfig++-dev libconfig-dev \ - libxpm-dev \ + libxrandr-dev libxpm-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev - name: Compile run: | @@ -32,7 +33,7 @@ jobs: - name: Extra Dependencies run: | sudo apt-get install -y \ - build-essential devscripts cmake + build-essential debhelper devscripts cmake - name: Debian package run: | bash ./distribution/buildDebSrc.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d785991..7ea99394 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,18 +198,21 @@ elseif (ANDROID) # else() # Linux - set (USE_GTK ON) + set (USE_GTK OFF CACHE BOOL "Use GTK2 for openfile dialog.") set (USE_XLIB ON) set (USE_LIBCONFIG ON) - find_package (GTK2 REQUIRED gtk) - if (NOT GTK2_FOUND) - message (FATAL_ERROR "could not find GTK2 (libgtk2.0-dev)" ) - endif() - if (NOT GTK2_INCLUDE_DIRS) - message (FATAL_ERROR "could not find GTK2 (GTK2_INCLUDE_DIRS)" ) + if (USE_GTK) + find_package (GTK2 REQUIRED gtk) + if (NOT GTK2_FOUND) + message (FATAL_ERROR "could not find GTK2 (libgtk2.0-dev)" ) + endif() + if (NOT GTK2_INCLUDE_DIRS) + message (FATAL_ERROR "could not find GTK2 (GTK2_INCLUDE_DIRS)" ) + endif() + message (STATUS "GTK2_INCLUDE_DIRS=${GTK2_INCLUDE_DIRS}") + add_definitions(-DST_HAVE_GTK) endif() - message (STATUS "GTK2_INCLUDE_DIRS=${GTK2_INCLUDE_DIRS}") find_package (X11 REQUIRED) if (NOT X11_FOUND) diff --git a/StShared/StFileNode2.cpp b/StShared/StFileNode2.cpp index 02d29216..e01395b2 100644 --- a/StShared/StFileNode2.cpp +++ b/StShared/StFileNode2.cpp @@ -13,7 +13,7 @@ #include #elif defined(__ANDROID__) // -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) #include #include #include @@ -136,7 +136,7 @@ bool StFileNode::openFileDialog(StString& theFilePath, #elif defined(__ANDROID__) //bool ST_NOT_IMPLEMENTED_FOR_ANDROID = true; return false; -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) if(!StMessageBox::initGlobals()) { return false; } @@ -167,6 +167,96 @@ bool StFileNode::openFileDialog(StString& theFilePath, gdk_flush(); // we need this call! gdk_threads_leave(); return isFileSelected; +#elif(__linux__) + // use Zenity + static const char ST_ZENITY[] = "/usr/bin/zenity"; + + StString aCmd = StString(ST_ZENITY) + " --file-selection --modal"; + if (theToSave) { + aCmd += " --save --confirm-overwrite"; + } + + if (!theInfo.Title.isEmpty()) { + aCmd += StString(" --title=\"") + theInfo.Title + "\""; + } + + if (!theInfo.Folder.isEmpty()) { + StString aFolder = theInfo.Folder; + if (!aFolder.isEndsWith('/')) { + aFolder += "/"; + } + aCmd += StString(" --filename=\"") + aFolder + "\""; + } + + StString aFilterString, anAllSupportedExt, anExtraSupportedExt; + for(size_t aMimeId = 0; aMimeId < theInfo.Filter.size(); ++aMimeId) { + const StMIME& aMime = theInfo.Filter[aMimeId]; + if(aMimeId > 0) { + anAllSupportedExt += StString(' '); + } + anAllSupportedExt += StString("*.") + aMime.getExtension(); + } + + // add 'All supported' + if(!anAllSupportedExt.isEmpty() && theInfo.Filter.size() > 1) { + aCmd += " --file-filter=\""; + aCmd += !theInfo.FilterTitle.isEmpty() ? theInfo.FilterTitle : StString("All supported"); + aCmd += StString(" | ") + anAllSupportedExt + "\""; + } + + // add 'Extra supported' + for(size_t aMimeId = 0; aMimeId < theInfo.ExtraFilter.size(); ++aMimeId) { + const StMIME& aMime = theInfo.ExtraFilter[aMimeId]; + if(aMimeId > 0) { + anExtraSupportedExt += StString(' '); + } + anExtraSupportedExt += StString("*.") + aMime.getExtension(); + } + if(!anExtraSupportedExt.isEmpty() && theInfo.ExtraFilter.size() > 1) { + aCmd += " --file-filter=\""; + aCmd += !theInfo.ExtraFilterTitle.isEmpty() ? theInfo.ExtraFilterTitle : StString("Extra supported"); + aCmd += StString(" | ") + anExtraSupportedExt + "\""; + } + + // add per-type filters + for(size_t aMimeId = 0; aMimeId < theInfo.Filter.size(); ++aMimeId) { + const StMIME& aMime = theInfo.Filter[aMimeId]; + if((aMimeId > 0) && (aMime.getDescription() == theInfo.Filter[aMimeId - 1].getDescription())) { + // append extension to previous MIME (prevent duplication) + aCmd = aCmd.subString(0, aCmd.getLength() - 1); // backstep + aCmd += StString(" *.") + aMime.getExtension() + "\""; + } else { + aCmd += StString(" --file-filter=\"") + aMime.getDescription() + StString(" | *.") + aMime.getExtension() + "\""; + } + } + + // add 'Any File' + aCmd += " --file-filter=\"All Files (*) | *\""; + + //ST_DEBUG_LOG(aCmd); + FILE* aPipe = popen(aCmd.toCString(), "r"); + if (aPipe == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " is not found!"); + return false; + } + + char aBuffer[4096] = {}; + if (fgets(aBuffer, sizeof(aBuffer), aPipe) == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " calling failure"); + } + int aRes = pclose(aPipe); + if (aRes != 0) { + return false; + } + + theFilePath = aBuffer; + if (theFilePath.isEndsWith('\n')) { + if (theFilePath.getLength() == 1) { + return false; + } + theFilePath = theFilePath.subString(0, theFilePath.getLength() - 1); + } + return true; #endif } diff --git a/StShared/StLogger.cpp b/StShared/StLogger.cpp index acef69dc..0af1fd93 100644 --- a/StShared/StLogger.cpp +++ b/StShared/StLogger.cpp @@ -13,6 +13,8 @@ #if defined(__ANDROID__) #include +#elif defined(__linux__) + static const char ST_ZENITY[] = "/usr/bin/zenity"; #endif // we do not use st::cerr here to avoid @@ -217,7 +219,7 @@ void StLogger::write(const StString& theMessage, #include #elif defined(__ANDROID__) // -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) #include #include namespace { @@ -258,8 +260,12 @@ void StMessageBox::setCallback(msgBoxFunc_t theFunc) { #elif defined(__linux__) bool StMessageBox::initGlobals() { +#ifdef ST_HAVE_GTK static const bool isInitOK = stGtkInitForce(); return isInitOK; +#else + return true; +#endif } #endif @@ -273,7 +279,7 @@ void StMessageBox::Info(const StString& theMessage) { } else { StMessageBox::InfoConsole(theMessage); } -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) if(initGlobals()) { gdk_threads_enter(); GtkWidget* dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", theMessage.toCString()); @@ -282,6 +288,26 @@ void StMessageBox::Info(const StString& theMessage) { gdk_flush(); // we need this call! gdk_threads_leave(); } +#elif defined(__linux__) + // use Zenity + /*StArrayList anArgs(4); + anArgs.add("--info").add("--no-markup").add("--no-wrap").add(StString() + "--text=" + theMessage + ""); + if(!StProcess::execProcess(ST_ZENITY, anArgs)) { ST_DEBUG_LOG(ST_ZENITY + " is not found!"); }*/ + + const StString aMsg = theMessage.replace(stCString("\""), stCString("\\\"")); + const StString aCmd = StString(ST_ZENITY) + " --info --no-markup --no-wrap --text=\"" + aMsg + "\""; + + FILE* aPipe = popen(aCmd.toCString(), "r"); + if (aPipe == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " is not found!"); + return; + } + + char aBuffer[4096] = {}; + if (fgets(aBuffer, sizeof(aBuffer), aPipe) == NULL) { + // + } + pclose(aPipe); #endif } @@ -295,7 +321,7 @@ void StMessageBox::Warn(const StString& theMessage) { } else { StMessageBox::WarnConsole(theMessage); } -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) if(initGlobals()) { gdk_threads_enter(); GtkWidget* dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", theMessage.toCString()); @@ -304,6 +330,26 @@ void StMessageBox::Warn(const StString& theMessage) { gdk_flush(); // we need this call! gdk_threads_leave(); } +#elif defined(__linux__) + // use Zenity + /*StArrayList anArgs(4); + anArgs.add("--warning").add("--no-markup").add("--no-wrap").add(StString() + "--text=" + theMessage + ""); + if(!StProcess::execProcess(ST_ZENITY, anArgs)) { ST_DEBUG_LOG(ST_ZENITY + " is not found!"); }*/ + + const StString aMsg = theMessage.replace(stCString("\""), stCString("\\\"")); + const StString aCmd = StString(ST_ZENITY) + " --warning --no-markup --no-wrap --text=\"" + aMsg + "\""; + + FILE* aPipe = popen(aCmd.toCString(), "r"); + if (aPipe == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " is not found!"); + return; + } + + char aBuffer[4096] = {}; + if (fgets(aBuffer, sizeof(aBuffer), aPipe) == NULL) { + // + } + pclose(aPipe); #endif } @@ -317,7 +363,7 @@ void StMessageBox::Error(const StString& theMessage) { } else { StMessageBox::ErrorConsole(theMessage); } -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) if(initGlobals()) { gdk_threads_enter(); GtkWidget* dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", theMessage.toCString()); @@ -326,6 +372,26 @@ void StMessageBox::Error(const StString& theMessage) { gdk_flush(); // we need this call! gdk_threads_leave(); } +#elif defined(__linux__) + // use Zenity + /*StArrayList anArgs(4); + anArgs.add("--error").add("--no-markup").add("--no-wrap").add(StString() + "--text=" + theMessage + ""); + if(!StProcess::execProcess(ST_ZENITY, anArgs)) { ST_DEBUG_LOG(ST_ZENITY + " is not found!"); }*/ + + const StString aMsg = theMessage.replace(stCString("\""), stCString("\\\"")); + const StString aCmd = StString(ST_ZENITY) + " --error --no-markup --no-wrap --text=\"" + aMsg + "\""; + + FILE* aPipe = popen(aCmd.toCString(), "r"); + if (aPipe == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " is not found!"); + return; + } + + char aBuffer[4096] = {}; + if (fgets(aBuffer, sizeof(aBuffer), aPipe) == NULL) { + // + } + pclose(aPipe); #endif } @@ -338,7 +404,7 @@ bool StMessageBox::Question(const StString& theMessage) { } else { return StMessageBox::QuestionConsole(theMessage); } -#elif defined(__linux__) +#elif defined(ST_HAVE_GTK) if(initGlobals()) { gdk_threads_enter(); GtkWidget* aDialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", theMessage.toCString()); @@ -349,6 +415,27 @@ bool StMessageBox::Question(const StString& theMessage) { return anAnswer == GTK_RESPONSE_YES; } return false; +#elif defined(__linux__) + // use Zenity + /*StArrayList anArgs(4); + anArgs.add("--question").add("--no-markup").add("--no-wrap").add(StString() + "--text=" + theMessage + ""); + if(!StProcess::execProcess(ST_ZENITY, anArgs)) { ST_DEBUG_LOG(ST_ZENITY + " is not found!"); }*/ + + const StString aMsg = theMessage.replace(stCString("\""), stCString("\\\"")); + const StString aCmd = StString(ST_ZENITY) + " --question --no-markup --no-wrap --text=\"" + aMsg + "\""; + + FILE* aPipe = popen(aCmd.toCString(), "r"); + if (aPipe == NULL) { + ST_DEBUG_LOG(ST_ZENITY + " is not found!"); + return false; + } + + char aBuffer[4096] = {}; + if (fgets(aBuffer, sizeof(aBuffer), aPipe) == NULL) { + // + } + int aRes = pclose(aPipe); + return aRes == 0; #endif } diff --git a/debian/control b/debian/control index 09458ee1..3831aa5b 100644 --- a/debian/control +++ b/debian/control @@ -2,13 +2,13 @@ Source: sview Section: contrib/video Priority: extra Maintainer: Kirill Gavrilov -Build-Depends: debhelper (>= 7.0.50~), g++ (>= 4.0), cmake, ninja-build, libopenal-dev, libgl-dev, libgtk2.0-dev, libxpm-dev, libavcodec-dev, libavdevice-dev, libavformat-dev, libavutil-dev, libswscale-dev, libconfig++-dev, libfreetype-dev, libxpm-dev, libopenvr-dev +Build-Depends: debhelper (>= 7.0.50~), g++ (>= 4.0), cmake, ninja-build, libopenal-dev, libgl-dev, libxrandr-dev, libxpm-dev, libavcodec-dev, libavdevice-dev, libavformat-dev, libavutil-dev, libswscale-dev, libconfig++-dev, libfontconfig-dev, libfreetype-dev, libxpm-dev, libopenvr-dev Standards-Version: 3.9.1 Homepage: http://www.sview.ru Package: sview Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, xdg-utils +Depends: ${shlibs:Depends}, ${misc:Depends}, xdg-utils, zenity Description: stereoscopic media player sView . sView is a stereoscopic Image Viewer and Movie Player. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 83c9e2e6..c73c41f0 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -4,29 +4,31 @@ sView requires several 3rd-party components for building: * C/C++ compiler (g++, MSVC 2010+) * FFmpeg (https://www.ffmpeg.org) * OpenAL soft (https://openal-soft.org/) -* GTK2+, Linux only (https://www.gtk.org) +* FreeType * libconfig++, Linux and Android (https://www.hyperrealm.com/libconfig/libconfig.html) -* libxpm, Linux only +* libxrandr, libxpm, libfontconfig, Linux only +* zenity, Linux only (https://help.gnome.org/users/zenity/stable/) On Debian/Ubuntu you might use the following command to install all dependencies at once: ~~~~~ sudo apt-get install \ - g++ \ - libgtk2.0-dev \ + g++ cmake \ + zenity \ libopenal-dev \ libgl-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libswscale-dev \ libconfig++-dev libconfig-dev \ - libxpm-dev \ + libfreetype-dev libfontconfig-dev \ + libxrandr-dev libxpm-dev \ libopenvr-dev ~~~~~ - + The similar command for RPM-based distributives: ~~~~~ yum install gcc gcc-c++ \ - gtk+-devel gtk2-devel \ + gtk+-devel \ mesa-libGLU-devel glew-devel \ openal-devel \ libconfig-devel @@ -38,7 +40,7 @@ yum install ffmpeg-devel ALT Linux: ~~~~~ apt-get install \ - gcc8-c++ gcc-c++ libGLU-devel libgtk+2-devel libXrandr-devel libfreetype-devel \ + gcc8-c++ gcc-c++ libGLU-devel libXrandr-devel libfreetype-devel \ libopenal-devel libconfig-devel libconfig-c++-devel libXpm-devel \ libavcodec-devel libavdevice-devel libavformat-devel libavutil-devel libswscale-devel ~~~~~ @@ -74,7 +76,7 @@ This is not a self-sustained solution, but rather a wrapper over existing UNIX ` ## IV. CMake -sView comes within CMake scripts, compatible with Visual Studio 2015 and higher. +sView comes within CMake scripts, compatible with Windows (Visual Studio 2015 and higher) and Linux. ## V. Building options