fltk/src/drivers/Posix/Fl_Posix_System_Driver.cxx

467 lines
13 KiB
C++

//
// Definition of Posix system driver (used by the X11, Wayland and macOS platforms).
//
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
// file is missing or damaged, see the license at:
//
// https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
// https://www.fltk.org/bugs.php
//
#include <config.h>
#include "Fl_Posix_System_Driver.H"
#include "../../flstring.h"
#include <FL/Fl_File_Icon.H>
#include <FL/filename.H>
#include <FL/fl_string_functions.h>
#include <FL/Fl.H>
#include <locale.h>
#include <stdio.h>
#if HAVE_DLFCN_H
# include <dlfcn.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <pwd.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
//
// Define missing POSIX/XPG4 macros as needed...
//
#ifndef S_ISDIR
# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#endif /* !S_ISDIR */
#if HAVE_DLFCN_H
void *Fl_Posix_System_Driver::load(const char *filename) {
return ::dlopen(filename, RTLD_LAZY);
}
#endif
int Fl_Posix_System_Driver::file_type(const char *filename)
{
int filetype;
struct stat fileinfo; // Information on file
if (!::stat(filename, &fileinfo))
{
if (S_ISDIR(fileinfo.st_mode))
filetype = Fl_File_Icon::DIRECTORY;
# ifdef S_ISFIFO
else if (S_ISFIFO(fileinfo.st_mode))
filetype = Fl_File_Icon::FIFO;
# endif // S_ISFIFO
# if defined(S_ISCHR) && defined(S_ISBLK)
else if (S_ISCHR(fileinfo.st_mode) || S_ISBLK(fileinfo.st_mode))
filetype = Fl_File_Icon::DEVICE;
# endif // S_ISCHR && S_ISBLK
# ifdef S_ISLNK
else if (S_ISLNK(fileinfo.st_mode))
filetype = Fl_File_Icon::LINK;
# endif // S_ISLNK
else
filetype = Fl_File_Icon::PLAIN;
}
else
filetype = Fl_File_Icon::PLAIN;
return filetype;
}
const char *Fl_Posix_System_Driver::getpwnam(const char *login) {
struct passwd *pwd;
pwd = ::getpwnam(login);
return pwd ? pwd->pw_dir : NULL;
}
void Fl_Posix_System_Driver::gettime(time_t *sec, int *usec) {
struct timeval tv;
gettimeofday(&tv, NULL);
*sec = tv.tv_sec;
*usec = tv.tv_usec;
}
// Run the specified program, returning 1 on success and 0 on failure
int Fl_Posix_System_Driver::run_program(const char *program, char **argv, char *msg, int msglen) {
pid_t pid; // Process ID of first child
int status; // Exit status from first child
sigset_t set, oldset; // Signal masks
// Block SIGCHLD while we run the program...
//
// Note that I only use the POSIX signal APIs, however older operating
// systems may either not support POSIX signals or have side effects.
// IRIX, for example, provides three separate and incompatible signal
// APIs, so it is possible that an application setting a signal handler
// via signal() or sigset() will not have its SIGCHLD signals blocked...
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, &oldset);
// Create child processes that actually run the program for us...
if ((pid = fork()) == 0) {
// First child comes here, fork a second child and exit...
if (!fork()) {
// Second child comes here, redirect stdin/out/err to /dev/null...
close(0);
::open("/dev/null", O_RDONLY);
close(1);
::open("/dev/null", O_WRONLY);
close(2);
::open("/dev/null", O_WRONLY);
// Detach from the current process group...
setsid();
// Run the program...
execv(program, argv);
_exit(0);
} else {
// First child gets here, exit immediately...
_exit(0);
}
} else if (pid < 0) {
// Restore signal handling...
sigprocmask(SIG_SETMASK, &oldset, NULL);
// Return indicating failure...
return 0;
}
// Wait for the first child to exit...
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) {
// Someone else grabbed the child status...
if (msg) snprintf(msg, msglen, "waitpid(%ld) failed: %s", (long)pid,
strerror(errno));
// Restore signal handling...
sigprocmask(SIG_SETMASK, &oldset, NULL);
// Return indicating failure...
return 0;
}
}
// Restore signal handling...
sigprocmask(SIG_SETMASK, &oldset, NULL);
// Return indicating success...
return 1;
}
#if HAVE_DLSYM && HAVE_DLFCN_H && !defined (__APPLE_CC__)
static void* quadruple_dlopen(const char *libname)
{
char filename2[FL_PATH_MAX];
snprintf(filename2, FL_PATH_MAX, "%s.so", libname);
void *ptr = dlopen(filename2, RTLD_LAZY | RTLD_GLOBAL);
if (!ptr) {
snprintf(filename2, FL_PATH_MAX, "%s.so.2", libname);
ptr = dlopen(filename2, RTLD_LAZY | RTLD_GLOBAL);
if (!ptr) {
snprintf(filename2, FL_PATH_MAX, "%s.so.1", libname);
ptr = dlopen(filename2, RTLD_LAZY | RTLD_GLOBAL);
if (!ptr) {
snprintf(filename2, FL_PATH_MAX, "%s.so.0", libname);
ptr = dlopen(filename2, RTLD_LAZY | RTLD_GLOBAL);
}
}
}
return ptr;
}
#endif // HAVE_DLSYM && HAVE_DLFCN_H && !defined (__APPLE_CC__)
/**
Returns the run-time address of a function or of a shared library.
\param lib_name shared library name (without its extension) or NULL to search the function in the running program
\param func_name function name or NULL
\return the address of the function (when func_name != NULL) or of the shared library, or NULL if not found.
*/
void *Fl_Posix_System_Driver::dlopen_or_dlsym(const char *lib_name, const char *func_name)
{
void *lib_address = NULL;
#if HAVE_DLSYM && HAVE_DLFCN_H
void *func_ptr = NULL;
if (func_name) {
#ifdef RTLD_DEFAULT
func_ptr = dlsym(RTLD_DEFAULT, func_name);
#else
void *p = dlopen(NULL, RTLD_LAZY);
func_ptr = dlsym(p, func_name);
#endif
if (func_ptr) return func_ptr;
}
#ifdef __APPLE_CC__ // allows using on Darwin + XQuartz + (homebrew or fink)
if (lib_name) {
char path[FL_PATH_MAX];
snprintf(path, FL_PATH_MAX, "/opt/X11/lib/%s.dylib", lib_name);
lib_address = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
if (!lib_address) {
snprintf(path, FL_PATH_MAX, "/opt/homebrew/lib/%s.dylib", lib_name);
lib_address = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
if (!lib_address) {
snprintf(path, FL_PATH_MAX, "/opt/sw/lib/%s.dylib", lib_name);
lib_address = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
if (!lib_address) {
snprintf(path, FL_PATH_MAX, "/sw/lib/%s.dylib", lib_name);
lib_address = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
// the GTK2 shared lib has a new name under homebrew in macOS, try it:
if (!lib_address && !strcmp(lib_name, "libgtk-x11-2.0")) {
lib_address = dlopen("/opt/homebrew/lib/libgtkmacintegration-gtk2.dylib", RTLD_LAZY | RTLD_GLOBAL);
}
}
}
}
}
#else
if (lib_name) lib_address = quadruple_dlopen(lib_name);
#endif // __APPLE_CC__
if (func_name && lib_address) return ::dlsym(lib_address, func_name);
#endif // HAVE_DLFCN_H
return lib_address;
}
#if HAVE_DLSYM && HAVE_DLFCN_H
void *Fl_Posix_System_Driver::ptr_gtk = NULL;
bool Fl_Posix_System_Driver::probe_for_GTK(int major, int minor, void **p_ptr_gtk) {
typedef int (*init_t)(int*, char***);
init_t init_f = NULL;
// was GTK previously loaded?
if (Fl_Posix_System_Driver::ptr_gtk) { // yes, it was.
*p_ptr_gtk = Fl_Posix_System_Driver::ptr_gtk;
return true;
}
// Try first with GTK3
Fl_Posix_System_Driver::ptr_gtk = Fl_Posix_System_Driver::dlopen_or_dlsym("libgtk-3");
if (Fl_Posix_System_Driver::ptr_gtk) {
#ifdef DEBUG
puts("selected GTK-3\n");
#endif
} else {
// Try then with GTK2
Fl_Posix_System_Driver::ptr_gtk = Fl_Posix_System_Driver::dlopen_or_dlsym("libgtk-x11-2.0");
#ifdef DEBUG
if (Fl_Posix_System_Driver::ptr_gtk) {
puts("selected GTK-2\n");
}
#endif
}
if (!Fl_Posix_System_Driver::ptr_gtk) {
#ifdef DEBUG
puts("Failure to load libgtk");
#endif
return false;
}
init_f = (init_t)dlsym(Fl_Posix_System_Driver::ptr_gtk, "gtk_init_check");
if (!init_f) return false;
*p_ptr_gtk = Fl_Posix_System_Driver::ptr_gtk;
// The point here is that after running gtk_init_check, the calling program's current locale can be modified.
// To avoid that, we memorize the calling program's current locale and restore the locale
// before returning.
char *before = NULL;
// record in "before" the calling program's current locale
char *p = setlocale(LC_ALL, NULL);
if (p) before = fl_strdup(p);
int ac = 0;
if ( !init_f(&ac, NULL) ) { // may change the locale
free(before);
return false;
}
if (before) {
setlocale(LC_ALL, before); // restore calling program's current locale
free(before);
}
// now check if running version is high enough
if (dlsym(Fl_Posix_System_Driver::ptr_gtk, "gtk_get_major_version") == NULL) { // YES indicates V 3
typedef const char* (*check_t)(int, int, int);
check_t check_f = (check_t)dlsym(Fl_Posix_System_Driver::ptr_gtk, "gtk_check_version");
if (!check_f || check_f(major, minor, 0) ) return false;
}
return true;
}
#endif // HAVE_DLSYM && HAVE_DLFCN_H
int Fl_Posix_System_Driver::close_fd(int fd) { return close(fd); }
////////////////////////////////////////////////////////////////
// POSIX threading...
#if defined(HAVE_PTHREAD)
# include <unistd.h>
# include <fcntl.h>
# include <pthread.h>
// Pipe for thread messaging via Fl::awake()...
static int thread_filedes[2];
// Mutex and state information for Fl::lock() and Fl::unlock()...
static pthread_mutex_t fltk_mutex;
static pthread_t owner;
static int counter;
static void lock_function_init_std() {
pthread_mutex_init(&fltk_mutex, NULL);
}
static void lock_function_std() {
if (!counter || owner != pthread_self()) {
pthread_mutex_lock(&fltk_mutex);
owner = pthread_self();
}
counter++;
}
static void unlock_function_std() {
if (!--counter) pthread_mutex_unlock(&fltk_mutex);
}
# ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
static bool lock_function_init_rec() {
pthread_mutexattr_t attrib;
pthread_mutexattr_init(&attrib);
if (pthread_mutexattr_settype(&attrib, PTHREAD_MUTEX_RECURSIVE)) {
pthread_mutexattr_destroy(&attrib);
return true;
}
pthread_mutex_init(&fltk_mutex, &attrib);
return false;
}
static void lock_function_rec() {
pthread_mutex_lock(&fltk_mutex);
}
static void unlock_function_rec() {
pthread_mutex_unlock(&fltk_mutex);
}
# endif // HAVE_PTHREAD_MUTEX_RECURSIVE
void Fl_Posix_System_Driver::awake(void* msg) {
if (thread_filedes[1]) {
if (write(thread_filedes[1], &msg, sizeof(void*))==0) { /* ignore */ }
}
}
static void* thread_message_;
void* Fl_Posix_System_Driver::thread_message() {
void* r = thread_message_;
thread_message_ = 0;
return r;
}
static void thread_awake_cb(int fd, void*) {
if (read(fd, &thread_message_, sizeof(void*))==0) {
/* This should never happen */
}
Fl_Awake_Handler func;
void *data;
while (Fl::get_awake_handler_(func, data)==0) {
(*func)(data);
}
}
// These pointers are in Fl_x.cxx:
extern void (*fl_lock_function)();
extern void (*fl_unlock_function)();
int Fl_Posix_System_Driver::lock() {
if (!thread_filedes[1]) {
// Initialize thread communication pipe to let threads awake FLTK
// from Fl::wait()
if (pipe(thread_filedes)==-1) {
/* this should not happen */
}
// Make the write side of the pipe non-blocking to avoid deadlock
// conditions (STR #1537)
fcntl(thread_filedes[1], F_SETFL,
fcntl(thread_filedes[1], F_GETFL) | O_NONBLOCK);
// Monitor the read side of the pipe so that messages sent via
// Fl::awake() from a thread will "wake up" the main thread in
// Fl::wait().
Fl::add_fd(thread_filedes[0], FL_READ, thread_awake_cb);
// Set lock/unlock functions for this system, using a system-supplied
// recursive mutex if supported...
# ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
if (!lock_function_init_rec()) {
fl_lock_function = lock_function_rec;
fl_unlock_function = unlock_function_rec;
} else {
# endif // HAVE_PTHREAD_MUTEX_RECURSIVE
lock_function_init_std();
fl_lock_function = lock_function_std;
fl_unlock_function = unlock_function_std;
# ifdef HAVE_PTHREAD_MUTEX_RECURSIVE
}
# endif // HAVE_PTHREAD_MUTEX_RECURSIVE
}
fl_lock_function();
return 0;
}
void Fl_Posix_System_Driver::unlock() {
fl_unlock_function();
}
// Mutex code for the awake ring buffer
static pthread_mutex_t *ring_mutex;
void Fl_Posix_System_Driver::unlock_ring() {
pthread_mutex_unlock(ring_mutex);
}
void Fl_Posix_System_Driver::lock_ring() {
if (!ring_mutex) {
ring_mutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(ring_mutex, NULL);
}
pthread_mutex_lock(ring_mutex);
}
#else // ! HAVE_PTHREAD
void Fl_Posix_System_Driver::awake(void*) {}
int Fl_Posix_System_Driver::lock() { return 1; }
void Fl_Posix_System_Driver::unlock() {}
void* Fl_Posix_System_Driver::thread_message() { return NULL; }
//void lock_ring() {}
//void unlock_ring() {}
#endif // HAVE_PTHREAD