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