[rkward/frameworks] /: EXPERIMENTAL: Remove the startup wrapper script, moving funcationality to the main executable, as needed.

Thomas Friedrichsmeier null at kde.org
Mon May 15 20:11:31 UTC 2017


Git commit e6610f3390046207a18f33898ed57c8a94227ecf by Thomas Friedrichsmeier.
Committed on 15/05/2017 at 20:09.
Pushed by tfry into branch 'frameworks'.

EXPERIMENTAL: Remove the startup wrapper script, moving funcationality to the main executable, as needed.

Should address a number of quirks (esp. on Windows and Mac), hopefully without adding  worse bugs in their place.

M  +4    -0    ChangeLog
M  +0    -9    doc/rkward/man-rkward.1.docbook
M  +14   -24   rkward/CMakeLists.txt
M  +136  -26   rkward/main.cpp
M  +18   -0    rkward/misc/rkcommonfunctions.cpp
M  +2    -0    rkward/misc/rkcommonfunctions.h
M  +1    -1    rkward/org.kde.rkward.desktop
M  +0    -5    rkward/rbackend/rinterface.cpp
M  +13   -10   rkward/rbackend/rkfrontendtransmitter.cpp
M  +5    -7    rkward/rbackend/rkrbackendprotocol_backend.cpp
D  +0    -332  rkward/rkward_startup_wrapper.cpp

https://commits.kde.org/rkward/e6610f3390046207a18f33898ed57c8a94227ecf

diff --git a/ChangeLog b/ChangeLog
index 5276786f..fc4ff8b0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+- Remove startup wrapper script (moving the still-needed functionality into the main executable)
+    TODO: This is still mostly EXPERIMENTAL, and only vaguely tested on Linux so far. It should help resolve a number of long standing quirks, though, such as command windows (on Windows), icons,
+          startup notification quirks, etc.
+    TODO: Clean up the Mac portions in CMakeLists.txt
 - Function argument hinting is less easily fooled by braces inside quotes
 - Preview status messages can now be closed
 - Show the message accompanying rk.show.files() or rk.edit.files() inside the main window, instead of a separate dialog
diff --git a/doc/rkward/man-rkward.1.docbook b/doc/rkward/man-rkward.1.docbook
index da46dfa9..03471efc 100644
--- a/doc/rkward/man-rkward.1.docbook
+++ b/doc/rkward/man-rkward.1.docbook
@@ -36,7 +36,6 @@
 <group choice="opt"><option>--evaluate</option> <replaceable> Rcode</replaceable></group>
 <group choice="opt"><option>--debug-level</option> <replaceable> level</replaceable></group>
 <group choice="opt"><option>--debug-flags</option> <replaceable> flags</replaceable></group>
-<arg><option>--debugger</option> <replaceable>debugger_command</replaceable><arg choice="opt">debugger_args<arg choice="opt"><option>--</option></arg></arg></arg>
 <group choice="opt"><option>--backend-debugger</option> <replaceable> debugger_command</replaceable></group>
 <group choice="opt"><option>--r-executable</option> <replaceable> path_to_executable</replaceable></group>
 <group choice="opt"><option>--reuse</option></group>
@@ -70,12 +69,6 @@
 <listitem><para>Allows to configure, which sections of code to debug. Flags are given as a binary number. Refer to the source files for documentation, as this really is an internal option.</para></listitem>
 </varlistentry>
 <varlistentry>
-<term><option>--debugger</option> <replaceable>command</replaceable> [<replaceable>arguments</replaceable> [<replaceable>--</replaceable>]]</term>
-<listitem><para>Run &rkward; through the specified debugger command. All arguments following this will be passed to the debugger command. To end debugger arguments (and add arguments to pass to &rkward;), use "--". <emphasis>NOTE:</emphasis> Only the frontend process will be debugged, using this option.</para>
-<para>Note that there are a number of pitfalls that may complicate setting up the debugger session as desired. Consider starting &rkward; with option \-\-debug-lebel 3, which will print the effective command line used to start the frontend (but not all relevant environment variables). As one hint, you will generally need to pass a separator argument with the debugger arguments, e.g. <command>rkward --debugger gdb --args</command>.
-Under Windows, the debugger command will <emphasis>not</emphasis> be connected to stdin. For interactive debugging, consider using a graphical debugger.</para></listitem>
-</varlistentry>
-<varlistentry>
 <term><option>--backend-debugger</option> <replaceable>command</replaceable></term>
 <listitem><para>Run the &rkward; backend through the specified debugger command. To add command line options to the debugger command, enclose them in single quotes ('') together with the command. <emphasis>NOTE:</emphasis> Debugger arguments will be split by spaces. If this is not appropriate, you will have to write your own wrapper script for invoking the debugger. Also, make sure to redirect all debugger output and/or input as appropriate. See the examples.</para></listitem>
 </varlistentry>
@@ -115,8 +108,6 @@ rkward --reuse file_a.R file_b.R
 
 # Run the rkward backend through valgrind
 rkward --backend-debugger 'valgrind --log-file=valgrind.log'.
-# Debug the frontend through gdb
-rkward --debugger 'gdb --args'
 </programlisting></para>
 </refsect1>
 
diff --git a/rkward/CMakeLists.txt b/rkward/CMakeLists.txt
index 1d7b4d1a..037d0ba9 100755
--- a/rkward/CMakeLists.txt
+++ b/rkward/CMakeLists.txt
@@ -40,8 +40,18 @@ SET(RKWard_Sources
 GET_DIRECTORY_PROPERTY(R_SHAREDLIBDIR DIRECTORY rbackend LINK_DIRECTORIES)
 LINK_DIRECTORIES(${R_SHAREDLIBDIR})
 
-add_executable(rkward.frontend ${RKWard_Sources})
-SET_TARGET_PROPERTIES(rkward.frontend PROPERTIES
+GET_DIRECTORY_PROPERTY(R_EXECUTABLE DIRECTORY rbackend DEFINITION R_EXECUTABLE)
+ADD_CUSTOM_COMMAND (OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rkward.ico"
+			COMMAND cmake -E copy "${CMAKE_CURRENT_SOURCE_DIR}/icons/app-icon/rkward.ico"
+			"${CMAKE_CURRENT_BINARY_DIR}/rkward.ico")
+
+add_executable(rkward ${RKWard_Sources} rkward_windows_icon.rc rkward.ico)
+# NOTE: These definitions are needed for the wrapper, only.
+# We should switch with to target_compile_definitions once we require CMAKE 2.6+
+add_definitions (-DR_EXECUTABLE="${R_EXECUTABLE}")
+add_definitions (-DINSTALL_PATH="${CMAKE_INSTALL_PREFIX}")
+add_definitions (-DR_LIBS="${R_LIBDIR}")
+SET_TARGET_PROPERTIES(rkward PROPERTIES
         MACOSX_BUNDLE_BUNDLE_NAME "RKWard")
 
 IF(Q_WS_MAC)
@@ -53,31 +63,12 @@ IF(Q_WS_MAC)
 		@ONLY)
 ENDIF(Q_WS_MAC)
 
-TARGET_LINK_LIBRARIES(rkward.frontend windows ${RKWARD_ADDLIBS} agents dialogs plugin settings dataeditor core scriptbackends rbackend misc KF5::WindowSystem Qt5::Widgets KF5::XmlGui ${LibIntl_LIBRARIES})
+TARGET_LINK_LIBRARIES(rkward windows ${RKWARD_ADDLIBS} agents dialogs plugin settings dataeditor core scriptbackends rbackend misc KF5::WindowSystem Qt5::Widgets KF5::XmlGui ${LibIntl_LIBRARIES})
 IF(KF5Crash_FOUND)
-	TARGET_LINK_LIBRARIES(rkward.frontend KF5::Crash)
+	TARGET_LINK_LIBRARIES(rkward KF5::Crash)
 	SET_SOURCE_FILES_PROPERTIES(main.cpp PROPERTIES COMPILE_DEFINITIONS WITH_KCRASH=1)
 ENDIF(KF5Crash_FOUND)
 
-# wrapper executable
-GET_DIRECTORY_PROPERTY(R_EXECUTABLE DIRECTORY rbackend DEFINITION R_EXECUTABLE)
-IF (WIN32)
-	SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
-ENDIF (WIN32)
-ADD_CUSTOM_COMMAND (OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rkward.ico"
-			COMMAND cmake -E copy "${CMAKE_CURRENT_SOURCE_DIR}/icons/app-icon/rkward.ico"
-			"${CMAKE_CURRENT_BINARY_DIR}/rkward.ico")
-ADD_EXECUTABLE (rkward rkward_startup_wrapper.cpp rkward_windows_icon.rc rkward.ico)
-# NOTE: These definitions are needed for the wrapper, only.
-# We should switch with to target_compile_definitions once we require CMAKE 2.6+
-add_definitions (-DR_EXECUTABLE="${R_EXECUTABLE}")
-add_definitions (-DINSTALL_PATH="${CMAKE_INSTALL_PREFIX}")
-add_definitions (-DR_LIBS="${R_LIBDIR}")
-add_definitions (-DRKWARD_FRONTEND_LOCATION="${CMAKE_INSTALL_PREFIX}/${RKWARD_FRONTEND_LOCATION}")
-TARGET_LINK_LIBRARIES(rkward Qt5::Gui Qt5::Core Qt5::DBus Qt5::Widgets)
-
-ADD_DEPENDENCIES(rkward.frontend rkward)
-
 ########### install files ###############
 
 INSTALL(DIRECTORY plugins/ pages DESTINATION ${DATA_INSTALL_DIR}/rkward
@@ -92,7 +83,6 @@ IF(Q_WS_MAC)
 	INSTALL(FILES ${CMAKE_SOURCE_DIR}/rkward/icons/app-icon/rkward.icns DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents/Resources)
 	INSTALL(FILES ${RKWARD_INFOPLIST_FILE} DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents)
 ELSE(Q_WS_MAC)
-	INSTALL(TARGETS rkward.frontend DESTINATION ${LIBEXEC_INSTALL_DIR})
 	INSTALL(TARGETS rkward DESTINATION ${BIN_INSTALL_DIR})
 ENDIF(Q_WS_MAC)
 
diff --git a/rkward/main.cpp b/rkward/main.cpp
index 782a9e85..487d8c08 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -66,6 +66,9 @@
 #include <QApplication>
 #include <QUrl>
 #include <QCommandLineParser>
+#include <QtDBus>
+#include <QSettings>
+#include <QMessageBox>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -74,6 +77,8 @@
 #include "rkglobals.h"
 #include "settings/rksettingsmoduledebug.h"
 #include "windows/rkdebugmessagewindow.h"
+#include "misc/rkdbusapi.h"
+#include "misc/rkcommonfunctions.h"
 
 #ifdef Q_OS_WIN
 	// these are needed for the exit hack.
@@ -84,6 +89,28 @@
 
 #include "version.h"
 
+#ifndef R_EXECUTABLE
+#	define R_EXECUTABLE ""
+#endif
+
+#ifdef Q_OS_WIN
+#	define PATH_VAR_SEP ';'
+#else
+#	define PATH_VAR_SEP ':'
+#endif
+
+QString findExeAtPath (const QString appname, const QString &path) {
+	QDir dir (path);
+	dir.makeAbsolute ();
+	if (QFileInfo (dir.filePath (appname)).isExecutable ()) return dir.filePath (appname);
+#ifdef Q_OS_WIN
+	if (QFileInfo (dir.filePath (appname + ".exe")).isExecutable ()) return dir.filePath (appname + ".exe");
+	if (QFileInfo (dir.filePath (appname + ".com")).isExecutable ()) return dir.filePath (appname + ".com");
+	if (QFileInfo (dir.filePath (appname + ".bat")).isExecutable ()) return dir.filePath (appname + ".bat");
+#endif
+	return QString ();
+}
+
 int RK_Debug_Level = 0;
 int RK_Debug_Flags = DEBUG_ALL;
 int RK_Debug_CommandStep = 0;
@@ -119,21 +146,12 @@ void RKDebug (int flags, int level, const char *fmt, ...) {
 	}
 }
 
-QString decodeArgument (const QString &input) {
-	return (QUrl::fromPercentEncoding (input.toUtf8()));
-}
-
 int main (int argc, char *argv[]) {
-	// before initializing the commandline args, remove the ".bin" from "rkward.bin".
-	// This is so it prints "Usage rkward..." instead of "Usage rkward.bin...", etc.
-	// it seems safest to keep a copy, since the shell still owns argv
-	char **argv_copy = new char*[argc];
-	argv_copy[0] = qstrdup (QString (argv[0]).remove (".frontend").replace (".exe", ".bat").toLocal8Bit ());
-	for (int i = 1; i < argc; ++i) {
-		argv_copy[i] = argv[i];
-	}
+	// TODO: This is a _temporary_ hack! --> Problems with output not updating on Windows Live Image. See https://mail.kde.org/pipermail/rkward-devel/2016-September/004660.html .
+#warning Remove me!
+	qputenv ("KDIRWATCH_METHOD", QByteArray ("Stat"));
 
-	QApplication app (argc, argv_copy);
+	QApplication app (argc, argv);
 #ifdef WITH_KCRASH
 	KCrash::setDrKonqiEnabled (true);
 #endif
@@ -174,7 +192,6 @@ int main (int argc, char *argv[]) {
 	parser.addOption (QCommandLineOption ("evaluate", i18n ("After starting (and after loading the specified workspace, if applicable), evaluate the given R code."), "Rcode", QString ()));
 	parser.addOption (QCommandLineOption ("debug-level", i18n ("Verbosity of debug messages (0-5)"), "level", "2"));
 	parser.addOption (QCommandLineOption ("debug-flags", i18n ("Mask for components to debug (see debug.h)"), "flags", QString::number (DEBUG_ALL)));
-	parser.addOption (QCommandLineOption ("debugger", i18n ("Debugger for the frontend. Specify last, or add '--' after all debugger arguments"), "command and arguments", QString ()));
 	parser.addOption (QCommandLineOption ("backend-debugger", i18n ("Debugger for the backend. (Enclose any debugger arguments in single quotes ('') together with the command. Make sure to re-direct stdout!)"), "command", QString ()));
 	parser.addOption (QCommandLineOption ("r-executable", i18n ("Use specified R installation, instead of the one configured at compile time (note: rkward R library must be installed to that installation of R)"), "command", QString ()));
 	parser.addOption (QCommandLineOption ("reuse", i18n ("Reuse a running RKWard instance (if available). If a running instance is reused, only the file arguments will be interpreted, all other options will be ignored.")));
@@ -185,30 +202,124 @@ int main (int argc, char *argv[]) {
 	parser.process (app);
 	aboutData.processCommandLine (&parser);
 
+	// Set up debugging
 	RK_Debug_Level = DL_FATAL - QString (parser.value ("debug-level")).toInt ();
 	RK_Debug_Flags = QString (parser.value ("debug-flags")).toInt ();
-	if (!parser.value ("debugger").isEmpty ()) {
-		RK_DEBUG (DEBUG_ALL, DL_ERROR, "--debugger option should have been handled by wrapper script. Ignoring.");
+	RKSettingsModuleDebug::debug_file = new QTemporaryFile (QDir::tempPath () + "/rkward.frontend");
+	RKSettingsModuleDebug::debug_file->setAutoRemove (false);
+	if (RKSettingsModuleDebug::debug_file->open ()) {
+		RK_DEBUG (APP, DL_INFO, "Full debug output is at %s", qPrintable (RKSettingsModuleDebug::debug_file->fileName ()));
+		qInstallMessageHandler (RKDebugMessageOutput);
 	}
 
+	// handle positional (file) arguments, first
 	QStringList url_args = parser.positionalArguments ();
 	if (!url_args.isEmpty ()) {
 		for (int i = 0; i < url_args.size (); ++i) {
-			url_args[i] = decodeArgument (url_args[i]);
+			url_args[i] = QUrl::fromUserInput (url_args[i], QDir::currentPath (), QUrl::AssumeLocalFile).toString ();
 		}
 		RKGlobals::startup_options["initial_urls"] = url_args;
 		RKGlobals::startup_options["warn_external"] = !parser.isSet ("nowarn-external");
 	}
-	RKGlobals::startup_options["evaluate"] = decodeArgument (parser.value ("evaluate"));
-	RKGlobals::startup_options["backend-debugger"] = decodeArgument (parser.value ("backend-debugger"));
+	RKGlobals::startup_options["evaluate"] = parser.value ("evaluate");
+	RKGlobals::startup_options["backend-debugger"] = parser.value ("backend-debugger");
 
-	// install message handler *after* the componentData has been initialized
-	RKSettingsModuleDebug::debug_file = new QTemporaryFile (QDir::tempPath () + "/rkward.frontend");
-	RKSettingsModuleDebug::debug_file->setAutoRemove (false);
-	if (RKSettingsModuleDebug::debug_file->open ()) {
-		RK_DEBUG (APP, DL_INFO, "Full debug output is at %s", qPrintable (RKSettingsModuleDebug::debug_file->fileName ()));
-		qInstallMessageHandler (RKDebugMessageOutput);
+	// Handle --reuse option, by placing a dbus-call to existing RKWard process (if any) and exiting
+	if (parser.isSet ("reuse")) {
+		if (!QDBusConnection::sessionBus ().isConnected ()) {
+			RK_DEBUG (DEBUG_ALL, DL_WARNING, "Could not connect to session dbus");
+		} else {
+			QDBusInterface iface (RKDBUS_SERVICENAME, "/", "", QDBusConnection::sessionBus ());
+			if (iface.isValid ()) {
+				QDBusReply<void> reply = iface.call ("openAnyUrl", url_args, !parser.isSet ("nowarn-external"));
+				if (!reply.isValid ()) {
+					RK_DEBUG (DEBUG_ALL, DL_ERROR, "Error while placing dbus call: %s", qPrintable (reply.error ().message ()));
+					return 1;
+				}
+				return 0;
+			}
+		}
+	}
+
+// MacOS may need some path adjustments, first
+#ifdef Q_OS_MAC
+	QString oldpath = qgetenv ("PATH");
+	if (!oldpath.contains (INSTALL_PATH)) {
+		//ensure that PATH is set to include what we deliver with the bundle
+		qputenv ("PATH", QString ("%1/bin:%1/sbin:%2").arg (INSTALL_PATH).arg (oldpath).toLocal8Bit ());
+		if (debug_level > 3) qDebug ("Adjusting system path to %s", qPrintable (qgetenv ("PATH")));
+	}
+	// ensure that RKWard finds its own packages
+	qputenv ("R_LIBS", R_LIBS);
+	QProcess::execute ("launchctl", QStringList () << "load" << "-w" << INSTALL_PATH "/Library/LaunchAgents/org.freedesktop.dbus-session.plist");
+#endif
+
+	// Locate KDE and RKWard installations
+	QString marker_exe_name ("qtpaths");    // Simply some file that should exist in the bin dir of a KDE installation on both Unix and Windows
+	QString marker_exe = findExeAtPath (marker_exe_name, QDir::currentPath ());
+	if (marker_exe.isNull ()) marker_exe = findExeAtPath (marker_exe_name, app.applicationDirPath ());
+	if (marker_exe.isNull ()) marker_exe = findExeAtPath (marker_exe_name, QDir (app.applicationDirPath ()).filePath ("KDE/bin"));
+	QStringList syspath = QString (qgetenv ("PATH")).split (PATH_VAR_SEP);
+	if (marker_exe.isNull ()) {
+		for (int i = 0; i < syspath.size (); ++i) {
+			marker_exe = findExeAtPath (marker_exe_name, syspath[i]);
+			if (!marker_exe.isNull ()) break;
+		}
+	}
+
+	if (marker_exe.isNull ()) {
+		QMessageBox::critical (0, "Could not find KDE installation", "The KDE installation could not be found (" + marker_exe_name + "). When moving / copying RKWard, make sure to copy the whole application folder, or create a shorcut / link, instead.");
+		exit (1);
+	}
+
+	QDir kde_dir (QFileInfo (marker_exe).absolutePath ());
+	kde_dir.makeAbsolute ();
+	QString kde_dir_safe_path = RKCommonFunctions::windowsShellScriptSafeCommand (kde_dir.path ());
+	if (syspath.indexOf (kde_dir.path ()) < 0) {
+		RK_DEBUG (DEBUG_ALL, DL_INFO, "Adding %s to the system path", qPrintable (kde_dir_safe_path));
+		qputenv ("PATH", QString (kde_dir_safe_path + PATH_VAR_SEP + qgetenv ("PATH")).toLocal8Bit ());
+	}
+
+	// Look for R:
+	//- command line parameter
+	//- Specified in cfg file next to rkward executable
+	//- compile-time default
+	QString r_exe = parser.value ("r-executable");
+	if (!r_exe.isNull ()) {
+		if (!QFileInfo (r_exe).isExecutable ()) {
+			QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe));
+			exit (1);
+		}
+		RK_DEBUG (APP, DL_DEBUG, "Using R specified on command line");
+	} else {
+		QDir frontend_path = app.applicationDirPath ();
+		QFileInfo rkward_ini_file (frontend_path.absoluteFilePath ("rkward.ini"));
+		if (rkward_ini_file.isReadable ()) {
+			QSettings rkward_ini (rkward_ini_file.absoluteFilePath (), QSettings::IniFormat);
+			r_exe = rkward_ini.value ("R executable").toString ();
+			if (!r_exe.isNull ()) {
+				if (QDir::isRelativePath (r_exe)) {
+					r_exe = frontend_path.absoluteFilePath (r_exe);
+				}
+				if (!QFileInfo (r_exe).isExecutable ()) {
+					QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified in the rkward.ini file (%1) does not exist or is not executable.").arg (rkward_ini_file.absoluteFilePath ()));
+					exit (1);
+				}
+			}
+			RK_DEBUG (APP, DL_DEBUG, "Using R as configured in config file %s", qPrintable (rkward_ini_file.absoluteFilePath ()));
+		}
+		if (r_exe.isNull ()) {
+			r_exe = R_EXECUTABLE;
+			if (!QFileInfo (r_exe).isExecutable ()) {
+				QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified at compile time (%1) does not exist or is not executable. Probably the installation of R has moved. You can use the command line parameter '--r-executable <i>PATH_TO_R</i>', or supply an rkward.ini file to specify the new location.").arg (r_exe));
+				exit (1);
+			}
+			RK_DEBUG (APP, DL_DEBUG, "Using R as configured at compile time");
+		}
 	}
+	// TODO: Store somewhere else
+	qputenv ("R_BINARY", r_exe.toLocal8Bit ());
+
 
 	if (app.isSessionRestored ()) {
 		RESTORE(RKWardMainWindow);	// well, whatever this is supposed to do -> TODO
@@ -227,7 +338,6 @@ int main (int argc, char *argv[]) {
 
 	qInstallMessageHandler (0);
 	RKSettingsModuleDebug::debug_file->close ();
-	delete argv_copy;
 
 	return status;
 }
diff --git a/rkward/misc/rkcommonfunctions.cpp b/rkward/misc/rkcommonfunctions.cpp
index 77e296f1..431014f7 100644
--- a/rkward/misc/rkcommonfunctions.cpp
+++ b/rkward/misc/rkcommonfunctions.cpp
@@ -222,4 +222,22 @@ namespace RKCommonFunctions {
 		return (i18n ("<p><em>Note:</em> This setting does not take effect until you restart RKWard.</p>"));
 	}
 
+#ifdef Q_OS_WIN
+#	include <windows.h>
+#	include <QTemporaryFile>
+#endif
+	QString windowsShellScriptSafeCommand (const QString &orig) {
+#ifdef Q_OS_WIN
+		// credits to http://erasmusjam.wordpress.com/2012/10/01/get-8-3-windows-short-path-names-in-a-qt-application/
+		QByteArray input (sizeof (wchar_t) * (orig.size()+1), '\0');
+		// wchar_t input[orig.size()+1]; -- No: MSVC (2013) does not support variable length arrays. Oh dear...
+		orig.toWCharArray ((wchar_t*) input.data ());
+		long length = GetShortPathName ((wchar_t*) input.data (), NULL, 0);
+		QByteArray output (sizeof (wchar_t) * (length), '\0');
+		GetShortPathName ((wchar_t*) input.data (), (wchar_t*) output.data (), length);
+		return QString::fromWCharArray ((wchar_t*) output.data (), length-1);
+#else
+		return orig;
+#endif
+	}
 }	// namespace
diff --git a/rkward/misc/rkcommonfunctions.h b/rkward/misc/rkcommonfunctions.h
index 23511cae..34683fc9 100644
--- a/rkward/misc/rkcommonfunctions.h
+++ b/rkward/misc/rkcommonfunctions.h
@@ -59,6 +59,8 @@ namespace RKCommonFunctions {
 /** simultaneously sets tool tips and what's this tips on up to three QWidgets */
 	void setTips (const QString tip, QWidget *first, QWidget *second=0, QWidget *third=0);
 	QString noteSettingsTakesEffectAfterRestart ();
+/** Passing commands as part of arguments to windows shell scripts will fail miserably for paths with spaces or special characters. Transform to short path names for safety. No-op on sane platforms.*/
+	QString windowsShellScriptSafeCommand (const QString &orig);
 };
 
 #endif
diff --git a/rkward/org.kde.rkward.desktop b/rkward/org.kde.rkward.desktop
index de75e66d..adb468eb 100755
--- a/rkward/org.kde.rkward.desktop
+++ b/rkward/org.kde.rkward.desktop
@@ -50,7 +50,7 @@ Comment[uk]=Графічний інтерфейс проекту R
 Comment[x-test]=xxGUI for the R-projectxx
 Icon=rkward
 X-DBUS-ServiceName=org.kde.rkward
-Exec=sh -c "STARTUP_ID_COPY=\\${DESKTOP_STARTUP_ID} rkward -qwindowtitle '%c'"
+Exec=rkward -qwindowtitle '%c'
 Terminal=false
 Type=Application
 X-DocPath=rkward/index.html
diff --git a/rkward/rbackend/rinterface.cpp b/rkward/rbackend/rinterface.cpp
index b058576e..37c812c9 100644
--- a/rkward/rbackend/rinterface.cpp
+++ b/rkward/rbackend/rinterface.cpp
@@ -77,11 +77,6 @@ int RInterface::na_int;
 RInterface::RInterface () {
 	RK_TRACE (RBACKEND);
 
-	// If R_HOME is not set, most certainly the user called the binary without the wrapper script
-	if (!getenv ("R_HOME")) {
-		RK_DEBUG (RBACKEND, DL_ERROR, "No R_HOME environment variable set. RKWard will quit in a moment. Always start rkward in the default way unless you know what you're doing.");
-	}
-
 	new RCommandStackModel (this);
 	RCommandStack::regular_stack = new RCommandStack ();
 	startup_phase2_error = false;
diff --git a/rkward/rbackend/rkfrontendtransmitter.cpp b/rkward/rbackend/rkfrontendtransmitter.cpp
index e8eeb5db..2f62e860 100644
--- a/rkward/rbackend/rkfrontendtransmitter.cpp
+++ b/rkward/rbackend/rkfrontendtransmitter.cpp
@@ -95,10 +95,11 @@ void RKFrontendTransmitter::run () {
 
 	QStringList args;
 	args.append ("--debug-level=" + QString::number (RK_Debug_Level));
-	args.append ("--server-name=" + server->fullServerName ());
-	args.append ("--rkd-server-name=" + rkd_transmitter->serverName ());
-	args.append ("--data-dir=" + RKSettingsModuleGeneral::filesPath ());
-	args.append ("--locale-dir=" + localeDir ());
+	// NOTE: QProcess quotes its arguments, *but* properly passing all spaces and quotes through the R CMD wrapper, seems near(?) impossible on Windows. Instead, we use percent encoding, internally.
+	args.append ("--server-name=" + server->fullServerName ().toUtf8 ().toPercentEncoding ());
+	args.append ("--rkd-server-name=" + rkd_transmitter->serverName ().toUtf8 ().toPercentEncoding ());
+	args.append ("--data-dir=" + RKSettingsModuleGeneral::filesPath ().toUtf8 ().toPercentEncoding ());
+	args.append ("--locale-dir=" + localeDir ().toUtf8 ().toPercentEncoding ());
 	connect (backend, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &RKFrontendTransmitter::backendExit);
 	QString backend_executable = findBackendAtPath (QCoreApplication::applicationDirPath ());
 	if (backend_executable.isEmpty ()) backend_executable = findBackendAtPath (QCoreApplication::applicationDirPath () + "/rbackend");	// for running directly from the build-dir
@@ -107,14 +108,16 @@ void RKFrontendTransmitter::run () {
 #endif
 	if (backend_executable.isEmpty ()) handleTransmissionError (i18n ("The backend executable could not be found. This is likely to be a problem with your installation."));
 	QString debugger = RKGlobals::startup_options["backend-debugger"].toString ();
+	args.prepend (RKCommonFunctions::windowsShellScriptSafeCommand (backend_executable));
+	args.prepend ("CMD");
 	if (!debugger.isEmpty ()) {
-		args.prepend (backend_executable);
-		QStringList l = debugger.split (' ');
-		args = l.mid (1) + args;
-		backend->start (l.first (), args, QIODevice::ReadOnly);
-	} else {
-		backend->start (backend_executable, args, QIODevice::ReadOnly);
+		args = debugger.split (' ') + args;
 	}
+for (int i = 0; i < args.size (); ++i) {
+qDebug ("%s", qPrintable (args[i]));
+}
+qDebug ("%s", qPrintable (qgetenv ("R_BINARY")));
+	backend->start (qgetenv ("R_BINARY"), args, QIODevice::ReadOnly);
 
 	if (!backend->waitForStarted ()) {
 		handleTransmissionError (i18n ("The backend executable could not be started. Error message was: %1", backend->errorString ()));
diff --git a/rkward/rbackend/rkrbackendprotocol_backend.cpp b/rkward/rbackend/rkrbackendprotocol_backend.cpp
index cbce78f8..84a87884 100644
--- a/rkward/rbackend/rkrbackendprotocol_backend.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_backend.cpp
@@ -33,6 +33,7 @@
 #include <QUuid>		// mis-used as a random-string generator
 #include <QTemporaryFile>
 #include <QDir>
+#include <QUrl>
 
 	void RK_setupGettext (const char*);
 	int RK_Debug_Level = 2;
@@ -83,16 +84,13 @@
 			if (args[i].startsWith ("--debug-level")) {
 				RK_Debug_Level = args[i].section ('=', 1).toInt ();
 			} else if (args[i].startsWith ("--server-name")) {
-				servername = args[i].section ('=', 1);
+				servername = QUrl::fromPercentEncoding (args[i].section ('=', 1).toUtf8 ());
 			} else if (args[i].startsWith ("--data-dir")) {
-#ifdef __GNUC__
-#	warning What about paths with spaces?!
-#endif
-				data_dir = args[i].section ('=', 1);
+				data_dir = QUrl::fromPercentEncoding (args[i].section ('=', 1).toUtf8 ());
 			} else if (args[i].startsWith ("--locale-dir")) {
-				locale_dir = args[i].section ('=', 1);
+				locale_dir = QUrl::fromPercentEncoding (args[i].section ('=', 1).toUtf8 ());
 			} else if (args[i].startsWith ("--rkd-server-name")) {
-				rkd_server_name = args[i].section ('=', 1);
+				rkd_server_name = QUrl::fromPercentEncoding (args[i].section ('=', 1).toUtf8 ());
 			} else {
 				printf ("unknown argument %s", qPrintable (args[i]));
 			}
diff --git a/rkward/rkward_startup_wrapper.cpp b/rkward/rkward_startup_wrapper.cpp
deleted file mode 100755
index b6f8f2a8..00000000
--- a/rkward/rkward_startup_wrapper.cpp
+++ /dev/null
@@ -1,332 +0,0 @@
-/***************************************************************************
-                          rkward_startup_wrapper  -  description
-                             -------------------
-    begin                : Sun Mar 10 2013
-    copyright            : (C) 2013, 2014, 2015 by Thomas Friedrichsmeier
-    email                : thomas.friedrichsmeier at kdemail.net
- ***************************************************************************/
-
-/***************************************************************************
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- ***************************************************************************/
-
-/** This simple helper executable is responsible for reading basic configuration
- * settings, checking for some services and starting RKWard. These things used
- * to be done using a script, and there was nothing wrong with that, in principle.
- * However, the binary allows more flexibility, and more consistency across
- * platforms. */
-
-#include <QApplication>
-#include <QMessageBox>
-#include <QDir>
-#include <QProcess>
-#include <QSettings>
-#include <QUrl>
-#include <QFile>
-#include <QtDBus>
-#include "misc/rkdbusapi.h"
-
-#ifndef RKWARD_FRONTEND_LOCATION
-#	define RKWARD_FRONTEND_LOCATION ""
-#endif
-
-#ifndef R_EXECUTABLE
-#	define R_EXECUTABLE ""
-#endif
-
-QString findExeAtPath (const QString appname, const QString &path) {
-	QDir dir (path);
-	dir.makeAbsolute ();
-	if (QFileInfo (dir.filePath (appname)).isExecutable ()) return dir.filePath (appname);
-#ifdef Q_OS_WIN
-	if (QFileInfo (dir.filePath (appname + ".exe")).isExecutable ()) return dir.filePath (appname + ".exe");
-	if (QFileInfo (dir.filePath (appname + ".com")).isExecutable ()) return dir.filePath (appname + ".com");
-	if (QFileInfo (dir.filePath (appname + ".bat")).isExecutable ()) return dir.filePath (appname + ".bat");
-#endif
-	return QString ();
-}
-
-QString findRKWardAtPath (const QString &path) {
-	return findExeAtPath ("rkward.frontend", path);
-}
-
-#ifdef Q_OS_WIN
-#include <windows.h>
-#include <QTemporaryFile>
-#endif
-QString quoteCommand (const QString &orig) {
-#ifdef Q_OS_WIN
-// Get short path name as a safe way to pass all sort of commands on the Windows shell
-// credits to http://erasmusjam.wordpress.com/2012/10/01/get-8-3-windows-short-path-names-in-a-qt-application/
-	QByteArray input (sizeof (wchar_t) * (orig.size()+1), '\0');
-	// wchar_t input[orig.size()+1]; -- No: MSVC (2013) does not support variable length arrays. Oh dear...
-	orig.toWCharArray ((wchar_t*) input.data ());
-	long length = GetShortPathName ((wchar_t*) input.data (), NULL, 0);
-	QByteArray output (sizeof (wchar_t) * (length), '\0');
-	GetShortPathName ((wchar_t*) input.data (), (wchar_t*) output.data (), length);
-	return QString::fromWCharArray ((wchar_t*) output.data (), length-1);
-#else
-	return orig;
-#endif
-}
-
-#ifndef Q_OS_WIN
-// see http://blog.qt.digia.com/blog/2006/03/16/starting-interactive-processes-with-qprocess/
-// Need an interactive process e.g. for running through gdb
-#	include <unistd.h>
-class InteractiveProcess : public QProcess {
-    static int stdinClone;
-public:
-    InteractiveProcess (QObject *parent = 0) : QProcess (parent) {
-        if (stdinClone == -1) stdinClone = ::dup (fileno(stdin));
-    }
-protected:
-    void setupChildProcess () override {
-        ::dup2 (stdinClone, fileno(stdin));
-    }
-};
-int InteractiveProcess::stdinClone = -1;
-#else
-// no easy solution for Windows. But ain't Windows the world of graphical debuggers, anyway...
-#	define InteractiveProcess QProcess
-#endif
-
-#ifdef Q_OS_WIN
-#	define PATH_VAR_SEP ';'
-#else
-#	define PATH_VAR_SEP ':'
-#endif
-
-int main (int argc, char *argv[]) {
-	QApplication app (argc, argv);
-	QStringList args = app.arguments ();
-	if (!args.isEmpty ()) args.pop_front ();	// The command itself
-	qputenv ("DESKTOP_STARTUP_ID", qgetenv ("STARTUP_ID_COPY"));	// for startup notifications (set via org.kde.rkward.desktop)
-	qputenv ("STARTUP_ID_COPY", "");
-
-	// Parse arguments that need handling in the wrapper
-	bool usage = false;
-	QStringList debugger_args;
-	QStringList file_args;
-	bool reuse = false;
-	bool warn_external = true;
-	QString r_exe_arg;
-	int debug_level = 2;
-
-	for (int i=0; i < args.size (); ++i) {
-		if (args[i] == "--debugger") {
-			args.removeAt (i);
-			while (i < args.size ()) {
-				QString arg = args.takeAt (i);
-				if (arg == "--") break;
-				debugger_args.append (arg);
-			}
-			if (debugger_args.isEmpty ()) usage = true;
-		} else if (args[i] == "--r-executable") {
-			if ((i+1) < args.size ()) {
-				r_exe_arg = args.takeAt (i + 1);
-			} else usage = true;
-			args.removeAt (i);
-			--i;
-		} else if (args[i] == "--debug-level") {
-			if ((i+1) < args.size ()) {
-				debug_level = args[i+1].toInt ();
-			}
-		} else if (args[i] == "--reuse") {
-			reuse = true;
-		} else if (args[i] == "--nowarn-external") {
-			warn_external = false;
-		} else if (args[i].startsWith ("--")) {
-			// all RKWard and KDE options (other than --reuse) are of the for --option <value>. So skip over the <value>
-			i++;
-		} else {
-			QUrl url = QUrl::fromUserInput (args[i], QDir::currentPath (), QUrl::AssumeLocalFile);
-			file_args.append (url.toString ());
-		}
-	}
-
-	if (reuse) {
-		if (!QDBusConnection::sessionBus ().isConnected ()) {
-			if (debug_level > 2) qDebug ("Could not connect to session dbus");
-		} else {
-			QDBusInterface iface (RKDBUS_SERVICENAME, "/", "", QDBusConnection::sessionBus ());
-			if (iface.isValid ()) {
-				QDBusReply<void> reply = iface.call ("openAnyUrl", file_args, warn_external);
-				if (!reply.isValid ()) {
-					if (debug_level > 2) qDebug ("Error while placing dbus call: %s", qPrintable (reply.error ().message ()));
-					return 1;
-				}
-				return 0;
-			}
-		}
-	}
-
-	// MacOS may need some path adjustments, first
-#ifdef Q_OS_MAC
-	QString oldpath = qgetenv ("PATH");
-	if (!oldpath.contains (INSTALL_PATH)) {
-		//ensure that PATH is set to include what we deliver with the bundle
-		qputenv ("PATH", QString ("%1/bin:%1/sbin:%2").arg (INSTALL_PATH).arg (oldpath).toLocal8Bit ());
-		if (debug_level > 3) qDebug ("Adjusting system path to %s", qPrintable (qgetenv ("PATH")));
-	}
-	// ensure that RKWard finds its own packages
-	qputenv ("R_LIBS", R_LIBS);
-	QProcess::execute ("launchctl", QStringList () << "load" << "-w" << INSTALL_PATH "/Library/LaunchAgents/org.freedesktop.dbus-session.plist");
-#endif
-
-	// Locate KDE and RKWard installations
-	QString marker_exe_name ("qtpaths");    // Simply some file that should exist in the bin dir of a KDE installation on both Unix and Windows
-	QString marker_exe = findExeAtPath (marker_exe_name, QDir::currentPath ());
-	if (marker_exe.isNull ()) marker_exe = findExeAtPath (marker_exe_name, app.applicationDirPath ());
-	if (marker_exe.isNull ()) marker_exe = findExeAtPath (marker_exe_name, QDir (app.applicationDirPath ()).filePath ("KDE/bin"));
-	QStringList syspath = QString (qgetenv ("PATH")).split (PATH_VAR_SEP);
-	if (marker_exe.isNull ()) {
-		for (int i = 0; i < syspath.size (); ++i) {
-			marker_exe = findExeAtPath (marker_exe_name, syspath[i]);
-			if (!marker_exe.isNull ()) break;
-		}
-	}
-
-	if (marker_exe.isNull ()) {
-		QMessageBox::critical (0, "Could not find KDE installation", "The KDE installation could not be found (" + marker_exe_name + "). When moving / copying RKWard, make sure to copy the whole application folder, or create a shorcut / link, instead.");
-		exit (1);
-	}
-
-	QDir kde_dir (QFileInfo (marker_exe).absolutePath ());
-	kde_dir.makeAbsolute ();
-	QString kde_dir_safe_path = quoteCommand (kde_dir.path ());
-	if (syspath.indexOf (kde_dir.path ()) < 0) {
-		if (debug_level > 3) qDebug ("Adding %s to the system path", qPrintable (kde_dir_safe_path));
-		qputenv ("PATH", QString (kde_dir_safe_path + PATH_VAR_SEP + qgetenv ("PATH")).toLocal8Bit ());
-	}
-
-	QString rkward_frontend_exe = findRKWardAtPath (app.applicationDirPath ());	// this is for running directly from a build tree
-#ifdef Q_OS_MAC
-	if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (app.applicationDirPath () + "/rkward.frontend.app/Contents/MacOS"); 	// this is for running directly from a build tree
-#endif
-	if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (RKWARD_FRONTEND_LOCATION);
-	if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath ("bin"));
-	if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath ("../lib/libexec"));
-	for (int i = 0; i < syspath.size (); ++i) {
-		if (!rkward_frontend_exe.isNull ()) break;
-		rkward_frontend_exe = findRKWardAtPath (syspath[i]);
-	}
-
-	if (rkward_frontend_exe.isNull ()) {
-		QMessageBox::critical (0, "RKWard frontend binary missing", "RKWard frontend binary could not be found. When moving / copying RKWard, make sure to copy the whole application folder, or create a shorcut / link, instead.");
-		exit (1);
-	}
-
-	if (usage) {
-		QProcess::execute (rkward_frontend_exe, QStringList ("--help"));
-		exit (1);
-	}
-
-#ifdef Q_OS_WIN
-	// Explicit initialization of KDE, in case Windows 7 asks for admin privileges
-	// KF5 TODO: _is_ there a kdeinit5.exe on Windows? Do we have to add some dependency? Do we still need this?
-	QString kdeinit5_exe = findExeAtPath ("kdeinit5", kde_dir.path ());
-	if (kdeinit5_exe.isNull ()) {
-		kdeinit5_exe = findExeAtPath ("kdeinit5", QFileInfo (rkward_frontend_exe).absolutePath ());
-	}
-	if (!kdeinit5_exe.isNull ()) QProcess::execute (kdeinit5_exe, QStringList ());
-#endif
-
-	// Look for R:
-	//- command line parameter
-	//- Specified in cfg file next to rkward executable
-	//- compile-time default
-	QString r_exe = r_exe_arg;
-	if (!r_exe.isNull ()) {
-		if (!QFileInfo (r_exe).isExecutable ()) {
-			QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe));
-			exit (1);
-		}
-		if (debug_level > 3) qDebug ("Using R specified on command line");
-	} else {
-		QFileInfo frontend_info (rkward_frontend_exe);
-		QDir frontend_path = frontend_info.absoluteDir ();
-		QFileInfo rkward_ini_file (frontend_path.absoluteFilePath ("rkward.ini"));
-		if (rkward_ini_file.isReadable ()) {
-			QSettings rkward_ini (rkward_ini_file.absoluteFilePath (), QSettings::IniFormat);
-			r_exe = rkward_ini.value ("R executable").toString ();
-			if (!r_exe.isNull ()) {
-				if (QDir::isRelativePath (r_exe)) {
-					r_exe = frontend_path.absoluteFilePath (r_exe);
-				}
-				if (!QFileInfo (r_exe).isExecutable ()) {
-					QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified in the rkward.ini file (%1) does not exist or is not executable.").arg (rkward_ini_file.absoluteFilePath ()));
-					exit (1);
-				}
-			}
-			if (debug_level > 3) qDebug ("Using R as configured in config file %s", qPrintable (rkward_ini_file.absoluteFilePath ()));
-		}
-		if (r_exe.isNull ()) {
-			r_exe = R_EXECUTABLE;
-			if (!QFileInfo (r_exe).isExecutable ()) {
-				QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified at compile time (%1) does not exist or is not executable. Probably the installation of R has moved. You can use the command line parameter '--r-executable <i>PATH_TO_R</i>', or supply an rkward.ini file to specify the new location.").arg (r_exe));
-				exit (1);
-			}
-			if (debug_level > 3) qDebug ("Using R as configured at compile time");
-		}
-	}
-
-	// TODO: This is a _temporary_ hack!
-#warning Remove me!
-	qputenv ("KDIRWATCH_METHOD", QByteArray ("Stat"));
-
-	qputenv ("R_BINARY", r_exe.toLocal8Bit ());
-	QStringList call_args ("CMD");
-	call_args.append (debugger_args);
-	call_args.append (quoteCommand (rkward_frontend_exe));
-
-	if (!args.isEmpty ()) {
-		// NOTE: QProcess quotes its arguments, *but* properly passing all spaces and quotes through the R CMD wrapper, seems near(?) impossible on Windows. Instead, we use percent encoding, internally.
-		for (int i = 0; i < args.size (); ++i) {
-			call_args.append (QString::fromUtf8 (QUrl::toPercentEncoding (args[i], QByteArray (), " \"")));
-		}
-	}
-
-	if (debug_level > 2) qDebug ("Starting frontend: %s %s", qPrintable (r_exe), qPrintable (call_args.join (" ")));
-
-	InteractiveProcess proc;
-#ifdef Q_OS_WIN
-	if (debugger_args.isEmpty ()) {
-		// start _without_ opening an annoying console window
-		QTemporaryFile *vbsf = new QTemporaryFile (QDir::tempPath () + "/rkwardlaunchXXXXXX.vbs");
-		vbsf->setAutoRemove (false);
-		if (vbsf->open ()) {
-			QTextStream vbs (vbsf);
-			vbs << "Dim WinScriptHost\r\nSet WinScriptHost = CreateObject(\"WScript.Shell\")\r\nWinScriptHost.Run \"" << quoteCommand (r_exe);
-			for (int i = 0;  i < call_args.length (); ++i) {
-				vbs << " " << call_args[i];
-			}
-			vbs << "\", 0\r\nSet WinScriptHost = Nothing\r\n";
-			vbsf->close ();
-			QString filename = vbsf->fileName ();
-			delete (vbsf);  // somehow, if creating vbsf on the stack, we cannot launch it, because "file is in use by another process", despite we have closed it.
-			proc.start ("WScript.exe", QStringList (filename));
-			bool ok = proc.waitForFinished (-1);
-			if (proc.exitCode () || !ok) {
-				QMessageBox::critical (0, "Error starting RKWard", QString ("Starting RKWard failed with error \"%1\"").arg (proc.errorString ()));
-			}
-			QFile (filename).remove ();
-			return (0);
-		}
-	}
-	// if that did not work or not on windows:
-#endif
-	proc.setProcessChannelMode (QProcess::ForwardedChannels);
-	proc.start (quoteCommand (r_exe), call_args);
-	bool ok = proc.waitForFinished (-1);
-	if (proc.exitCode () || !ok) {
-		QMessageBox::critical (0, "Error starting RKWard", QString ("Starting RKWard failed with error \"%1\"").arg (proc.errorString ()));
-	}
-
-	return (0);
-}



More information about the rkward-tracker mailing list