297 lines
9.8 KiB
Plaintext
Executable File
297 lines
9.8 KiB
Plaintext
Executable File
#!@PYTHON@
|
|
|
|
# cartridges-search-provider.in
|
|
#
|
|
# Copyright (c) 2014-2016 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
|
|
# Copyright 2023 kramo
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
# Heavily inspired by:
|
|
# https://gitlab.gnome.org/World/lollypop/-/blob/master/search-provider/lollypop-sp.in
|
|
|
|
import json
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gdk", "4.0")
|
|
gi.require_version("GdkPixbuf", "2.0")
|
|
|
|
# pylint: disable=wrong-import-position
|
|
from gi.repository import GdkPixbuf, Gio, GLib
|
|
|
|
from cartridges import shared
|
|
|
|
|
|
class Server:
|
|
def __init__(self, con, path):
|
|
method_outargs = {}
|
|
method_inargs = {}
|
|
for interface in Gio.DBusNodeInfo.new_for_xml(self.__doc__).interfaces:
|
|
for method in interface.methods:
|
|
method_outargs[method.name] = (
|
|
"(" + "".join([arg.signature for arg in method.out_args]) + ")"
|
|
)
|
|
method_inargs[method.name] = tuple(
|
|
arg.signature for arg in method.in_args
|
|
)
|
|
|
|
con.register_object(
|
|
object_path=path,
|
|
interface_info=interface,
|
|
method_call_closure=self.on_method_call,
|
|
)
|
|
|
|
self.method_inargs = method_inargs
|
|
self.method_outargs = method_outargs
|
|
|
|
def on_method_call(
|
|
self,
|
|
_connection,
|
|
_sender,
|
|
_object_path,
|
|
_interface_name,
|
|
method_name,
|
|
parameters,
|
|
invocation,
|
|
):
|
|
args = list(parameters.unpack())
|
|
for i, sig in enumerate(self.method_inargs[method_name]):
|
|
if sig == "h":
|
|
msg = invocation.get_message()
|
|
fd_list = msg.get_unix_fd_list()
|
|
args[i] = fd_list.get(args[i])
|
|
|
|
try:
|
|
result = getattr(self, method_name)(*args)
|
|
|
|
# out_args is atleast (signature1).
|
|
# We therefore always wrap the result as a tuple.
|
|
# Refer to https://bugzilla.gnome.org/show_bug.cgi?id=765603
|
|
result = (result,)
|
|
|
|
out_args = self.method_outargs[method_name]
|
|
if out_args != "()":
|
|
variant = GLib.Variant(out_args, result)
|
|
invocation.return_value(variant)
|
|
else:
|
|
invocation.return_value(None)
|
|
except Exception: # pylint: disable=broad-exception-caught
|
|
pass
|
|
|
|
|
|
class SearchCartridgesService(Server, Gio.Application):
|
|
"""
|
|
<!DOCTYPE node PUBLIC
|
|
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
|
|
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
|
|
<node>
|
|
<interface name="org.gnome.Shell.SearchProvider2">
|
|
|
|
<method name="GetInitialResultSet">
|
|
<arg type="as" name="terms" direction="in" />
|
|
<arg type="as" name="results" direction="out" />
|
|
</method>
|
|
|
|
<method name="GetSubsearchResultSet">
|
|
<arg type="as" name="previous_results" direction="in" />
|
|
<arg type="as" name="terms" direction="in" />
|
|
<arg type="as" name="results" direction="out" />
|
|
</method>
|
|
|
|
<method name="GetResultMetas">
|
|
<arg type="as" name="identifiers" direction="in" />
|
|
<arg type="aa{sv}" name="metas" direction="out" />
|
|
</method>
|
|
|
|
<method name="ActivateResult">
|
|
<arg type="s" name="identifier" direction="in" />
|
|
<arg type="as" name="terms" direction="in" />
|
|
<arg type="u" name="timestamp" direction="in" />
|
|
</method>
|
|
|
|
<method name="LaunchSearch">
|
|
<arg type="as" name="terms" direction="in" />
|
|
<arg type="u" name="timestamp" direction="in" />
|
|
</method>
|
|
|
|
</interface>
|
|
</node>
|
|
"""
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
__SEARCH_BUS = "org.gnome.Shell.SearchProvider2"
|
|
__PATH_BUS = "@PREFIX@/SearchProvider"
|
|
|
|
def __init__(self):
|
|
Gio.Application.__init__(
|
|
self,
|
|
application_id="@APP_ID@.SearchProvider",
|
|
flags=Gio.ApplicationFlags.IS_SERVICE,
|
|
inactivity_timeout=10000,
|
|
)
|
|
|
|
self.games = {}
|
|
self.load_games_from_disk()
|
|
|
|
self.__bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
|
Gio.bus_own_name_on_connection(
|
|
self.__bus, self.__SEARCH_BUS, Gio.BusNameOwnerFlags.NONE, None, None
|
|
)
|
|
Server.__init__(self, self.__bus, self.__PATH_BUS)
|
|
|
|
def load_games_from_disk(self):
|
|
if not shared.games_dir.is_dir():
|
|
return
|
|
|
|
for game_file in shared.games_dir.iterdir():
|
|
try:
|
|
data = json.load(game_file.open())
|
|
except (OSError, json.decoder.JSONDecodeError):
|
|
continue
|
|
|
|
try:
|
|
# Use .get for compatibility with pre-2.0 games
|
|
if any(
|
|
{data.get("hidden"), data.get("blacklisted"), data.get("removed")}
|
|
):
|
|
print(f"Skipped {game_file.name}")
|
|
continue
|
|
|
|
self.games[data["game_id"]] = (data["name"], data["developer"])
|
|
except KeyError:
|
|
continue
|
|
|
|
def ActivateResult(self, game_id, _array, _utime):
|
|
argv = ["cartridges", "--launch", game_id]
|
|
(pid, _stdin, _stdout, _stderr) = GLib.spawn_async(
|
|
argv,
|
|
flags=GLib.SpawnFlags.SEARCH_PATH,
|
|
standard_input=False,
|
|
standard_output=False,
|
|
standard_error=False,
|
|
)
|
|
GLib.spawn_close_pid(pid)
|
|
|
|
def GetInitialResultSet(self, terms):
|
|
return self.__search(terms)
|
|
|
|
def GetResultMetas(self, game_ids):
|
|
results = []
|
|
|
|
try:
|
|
for game_id in game_ids:
|
|
empty_pixbuf = GdkPixbuf.Pixbuf.new(
|
|
GdkPixbuf.Colorspace.RGB, True, 8, 32, 32
|
|
)
|
|
pixbuf = None
|
|
if (path := shared.covers_dir / (game_id + ".tiff")).is_file():
|
|
try:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
|
str(path), -1, 32, True
|
|
)
|
|
except GLib.Error as e:
|
|
print(e)
|
|
continue
|
|
elif (path := shared.covers_dir / (game_id + ".gif")).is_file():
|
|
try:
|
|
pixbuf = GdkPixbuf.PixbufAnimation.new_from_file(
|
|
str(path)
|
|
).get_static_image()
|
|
except GLib.Error as e:
|
|
print(e)
|
|
continue
|
|
d = {
|
|
"id": GLib.Variant("s", game_id),
|
|
"name": GLib.Variant("s", self.games[game_id][0]),
|
|
}
|
|
if pixbuf:
|
|
pixbuf.composite(
|
|
empty_pixbuf,
|
|
6,
|
|
0,
|
|
21,
|
|
32,
|
|
6,
|
|
0,
|
|
21 / pixbuf.get_width(),
|
|
32 / pixbuf.get_height(),
|
|
GdkPixbuf.InterpType.NEAREST,
|
|
255,
|
|
)
|
|
|
|
d["icon-data"] = GLib.Variant(
|
|
"(iiibiiay)",
|
|
[
|
|
empty_pixbuf.get_width(),
|
|
empty_pixbuf.get_height(),
|
|
empty_pixbuf.get_rowstride(),
|
|
empty_pixbuf.get_has_alpha(),
|
|
empty_pixbuf.get_bits_per_sample(),
|
|
empty_pixbuf.get_n_channels(),
|
|
empty_pixbuf.read_pixel_bytes().get_data(),
|
|
],
|
|
)
|
|
if self.games[game_id][1]:
|
|
d["description"] = GLib.Variant(
|
|
"s", GLib.markup_escape_text(self.games[game_id][1])
|
|
)
|
|
results.append(d)
|
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
print("SearchCartridgesService::GetResultMetas():", e)
|
|
return []
|
|
return results
|
|
|
|
def GetSubsearchResultSet(self, _previous_results, new_terms):
|
|
return self.__search(new_terms)
|
|
|
|
def LaunchSearch(self, terms, _utime):
|
|
search = " ".join(terms)
|
|
argv = ["cartridges", "--search", search]
|
|
(pid, _stdin, _stdout, _stderr) = GLib.spawn_async(
|
|
argv,
|
|
flags=GLib.SpawnFlags.SEARCH_PATH,
|
|
standard_input=False,
|
|
standard_output=False,
|
|
standard_error=False,
|
|
)
|
|
GLib.spawn_close_pid(pid)
|
|
|
|
def __search(self, terms):
|
|
game_ids = []
|
|
search = " ".join(terms).lower()
|
|
try:
|
|
for game_id, data in self.games.items():
|
|
if search in data[0].lower():
|
|
game_ids.append(game_id)
|
|
continue
|
|
if data[1] and search in data[1].lower():
|
|
game_ids.append(game_id)
|
|
continue
|
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
print("SearchCartridgesService::__search():", e)
|
|
return game_ids
|
|
|
|
|
|
def main():
|
|
service = SearchCartridgesService()
|
|
service.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|