Make the GTK dependency optional at runtime on Linux

Build the GTK dialog as a separate shared library which
is embedded into the main updater binary.

At runtime the updater extracts the shared library and attempts to load it.
If this succeeds, the GTK UI is used, otherwise the updater will fall
back to something else - currently a silent install.
This commit is contained in:
Robert Knight 2011-08-26 16:59:03 +01:00
parent 93f58e77da
commit 630d64e859
6 changed files with 189 additions and 16 deletions

View file

@ -16,6 +16,8 @@ if (ENABLE_GTK)
include_directories(${GTK2_INCLUDE_DIR}) include_directories(${GTK2_INCLUDE_DIR})
add_definitions(${GTK2_CFLAGS}) add_definitions(${GTK2_CFLAGS})
add_definitions(-DENABLE_GTK) add_definitions(-DENABLE_GTK)
add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
target_link_libraries(updatergtk ${GTK2_LINK_FLAGS})
endif() endif()
add_definitions(-DTIXML_USE_STL) add_definitions(-DTIXML_USE_STL)
@ -30,9 +32,6 @@ set (SOURCES
UpdaterOptions.cpp UpdaterOptions.cpp
) )
if (ENABLE_GTK)
set(SOURCES ${SOURCES} UpdateDialogGtk.cpp)
endif()
if (APPLE) if (APPLE)
set(SOURCES ${SOURCES} UpdateDialogCocoa.mm) set(SOURCES ${SOURCES} UpdateDialogCocoa.mm)
endif() endif()
@ -51,8 +50,19 @@ set (HEADERS
) )
if (ENABLE_GTK) if (ENABLE_GTK)
set(HEADERS ${HEADERS} UpdateDialogGtk.h) # embed the GTK helper library into the updater binary.
# At runtime it will be extracted and loaded if the
# GTK libraries are available
set(GTK_UPDATER_LIB libupdatergtk.so)
set(GTK_BIN_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
add_custom_command(OUTPUT ${GTK_BIN_FILE}
COMMAND xxd -i ${GTK_UPDATER_LIB} ${GTK_BIN_FILE}
DEPENDS updatergtk)
set(SOURCES ${SOURCES} UpdateDialogGtkWrapper.cpp ${GTK_BIN_FILE})
set(HEADERS ${HEADERS} UpdateDialogGtkWrapper.h)
endif() endif()
if (APPLE) if (APPLE)
set(HEADERS ${HEADERS} UpdateDialogCocoa.h) set(HEADERS ${HEADERS} UpdateDialogCocoa.h)
endif() endif()
@ -70,14 +80,8 @@ target_link_libraries(updatershared
tinyxml tinyxml
minizip minizip
tinythread tinythread
${GTK2_LINK_FLAGS}
) )
if (ENABLE_GTK)
target_link_libraries(updatershared
${GTK2_LINK_FLAGS})
endif()
if (UNIX) if (UNIX)
target_link_libraries(updatershared pthread) target_link_libraries(updatershared pthread)
endif() endif()
@ -97,6 +101,7 @@ endif()
target_link_libraries(updater target_link_libraries(updater
updatershared updatershared
dl
) )
install(TARGETS updater RUNTIME DESTINATION bin) install(TARGETS updater RUNTIME DESTINATION bin)

View file

@ -1,11 +1,37 @@
#include "UpdateDialogGtk.h" #include "UpdateDialogGtk.h"
#include "Log.h"
#include "StringUtils.h" #include "StringUtils.h"
#include <glib.h> #include <glib.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv)
{
UpdateDialogGtk* dialog = new UpdateDialogGtk;
dialog->init(argc,argv);
return dialog;
}
void update_dialog_gtk_exec(UpdateDialogGtk* dialog)
{
dialog->exec();
}
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage)
{
dialog->updateError(errorMessage);
}
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage)
{
dialog->updateProgress(percentage);
}
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog)
{
dialog->updateFinished();
}
UpdateDialogGtk::UpdateDialogGtk() UpdateDialogGtk::UpdateDialogGtk()
: m_hadError(false) : m_hadError(false)
{ {

View file

@ -30,3 +30,14 @@ class UpdateDialogGtk : public UpdateObserver
bool m_hadError; bool m_hadError;
}; };
// helper functions which allow the GTK dialog to be loaded dynamically
// at runtime and used only if the GTK libraries are actually present
extern "C" {
UpdateDialogGtk* update_dialog_gtk_new(int argc, char** argv);
void update_dialog_gtk_exec(UpdateDialogGtk* dialog);
void update_dialog_gtk_handle_error(UpdateDialogGtk* dialog, const std::string& errorMessage);
void update_dialog_gtk_handle_progress(UpdateDialogGtk* dialog, int percentage);
void update_dialog_gtk_handle_finished(UpdateDialogGtk* dialog);
}

View file

@ -0,0 +1,95 @@
#include "UpdateDialogGtkWrapper.h"
#include "Log.h"
#include "StringUtils.h"
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
// GTK updater UI library embedded into
// the updater binary
extern unsigned char libupdatergtk_so[];
extern unsigned int libupdatergtk_so_len;
// pointers to helper functions in the GTK updater UI library
UpdateDialogGtk* (*update_dialog_gtk_new)(int,char**) = 0;
void (*update_dialog_gtk_exec)(UpdateDialogGtk* dialog) = 0;
void (*update_dialog_gtk_handle_error)(UpdateDialogGtk* dialog, const std::string& errorMessage) = 0;
void (*update_dialog_gtk_handle_progress)(UpdateDialogGtk* dialog, int percentage) = 0;
void (*update_dialog_gtk_handle_finished)(UpdateDialogGtk* dialog) = 0;
#define BIND_FUNCTION(library,function) \
function = reinterpret_cast<typeof(function)>(dlsym(library,#function));
bool extractFileFromBinary(const char* path, const void* buffer, size_t length)
{
int fd = open(path,O_CREAT | O_WRONLY | O_TRUNC,0755);
size_t count = write(fd,buffer,length);
if (fd < 0 || count < length)
{
if (fd >= 0)
{
close(fd);
}
return false;
}
close(fd);
return true;
}
UpdateDialogGtkWrapper::UpdateDialogGtkWrapper()
: m_dialog(0)
{
}
bool UpdateDialogGtkWrapper::init(int argc, char** argv)
{
const char* libPath = "/tmp/libupdatergtk.so";
if (!extractFileFromBinary(libPath,libupdatergtk_so,libupdatergtk_so_len))
{
LOG(Warn,"Failed to load the GTK UI library - " + std::string(strerror(errno)));
}
void* gtkLib = dlopen(libPath,RTLD_LAZY);
if (!gtkLib)
{
LOG(Warn,"Failed to load the GTK UI - " + std::string(dlerror()));
return false;
}
BIND_FUNCTION(gtkLib,update_dialog_gtk_new);
BIND_FUNCTION(gtkLib,update_dialog_gtk_exec);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_error);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_progress);
BIND_FUNCTION(gtkLib,update_dialog_gtk_handle_finished);
m_dialog = update_dialog_gtk_new(argc,argv);
return true;
}
void UpdateDialogGtkWrapper::exec()
{
update_dialog_gtk_exec(m_dialog);
}
void UpdateDialogGtkWrapper::updateError(const std::string& errorMessage)
{
update_dialog_gtk_handle_error(m_dialog,errorMessage);
}
void UpdateDialogGtkWrapper::updateProgress(int percentage)
{
update_dialog_gtk_handle_progress(m_dialog,percentage);
}
void UpdateDialogGtkWrapper::updateFinished()
{
update_dialog_gtk_handle_finished(m_dialog);
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "UpdateObserver.h"
class UpdateDialogGtk;
/** A wrapper around UpdateDialogGtk which allows the GTK UI to
* be loaded dynamically at runtime if the GTK libraries are
* available.
*/
class UpdateDialogGtkWrapper : public UpdateObserver
{
public:
UpdateDialogGtkWrapper();
/** Attempt to load and initialize the GTK updater UI.
* If this function returns false, other calls in UpdateDialogGtkWrapper
* may not be used.
*/
bool init(int argc, char** argv);
void exec();
virtual void updateError(const std::string& errorMessage);
virtual void updateProgress(int percentage);
virtual void updateFinished();
private:
UpdateDialogGtk* m_dialog;
};

View file

@ -8,7 +8,7 @@
#include "tinythread.h" #include "tinythread.h"
#if defined(PLATFORM_LINUX) and defined(ENABLE_GTK) #if defined(PLATFORM_LINUX) and defined(ENABLE_GTK)
#include "UpdateDialogGtk.h" #include "UpdateDialogGtkWrapper.h"
#endif #endif
#if defined(PLATFORM_MAC) #if defined(PLATFORM_MAC)
@ -88,11 +88,17 @@ int main(int argc, char** argv)
void runWithUi(int argc, char** argv, UpdateInstaller* installer) void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{ {
#ifdef ENABLE_GTK #ifdef ENABLE_GTK
UpdateDialogGtk dialog; UpdateDialogGtkWrapper dialog;
bool useGtk = dialog.init(argc,argv);
if (useGtk)
{
installer->setObserver(&dialog); installer->setObserver(&dialog);
dialog.init(argc,argv); }
tthread::thread updaterThread(runUpdaterThread,installer); tthread::thread updaterThread(runUpdaterThread,installer);
if (useGtk)
{
dialog.exec(); dialog.exec();
}
updaterThread.join(); updaterThread.join();
#else #else
// no UI available - do a silent install // no UI available - do a silent install