3434LAUNCHER_CATEGORIES = ("Education" , "X-Kolibri-Channel" )
3535
3636KOLIBRI_APP_ID = os .environ .get ("FLATPAK_ID" , "org.learningequality.Kolibri" )
37+ KOLIBRI_SEARCH_PROVIDER_BUS_NAME = KOLIBRI_APP_ID + ".SearchProvider"
38+ KOLIBRI_SEARCH_PROVIDER_OBJECT_PATH = "/" + KOLIBRI_SEARCH_PROVIDER_BUS_NAME .replace ("." , "/" )
3739
3840CHANNEL_DESKTOP_ID_FORMAT = KOLIBRI_APP_ID + ".channel_{}"
41+ CHANNEL_SEARCH_PROVIDER_OBJECT_PATH_FORMAT = (
42+ KOLIBRI_SEARCH_PROVIDER_OBJECT_PATH + "/channel_{}"
43+ )
3944
4045
4146def update_channel_launchers (force = False ):
@@ -83,6 +88,12 @@ class ChannelLaunchersContext(object):
8388 def applications_dir (self ):
8489 return os .path .join (get_content_share_dir_path (), "applications" )
8590
91+ @property
92+ def search_providers_dir (self ):
93+ return os .path .join (
94+ get_content_share_dir_path (), "gnome-shell" , "search-providers"
95+ )
96+
8697 @property
8798 def icon_theme_dir (self ):
8899 return os .path .join (get_content_share_dir_path (), "icons" , "hicolor" )
@@ -114,8 +125,17 @@ def desktop_file_path(self):
114125 @property
115126 def desktop_file_name (self ):
116127 return "{}.desktop" .format (self .desktop_id )
128+
129+ @property
130+ def search_provider_file_path (self ):
131+ return os .path .join (
132+ self .__context .search_providers_dir , self .search_provider_file_name
117133 )
118134
135+ @property
136+ def search_provider_file_name (self ):
137+ return "{}.ini" .format (self .desktop_id )
138+
119139 def get_icon_file_path (self , file_name , size = "256x256" ):
120140 return os .path .join (self .__context .icon_theme_dir , size , "apps" , file_name )
121141
@@ -127,7 +147,10 @@ def compare(self, other):
127147 return (self_channel - other_channel ) or (self_format - other_format )
128148
129149 def is_same_channel (self , other ):
130- return self .channel_id == other .channel_id
150+ return (
151+ self .desktop_file_path == other .desktop_file_path
152+ and self .channel_id == other .channel_id
153+ )
131154
132155 def save (self ):
133156 try :
@@ -145,8 +168,18 @@ def save(self):
145168 "Error writing desktop file for channel %s: %s" , self .channel_id , error
146169 )
147170
171+ try :
172+ self .write_search_provider ()
173+ except Exception as error :
174+ logger .warning (
175+ "Error writing search provider for channel %s: %s" ,
176+ self .channel_id ,
177+ error ,
178+ )
179+
148180 def delete (self ):
149181 self .delete_desktop_file ()
182+ self .delete_search_provider ()
150183 self .delete_channel_icon ()
151184
152185 def write_desktop_file (self , icon_name ):
@@ -155,6 +188,12 @@ def write_desktop_file(self, icon_name):
155188 def delete_desktop_file (self ):
156189 os .remove (self .desktop_file_path )
157190
191+ def write_search_provider (self ):
192+ raise NotImplementedError ()
193+
194+ def delete_search_provider (self ):
195+ os .remove (self .search_provider_file_path )
196+
158197 def write_channel_icon (self ):
159198 raise NotImplementedError ()
160199
@@ -163,7 +202,7 @@ def delete_channel_icon(self):
163202
164203
165204class ChannelLauncher_FromDatabase (ChannelLauncher ):
166- FORMAT_VERSION = 5
205+ FORMAT_VERSION = 6
167206
168207 def __init__ (self , context , channelmetadata ):
169208 super ().__init__ (context )
@@ -202,7 +241,9 @@ def write_desktop_file(self, icon_name):
202241 desktop_file_parser .set (
203242 "Desktop Entry" ,
204243 "Exec" ,
205- f"gio open kolibri-channel:{ self .channel_id } " ,
244+ "gio open x-kolibri-dispatch://{channel_id}" .format (
245+ channel_id = self .channel_id
246+ ),
206247 )
207248 desktop_file_parser .set ("Desktop Entry" , "X-Endless-LaunchMaximized" , "True" )
208249 desktop_file_parser .set (
@@ -222,6 +263,29 @@ def write_desktop_file(self, icon_name):
222263 with open (self .desktop_file_path , "w" ) as desktop_entry_file :
223264 desktop_file_parser .write (desktop_entry_file , space_around_delimiters = False )
224265
266+ def write_search_provider (self ):
267+ search_provider_file_parser = configparser .ConfigParser ()
268+ search_provider_file_parser .optionxform = str
269+ search_provider_file_parser .add_section ("Shell Search Provider" )
270+ search_provider_file_parser .set (
271+ "Shell Search Provider" , "DesktopId" , self .desktop_file_name
272+ )
273+ search_provider_file_parser .set (
274+ "Shell Search Provider" , "BusName" , KOLIBRI_SEARCH_PROVIDER_BUS_NAME
275+ )
276+ search_provider_file_parser .set (
277+ "Shell Search Provider" ,
278+ "ObjectPath" ,
279+ CHANNEL_SEARCH_PROVIDER_OBJECT_PATH_FORMAT .format (self .channel_id ),
280+ )
281+ search_provider_file_parser .set ("Shell Search Provider" , "Version" , "2" )
282+
283+ ensure_dir (self .search_provider_file_path )
284+ with open (self .search_provider_file_path , "w" ) as search_provider_file :
285+ search_provider_file_parser .write (
286+ search_provider_file , space_around_delimiters = False
287+ )
288+
225289 def write_channel_icon (self ):
226290 if not self .__channel_icon :
227291 return
@@ -289,7 +353,7 @@ def delete_channel_icon(self):
289353 icon_file_path = self .get_icon_file_path (icon_name + ".png" )
290354
291355 if icon_file_path and os .path .isfile (icon_file_path ):
292- try_remove (self . icon_file_path )
356+ try_remove (icon_file_path )
293357
294358
295359class ChannelIcon (object ):
0 commit comments