blob: 4211eaf454b60271bfde1bbb75c2eb274dee67c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Kevin Cornell (Rational Software Corporation)
* Tom Tromey (Red Hat, Inc.)
*******************************************************************************/
#include "eclipseCommon.h"
#include "eclipseOS.h"
#include "eclipseUtil.h"
#include "eclipseGtk.h"
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <semaphore.h>
#include <fcntl.h>
/* Global Variables */
char* defaultVM = "java";
char* vmLibrary = "libjvm.so";
char* shippedVMDir = "jre/bin/";
/* Define the special arguments for the various Java VMs. */
static char* argVM_JAVA[] = { NULL };
/* Define local variables . */
static GtkWidget* splashHandle = 0;
static GtkWidget* shellHandle = 0;
static _TCHAR** openFilePath = NULL; /* the files we want to open */
static int openFileTimeout = 60; /* number of seconds to wait before timeout */
static int filesPassedToSWT = 0; /* set to 1 on success */
static const int FILEOPEN_RETRY_TIMEOUT_MS = 1000;
/** GDBus related */
static const gchar GDBUS_SERVICE[] = "org.eclipse.swt";
static const gchar GDBUS_OBJECT[] = "/org/eclipse/swt";
static const gchar GDBUS_INTERFACE[] = "org.eclipse.swt";
GDBusProxy *gdbus_proxy = NULL;
gboolean gdbus_initProxy ();
gboolean gdbus_testConnection();
gboolean gdbus_FileOpen_TimerProc(gpointer data);
gboolean gdbus_call_FileOpen ();
/*
* Deals with opening files passed to eclipse. e.g: ./eclipse /myfile
*
* return 1 = Files passed to eclipse. Don't spawn another instance.
* 0 = Launch a new eclipse instance, will try to pass files to eclipse once instance is launched.
*/
gboolean reuseWorkbench(_TCHAR** filePath, int timeout) {
openFileTimeout = timeout;
openFilePath = filePath;
if (initWindowSystem(&initialArgc, initialArgv, 1) != 0)
return -1;
if (!gdbus_initProxy()) {
_ftprintf(stderr, "Launcher Error. Could not init gdbus proxy. Bug? Launching eclipse without opening files passed in.\n");
return 0;
}
// If eclipse already open, just pass files.
if (gdbus_testConnection()) {
return gdbus_call_FileOpen();
} else {
// Otherwise add a timer that will keep trying to pass files to eclipse for a few minutes until it succeeds or times out.
// Note, the while loop in launchJavaVM() ensures the launcher doesn't quit before the timer expired.
gtk.g_timeout_add(FILEOPEN_RETRY_TIMEOUT_MS, gdbus_FileOpen_TimerProc, 0);
return 0;
}
}
/**
* Initializes variables/structures for dbus connectivity to GDBus session.
* Can be called multiple times, only first time initializes, other times just return early (1).
*
* DO NOT USE TO TEST CONNECTION. Use dbus_testConnection() instead.
*
* return: 0 (false) bug, something that shouldn't fail failed. This can happen if dynamic function calls failed or gdbus is not available etc..
* 1 (true) proxy configured.
*/
gboolean gdbus_initProxy () {
if (gdbus_proxy != NULL)
return 1; // already initialized.
// Construct service name: org.eclipse.swt.<name>
const gint serviceNameLength = strlen(GDBUS_SERVICE) + strlen(getOfficialName()) + 2;
gchar *serviceName = (gchar *) malloc(serviceNameLength * sizeof(gchar));
snprintf(serviceName, serviceNameLength, "%s.%s", GDBUS_SERVICE, getOfficialName());
// Replace any characters that are not valid in a GDBus name with a hyphen.
int i;
for (i = 0; i < serviceNameLength - 1; i++) {
gchar c = serviceName[i];
if (!((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c == '_') ||(c == '-') || (c == '.')
)) {
serviceName[i] = '-';
}
}
// Function 'g_type_init()' is not needed anymore as of glib 2.36 as gtype system is initialized earlier. It is marked as deprecated.
// It is here because at the time of writing, eclipse supports glib 2.28.
// It is dynamic to prevent compile warning. But should be removed once min glib eclipse version >= 2.36
gtk.g_type_init();
GError *error = NULL; // Some functions return errors through params
gdbus_proxy = gtk.g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, serviceName, GDBUS_OBJECT, GDBUS_INTERFACE, NULL, &error);
if ((gdbus_proxy == NULL) || (error != NULL)) {
fprintf(stderr, "Launcher error: GDBus proxy init failed to connect %s:%s on %s.\n", serviceName, GDBUS_OBJECT, GDBUS_INTERFACE);
if (error != NULL) {
_ftprintf(stderr, "Launcher error: GDBus gdbus_proxy init failed for reason: %s\n", error->message);
gtk.g_error_free (error);
}
free(serviceName);
return 0;
} else {
free(serviceName);
return 1;
}
}
/*
* Test if we can reach org.eclipse.swt dbus session.
*
* return 0 (false) No connection.
* 1 (true) org.eclipse.swt listens to calls.
*/
gboolean gdbus_testConnection() {
if (!gdbus_initProxy())
return 0;
GError *error = NULL; // Some functions return errors through params
GVariant *result; // The value result from a call
result = gtk.g_dbus_proxy_call_sync(gdbus_proxy, "org.freedesktop.DBus.Peer.Ping", 0, G_DBUS_CALL_FLAGS_NONE, /* Proxy default timeout */ -1, NULL, &error);
if (error != NULL) {
gtk.g_error_free(error);
return 0;
}
if (result != NULL) {
gtk.g_variant_unref (result);
return 1;
}
_ftprintf(stderr, "ERROR: testConnection failed due to unknown reason. Bug in eclipseGtk.c? Potential cause could be dynamic function not initialized properly\n");
return 0;
}
/*
* Timer callback function.
*
* Try to pass files to eclipse. If eclipse not up yet, try again later.
*
* Timer ends when it returns false. (Files passed to eclipse or timeout).
*/
gboolean gdbus_FileOpen_TimerProc(gpointer data) {
if (openFileTimeout == 0)
return 0; // stop timer.
openFileTimeout--;
if (gdbus_testConnection()) {
gdbus_call_FileOpen();
filesPassedToSWT = 1;
return 0; // stop timer.
}
return 1; // run timer again.
}
/*
* Call fileOpen method in SWT. Note, in SWT, see GDBus.java. fileOpen GDBusMethod is defined in Display.java.
* This call can be called multiple times if Eclipse hasn't launched yet.
*
* Return: FALSE (0) Call did not work. Probably eclipse not fired up yet. (try again later)
* TRUE (1) GDBus call completed successfully.
*/
gboolean gdbus_call_FileOpen () {
if (!gdbus_initProxy())
return 0;
// Construct GDBus arguments based on files passed into launcher.
GVariantBuilder *builder;
GVariant *paramaters;
builder = gtk.g_variant_builder_new ((const GVariantType *) "as"); // as = G_VARIANT_TYPE_STRING_ARRAY
int i = -1;
while (openFilePath[++i] != NULL) {
gtk.g_variant_builder_add (builder, (const gchar *) (const GVariantType *) "s", (const gchar *) openFilePath[i]); // s = G_VARIANT_TYPE_STRING
}
paramaters = gtk.g_variant_new ("(as)", builder);
gtk.g_variant_builder_unref (builder);
// Send a message
GError *error = NULL;
GVariant *result;
result = gtk.g_dbus_proxy_call_sync(gdbus_proxy, "FileOpen", paramaters, G_DBUS_CALL_FLAGS_NONE, /* Proxy default timeout */ -1, NULL, &error);
if (error != NULL) {
gtk.g_error_free (error);
return 0; // did not work. Eclipse probably not up yet. Try again later.
} else {
if (result != NULL) {
// Because this not straight forward, below is an example of how to retrieve string return value if needed in the future.
// Note, arguments are packaged into a tuple because we deal with gdbus in dynamic way.
// gchar *str;
// g_variant_get(result, "(&s)", &str);
gtk.g_variant_unref(result);
}
return 1; // worked.
}
}
/* Get current scaling-factor */
float scaleFactor () {
float scaleFactor = 1;
GdkScreen * screen;
double resolution;
screen = gtk.gdk_screen_get_default();
resolution = gtk.gdk_screen_get_resolution (screen);
if (resolution <= 0) resolution = 96; // in unix and windows 100% corresponds to dpi of 96
resolution = ((int)((resolution + 24) / 96)) * 96; //rounding the resolution to 100% multiples,this implementation needs to be kept in sync with org.eclipse.swt.internal.DPIUtil#setDeviceZoom(int)
scaleFactor = (float)(resolution / 96);
return scaleFactor;
}
/* Create and Display the Splash Window */
int showSplash( const char* featureImage ) {
GtkWidget *image;
GdkPixbuf *pixbuf, *scaledPixbuf;
int width, height;
float scalingFactor;
if (splashHandle != 0)
return 0; /* already showing splash */
if (featureImage == NULL)
return -1;
if (initialArgv == NULL)
initialArgc = 0;
if( initWindowSystem(&initialArgc, initialArgv, 1) != 0)
return -1;
shellHandle = gtk.gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk.gtk_window_set_decorated((GtkWindow*)(shellHandle), FALSE);
gtk.gtk_window_set_type_hint((GtkWindow*)(shellHandle), 4 /*GDK_WINDOW_TYPE_HINT_SPLASHSCREEN*/);
gtk.g_signal_connect_data((gpointer)shellHandle, "destroy", (GCallback)(gtk.gtk_widget_destroyed), &shellHandle, NULL, 0);
pixbuf = gtk.gdk_pixbuf_new_from_file(featureImage, NULL);
width = gtk.gdk_pixbuf_get_width(pixbuf);
height = gtk.gdk_pixbuf_get_height(pixbuf);
scalingFactor = scaleFactor();
if (scalingFactor > 1) {
scaledPixbuf = gtk.gdk_pixbuf_scale_simple(pixbuf, width * scalingFactor, height * scalingFactor, GDK_INTERP_BILINEAR);
} else {
scaledPixbuf = pixbuf;
}
image = gtk.gtk_image_new_from_pixbuf(scaledPixbuf);
if (pixbuf) {
gtk.g_object_unref(pixbuf);
}
gtk.gtk_container_add((GtkContainer*)(shellHandle), image);
if (getOfficialName() != NULL)
gtk.gtk_window_set_title((GtkWindow*)(shellHandle), getOfficialName());
gtk.gtk_window_set_position((GtkWindow*)(shellHandle), GTK_WIN_POS_CENTER);
gtk.gtk_window_resize((GtkWindow*)(shellHandle), gtk.gdk_pixbuf_get_width(scaledPixbuf), gtk.gdk_pixbuf_get_height(scaledPixbuf));
gtk.gtk_widget_show_all((GtkWidget*)(shellHandle));
splashHandle = shellHandle;
dispatchMessages();
return 0;
}
void dispatchMessages() {
if (gtk.g_main_context_iteration != 0)
while(gtk.g_main_context_iteration(0,0) != 0) {}
}
jlong getSplashHandle() {
return (jlong) splashHandle;
}
void takeDownSplash() {
if(shellHandle != 0) {
gtk.gtk_widget_destroy(shellHandle);
dispatchMessages();
splashHandle = 0;
shellHandle = NULL;
}
}
/* Get the window system specific VM arguments */
char** getArgVM( char* vm ) {
char** result;
/* if (isJ9VM( vm ))
return argVM_J9;*/
/* Use the default arguments for a standard Java VM */
result = argVM_JAVA;
return result;
}
JavaResults* launchJavaVM( char* args[] ) {
JavaResults* jvmResults = NULL;
pid_t jvmProcess, finishedProcess = 0;
int exitCode;
jvmProcess = fork();
if (jvmProcess == 0)
{
/* Child process ... start the JVM */
execv(args[0], args);
/* The JVM would not start ... return error code to parent process. */
/* TODO, how to distinguish this as a launch problem to the other process? */
_exit(errno);
}
jvmResults = malloc(sizeof(JavaResults));
memset(jvmResults, 0, sizeof(JavaResults));
/* If the JVM is still running, wait for it to terminate. */
if (jvmProcess != 0)
{
/* When attempting a file open, we need to spin the event loop
* for setAppWindowTimerProc to run. When that succeeds or times out,
* we can stop the event loop and just wait on the child process.
*/
if (openFilePath != NULL) {
struct timespec sleepTime;
sleepTime.tv_sec = 0;
sleepTime.tv_nsec = 5e+8; // 500 milliseconds
// Ensure we don't quit the launcher until gdbus_FileOpen_TimerProc() finished or timed out.
// If making any changes to this loop, ensure "./eclipse /myFile" still works.
while(openFileTimeout > 0 && !filesPassedToSWT && (finishedProcess = waitpid(jvmProcess, &exitCode, WNOHANG)) == 0) {
dispatchMessages();
nanosleep(&sleepTime, NULL);
}
}
if (finishedProcess == 0)
waitpid(jvmProcess, &exitCode, 0);
if (WIFEXITED(exitCode))
/* TODO, this should really be a runResult if we could distinguish the launch problem above */
jvmResults->launchResult = WEXITSTATUS(exitCode);
}
return jvmResults;
}