Added
Link Here
|
1 |
#include <signal.h> |
2 |
#include <unistd.h> |
3 |
#include <gtk/gtk.h> |
4 |
#include <limits.h> |
5 |
#include <stdlib.h> |
6 |
#include <sys/types.h> |
7 |
#include <sys/stat.h> |
8 |
#include <sys/socket.h> |
9 |
#include <arpa/inet.h> |
10 |
#include <sys/un.h> |
11 |
#include <fcntl.h> |
12 |
#include <stdio.h> |
13 |
|
14 |
#define PIPEDEFAULTPATH "/tmp" |
15 |
#define PIPEALTERNATEPATH "/var/tmp" |
16 |
|
17 |
static char * |
18 |
read_path_and_chomp (const char *app) |
19 |
{ |
20 |
int len; |
21 |
char buffer[PATH_MAX]; |
22 |
|
23 |
/* Readlink on argv[0] to point to the install path */ |
24 |
if ((len = readlink (app, buffer, sizeof(buffer))) < 0) |
25 |
return NULL; |
26 |
buffer[MIN(len, 4095)] = '\0'; |
27 |
|
28 |
return g_path_get_dirname (buffer); |
29 |
} |
30 |
|
31 |
static char * |
32 |
get_app_path (const char *app_exec) |
33 |
{ |
34 |
if (app_exec[0] != '/') |
35 |
g_error ("%s must be called with an absolute path\n", app_exec); |
36 |
|
37 |
return g_path_get_dirname (app_exec); |
38 |
} |
39 |
|
40 |
/* OO.o likes to find real paths, unwinding symlinks etc. */ |
41 |
static char * |
42 |
get_real_app_path (const char *app_path) |
43 |
{ |
44 |
char *path, *bin_path; |
45 |
|
46 |
path = g_strdup (app_path); |
47 |
|
48 |
/* Hack for use with linkoo: follow soffice.bin |
49 |
if it is a symlink */ |
50 |
bin_path = g_strconcat (app_path, "/soffice.bin", NULL); |
51 |
if (g_file_test (bin_path, G_FILE_TEST_IS_SYMLINK)) |
52 |
{ |
53 |
g_free (path); |
54 |
path = read_path_and_chomp (bin_path); |
55 |
} |
56 |
g_free (bin_path); |
57 |
|
58 |
/* Nasty - but OO.o likes to call realpath too ... */ |
59 |
char *real_path = (char *)g_malloc (PATH_MAX + 1); |
60 |
realpath (path, real_path); |
61 |
real_path[PATH_MAX] = '\0'; |
62 |
g_free (path); |
63 |
|
64 |
return real_path; |
65 |
} |
66 |
|
67 |
static char * |
68 |
get_pipe_path (const char *app_path) |
69 |
{ |
70 |
char *pipe_path; |
71 |
char *real_app_path; |
72 |
char *terminated_path; |
73 |
const char *base_path; |
74 |
|
75 |
real_app_path = get_real_app_path (app_path); |
76 |
terminated_path = g_strconcat (real_app_path, "/", NULL); |
77 |
|
78 |
if (access(PIPEDEFAULTPATH, R_OK|W_OK) == 0) |
79 |
base_path = PIPEDEFAULTPATH; |
80 |
else |
81 |
base_path = PIPEALTERNATEPATH; |
82 |
|
83 |
pipe_path = g_strdup_printf ( |
84 |
"%s/OSL_PIPE_%u_SingleOfficeIPC_%d-%x", base_path, (int)getuid(), |
85 |
SUPD, g_str_hash (terminated_path)); |
86 |
|
87 |
/* fprintf (stderr, "pipe path is '%s' from '%s'\n", pipe_path, real_app_path); */ |
88 |
|
89 |
g_free (terminated_path); |
90 |
g_free (real_app_path); |
91 |
|
92 |
return pipe_path; |
93 |
} |
94 |
|
95 |
static int |
96 |
connect_pipe (const char *pipe_path) |
97 |
{ |
98 |
int fd; |
99 |
size_t len; |
100 |
struct sockaddr_un addr; |
101 |
|
102 |
memset(&addr, 0, sizeof(addr)); |
103 |
|
104 |
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) |
105 |
return fd; |
106 |
|
107 |
fcntl (fd, F_SETFD, FD_CLOEXEC); |
108 |
|
109 |
addr.sun_family = AF_UNIX; |
110 |
strncpy(addr.sun_path, pipe_path, sizeof(addr.sun_path)); |
111 |
|
112 |
/* cut / paste from osl's pipe.c */ |
113 |
#if defined(FREEBSD) |
114 |
len = SUN_LEN(&addr); |
115 |
#else |
116 |
len = sizeof(addr); |
117 |
#endif |
118 |
|
119 |
if (connect (fd, (struct sockaddr *)&addr, len) < 0) |
120 |
return -1; |
121 |
|
122 |
return fd; |
123 |
} |
124 |
|
125 |
static gboolean |
126 |
send_args (int fd, GPtrArray *args, const char *cwd) |
127 |
{ |
128 |
int i, len; |
129 |
GString *arg_str = g_string_new (""); |
130 |
|
131 |
for (i = 1; i < args->len; i++) |
132 |
{ |
133 |
g_string_append (arg_str, (char *)args->pdata[i]); |
134 |
g_string_append_c (arg_str, '|'); |
135 |
} |
136 |
|
137 |
#ifdef DEBUG |
138 |
fprintf (stderr, "Pass args: '%s'\n", arg_str->str); |
139 |
#endif |
140 |
len = arg_str->len + 1; |
141 |
|
142 |
return write (fd, arg_str->str, len) == len; |
143 |
} |
144 |
|
145 |
static void |
146 |
init_gtk (int argc, char **argv) |
147 |
{ |
148 |
int i; |
149 |
char *args[3]; |
150 |
char **argptr = args; |
151 |
|
152 |
args[0] = argv[0]; |
153 |
argc = 1; |
154 |
for (i = 0; i < argc - 1; i++) |
155 |
{ |
156 |
if (!strcmp (argv[i], "-display") || |
157 |
!strcmp (argv[i], "--display")) |
158 |
{ |
159 |
args[argc++] = g_strdup ("--display"); |
160 |
args[argc++] = argv[i+1]; |
161 |
} |
162 |
} |
163 |
gtk_init_check( &argc, &argptr ); |
164 |
} |
165 |
|
166 |
static void |
167 |
center_window (GtkWindow *window, void *dummy) |
168 |
{ |
169 |
GdkRectangle rect; |
170 |
|
171 |
gdk_screen_get_monitor_geometry (window->screen, 0, &rect); |
172 |
int sx = rect.width; |
173 |
int sy = rect.height; |
174 |
|
175 |
GdkGeometry geom; |
176 |
geom.base_width = GTK_WIDGET (window)->allocation.width; |
177 |
geom.base_height = GTK_WIDGET (window)->allocation.height; |
178 |
geom.min_width = geom.max_width = geom.base_width; |
179 |
geom.min_height = geom.max_height = geom.base_height; |
180 |
|
181 |
gtk_window_move (window, sx / 2 - geom.min_width / 2, |
182 |
sy / 2 - geom.min_height / 2); |
183 |
|
184 |
int hints = GDK_HINT_POS | GDK_HINT_USER_POS | |
185 |
GDK_HINT_BASE_SIZE | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | |
186 |
GDK_HINT_USER_SIZE; |
187 |
|
188 |
gtk_window_set_geometry_hints (window, NULL, &geom, (GdkWindowHints) hints); |
189 |
} |
190 |
|
191 |
typedef struct { |
192 |
gboolean finish; |
193 |
gboolean error; |
194 |
GtkProgressBar *progress_bar; |
195 |
} StatusClosure; |
196 |
|
197 |
static gboolean |
198 |
status_pipe_update (GIOChannel * source, |
199 |
GIOCondition condition, |
200 |
StatusClosure *cl) |
201 |
{ |
202 |
gboolean retval = TRUE; |
203 |
|
204 |
#ifdef DEBUG |
205 |
fprintf (stderr, "Status pipe update (0x%x)\n", (int) condition); |
206 |
#endif |
207 |
|
208 |
if ((int)condition & (G_IO_IN | G_IO_PRI)) |
209 |
{ |
210 |
int percent; |
211 |
GString *str = g_string_new (""); |
212 |
switch (g_io_channel_read_line_string (source, str, NULL, NULL)) { |
213 |
case G_IO_STATUS_ERROR: |
214 |
cl->error = TRUE; |
215 |
/* drop through */ |
216 |
case G_IO_STATUS_EOF: |
217 |
retval = FALSE; |
218 |
break; |
219 |
default: |
220 |
/* pipe lifecycle is odd - HUP only for remote process death */ |
221 |
if (!g_ascii_strncasecmp (str->str, "end", 3)) |
222 |
retval = FALSE; |
223 |
if (sscanf (str->str, "%d%%", &percent)) |
224 |
gtk_progress_bar_set_fraction (cl->progress_bar, |
225 |
(float) percent / 100.0); |
226 |
break; |
227 |
} |
228 |
g_string_free (str, TRUE); |
229 |
} |
230 |
|
231 |
if ((int)condition & (G_IO_HUP | G_IO_NVAL | G_IO_ERR)) |
232 |
{ |
233 |
cl->error = TRUE; |
234 |
retval = FALSE; |
235 |
} |
236 |
|
237 |
if (!retval) |
238 |
cl->finish = TRUE; |
239 |
|
240 |
return retval; |
241 |
} |
242 |
|
243 |
static GdkPixbuf * |
244 |
get_splash (const char *image_path) |
245 |
{ |
246 |
char *path; |
247 |
GdkPixbuf *pixbuf; |
248 |
|
249 |
path = g_strconcat (image_path, "/intro-nld.bmp", NULL); |
250 |
pixbuf = gdk_pixbuf_new_from_file (path, NULL); |
251 |
g_free (path); |
252 |
if (pixbuf) |
253 |
return pixbuf; |
254 |
|
255 |
path = g_strconcat (image_path, "/intro.bmp", NULL); |
256 |
pixbuf = gdk_pixbuf_new_from_file (path, NULL); |
257 |
g_free (path); |
258 |
|
259 |
return pixbuf; |
260 |
} |
261 |
|
262 |
static void |
263 |
show_splash (const char *image_path, GIOChannel *status_channel) |
264 |
{ |
265 |
GdkPixbuf *pixbuf; |
266 |
GtkWidget *contents; |
267 |
GtkWidget *vbox; |
268 |
StatusClosure cl; |
269 |
|
270 |
if ((pixbuf = get_splash (image_path))) |
271 |
contents = gtk_image_new_from_pixbuf (pixbuf); |
272 |
else |
273 |
contents = gtk_label_new ("No splash pixmap"); |
274 |
|
275 |
/* setup closure */ |
276 |
cl.finish = FALSE; |
277 |
cl.error = FALSE; |
278 |
cl.progress_bar = GTK_PROGRESS_BAR (gtk_progress_bar_new ()); |
279 |
|
280 |
/* pack contents & progres bar */ |
281 |
vbox = gtk_vbox_new (FALSE, 0); |
282 |
gtk_box_pack_start (GTK_BOX (vbox), contents, |
283 |
TRUE, FALSE, 0); |
284 |
gtk_progress_bar_set_orientation ( |
285 |
cl.progress_bar, GTK_PROGRESS_LEFT_TO_RIGHT); |
286 |
gtk_progress_bar_set_fraction (cl.progress_bar, 0.03); |
287 |
gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (cl.progress_bar), |
288 |
FALSE, FALSE, 0); |
289 |
|
290 |
/* setup window */ |
291 |
GtkWindow *window; |
292 |
window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); |
293 |
gtk_window_set_has_frame (window, FALSE); |
294 |
gtk_window_set_resizable (window, FALSE); |
295 |
gtk_window_set_decorated (window, FALSE); |
296 |
/* gtk_window_set_icon_name (window, "foo"); FIXME splash WM icon ? */ |
297 |
|
298 |
/* click to dismiss */ |
299 |
GtkWidget *event_box; |
300 |
event_box = gtk_event_box_new (); |
301 |
gtk_container_add (GTK_CONTAINER (event_box), vbox); |
302 |
g_signal_connect_swapped (G_OBJECT (event_box), "button_press_event", |
303 |
G_CALLBACK (gtk_widget_hide), (gpointer) window); |
304 |
|
305 |
gtk_container_add (GTK_CONTAINER (window), event_box); |
306 |
g_signal_connect (G_OBJECT (window), "realize", |
307 |
G_CALLBACK (center_window), NULL); |
308 |
gtk_widget_show_all (GTK_WIDGET (window)); |
309 |
|
310 |
/* setup status listener ... */ |
311 |
g_io_add_watch (status_channel, |
312 |
(GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_NVAL | G_IO_ERR), |
313 |
(GIOFunc) status_pipe_update, (gpointer) &cl); |
314 |
|
315 |
#ifdef DEBUG |
316 |
g_warning ("Start main loop...\n"); |
317 |
#endif |
318 |
while (!cl.finish) |
319 |
g_main_context_iteration (g_main_context_default (), TRUE); |
320 |
|
321 |
if (cl.error) |
322 |
g_warning ("Unknown error forking main binary / abnormal early exit ..."); |
323 |
|
324 |
g_io_channel_shutdown (status_channel, FALSE, NULL); |
325 |
g_io_channel_unref (status_channel); |
326 |
} |
327 |
|
328 |
typedef struct { |
329 |
int pipe_fd; |
330 |
int clone_fd; |
331 |
} ChildClosure; |
332 |
|
333 |
static void |
334 |
child_setup (gpointer user_data) |
335 |
{ |
336 |
ChildClosure *cl = (ChildClosure *)user_data; |
337 |
if (cl->pipe_fd >= 0) |
338 |
close (cl->pipe_fd); |
339 |
close (cl->clone_fd); |
340 |
} |
341 |
|
342 |
static gboolean |
343 |
fork_app (const char *app_path, GPtrArray *args, int pipe_fd, |
344 |
GIOChannel **status_channel) |
345 |
{ |
346 |
int status_pipe[2]; |
347 |
gboolean spawn_success; |
348 |
GError *error = NULL; |
349 |
char *splash_pipe_arg; |
350 |
ChildClosure *cl = g_new (ChildClosure, 1); |
351 |
GIOChannel *status; |
352 |
|
353 |
args->pdata[0] = (gpointer)g_strconcat (app_path, "/soffice", NULL); |
354 |
|
355 |
/* create pipe */ |
356 |
if (pipe (status_pipe) < 0) |
357 |
g_error ("no file handles\n"); |
358 |
|
359 |
status = g_io_channel_unix_new (status_pipe[0]); |
360 |
g_io_channel_set_encoding (status, NULL, NULL); |
361 |
|
362 |
splash_pipe_arg = g_strdup_printf ("-splash-pipe=%d", status_pipe[1]); |
363 |
#ifdef DEBUG |
364 |
fprintf ( stderr, "Pass splash pipe arg: '%s'\n", splash_pipe_arg); |
365 |
#endif |
366 |
g_ptr_array_add (args, splash_pipe_arg); |
367 |
|
368 |
#ifdef DEBUG |
369 |
fprintf (stderr, "Fork app '%s' in '%s'\n", (char *)args->pdata[0], app_path); |
370 |
#endif |
371 |
cl->pipe_fd = pipe_fd; |
372 |
cl->clone_fd = status_pipe[0]; |
373 |
spawn_success = g_spawn_async |
374 |
(app_path, (char **)args->pdata, NULL, |
375 |
G_SPAWN_LEAVE_DESCRIPTORS_OPEN, |
376 |
(GSpawnChildSetupFunc) child_setup, |
377 |
(gpointer) cl, |
378 |
NULL, /* child_pid */ |
379 |
&error); |
380 |
close (status_pipe[1]); |
381 |
|
382 |
if (!spawn_success) |
383 |
fprintf (stderr, "Error forking '%s/%s': '%s'\n", |
384 |
app_path, "/soffice", |
385 |
error ? error->message : "<no message>"); |
386 |
|
387 |
*status_channel = status; |
388 |
|
389 |
return spawn_success; |
390 |
} |
391 |
|
392 |
static gboolean |
393 |
arg_check (const char *arg, const char *cmp_with) |
394 |
{ |
395 |
if (arg[0] == '-') |
396 |
arg++; |
397 |
else |
398 |
return FALSE; |
399 |
if (arg[0] == '-') /* tolerate -- prefixes etc. */ |
400 |
arg++; |
401 |
return !g_ascii_strcasecmp (arg, cmp_with); |
402 |
} |
403 |
|
404 |
static GPtrArray * |
405 |
setup_args (int argc, char **argv, gboolean *inhibit_splash) |
406 |
{ |
407 |
int i; |
408 |
gboolean have_non_option = FALSE; |
409 |
GPtrArray *args = g_ptr_array_sized_new (argc + 8); |
410 |
|
411 |
*inhibit_splash = FALSE; |
412 |
|
413 |
for (i = 0; i < argc; i++) |
414 |
{ |
415 |
if (arg_check (argv[i], "widgets-set")) { |
416 |
char *widget_env; |
417 |
if (i >= argc - 1) |
418 |
continue; |
419 |
widget_env = g_strconcat ("SAL_USE_VCLPLUGIN=", argv[i+1], NULL); |
420 |
putenv (widget_env); /* deliberate leak */ |
421 |
i++; |
422 |
continue; |
423 |
} |
424 |
g_ptr_array_add (args, g_strdup (argv[i])); |
425 |
if (!i) |
426 |
continue; |
427 |
|
428 |
if (argv[i][0] != '-') |
429 |
have_non_option = TRUE; |
430 |
|
431 |
else if (arg_check (argv[i], "nologo") || |
432 |
arg_check (argv[i], "headless") || |
433 |
arg_check (argv[i], "invisible")) |
434 |
*inhibit_splash = TRUE; |
435 |
} |
436 |
|
437 |
/* It's necessary to append eg. -writer for writer. */ |
438 |
if (!have_non_option) |
439 |
{ |
440 |
const char *extra_arg = g_getenv ("OOO_EXTRA_ARG"); |
441 |
if (!extra_arg) |
442 |
g_error ("Missing default argument in OOO_EXTRA_ARG"); |
443 |
g_ptr_array_add (args, g_strdup (extra_arg)); |
444 |
} |
445 |
|
446 |
return args; |
447 |
} |
448 |
|
449 |
static void |
450 |
system_checks () |
451 |
{ |
452 |
#ifdef LINUX |
453 |
int fd; |
454 |
char *recently_used; |
455 |
|
456 |
/* FIXME: force right ownership of ~/.ooo-* as well |
457 |
* really all that work should be done by OO.o - not here. |
458 |
*/ |
459 |
|
460 |
/* force right ownership on ~/.recently-used despite umask */ |
461 |
recently_used = g_strconcat (g_get_home_dir (), "/.recently-used", NULL); |
462 |
fd = open (recently_used, O_RDWR|O_CREAT, S_IREAD|S_IWRITE); |
463 |
if (fd >= 0) |
464 |
close (fd); |
465 |
g_free (recently_used); |
466 |
|
467 |
/* check proc is mounted - lots of things fail otherwise */ |
468 |
if (!g_file_test ("/proc/version", G_FILE_TEST_EXISTS)) |
469 |
g_warning ("/proc not mounted - OO.o is unlikely to work well if at all"); |
470 |
#endif |
471 |
} |
472 |
|
473 |
int main (int argc, char **argv) |
474 |
{ |
475 |
int fd; |
476 |
gboolean inhibit_splash; |
477 |
GIOChannel *status_channel = NULL; |
478 |
gboolean sent_args = FALSE; |
479 |
char *app_path, *pipe_path; |
480 |
GPtrArray *args; |
481 |
char cwd_str[PATH_MAX]; |
482 |
|
483 |
/* turn SIGPIPE into an error */ |
484 |
signal (SIGPIPE, SIG_IGN); |
485 |
if (!getcwd (cwd_str, sizeof (cwd_str))) |
486 |
g_error ("can't get cwd"); |
487 |
cwd_str[sizeof(cwd_str)-1] = '\0'; |
488 |
|
489 |
args = setup_args (argc, argv, &inhibit_splash); |
490 |
|
491 |
app_path = get_app_path (argv[0]); |
492 |
if (!app_path) |
493 |
g_error ("Pathalogical failure: can't read app link\n"); |
494 |
|
495 |
pipe_path = get_pipe_path (app_path); |
496 |
|
497 |
if ((fd = connect_pipe (pipe_path)) >= 0) |
498 |
sent_args = send_args (fd, args, cwd_str); |
499 |
#ifdef DEBUG |
500 |
else |
501 |
fprintf (stderr, "Failed to connect to pipe '%s'\n", pipe_path); |
502 |
#endif |
503 |
|
504 |
if (!sent_args) |
505 |
{ |
506 |
if (! fork_app (app_path, args, fd, &status_channel)) |
507 |
return 1; |
508 |
} |
509 |
|
510 |
init_gtk (argc, argv); |
511 |
if (sent_args || inhibit_splash) |
512 |
{ |
513 |
gdk_notify_startup_complete (); |
514 |
return 0; |
515 |
} |
516 |
|
517 |
if (!inhibit_splash) |
518 |
show_splash (app_path, status_channel); |
519 |
|
520 |
g_ptr_array_foreach (args, (GFunc)g_free, NULL); |
521 |
g_ptr_array_free (args, TRUE); |
522 |
g_free (app_path); |
523 |
|
524 |
return 0; |
525 |
} |