From 93428983612b8837df0645a280c15493ea67965d Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Sat, 12 Nov 2016 18:32:09 -0500 Subject: [PATCH] - Added support for GTK3 (thanks to "MineyMe" and edward-san) - Replaced GTK/OS X (note different from Cocoa) clipboard code with SDL clipboard API. - Removed requirement to link to GTK in order to compile with GTK support. - GTK is no longer init'd if the GTK IWAD picker is not used. - Our usage of GTK is such that the dynamic loader can work with both GTK2 and GTK3 depending on what's installed. - Since we're accumulating a lot of library loaders I've built a generic interface as FModule which replaces TOptWin32Proc and the loaders in the OpenAL and Fluidsynth code. --- src/CMakeLists.txt | 37 ++- src/i_module.cpp | 101 +++++++ src/i_module.h | 229 +++++++++++++++ src/posix/sdl/i_main.cpp | 11 - src/posix/sdl/i_system.cpp | 329 +-------------------- src/posix/unix/iwadpicker_gtk.cpp | 331 ++++++++++++++++++++++ src/sound/i_musicinterns.h | 64 ++--- src/sound/music_fluidsynth_mididevice.cpp | 177 ++++-------- src/sound/oalload.h | 17 +- src/sound/oalsound.cpp | 36 +-- src/win32/i_main.cpp | 23 ++ src/win32/i_specialpaths.cpp | 16 +- src/win32/i_system.cpp | 11 +- src/win32/i_system.h | 24 -- src/win32/optwin32.h | 24 ++ src/win32/win32video.cpp | 9 +- 16 files changed, 857 insertions(+), 582 deletions(-) create mode 100644 src/i_module.cpp create mode 100644 src/i_module.h create mode 100644 src/posix/unix/iwadpicker_gtk.cpp create mode 100644 src/win32/optwin32.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 576da3647..04e6087ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -189,6 +189,7 @@ else() set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) set( NO_GTK ON ) + set( DYN_GTK OFF ) # Prevent inclusion of fp.h and FixMath.h from Carbon framework # Declarations from these files are not used but cause the following conflicts: @@ -197,6 +198,7 @@ else() add_definitions( -D__FP__ -D__FIXMATH__ ) else() option( NO_GTK "Disable GTK+ dialogs (Not applicable to Windows)" ) + option( DYN_GTK "Load GTK+ at runtime instead of compile time" ON ) option( VALGRIND "Add special Valgrind sequences to self-modifying code" ) set( FMOD_SEARCH_PATHS @@ -212,22 +214,37 @@ else() # Use GTK+ for the IWAD picker, if available. if( NOT NO_GTK ) - pkg_check_modules( GTK2 gtk+-2.0 ) - if( GTK2_FOUND ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} ${GTK2_LIBRARIES} ) - include_directories( ${GTK2_INCLUDE_DIRS} ) - link_directories( ${GTK2_LIBRARY_DIRS} ) + pkg_check_modules( GTK3 gtk+-3.0 ) + if( GTK3_FOUND ) + if( NOT DYN_GTK ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${GTK3_LIBRARIES} ) + endif() + include_directories( ${GTK3_INCLUDE_DIRS} ) + link_directories( ${GTK3_LIBRARY_DIRS} ) else() - set( NO_GTK ON ) + pkg_check_modules( GTK2 gtk+-2.0 ) + if( GTK2_FOUND ) + if( NOT DYN_GTK ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${GTK2_LIBRARIES} ) + endif() + include_directories( ${GTK2_INCLUDE_DIRS} ) + link_directories( ${GTK2_LIBRARY_DIRS} ) + else() + set( NO_GTK ON ) + endif() endif() endif() endif() set( NASM_NAMES nasm ) if( NO_GTK ) - add_definitions( -DNO_GTK=1 ) + add_definitions( -DNO_GTK ) + elseif( DYN_GTK ) + add_definitions( -DDYN_GTK=1 ) + else() + add_definitions( -DDYN_GTK=0 ) endif() - + # Non-Windows version also needs SDL except native OS X backend if( NOT APPLE OR NOT OSX_COCOA_BACKEND ) find_package( SDL2 REQUIRED ) @@ -688,7 +705,8 @@ set( PLAT_SDL_SOURCES posix/sdl/sdlvideo.cpp posix/sdl/st_start.cpp ) set( PLAT_UNIX_SOURCES - posix/unix/i_specialpaths.cpp ) + posix/unix/i_specialpaths.cpp + posix/unix/iwadpicker_gtk.cpp ) set( PLAT_OSX_SOURCES posix/osx/iwadpicker_cocoa.mm posix/osx/i_specialpaths.mm @@ -1078,6 +1096,7 @@ set (PCH_SOURCES gi.cpp gitinfo.cpp hu_scores.cpp + i_module.cpp i_net.cpp info.cpp keysections.cpp diff --git a/src/i_module.cpp b/src/i_module.cpp new file mode 100644 index 000000000..1ed40310f --- /dev/null +++ b/src/i_module.cpp @@ -0,0 +1,101 @@ +/* +** i_module.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2016 Braden Obrzut +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "i_module.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define USE_WINDOWS_DWORD +#else +#include +#endif + +#ifndef _WIN32 +#define LoadLibrary(x) dlopen((x), RTLD_LAZY) +#define GetProcAddress(a,b) dlsym((a),(b)) +#define FreeLibrary(x) dlclose((x)) +using HMODULE = void*; +#endif + +bool FModule::Load(std::initializer_list libnames) +{ + for(auto lib : libnames) + { + if(!Open(lib)) + continue; + + StaticProc *proc; + for(proc = reqSymbols;proc;proc = proc->Next) + { + if(!(proc->Call = GetSym(proc->Name)) && !proc->Optional) + { + Unload(); + break; + } + } + + if(IsLoaded()) + return true; + } + + return false; +} + +void FModule::Unload() +{ + if(handle) + { + FreeLibrary((HMODULE)handle); + handle = nullptr; + } +} + +bool FModule::Open(const char* lib) +{ +#ifdef _WIN32 + if((handle = GetModuleHandle(lib)) != nullptr) + return true; +#else + // Loading an empty string in Linux doesn't do what we expect it to. + if(*lib == '\0') + return false; +#endif + handle = LoadLibrary(lib); + return handle != nullptr; +} + +void *FModule::GetSym(const char* name) +{ + return GetProcAddress((HMODULE)handle, name); +} diff --git a/src/i_module.h b/src/i_module.h new file mode 100644 index 000000000..9690d8c2f --- /dev/null +++ b/src/i_module.h @@ -0,0 +1,229 @@ +/* +** i_module.h +** +**--------------------------------------------------------------------------- +** Copyright 2016 Braden Obrzut +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#pragma once + +#include +#include + +/* FModule Run Time Library Loader + * + * This provides an interface for loading optional dependencies or detecting + * version specific symbols at run time. These classes largely provide an + * interface for statically declaring the symbols that are going to be used + * ahead of time, thus should not be used on the stack or as part of a + * dynamically allocated object. The procedure templates take the FModule + * as a template argument largely to make such use of FModule awkward. + * + * Declared procedures register themselves with FModule and the module will not + * be considered loaded unless all required procedures can be resolved. In + * order to remove the need for boilerplate code, optional procedures do not + * enforce the requirement that the value is null checked before use. As a + * debugging aid debug builds will check that operator bool was called at some + * point, but this is just a first order sanity check. + */ + +class FModule; +class FStaticModule; + +template +class TOptProc; + +template +class TReqProc; + +template +class TStaticProc; + +class FModule +{ + template + friend class TOptProc; + template + friend class TReqProc; + + struct StaticProc + { + void *Call; + const char* Name; + StaticProc *Next; + bool Optional; + }; + + void RegisterStatic(StaticProc &proc) + { + proc.Next = reqSymbols; + reqSymbols = &proc; + } + + void *handle = nullptr; + + // Debugging aid + const char *name; + + // Since FModule is supposed to be statically allocated it is assumed that + // reqSymbols will be initialized to nullptr avoiding initialization order + // problems with declaring procedures. + StaticProc *reqSymbols; + + bool Open(const char* lib); + void *GetSym(const char* name); + +public: + template + using Opt = TOptProc; + template + using Req = TReqProc; + + FModule(const char* name) : name(name) {}; + ~FModule() { Unload(); } + + // Load a shared library using the first library name which satisfies all + // of the required symbols. + bool Load(std::initializer_list libnames); + void Unload(); + + bool IsLoaded() const { return handle != nullptr; } +}; + +// Null version of FModule which satisfies the API so the same code can be used +// for run time and compile time linking. +class FStaticModule +{ + template + friend class TStaticProc; + + const char *name; +public: + template + using Opt = TStaticProc; + template + using Req = TStaticProc; + + FStaticModule(const char* name) : name(name) {}; + + bool Load(std::initializer_list libnames) { return true; } + void Unload() {} + + bool IsLoaded() const { return true; } +}; + +// Allow FModuleMaybe to switch based on preprocessor flag. +// Use FModuleMaybe::Opt and FModuleMaybe::Req for procs. +template +struct TModuleType { using Type = FModule; }; +template<> +struct TModuleType { using Type = FStaticModule; }; + +template +using FModuleMaybe = typename TModuleType::Type; + +// ------------------------------------------------------------------------ + +template +class TOptProc +{ + FModule::StaticProc proc; +#ifndef NDEBUG + mutable bool checked = false; +#endif + + // I am not a pointer + bool operator==(void*) const; + bool operator!=(void*) const; + +public: + TOptProc(const char* function) + { + proc.Name = function; + proc.Optional = true; + Module.RegisterStatic(proc); + } + + operator Proto() const + { +#ifndef NDEBUG + assert(checked); +#endif + return (Proto)proc.Call; + } + explicit operator bool() const + { +#ifndef NDEBUG + assert(Module.IsLoaded()); + checked = true; +#endif + return proc.Call != nullptr; + } +}; + +template +class TReqProc +{ + FModule::StaticProc proc; + + // I am not a pointer + bool operator==(void*) const; + bool operator!=(void*) const; + +public: + TReqProc(const char* function) + { + proc.Name = function; + proc.Optional = false; + Module.RegisterStatic(proc); + } + + operator Proto() const + { +#ifndef NDEBUG + assert(Module.IsLoaded()); +#endif + return (Proto)proc.Call; + } + explicit operator bool() const { return true; } +}; + +template +class TStaticProc +{ + // I am not a pointer + bool operator==(void*) const; + bool operator!=(void*) const; + +public: + TStaticProc(const char* function) {} + + operator Proto() const { return Sym; } + explicit operator bool() const { return Sym != nullptr; } +}; diff --git a/src/posix/sdl/i_main.cpp b/src/posix/sdl/i_main.cpp index 41497afe3..cb9ed5872 100644 --- a/src/posix/sdl/i_main.cpp +++ b/src/posix/sdl/i_main.cpp @@ -40,9 +40,6 @@ #include #include #include -#ifndef NO_GTK -#include -#endif #include #if defined(__MACH__) && !defined(NOASM) #include @@ -87,10 +84,6 @@ void Mac_I_FatalError(const char* errortext); // PUBLIC DATA DEFINITIONS ------------------------------------------------- -#ifndef NO_GTK -bool GtkAvailable; -#endif - // The command line arguments. DArgs *Args; @@ -259,10 +252,6 @@ int main (int argc, char **argv) // Note that the LANG environment variable is overridden by LC_* setenv ("LC_NUMERIC", "C", 1); -#ifndef NO_GTK - GtkAvailable = gtk_init_check (&argc, &argv); -#endif - setlocale (LC_ALL, "C"); if (SDL_Init (0) < 0) diff --git a/src/posix/sdl/i_system.cpp b/src/posix/sdl/i_system.cpp index dcefcdbad..8e4e63c4f 100644 --- a/src/posix/sdl/i_system.cpp +++ b/src/posix/sdl/i_system.cpp @@ -34,10 +34,8 @@ #include #include #include -#ifndef NO_GTK -#include -#include -#endif + +#include #include "doomerrors.h" #include @@ -71,10 +69,6 @@ #include "m_fixed.h" #include "g_level.h" -#ifdef __APPLE__ -#include -#endif // __APPLE__ - EXTERN_CVAR (String, language) extern "C" @@ -84,7 +78,8 @@ extern "C" } #ifndef NO_GTK -extern bool GtkAvailable; +bool I_GtkAvailable (); +int I_PickIWad_Gtk (WadStuff *wads, int numwads, bool showwin, int defaultiwad); #elif defined(__APPLE__) int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad); #endif @@ -262,183 +257,6 @@ void I_PrintStr (const char *cp) fflush (stdout); } -#ifndef NO_GTK -// GtkTreeViews eats return keys. I want this to be like a Windows listbox -// where pressing Return can still activate the default button. -gint AllowDefault(GtkWidget *widget, GdkEventKey *event, gpointer func_data) -{ - if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Return) - { - gtk_window_activate_default (GTK_WINDOW(func_data)); - } - return FALSE; -} - -// Double-clicking an entry in the list is the same as pressing OK. -gint DoubleClickChecker(GtkWidget *widget, GdkEventButton *event, gpointer func_data) -{ - if (event->type == GDK_2BUTTON_PRESS) - { - *(int *)func_data = 1; - gtk_main_quit(); - } - return FALSE; -} - -// When the user presses escape, that should be the same as canceling the dialog. -gint CheckEscape (GtkWidget *widget, GdkEventKey *event, gpointer func_data) -{ - if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Escape) - { - gtk_main_quit(); - } - return FALSE; -} - -void ClickedOK(GtkButton *button, gpointer func_data) -{ - *(int *)func_data = 1; - gtk_main_quit(); -} - -EXTERN_CVAR (Bool, queryiwad); - -int I_PickIWad_Gtk (WadStuff *wads, int numwads, bool showwin, int defaultiwad) -{ - GtkWidget *window; - GtkWidget *vbox; - GtkWidget *hbox; - GtkWidget *bbox; - GtkWidget *widget; - GtkWidget *tree; - GtkWidget *check; - GtkListStore *store; - GtkCellRenderer *renderer; - GtkTreeViewColumn *column; - GtkTreeSelection *selection; - GtkTreeIter iter, defiter; - int close_style = 0; - int i; - char caption[100]; - - // Create the dialog window. - window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - mysnprintf(caption, countof(caption), GAMESIG " %s: Select an IWAD to use", GetVersionString()); - gtk_window_set_title (GTK_WINDOW(window), caption); - gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); - gtk_container_set_border_width (GTK_CONTAINER(window), 10); - g_signal_connect (window, "delete_event", G_CALLBACK(gtk_main_quit), NULL); - g_signal_connect (window, "key_press_event", G_CALLBACK(CheckEscape), NULL); - - // Create the vbox container. - vbox = gtk_vbox_new (FALSE, 10); - gtk_container_add (GTK_CONTAINER(window), vbox); - - // Create the top label. - widget = gtk_label_new (GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"); - gtk_box_pack_start (GTK_BOX(vbox), widget, false, false, 0); - gtk_misc_set_alignment (GTK_MISC(widget), 0, 0); - - // Create a list store with all the found IWADs. - store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); - for (i = 0; i < numwads; ++i) - { - const char *filepart = strrchr (wads[i].Path, '/'); - if (filepart == NULL) - filepart = wads[i].Path; - else - filepart++; - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, - 0, filepart, - 1, wads[i].Name.GetChars(), - 2, i, - -1); - if (i == defaultiwad) - { - defiter = iter; - } - } - - // Create the tree view control to show the list. - tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)); - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("IWAD", renderer, "text", 0, NULL); - gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes ("Game", renderer, "text", 1, NULL); - gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); - gtk_box_pack_start (GTK_BOX(vbox), GTK_WIDGET(tree), true, true, 0); - g_signal_connect(G_OBJECT(tree), "button_press_event", G_CALLBACK(DoubleClickChecker), &close_style); - g_signal_connect(G_OBJECT(tree), "key_press_event", G_CALLBACK(AllowDefault), window); - - // Select the default IWAD. - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); - gtk_tree_selection_select_iter (selection, &defiter); - - // Create the hbox for the bottom row. - hbox = gtk_hbox_new (FALSE, 0); - gtk_box_pack_end (GTK_BOX(vbox), hbox, false, false, 0); - - // Create the "Don't ask" checkbox. - check = gtk_check_button_new_with_label ("Don't ask me this again"); - gtk_box_pack_start (GTK_BOX(hbox), check, false, false, 0); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(check), !showwin); - - // Create the OK/Cancel button box. - bbox = gtk_hbutton_box_new (); - gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_set_spacing (GTK_BOX(bbox), 10); - gtk_box_pack_end (GTK_BOX(hbox), bbox, false, false, 0); - - // Create the OK button. - widget = gtk_button_new_from_stock (GTK_STOCK_OK); - gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); - GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_DEFAULT); - gtk_widget_grab_default (widget); - g_signal_connect (widget, "clicked", G_CALLBACK(ClickedOK), &close_style); - g_signal_connect (widget, "activate", G_CALLBACK(ClickedOK), &close_style); - - // Create the cancel button. - widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL); - gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); - g_signal_connect (widget, "clicked", G_CALLBACK(gtk_main_quit), &window); - - // Finally we can show everything. - gtk_widget_show_all (window); - - gtk_main (); - - if (close_style == 1) - { - GtkTreeModel *model; - GValue value = { 0, { {0} } }; - - // Find out which IWAD was selected. - gtk_tree_selection_get_selected (selection, &model, &iter); - gtk_tree_model_get_value (GTK_TREE_MODEL(model), &iter, 2, &value); - i = g_value_get_int (&value); - g_value_unset (&value); - - // Set state of queryiwad based on the checkbox. - queryiwad = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(check)); - } - else - { - i = -1; - } - - if (GTK_IS_WINDOW(window)) - { - gtk_widget_destroy (window); - // If we don't do this, then the X window might not actually disappear. - while (g_main_context_iteration (NULL, FALSE)) {} - } - - return i; -} -#endif - int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) { int i; @@ -448,7 +266,7 @@ int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) return defaultiwad; } -#if !defined(__APPLE__) +#ifndef __APPLE__ const char *str; if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) { @@ -500,12 +318,15 @@ int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) } } #endif + #ifndef NO_GTK - if (GtkAvailable) + if (I_GtkAvailable()) { return I_PickIWad_Gtk (wads, numwads, showwin, defaultiwad); } -#elif defined(__APPLE__) +#endif + +#ifdef __APPLE__ return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); #endif @@ -605,139 +426,19 @@ int I_FindAttr (findstate_t *fileinfo) return 0; } -#ifdef __APPLE__ -static PasteboardRef s_clipboard; - -static CFDataRef GetPasteboardData(const PasteboardItemID itemID, const CFStringRef flavorType) -{ - CFDataRef data = NULL; - - const OSStatus result = PasteboardCopyItemFlavorData(s_clipboard, itemID, flavorType, &data); - - return noErr == result ? data : NULL; -} -#endif // __APPLE__ - -// Clipboard support requires GTK+ -// TODO: GTK+ uses UTF-8. We don't, so some conversions would be appropriate. void I_PutInClipboard (const char *str) { -#ifndef NO_GTK - if (GtkAvailable) - { - GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); - if (clipboard != NULL) - { - gtk_clipboard_set_text(clipboard, str, -1); - } - /* Should I? I don't know. It's not actually a selection. - clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); - if (clipboard != NULL) - { - gtk_clipboard_set_text(clipboard, str, -1); - } - */ - } -#elif defined __APPLE__ - if (NULL == s_clipboard) - { - PasteboardCreate(kPasteboardClipboard, &s_clipboard); - } - - PasteboardClear(s_clipboard); - PasteboardSynchronize(s_clipboard); - - const CFDataRef textData = CFDataCreate(kCFAllocatorDefault, - reinterpret_cast(str), strlen(str)); - - if (NULL != textData) - { - PasteboardPutItemFlavor(s_clipboard, PasteboardItemID(1), - CFSTR("public.utf8-plain-text"), textData, 0); - } -#endif + SDL_SetClipboardText(str); } FString I_GetFromClipboard (bool use_primary_selection) { -#ifndef NO_GTK - if (GtkAvailable) + if(char *ret = SDL_GetClipboardText()) { - GtkClipboard *clipboard = gtk_clipboard_get(use_primary_selection ? - GDK_SELECTION_PRIMARY : GDK_SELECTION_CLIPBOARD); - if (clipboard != NULL) - { - gchar *text = gtk_clipboard_wait_for_text(clipboard); - if (text != NULL) - { - FString copy(text); - g_free(text); - return copy; - } - } + FString text(ret); + SDL_free(ret); + return text; } -#elif defined __APPLE__ - FString result; - - if (NULL == s_clipboard) - { - PasteboardCreate(kPasteboardClipboard, &s_clipboard); - } - - PasteboardSynchronize(s_clipboard); - - ItemCount itemCount = 0; - PasteboardGetItemCount(s_clipboard, &itemCount); - - if (0 == itemCount) - { - return FString(); - } - - PasteboardItemID itemID; - - if (0 != PasteboardGetItemIdentifier(s_clipboard, 1, &itemID)) - { - return FString(); - } - - if (CFDataRef data = GetPasteboardData(itemID, kUTTypeUTF8PlainText)) - { - const CFIndex bufferLength = CFDataGetLength(data); - char* const buffer = result.LockNewBuffer(bufferLength); - - memcpy(buffer, CFDataGetBytePtr(data), bufferLength); - - result.UnlockBuffer(); - } - else if (CFDataRef data = GetPasteboardData(itemID, kUTTypeUTF16PlainText)) - { -#ifdef __LITTLE_ENDIAN__ - static const CFStringEncoding ENCODING = kCFStringEncodingUTF16LE; -#else // __BIG_ENDIAN__ - static const CFStringEncoding ENCODING = kCFStringEncodingUTF16BE; -#endif // __LITTLE_ENDIAN__ - - if (const CFStringRef utf16 = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, ENCODING)) - { - const CFRange range = { 0, CFStringGetLength(utf16) }; - CFIndex bufferLength = 0; - - if (CFStringGetBytes(utf16, range, kCFStringEncodingUTF8, '?', false, NULL, 0, &bufferLength) > 0) - { - UInt8* const buffer = reinterpret_cast(result.LockNewBuffer(bufferLength)); - - CFStringGetBytes(utf16, range, kCFStringEncodingUTF8, '?', false, buffer, bufferLength, NULL); - - result.UnlockBuffer(); - } - - CFRelease(utf16); - } - } - - return result; -#endif return ""; } diff --git a/src/posix/unix/iwadpicker_gtk.cpp b/src/posix/unix/iwadpicker_gtk.cpp new file mode 100644 index 000000000..f5fd393b9 --- /dev/null +++ b/src/posix/unix/iwadpicker_gtk.cpp @@ -0,0 +1,331 @@ +#ifndef NO_GTK + +#if !DYN_GTK +// Function addresses will never be NULL, but that's because we're using the +// same code for both dynamic and static. +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#include +#if GTK_MAJOR_VERSION >= 3 +#include +#else +#include +#endif + +#include "c_cvars.h" +#include "d_main.h" +#include "i_module.h" +#include "i_system.h" +#include "version.h" + +EXTERN_CVAR (Bool, queryiwad); + +namespace Gtk { + +FModuleMaybe GtkModule{"GTK"}; +static int GtkAvailable = -1; + +#define DYN_GTK_SYM(x) const FModuleMaybe::Req x{#x}; +#define DYN_GTK_REQ_SYM(x, proto) const FModuleMaybe::Req x{#x}; +#if GTK_MAJOR_VERSION >= 3 +#define DYN_GTK_OPT2_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#define DYN_GTK_OPT3_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#else +#define DYN_GTK_OPT2_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#define DYN_GTK_OPT3_SYM(x, proto) const FModuleMaybe::Opt x{#x}; +#endif + +DYN_GTK_SYM(g_main_context_iteration); +DYN_GTK_SYM(g_signal_connect_data); +DYN_GTK_SYM(g_type_check_instance_cast); +DYN_GTK_SYM(g_type_check_instance_is_a); +DYN_GTK_SYM(g_value_get_int); +DYN_GTK_SYM(g_value_unset); +DYN_GTK_SYM(gtk_box_get_type); +DYN_GTK_SYM(gtk_box_pack_end); +DYN_GTK_SYM(gtk_box_pack_start); +DYN_GTK_SYM(gtk_box_set_spacing); +DYN_GTK_SYM(gtk_button_box_get_type); +DYN_GTK_SYM(gtk_button_box_set_layout); +DYN_GTK_SYM(gtk_button_new_with_label); +DYN_GTK_SYM(gtk_cell_renderer_text_new); +DYN_GTK_SYM(gtk_check_button_new_with_label); +DYN_GTK_SYM(gtk_container_add); +DYN_GTK_SYM(gtk_container_get_type); +DYN_GTK_SYM(gtk_container_set_border_width); +DYN_GTK_SYM(gtk_init_check); +DYN_GTK_SYM(gtk_label_new); +DYN_GTK_SYM(gtk_list_store_append); +DYN_GTK_SYM(gtk_list_store_new); +DYN_GTK_SYM(gtk_list_store_set); +DYN_GTK_SYM(gtk_toggle_button_get_type); +DYN_GTK_SYM(gtk_toggle_button_set_active); +DYN_GTK_SYM(gtk_tree_model_get_type); +DYN_GTK_SYM(gtk_tree_model_get_value); +DYN_GTK_SYM(gtk_tree_selection_get_selected); +DYN_GTK_SYM(gtk_tree_selection_select_iter); +DYN_GTK_SYM(gtk_tree_view_append_column); +// Explicitly give the type so that attributes don't cause a warning. +DYN_GTK_REQ_SYM(gtk_tree_view_column_new_with_attributes, GtkTreeViewColumn *(*)(const gchar *, GtkCellRenderer *, ...)); +DYN_GTK_SYM(gtk_toggle_button_get_active); +DYN_GTK_SYM(gtk_tree_view_get_selection); +DYN_GTK_SYM(gtk_tree_view_get_type); +DYN_GTK_SYM(gtk_tree_view_new_with_model); +DYN_GTK_SYM(gtk_main); +DYN_GTK_SYM(gtk_main_quit); +DYN_GTK_SYM(gtk_widget_destroy); +DYN_GTK_SYM(gtk_widget_grab_default); +DYN_GTK_SYM(gtk_widget_get_type); +DYN_GTK_SYM(gtk_widget_set_can_default); +DYN_GTK_SYM(gtk_widget_show_all); +DYN_GTK_SYM(gtk_window_activate_default); +DYN_GTK_SYM(gtk_window_get_type); +DYN_GTK_SYM(gtk_window_new); +DYN_GTK_SYM(gtk_window_set_gravity); +DYN_GTK_SYM(gtk_window_set_position); +DYN_GTK_SYM(gtk_window_set_title); + +// Gtk3 Only +DYN_GTK_OPT3_SYM(gtk_box_new, GtkWidget *(*)(GtkOrientation, gint)); +DYN_GTK_OPT3_SYM(gtk_button_box_new, GtkWidget *(*)(GtkOrientation)); +DYN_GTK_OPT3_SYM(gtk_widget_set_halign, void(*)(GtkWidget *, GtkAlign)); +DYN_GTK_OPT3_SYM(gtk_widget_set_valign, void(*)(GtkWidget *, GtkAlign)); + +// Gtk2 Only +DYN_GTK_OPT2_SYM(gtk_misc_get_type, GType(*)()); +DYN_GTK_OPT2_SYM(gtk_hbox_new, GtkWidget *(*)(gboolean, gint)); +DYN_GTK_OPT2_SYM(gtk_hbutton_box_new, GtkWidget *(*)()); +DYN_GTK_OPT2_SYM(gtk_misc_set_alignment, void(*)(GtkMisc *, gfloat, gfloat)); +DYN_GTK_OPT2_SYM(gtk_vbox_new, GtkWidget *(*)(gboolean, gint)); + +#undef DYN_GTK_SYM +#undef DYN_GTK_REQ_SYM +#undef DYN_GTK_OPT2_SYM +#undef DYN_GTK_OPT3_SYM + +// GtkTreeViews eats return keys. I want this to be like a Windows listbox +// where pressing Return can still activate the default button. +static gint AllowDefault(GtkWidget *widget, GdkEventKey *event, gpointer func_data) +{ + if (event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Return) + { + gtk_window_activate_default (GTK_WINDOW(func_data)); + } + return FALSE; +} + +// Double-clicking an entry in the list is the same as pressing OK. +static gint DoubleClickChecker(GtkWidget *widget, GdkEventButton *event, gpointer func_data) +{ + if (event->type == GDK_2BUTTON_PRESS) + { + *(int *)func_data = 1; + gtk_main_quit(); + } + return FALSE; +} + +// When the user presses escape, that should be the same as canceling the dialog. +static gint CheckEscape (GtkWidget *widget, GdkEventKey *event, gpointer func_data) +{ + if (event->type == GDK_KEY_PRESS && event->keyval == GDK_KEY_Escape) + { + gtk_main_quit(); + } + return FALSE; +} + +static void ClickedOK(GtkButton *button, gpointer func_data) +{ + *(int *)func_data = 1; + gtk_main_quit(); +} + +static int PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + GtkWidget *window; + GtkWidget *vbox = nullptr; + GtkWidget *hbox = nullptr; + GtkWidget *bbox = nullptr; + GtkWidget *widget; + GtkWidget *tree; + GtkWidget *check; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + GtkTreeIter iter, defiter; + int close_style = 0; + int i; + char caption[100]; + + // Create the dialog window. + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + mysnprintf(caption, countof(caption), GAMESIG " %s: Select an IWAD to use", GetVersionString()); + gtk_window_set_title (GTK_WINDOW(window), caption); + gtk_window_set_position (GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_gravity (GTK_WINDOW(window), GDK_GRAVITY_CENTER); + gtk_container_set_border_width (GTK_CONTAINER(window), 10); + g_signal_connect (window, "delete_event", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect (window, "key_press_event", G_CALLBACK(CheckEscape), NULL); + + // Create the vbox container. + if (gtk_box_new) // Gtk3 + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + else if (gtk_vbox_new) // Gtk2 + vbox = gtk_vbox_new (FALSE, 10); + + gtk_container_add (GTK_CONTAINER(window), vbox); + + // Create the top label. + widget = gtk_label_new (GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"); + gtk_box_pack_start (GTK_BOX(vbox), widget, false, false, 0); + + if (gtk_widget_set_halign && gtk_widget_set_valign) // Gtk3 + { + gtk_widget_set_halign (widget, GTK_ALIGN_START); + gtk_widget_set_valign (widget, GTK_ALIGN_START); + } + else if (gtk_misc_set_alignment && gtk_misc_get_type) // Gtk2 + gtk_misc_set_alignment (GTK_MISC(widget), 0, 0); + + // Create a list store with all the found IWADs. + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); + for (i = 0; i < numwads; ++i) + { + const char *filepart = strrchr (wads[i].Path, '/'); + if (filepart == NULL) + filepart = wads[i].Path; + else + filepart++; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, filepart, + 1, wads[i].Name.GetChars(), + 2, i, + -1); + if (i == defaultiwad) + { + defiter = iter; + } + } + + // Create the tree view control to show the list. + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("IWAD", renderer, "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Game", renderer, "text", 1, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW(tree), column); + gtk_box_pack_start (GTK_BOX(vbox), GTK_WIDGET(tree), true, true, 0); + g_signal_connect(G_OBJECT(tree), "button_press_event", G_CALLBACK(DoubleClickChecker), &close_style); + g_signal_connect(G_OBJECT(tree), "key_press_event", G_CALLBACK(AllowDefault), window); + + // Select the default IWAD. + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); + gtk_tree_selection_select_iter (selection, &defiter); + + // Create the hbox for the bottom row. + if (gtk_box_new) // Gtk3 + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + else if (gtk_hbox_new) // Gtk2 + hbox = gtk_hbox_new (FALSE, 0); + + gtk_box_pack_end (GTK_BOX(vbox), hbox, false, false, 0); + + // Create the "Don't ask" checkbox. + check = gtk_check_button_new_with_label ("Don't ask me this again"); + gtk_box_pack_start (GTK_BOX(hbox), check, false, false, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(check), !showwin); + + // Create the OK/Cancel button box. + if (gtk_button_box_new) // Gtk3 + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + else if (gtk_hbutton_box_new) // Gtk2 + bbox = gtk_hbutton_box_new (); + + gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX(bbox), 10); + gtk_box_pack_end (GTK_BOX(hbox), bbox, false, false, 0); + + // Create the OK button. + widget = gtk_button_new_with_label ("OK"); + + gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); + + gtk_widget_set_can_default (widget, true); + + gtk_widget_grab_default (widget); + g_signal_connect (widget, "clicked", G_CALLBACK(ClickedOK), &close_style); + g_signal_connect (widget, "activate", G_CALLBACK(ClickedOK), &close_style); + + // Create the cancel button. + widget = gtk_button_new_with_label ("Cancel"); + + gtk_box_pack_start (GTK_BOX(bbox), widget, false, false, 0); + g_signal_connect (widget, "clicked", G_CALLBACK(gtk_main_quit), &window); + + // Finally we can show everything. + gtk_widget_show_all (window); + + gtk_main (); + + if (close_style == 1) + { + GtkTreeModel *model; + GValue value = { 0, { {0} } }; + + // Find out which IWAD was selected. + gtk_tree_selection_get_selected (selection, &model, &iter); + gtk_tree_model_get_value (GTK_TREE_MODEL(model), &iter, 2, &value); + i = g_value_get_int (&value); + g_value_unset (&value); + + // Set state of queryiwad based on the checkbox. + queryiwad = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(check)); + } + else + { + i = -1; + } + + if (GTK_IS_WINDOW(window)) + { + gtk_widget_destroy (window); + // If we don't do this, then the X window might not actually disappear. + while (g_main_context_iteration (NULL, FALSE)) {} + } + + return i; +} + +} // namespace Gtk + +int I_PickIWad_Gtk (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + return Gtk::PickIWad (wads, numwads, showwin, defaultiwad); +} + +bool I_GtkAvailable() +{ + using namespace Gtk; + + if(GtkAvailable < 0) + { + if (!GtkModule.Load({"libgtk-3.so.0", "libgtk-x11-2.0.so.0"})) + { + GtkAvailable = 0; + return false; + } + + int argc = 0; + char **argv = nullptr; + GtkAvailable = Gtk::gtk_init_check (&argc, &argv); + } + + return GtkAvailable != 0; +} + +#endif diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 861d94927..a86429e97 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -359,6 +359,9 @@ protected: #ifndef DYN_FLUIDSYNTH #include #else +#include "i_module.h" +extern FModule FluidSynthModule; + struct fluid_settings_t; struct fluid_synth_t; #endif @@ -386,40 +389,35 @@ protected: #ifdef DYN_FLUIDSYNTH enum { FLUID_FAILED = -1, FLUID_OK = 0 }; - fluid_settings_t *(*new_fluid_settings)(); - fluid_synth_t *(*new_fluid_synth)(fluid_settings_t *); - int (*delete_fluid_synth)(fluid_synth_t *); - void (*delete_fluid_settings)(fluid_settings_t *); - int (*fluid_settings_setnum)(fluid_settings_t *, const char *, double); - int (*fluid_settings_setstr)(fluid_settings_t *, const char *, const char *); - int (*fluid_settings_setint)(fluid_settings_t *, const char *, int); - int (*fluid_settings_getstr)(fluid_settings_t *, const char *, char **); - int (*fluid_settings_getint)(fluid_settings_t *, const char *, int *); - void (*fluid_synth_set_reverb_on)(fluid_synth_t *, int); - void (*fluid_synth_set_chorus_on)(fluid_synth_t *, int); - int (*fluid_synth_set_interp_method)(fluid_synth_t *, int, int); - int (*fluid_synth_set_polyphony)(fluid_synth_t *, int); - int (*fluid_synth_get_polyphony)(fluid_synth_t *); - int (*fluid_synth_get_active_voice_count)(fluid_synth_t *); - double (*fluid_synth_get_cpu_load)(fluid_synth_t *); - int (*fluid_synth_system_reset)(fluid_synth_t *); - int (*fluid_synth_noteon)(fluid_synth_t *, int, int, int); - int (*fluid_synth_noteoff)(fluid_synth_t *, int, int); - int (*fluid_synth_cc)(fluid_synth_t *, int, int, int); - int (*fluid_synth_program_change)(fluid_synth_t *, int, int); - int (*fluid_synth_channel_pressure)(fluid_synth_t *, int, int); - int (*fluid_synth_pitch_bend)(fluid_synth_t *, int, int); - int (*fluid_synth_write_float)(fluid_synth_t *, int, void *, int, int, void *, int, int); - int (*fluid_synth_sfload)(fluid_synth_t *, const char *, int); - void (*fluid_synth_set_reverb)(fluid_synth_t *, double, double, double, double); - void (*fluid_synth_set_chorus)(fluid_synth_t *, int, double, double, double, int); - int (*fluid_synth_sysex)(fluid_synth_t *, const char *, int, char *, int *, int *, int); + static TReqProc new_fluid_settings; + static TReqProc new_fluid_synth; + static TReqProc delete_fluid_synth; + static TReqProc delete_fluid_settings; + static TReqProc fluid_settings_setnum; + static TReqProc fluid_settings_setstr; + static TReqProc fluid_settings_setint; + static TReqProc fluid_settings_getstr; + static TReqProc fluid_settings_getint; + static TReqProc fluid_synth_set_reverb_on; + static TReqProc fluid_synth_set_chorus_on; + static TReqProc fluid_synth_set_interp_method; + static TReqProc fluid_synth_set_polyphony; + static TReqProc fluid_synth_get_polyphony; + static TReqProc fluid_synth_get_active_voice_count; + static TReqProc fluid_synth_get_cpu_load; + static TReqProc fluid_synth_system_reset; + static TReqProc fluid_synth_noteon; + static TReqProc fluid_synth_noteoff; + static TReqProc fluid_synth_cc; + static TReqProc fluid_synth_program_change; + static TReqProc fluid_synth_channel_pressure; + static TReqProc fluid_synth_pitch_bend; + static TReqProc fluid_synth_write_float; + static TReqProc fluid_synth_sfload; + static TReqProc fluid_synth_set_reverb; + static TReqProc fluid_synth_set_chorus; + static TReqProc fluid_synth_sysex; -#ifdef _WIN32 - HMODULE FluidSynthDLL; -#else - void *FluidSynthSO; -#endif bool LoadFluidSynth(); void UnloadFluidSynth(); #endif diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index fd0bab645..bbd6536bc 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -60,9 +60,9 @@ #include #ifdef __APPLE__ -#define FLUIDSYNTHLIB "libfluidsynth.1.dylib" +#define FLUIDSYNTHLIB1 "libfluidsynth.1.dylib" #else // !__APPLE__ -#define FLUIDSYNTHLIB "libfluidsynth.so.1" +#define FLUIDSYNTHLIB1 "libfluidsynth.so.1" #endif // __APPLE__ #endif @@ -644,12 +644,6 @@ FString FluidSynthMIDIDevice::GetStats() #ifdef DYN_FLUIDSYNTH -struct LibFunc -{ - void **FuncPointer; - const char *FuncName; -}; - //========================================================================== // // FluidSynthMIDIDevice :: LoadFluidSynth @@ -658,124 +652,65 @@ struct LibFunc // //========================================================================== +FModuleMaybe FluidSynthModule{"FluidSynth"}; + +#define DYN_FLUID_SYM(x) decltype(FluidSynthMIDIDevice::x) FluidSynthMIDIDevice::x{#x} +DYN_FLUID_SYM(new_fluid_settings); +DYN_FLUID_SYM(new_fluid_synth); +DYN_FLUID_SYM(delete_fluid_synth); +DYN_FLUID_SYM(delete_fluid_settings); +DYN_FLUID_SYM(fluid_settings_setnum); +DYN_FLUID_SYM(fluid_settings_setstr); +DYN_FLUID_SYM(fluid_settings_setint); +DYN_FLUID_SYM(fluid_settings_getstr); +DYN_FLUID_SYM(fluid_settings_getint); +DYN_FLUID_SYM(fluid_synth_set_reverb_on); +DYN_FLUID_SYM(fluid_synth_set_chorus_on); +DYN_FLUID_SYM(fluid_synth_set_interp_method); +DYN_FLUID_SYM(fluid_synth_set_polyphony); +DYN_FLUID_SYM(fluid_synth_get_polyphony); +DYN_FLUID_SYM(fluid_synth_get_active_voice_count); +DYN_FLUID_SYM(fluid_synth_get_cpu_load); +DYN_FLUID_SYM(fluid_synth_system_reset); +DYN_FLUID_SYM(fluid_synth_noteon); +DYN_FLUID_SYM(fluid_synth_noteoff); +DYN_FLUID_SYM(fluid_synth_cc); +DYN_FLUID_SYM(fluid_synth_program_change); +DYN_FLUID_SYM(fluid_synth_channel_pressure); +DYN_FLUID_SYM(fluid_synth_pitch_bend); +DYN_FLUID_SYM(fluid_synth_write_float); +DYN_FLUID_SYM(fluid_synth_sfload); +DYN_FLUID_SYM(fluid_synth_set_reverb); +DYN_FLUID_SYM(fluid_synth_set_chorus); +DYN_FLUID_SYM(fluid_synth_sysex); + bool FluidSynthMIDIDevice::LoadFluidSynth() { - LibFunc imports[] = - { - { (void **)&new_fluid_settings, "new_fluid_settings" }, - { (void **)&new_fluid_synth, "new_fluid_synth" }, - { (void **)&delete_fluid_synth, "delete_fluid_synth" }, - { (void **)&delete_fluid_settings, "delete_fluid_settings" }, - { (void **)&fluid_settings_setnum, "fluid_settings_setnum" }, - { (void **)&fluid_settings_setstr, "fluid_settings_setstr" }, - { (void **)&fluid_settings_setint, "fluid_settings_setint" }, - { (void **)&fluid_settings_getstr, "fluid_settings_getstr" }, - { (void **)&fluid_settings_getint, "fluid_settings_getint" }, - { (void **)&fluid_synth_set_reverb_on, "fluid_synth_set_reverb_on" }, - { (void **)&fluid_synth_set_chorus_on, "fluid_synth_set_chorus_on" }, - { (void **)&fluid_synth_set_interp_method, "fluid_synth_set_interp_method" }, - { (void **)&fluid_synth_set_polyphony, "fluid_synth_set_polyphony" }, - { (void **)&fluid_synth_get_polyphony, "fluid_synth_get_polyphony" }, - { (void **)&fluid_synth_get_active_voice_count, "fluid_synth_get_active_voice_count" }, - { (void **)&fluid_synth_get_cpu_load, "fluid_synth_get_cpu_load" }, - { (void **)&fluid_synth_system_reset, "fluid_synth_system_reset" }, - { (void **)&fluid_synth_noteon, "fluid_synth_noteon" }, - { (void **)&fluid_synth_noteoff, "fluid_synth_noteoff" }, - { (void **)&fluid_synth_cc, "fluid_synth_cc" }, - { (void **)&fluid_synth_program_change, "fluid_synth_program_change" }, - { (void **)&fluid_synth_channel_pressure, "fluid_synth_channel_pressure" }, - { (void **)&fluid_synth_pitch_bend, "fluid_synth_pitch_bend" }, - { (void **)&fluid_synth_write_float, "fluid_synth_write_float" }, - { (void **)&fluid_synth_sfload, "fluid_synth_sfload" }, - { (void **)&fluid_synth_set_reverb, "fluid_synth_set_reverb" }, - { (void **)&fluid_synth_set_chorus, "fluid_synth_set_chorus" }, - { (void **)&fluid_synth_sysex, "fluid_synth_sysex" }, - }; - int fail = 0; - const char *libname; - -#ifdef _WIN32 if (strlen(fluid_lib) > 0) { - FluidSynthDLL = LoadLibrary(libname = fluid_lib); - if (nullptr == FluidSynthDLL) + if(!FluidSynthModule.Load({fluid_lib})) { + const char* libname = fluid_lib; Printf(TEXTCOLOR_RED "Could not load %s\n", libname); } - } - else - { - FluidSynthDLL = nullptr; - } - - if (nullptr == FluidSynthDLL) - { - FluidSynthDLL = LoadLibrary(libname = FLUIDSYNTHLIB1); - if (nullptr == FluidSynthDLL) - { - FluidSynthDLL = LoadLibrary(libname = FLUIDSYNTHLIB2); - if (nullptr == FluidSynthDLL) - { - Printf(TEXTCOLOR_RED "Could not load " FLUIDSYNTHLIB1 " or " FLUIDSYNTHLIB2 "\n"); - return false; - } - } - } -#else - if (strlen(fluid_lib) > 0) - { - FluidSynthSO = dlopen(libname = fluid_lib, RTLD_LAZY); - if (nullptr == FluidSynthSO) - { - Printf(TEXTCOLOR_RED "Could not load %s: %s\n", libname, dlerror()); - } - } - else - { - FluidSynthSO = nullptr; + else + return true; } - if (nullptr == FluidSynthSO) +#ifdef FLUIDSYNTHLIB2 + if(!FluidSynthModule.Load({FLUIDSYNTHLIB1, FLUIDSYNTHLIB2})) { - FluidSynthSO = dlopen(libname = FLUIDSYNTHLIB, RTLD_LAZY); - if (nullptr == FluidSynthSO) - { - Printf(TEXTCOLOR_RED "Could not load " FLUIDSYNTHLIB ": %s\n", dlerror()); - return false; - } - } -#endif - - for (size_t i = 0; i < countof(imports); ++i) - { -#ifdef _WIN32 - FARPROC proc = GetProcAddress(FluidSynthDLL, imports[i].FuncName); -#else - void *proc = dlsym(FluidSynthSO, imports[i].FuncName); -#endif - if (proc == NULL) - { - Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, libname); - fail++; - } - *imports[i].FuncPointer = (void *)proc; - } - if (fail == 0) - { - return true; - } - else - { -#ifdef _WIN32 - FreeLibrary(FluidSynthDLL); - FluidSynthDLL = NULL; -#else - dlclose(FluidSynthSO); - FluidSynthSO = NULL; -#endif + Printf(TEXTCOLOR_RED "Could not load " FLUIDSYNTHLIB1 " or " FLUIDSYNTHLIB2 "\n"); return false; } - +#else + if(!FluidSynthModule.Load({fluid_lib, FLUIDSYNTHLIB1})) + { + Printf(TEXTCOLOR_RED "Could not load " FLUIDSYNTHLIB1 ": %s\n", dlerror()); + return false; + } +#endif + return true; } //========================================================================== @@ -786,19 +721,7 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() void FluidSynthMIDIDevice::UnloadFluidSynth() { -#ifdef _WIN32 - if (FluidSynthDLL != NULL) - { - FreeLibrary(FluidSynthDLL); - FluidSynthDLL = NULL; - } -#else - if (FluidSynthSO != NULL) - { - dlclose(FluidSynthSO); - FluidSynthSO = NULL; - } -#endif + FluidSynthModule.Unload(); } #endif diff --git a/src/sound/oalload.h b/src/sound/oalload.h index ca02d25d9..1fcf53773 100644 --- a/src/sound/oalload.h +++ b/src/sound/oalload.h @@ -3,24 +3,9 @@ #if !defined NO_OPENAL && defined DYN_OPENAL -#ifndef _WIN32 -typedef void* FARPROC; -#endif - -#define DEFINE_ENTRY(type, name) static type p_##name; +#define DEFINE_ENTRY(type, name) static TReqProc p_##name{#name}; #include "oaldef.h" #undef DEFINE_ENTRY -struct oalloadentry -{ - const char *name; - FARPROC *funcaddr; -}; -static oalloadentry oalfuncs[] = { -#define DEFINE_ENTRY(type, name) { #name, (FARPROC*)&p_##name }, -#include "oaldef.h" -#undef DEFINE_ENTRY -{ NULL, 0 } -}; #ifndef IN_IDE_PARSER #define alEnable p_alEnable diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp index 21370d082..c1973780b 100644 --- a/src/sound/oalsound.cpp +++ b/src/sound/oalsound.cpp @@ -55,29 +55,25 @@ #include "actor.h" #include "r_state.h" #include "w_wad.h" +#include "i_module.h" #include "i_music.h" #include "i_musicinterns.h" #include "tempfiles.h" +FModule OpenALModule{"OpenAL"}; + #include "oalload.h" CVAR (String, snd_aldevice, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) #ifdef _WIN32 -static HMODULE hmodOpenAL; #define OPENALLIB "openal32.dll" -#else -static void* hmodOpenAL; -#ifdef __APPLE__ +#elif defined(__APPLE__) #define OPENALLIB "OpenAL.framework/OpenAL" #else #define OPENALLIB "libopenal.so.1" #endif -#define LoadLibrary(x) dlopen((x), RTLD_LAZY) -#define GetProcAddress(a,b) dlsym((a),(b)) -#define FreeLibrary(x) dlclose((x)) -#endif bool IsOpenALPresent() { @@ -92,29 +88,7 @@ bool IsOpenALPresent() if (!done) { done = true; - if (hmodOpenAL == NULL) - { - hmodOpenAL = LoadLibrary(NicePath("$PROGDIR/" OPENALLIB)); - if (hmodOpenAL == NULL) - { - hmodOpenAL = LoadLibrary(OPENALLIB); - if (hmodOpenAL == NULL) - { - return false; - } - } - for(int i = 0; oalfuncs[i].name != NULL; i++) - { - *oalfuncs[i].funcaddr = GetProcAddress(hmodOpenAL, oalfuncs[i].name); - if (*oalfuncs[i].funcaddr == NULL) - { - FreeLibrary(hmodOpenAL); - hmodOpenAL = NULL; - return false; - } - } - } - cached_result = true; + cached_result = OpenALModule.Load({NicePath("$PROGDIR/" OPENALLIB), OPENALLIB}); } return cached_result; #endif diff --git a/src/win32/i_main.cpp b/src/win32/i_main.cpp index 52e78c3bc..db92467bc 100644 --- a/src/win32/i_main.cpp +++ b/src/win32/i_main.cpp @@ -68,6 +68,7 @@ #include "doomtype.h" #include "m_argv.h" #include "d_main.h" +#include "i_module.h" #include "i_system.h" #include "c_console.h" #include "version.h" @@ -84,6 +85,8 @@ #include "stats.h" #include "st_start.h" +#include "optwin32.h" + #include // MACROS ------------------------------------------------------------------ @@ -143,6 +146,21 @@ LONG GameTitleFontHeight; LONG DefaultGUIFontHeight; LONG ErrorIconChar; +FModule Kernel32Module{"Kernel32"}; +FModule Shell32Module{"Shell32"}; +FModule User32Module{"User32"}; + +namespace OptWin32 { +#define DYN_WIN32_SYM(x) decltype(x) x{#x} + +DYN_WIN32_SYM(SHGetFolderPathA); +DYN_WIN32_SYM(SHGetKnownFolderPath); +DYN_WIN32_SYM(GetLongPathNameA); +DYN_WIN32_SYM(GetMonitorInfoA); + +#undef DYN_WIN32_SYM +} // namespace OptWin32 + // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const char WinClassName[] = GAMENAME "MainWindow"; @@ -818,6 +836,11 @@ void DoMain (HINSTANCE hInstance) Args = new DArgs(__argc, __argv); + // Load Win32 modules + Kernel32Module.Load({"kernel32.dll"}); + Shell32Module.Load({"shell32.dll"}); + User32Module.Load({"user32.dll"}); + // Under XP, get our session ID so we can know when the user changes/locks sessions. // Since we need to remain binary compatible with older versions of Windows, we // need to extract the ProcessIdToSessionId function from kernel32.dll manually. diff --git a/src/win32/i_specialpaths.cpp b/src/win32/i_specialpaths.cpp index ed8dc2ee6..6d4890ea9 100644 --- a/src/win32/i_specialpaths.cpp +++ b/src/win32/i_specialpaths.cpp @@ -43,7 +43,7 @@ #include "version.h" // for GAMENAME #include "i_system.h" -typedef HRESULT (WINAPI *GKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *); +#include "optwin32.h" //=========================================================================== // @@ -94,19 +94,17 @@ bool UseKnownFolders() bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create, FString &path) { - static TOptWin32Proc SHGetKnownFolderPath("shell32.dll", "SHGetKnownFolderPath"); + using OptWin32::SHGetFolderPathA; + using OptWin32::SHGetKnownFolderPath; char pathstr[MAX_PATH]; // SHGetKnownFolderPath knows about more folders than SHGetFolderPath, but is // new to Vista, hence the reason we support both. - if (SHGetKnownFolderPath == NULL) + if (!SHGetKnownFolderPath) { - static TOptWin32Proc - SHGetFolderPathA("shell32.dll", "SHGetFolderPathA"); - // NT4 doesn't even have this function. - if (SHGetFolderPathA == NULL) + if (!SHGetFolderPathA) return false; if (shell_folder < 0) @@ -117,7 +115,7 @@ bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create { shell_folder |= CSIDL_FLAG_CREATE; } - if (FAILED(SHGetFolderPathA.Call(NULL, shell_folder, NULL, 0, pathstr))) + if (FAILED(SHGetFolderPathA(NULL, shell_folder, NULL, 0, pathstr))) { return false; } @@ -127,7 +125,7 @@ bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create else { PWSTR wpath; - if (FAILED(SHGetKnownFolderPath.Call(known_folder, create ? KF_FLAG_CREATE : 0, NULL, &wpath))) + if (FAILED(SHGetKnownFolderPath(known_folder, create ? KF_FLAG_CREATE : 0, NULL, &wpath))) { return false; } diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 2075bd276..d55d1e093 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -86,6 +86,8 @@ #include "textures/bitmap.h" #include "textures/textures.h" +#include "optwin32.h" + // MACROS ------------------------------------------------------------------ #ifdef _MSC_VER @@ -1716,20 +1718,19 @@ unsigned int I_MakeRNGSeed() FString I_GetLongPathName(FString shortpath) { - static TOptWin32Proc - GetLongPathNameA("kernel32.dll", "GetLongPathNameA"); + using OptWin32::GetLongPathNameA; // Doesn't exist on NT4 - if (GetLongPathName == NULL) + if (!GetLongPathName) return shortpath; - DWORD buffsize = GetLongPathNameA.Call(shortpath.GetChars(), NULL, 0); + DWORD buffsize = GetLongPathNameA(shortpath.GetChars(), NULL, 0); if (buffsize == 0) { // nothing to change (it doesn't exist, maybe?) return shortpath; } TCHAR *buff = new TCHAR[buffsize]; - DWORD buffsize2 = GetLongPathNameA.Call(shortpath.GetChars(), buff, buffsize); + DWORD buffsize2 = GetLongPathNameA(shortpath.GetChars(), buff, buffsize); if (buffsize2 >= buffsize) { // Failure! Just return the short path delete[] buff; diff --git a/src/win32/i_system.h b/src/win32/i_system.h index a7fca25a6..e7437beb8 100644 --- a/src/win32/i_system.h +++ b/src/win32/i_system.h @@ -51,30 +51,6 @@ typedef enum { extern os_t OSPlatform; -// Helper template so that we can access newer Win32 functions with a single static -// variable declaration. If this were C++11 it could be totally transparent. -template -class TOptWin32Proc -{ - static Proto GetOptionalWin32Proc(const char* module, const char* function) - { - HMODULE hmodule = GetModuleHandle(module); - if (hmodule == NULL) - return NULL; - - return (Proto)GetProcAddress(hmodule, function); - } - -public: - const Proto Call; - - TOptWin32Proc(const char* module, const char* function) - : Call(GetOptionalWin32Proc(module, function)) {} - - // Wrapper object can be tested against NULL, but not directly called. - operator const void*() const { return Call; } -}; - // Called by DoomMain. void I_Init (void); diff --git a/src/win32/optwin32.h b/src/win32/optwin32.h new file mode 100644 index 000000000..0f1458c57 --- /dev/null +++ b/src/win32/optwin32.h @@ -0,0 +1,24 @@ +#pragma once + +// Forward declarations for optional Win32 API procedures +// implemented in i_main.cpp + +#define WIN32_LEAN_AND_MEAN +#include +#include +#define USE_WINDOWS_DWORD + +#include "i_module.h" + +extern FModule Kernel32Module; +extern FModule Shell32Module; +extern FModule User32Module; + +namespace OptWin32 { + +extern TOptProc SHGetFolderPathA; +extern TOptProc SHGetKnownFolderPath; +extern TOptProc GetLongPathNameA; +extern TOptProc GetMonitorInfoA; + +} // namespace OptWin32 diff --git a/src/win32/win32video.cpp b/src/win32/win32video.cpp index 8eb2349ec..18c66f5d3 100644 --- a/src/win32/win32video.cpp +++ b/src/win32/win32video.cpp @@ -74,6 +74,8 @@ #include "win32iface.h" +#include "optwin32.h" + // MACROS ------------------------------------------------------------------ // TYPES ------------------------------------------------------------------- @@ -387,6 +389,8 @@ void Win32Video::BlankForGDI () void Win32Video::DumpAdapters() { + using OptWin32::GetMonitorInfoA; + if (D3D == NULL) { Printf("Multi-monitor support requires Direct3D.\n"); @@ -415,9 +419,8 @@ void Win32Video::DumpAdapters() MONITORINFOEX mi; mi.cbSize = sizeof(mi); - TOptWin32Proc GetMonitorInfo("user32.dll", "GetMonitorInfoW"); - assert(GetMonitorInfo != NULL); // Missing in NT4, but so is D3D - if (GetMonitorInfo.Call(hm, &mi)) + assert(GetMonitorInfo); // Missing in NT4, but so is D3D + if (GetMonitorInfo(hm, &mi)) { mysnprintf(moreinfo, countof(moreinfo), " [%ldx%ld @ (%ld,%ld)]%s", mi.rcMonitor.right - mi.rcMonitor.left,