1 /*
2  * main: Common functions, shared data and main entry point of application
3  *
4  * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  *
21  *
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include <glib/gi18n-lib.h>
29 #include <gtk/gtk.h>
30 #include <clutter/clutter.h>
31 #ifdef CLUTTER_WINDOWING_X11
32 #include <clutter/x11/clutter-x11.h>
33 #endif
34 #include <libxfce4util/libxfce4util.h>
35 #include <libxfdashboard/application.h>
36 #include <libxfdashboard/window-tracker-backend.h>
37 
38 
39 typedef struct _RestartData		RestartData;
40 struct _RestartData
41 {
42 	GMainLoop				*mainLoop;
43 	XfdashboardApplication	*application;
44 	gboolean				appHasQuitted;
45 };
46 
47 #define DEFAULT_RESTART_WAIT_TIMEOUT	5000	/* Timeout in milliseconds */
48 
49 
50 /* Timeout to wait for application to disappear has been reached */
_on_quit_timeout(gpointer inUserData)51 static gboolean _on_quit_timeout(gpointer inUserData)
52 {
53 	RestartData			*restartData;
54 
55 	g_return_val_if_fail(inUserData, G_SOURCE_REMOVE);
56 
57 	restartData=(RestartData*)inUserData;
58 
59 	/* Enforce flag that application has been disappeared to be unset */
60 	restartData->appHasQuitted=FALSE;
61 
62 	 /* Return from main loop */
63 	g_main_loop_quit(restartData->mainLoop);
64 	g_debug("Timeout for waiting the application has been reached!");
65 
66 	/* Remove this source from main loop and prevent getting it called again */
67 	return(G_SOURCE_REMOVE);
68 }
69 
70 /* Idle callback for quitting application has been called */
_on_quit_idle(gpointer inUserData)71 static gboolean _on_quit_idle(gpointer inUserData)
72 {
73 	/* Quit application */
74 	xfdashboard_application_quit_forced(NULL);
75 
76 	/* Remove this source from main loop and prevent getting it called again */
77 	return(G_SOURCE_REMOVE);
78 }
79 
80 /* Requested name appeared at DBUS */
_on_dbus_watcher_name_appeared(GDBusConnection * inConnection,const gchar * inName,const gchar * inOwner,gpointer inUserData)81 static void _on_dbus_watcher_name_appeared(GDBusConnection *inConnection,
82 											const gchar *inName,
83 											const gchar *inOwner,
84 											gpointer inUserData)
85 {
86 	RestartData			*restartData;
87 	GSource				*quitIdleSource;
88 
89 	g_return_if_fail(inUserData);
90 
91 	restartData=(RestartData*)inUserData;
92 
93 	/* Add an idle source to main loop to quit running application _after_
94 	 * the main loop is running and the DBUS watcher was set up.
95 	 */
96 	quitIdleSource=g_idle_source_new();
97 	g_source_set_callback(quitIdleSource, _on_quit_idle, NULL, NULL);
98 	g_source_attach(quitIdleSource, g_main_loop_get_context(restartData->mainLoop));
99 	g_source_unref(quitIdleSource);
100 
101 	g_debug("Name %s appeared at DBUS and is owned by %s", inName, inOwner);
102 }
103 
104 /* Requested name vanished from DBUS */
_on_dbus_watcher_name_vanished(GDBusConnection * inConnection,const gchar * inName,gpointer inUserData)105 static void _on_dbus_watcher_name_vanished(GDBusConnection *inConnection,
106 											const gchar *inName,
107 											gpointer inUserData)
108 {
109 	RestartData			*restartData;
110 
111 	g_return_if_fail(inUserData);
112 
113 	restartData=(RestartData*)inUserData;
114 
115 	/* Set flag that application disappeared */
116 	restartData->appHasQuitted=TRUE;
117 
118 	/* Now the application is gone and we can safely restart application.
119 	 * So return from main loop now.
120 	 */
121 	g_main_loop_quit(restartData->mainLoop);
122 
123 	g_debug("Name %s does not exist at DBUS or vanished", inName);
124 }
125 
126 /* Quit running application and restart application */
_restart(XfdashboardApplication * inApplication)127 static gboolean _restart(XfdashboardApplication *inApplication)
128 {
129 	GMainLoop			*mainLoop;
130 	guint				dbusWatcherID;
131 	GSource				*timeoutSource;
132 	RestartData			restartData;
133 
134 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION(inApplication), FALSE);
135 
136 	/* Create main loop for watching DBUS for application to disappear */
137 	mainLoop=g_main_loop_new(NULL, FALSE);
138 
139 	/* Set up user data for callbacks */
140 	restartData.mainLoop=mainLoop;
141 	restartData.application=inApplication;
142 	restartData.appHasQuitted=FALSE;
143 
144 	/* Set up DBUS watcher to get noticed when application disappears
145 	 * which means it is safe to start a new instance. But if it takes
146 	 * too long assume that either application did not quit and is still
147 	 * running or we did get notified although we set up DBUS watcher
148 	 * before quitting application.
149 	 */
150 	dbusWatcherID=g_bus_watch_name(G_BUS_TYPE_SESSION,
151 									g_application_get_application_id(G_APPLICATION(inApplication)),
152 									G_BUS_NAME_WATCHER_FLAGS_NONE,
153 									_on_dbus_watcher_name_appeared,
154 									_on_dbus_watcher_name_vanished,
155 									&restartData,
156 									NULL);
157 
158 	/* Add an idle source to main loop to quit running application _after_
159 	 * the main loop is running and the DBUS watcher was set up.
160 	 */
161 	timeoutSource=g_timeout_source_new(DEFAULT_RESTART_WAIT_TIMEOUT);
162 	g_source_set_callback(timeoutSource, _on_quit_timeout, &restartData, NULL);
163 	g_source_attach(timeoutSource, g_main_loop_get_context(mainLoop));
164 	g_source_unref(timeoutSource);
165 
166 	/* Run main loop */
167 	g_debug("Starting main loop for waiting the application to quit");
168 	g_main_loop_run(mainLoop);
169 	g_debug("Returned from main loop for waiting the application to quit");
170 
171 	/* Show warning if timeout had been reached */
172 	if(!restartData.appHasQuitted)
173 	{
174 		g_warning("Cannot restart application: Failed to quit running instance");
175 	}
176 
177 	/* Destroy DBUS watcher */
178 	g_bus_unwatch_name(dbusWatcherID);
179 
180 	/* Return TRUE if application was quitted successfully
181 	 * otherwise FALSE.
182 	 */
183 	return(restartData.appHasQuitted);
184 }
185 
186 /* Main entry point */
main(int argc,char ** argv)187 int main(int argc, char **argv)
188 {
189 	XfdashboardApplication		*app=NULL;
190 	gint						status;
191 #if CLUTTER_CHECK_VERSION(1, 16, 0)
192 	const gchar					*backend;
193 #endif
194 
195 #ifdef ENABLE_NLS
196 	/* Set up localization */
197 	xfce_textdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");
198 #endif
199 
200 #if !GLIB_CHECK_VERSION(2, 36, 0)
201 	/* Initialize GObject type system */
202 	g_type_init();
203 #endif
204 
205 #if CLUTTER_CHECK_VERSION(1, 16, 0)
206 	/* Enforce X11 backend in Clutter if no specific backend was requested via
207 	 * the XFDASHBOARD_BACKEND environment variable. If this environment variable
208 	 * is set, enforce this backend.
209 	 *
210 	 * This function must be called before any API function call at libxfdashboard
211 	 * or other library API function like the one of Clutter, GTK+ etc.
212 	 */
213 	backend=g_getenv("XFDASHBOARD_BACKEND");
214 	if(backend)
215 	{
216 		xfdashboard_window_tracker_backend_set_backend(backend);
217 		g_debug("Setting backend to '%s'", backend);
218 	}
219 		else
220 		{
221 			clutter_set_windowing_backend("x11");
222 			g_debug("Enforcing X11 backend");
223 		}
224 #endif
225 
226 #ifdef CLUTTER_WINDOWING_X11
227 	/* Tell clutter to try to initialize an RGBA visual if the X11 backend is
228 	 * used before fallback to default and use RGB visual without alpha channel.
229 	 */
230 	clutter_x11_set_use_argb_visual(TRUE);
231 #endif
232 
233 	/* Initialize GTK+ and Clutter */
234 	gtk_init(&argc, &argv);
235 	if(!clutter_init(&argc, &argv))
236 	{
237 		g_error("Initializing clutter failed!");
238 		return(1);
239 	}
240 
241 	/* Notify that application has started and main loop will be entered */
242 	gdk_notify_startup_complete();
243 
244 	/* Start application as primary or remote instace */
245 	app=xfdashboard_application_get_default();
246 	if(!app)
247 	{
248 		g_warning("Failed to create application instance");
249 		return(XFDASHBOARD_APPLICATION_ERROR_FAILED);
250 	}
251 
252 	status=g_application_run(G_APPLICATION(app), argc, argv);
253 	if(status==XFDASHBOARD_APPLICATION_ERROR_RESTART &&
254 		g_application_get_is_remote(G_APPLICATION(app)))
255 	{
256 		/* Wait for existing primary instance to quit */
257 		if(_restart(app))
258 		{
259 			g_debug("Reached clean state to restart application");
260 
261 			/* Destroy remote instance application object for restart */
262 			g_object_unref(app);
263 			app=NULL;
264 
265 			/* Create new application instance which should become
266 			 * the new primary instance.
267 			 */
268 			app=xfdashboard_application_get_default();
269 			if(!app)
270 			{
271 				g_warning("Failed to create application instance");
272 				return(XFDASHBOARD_APPLICATION_ERROR_FAILED);
273 			}
274 
275 			g_debug("Starting new primary instance");
276 			status=g_application_run(G_APPLICATION(app), argc, argv);
277 		}
278 			else
279 			{
280 				g_warning("Could not restart application because existing instance seems still to be running.");
281 			}
282 	}
283 
284 	/* Clean up, release allocated resources */
285 	g_object_unref(app);
286 
287 	return(status);
288 }
289