4
* Copyright 2007 PCMan <pcman.tw@gmail.com>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26
#include "mime-action.h"
27
#include "glib-utils.h"
30
#include <sys/types.h>
34
gboolean save_to_file( const char* path, const char* data, gssize len )
36
int fd = creat( path, 0644 );
40
if( write( fd, data, len ) == -1 )
49
const char group_desktop[] = "Desktop Entry";
50
const char key_mime_type[] = "MimeType";
52
typedef char* (*DataDirFunc) ( const char* dir, const char* mime_type, gpointer user_data );
54
static char* data_dir_foreach( DataDirFunc func, const char* mime_type, gpointer user_data )
57
const gchar* const * dirs;
58
const char* dir = g_get_user_data_dir();
60
if( (ret = func( dir, mime_type, user_data )) )
63
dirs = g_get_system_data_dirs();
64
for( ; *dirs; ++dirs )
66
if( (ret = func( *dirs, mime_type, user_data )) )
72
static void update_desktop_database()
75
argv[0] = g_find_program_in_path( "update-desktop-database" );
76
if( G_UNLIKELY( ! argv[0] ) )
78
argv[1] = g_build_filename( g_get_user_data_dir(), "applications", NULL );
80
g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
85
static int strv_index( char** strv, const char* str )
88
if( G_LIKELY( strv && str ) )
90
for( p = strv; *p; ++p )
92
if( 0 == strcmp( *p, str ) )
99
static char* get_actions( const char* dir, const char* type, GArray* actions )
104
char* path = g_build_filename( dir, "applications/mimeinfo.cache", NULL );
105
file = g_key_file_new();
106
opened = g_key_file_load_from_file( file, path, 0, NULL );
108
if( G_LIKELY( opened ) )
111
apps = g_key_file_get_string_list( file, "MIME Cache", type, &n_apps, NULL );
112
for( i = 0; i < n_apps; ++i )
114
if( -1 == strv_index( (char**)actions->data, apps[i] ) )
116
/* check for existence */
117
path = mime_type_locate_desktop_file( dir, apps[i] );
120
g_array_append_val( actions, apps[i] );
125
apps[i] = NULL; /* steal the string */
133
g_free( apps ); /* don't call g_strfreev since all strings in the array was stolen. */
135
g_key_file_free( file );
136
return NULL; /* return NULL so the for_each operation doesn't stop. */
140
* Get a list of applications supporting this mime-type
141
* The returned string array was newly allocated, and should be
142
* freed with g_strfreev() when no longer used.
144
char** mime_type_get_actions( const char* type )
146
GArray* actions = g_array_sized_new( TRUE, FALSE, sizeof(char*), 10 );
147
char* default_app = NULL;
149
/* FIXME: actions of parent types should be added, too. */
151
/* get all actions for this file type */
152
data_dir_foreach( (DataDirFunc)get_actions, type, actions );
154
/* ensure default app is in the list */
155
if( G_LIKELY( ( default_app = mime_type_get_default_action( type ) ) ) )
157
int i = strv_index( (char**)actions->data, default_app );
158
if( i == -1 ) /* default app is not in the list, add it! */
160
g_array_prepend_val( actions, default_app );
162
else /* default app is in the list, move it to the first. */
166
char** pdata = (char**)actions->data;
167
char* tmp = pdata[i];
168
g_array_remove_index( actions, i );
169
g_array_prepend_val( actions, tmp );
171
g_free( default_app );
174
return (char**)g_array_free( actions, actions->len == 0 );
179
* This API is very time consuming, but unfortunately, due to the damn poor design of
180
* Freedesktop.org spec, all the insane checks here are necessary. Sigh... :-(
182
gboolean mime_type_has_action( const char* type, const char* desktop_id )
184
char** actions, **action;
185
char *cmd = NULL, *name = NULL;
186
gboolean found = FALSE;
187
gboolean is_desktop = g_str_has_suffix( desktop_id, ".desktop" );
192
GKeyFile* kf = g_key_file_new();
193
char* filename = mime_type_locate_desktop_file( NULL, desktop_id );
194
if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
196
types = g_key_file_get_string_list( kf, group_desktop, key_mime_type, NULL, NULL );
197
if( -1 != strv_index( types, type ) )
199
/* our mime-type is already found in the desktop file. no further check is needed */
204
if( ! found ) /* get the content of desktop file for comparison */
206
cmd = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
207
name = g_key_file_get_string( kf, group_desktop, "Name", NULL );
211
g_key_file_free( kf );
215
cmd = (char*)desktop_id;
218
actions = mime_type_get_actions( type );
221
for( action = actions; ! found && *action; ++action )
223
/* Try to match directly by desktop_id first */
224
if( is_desktop && 0 == strcmp( *action, desktop_id ) )
228
else /* Then, try to match by "Exec" and "Name" keys */
230
char *name2 = NULL, *cmd2 = NULL, *filename = NULL;
231
GKeyFile* kf = g_key_file_new();
232
filename = mime_type_locate_desktop_file( NULL, *action );
233
if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
235
cmd2 = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
236
if( cmd && cmd2 && 0 == strcmp( cmd, cmd2 ) ) /* 2 desktop files have same "Exec" */
240
name2 = g_key_file_get_string( kf, group_desktop, "Name", NULL );
241
/* Then, check if the "Name" keys of 2 desktop files are the same. */
242
if( name && name2 && 0 == strcmp( name, name2 ) )
244
/* Both "Exec" and "Name" keys of the 2 desktop files are
245
* totally the same. So, despite having different desktop id
246
* They actually refer to the same application. */
257
g_key_file_free( kf );
260
g_strfreev( actions );
271
static gboolean is_custom_desktop_file( const char* desktop_id )
273
char* path = g_build_filename( g_get_user_data_dir(), "applications", desktop_id, NULL );
274
gboolean ret = g_file_test( path, G_FILE_TEST_EXISTS );
280
static char* make_custom_desktop_file( const char* desktop_id, const char* mime_type )
282
char *name = NULL, *cust_template = NULL, *cust = NULL, *path, *dir;
283
char* file_content = NULL;
287
if( G_LIKELY( g_str_has_suffix(desktop_id, ".desktop") ) )
289
GKeyFile *kf = g_key_file_new();
290
char* name = mime_type_locate_desktop_file( NULL, desktop_id );
291
if( G_UNLIKELY( ! name || ! g_key_file_load_from_file( kf, name,
292
G_KEY_FILE_KEEP_TRANSLATIONS, NULL ) ) )
295
return NULL; /* not a valid desktop file */
299
FIXME: If the source desktop_id refers to a custom desktop file, and
300
value of the MimeType key equals to our mime-type, there is no
301
need to generate a new desktop file.
302
if( G_UNLIKELY( is_custom_desktop_file( desktop_id ) ) )
306
/* set our mime-type */
307
g_key_file_set_string_list( kf, group_desktop, key_mime_type, &mime_type, 1 );
308
/* store id of original desktop file, for future use. */
309
g_key_file_set_string( kf, group_desktop, "X-MimeType-Derived", desktop_id );
310
g_key_file_set_string( kf, group_desktop, "NoDisplay", "true" );
312
name = g_strndup( desktop_id, strlen(desktop_id) - 8 );
313
cust_template = g_strdup_printf( "%s-usercustom-%%d.desktop", name );
316
file_content = g_key_file_to_data( kf, &len, NULL );
317
g_key_file_free( kf );
319
else /* it's not a desktop_id, but a command */
322
const char file_templ[] =
329
"NoDisplay=true\n"; /* FIXME: Terminal? */
330
/* Make a user-created desktop file for the command */
331
name = g_path_get_basename( desktop_id );
332
if( (p = strchr(name, ' ')) ) /* FIXME: skip command line arguments. is this safe? */
334
file_content = g_strdup_printf( file_templ, name, desktop_id, mime_type );
335
len = strlen( file_content );
336
cust_template = g_strdup_printf( "%s-usercreated-%%d.desktop", name );
340
/* generate unique file name */
341
dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
342
g_mkdir_with_parents( dir, 0700 );
345
/* generate the basename */
346
cust = g_strdup_printf( cust_template, i );
347
path = g_build_filename( dir, cust, NULL ); /* test if the filename already exists */
348
if( g_file_test( path, G_FILE_TEST_EXISTS ) )
353
else /* this generated filename can be used */
357
if( G_LIKELY( path ) )
359
save_to_file( path, file_content, len );
362
/* execute update-desktop-database" to update mimeinfo.cache */
363
update_desktop_database();
369
* Add an applications used to open this mime-type
370
* desktop_id is the name of *.desktop file.
372
* custom_desktop: used to store name of the newly created user-custom desktop file, can be NULL.
374
void mime_type_add_action( const char* type, const char* desktop_id, char** custom_desktop )
377
if( mime_type_has_action( type, desktop_id ) )
380
*custom_desktop = g_strdup( desktop_id );
384
cust = make_custom_desktop_file( desktop_id, type );
386
*custom_desktop = cust;
391
static char* _locate_desktop_file( const char* dir, const char* unused, const gpointer desktop_id )
393
char *path, *sep = NULL;
394
gboolean found = FALSE;
396
path = g_build_filename( dir, "applications", (const char*)desktop_id, NULL );
397
sep = strchr( (const char*)desktop_id, '-' );
399
sep = strrchr( path, '-' );
402
if( g_file_test( path, G_FILE_TEST_IS_REGULAR ) )
410
sep = strchr( sep + 1, '-' );
424
char* mime_type_locate_desktop_file( const char* dir, const char* desktop_id )
427
return _locate_desktop_file( dir, NULL, (gpointer) desktop_id );
428
return data_dir_foreach( _locate_desktop_file, NULL, (gpointer) desktop_id );
431
static char* get_default_action( const char* dir, const char* type, gpointer user_data )
436
char* path = g_build_filename( dir, "applications/defaults.list", NULL );
437
file = g_key_file_new();
438
opened = g_key_file_load_from_file( file, path, 0, NULL );
440
if( G_LIKELY( opened ) )
441
action = g_key_file_get_string( file, "Default Applications", type, NULL );
442
g_key_file_free( file );
444
/* check for existence */
447
path = mime_type_locate_desktop_file( NULL, action );
448
if( G_LIKELY( path ) )
460
* Get default applications used to open this mime-type
461
* The returned string was newly allocated, and should be
462
* freed when no longer used.
463
* If NULL is returned, that means default app is not set for this mime-type.
465
char* mime_type_get_default_action( const char* type )
467
/* FIXME: need to check parent types if default action of current type is not set. */
468
return data_dir_foreach( (DataDirFunc)get_default_action, type, NULL );
472
* Set default applications used to open this mime-type
473
* desktop_id is the name of *.desktop file.
475
void mime_type_set_default_action( const char* type, const char* desktop_id )
480
char* dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
481
char* path = g_build_filename( dir, "defaults.list", NULL );
483
g_mkdir_with_parents( dir, 0700 );
486
file = g_key_file_new();
487
/* Load old file content, if available */
488
g_key_file_load_from_file( file, path, 0, NULL );
489
g_key_file_set_string( file, "Default Applications", type, desktop_id );
490
data = g_key_file_to_data( file, &len, NULL );
491
g_key_file_free( file );
493
save_to_file( path, data, len );