diff --git a/Assets/StandaloneFileBrowser/Plugins/Linux/x86_64/libStandaloneFileBrowser.so b/Assets/StandaloneFileBrowser/Plugins/Linux/x86_64/libStandaloneFileBrowser.so index 344084e..d913c9b 100755 Binary files a/Assets/StandaloneFileBrowser/Plugins/Linux/x86_64/libStandaloneFileBrowser.so and b/Assets/StandaloneFileBrowser/Plugins/Linux/x86_64/libStandaloneFileBrowser.so differ diff --git a/Plugins/Linux/StandaloneFileBrowser/.gitignore b/Plugins/Linux/StandaloneFileBrowser/.gitignore index 8b1aab2..5dc6142 100644 --- a/Plugins/Linux/StandaloneFileBrowser/.gitignore +++ b/Plugins/Linux/StandaloneFileBrowser/.gitignore @@ -1,2 +1,3 @@ .idea -cmake-build-debug \ No newline at end of file +cmake-build-debug +build diff --git a/Plugins/Linux/StandaloneFileBrowser/CMakeLists.txt b/Plugins/Linux/StandaloneFileBrowser/CMakeLists.txt index cbe75a6..4db42af 100644 --- a/Plugins/Linux/StandaloneFileBrowser/CMakeLists.txt +++ b/Plugins/Linux/StandaloneFileBrowser/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.12) project(StandaloneFileBrowser C) set(CMAKE_C_STANDARD 11) +set(CMAKE_C_FLAGS "-Wall") +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_RELEASE "-O2") # Use the package PkgConfig to detect GTK+ headers/library files FIND_PACKAGE(PkgConfig REQUIRED) @@ -15,10 +18,6 @@ LINK_DIRECTORIES(${GTK3_LIBRARY_DIRS}) # Add other flags to the compiler ADD_DEFINITIONS(${GTK3_CFLAGS_OTHER}) -add_library(${PROJECT_NAME} SHARED library.c library.h) +add_executable(dialog dialog.c) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${GTK3_LIBRARIES}) - -add_executable(${PROJECT_NAME}_EXE main.c) - -TARGET_LINK_LIBRARIES(${PROJECT_NAME}_EXE ${PROJECT_NAME} ${GTK3_LIBRARIES}) \ No newline at end of file +TARGET_LINK_LIBRARIES(dialog ${GTK3_LIBRARIES}) diff --git a/Plugins/Linux/StandaloneFileBrowser/Makefile b/Plugins/Linux/StandaloneFileBrowser/Makefile new file mode 100644 index 0000000..44f511a --- /dev/null +++ b/Plugins/Linux/StandaloneFileBrowser/Makefile @@ -0,0 +1,22 @@ +LD = ld +CC = gcc +COPTS = -O2 -Wall -Wextra + +.PHONY: all +all: build/main build/libStandaloneFileBrowser.so + +build/dialog: dialog.c + mkdir -p build + cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make +build/dialog.bin.o: build/dialog + $(LD) -r -b binary -o build/dialog.bin.o build/dialog +build/library.o: library.c library.h + $(CC) $(COPTS) -o build/library.o -c library.c -fPIC +build/libStandaloneFileBrowser.so: build/dialog.bin.o build/library.o + $(CC) $(COPTS) -o build/libStandaloneFileBrowser.so build/library.o build/dialog.bin.o -shared +build/main: build/libStandaloneFileBrowser.so main.c library.h + $(CC) $(COPTS) -o build/main main.c build/libStandaloneFileBrowser.so + +.PHONY: clean +clean: + rm -rf build diff --git a/Plugins/Linux/StandaloneFileBrowser/dialog.c b/Plugins/Linux/StandaloneFileBrowser/dialog.c new file mode 100644 index 0000000..3bd7b54 --- /dev/null +++ b/Plugins/Linux/StandaloneFileBrowser/dialog.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include + +char* strcat_realloc(char* str1, const char* str2) { + char* new_str; + size_t length; + size_t str1length; + + if (str2 == NULL) + return str1; + str1length = 0; + if (str1 != NULL) + str1length = strlen(str1); + length = strlen(str2) + str1length; + new_str = (char*) realloc(str1, (1 + length) * sizeof(char)); + if (new_str == NULL) + return str1; + new_str[str1length] = '\0'; + + strcat(new_str, str2); + + return new_str; +} + +// Rewrite strcpy() to avoid linking with memcpy function of high version (2.14) glibc ABI +char *strcpy_menci(char* dst, const char* src) { + char *p = dst; + do *p++ = *src; while (*src++); + return dst; +} + +void DialogInit() { + gtk_init(0, NULL); +} + +const char* +GTKOpenPanel(const char* title, const char* directory, const char* extension, bool multiselect, + GtkFileChooserAction action); + +const char* +GTKSavePanel(const char* title, const char* directory, const char* defaultName, const char* filters); + +void GTKSetFilters(const char* extension, GtkWidget* dialog); + +const char* DialogOpenFilePanel(const char* title, const char* directory, const char* extension, + bool multiselect) { + return GTKOpenPanel(title, directory, extension, multiselect, + GTK_FILE_CHOOSER_ACTION_OPEN); +} + +const char* DialogOpenFolderPanel(const char* title, const char* directory, bool multiselect) { + return GTKOpenPanel(title, directory, "", multiselect, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); +} + +const char* DialogSaveFilePanel(const char* title, const char* directory, const char* defaultName, + const char* filters) { + return GTKSavePanel(title, directory, defaultName, filters); +} + +const char* +GTKOpenPanel(const char* title, const char* directory, const char* extensions, bool multiselect, + GtkFileChooserAction action) { + char* filename = NULL; + GtkWidget* dialog; + gint res; + + dialog = gtk_file_chooser_dialog_new(title, + NULL, + action, + ("_Cancel"), + GTK_RESPONSE_CANCEL, + ("_Open"), + GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_select_multiple((GtkFileChooser*) dialog, multiselect); + gtk_file_chooser_set_current_folder((GtkFileChooser*) dialog, directory); + + GTKSetFilters(extensions, dialog); + + res = gtk_dialog_run(GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + GtkFileChooser* chooser = GTK_FILE_CHOOSER (dialog); + + if (multiselect) { + GSList* filenamepus = gtk_file_chooser_get_filenames(chooser); + + int nIndex; + GSList* node; + + char split[] = {(char)28, 0}; + for (nIndex = 0; (node = g_slist_nth(filenamepus, (guint) nIndex)); nIndex++) { + if (nIndex == 0) { + filename = malloc((strlen(node->data) + 1) * sizeof(char)); + strcpy_menci(filename, node->data); + continue; + } + filename = strcat_realloc(filename, split); + filename = strcat_realloc(filename, node->data); + + g_free(node->data); + } + g_slist_free(filenamepus); + } else { + char* name = gtk_file_chooser_get_filename(chooser); + filename = malloc(strlen(name) * sizeof(char) + 1); + strcpy_menci(filename, name); + g_free(name); + } + } else { // if (res == GTK_RESPONSE_CANCEL) { + filename = malloc(sizeof(char)); + filename[0] = '\0'; + } + + gtk_widget_destroy(dialog); + + while (gtk_events_pending ()) + gtk_main_iteration (); + return filename; +} + +const char* +GTKSavePanel(const char* title, const char* directory, const char* defaultName, const char* filters) { + char* filename = NULL; + GtkWidget *dialog; + GtkFileChooser *chooser; + gint res; + + dialog = gtk_file_chooser_dialog_new ("Save File", + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + ("_Cancel"), + GTK_RESPONSE_CANCEL, + ("_Save"), + GTK_RESPONSE_ACCEPT, + NULL); + chooser = GTK_FILE_CHOOSER (dialog); + + gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); + gtk_file_chooser_set_current_name(chooser, defaultName); + gtk_file_chooser_set_current_folder(chooser, directory); + + GTKSetFilters(filters, dialog); + + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_ACCEPT) + { + char* name = gtk_file_chooser_get_filename(chooser); + filename = malloc(strlen(name) * sizeof(char) + 1); + strcpy_menci(filename, name); + g_free(name); + } + else if (res == GTK_RESPONSE_CANCEL) { + filename = malloc(sizeof(char)); + filename[0] = '\0'; + } + + gtk_widget_destroy (dialog); + + while (gtk_events_pending ()) + gtk_main_iteration (); + return filename; +} + +void GTKSetFilters(const char* extensions, GtkWidget* dialog) { + if (extensions == NULL || strlen(extensions) == 0) { + return; + } + + // Image Files;png,jpg,jpeg|Sound Files;mp3,wav|All Files;* + char* extensions_tok = malloc(sizeof(char) * (1+ strlen(extensions))); + extensions_tok = strcpy_menci(extensions_tok, extensions); + char* extensions_tok_beginning = extensions_tok; + + char *extension_filters; + char *name_or_filters; + char *ext; + while ((extension_filters = strtok_r(extensions_tok, "|", &extensions_tok))) { + puts(extension_filters); + int i = 0; + GtkFileFilter* filter = gtk_file_filter_new(); + if (extension_filters[0] == ';') { // no filter name + ++i; + } + while ((name_or_filters = strtok_r(extension_filters, ";", &extension_filters))) { + puts(name_or_filters); + if (i == 0) { + // Filter Name + gtk_file_filter_set_name(filter, name_or_filters); + } else { + // Filter extentions + while ((ext = strtok_r(name_or_filters, ",", &name_or_filters))) { + puts(ext); + if (ext[0] == '*') { + gtk_file_filter_add_pattern(filter, ext); + } else { + char* ext_s;// = "*."; + ext_s = malloc(3 * sizeof(char)); + ext_s = strcpy_menci(ext_s, "*."); + ext_s = strcat_realloc(ext_s, ext); + gtk_file_filter_add_pattern(filter, ext_s); + free(ext_s); + } + } + } + ++i; + } + gtk_file_chooser_add_filter((GtkFileChooser*) dialog, filter); + } + + free(extensions_tok_beginning); +} + +int main(int argc, char *argv[]) { + DialogInit(); + + // fprintf(stderr, "operation = %s\n", argv[1]); + + const char *result = NULL; + if (strcmp(argv[1], "DialogOpenFolderPanel") == 0) { + result = DialogOpenFolderPanel(argv[2], argv[3], argv[4][0] == '1'); + } else if (strcmp(argv[1], "DialogOpenFilePanel") == 0) { + result = DialogOpenFilePanel(argv[2], argv[3], argv[4], argv[5][0] == '1'); + } else if (strcmp(argv[1], "DialogSaveFilePanel") == 0) { + result = DialogSaveFilePanel(argv[2], argv[3], argv[4], argv[5]); + } + + size_t size = result ? strlen(result) : 0; + write(STDOUT_FILENO, &size, sizeof(size)); + if (result) + write(STDOUT_FILENO, result, size); + + return 0; +} diff --git a/Plugins/Linux/StandaloneFileBrowser/library.c b/Plugins/Linux/StandaloneFileBrowser/library.c index 0599616..576ee23 100644 --- a/Plugins/Linux/StandaloneFileBrowser/library.c +++ b/Plugins/Linux/StandaloneFileBrowser/library.c @@ -1,232 +1,112 @@ +#define _GNU_SOURCE #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include + #include "library.h" -char* strcat_realloc(char* str1, const char* str2) { - char* new_str; - size_t length; - size_t str1length; - - if (str2 == NULL) - return str1; - str1length = 0; - if (str1 != NULL) - str1length = strlen(str1); - length = strlen(str2) + str1length; - new_str = (char*) realloc(str1, (1 + length) * sizeof(char)); - if (new_str == NULL) - return str1; - new_str[str1length] = '\0'; - - strcat(new_str, str2); - - return new_str; -} +typedef void (*callbackFunc)(const char *); -static callbackFunc asyncCallback; +extern char _binary_build_dialog_start[]; +extern char _binary_build_dialog_end[]; + +char *dialogExecutablePath; void DialogInit() { - gtk_init(0, NULL); + // Avoid linking against higher glibc ABI version + // int fd = memfd_create("Menci", MFD_CLOEXEC); + int fd = syscall(__NR_memfd_create, "Menci", MFD_CLOEXEC); + assert(fd != 0); + write(fd, _binary_build_dialog_start, _binary_build_dialog_end - _binary_build_dialog_start); + asprintf(&dialogExecutablePath, "/proc/self/fd/%d", fd); +} + +void readn(pid_t pid, int fd, void *buffer, size_t n) { + while (n) { + int status; + if (waitpid(pid, &status, WNOHANG) == pid) { + assert(("The child process exited unexpectedly. Please refer to the log in stderr.", false)); + } + ssize_t r; + assert((r = read(fd, buffer, n)) >= 0); + n -= r; + buffer = (void *)((char *)buffer + r); + } } -const char* -GTKOpenPanel(const char* title, const char* directory, const char* extension, bool multiselect, - GtkFileChooserAction action); +const char* runSubprocess(const char **args) { + args[0] = "Menci"; + + int pipeFd[2]; + assert(pipe(pipeFd) != -1); -const char* -GTKSavePanel(const char* title, const char* directory, const char* defaultName, const char* filters); + pid_t pid = fork(); + assert(pid != -1); + if (pid == 0) { + assert(dup2(pipeFd[1], STDOUT_FILENO) != -1); + execv(dialogExecutablePath, (char* const*)args); + perror("execv"); + abort(); + } else { + assert(close(pipeFd[1]) == 0); -void GTKSetFilters(const char* extension, GtkWidget* dialog); + size_t size; + readn(pid, pipeFd[0], &size, sizeof(size)); + + char *result = malloc(size + 1); + readn(pid, pipeFd[0], result, size); + result[size] = '\0'; + + assert(close(pipeFd[0]) == 0); + + int status; + waitpid(pid, &status, 0); + assert(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + return result; + } +} const char* DialogOpenFilePanel(const char* title, const char* directory, const char* extension, bool multiselect) { - return GTKOpenPanel(title, directory, extension, multiselect, - GTK_FILE_CHOOSER_ACTION_OPEN); + const char *args[] = {NULL, "DialogOpenFilePanel", title, directory, extension, multiselect ? "1" : "0", NULL}; + return runSubprocess(args); } const char* DialogOpenFolderPanel(const char* title, const char* directory, bool multiselect) { - return GTKOpenPanel(title, directory, "", multiselect, - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + const char *args[] = {NULL, "DialogOpenFolderPanel", title, directory, multiselect ? "1" : "0", NULL}; + return runSubprocess(args); } const char* DialogSaveFilePanel(const char* title, const char* directory, const char* defaultName, const char* filters) { - return GTKSavePanel(title, directory, defaultName, filters); + const char *args[] = {NULL, "DialogSaveFilePanel", title, directory, defaultName, filters, NULL}; + return runSubprocess(args); } void DialogOpenFilePanelAsync(const char* title, const char* directory, const char* extension, bool multiselect, callbackFunc cb) { // TODO Add async capability - cb(GTKOpenPanel(title, directory, extension, multiselect, - GTK_FILE_CHOOSER_ACTION_OPEN)); + cb(DialogOpenFilePanel(title, directory, extension, multiselect)); } void DialogOpenFolderPanelAsync(const char* title, const char* directory, bool multiselect, callbackFunc cb) { // TODO Add async capability - cb(GTKOpenPanel(title, directory, "", multiselect, - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)); + cb(DialogOpenFolderPanel(title, directory, multiselect)); } void DialogSaveFilePanelAsync(const char* title, const char* directory, const char* defaultName, const char* filters, callbackFunc cb) { // TODO Add async capability - cb(GTKSavePanel(title, directory, defaultName, filters)); -} - -const char* -GTKOpenPanel(const char* title, const char* directory, const char* extensions, bool multiselect, - GtkFileChooserAction action) { - char* filename = NULL; - GtkWidget* dialog; - gint res; - - dialog = gtk_file_chooser_dialog_new(title, - NULL, - action, - ("_Cancel"), - GTK_RESPONSE_CANCEL, - ("_Open"), - GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_select_multiple((GtkFileChooser*) dialog, multiselect); - gtk_file_chooser_set_current_folder((GtkFileChooser*) dialog, directory); - - GTKSetFilters(extensions, dialog); - - res = gtk_dialog_run(GTK_DIALOG (dialog)); - if (res == GTK_RESPONSE_ACCEPT) { - GtkFileChooser* chooser = GTK_FILE_CHOOSER (dialog); - - if (multiselect) { - GSList* filenamepus = gtk_file_chooser_get_filenames(chooser); - - int nIndex; - GSList* node; - - char split = (char) 28; - for (nIndex = 0; (node = g_slist_nth(filenamepus, (guint) nIndex)); nIndex++) { - if (nIndex == 0) { - filename = malloc((strlen(node->data) + 1) * sizeof(char)); - strcpy(filename, node->data); - continue; - } - filename = strcat_realloc(filename, &split); - filename = strcat_realloc(filename, node->data); - - g_free(node->data); - } - g_slist_free(filenamepus); - } else { - char* name = gtk_file_chooser_get_filename(chooser); - filename = malloc(strlen(name) * sizeof(char)); - strcpy(filename, name); - g_free(name); - } - } else { // if (res == GTK_RESPONSE_CANCEL) { - filename = malloc(sizeof(char)); - filename[0] = '\0'; - } - - gtk_widget_destroy(dialog); - - while (gtk_events_pending ()) - gtk_main_iteration (); - return filename; -} - -const char* -GTKSavePanel(const char* title, const char* directory, const char* defaultName, const char* filters) { - char* filename = NULL; - GtkWidget *dialog; - GtkFileChooser *chooser; - gint res; - - dialog = gtk_file_chooser_dialog_new ("Save File", - NULL, - GTK_FILE_CHOOSER_ACTION_SAVE, - ("_Cancel"), - GTK_RESPONSE_CANCEL, - ("_Save"), - GTK_RESPONSE_ACCEPT, - NULL); - chooser = GTK_FILE_CHOOSER (dialog); - - gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); - gtk_file_chooser_set_current_name(chooser, defaultName); - gtk_file_chooser_set_current_folder(chooser, directory); - - GTKSetFilters(filters, dialog); - - res = gtk_dialog_run (GTK_DIALOG (dialog)); - if (res == GTK_RESPONSE_ACCEPT) - { - char* name = gtk_file_chooser_get_filename(chooser); - filename = malloc(strlen(name) * sizeof(char)); - strcpy(filename, name); - g_free(name); - } - else if (res == GTK_RESPONSE_CANCEL) { - filename = malloc(sizeof(char)); - filename[0] = '\0'; - } - - gtk_widget_destroy (dialog); - - while (gtk_events_pending ()) - gtk_main_iteration (); - return filename; + cb(DialogSaveFilePanel(title, directory, defaultName, filters)); } - -void GTKSetFilters(const char* extensions, GtkWidget* dialog) { - if (extensions == NULL || strlen(extensions) == 0) { - return; - } - - // Image Files;png,jpg,jpeg|Sound Files;mp3,wav|All Files;* - char* extensions_tok = malloc(sizeof(char) * (1+ strlen(extensions))); - extensions_tok = strcpy(extensions_tok, extensions); - char* extensions_tok_beginning = extensions_tok; - - char *extension_filters; - char *name_or_filters; - char *ext; - while ((extension_filters = strtok_r(extensions_tok, "|", &extensions_tok))) { - puts(extension_filters); - int i = 0; - GtkFileFilter* filter = gtk_file_filter_new(); - if (extension_filters[0] == ';') { // no filter name - ++i; - } - while ((name_or_filters = strtok_r(extension_filters, ";", &extension_filters))) { - puts(name_or_filters); - if (i == 0) { - // Filter Name - gtk_file_filter_set_name(filter, name_or_filters); - } else { - // Filter extentions - while ((ext = strtok_r(name_or_filters, ",", &name_or_filters))) { - puts(ext); - if (ext[0] == '*') { - gtk_file_filter_add_pattern(filter, ext); - } else { - char* ext_s;// = "*."; - ext_s = malloc(3 * sizeof(char)); - ext_s = strcpy(ext_s, "*."); - ext_s = strcat_realloc(ext_s, ext); - gtk_file_filter_add_pattern(filter, ext_s); - free(ext_s); - } - } - } - ++i; - } - gtk_file_chooser_add_filter((GtkFileChooser*) dialog, filter); - } - - free(extensions_tok_beginning); -} \ No newline at end of file diff --git a/Plugins/Linux/StandaloneFileBrowser/main.c b/Plugins/Linux/StandaloneFileBrowser/main.c index 0fab69f..0a42217 100644 --- a/Plugins/Linux/StandaloneFileBrowser/main.c +++ b/Plugins/Linux/StandaloneFileBrowser/main.c @@ -1,9 +1,8 @@ #include -#include #include #include "library.h" -int main(int argc, char *argv[]) { +int main() { DialogInit(); const char* folders = DialogOpenFolderPanel("Unity Open Folder", "/", true); printf("Folders selected: %s\n", folders); @@ -12,4 +11,4 @@ int main(int argc, char *argv[]) { const char* filesFilters = DialogOpenFilePanel("Unity Open File With Filters", "", "Image Files;png,jpg,jpeg|Sound Files;mp3,wav|All Files;*", true); printf("Files selected: %s\n", filesFilters); return 0; -} \ No newline at end of file +}