2
* Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
* Authored by Michal Hruby <michal.mhr@gmail.com>
25
private const string GOOGLE_SEARCH_XML = """
26
<?xml version="1.0" encoding="UTF-8"?>
27
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
28
<ShortName>Google</ShortName>
29
<Description>Search the web using google.com</Description>
30
<Url type="text/html" method="get" template="http://www.google.com/search?q={searchTerms}&hl={language}"/>
31
<Url type="application/x-suggestions+json" template="http://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl=en&q={searchTerms}"/>
33
<Developer>Synapse dev team</Developer>
34
<InputEncoding>UTF-8</InputEncoding>
35
</OpenSearchDescription>
37
private const string GOOGLE_MAPS_XML = """
38
<?xml version="1.0" encoding="UTF-8"?>
39
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
40
<ShortName>Google Maps</ShortName>
41
<Description>Search using Google Maps</Description>
42
<Url type="text/html" method="get" template="http://maps.google.com/maps?q={searchTerms}&hl={language}"/>
44
<Developer>Synapse dev team</Developer>
45
<InputEncoding>UTF-8</InputEncoding>
46
</OpenSearchDescription>
49
public class OpenSearchPlugin: Object, Activatable, ActionProvider
51
public bool enabled { get; set; default = true; }
53
public void activate ()
55
actions = new Gee.ArrayList<SearchAction> ();
59
public void deactivate ()
64
private class OpenSearchParser: Object
66
const MarkupParser parser =
68
start, end, text, null, null
71
MarkupParseContext context;
72
bool is_opensearch = false;
73
bool in_name_elem = false;
74
bool in_description_elem = false;
76
bool has_name = false;
77
bool has_desc = false;
80
public string short_name { get; set; }
81
public string description { get; set; }
82
public string query_url { get; set; }
83
public string suggestion_url { get; set; }
87
context = new MarkupParseContext (parser, 0, this, null);
90
public bool parse (string content) throws MarkupError
92
return context.parse (content, -1);
95
public bool has_valid_result ()
97
return is_opensearch && has_name && has_desc && has_url;
100
private void process_url (string[] attrs, string[] vals)
102
uint len = strv_length (attrs);
103
bool main_type = false;
104
bool suggestion_type = false;
106
for (uint i=0; i<len; i++)
111
if (vals[i] == "text/html") main_type = true;
112
else if (vals[i] == "application/x-suggestions+json") suggestion_type = true;
120
else if (suggestion_type) suggestion_url = vals[i];
127
private void start (MarkupParseContext ctx, string name,
128
string[] attr_names, string[] attr_vals) throws MarkupError
132
case "SearchPlugin": //try to support Mozilla OpenSearch xmls
133
if ("xmlns:os" in attr_names)
134
is_opensearch = true;
136
case "OpenSearchDescription": is_opensearch = true; break;
138
case "ShortName": has_name = true; in_name_elem = true; break;
139
case "os:Description":
140
case "Description": has_desc = true; in_description_elem = true; break;
142
case "Url": process_url (attr_names, attr_vals); break;
147
private void end (MarkupParseContext ctx, string name) throws MarkupError
152
case "ShortName": in_name_elem = false; break;
153
case "os:Description":
154
case "Description": in_description_elem = false; break;
159
private void text (MarkupParseContext ctx, string text, size_t text_len) throws MarkupError
161
if (in_name_elem) short_name = text;
162
else if (in_description_elem) description = text;
166
private class SearchAction: Object, Match
168
// from Match interface
169
public string title { get; construct set; }
170
public string description { get; set; }
171
public string icon_name { get; construct set; }
172
public bool has_thumbnail { get; construct set; }
173
public string thumbnail_path { get; construct set; }
174
public MatchType match_type { get; construct set; }
176
public int default_relevancy { get; set; default = Match.Score.INCREMENT_MINOR; }
177
public string query_template { get; construct set; }
179
public void execute (Match? match)
183
string what = (match is TextMatch) ?
184
(match as TextMatch).get_text () : match.title;
185
AppInfo.launch_default_for_uri (get_query_url (what),
186
new Gdk.AppLaunchContext ());
190
warning ("%s", err.message);
194
protected string get_query_url (string query)
197
result = query_template.replace ("{searchTerms}",
198
Uri.escape_string (query, "", false));
199
result = result.replace ("{language}", get_lang ());
200
// FIXME: remove all other "{codes}"
205
protected string get_lang ()
207
string? result = null;
208
foreach (unowned string lang in Intl.get_language_names ())
210
if (lang.length == 2)
217
return result ?? "en";
220
public SearchAction (string name, string description, string url)
223
description: description,
225
has_thumbnail: false, icon_name: "applications-internet");
229
private class Config: ConfigObject
231
public bool use_internal { get; set; default = true; }
233
private string[] _search_engines;
234
public string[] search_engines
238
return _search_engines;
242
_search_engines = value;
247
static void register_plugin ()
249
DataSink.PluginRegistry.get_default ().register_plugin (
250
typeof (OpenSearchPlugin),
252
_ ("Search the web."),
253
"applications-internet",
262
// keep in sync with the internal XMLs!
263
unowned string dummy;
264
dummy = N_ ("Google");
265
dummy = N_ ("Search the web using google.com");
266
dummy = N_ ("Google Maps");
267
dummy = N_ ("Search using Google Maps");
270
private Gee.List<SearchAction> actions;
271
private Config config;
275
var cs = ConfigService.get_default ();
276
config = (Config) cs.get_config ("plugins", "opensearch", typeof (Config));
279
private async void load_xmls ()
281
OpenSearchParser parser;
282
if (config.use_internal)
284
Gee.List<unowned string> internals = new Gee.ArrayList<unowned string> ();
285
internals.add (GOOGLE_SEARCH_XML);
286
internals.add (GOOGLE_MAPS_XML);
287
foreach (unowned string s in internals)
289
parser = new OpenSearchParser ();
293
if (parser.has_valid_result ())
295
actions.add (new SearchAction (_ (parser.short_name),
296
_ (parser.description),
300
catch (Error no_way) { /* this really shouldn't happen */ }
304
string[] xmls = config.search_engines;
305
foreach (unowned string xml in xmls)
307
string xml_path = xml;
308
if (xml_path.has_prefix ("~"))
310
xml_path = Environment.get_home_dir () +
311
xml_path.substring (1);
313
var f = File.new_for_path (xml_path);
316
uint8[] file_contents;
319
yield f.load_contents_async (null, out file_contents, null);
320
contents = (string) file_contents;
321
len = file_contents.length;
323
parser = new OpenSearchParser ();
324
parser.parse (contents);
325
if (parser.has_valid_result ())
327
actions.add (new SearchAction (parser.short_name,
331
else warning ("Unable to parse search plugin [%s]", xml);
335
warning ("Unable to load search plugin [%s]: %s", xml, err.message);
340
public bool handles_unknown ()
345
public ResultSet? find_for_match (ref Query query, Match match)
347
if (match.match_type != MatchType.UNKNOWN &&
348
match.match_type != MatchType.TEXT)
352
var my_flags = QueryFlags.ACTIONS | QueryFlags.INTERNET;
353
if ((query.query_type & my_flags) == 0) return null;
355
bool query_empty = query.query_string == "";
356
var results = new ResultSet ();
360
foreach (var action in actions)
362
results.add (action, action.default_relevancy);
367
var matchers = Query.get_matchers_for_query (query.query_string, 0,
368
RegexCompileFlags.CASELESS);
369
foreach (var item in actions)
371
foreach (var matcher in matchers)
373
if (matcher.key.match (item.title))
375
results.add (item, matcher.value);