Merge lp:~midori/midori/openWith into lp:midori

Proposed by Cris Dywan
Status: Merged
Approved by: Paweł Forysiuk
Approved revision: 6587
Merged at revision: 6569
Proposed branch: lp:~midori/midori/openWith
Merge into: lp:midori
Diff against target: 1725 lines (+904/-475)
19 files modified
extensions/addons.c (+5/-4)
extensions/open-with.vala (+802/-0)
extensions/transfers.vala (+2/-7)
katze/katze-utils.c (+1/-211)
katze/katze.vapi (+7/-0)
katze/midori-uri.vala (+17/-0)
midori/midori-app.c (+1/-1)
midori/midori-browser.c (+5/-44)
midori/midori-dialog.vala (+14/-0)
midori/midori-download.vala (+7/-4)
midori/midori-extension.c (+2/-0)
midori/midori-frontend.c (+2/-1)
midori/midori-preferences.c (+0/-12)
midori/midori-tab.vala (+2/-0)
midori/midori-view.c (+13/-31)
midori/midori.vapi (+1/-0)
midori/sokoke.c (+22/-143)
midori/sokoke.h (+0/-17)
po/POTFILES.in (+1/-0)
To merge this branch: bzr merge lp:~midori/midori/openWith
Reviewer Review Type Date Requested Status
Paweł Forysiuk Approve
Review via email: mp+207737@code.launchpad.net

Commit message

Implement Open With extension dealing with file types

To post a comment you must log in.
lp:~midori/midori/openWith updated
6583. By Paweł Forysiuk

Don't bother fetching hicon if path to executable is null, update code style a bit

6584. By Paweł Forysiuk

Merge lp:midori

6585. By Paweł Forysiuk

Add some sanity checks when feching hIcon

6586. By Cris Dywan

Implement Customize… menu item in Chooser

6587. By Cris Dywan

Add tooltip about customization to treeview

Revision history for this message
Paweł Forysiuk (tuxator) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'extensions/addons.c'
2--- extensions/addons.c 2013-06-21 23:18:01 +0000
3+++ extensions/addons.c 2014-02-25 19:27:20 +0000
4@@ -485,8 +485,8 @@
5 else
6 {
7 gchar* element_uri = g_filename_to_uri (element->fullpath, NULL, NULL);
8- sokoke_show_uri (NULL, element_uri,
9- gtk_get_current_event_time (), NULL);
10+ gboolean handled = FALSE;
11+ g_signal_emit_by_name (midori_browser_get_current_tab (browser), "open-uri", element_uri, &handled);
12 g_free (element_uri);
13 }
14
15@@ -522,8 +522,9 @@
16 folder_uri = g_filename_to_uri (folder, NULL, NULL);
17 g_free (folder);
18
19- sokoke_show_uri (gtk_widget_get_screen (GTK_WIDGET (addons->treeview)),
20- folder_uri, gtk_get_current_event_time (), NULL);
21+ MidoriBrowser* browser = midori_browser_get_for_widget (addons->treeview);
22+ gboolean handled = FALSE;
23+ g_signal_emit_by_name (midori_browser_get_current_tab (browser), "open-uri", folder_uri, &handled);
24 g_free (folder_uri);
25 }
26
27
28=== added file 'extensions/open-with.vala'
29--- extensions/open-with.vala 1970-01-01 00:00:00 +0000
30+++ extensions/open-with.vala 2014-02-25 19:27:20 +0000
31@@ -0,0 +1,802 @@
32+/*
33+ Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
34+
35+ This library is free software; you can redistribute it and/or
36+ modify it under the terms of the GNU Lesser General Public
37+ License as published by the Free Software Foundation; either
38+ version 2.1 of the License, or (at your option) any later version.
39+
40+ See the file COPYING for the full license text.
41+*/
42+
43+#if HAVE_WIN32
44+namespace Sokoke {
45+ extern static Gdk.Pixbuf get_gdk_pixbuf_from_win32_executable (string path);
46+}
47+#endif
48+
49+namespace ExternalApplications {
50+ bool open_app_info (AppInfo app_info, string uri, string content_type) {
51+ Midori.URI.recursive_fork_protection (uri, true);
52+
53+ try {
54+ var uris = new List<File> ();
55+ uris.append (File.new_for_uri (uri));
56+ app_info.launch (uris, null);
57+ new Associations ().remember (content_type, app_info);
58+ return true;
59+ } catch (Error error) {
60+ warning ("Failed to open \"%s\": %s", uri, error.message);
61+ return false;
62+ }
63+ }
64+
65+ class Associations : Object {
66+#if HAVE_WIN32
67+ string config_dir;
68+ string filename;
69+ KeyFile keyfile;
70+
71+ public Associations () {
72+ config_dir = Midori.Paths.get_extension_config_dir ("open-with");
73+ filename = Path.build_filename (config_dir, "config");
74+ keyfile = new KeyFile ();
75+
76+ try {
77+ keyfile.load_from_file (filename, KeyFileFlags.NONE);
78+ } catch (FileError.NOENT exist_error) {
79+ /* It's no error if no config file exists */
80+ } catch (Error error) {
81+ warning ("Failed to load associations: %s", error.message);
82+ }
83+ }
84+
85+ public bool open (string content_type, string uri) {
86+ Midori.URI.recursive_fork_protection (uri, true);
87+ try {
88+ string commandline = keyfile.get_string ("mimes", content_type);
89+ if ("%u" in commandline)
90+ commandline = commandline.replace ("%u", Shell.quote (uri));
91+ else if ("%F" in commandline)
92+ commandline = commandline.replace ("%F", Shell.quote (Filename.from_uri (uri)));
93+ return Process.spawn_command_line_async (commandline);
94+ } catch (KeyFileError error) {
95+ /* Not remembered before */
96+ return false;
97+ } catch (Error error) {
98+ warning ("Failed to open \"%s\": %s", uri, error.message);
99+ return false;
100+ }
101+ }
102+
103+ public void remember (string content_type, AppInfo app_info) throws Error {
104+ keyfile.set_string ("mimes", content_type, get_commandline (app_info));
105+ FileUtils.set_contents (filename, keyfile.to_data ());
106+ }
107+
108+ public void custom (string content_type, string commandline, string name, string uri) {
109+ keyfile.set_string ("mimes", content_type, commandline);
110+ try {
111+ FileUtils.set_contents (filename, keyfile.to_data ());
112+ } catch (Error error) {
113+ warning ("Failed to add remember custom command line for \"%s\": %s", uri, error.message);
114+ }
115+ open (content_type, uri);
116+ }
117+ }
118+#else
119+ public Associations () {
120+ }
121+
122+ public bool open (string content_type, string uri) {
123+ var app_info = AppInfo.get_default_for_type (content_type, false);
124+ if (app_info == null)
125+ return false;
126+ return open_app_info (app_info, uri, content_type);
127+ }
128+
129+ public void remember (string content_type, AppInfo app_info) throws Error {
130+ app_info.set_as_last_used_for_type (content_type);
131+ app_info.set_as_default_for_type (content_type);
132+ }
133+
134+ public void custom (string content_type, string commandline, string name, string uri) {
135+ try {
136+ var app_info = AppInfo.create_from_commandline (commandline, name,
137+ "%u" in commandline ? AppInfoCreateFlags.SUPPORTS_URIS : AppInfoCreateFlags.NONE);
138+ open_app_info (app_info, uri, content_type);
139+ } catch (Error error) {
140+ warning ("Failed to add custom command line for \"%s\": %s", uri, error.message);
141+ }
142+ }
143+ }
144+#endif
145+
146+ static string get_commandline (AppInfo app_info) {
147+ return app_info.get_commandline () ?? app_info.get_executable ();
148+ }
149+
150+ static string describe_app_info (AppInfo app_info) {
151+ string name = app_info.get_display_name () ?? (Path.get_basename (app_info.get_executable ()));
152+ string desc = app_info.get_description () ?? get_commandline (app_info);
153+ return Markup.printf_escaped ("<b>%s</b>\n%s", name, desc);
154+ }
155+
156+ static Icon? app_info_get_icon (AppInfo app_info) {
157+ #if HAVE_WIN32
158+ return Sokoke.get_gdk_pixbuf_from_win32_executable (app_info.get_executable ());
159+ #else
160+ return app_info.get_icon ();
161+ #endif
162+ }
163+
164+ class CustomizerDialog : Gtk.Dialog {
165+ public Gtk.Entry name_entry;
166+ public Gtk.Entry commandline_entry;
167+
168+ public CustomizerDialog (AppInfo app_info, Gtk.Widget widget) {
169+ var browser = Midori.Browser.get_for_widget (widget);
170+ transient_for = browser;
171+
172+ title = _("Custom…");
173+#if !HAVE_GTK3
174+ has_separator = false;
175+#endif
176+ destroy_with_parent = true;
177+ set_icon_name (Gtk.STOCK_OPEN);
178+ resizable = false;
179+ add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
180+ Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT);
181+
182+ var vbox = new Gtk.VBox (false, 8);
183+ vbox.border_width = 8;
184+ (get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
185+
186+ var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
187+ var label = new Gtk.Label (_("Name:"));
188+ sizegroup.add_widget (label);
189+ label.set_alignment (0.0f, 0.5f);
190+ vbox.pack_start (label, false, false, 0);
191+ name_entry = new Gtk.Entry ();
192+ name_entry.activates_default = true;
193+ sizegroup.add_widget (name_entry);
194+ vbox.pack_start (name_entry, true, true, 0);
195+
196+ label = new Gtk.Label (_("Command Line:"));
197+ sizegroup.add_widget (label);
198+ label.set_alignment (0.0f, 0.5f);
199+ vbox.pack_start (label, false, false, 0);
200+ commandline_entry = new Gtk.Entry ();
201+ commandline_entry.activates_default = true;
202+ sizegroup.add_widget (name_entry);
203+ sizegroup.add_widget (commandline_entry);
204+ vbox.pack_start (commandline_entry, true, true, 0);
205+ get_content_area ().show_all ();
206+ set_default_response (Gtk.ResponseType.ACCEPT);
207+
208+ name_entry.text = app_info.get_name ();
209+ commandline_entry.text = get_commandline (app_info);
210+ }
211+ }
212+
213+ private class Chooser : Gtk.VBox {
214+ Gtk.ListStore store = new Gtk.ListStore (1, typeof (AppInfo));
215+ Gtk.TreeView treeview;
216+ List<AppInfo> available;
217+ string content_type;
218+ string uri;
219+
220+ public Chooser (string uri, string content_type) {
221+ this.content_type = content_type;
222+ this.uri = uri;
223+
224+ Gtk.TreeViewColumn column;
225+
226+ treeview = new Gtk.TreeView.with_model (store);
227+ treeview.headers_visible = false;
228+
229+ store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
230+ store.set_sort_func (0, tree_sort_func);
231+
232+ column = new Gtk.TreeViewColumn ();
233+ Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
234+ column.pack_start (renderer_icon, false);
235+ column.set_cell_data_func (renderer_icon, on_render_icon);
236+ treeview.append_column (column);
237+
238+ column = new Gtk.TreeViewColumn ();
239+ column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
240+ Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
241+ column.pack_start (renderer_text, true);
242+ column.set_expand (true);
243+ column.set_cell_data_func (renderer_text, on_render_text);
244+ treeview.append_column (column);
245+
246+ treeview.row_activated.connect (row_activated);
247+ treeview.show ();
248+ var scrolled = new Gtk.ScrolledWindow (null, null);
249+ scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
250+ scrolled.add (treeview);
251+ pack_start (scrolled);
252+ int height;
253+ treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
254+ scrolled.set_size_request (-1, height * 5);
255+ treeview.button_release_event.connect (button_released);
256+ treeview.tooltip_text = _("Right-click a suggestion to customize it");
257+
258+ available = new List<AppInfo> ();
259+ foreach (var app_info in AppInfo.get_all_for_type (content_type))
260+ launcher_added (app_info, uri);
261+
262+ if (store.iter_n_children (null) < 1) {
263+ foreach (var app_info in AppInfo.get_all ())
264+ launcher_added (app_info, uri);
265+ }
266+ }
267+
268+ bool button_released (Gdk.EventButton event) {
269+ if (event.button == 3)
270+ return show_popup_menu (event);
271+ return false;
272+ }
273+
274+ bool show_popup_menu (Gdk.EventButton? event) {
275+ Gtk.TreeIter iter;
276+ if (treeview.get_selection ().get_selected (null, out iter)) {
277+ AppInfo app_info;
278+ store.get (iter, 0, out app_info);
279+
280+ var menu = new Gtk.Menu ();
281+ var menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Custom…"));
282+ menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_EDIT, Gtk.IconSize.MENU);
283+ menuitem.activate.connect (() => {
284+ customize_app_info (app_info, content_type, uri);
285+ });
286+ menu.append (menuitem);
287+ menu.show_all ();
288+ Katze.widget_popup (treeview, menu, null, Katze.MenuPos.CURSOR);
289+
290+ return true;
291+ }
292+ return false;
293+ }
294+
295+ void customize_app_info (AppInfo app_info, string content_type, string uri) {
296+ var dialog = new CustomizerDialog (app_info, this);
297+ bool accept = dialog.run () == Gtk.ResponseType.ACCEPT;
298+ if (accept) {
299+ string name = dialog.name_entry.text;
300+ string commandline = dialog.commandline_entry.text;
301+ new Associations ().custom (content_type, commandline, name, uri);
302+ customized (app_info, content_type, uri);
303+ }
304+ dialog.destroy ();
305+ }
306+
307+ public List<AppInfo> get_available () {
308+ return available.copy ();
309+ }
310+
311+ public AppInfo get_app_info () {
312+ Gtk.TreeIter iter;
313+ if (treeview.get_selection ().get_selected (null, out iter)) {
314+ AppInfo app_info;
315+ store.get (iter, 0, out app_info);
316+ return app_info;
317+ }
318+ assert_not_reached ();
319+ }
320+
321+ void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
322+ Gtk.TreeModel model, Gtk.TreeIter iter) {
323+
324+ AppInfo app_info;
325+ model.get (iter, 0, out app_info);
326+
327+ renderer.set ("gicon", app_info_get_icon (app_info),
328+ "stock-size", Gtk.IconSize.DIALOG,
329+ "xpad", 4);
330+ }
331+
332+ void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
333+ Gtk.TreeModel model, Gtk.TreeIter iter) {
334+
335+ AppInfo app_info;
336+ model.get (iter, 0, out app_info);
337+ renderer.set ("markup", describe_app_info (app_info),
338+ "ellipsize", Pango.EllipsizeMode.END);
339+ }
340+
341+ void launcher_added (AppInfo app_info, string uri) {
342+ if (!app_info.should_show ())
343+ return;
344+
345+ Gtk.TreeIter iter;
346+ store.append (out iter);
347+ store.set (iter, 0, app_info);
348+
349+ available.append (app_info);
350+ }
351+
352+ int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
353+ AppInfo app_info1, app_info2;
354+ model.get (a, 0, out app_info1);
355+ model.get (b, 0, out app_info2);
356+ return strcmp (app_info1.get_display_name (), app_info2.get_display_name ());
357+ }
358+
359+ void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
360+ Gtk.TreeIter iter;
361+ if (store.get_iter (out iter, path)) {
362+ AppInfo app_info;
363+ store.get (iter, 0, out app_info);
364+ selected (app_info);
365+ }
366+ }
367+
368+ public signal void selected (AppInfo app_info);
369+ public signal void customized (AppInfo app_info, string content_type, string uri);
370+ }
371+
372+ class ChooserDialog : Gtk.Dialog {
373+ public Chooser chooser { get; private set; }
374+
375+ public ChooserDialog (string uri, string content_type, Gtk.Widget widget) {
376+ string filename;
377+ if (uri.has_prefix ("file://"))
378+ filename = Midori.Download.get_basename_for_display (uri);
379+ else
380+ filename = uri;
381+
382+ var browser = Midori.Browser.get_for_widget (widget);
383+ transient_for = browser;
384+
385+ title = _("Choose application");
386+#if !HAVE_GTK3
387+ has_separator = false;
388+#endif
389+ destroy_with_parent = true;
390+ set_icon_name (Gtk.STOCK_OPEN);
391+ resizable = false;
392+ add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
393+ Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT);
394+
395+ var vbox = new Gtk.VBox (false, 8);
396+ vbox.border_width = 8;
397+ (get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
398+ var label = new Gtk.Label (_("Select an application to open \"%s\"".printf (filename)));
399+ label.ellipsize = Pango.EllipsizeMode.MIDDLE;
400+ vbox.pack_start (label, false, false, 0);
401+ if (uri == "")
402+ label.no_show_all = true;
403+ chooser = new Chooser (uri, content_type);
404+ vbox.pack_start (chooser, true, true, 0);
405+
406+ get_content_area ().show_all ();
407+ set_default_response (Gtk.ResponseType.ACCEPT);
408+ chooser.selected.connect ((app_info) => {
409+ response (Gtk.ResponseType.ACCEPT);
410+ });
411+ chooser.customized.connect ((app_info, content_type, uri) => {
412+ response (Gtk.ResponseType.CANCEL);
413+ });
414+ }
415+
416+ public AppInfo? open_with () {
417+ show ();
418+ bool accept = run () == Gtk.ResponseType.ACCEPT;
419+ hide ();
420+
421+ if (!accept)
422+ return null;
423+ return chooser.get_app_info ();
424+ }
425+ }
426+
427+ class ChooserButton : Gtk.Button {
428+ public AppInfo? app_info { get; set; }
429+ public string? commandline { get; set; }
430+ ChooserDialog dialog;
431+ Gtk.Label app_name;
432+ Gtk.Image icon;
433+
434+ public ChooserButton (string mime_type, string? commandline) {
435+ string content_type = ContentType.from_mime_type (mime_type);
436+ dialog = new ChooserDialog ("", content_type, this);
437+ app_info = null;
438+ foreach (var candidate in dialog.chooser.get_available ()) {
439+ if (get_commandline (candidate) == commandline)
440+ app_info = candidate;
441+ }
442+
443+ var hbox = new Gtk.HBox (false, 4);
444+ icon = new Gtk.Image ();
445+ hbox.pack_start (icon, false, false, 0);
446+ app_name = new Gtk.Label (null);
447+ app_name.use_markup = true;
448+ app_name.ellipsize = Pango.EllipsizeMode.END;
449+ hbox.pack_start (app_name, true, true, 0);
450+ add (hbox);
451+ show_all ();
452+ update_label ();
453+
454+ clicked.connect (() => {
455+ app_info = dialog.open_with ();
456+ string new_commandline = app_info != null ? get_commandline (app_info) : null;
457+ commandline = new_commandline;
458+ selected (new_commandline);
459+ update_label ();
460+ });
461+ }
462+
463+ void update_label () {
464+ app_name.label = app_info != null ? describe_app_info (app_info).replace ("\n", " ") : _("None");
465+ icon.set_from_gicon (app_info != null ? app_info_get_icon (app_info) : null, Gtk.IconSize.BUTTON);
466+ }
467+
468+ public signal void selected (string? commandline);
469+ }
470+
471+ class Types : Gtk.VBox {
472+ public Gtk.ListStore store = new Gtk.ListStore (2, typeof (string), typeof (AppInfo));
473+ Gtk.TreeView treeview;
474+
475+ public Types () {
476+ Gtk.TreeViewColumn column;
477+
478+ treeview = new Gtk.TreeView.with_model (store);
479+ treeview.headers_visible = false;
480+
481+ store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
482+ store.set_sort_func (0, tree_sort_func);
483+
484+ column = new Gtk.TreeViewColumn ();
485+ column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
486+ Gtk.CellRendererPixbuf renderer_type_icon = new Gtk.CellRendererPixbuf ();
487+ column.pack_start (renderer_type_icon, false);
488+ column.set_cell_data_func (renderer_type_icon, on_render_type_icon);
489+ treeview.append_column (column);
490+
491+ column = new Gtk.TreeViewColumn ();
492+ column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
493+ Gtk.CellRendererText renderer_type_text = new Gtk.CellRendererText ();
494+ column.pack_start (renderer_type_text, true);
495+ column.set_cell_data_func (renderer_type_text, on_render_type_text);
496+ treeview.append_column (column);
497+
498+ column = new Gtk.TreeViewColumn ();
499+ column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
500+ Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
501+ column.pack_start (renderer_icon, false);
502+ column.set_cell_data_func (renderer_icon, on_render_icon);
503+ treeview.append_column (column);
504+
505+ column = new Gtk.TreeViewColumn ();
506+ column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
507+ Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
508+ column.pack_start (renderer_text, true);
509+ column.set_expand (true);
510+ column.set_cell_data_func (renderer_text, on_render_text);
511+ treeview.append_column (column);
512+
513+ treeview.row_activated.connect (row_activated);
514+ treeview.show ();
515+ var scrolled = new Gtk.ScrolledWindow (null, null);
516+ scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
517+ scrolled.add (treeview);
518+ pack_start (scrolled);
519+ int height;
520+ treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
521+ scrolled.set_size_request (-1, height * 5);
522+
523+ foreach (string content_type in ContentType.list_registered ())
524+ launcher_added (content_type);
525+ foreach (string scheme in Vfs.get_default ().get_supported_uri_schemes ())
526+ launcher_added ("x-scheme-handler/" + scheme);
527+
528+ treeview.size_allocate.connect_after ((allocation) => {
529+ treeview.columns_autosize ();
530+ });
531+ }
532+
533+ void on_render_type_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
534+ Gtk.TreeModel model, Gtk.TreeIter iter) {
535+
536+ string content_type;
537+ store.get (iter, 0, out content_type);
538+
539+ renderer.set ("gicon", ContentType.get_icon (content_type),
540+ "stock-size", Gtk.IconSize.BUTTON,
541+ "xpad", 4);
542+ }
543+
544+ void on_render_type_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
545+ Gtk.TreeModel model, Gtk.TreeIter iter) {
546+
547+ string content_type;
548+ AppInfo app_info;
549+ store.get (iter, 0, out content_type, 1, out app_info);
550+
551+ string desc, mime_type;
552+ if (content_type.has_prefix ("x-scheme-handler/")) {
553+ desc = content_type.split ("/")[1] + "://";
554+ mime_type = "";
555+ } else {
556+ desc = ContentType.get_description (content_type);
557+ mime_type = ContentType.get_mime_type (content_type);
558+ }
559+
560+ renderer.set ("markup",
561+ Markup.printf_escaped ("<b>%s</b>\n%s",
562+ desc, mime_type),
563+#if HAVE_GTK3
564+ "max-width-chars", 30,
565+#else
566+ "width-chars", 30,
567+#endif
568+ "ellipsize", Pango.EllipsizeMode.END);
569+ }
570+
571+ void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
572+ Gtk.TreeModel model, Gtk.TreeIter iter) {
573+
574+ AppInfo app_info;
575+ model.get (iter, 1, out app_info);
576+
577+ renderer.set ("gicon", app_info_get_icon (app_info),
578+ "stock-size", Gtk.IconSize.MENU,
579+ "xpad", 4);
580+ }
581+
582+ void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
583+ Gtk.TreeModel model, Gtk.TreeIter iter) {
584+
585+ AppInfo app_info;
586+ model.get (iter, 1, out app_info);
587+ renderer.set ("markup", describe_app_info (app_info),
588+ "ellipsize", Pango.EllipsizeMode.END);
589+ }
590+
591+ void launcher_added (string content_type) {
592+ var app_info = AppInfo.get_default_for_type (content_type, false);
593+ if (app_info == null)
594+ return;
595+
596+ Gtk.TreeIter iter;
597+ store.append (out iter);
598+ store.set (iter, 0, content_type, 1, app_info);
599+ }
600+
601+ int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
602+ string content_type1, content_type2;
603+ model.get (a, 0, out content_type1);
604+ model.get (b, 0, out content_type2);
605+ return strcmp (content_type1, content_type2);
606+ }
607+
608+ void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
609+ Gtk.TreeIter iter;
610+ if (store.get_iter (out iter, path)) {
611+ string content_type;
612+ store.get (iter, 0, out content_type);
613+ selected (content_type, iter);
614+ }
615+ }
616+
617+ public signal void selected (string content_type, Gtk.TreeIter iter);
618+ }
619+
620+
621+ private class Manager : Midori.Extension {
622+ enum NextStep {
623+ TRY_OPEN,
624+ OPEN_WITH
625+ }
626+
627+ bool open_uri (Midori.Tab tab, string uri) {
628+ return open_with_type (uri, get_content_type (uri, null), tab, NextStep.TRY_OPEN);
629+ }
630+
631+ bool navigation_requested (WebKit.WebView web_view, WebKit.WebFrame frame, WebKit.NetworkRequest request,
632+ WebKit.WebNavigationAction action, WebKit.WebPolicyDecision decision) {
633+
634+ string uri = request.uri;
635+ if (Midori.URI.is_http (uri) || Midori.URI.is_blank (uri))
636+ return false;
637+
638+ decision.ignore ();
639+
640+ string content_type = get_content_type (uri, null);
641+ open_with_type (uri, content_type, web_view, NextStep.TRY_OPEN);
642+ return true;
643+ }
644+
645+ string get_content_type (string uri, string? mime_type) {
646+ if (!uri.has_prefix ("file://") && !Midori.URI.is_http (uri)) {
647+ string protocol = uri.split(":", 2)[0];
648+ return "x-scheme-handler/" + protocol;
649+ } else if (mime_type == null) {
650+ string filename;
651+ bool uncertain;
652+ try {
653+ filename = Filename.from_uri (uri);
654+ } catch (Error error) {
655+ filename = uri;
656+ }
657+ return ContentType.guess (filename, null, out uncertain);
658+ }
659+ return ContentType.from_mime_type (mime_type);
660+ }
661+
662+ bool open_with_type (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
663+ #if HAVE_WEBKIT2
664+ return open_now (uri, content_type, widget, next_step);
665+ #else
666+ if (!Midori.URI.is_http (uri))
667+ return open_now (uri, content_type, widget, next_step);
668+
669+ var download = new WebKit.Download (new WebKit.NetworkRequest (uri));
670+ download.destination_uri = Midori.Download.prepare_destination_uri (download, null);
671+ if (!Midori.Download.has_enough_space (download, download.destination_uri))
672+ return false;
673+
674+ download.notify["status"].connect ((pspec) => {
675+ if (download.status == WebKit.DownloadStatus.FINISHED) {
676+ open_now (download.destination_uri, content_type, widget, next_step);
677+ }
678+ else if (download.status == WebKit.DownloadStatus.ERROR)
679+ Midori.show_message_dialog (Gtk.MessageType.ERROR,
680+ _("Error downloading the image!"),
681+ _("Can not download selected image."), false);
682+ });
683+ download.start ();
684+ return true;
685+ #endif
686+ }
687+
688+ bool open_now (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
689+ if (next_step == NextStep.TRY_OPEN && (new Associations ()).open (content_type, uri))
690+ return true;
691+ if (open_with (uri, content_type, widget) != null)
692+ return true;
693+ return false;
694+ }
695+
696+ AppInfo? open_with (string uri, string content_type, Gtk.Widget widget) {
697+ var dialog = new ChooserDialog (uri, content_type, widget);
698+
699+ var app_info = dialog.open_with ();
700+ dialog.destroy ();
701+
702+ if (uri == "")
703+ return app_info;
704+
705+ if (app_info == null)
706+ return app_info;
707+
708+ return open_app_info (app_info, uri, content_type) ? app_info : null;
709+ }
710+
711+ void context_menu (Midori.Tab tab, WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
712+ if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
713+ string uri = hit_test_result.link_uri;
714+ var action = new Gtk.Action ("OpenWith", _("Open _with…"), null, null);
715+ action.activate.connect ((action) => {
716+ open_with_type (uri, get_content_type (uri, null), tab, NextStep.OPEN_WITH);
717+ });
718+ menu.add (action);
719+ }
720+#if !HAVE_WEBKIT2
721+ if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
722+ string uri = hit_test_result.image_uri;
723+ var action = new Gtk.Action ("OpenImageInViewer", _("Open in Image _Viewer"), null, null);
724+ action.activate.connect ((action) => {
725+ open_with_type (uri, get_content_type (uri, null), tab, NextStep.TRY_OPEN);
726+ });
727+ menu.add (action);
728+ }
729+#endif
730+ }
731+
732+ void show_preferences (Katze.Preferences preferences) {
733+ var settings = get_app ().settings;
734+ var category = preferences.add_category (_("File Types"), Gtk.STOCK_FILE);
735+ preferences.add_group (null);
736+
737+ var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
738+ var label = new Gtk.Label (_("Text Editor"));
739+ sizegroup.add_widget (label);
740+ label.set_alignment (0.0f, 0.5f);
741+ preferences.add_widget (label, "indented");
742+ var entry = new ChooserButton ("text/plain", settings.text_editor);
743+ sizegroup.add_widget (entry);
744+ entry.selected.connect ((commandline) => {
745+ settings.text_editor = commandline;
746+ });
747+ preferences.add_widget (entry, "spanned");
748+
749+ label = new Gtk.Label (_("News Aggregator"));
750+ sizegroup.add_widget (label);
751+ label.set_alignment (0.0f, 0.5f);
752+ preferences.add_widget (label, "indented");
753+ entry = new ChooserButton ("application/rss+xml", settings.news_aggregator);
754+ sizegroup.add_widget (entry);
755+ entry.selected.connect ((commandline) => {
756+ settings.news_aggregator = commandline;
757+ });
758+ preferences.add_widget (entry, "spanned");
759+
760+ var types = new Types ();
761+ types.selected.connect ((content_type, iter) => {
762+ var app_info = open_with ("", content_type, preferences);
763+ if (app_info == null)
764+ return;
765+ try {
766+ app_info.set_as_default_for_type (content_type);
767+ types.store.set (iter, 1, app_info);
768+ } catch (Error error) {
769+ warning ("Failed to select default for \"%s\": %s", content_type, error.message);
770+ }
771+ });
772+ category.pack_start (types, true, true, 0);
773+ types.show_all ();
774+ }
775+
776+ public void tab_added (Midori.Browser browser, Midori.View view) {
777+ view.web_view.navigation_policy_decision_requested.connect (navigation_requested);
778+ view.open_uri.connect (open_uri);
779+ view.context_menu.connect (context_menu);
780+ }
781+
782+ public void tab_removed (Midori.Browser browser, Midori.View view) {
783+ view.web_view.navigation_policy_decision_requested.disconnect (navigation_requested);
784+ view.open_uri.disconnect (open_uri);
785+ view.context_menu.disconnect (context_menu);
786+ }
787+
788+ void browser_added (Midori.Browser browser) {
789+ foreach (var tab in browser.get_tabs ())
790+ tab_added (browser, tab);
791+ browser.add_tab.connect (tab_added);
792+ browser.remove_tab.connect (tab_removed);
793+ browser.show_preferences.connect (show_preferences);
794+ }
795+
796+ void activated (Midori.App app) {
797+ foreach (var browser in app.get_browsers ())
798+ browser_added (browser);
799+ app.add_browser.connect (browser_added);
800+ }
801+
802+ void browser_removed (Midori.Browser browser) {
803+ foreach (var tab in browser.get_tabs ())
804+ tab_removed (browser, tab);
805+ browser.add_tab.disconnect (tab_added);
806+ browser.remove_tab.disconnect (tab_removed);
807+ browser.show_preferences.disconnect (show_preferences);
808+ }
809+
810+ void deactivated () {
811+ var app = get_app ();
812+ foreach (var browser in app.get_browsers ())
813+ browser_removed (browser);
814+ app.add_browser.disconnect (browser_added);
815+
816+ }
817+
818+ internal Manager () {
819+ GLib.Object (name: "External Applications",
820+ description: "Choose what to open unknown file types with",
821+ version: "0.1" + Midori.VERSION_SUFFIX,
822+ authors: "Christian Dywan <christian@twotoasts.de>");
823+
824+ this.activate.connect (activated);
825+ this.deactivate.connect (deactivated);
826+ }
827+ }
828+}
829+
830+public Midori.Extension extension_init () {
831+ return new ExternalApplications.Manager ();
832+}
833+
834
835=== modified file 'extensions/transfers.vala'
836--- extensions/transfers.vala 2014-01-06 21:51:09 +0000
837+++ extensions/transfers.vala 2014-02-25 19:27:20 +0000
838@@ -14,7 +14,6 @@
839 }
840
841 namespace Sokoke {
842- extern static bool show_uri (Gdk.Screen screen, string uri, uint32 timestamp) throws Error;
843 extern static void widget_get_text_size (Gtk.Widget widget, string sample, out int width, out int height);
844 }
845
846@@ -214,12 +213,8 @@
847 menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Open Destination _Folder"));
848 menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_DIRECTORY, Gtk.IconSize.MENU);
849 menuitem.activate.connect (() => {
850- try {
851- var folder = GLib.File.new_for_uri (transfer.destination);
852- Sokoke.show_uri (get_screen (), folder.get_parent ().get_uri (), 0);
853- } catch (Error error_folder) {
854- GLib.warning (_("Failed to open download: %s"), error_folder.message);
855- }
856+ var folder = GLib.File.new_for_uri (transfer.destination);
857+ (Midori.Browser.get_for_widget (this).tab as Midori.Tab).open_uri (folder.get_parent ().get_uri ());
858 });
859 menu.append (menuitem);
860 menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Copy Link Loc_ation"));
861
862=== modified file 'katze/katze-utils.c'
863--- katze/katze-utils.c 2013-11-05 21:51:18 +0000
864+++ katze/katze-utils.c 2014-02-25 19:27:20 +0000
865@@ -101,78 +101,12 @@
866 }
867 #endif
868
869-static const gchar*
870-katze_app_info_get_commandline (GAppInfo* info)
871-{
872- const gchar* exe;
873-
874- exe = g_app_info_get_commandline (info);
875- if (!exe)
876- exe = g_app_info_get_executable (info);
877- if (!exe)
878- exe = g_app_info_get_name (info);
879- return exe;
880-}
881-
882 static gboolean
883 proxy_entry_focus_out_event_cb (GtkEntry* entry,
884 GdkEventFocus* event,
885 GObject* object);
886
887 static void
888-proxy_combo_box_apps_changed_cb (GtkComboBox* button,
889- GObject* object)
890-{
891- guint active = gtk_combo_box_get_active (button);
892- GtkTreeModel* model = gtk_combo_box_get_model (button);
893- GtkTreeIter iter;
894-
895- if (gtk_tree_model_iter_nth_child (model, &iter, NULL, active))
896- {
897- GAppInfo* info;
898- gboolean use_entry;
899- GtkWidget* child;
900- const gchar* exe;
901- const gchar* property = g_object_get_data (G_OBJECT (button), "property");
902-
903- gtk_tree_model_get (model, &iter, 0, &info, -1);
904-
905- use_entry = info && !g_app_info_get_icon (info);
906- child = gtk_bin_get_child (GTK_BIN (button));
907- if (use_entry && GTK_IS_CELL_VIEW (child))
908- {
909- GtkWidget* entry = gtk_entry_new ();
910- exe = g_app_info_get_executable (info);
911- if (exe && *exe && strcmp (exe, "%f"))
912- gtk_entry_set_text (GTK_ENTRY (entry), exe);
913- gtk_widget_show (entry);
914- gtk_container_add (GTK_CONTAINER (button), entry);
915- gtk_widget_grab_focus (entry);
916- g_signal_connect (entry, "focus-out-event",
917- G_CALLBACK (proxy_entry_focus_out_event_cb), object);
918- g_object_set_data_full (G_OBJECT (entry), "property",
919- g_strdup (property), g_free);
920- }
921- else if (!use_entry && GTK_IS_ENTRY (child))
922- {
923- /* Force the combo to change the item again */
924- gtk_widget_destroy (child);
925- gtk_combo_box_set_active (button, 0);
926- gtk_combo_box_set_active_iter (button, &iter);
927- }
928-
929- if (info)
930- {
931- exe = katze_app_info_get_commandline (info);
932- g_object_set (object, property, exe, NULL);
933- g_object_unref (info);
934- }
935- else
936- g_object_set (object, property, "", NULL);
937- }
938-}
939-
940-static void
941 proxy_entry_activate_cb (GtkEntry* entry,
942 GObject* object)
943 {
944@@ -326,130 +260,6 @@
945 proxy_object_notify_string_cb, proxy);
946 }
947
948-static GList*
949-katze_app_info_get_all_for_category (const gchar* category)
950-{
951- #ifdef _WIN32
952- /* FIXME: Real filtering by category would be better */
953- const gchar* content_type = g_content_type_from_mime_type (category);
954- GList* all_apps = g_app_info_get_all_for_type (content_type);
955- #else
956- GList* all_apps = g_app_info_get_all ();
957- #endif
958- GList* apps = NULL;
959- GList* app;
960- for (app = apps; app; app = g_list_next (app))
961- {
962- GAppInfo* info = app->data;
963- #ifdef GDK_WINDOWING_X11
964- gchar* filename = g_strconcat ("applications/", g_app_info_get_id (info), NULL);
965- GKeyFile* file = g_key_file_new ();
966-
967- if (g_key_file_load_from_data_dirs (file, filename, NULL, G_KEY_FILE_NONE, NULL))
968- {
969- gchar* cat = g_key_file_get_string (file, "Desktop Entry",
970- "Categories", NULL);
971- if (cat && g_strrstr (cat, category))
972- apps = g_list_append (apps, info);
973-
974- g_free (cat);
975- }
976- g_key_file_free (file);
977- g_free (filename);
978- #else
979- apps = g_list_append (apps, info);
980- #endif
981- }
982- g_list_free (all_apps);
983- return apps;
984-}
985-
986-static gboolean
987-proxy_populate_apps (GtkWidget* widget)
988-{
989- const gchar* property = g_object_get_data (G_OBJECT (widget), "property");
990- GObject* object = g_object_get_data (G_OBJECT (widget), "object");
991- gchar* string = katze_object_get_string (object, property);
992- if (!g_strcmp0 (string, ""))
993- katze_assign (string, NULL);
994- GtkSettings* settings = gtk_widget_get_settings (widget);
995- gint icon_width = 16;
996- if (settings == NULL)
997- settings = gtk_settings_get_for_screen (gdk_screen_get_default ());
998- gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU,
999- &icon_width, NULL);
1000-
1001- GtkComboBox* combo = GTK_COMBO_BOX (widget);
1002- GtkListStore* model = GTK_LIST_STORE (gtk_combo_box_get_model (combo));
1003- GtkTreeIter iter_none;
1004- gtk_list_store_insert_with_values (model, &iter_none, 0,
1005- 0, NULL, 1, NULL, 2, _("None"), 3, icon_width, -1);
1006-
1007- const gchar* app_type = g_object_get_data (G_OBJECT (widget), "app-type");
1008- GList* apps = g_app_info_get_all_for_type (app_type);
1009- GAppInfo* info;
1010- if (!apps)
1011- apps = katze_app_info_get_all_for_category (app_type);
1012- if (apps != NULL)
1013- {
1014- GList* app;
1015- for (app = apps; app; app = g_list_next (app))
1016- {
1017- info = app->data;
1018- const gchar* name = g_app_info_get_name (info);
1019- GIcon* icon = g_app_info_get_icon (info);
1020- gchar* icon_name;
1021- GtkTreeIter iter;
1022-
1023- if (!g_app_info_should_show (info))
1024- continue;
1025-
1026- icon_name = icon ? g_icon_to_string (icon) : NULL;
1027- gtk_list_store_insert_with_values (model, &iter, G_MAXINT,
1028- 0, info, 1, icon_name, 2, name, 3, icon_width, -1);
1029- if (string && !strcmp (katze_app_info_get_commandline (info), string))
1030- gtk_combo_box_set_active_iter (combo, &iter);
1031-
1032- g_free (icon_name);
1033- }
1034- g_list_free (apps);
1035- }
1036-
1037- info = g_app_info_create_from_commandline ("",
1038- "", G_APP_INFO_CREATE_NONE, NULL);
1039- gtk_list_store_insert_with_values (model, NULL, G_MAXINT,
1040- 0, info, 1, NULL, 2, _("Custom…"), 3, icon_width, -1);
1041- g_object_unref (info);
1042-
1043- if (gtk_combo_box_get_active (combo) == -1)
1044- {
1045- if (string)
1046- {
1047- GtkWidget* entry;
1048- const gchar* exe;
1049-
1050- info = g_app_info_create_from_commandline (string,
1051- NULL, G_APP_INFO_CREATE_NONE, NULL);
1052- entry = gtk_entry_new ();
1053- exe = g_app_info_get_executable (info);
1054- if (exe && *exe && strcmp (exe, "%f"))
1055- gtk_entry_set_text (GTK_ENTRY (entry), string);
1056- gtk_widget_show (entry);
1057- gtk_container_add (GTK_CONTAINER (combo), entry);
1058- g_object_unref (info);
1059- g_signal_connect (entry, "focus-out-event",
1060- G_CALLBACK (proxy_entry_focus_out_event_cb), object);
1061- g_object_set_data_full (G_OBJECT (entry), "property",
1062- g_strdup (property), g_free);
1063- }
1064- else
1065- gtk_combo_box_set_active_iter (combo, &iter_none);
1066- }
1067- g_signal_connect (widget, "changed",
1068- G_CALLBACK (proxy_combo_box_apps_changed_cb), object);
1069- return G_SOURCE_REMOVE;
1070-}
1071-
1072 /**
1073 * katze_property_proxy:
1074 * @object: a #GObject
1075@@ -483,6 +293,7 @@
1076 * for choosing an application to open TYPE files, ie. "text/plain".
1077 * "application-CATEGORY": the widget created will be particularly suitable
1078 * for choosing an application to open CATEGORY files, ie. "Network".
1079+ * Since 0.5.8 the CATEGORY hint is no longer supported.
1080 * "custom-PROPERTY": the last value of an enumeration will be the "custom"
1081 * value, where the user may enter text freely, which then updates
1082 * the property PROPERTY instead. This applies only to enumerations.
1083@@ -652,27 +463,6 @@
1084 g_free (families);
1085 #endif
1086 }
1087- else if (type == G_TYPE_PARAM_STRING && hint && g_str_has_prefix (hint, "application-"))
1088- {
1089- GtkListStore* model;
1090- GtkCellRenderer* renderer;
1091- const gchar* app_type = &hint[12];
1092-
1093- model = gtk_list_store_new (4, G_TYPE_APP_INFO, G_TYPE_STRING,
1094- G_TYPE_STRING, G_TYPE_INT);
1095- widget = gtk_combo_box_new_with_model (GTK_TREE_MODEL (model));
1096- renderer = gtk_cell_renderer_pixbuf_new ();
1097- gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), renderer, FALSE);
1098- gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (widget), renderer, "icon-name", 1);
1099- gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (widget), renderer, "width", 3);
1100- renderer = gtk_cell_renderer_text_new ();
1101- gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), renderer, TRUE);
1102- gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (widget), renderer, "text", 2);
1103-
1104- g_object_set_data_full (G_OBJECT (widget), "app-type", g_strdup (app_type), g_free);
1105- g_object_set_data_full (G_OBJECT (widget), "object", g_object_ref (object), g_object_unref);
1106- g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc)proxy_populate_apps, widget, NULL);
1107- }
1108 else if (type == G_TYPE_PARAM_STRING)
1109 {
1110 gchar* notify_property;
1111
1112=== modified file 'katze/katze.vapi'
1113--- katze/katze.vapi 2013-09-22 16:22:10 +0000
1114+++ katze/katze.vapi 2014-02-25 19:27:20 +0000
1115@@ -36,5 +36,12 @@
1116 public unowned string? get_meta_string (string key);
1117 public void set_meta_string (string key, string value);
1118 }
1119+
1120+ [CCode (cheader_filename = "katze/katze.h")]
1121+ public class Preferences : Gtk.Dialog {
1122+ public unowned Gtk.Box add_category (string label, string icon);
1123+ public void add_group (string? label);
1124+ public void add_widget (Gtk.Widget widget, string type);
1125+ }
1126 }
1127
1128
1129=== modified file 'katze/midori-uri.vala'
1130--- katze/midori-uri.vala 2014-01-06 21:51:09 +0000
1131+++ katze/midori-uri.vala 2014-02-25 19:27:20 +0000
1132@@ -16,6 +16,8 @@
1133
1134 namespace Midori {
1135 public class URI : Object {
1136+ static string? fork_uri = null;
1137+
1138 public static string? parse_hostname (string? uri, out string path) {
1139 path = null;
1140 if (uri == null)
1141@@ -205,5 +207,20 @@
1142 label = display;
1143 return type;
1144 }
1145+
1146+ /*
1147+ Protects against recursive invokations of Midori with the same URI.
1148+ Consider a tel:// URI opened via Tab.open_uri, being handed off to GIO,
1149+ which in turns calls exo-open, which in turn can't open tel:// and falls
1150+ back to the browser ie. Midori.
1151+ So: code opening URIs calls this function with %true, #Midori.App passes %false.
1152+
1153+ Since: 0.5.8
1154+ */
1155+ public static bool recursive_fork_protection (string uri, bool set_uri) {
1156+ if (set_uri)
1157+ fork_uri = uri;
1158+ return fork_uri != uri;
1159+ }
1160 }
1161 }
1162
1163=== modified file 'midori/midori-app.c'
1164--- midori/midori-app.c 2014-02-24 11:33:50 +0000
1165+++ midori/midori-app.c 2014-02-25 19:27:20 +0000
1166@@ -535,7 +535,7 @@
1167 for (i = 0; i < n_files; i++)
1168 {
1169 gchar* uri = g_file_get_uri (files[i]);
1170- if (sokoke_recursive_fork_protection (uri, FALSE))
1171+ if (midori_uri_recursive_fork_protection (uri, FALSE))
1172 {
1173 if (first)
1174 {
1175
1176=== modified file 'midori/midori-browser.c'
1177--- midori/midori-browser.c 2014-02-12 22:56:28 +0000
1178+++ midori/midori-browser.c 2014-02-25 19:27:20 +0000
1179@@ -1496,7 +1496,11 @@
1180 MidoriNewView where)
1181 {
1182 if (midori_paths_get_runtime_mode () == MIDORI_RUNTIME_MODE_APP)
1183- return sokoke_show_uri (gtk_widget_get_screen (view), uri, 0, NULL);
1184+ {
1185+ gboolean handled = FALSE;
1186+ g_signal_emit_by_name (view, "open-uri", uri, &handled);
1187+ return handled;
1188+ }
1189 else if (midori_paths_get_runtime_mode () == MIDORI_RUNTIME_MODE_PRIVATE)
1190 {
1191 if (where == MIDORI_NEW_VIEW_WINDOW)
1192@@ -1613,37 +1617,6 @@
1193 }
1194 }
1195
1196-static void
1197-midori_browser_download_status_cb (WebKitDownload* download,
1198- GParamSpec* pspec,
1199- GtkWidget* widget)
1200-{
1201-#ifndef HAVE_WEBKIT2
1202- const gchar* uri = webkit_download_get_destination_uri (download);
1203- switch (webkit_download_get_status (download))
1204- {
1205- case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1206- if (!sokoke_show_uri (gtk_widget_get_screen (widget), uri, 0, NULL))
1207- {
1208- sokoke_message_dialog (GTK_MESSAGE_ERROR,
1209- _("Error opening the image!"),
1210- _("Can not open selected image in a default viewer."), FALSE);
1211- }
1212- break;
1213- case WEBKIT_DOWNLOAD_STATUS_ERROR:
1214- webkit_download_cancel (download);
1215- sokoke_message_dialog (GTK_MESSAGE_ERROR,
1216- _("Error downloading the image!"),
1217- _("Can not download selected image."), FALSE);
1218- break;
1219- case WEBKIT_DOWNLOAD_STATUS_CREATED:
1220- case WEBKIT_DOWNLOAD_STATUS_STARTED:
1221- case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1222- break;
1223- }
1224-#endif
1225-}
1226-
1227 #ifdef HAVE_WEBKIT2
1228 static void
1229 midori_browser_close_tab_idle (GObject* resource,
1230@@ -1679,18 +1652,6 @@
1231 {
1232 handled = FALSE;
1233 }
1234- else if (type == MIDORI_DOWNLOAD_OPEN_IN_VIEWER)
1235- {
1236- gchar* destination_uri =
1237- midori_download_prepare_destination_uri (download, NULL);
1238- midori_browser_prepare_download (browser, download, destination_uri);
1239- g_signal_connect (download, "notify::status",
1240- G_CALLBACK (midori_browser_download_status_cb), GTK_WIDGET (browser));
1241- g_free (destination_uri);
1242- #ifndef HAVE_WEBKIT2
1243- webkit_download_start (download);
1244- #endif
1245- }
1246 #ifdef HAVE_WEBKIT2
1247 else if (!webkit_download_get_destination (download))
1248 #else
1249
1250=== modified file 'midori/midori-dialog.vala'
1251--- midori/midori-dialog.vala 2014-01-25 21:04:57 +0000
1252+++ midori/midori-dialog.vala 2014-02-25 19:27:20 +0000
1253@@ -103,6 +103,20 @@
1254 }
1255 }
1256
1257+ public static void show_message_dialog (Gtk.MessageType type, string short, string detailed, bool modal) {
1258+ var dialog = new Gtk.MessageDialog (null, 0, type, Gtk.ButtonsType.OK, "%s", short);
1259+ dialog.format_secondary_text ("%s", detailed);
1260+ if (modal) {
1261+ dialog.run ();
1262+ dialog.destroy ();
1263+ } else {
1264+ dialog.response.connect ((response) => {
1265+ dialog.destroy ();
1266+ });
1267+ dialog.show ();
1268+ }
1269+ }
1270+
1271 public class FileChooserDialog : Gtk.FileChooserDialog {
1272 public FileChooserDialog (string title, Gtk.Window? window, Gtk.FileChooserAction action) {
1273 /* Creates a new file chooser dialog to Open or Save and Cancel.
1274
1275=== modified file 'midori/midori-download.vala'
1276--- midori/midori-download.vala 2014-01-06 21:51:09 +0000
1277+++ midori/midori-download.vala 2014-02-25 19:27:20 +0000
1278@@ -11,7 +11,6 @@
1279
1280 namespace Sokoke {
1281 #if !HAVE_WEBKIT2
1282- extern static bool show_uri (Gdk.Screen screen, string uri, uint32 timestamp) throws Error;
1283 extern static bool message_dialog (Gtk.MessageType type, string short, string detailed, bool modal);
1284 #endif
1285 }
1286@@ -212,9 +211,13 @@
1287
1288 public static bool open (WebKit.Download download, Gtk.Widget widget) throws Error {
1289 #if !HAVE_WEBKIT2
1290- if (!has_wrong_checksum (download))
1291- return Sokoke.show_uri (widget.get_screen (),
1292- download.destination_uri, Gtk.get_current_event_time ());
1293+ if (!has_wrong_checksum (download)) {
1294+ var browser = widget.get_toplevel ();
1295+ Tab? tab = null;
1296+ browser.get ("tab", tab);
1297+ if (tab != null)
1298+ tab.open_uri (download.destination_uri);
1299+ }
1300
1301 Sokoke.message_dialog (Gtk.MessageType.WARNING,
1302 _("The downloaded file is erroneous."),
1303
1304=== modified file 'midori/midori-extension.c'
1305--- midori/midori-extension.c 2013-12-09 19:49:15 +0000
1306+++ midori/midori-extension.c 2014-02-25 19:27:20 +0000
1307@@ -563,6 +563,7 @@
1308 g_assert (midori_extension_activate_gracefully (app, extension_path, "libapps." G_MODULE_SUFFIX, activate));
1309 g_assert (midori_extension_activate_gracefully (app, extension_path, "libdelayed-load." G_MODULE_SUFFIX, activate));
1310 g_assert (midori_extension_activate_gracefully (app, extension_path, "libtabby." G_MODULE_SUFFIX, activate));
1311+ g_assert (midori_extension_activate_gracefully (app, extension_path, "libopen-with." G_MODULE_SUFFIX, activate));
1312 g_assert (midori_extension_activate_gracefully (app, extension_path, "libflummi." G_MODULE_SUFFIX, activate));
1313 }
1314 else
1315@@ -679,6 +680,7 @@
1316 && strcmp (filename, "libapps." G_MODULE_SUFFIX)
1317 && strcmp (filename, "libdelayed-load." G_MODULE_SUFFIX)
1318 && strcmp (filename, "libtabby." G_MODULE_SUFFIX)
1319+ && strcmp (filename, "libopen-with." G_MODULE_SUFFIX)
1320 && strcmp (filename, "libflummi." G_MODULE_SUFFIX))
1321 katze_array_add_item (extensions, extension);
1322
1323
1324=== modified file 'midori/midori-frontend.c'
1325--- midori/midori-frontend.c 2013-11-29 16:29:02 +0000
1326+++ midori/midori-frontend.c 2014-02-25 19:27:20 +0000
1327@@ -239,6 +239,7 @@
1328
1329 /* FIXME need proper stock extension mechanism */
1330 midori_browser_activate_action (browser, "libtransfers." G_MODULE_SUFFIX "=true");
1331+ midori_browser_activate_action (browser, "libopen-with." G_MODULE_SUFFIX "=true");
1332 g_assert (g_module_error () == NULL);
1333
1334 return browser;
1335@@ -301,7 +302,7 @@
1336 gchar* crash_log)
1337 {
1338 GError* error = NULL;
1339- if (!sokoke_show_uri (gtk_widget_get_screen (button), crash_log, 0, &error))
1340+ if (!gtk_show_uri (gtk_widget_get_screen (button), crash_log, 0, &error))
1341 {
1342 sokoke_message_dialog (GTK_MESSAGE_ERROR,
1343 _("Could not run external program."),
1344
1345=== modified file 'midori/midori-preferences.c'
1346--- midori/midori-preferences.c 2013-12-09 11:40:39 +0000
1347+++ midori/midori-preferences.c 2014-02-25 19:27:20 +0000
1348@@ -490,18 +490,6 @@
1349 gtk_button_set_label (GTK_BUTTON (button), _("Open tabs in the background"));
1350 SPANNED_ADD (button);
1351
1352- INDENTED_ADD (gtk_label_new (NULL));
1353- label = gtk_label_new (_("Text Editor"));
1354- gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1355- INDENTED_ADD (label);
1356- entry = katze_property_proxy (settings, "text-editor", "application-text/plain");
1357- SPANNED_ADD (entry);
1358- label = gtk_label_new (_("News Aggregator"));
1359- gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1360- INDENTED_ADD (label);
1361- entry = katze_property_proxy (settings, "news-aggregator", "application-News");
1362- SPANNED_ADD (entry);
1363-
1364 /* Page "Network" */
1365 PAGE_NEW (GTK_STOCK_NETWORK, _("Network"));
1366 FRAME_NEW (NULL);
1367
1368=== modified file 'midori/midori-tab.vala'
1369--- midori/midori-tab.vala 2013-12-09 20:23:03 +0000
1370+++ midori/midori-tab.vala 2014-02-25 19:27:20 +0000
1371@@ -88,6 +88,8 @@
1372 }
1373 }
1374
1375+ /* Since: 0.5.8 */
1376+ public signal bool open_uri (string uri);
1377 public signal void console_message (string message, int line, string source_id);
1378 public signal void attach_inspector (WebKit.WebView inspector_view);
1379 /* Emitted when an open inspector that was previously
1380
1381=== modified file 'midori/midori-view.c'
1382--- midori/midori-view.c 2013-12-09 20:23:03 +0000
1383+++ midori/midori-view.c 2014-02-25 19:27:20 +0000
1384@@ -558,19 +558,6 @@
1385 g_free (new_uri);
1386 return TRUE;
1387 }
1388- else if (sokoke_external_uri (uri))
1389- {
1390- if (sokoke_show_uri (gtk_widget_get_screen (GTK_WIDGET (web_view)),
1391- uri, GDK_CURRENT_TIME, NULL))
1392- {
1393- #ifdef HAVE_WEBKIT2
1394- webkit_policy_decision_ignore (decision);
1395- #else
1396- webkit_web_policy_decision_ignore (decision);
1397- #endif
1398- return TRUE;
1399- }
1400- }
1401 else if (g_str_has_prefix (uri, "data:image/"))
1402 {
1403 /* For security reasons, main content served as data: is limited to images
1404@@ -2109,16 +2096,6 @@
1405 }
1406
1407 static void
1408-midori_web_view_open_in_viewer_cb (GtkAction* action,
1409- gpointer user_data)
1410-{
1411- MidoriView* view = user_data;
1412- gchar* uri = katze_object_get_string (view->hit_test, "image-uri");
1413- midori_view_download_uri (view, MIDORI_DOWNLOAD_OPEN_IN_VIEWER, uri);
1414- g_free (uri);
1415-}
1416-
1417-static void
1418 midori_web_view_menu_video_copy_activate_cb (GtkAction* action,
1419 gpointer user_data)
1420 {
1421@@ -2146,8 +2123,8 @@
1422 MidoriView* view = user_data;
1423 gchar* data = (gchar*)g_object_get_data (G_OBJECT (action), "uri");
1424 gchar* uri = g_strconcat ("mailto:", data, NULL);
1425- sokoke_show_uri (gtk_widget_get_screen (view->web_view),
1426- uri, GDK_CURRENT_TIME, NULL);
1427+ gboolean handled = FALSE;
1428+ g_signal_emit_by_name (view, "open-uri", uri, &handled);
1429 g_free (uri);
1430 }
1431 #endif
1432@@ -2358,8 +2335,6 @@
1433 midori_web_view_menu_image_copy_activate_cb, view);
1434 midori_context_action_add_simple (menu, "SaveImage", _("Save I_mage"), NULL, GTK_STOCK_SAVE,
1435 midori_web_view_menu_image_save_activate_cb, view);
1436- midori_context_action_add_simple (menu, "OpenImageInViewer", _("Open in Image _Viewer"), NULL, GTK_STOCK_OPEN,
1437- midori_web_view_open_in_viewer_cb, view);
1438 }
1439
1440 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA)
1441@@ -4013,12 +3988,19 @@
1442 g_free (exception);
1443 }
1444 }
1445- else if (sokoke_external_uri (uri))
1446- {
1447- sokoke_show_uri (NULL, uri, GDK_CURRENT_TIME, NULL);
1448- }
1449 else
1450 {
1451+ if (sokoke_external_uri (uri))
1452+ {
1453+ gboolean handled = FALSE;
1454+ g_signal_emit_by_name (view, "open-uri", uri, &handled);
1455+ if (handled)
1456+ {
1457+ g_free (temporary_uri);
1458+ return;
1459+ }
1460+ }
1461+
1462 midori_tab_set_uri (MIDORI_TAB (view), uri);
1463 katze_item_set_uri (view->item, midori_tab_get_uri (MIDORI_TAB (view)));
1464 katze_assign (view->title, NULL);
1465
1466=== modified file 'midori/midori.vapi'
1467--- midori/midori.vapi 2014-01-30 21:43:31 +0000
1468+++ midori/midori.vapi 2014-02-25 19:27:20 +0000
1469@@ -118,6 +118,7 @@
1470 public signal void quit ();
1471 public signal void send_notification (string title, string message);
1472 public static void update_history (Katze.Item item, string type, string event);
1473+ public signal void show_preferences (Katze.Preferences preferences);
1474 }
1475
1476 [CCode (cheader_filename = "midori/midori.h")]
1477
1478=== modified file 'midori/sokoke.c'
1479--- midori/sokoke.c 2013-10-28 23:36:29 +0000
1480+++ midori/sokoke.c 2014-02-25 19:27:20 +0000
1481@@ -37,6 +37,7 @@
1482 #ifdef G_OS_WIN32
1483 #include <windows.h>
1484 #include <shlobj.h>
1485+#include <gdk/gdkwin32.h>
1486 #endif
1487
1488 static gchar*
1489@@ -109,39 +110,8 @@
1490 const gchar* detailed_message,
1491 gboolean modal)
1492 {
1493- GtkWidget* dialog = gtk_message_dialog_new (
1494- NULL, 0, message_type, GTK_BUTTONS_OK, "%s", short_message);
1495- gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1496- "%s", detailed_message);
1497- if (modal)
1498- {
1499- gtk_dialog_run (GTK_DIALOG (dialog));
1500- gtk_widget_destroy (dialog);
1501- }
1502- else
1503- {
1504- g_signal_connect_swapped (dialog, "response",
1505- G_CALLBACK (gtk_widget_destroy), dialog);
1506- gtk_widget_show (dialog);
1507- }
1508-
1509-}
1510-
1511-#ifndef G_OS_WIN32
1512-static void
1513-sokoke_open_with_response_cb (GtkWidget* dialog,
1514- gint response,
1515- GtkEntry* entry)
1516-{
1517- if (response == GTK_RESPONSE_ACCEPT)
1518- {
1519- const gchar* command = gtk_entry_get_text (entry);
1520- const gchar* uri = g_object_get_data (G_OBJECT (dialog), "uri");
1521- sokoke_spawn_program (command, FALSE, uri, TRUE, FALSE);
1522- }
1523- gtk_widget_destroy (dialog);
1524-}
1525-#endif
1526+ midori_show_message_dialog (message_type, short_message, detailed_message, modal);
1527+}
1528
1529 GAppInfo*
1530 sokoke_default_for_uri (const gchar* uri,
1531@@ -164,86 +134,6 @@
1532 }
1533
1534 /**
1535- * sokoke_show_uri:
1536- * @screen: a #GdkScreen, or %NULL
1537- * @uri: the URI to show
1538- * @timestamp: the timestamp of the event
1539- * @error: the location of a #GError, or %NULL
1540- *
1541- * Shows the specified URI with an application or xdg-open.
1542- * x-scheme-handler is supported for GLib < 2.28 as of 0.3.3.
1543- *
1544- * Return value: %TRUE on success, %FALSE if an error occurred
1545- **/
1546-gboolean
1547-sokoke_show_uri (GdkScreen* screen,
1548- const gchar* uri,
1549- guint32 timestamp,
1550- GError** error)
1551-{
1552- #ifdef G_OS_WIN32
1553- CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
1554- SHELLEXECUTEINFO info = { sizeof (info) };
1555- info.nShow = SW_SHOWNORMAL;
1556- info.lpFile = uri;
1557-
1558- return ShellExecuteEx (&info);
1559- #else
1560-
1561- GtkWidget* dialog;
1562- GtkWidget* box;
1563- gchar* filename;
1564- gchar* ms;
1565- GtkWidget* entry;
1566-
1567- g_return_val_if_fail (GDK_IS_SCREEN (screen) || !screen, FALSE);
1568- g_return_val_if_fail (uri != NULL, FALSE);
1569- g_return_val_if_fail (!error || !*error, FALSE);
1570-
1571- sokoke_recursive_fork_protection (uri, TRUE);
1572-
1573- /* g_app_info_launch_default_for_uri, gdk_display_get_app_launch_context */
1574- if (gtk_show_uri (screen, uri, timestamp, error))
1575- return TRUE;
1576-
1577- {
1578- gchar* command = g_strconcat ("xdg-open ", uri, NULL);
1579- gboolean result = g_spawn_command_line_async (command, error);
1580- g_free (command);
1581- if (result)
1582- return TRUE;
1583- if (error)
1584- *error = NULL;
1585- }
1586-
1587- dialog = gtk_dialog_new_with_buttons (_("Open with"), NULL, 0,
1588- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1589- GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
1590- box = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
1591- if (g_str_has_prefix (uri, "file:///"))
1592- filename = g_filename_from_uri (uri, NULL, NULL);
1593- else
1594- filename = g_strdup (uri);
1595- ms = g_strdup_printf (_("Choose an application or command to open \"%s\":"),
1596- filename);
1597- gtk_box_pack_start (GTK_BOX (box), gtk_label_new (ms), TRUE, FALSE, 4);
1598- g_free (ms);
1599- entry = gtk_entry_new ();
1600- gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
1601- gtk_box_pack_start (GTK_BOX (box), entry, TRUE, FALSE, 4);
1602- g_signal_connect (dialog, "response",
1603- G_CALLBACK (sokoke_open_with_response_cb), entry);
1604- g_object_set_data_full (G_OBJECT (dialog), "uri",
1605- filename, (GDestroyNotify)g_free);
1606- gtk_widget_show_all (dialog);
1607- gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
1608- gtk_widget_grab_focus (entry);
1609-
1610- return TRUE;
1611- #endif
1612-}
1613-
1614-/**
1615 * sokoke_prepare_command:
1616 * @command: the command, properly quoted
1617 * @argument: any arguments, properly quoted
1618@@ -919,36 +809,6 @@
1619 #endif
1620 }
1621
1622-/**
1623- * sokoke_recursive_fork_protection
1624- * @uri: the URI to check
1625- * @set_uri: if TRUE the URI will be saved
1626- *
1627- * Protects against recursive invokations of the Midori executable
1628- * with the same URI.
1629- *
1630- * As an example, consider having an URI starting with 'tel://'. You
1631- * could attempt to open it with sokoke_show_uri. In turn, 'exo-open'
1632- * might be called. Now quite possibly 'exo-open' is unable to handle
1633- * 'tel://' and might well fall back to 'midori' as default browser.
1634- *
1635- * To protect against this scenario, call this function with the
1636- * URI and %TRUE before calling any external tool.
1637- * #MidoriApp calls sokoke_recursive_fork_protection() with %FALSE
1638- * and bails out if %FALSE is returned.
1639- *
1640- * Return value: %TRUE if @uri is new, %FALSE on recursion
1641- **/
1642-gboolean
1643-sokoke_recursive_fork_protection (const gchar* uri,
1644- gboolean set_uri)
1645-{
1646- static gchar* fork_uri = NULL;
1647- if (set_uri)
1648- katze_assign (fork_uri, g_strdup (uri));
1649- return g_strcmp0 (fork_uri, uri) == 0 ? FALSE : TRUE;
1650-}
1651-
1652 static void
1653 sokoke_widget_clipboard_owner_clear_func (GtkClipboard* clipboard,
1654 gpointer user_data)
1655@@ -1121,4 +981,23 @@
1656 g_free (lnk_path);
1657 g_free (launcher_type);
1658 }
1659+
1660+GdkPixbuf*
1661+sokoke_get_gdk_pixbuf_from_win32_executable (gchar* path)
1662+{
1663+ if (path == NULL)
1664+ return NULL;
1665+
1666+ GdkPixbuf* pixbuf = NULL;
1667+ HICON hIcon = NULL;
1668+ HINSTANCE hInstance = NULL;
1669+ hIcon = ExtractIcon (hInstance, (LPCSTR)path, 0);
1670+ if (hIcon == NULL)
1671+ return NULL;
1672+
1673+ pixbuf = gdk_win32_icon_to_pixbuf_libgtk_only (hIcon, NULL, NULL);
1674+ DestroyIcon (hIcon);
1675+
1676+ return pixbuf;
1677+}
1678 #endif
1679
1680=== modified file 'midori/sokoke.h'
1681--- midori/sokoke.h 2013-08-02 16:40:27 +0000
1682+++ midori/sokoke.h 2014-02-25 19:27:20 +0000
1683@@ -29,19 +29,6 @@
1684 const gchar* detailed_message,
1685 gboolean modal);
1686
1687-gboolean
1688-sokoke_show_uri_with_mime_type (GdkScreen* screen,
1689- const gchar* uri,
1690- const gchar* mime_type,
1691- guint32 timestamp,
1692- GError** error);
1693-
1694-gboolean
1695-sokoke_show_uri (GdkScreen* screen,
1696- const gchar* uri,
1697- guint32 timestamp,
1698- GError** error);
1699-
1700 gchar*
1701 sokoke_prepare_command (const gchar* command,
1702 gboolean quote_command,
1703@@ -112,10 +99,6 @@
1704 gboolean
1705 sokoke_resolve_hostname (const gchar* hostname);
1706
1707-gboolean
1708-sokoke_recursive_fork_protection (const gchar* uri,
1709- gboolean set_uri);
1710-
1711 void
1712 sokoke_widget_copy_clipboard (GtkWidget* widget,
1713 const gchar* text,
1714
1715=== modified file 'po/POTFILES.in'
1716--- po/POTFILES.in 2014-02-20 20:30:42 +0000
1717+++ po/POTFILES.in 2014-02-25 19:27:20 +0000
1718@@ -45,6 +45,7 @@
1719 extensions/delayed-load.vala
1720 extensions/devpet.vala
1721 extensions/external-download-manager.vala
1722+extensions/open-with.vala
1723 extensions/feed-panel/feed-atom.c
1724 extensions/feed-panel/feed-panel.c
1725 extensions/feed-panel/feed-parse.c

Subscribers

People subscribed via source and target branches

to all changes: