Skip to content

Commit

Permalink
booru/browser: Allow tabs to be detached
Browse files Browse the repository at this point in the history
Use Gio::Application instead of Gtk::Main.
Gio::Application is used to create a unique process (requires dbus), nothing
really changes for the user but it allows for booru browser tabs to be
moved between any ahoviewer window cleanly.

Without dbus nothing changes, but tabs will not be drag-and-dropable
between different processes.

D-Bus on Windows seems to be rather slow resulting in slow application
startup speed.

Some code clean up and misc bug fixes are mixed into this commit.
  • Loading branch information
ahodesuka committed May 27, 2018
1 parent 2e4835c commit d9a8e9d
Show file tree
Hide file tree
Showing 23 changed files with 570 additions and 278 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -6,7 +6,7 @@ A GTK2 image viewer, manga reader, and booru browser.
### Dependencies
* C++ Compiler that supports the C++14 standard is required.
* gtkmm-2.4 `>= 2.20.0`
* glibmm-2.4 `>= 2.36.0`
* glibmm-2.4 `>= 2.46.0`
* libconfig++ `>= 1.4`
* libcurl `>= 7.32.0`
* libxml2
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.am
Expand Up @@ -22,6 +22,7 @@ ahoviewer_SOURCES = \
booru/site.cc \
booru/tagentry.cc \
booru/tagview.cc \
application.cc \
image.cc \
imagebox.cc \
imagelist.cc \
Expand Down
231 changes: 231 additions & 0 deletions src/application.cc
@@ -0,0 +1,231 @@
#include <gtkmm.h>
#include <iostream>
#include <curl/curl.h>
#include <libxml/parser.h>

#include "application.h"
using namespace AhoViewer;

#include "config.h"
#include "mainwindow.h"

#ifdef USE_OPENSSL
#include <openssl/opensslv.h>
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
#include <openssl/crypto.h>
#include <shared_mutex>
#include <deque>

std::deque<std::shared_timed_mutex> locks;

static void lock_callback(int mode, int type, const char*, int)
{
if ((mode & CRYPTO_LOCK))
{
if ((mode & CRYPTO_READ))
locks[type].lock_shared();
else
locks[type].lock();
}
else
{
if ((mode & CRYPTO_READ))
locks[type].unlock_shared();
else
locks[type].unlock();
}
}

static unsigned long thread_id()
{
return static_cast<unsigned long>(pthread_self());
}

static void init_openssl_locks()
{
locks.resize(CRYPTO_num_locks());
CRYPTO_set_id_callback(&thread_id);
CRYPTO_set_locking_callback(&lock_callback);
}
#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
#endif // USE_OPENSSL

#ifdef USE_GNUTLS
#include <gcrypt.h>
#if (GCRYPT_VERSION_NUMBER < 0x010600)
GCRY_THREAD_OPTION_PTHREAD_IMPL;

void init_gnutls_locks()
{
gcry_control(GCRYCTL_SET_THREAD_CBS);
}
#endif // GCRYPT_VERSION_NUMBER < 0x010600
#endif // USE_GNUTLS

#ifdef HAVE_GSTREAMER
#include <gst/gst.h>
#endif // HAVE_GSTREAMER

static void glibmm_log_filter(const gchar *ld, GLogLevelFlags ll, const gchar *msg, gpointer ud)
{
if (strcmp(msg, "Dropped dispatcher message as the dispatcher no longer exists") != 0)
g_log_default_handler(ld, ll, msg, ud);
}

extern const unsigned char ahoviewer_ui[];
extern const unsigned long ahoviewer_ui_size;

Application::Application()
: Gio::Application("com.github.ahodesuka.ahoviewer", Gio::APPLICATION_HANDLES_OPEN)
{
// Disgusting win32 api to start dbus-daemon and make it close when
// the ahoviewer process ends
#ifdef _WIN32
HANDLE job = CreateJobObject(NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
char cmd[] = "dbus-daemon.exe --session";

jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
si.cb = sizeof(si);

if (CreateProcess(NULL, cmd, NULL, NULL, FALSE,
CREATE_NO_WINDOW | CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB,
NULL, NULL, &si, &pi))
{
AssignProcessToJobObject(job, pi.hProcess);
ResumeThread(pi.hThread);
}
#endif // _WIN32
Glib::set_application_name(PACKAGE_NAME);
}

Application& Application::get_instance()
{
static Application i;
return i;
}

MainWindow* Application::create_window()
{
Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create();

try
{
builder->add_from_string(reinterpret_cast<const char*>(ahoviewer_ui),
ahoviewer_ui_size);
}
catch (const Glib::Error &ex)
{
std::cerr << "Gtk::Builder::add_from_string: " << ex.what() << std::endl;
return nullptr;
}

MainWindow *w = nullptr;
builder->get_widget_derived("MainWindow", w);

if (!w)
throw std::runtime_error("Failed to create window");

add_window(w);

return w;
}

int Application::run(int argc, char **argv)
{
register_application();

if (!is_remote())
{
gtk_init(&argc, &argv);
#ifdef HAVE_GSTREAMER
gst_init(&argc, &argv);
#endif // HAVE_GSTREAMER
}

return Gio::Application::run(argc, argv);
}

// Finds the first window with no local image list or creates a
// new window and then opens the file
void Application::on_open(const std::vector<Glib::RefPtr<Gio::File>> &f,
const Glib::ustring&)
{
auto it = std::find_if(m_Windows.cbegin(), m_Windows.cend(),
[](MainWindow *w){ return w->m_LocalImageList->empty(); });

if (m_Windows.size() == 0 || it == m_Windows.cend())
{
auto w = create_window();
w->open_file(f.front()->get_path());
}
else
{
(*it)->open_file(f.front()->get_path());
}
}

void Application::on_startup()
{
LIBXML_TEST_VERSION

curl_global_init(CURL_GLOBAL_DEFAULT);

curl_version_info_data *ver_info = curl_version_info(CURLVERSION_NOW);
std::string ssl_lib = ver_info->ssl_version;

#if defined(USE_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
if (ssl_lib.find("OpenSSL") != std::string::npos)
init_openssl_locks();
#endif // defined(USE_OPENSSL) && (OPENSSL_VERSION_NUMBER < 0x10100000L)

#if defined(USE_GNUTLS) && GCRYPT_VERSION_NUMBER < 0x010600
if (ssl_lib.find("GnuTLS") != std::string::npos)
init_gnutls_locks();
#endif // defined(USE_GNUTLS) && GCRYPT_VERSION_NUMBER < 0x010600

Gtk::Main::init_gtkmm_internals();

// Get rid of the stupid dispatcher warnings from glibmm
// That happen when cancelling downloads
g_log_set_handler("glibmm", G_LOG_LEVEL_WARNING, glibmm_log_filter, NULL);

Gio::Application::on_startup();
}

void Application::on_activate()
{
auto w = create_window();

if (m_Windows.size() == 1)
w->restore_last_file();

Gio::Application::on_activate();
}

void Application::add_window(MainWindow* w)
{
if (m_Windows.size() == 0)
w->m_OriginalWindow = true;

m_Windows.push_back(w);
w->signal_hide().connect(
sigc::bind(sigc::mem_fun(*this, &Application::remove_window), w));

hold();
}

void Application::remove_window(MainWindow* w)
{
m_Windows.erase(
std::remove(m_Windows.begin(), m_Windows.end(), w), m_Windows.end());
delete w;

if (m_Windows.size() == 1)
m_Windows.front()->m_OriginalWindow = true;

release();
}
32 changes: 32 additions & 0 deletions src/application.h
@@ -0,0 +1,32 @@
#ifndef _APPLICATION_H_
#define _APPLICATION_H_

#include <giomm/application.h>

namespace AhoViewer
{
class MainWindow;
class Application : public Gio::Application
{
public:
static Application& get_instance();

MainWindow* create_window();

int run(int argc, char **argv);
protected:
Application();

virtual void on_open(const std::vector<Glib::RefPtr<Gio::File>> &f,
const Glib::ustring&) override;
virtual void on_startup() override;
virtual void on_activate() override;
private:
void add_window(MainWindow* w);
void remove_window(MainWindow* w);

std::vector<MainWindow*> m_Windows;
};
}

#endif /* _APPLICATION_H_ */
45 changes: 20 additions & 25 deletions src/archive/zip.cc
Expand Up @@ -23,34 +23,20 @@ bool Zip::extract(const std::string &file) const

if (zip)
{
for (size_t i = 0, n = zip_get_num_entries(zip, 0); i < n; ++i)
{
struct zip_stat st;
zip_stat_init(&st);

if (zip_stat_index(zip, i, 0, &st) == -1)
{
std::cerr << "zip_stat_index: Failed to stat file #" << i
<< " in '" + m_Path + "'" << std::endl;
break;
}

if (st.name == file)
{
std::string fPath = Glib::build_filename(m_ExtractedPath, st.name);
struct zip_stat st;
zip_stat_init(&st);

if (!Glib::file_test(Glib::path_get_dirname(fPath), Glib::FILE_TEST_EXISTS))
g_mkdir_with_parents(Glib::path_get_dirname(fPath).c_str(), 0755);
if (zip_stat(zip, file.c_str(), 0, &st) == 0)
{
std::string fPath = Glib::build_filename(m_ExtractedPath, st.name);

if (!Glib::file_test(Glib::path_get_dirname(fPath), Glib::FILE_TEST_EXISTS))
g_mkdir_with_parents(Glib::path_get_dirname(fPath).c_str(), 0755);

zip_file *zfile = zip_fopen_index(zip, i, 0);
if (!zfile)
{
std::cerr << "zip_fopen_index: Failed to open file #" << i
<< " (" << st.name << ") in '" + m_Path + "'" << std::endl;
break;
}

zip_file *zfile = zip_fopen(zip, file.c_str(), 0);
if (zfile)
{
std::vector<char> buf(st.size);
int bufSize;
if ((bufSize = zip_fread(zfile, &buf[0], st.size)) != -1)
Expand All @@ -67,8 +53,17 @@ bool Zip::extract(const std::string &file) const

zip_fclose(zfile);
found = true;
break;
}
else
{
std::cerr << "zip_fopen_index: Failed to open file "
<< st.name << " in '" + m_Path + "'" << std::endl;
}
}
else
{
std::cerr << "zip_stat_index: Failed to stat file " << file
<< " in '" + m_Path + "'" << std::endl;
}

zip_close(zip);
Expand Down

0 comments on commit d9a8e9d

Please sign in to comment.