1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=8:
4 /* ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is mozilla.org code.
19 * The Initial Developer of the Original Code is
20 * Christopher Blizzard.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
25 * Christopher Blizzard <blizzard@mozilla.org>
26 * Benjamin Smedberg <benjamin@smedbergs.us>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include "nsGTKRemoteService.h"
44 #include <X11/Xatom.h> // for XA_STRING
46 #include <gtk/gtkinvisible.h> // For some reason GTK+ doesn't include this file
47 // automatically from gtk.h
51 #include "nsIBaseWindow.h"
52 #include "nsIDocShell.h"
53 #include "nsPIDOMWindow.h"
54 #include "nsIGenericFactory.h"
55 #include "nsILocalFile.h"
56 #include "nsIObserverService.h"
57 #include "nsIServiceManager.h"
58 #include "nsIWeakReference.h"
59 #include "nsIWidget.h"
60 #include "nsIAppShellService.h"
61 #include "nsAppShellCID.h"
69 #ifdef MOZ_WIDGET_GTK2
70 #include "nsGTKToolkit.h"
73 #include "nsICommandLineRunner.h"
74 #include "nsXULAppAPI.h"
76 #define MOZILLA_VERSION_PROP "_MOZILLA_VERSION"
77 #define MOZILLA_LOCK_PROP "_MOZILLA_LOCK"
78 #define MOZILLA_COMMAND_PROP "_MOZILLA_COMMAND"
79 #define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE"
80 #define MOZILLA_USER_PROP "_MOZILLA_USER"
81 #define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE"
82 #define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM"
83 #define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE"
86 #define TO_LITTLE_ENDIAN32(x) \
87 ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
88 (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
90 #define TO_LITTLE_ENDIAN32(x) (x)
93 const unsigned char kRemoteVersion
[] = "5.1";
95 NS_IMPL_ISUPPORTS2(nsGTKRemoteService
,
100 nsGTKRemoteService::Startup(const char* aAppName
, const char* aProfileName
)
102 NS_ASSERTION(aAppName
, "Don't pass a null appname!");
105 if (mServerWindow
) return NS_ERROR_ALREADY_INITIALIZED
;
108 ToLowerCase(mAppName
);
110 mProfileName
= aProfileName
;
112 mServerWindow
= gtk_invisible_new();
113 gtk_widget_realize(mServerWindow
);
114 HandleCommandsFor(mServerWindow
, nsnull
);
116 if (!mWindows
.IsInitialized())
119 mWindows
.EnumerateRead(StartupHandler
, this);
121 nsCOMPtr
<nsIObserverService
> obs
122 (do_GetService("@mozilla.org/observer-service;1"));
124 obs
->AddObserver(this, "xpcom-shutdown", PR_FALSE
);
125 obs
->AddObserver(this, "quit-application", PR_FALSE
);
132 nsGTKRemoteService::StartupHandler(const void* aKey
,
133 nsIWeakReference
* aData
,
136 GtkWidget
* widget
= (GtkWidget
*) aKey
;
137 nsGTKRemoteService
* aThis
= (nsGTKRemoteService
*) aClosure
;
139 aThis
->HandleCommandsFor(widget
, aData
);
140 return PL_DHASH_NEXT
;
143 static nsIWidget
* GetMainWidget(nsIDOMWindow
* aWindow
)
145 // get the native window for this instance
146 nsCOMPtr
<nsPIDOMWindow
> window(do_QueryInterface(aWindow
));
147 NS_ENSURE_TRUE(window
, nsnull
);
149 nsCOMPtr
<nsIBaseWindow
> baseWindow
150 (do_QueryInterface(window
->GetDocShell()));
151 NS_ENSURE_TRUE(baseWindow
, nsnull
);
153 nsCOMPtr
<nsIWidget
> mainWidget
;
154 baseWindow
->GetMainWidget(getter_AddRefs(mainWidget
));
158 #ifdef MOZ_WIDGET_GTK2
159 static nsGTKToolkit
* GetGTKToolkit()
161 nsCOMPtr
<nsIAppShellService
> svc
= do_GetService(NS_APPSHELLSERVICE_CONTRACTID
);
164 nsCOMPtr
<nsIDOMWindowInternal
> window
;
165 svc
->GetHiddenDOMWindow(getter_AddRefs(window
));
168 nsIWidget
* widget
= GetMainWidget(window
);
171 nsIToolkit
* toolkit
= widget
->GetToolkit();
174 return static_cast<nsGTKToolkit
*>(toolkit
);
179 nsGTKRemoteService::RegisterWindow(nsIDOMWindow
* aWindow
)
181 nsIWidget
* mainWidget
= GetMainWidget(aWindow
);
182 NS_ENSURE_TRUE(mainWidget
, NS_ERROR_FAILURE
);
184 // walk up the widget tree and find the toplevel window in the
187 nsIWidget
* tempWidget
= mainWidget
->GetParent();
190 tempWidget
= tempWidget
->GetParent();
192 mainWidget
= tempWidget
;
196 (GtkWidget
*) mainWidget
->GetNativeData(NS_NATIVE_SHELLWIDGET
);
197 NS_ENSURE_TRUE(widget
, NS_ERROR_FAILURE
);
199 nsCOMPtr
<nsIWeakReference
> weak
= do_GetWeakReference(aWindow
);
200 NS_ENSURE_TRUE(weak
, NS_ERROR_FAILURE
);
202 if (!mWindows
.IsInitialized())
205 mWindows
.Put(widget
, weak
);
207 // If Startup() has already been called, immediately register this window.
209 HandleCommandsFor(widget
, weak
);
216 nsGTKRemoteService::Shutdown()
219 return NS_ERROR_NOT_INITIALIZED
;
221 gtk_widget_destroy(mServerWindow
);
222 mServerWindow
= nsnull
;
227 nsGTKRemoteService::Observe(nsISupports
* aSubject
,
229 const PRUnichar
*aData
)
231 // This can be xpcom-shutdown or quit-application, but it's the same either
237 // Minimize the roundtrips to the X server by getting all the atoms at once
238 static char *XAtomNames
[] = {
239 MOZILLA_VERSION_PROP
,
241 MOZILLA_COMMAND_PROP
,
242 MOZILLA_RESPONSE_PROP
,
244 MOZILLA_PROFILE_PROP
,
245 MOZILLA_PROGRAM_PROP
,
246 MOZILLA_COMMANDLINE_PROP
248 static Atom XAtoms
[NS_ARRAY_LENGTH(XAtomNames
)];
251 nsGTKRemoteService::EnsureAtoms(void)
256 XInternAtoms(GDK_DISPLAY(), XAtomNames
, NS_ARRAY_LENGTH(XAtomNames
),
259 sMozVersionAtom
= XAtoms
[i
++];
260 sMozLockAtom
= XAtoms
[i
++];
261 sMozCommandAtom
= XAtoms
[i
++];
262 sMozResponseAtom
= XAtoms
[i
++];
263 sMozUserAtom
= XAtoms
[i
++];
264 sMozProfileAtom
= XAtoms
[i
++];
265 sMozProgramAtom
= XAtoms
[i
++];
266 sMozCommandLineAtom
= XAtoms
[i
++];
269 // Set desktop startup ID to the passed ID, if there is one, so that any created
270 // windows get created with the right window manager metadata, and any windows
271 // that get new tabs and are activated also get the right WM metadata.
272 // If there is no desktop startup ID, then use the X event's timestamp
273 // for _NET_ACTIVE_WINDOW when the window gets focused or shown.
275 SetDesktopStartupIDOrTimestamp(const nsACString
& aDesktopStartupID
,
276 PRUint32 aTimestamp
) {
277 #ifdef MOZ_WIDGET_GTK2
278 nsGTKToolkit
* toolkit
= GetGTKToolkit();
281 if (!aDesktopStartupID
.IsEmpty()) {
282 toolkit
->SetDesktopStartupID(aDesktopStartupID
);
284 toolkit
->SetFocusTimestamp(aTimestamp
);
290 FindExtensionParameterInCommand(const char* aParameterName
,
291 const nsACString
& aCommand
,
295 nsCAutoString searchFor
;
296 searchFor
.Append(aSeparator
);
297 searchFor
.Append(aParameterName
);
298 searchFor
.Append('=');
300 nsACString::const_iterator start
, end
;
301 aCommand
.BeginReading(start
);
302 aCommand
.EndReading(end
);
303 if (!FindInReadable(searchFor
, start
, end
))
306 nsACString::const_iterator charStart
, charEnd
;
308 aCommand
.EndReading(charEnd
);
309 nsACString::const_iterator idStart
= charStart
, idEnd
;
310 if (FindCharInReadable(aSeparator
, charStart
, charEnd
)) {
315 *aValue
= nsDependentCSubstring(idStart
, idEnd
);
320 nsGTKRemoteService::HandleCommand(char* aCommand
, nsIDOMWindow
* aWindow
,
325 nsCOMPtr
<nsICommandLineRunner
> cmdline
326 (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv
));
328 return "509 internal error";
330 // 1) Make sure that it looks remotely valid with parens
331 // 2) Treat ping() immediately and specially
333 nsCAutoString
command(aCommand
);
335 p1
= command
.FindChar('(');
336 p2
= command
.FindChar(')');
338 if (p1
== kNotFound
|| p2
== kNotFound
|| p1
== 0 || p2
< p1
) {
339 return "500 command not parseable";
342 command
.Truncate(p1
);
343 command
.Trim(" ", PR_TRUE
, PR_TRUE
);
344 ToLowerCase(command
);
346 #ifdef DEBUG_bsmedberg
347 printf("Processing xremote command: %s\n", command
.get());
350 if (!command
.EqualsLiteral("ping")) {
351 nsCAutoString desktopStartupID
;
352 nsDependentCString
cmd(aCommand
);
353 FindExtensionParameterInCommand("DESKTOP_STARTUP_ID",
357 char* argv
[3] = {"dummyappname", "-remote", aCommand
};
358 rv
= cmdline
->Init(3, argv
, nsnull
, nsICommandLine::STATE_REMOTE_EXPLICIT
);
360 return "509 internal error";
363 cmdline
->SetWindowContext(aWindow
);
365 SetDesktopStartupIDOrTimestamp(desktopStartupID
, aTimestamp
);
368 if (NS_ERROR_ABORT
== rv
)
369 return "500 command not parseable";
371 return "509 internal error";
374 return "200 executed command";
378 nsGTKRemoteService::HandleCommandLine(char* aBuffer
, nsIDOMWindow
* aWindow
,
383 nsCOMPtr
<nsICommandLineRunner
> cmdline
384 (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv
));
386 return "509 internal error";
388 // the commandline property is constructed as an array of PRInt32
389 // followed by a series of null-terminated strings:
391 // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
392 // (offset is from the beginning of the buffer)
394 PRInt32 argc
= TO_LITTLE_ENDIAN32(*reinterpret_cast<PRInt32
*>(aBuffer
));
395 char *wd
= aBuffer
+ ((argc
+ 1) * sizeof(PRInt32
));
397 #ifdef DEBUG_bsmedberg
398 printf("Receiving command line:\n"
404 nsCOMPtr
<nsILocalFile
> lf
;
405 rv
= NS_NewNativeLocalFile(nsDependentCString(wd
), PR_TRUE
,
408 return "509 internal error";
410 nsCAutoString desktopStartupID
;
412 char **argv
= (char**) malloc(sizeof(char*) * argc
);
413 if (!argv
) return "509 internal error";
415 PRInt32
*offset
= reinterpret_cast<PRInt32
*>(aBuffer
) + 1;
417 for (int i
= 0; i
< argc
; ++i
) {
418 argv
[i
] = aBuffer
+ TO_LITTLE_ENDIAN32(offset
[i
]);
421 nsDependentCString
cmd(argv
[0]);
422 FindExtensionParameterInCommand("DESKTOP_STARTUP_ID",
426 #ifdef DEBUG_bsmedberg
427 printf(" argv[%i]:\t%s\n", i
, argv
[i
]);
431 rv
= cmdline
->Init(argc
, argv
, lf
, nsICommandLine::STATE_REMOTE_AUTO
);
434 return "509 internal error";
438 cmdline
->SetWindowContext(aWindow
);
440 SetDesktopStartupIDOrTimestamp(desktopStartupID
, aTimestamp
);
444 if (NS_ERROR_ABORT
== rv
)
445 return "500 command not parseable";
448 return "509 internal error";
450 return "200 executed command";
454 nsGTKRemoteService::HandleCommandsFor(GtkWidget
* widget
,
455 nsIWeakReference
* aWindow
)
457 #ifdef MOZ_WIDGET_GTK2
458 g_signal_connect(G_OBJECT(widget
), "property_notify_event",
459 G_CALLBACK(HandlePropertyChange
), aWindow
);
461 gtk_signal_connect(GTK_OBJECT(widget
), "property_notify_event",
462 GTK_SIGNAL_FUNC(HandlePropertyChange
), aWindow
);
465 gtk_widget_add_events(widget
, GDK_PROPERTY_CHANGE_MASK
);
467 Window window
= GDK_WINDOW_XWINDOW(widget
->window
);
470 XChangeProperty(GDK_DISPLAY(), window
, sMozVersionAtom
, XA_STRING
,
471 8, PropModeReplace
, kRemoteVersion
, sizeof(kRemoteVersion
) - 1);
474 unsigned char *logname
;
475 logname
= (unsigned char*) PR_GetEnv("LOGNAME");
477 // set the property on the window if it's available
478 XChangeProperty(GDK_DISPLAY(), window
, sMozUserAtom
, XA_STRING
,
479 8, PropModeReplace
, logname
, strlen((char*) logname
));
482 XChangeProperty(GDK_DISPLAY(), window
, sMozProgramAtom
, XA_STRING
,
483 8, PropModeReplace
, (unsigned char*) mAppName
.get(), mAppName
.Length());
485 if (!mProfileName
.IsEmpty()) {
486 XChangeProperty(GDK_DISPLAY(), window
, sMozProfileAtom
, XA_STRING
,
487 8, PropModeReplace
, (unsigned char*) mProfileName
.get(), mProfileName
.Length());
491 #ifdef MOZ_WIDGET_GTK2
492 #define CMP_GATOM_XATOM(gatom,xatom) (gatom == gdk_x11_xatom_to_atom(xatom))
494 #define CMP_GATOM_XATOM(gatom,xatom) (gatom == xatom)
498 nsGTKRemoteService::HandlePropertyChange(GtkWidget
*aWidget
,
499 GdkEventProperty
*pevent
,
500 nsIWeakReference
* aThis
)
502 nsCOMPtr
<nsIDOMWindow
> window (do_QueryReferent(aThis
));
504 if (pevent
->state
== GDK_PROPERTY_NEW_VALUE
&&
505 CMP_GATOM_XATOM(pevent
->atom
, sMozCommandAtom
)) {
507 // We got a new command atom.
511 unsigned long nitems
, bytes_after
;
514 result
= XGetWindowProperty (GDK_DISPLAY(),
515 GDK_WINDOW_XWINDOW(pevent
->window
),
518 (65536 / sizeof (long)), /* long_length */
519 True
, /* atomic delete after */
520 XA_STRING
, /* req_type */
521 &actual_type
, /* actual_type return */
522 &actual_format
, /* actual_format_return */
523 &nitems
, /* nitems_return */
524 &bytes_after
, /* bytes_after_return */
525 (unsigned char **)&data
); /* prop_return
529 #ifdef DEBUG_bsmedberg
530 printf("Handling command: %s\n", data
);
533 // Failed to get property off the window?
534 if (result
!= Success
)
537 // Failed to get the data off the window or it was the wrong type?
538 if (!data
|| !TO_LITTLE_ENDIAN32(*reinterpret_cast<PRInt32
*>(data
)))
541 // cool, we got the property data.
542 const char *response
= HandleCommand(data
, window
, pevent
->time
);
544 // put the property onto the window as the response
545 XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent
->window
),
546 sMozResponseAtom
, XA_STRING
,
547 8, PropModeReplace
, (const unsigned char *)response
, strlen (response
));
552 if (pevent
->state
== GDK_PROPERTY_NEW_VALUE
&&
553 CMP_GATOM_XATOM(pevent
->atom
, sMozCommandLineAtom
)) {
555 // We got a new commandline atom.
559 unsigned long nitems
, bytes_after
;
562 result
= XGetWindowProperty (GDK_DISPLAY(),
563 GDK_WINDOW_XWINDOW(pevent
->window
),
566 (65536 / sizeof (long)), /* long_length */
567 True
, /* atomic delete after */
568 XA_STRING
, /* req_type */
569 &actual_type
, /* actual_type return */
570 &actual_format
, /* actual_format_return */
571 &nitems
, /* nitems_return */
572 &bytes_after
, /* bytes_after_return */
573 (unsigned char **)&data
); /* prop_return
577 // Failed to get property off the window?
578 if (result
!= Success
)
581 // Failed to get the data off the window or it was the wrong type?
582 if (!data
|| !TO_LITTLE_ENDIAN32(*reinterpret_cast<PRInt32
*>(data
)))
585 // cool, we got the property data.
586 const char *response
= HandleCommandLine(data
, window
, pevent
->time
);
588 // put the property onto the window as the response
589 XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent
->window
),
590 sMozResponseAtom
, XA_STRING
,
591 8, PropModeReplace
, (const unsigned char *)response
, strlen (response
));
596 if (pevent
->state
== GDK_PROPERTY_NEW_VALUE
&&
597 CMP_GATOM_XATOM(pevent
->atom
, sMozResponseAtom
)) {
598 // client accepted the response. party on wayne.
602 if (pevent
->state
== GDK_PROPERTY_NEW_VALUE
&&
603 CMP_GATOM_XATOM(pevent
->atom
, sMozLockAtom
)) {
604 // someone locked the window
611 Atom
nsGTKRemoteService::sMozVersionAtom
;
612 Atom
nsGTKRemoteService::sMozLockAtom
;
613 Atom
nsGTKRemoteService::sMozCommandAtom
;
614 Atom
nsGTKRemoteService::sMozResponseAtom
;
615 Atom
nsGTKRemoteService::sMozUserAtom
;
616 Atom
nsGTKRemoteService::sMozProfileAtom
;
617 Atom
nsGTKRemoteService::sMozProgramAtom
;
618 Atom
nsGTKRemoteService::sMozCommandLineAtom
;
620 // {C0773E90-5799-4eff-AD03-3EBCD85624AC}
621 #define NS_REMOTESERVICE_CID \
622 { 0xc0773e90, 0x5799, 0x4eff, { 0xad, 0x3, 0x3e, 0xbc, 0xd8, 0x56, 0x24, 0xac } }
624 NS_GENERIC_FACTORY_CONSTRUCTOR(nsGTKRemoteService
)
626 static const nsModuleComponentInfo components
[] =
629 NS_REMOTESERVICE_CID
,
630 "@mozilla.org/toolkit/remote-service;1",
631 nsGTKRemoteServiceConstructor
635 NS_IMPL_NSGETMODULE(RemoteServiceModule
, components
)