1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/Types.h"
7 
8 #include <gtk/gtk.h>
9 
10 #include "nsApplicationChooser.h"
11 #include "WidgetUtils.h"
12 #include "nsIMIMEInfo.h"
13 #include "nsIWidget.h"
14 #include "nsCExternalHandlerService.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsGtkUtils.h"
17 #include "nsPIDOMWindow.h"
18 
19 using namespace mozilla;
20 
21 NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser)
22 
23 nsApplicationChooser::nsApplicationChooser() = default;
24 
25 nsApplicationChooser::~nsApplicationChooser() = default;
26 
27 NS_IMETHODIMP
Init(mozIDOMWindowProxy * aParent,const nsACString & aTitle)28 nsApplicationChooser::Init(mozIDOMWindowProxy* aParent,
29                            const nsACString& aTitle) {
30   NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE);
31   auto* parent = nsPIDOMWindowOuter::From(aParent);
32   mParentWidget = widget::WidgetUtils::DOMWindowToWidget(parent);
33   mWindowTitle.Assign(aTitle);
34   return NS_OK;
35 }
36 
37 NS_IMETHODIMP
Open(const nsACString & aContentType,nsIApplicationChooserFinishedCallback * aCallback)38 nsApplicationChooser::Open(const nsACString& aContentType,
39                            nsIApplicationChooserFinishedCallback* aCallback) {
40   MOZ_ASSERT(aCallback);
41   if (mCallback) {
42     NS_WARNING("Chooser is already in progress.");
43     return NS_ERROR_ALREADY_INITIALIZED;
44   }
45   mCallback = aCallback;
46   NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE);
47   GtkWindow* parent_widget =
48       GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
49 
50   GtkWidget* chooser = gtk_app_chooser_dialog_new_for_content_type(
51       parent_widget,
52       (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
53       PromiseFlatCString(aContentType).get());
54   gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser),
55                                      mWindowTitle.BeginReading());
56   NS_ADDREF_THIS();
57   g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this);
58   g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this);
59   gtk_widget_show(chooser);
60   return NS_OK;
61 }
62 
63 /* static */
OnResponse(GtkWidget * chooser,gint response_id,gpointer user_data)64 void nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id,
65                                       gpointer user_data) {
66   static_cast<nsApplicationChooser*>(user_data)->Done(chooser, response_id);
67 }
68 
69 /* static */
OnDestroy(GtkWidget * chooser,gpointer user_data)70 void nsApplicationChooser::OnDestroy(GtkWidget* chooser, gpointer user_data) {
71   static_cast<nsApplicationChooser*>(user_data)->Done(chooser,
72                                                       GTK_RESPONSE_CANCEL);
73 }
74 
Done(GtkWidget * chooser,gint response)75 void nsApplicationChooser::Done(GtkWidget* chooser, gint response) {
76   nsCOMPtr<nsILocalHandlerApp> localHandler;
77   nsresult rv;
78   switch (response) {
79     case GTK_RESPONSE_OK:
80     case GTK_RESPONSE_ACCEPT: {
81       localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
82       if (NS_FAILED(rv)) {
83         NS_WARNING("Out of memory.");
84         break;
85       }
86       GAppInfo* app_info =
87           gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser));
88 
89       nsCOMPtr<nsIFile> localExecutable;
90       gchar* fileWithFullPath =
91           g_find_program_in_path(g_app_info_get_executable(app_info));
92       if (!fileWithFullPath) {
93         g_object_unref(app_info);
94         NS_WARNING("Cannot find program in path.");
95         break;
96       }
97       rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false,
98                                  getter_AddRefs(localExecutable));
99       g_free(fileWithFullPath);
100       if (NS_FAILED(rv)) {
101         NS_WARNING("Cannot create local filename.");
102         localHandler = nullptr;
103       } else {
104         localHandler->SetExecutable(localExecutable);
105         localHandler->SetName(
106             NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info)));
107       }
108       g_object_unref(app_info);
109     }
110 
111     break;
112     case GTK_RESPONSE_CANCEL:
113     case GTK_RESPONSE_CLOSE:
114     case GTK_RESPONSE_DELETE_EVENT:
115       break;
116     default:
117       NS_WARNING("Unexpected response");
118       break;
119   }
120 
121   // A "response" signal won't be sent again but "destroy" will be.
122   g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy),
123                                        this);
124   gtk_widget_destroy(chooser);
125 
126   if (mCallback) {
127     mCallback->Done(localHandler);
128     mCallback = nullptr;
129   }
130   NS_RELEASE_THIS();
131 }
132