From 7b44b277d2aac312c7783159d6e21e536f01f63f Mon Sep 17 00:00:00 2001 From: JrFernando Date: Sun, 9 Nov 2025 15:52:14 -0300 Subject: [PATCH 1/5] feat: Add WebSearchPlugin with support for multiple search engines. --- plugins/WebSearchPlugin/Plugin.vala | 78 +++++++++++++++++++++++++++++ plugins/WebSearchPlugin/meson.build | 7 +++ plugins/meson.build | 1 + 3 files changed, 86 insertions(+) create mode 100644 plugins/WebSearchPlugin/Plugin.vala create mode 100644 plugins/WebSearchPlugin/meson.build diff --git a/plugins/WebSearchPlugin/Plugin.vala b/plugins/WebSearchPlugin/Plugin.vala new file mode 100644 index 0000000..d0e286c --- /dev/null +++ b/plugins/WebSearchPlugin/Plugin.vala @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2025 Fernando Júnior Gomes da Silva + */ + +public class Detective.WebSearchMatch : Match { + public string search_engine { get; construct; } + public string search_query { get; construct; } + public string url { get; construct; } + + public WebSearchMatch (string search_engine, string search_query, string url) { + var icon = new ThemedIcon ("system-search"); + + Object ( + relevancy: Relevancy.LOWEST, + search_engine: search_engine, + search_query: search_query, + url: url, + title: @"Search \"$search_query\" on $search_engine", + description: url, + icon: icon + ); + } + + public override async void activate () throws Error { + Process.spawn_command_line_async (@"flatpak-spawn --host xdg-open \"$url\""); + } +} + +public class Detective.WebSearchProvider : SearchProvider { + private const SearchEngine[] SEARCH_ENGINES = { + { "Google", "https://www.google.com/search?q=%s" }, + { "DuckDuckGo", "https://duckduckgo.com/?q=%s" }, + { "Bing", "https://www.bing.com/search?q=%s" }, + }; + + private struct SearchEngine { + string name; + string url_template; + } + + private ListStore matches_internal; + + construct { + matches_internal = new ListStore (typeof (Match)); + + var match_types = new ListStore (typeof (MatchType)); + match_types.append (new MatchType (_("Web Search"), matches_internal)); + this.match_types = match_types; + } + + public override void search (Query query) { + matches_internal.remove_all (); + + // Só mostra resultados se houver pelo menos 2 caracteres + if (query.search_term.length < 2) { + return; + } + + // Codifica a query para URL + string encoded_query = Uri.escape_string (query.search_term, null, true); + + // Cria um match para cada search engine + foreach (var engine in SEARCH_ENGINES) { + string url = engine.url_template.printf (encoded_query); + var match = new WebSearchMatch (engine.name, query.search_term, url); + matches_internal.append (match); + } + } + + public override void clear () { + matches_internal.remove_all (); + } +} + +public Detective.WebSearchProvider get_provider () { + return new Detective.WebSearchProvider (); +} diff --git a/plugins/WebSearchPlugin/meson.build b/plugins/WebSearchPlugin/meson.build new file mode 100644 index 0000000..d0aa2e3 --- /dev/null +++ b/plugins/WebSearchPlugin/meson.build @@ -0,0 +1,7 @@ +shared_library ( + 'WebSearchPlugin', + 'Plugin.vala', + dependencies: lib_dep, + install: true, + install_dir: plugin_dir +) diff --git a/plugins/meson.build b/plugins/meson.build index 63a78ee..0b21041 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -2,3 +2,4 @@ subdir('AppsPlugin') subdir('CalculatorPlugin') subdir('FilePlugin') subdir('LocationPlugin') +subdir('WebSearchPlugin') From 69ea1597eb08e391ad1bc55394b8e02e2dc796a2 Mon Sep 17 00:00:00 2001 From: JrFernando Date: Sun, 9 Nov 2025 16:35:40 -0300 Subject: [PATCH 2/5] feat: Adds WebSearchPlugin text to i18n files --- plugins/WebSearchPlugin/Plugin.vala | 5 ++++- po/POTFILES | 1 + po/io.github.leolost2605.detective.pot | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/WebSearchPlugin/Plugin.vala b/plugins/WebSearchPlugin/Plugin.vala index d0e286c..a77fb53 100644 --- a/plugins/WebSearchPlugin/Plugin.vala +++ b/plugins/WebSearchPlugin/Plugin.vala @@ -11,12 +11,15 @@ public class Detective.WebSearchMatch : Match { public WebSearchMatch (string search_engine, string search_query, string url) { var icon = new ThemedIcon ("system-search"); + // Translators: %s is the search query, %s is the search engine name + string match_title = _("Search \"%s\" on %s").printf (search_query, search_engine); + Object ( relevancy: Relevancy.LOWEST, search_engine: search_engine, search_query: search_query, url: url, - title: @"Search \"$search_query\" on $search_engine", + title: match_title, description: url, icon: icon ); diff --git a/po/POTFILES b/po/POTFILES index e274c4e..180d632 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -4,3 +4,4 @@ src/Application.vala src/MatchRow.vala src/SearchWindow.vala src/ShellKeyGrabber.vala +plugins/WebSearchPlugin/Plugin.vala diff --git a/po/io.github.leolost2605.detective.pot b/po/io.github.leolost2605.detective.pot index e61f45d..304fe4b 100644 --- a/po/io.github.leolost2605.detective.pot +++ b/po/io.github.leolost2605.detective.pot @@ -44,3 +44,13 @@ msgid "" "Detective needs to run in the background to be easily invokable via keyboard " "shortcuts." msgstr "" + +#. Translators: %s is the search query, %s is the search engine name +#: plugins/WebSearchPlugin/Plugin.vala:15 +#, c-format +msgid "Search \"%s\" on %s" +msgstr "" + +#: plugins/WebSearchPlugin/Plugin.vala:47 +msgid "Web Search" +msgstr "" From 6e0d66269a6a92c02f45a98ebcf758c15678f1a0 Mon Sep 17 00:00:00 2001 From: JrFernando Date: Sun, 9 Nov 2025 20:23:27 -0300 Subject: [PATCH 3/5] feat: Added support for opening URLs directly in the browser. --- plugins/WebSearchPlugin/Plugin.vala | 79 +++++++++++++++++++++++++- po/io.github.leolost2605.detective.pot | 12 +++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/plugins/WebSearchPlugin/Plugin.vala b/plugins/WebSearchPlugin/Plugin.vala index a77fb53..c5626b4 100644 --- a/plugins/WebSearchPlugin/Plugin.vala +++ b/plugins/WebSearchPlugin/Plugin.vala @@ -30,6 +30,29 @@ public class Detective.WebSearchMatch : Match { } } +public class Detective.OpenUrlMatch : Match { + public string url { get; construct; } + + public OpenUrlMatch (string url) { + var icon = new ThemedIcon ("applications-internet"); + + // Translators: %s is the URL to open + string match_title = _("Open %s").printf (url); + + Object ( + relevancy: Relevancy.HIGHEST, + url: url, + title: match_title, + description: _("Open URL in browser"), + icon: icon + ); + } + + public override async void activate () throws Error { + Process.spawn_command_line_async (@"flatpak-spawn --host xdg-open \"$url\""); + } +} + public class Detective.WebSearchProvider : SearchProvider { private const SearchEngine[] SEARCH_ENGINES = { { "Google", "https://www.google.com/search?q=%s" }, @@ -52,18 +75,68 @@ public class Detective.WebSearchProvider : SearchProvider { this.match_types = match_types; } + private bool is_url (string text) { + if (text.has_prefix ("http://") || text.has_prefix ("https://")) { + return true; + } + + if (text.contains (" ")) { + return false; + } + + if (text.has_prefix ("www.")) { + return true; + } + + // Check generic domain pattern: contains at least one dot + // followed by 2-6 alphabetic characters (typical TLD: .com, .br, .museum) + if (text.contains (".")) { + var parts = text.split ("."); + if (parts.length >= 2) { + // Get the last part (TLD) + string tld = parts[parts.length - 1].down (); + + // TLDs usually have 2-6 characters and are alphabetic + if (tld.length >= 2 && tld.length <= 6) { + // Check if it's alphabetic (a-z) + for (int i = 0; i < tld.length; i++) { + unichar c = tld.get_char (i); + if (c < 'a' || c > 'z') { + return false; + } + } + return true; + } + } + } + + return false; + } + + private string normalize_url (string text) { + if (text.has_prefix ("http://") || text.has_prefix ("https://")) { + return text; + } + + return "https://" + text; + } + public override void search (Query query) { matches_internal.remove_all (); - // Só mostra resultados se houver pelo menos 2 caracteres if (query.search_term.length < 2) { return; } - // Codifica a query para URL + if (is_url (query.search_term)) { + string normalized_url = normalize_url (query.search_term); + var open_url_match = new OpenUrlMatch (normalized_url); + matches_internal.append (open_url_match); + return; + } + string encoded_query = Uri.escape_string (query.search_term, null, true); - // Cria um match para cada search engine foreach (var engine in SEARCH_ENGINES) { string url = engine.url_template.printf (encoded_query); var match = new WebSearchMatch (engine.name, query.search_term, url); diff --git a/po/io.github.leolost2605.detective.pot b/po/io.github.leolost2605.detective.pot index 304fe4b..a293740 100644 --- a/po/io.github.leolost2605.detective.pot +++ b/po/io.github.leolost2605.detective.pot @@ -51,6 +51,16 @@ msgstr "" msgid "Search \"%s\" on %s" msgstr "" -#: plugins/WebSearchPlugin/Plugin.vala:47 +#. Translators: %s is the URL to open +#: plugins/WebSearchPlugin/Plugin.vala:40 +#, c-format +msgid "Open %s" +msgstr "" + +#: plugins/WebSearchPlugin/Plugin.vala:46 +msgid "Open URL in browser" +msgstr "" + +#: plugins/WebSearchPlugin/Plugin.vala:74 msgid "Web Search" msgstr "" From 3309bc28f3b5c5e3cec22d6c615d7c637e71b35d Mon Sep 17 00:00:00 2001 From: JrFernando Date: Mon, 10 Nov 2025 20:18:59 -0300 Subject: [PATCH 4/5] feat: Replace the activation method for opening URLs using Gtk.UriLauncher as requested in the review. --- plugins/WebSearchPlugin/Plugin.vala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/WebSearchPlugin/Plugin.vala b/plugins/WebSearchPlugin/Plugin.vala index c5626b4..6343f14 100644 --- a/plugins/WebSearchPlugin/Plugin.vala +++ b/plugins/WebSearchPlugin/Plugin.vala @@ -26,7 +26,8 @@ public class Detective.WebSearchMatch : Match { } public override async void activate () throws Error { - Process.spawn_command_line_async (@"flatpak-spawn --host xdg-open \"$url\""); + var launcher = new Gtk.UriLauncher (url); + yield launcher.launch (null, null); } } @@ -49,7 +50,8 @@ public class Detective.OpenUrlMatch : Match { } public override async void activate () throws Error { - Process.spawn_command_line_async (@"flatpak-spawn --host xdg-open \"$url\""); + var launcher = new Gtk.UriLauncher (url); + yield launcher.launch (null, null); } } From 2c0143d3eb560ca4998606250ff89d57619a3f06 Mon Sep 17 00:00:00 2001 From: JrFernando Date: Mon, 10 Nov 2025 20:56:55 -0300 Subject: [PATCH 5/5] feat: Update translations for the web search plugin --- po/pt_BR.po | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index 673e4de..5415904 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: io.github.leolost2605.detective\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-09 15:00-0300\n" -"PO-Revision-Date: 2025-11-09 15:00-0300\n" +"POT-Creation-Date: 2024-06-26 22:20+0200\n" +"PO-Revision-Date: 2025-11-10 02:05-0300\n" "Last-Translator: Fernando Júnior Gomes da Silva \n" "Language-Team: Brazilian Portuguese\n" "Language: pt_BR\n" @@ -69,4 +69,24 @@ msgstr "Arquivos" #: plugins/LocationPlugin/Plugin.vala:33 msgid "Places" -msgstr "Locais" \ No newline at end of file +msgstr "Locais" + +#. Translators: %s is the search query, %s is the search engine name +#: plugins/WebSearchPlugin/Plugin.vala:15 +#, c-format +msgid "Search \"%s\" on %s" +msgstr "Pesquisar \"%s\" no %s" + +#. Translators: %s is the URL to open +#: plugins/WebSearchPlugin/Plugin.vala:40 +#, c-format +msgid "Open %s" +msgstr "Abrir %s" + +#: plugins/WebSearchPlugin/Plugin.vala:46 +msgid "Open URL in browser" +msgstr "Abrir URL no navegador" + +#: plugins/WebSearchPlugin/Plugin.vala:74 +msgid "Web Search" +msgstr "Busca na Web" \ No newline at end of file