1 /***************************************************************************
2  *   Copyright (C) 2010~2010 by CSSlayer                                   *
3  *   wengxt@gmail.com                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
19  ***************************************************************************/
20 
21 /**
22  * @file addon.c
23  * Addon Support for fcitx
24  * @author CSSlayer wengxt@gmail.com
25  */
26 
27 #include <sys/stat.h>
28 #include <libintl.h>
29 #include <dlfcn.h>
30 
31 #include "fcitx/fcitx.h"
32 #include "addon.h"
33 #include "fcitx-config/xdg.h"
34 #include "fcitx-utils/log.h"
35 #include "fcitx-utils/utils.h"
36 #include "instance.h"
37 #include "instance-internal.h"
38 #include "addon-internal.h"
39 
40 CONFIG_BINDING_BEGIN(FcitxAddon)
41 CONFIG_BINDING_REGISTER("Addon", "Name", name)
42 CONFIG_BINDING_REGISTER("Addon", "GeneralName", generalname)
43 CONFIG_BINDING_REGISTER("Addon", "Comment", comment)
44 CONFIG_BINDING_REGISTER("Addon", "Category", category)
45 CONFIG_BINDING_REGISTER("Addon", "Enabled", bEnabled)
46 CONFIG_BINDING_REGISTER("Addon", "Library", library)
47 CONFIG_BINDING_REGISTER("Addon", "Type", type)
48 CONFIG_BINDING_REGISTER("Addon", "Dependency", depend)
49 CONFIG_BINDING_REGISTER("Addon", "Priority", priority)
50 CONFIG_BINDING_REGISTER("Addon", "SubConfig", subconfig)
51 CONFIG_BINDING_REGISTER("Addon", "IMRegisterMethod", registerMethod)
52 CONFIG_BINDING_REGISTER("Addon", "IMRegisterArgument", registerArgument)
53 CONFIG_BINDING_REGISTER("Addon", "UIFallback", uifallback)
54 CONFIG_BINDING_REGISTER("Addon", "Advance", advance)
55 CONFIG_BINDING_REGISTER("Addon", "LoadLocal", loadLocal)
56 CONFIG_BINDING_END()
57 
58 static const UT_icd addon_icd = {
59     sizeof(FcitxAddon), NULL , NULL, FcitxAddonFree
60 };
AddonPriorityCmp(const void * a,const void * b)61 static int AddonPriorityCmp(const void* a, const void* b)
62 {
63     FcitxAddon *aa = (FcitxAddon*)a, *ab = (FcitxAddon*)b;
64     return aa->priority - ab->priority;
65 }
66 
67 FCITX_EXPORT_API
FcitxAddonsInit(UT_array * addons)68 void FcitxAddonsInit(UT_array* addons)
69 {
70     utarray_init(addons, &addon_icd);
71     /*
72      * FIXME: this is a workaround since everyone is using "FcitxAddon*" everywhere,
73      * so realloc will really do some evil things.
74      *
75      * We might better use UT_hash for fcitx addon in the future
76      */
77     utarray_reserve(addons, 512);
78 }
79 
FcitxGetSymbol(void * handle,const char * addonName,const char * symbolName)80 void* FcitxGetSymbol(void* handle, const char* addonName, const char* symbolName)
81 {
82     char *p;
83     char *escapedAddonName;
84     fcitx_utils_alloc_cat_str(escapedAddonName, addonName, "_", symbolName);
85     for (p = escapedAddonName;*p;p++) {
86         if (*p == '-') {
87             *p = '_';
88         }
89     }
90     void *result = dlsym(handle, escapedAddonName);
91     free(escapedAddonName);
92     if (!result)
93         return dlsym(handle, symbolName);
94     return result;
95 }
96 
97 /**
98  * Load Addon Info
99  */
100 FCITX_EXPORT_API
FcitxAddonsLoad(UT_array * addons)101 void FcitxAddonsLoad(UT_array* addons)
102 {
103     FcitxAddonsLoadInternal(addons, false);
104 }
105 
FcitxAddonsLoadInternal(UT_array * addons,boolean reloadIM)106 FcitxAddon* FcitxAddonsLoadInternal(UT_array* addons, boolean reloadIM)
107 {
108     char **addonPath;
109     size_t len;
110     size_t start;
111     if (!reloadIM)
112         utarray_clear(addons);
113 
114     start = utarray_len(addons);
115 
116     FcitxStringHashSet* sset = FcitxXDGGetFiles("addon", NULL, ".conf");
117     addonPath = FcitxXDGGetPathWithPrefix(&len, "addon");
118     char *paths[len];
119     HASH_FOREACH(string, sset, FcitxStringHashSet) {
120         // FIXME: if it will cause realloc, then it's evil for fcitx 4.2 series
121         if (reloadIM && addons->i == addons->n) {
122             break;
123         }
124 
125         int i;
126         for (i = len - 1; i >= 0; i--) {
127             fcitx_utils_alloc_cat_str(paths[i], addonPath[len - i - 1],
128                                       "/", string->name);
129             FcitxLog(DEBUG, "Load Addon Config File:%s", paths[i]);
130         }
131         FcitxConfigFile* cfile = FcitxConfigParseMultiConfigFile(paths, len, FcitxAddonGetConfigDesc());
132         if (cfile) {
133             utarray_extend_back(addons);
134             FcitxAddon *a = (FcitxAddon*) utarray_back(addons);
135             utarray_init(&a->functionList, fcitx_ptr_icd);
136             FcitxAddonConfigBind(a, cfile, FcitxAddonGetConfigDesc());
137             FcitxConfigBindSync((FcitxGenericConfig*)a);
138             FcitxLog(DEBUG, _("Addon Config %s is %s"), string->name, (a->bEnabled) ? "Enabled" : "Disabled");
139             boolean error = false;
140             if (reloadIM) {
141                 if (a->category !=  AC_INPUTMETHOD)
142                     error = true;
143             }
144             /* if loaded, don't touch the old one */
145             if (FcitxAddonsGetAddonByNameInternal(addons, a->name, true) != a)
146                 error = true;
147 
148             if (error)
149                 utarray_pop_back(addons);
150             else
151                 FcitxLog(INFO, _("Load Addon Config File:%s"), string->name);
152         }
153 
154         for (i = len - 1;i >= 0;i--) {
155             free(paths[i]);
156         }
157     }
158     FcitxXDGFreePath(addonPath);
159 
160     fcitx_utils_free_string_hash_set(sset);
161 
162     size_t to = utarray_len(addons);
163     utarray_sort_range(addons, AddonPriorityCmp, start, to);
164 
165     return (FcitxAddon*)utarray_eltptr(addons, start);
166 }
167 
FcitxInstanceFillAddonOwner(FcitxInstance * instance,FcitxAddon * addonHead)168 void FcitxInstanceFillAddonOwner(FcitxInstance* instance, FcitxAddon* addonHead)
169 {
170     /* FIXME: a walkaround for not have instance in function FcitxModuleInvokeFunction */
171     FcitxAddon* addon;
172     if (addonHead)
173         addon = addonHead;
174     else
175         addon = (FcitxAddon *) utarray_front(&instance->addons);
176     for (; addon != NULL; addon = (FcitxAddon *) utarray_next(&instance->addons, addon)) {
177         addon->owner = instance;
178     }
179 }
180 
181 FCITX_EXPORT_API
FcitxInstanceResolveAddonDependency(FcitxInstance * instance)182 void FcitxInstanceResolveAddonDependency(FcitxInstance* instance)
183 {
184     FcitxInstanceResolveAddonDependencyInternal(instance, NULL);
185 }
186 
FcitxInstanceResolveAddonDependencyInternal(FcitxInstance * instance,FcitxAddon * startAddon)187 void FcitxInstanceResolveAddonDependencyInternal(FcitxInstance* instance, FcitxAddon* startAddon)
188 {
189     UT_array* addons = &instance->addons;
190     boolean remove = true;
191     FcitxAddon *addon;
192     FcitxAddon *uiaddon = NULL, *uifallbackaddon = NULL;
193     boolean reloadIM = true;
194 
195     if (!startAddon) {
196         startAddon = (FcitxAddon*) utarray_front(addons);
197         reloadIM = false;
198     }
199 
200     /* check "all" */
201     if (instance->disableList
202         && utarray_len(instance->disableList) == 1
203         && fcitx_utils_string_list_contains(instance->disableList, "all"))
204     {
205         for (addon = startAddon;
206             addon != NULL;
207             addon = (FcitxAddon *) utarray_next(addons, addon)) {
208             addon->bEnabled = false;
209         }
210     }
211 
212     /* override the enable and disable option */
213     for (addon = startAddon;
214          addon != NULL;
215          addon = (FcitxAddon *) utarray_next(addons, addon)) {
216         if (instance->enableList && fcitx_utils_string_list_contains(instance->enableList, addon->name))
217             addon->bEnabled = true;
218         else if (instance->disableList && fcitx_utils_string_list_contains(instance->disableList, addon->name))
219             addon->bEnabled = false;
220     }
221 
222     if (!reloadIM) {
223         /* choose ui */
224         for (addon = startAddon;
225              addon != NULL;
226              addon = (FcitxAddon *) utarray_next(addons, addon)) {
227             if (addon->category == AC_UI) {
228                 if (instance->uiname == NULL) {
229                     if (addon->bEnabled) {
230                         uiaddon = addon;
231                         break;
232                     }
233                 } else {
234                     if (strcmp(instance->uiname, addon->name) == 0) {
235                         addon->bEnabled = true;
236                         uiaddon = addon;
237                         break;
238                     }
239                 }
240             }
241         }
242 
243         if (uiaddon && uiaddon->uifallback) {
244             for (addon = startAddon;
245                  addon != NULL;
246                  addon = (FcitxAddon *) utarray_next(addons, addon)) {
247                 if (addon->category == AC_UI && addon->bEnabled && strcmp(uiaddon->uifallback, addon->name) == 0) {
248                     FcitxAddon temp;
249                     int uiidx = utarray_eltidx(addons, uiaddon);
250                     int fallbackidx = utarray_eltidx(addons, addon);
251                     if (fallbackidx < uiidx) {
252                         temp = *uiaddon;
253                         *uiaddon = *addon;
254                         *addon = temp;
255 
256                         /* they swapped, addon is normal ui, and ui addon is fallback */
257                         uifallbackaddon = uiaddon;
258                         uiaddon = addon;
259                     }
260                     else {
261                         uifallbackaddon = addon;
262                     }
263                     break;
264                 }
265             }
266         }
267 
268         for (addon = startAddon;
269                 addon != NULL;
270                 addon = (FcitxAddon *) utarray_next(addons, addon)) {
271             if (addon->category == AC_UI && addon != uiaddon && addon != uifallbackaddon) {
272                 addon->bEnabled = false;
273             }
274         }
275     }
276 
277     while (remove) {
278         remove = false;
279         for (addon = startAddon;
280                 addon != NULL;
281                 addon = (FcitxAddon *) utarray_next(addons, addon)) {
282             if (!addon->bEnabled)
283                 continue;
284             UT_array* dependlist = fcitx_utils_split_string(addon->depend, ',');
285             boolean valid = true;
286             char **depend = NULL;
287             for (depend = (char **) utarray_front(dependlist);
288                     depend != NULL;
289                     depend = (char **) utarray_next(dependlist, depend)) {
290                 if (!FcitxAddonsIsAddonAvailable(addons, *depend)) {
291                     valid = false;
292                     break;
293                 }
294             }
295 
296             utarray_free(dependlist);
297             if (!valid) {
298                 FcitxLog(WARNING, _("Disable addon %s, dependency %s cannot be satisfied."), addon->name, addon->depend);
299                 addon->bEnabled = false;
300             }
301         }
302     }
303 }
304 
305 FCITX_EXPORT_API
FcitxAddonsIsAddonAvailable(UT_array * addons,const char * name)306 boolean FcitxAddonsIsAddonAvailable(UT_array* addons, const char* name)
307 {
308     return FcitxAddonsGetAddonByNameInternal(addons, name, false) != NULL;
309 }
310 
FcitxAddonsGetAddonByNameInternal(UT_array * addons,const char * name,boolean checkDisabled)311 FcitxAddon* FcitxAddonsGetAddonByNameInternal(UT_array* addons, const char* name, boolean checkDisabled)
312 {
313     FcitxAddon *addon;
314     for (addon = (FcitxAddon *) utarray_front(addons);
315             addon != NULL;
316             addon = (FcitxAddon *) utarray_next(addons, addon)) {
317         if ((checkDisabled || addon->bEnabled) && strcmp(name, addon->name) == 0)
318             return addon;
319     }
320     return NULL;
321 }
322 
323 
324 FCITX_EXPORT_API
FcitxAddonsGetAddonByName(UT_array * addons,const char * name)325 FcitxAddon* FcitxAddonsGetAddonByName(UT_array* addons, const char* name)
326 {
327     return FcitxAddonsGetAddonByNameInternal(addons, name, false);
328 }
329 
330 /**
331  * Load addon.desc file
332  *
333  * @return the description of addon configure.
334  */
335 FCITX_EXPORT_API
336 CONFIG_DESC_DEFINE(FcitxAddonGetConfigDesc, "addon.desc")
337 
338 FCITX_EXPORT_API
FcitxAddonFree(void * v)339 void FcitxAddonFree(void* v)
340 {
341     FcitxAddon *addon = (FcitxAddon*) v;
342     if (!addon)
343         return ;
344     FcitxConfigFreeConfigFile(addon->config.configFile);
345     free(addon->name);
346     free(addon->library);
347     free(addon->comment);
348     free(addon->generalname);
349     free(addon->depend);
350     free(addon->subconfig);
351 }
352 
FcitxCheckABIVersion(void * handle,const char * addonName)353 boolean FcitxCheckABIVersion(void* handle, const char* addonName)
354 {
355     int* version = (int*) FcitxGetSymbol(handle, addonName, "ABI_VERSION");
356     if (!version)
357         return false;
358     if (*version < FCITX_ABI_VERSION)
359         return false;
360     return true;
361 }
362 
363 // kate: indent-mode cstyle; space-indent on; indent-width 0;
364