4
* Copyright 2009 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,
23
#include "fm-mime-type.h"
25
#include <sys/types.h>
31
/* FIXME: how can we handle reload of xdg mime? */
33
static GHashTable *mime_hash = NULL;
34
G_LOCK_DEFINE(mime_hash);
36
static guint reload_callback_id = 0;
37
static GList* reload_cb = NULL;
39
//static VFSFileMonitor** mime_caches_monitor = NULL;
47
static gboolean fm_mime_type_reload( gpointer user_data )
50
/* FIXME: process mime database reloading properly. */
51
/* Remove all items in the hash table */
54
g_static_rw_lock_writer_lock( &mime_hash_lock );
55
g_hash_table_foreach_remove ( mime_hash, ( GHRFunc ) gtk_true, NULL );
56
g_static_rw_lock_writer_unlock( &mime_hash_lock );
58
g_source_remove( reload_callback_id );
59
reload_callback_id = 0;
61
/* g_debug( "reload mime-types" ); */
63
/* call all registered callbacks */
64
for( l = reload_cb; l; l = l->next )
66
FmMimeReloadCbEnt* ent = (FmMimeReloadCbEnt*)l->data;
67
ent->cb( ent->user_data );
73
static void on_mime_cache_changed( VFSFileMonitor* fm,
74
VFSFileMonitorEvent event,
75
const char* file_name,
78
MimeCache* cache = (MimeCache*)user_data;
81
case FM_FILE_MONITOR_CREATE:
82
case FM_FILE_MONITOR_DELETE:
83
/* NOTE: FAM sometimes generate incorrect "delete" notification for non-existent files.
84
* So if the cache is not loaded originally (the cache file is non-existent), we skip it. */
87
case FM_FILE_MONITOR_CHANGE:
88
mime_cache_reload( cache );
89
/* g_debug( "reload cache: %s", file_name ); */
90
if( 0 == reload_callback_id )
91
reload_callback_id = g_idle_add( fm_mime_type_reload, NULL );
96
void fm_mime_type_init()
100
GtkIconTheme * theme;
106
/* install file alteration monitor for mime-cache */
107
caches = mime_type_get_caches( &n_caches );
108
mime_caches_monitor = g_new0( VFSFileMonitor*, n_caches );
109
for( i = 0; i < n_caches; ++i )
111
VFSFileMonitor* fm = vfs_file_monitor_add_file( caches[i]->file_path,
112
on_mime_cache_changed, caches[i] );
113
mime_caches_monitor[i] = fm;
117
mime_hash = g_hash_table_new_full( g_str_hash, g_str_equal,
118
NULL, fm_mime_type_unref );
121
void fm_mime_type_finalize()
124
GtkIconTheme * theme;
128
theme = gtk_icon_theme_get_default();
129
g_signal_handler_disconnect( theme, theme_change_notify );
131
/* remove file alteration monitor for mime-cache */
132
caches = mime_type_get_caches( &n_caches );
133
for( i = 0; i < n_caches; ++i )
135
vfs_file_monitor_remove( mime_caches_monitor[i],
136
on_mime_cache_changed, caches[i] );
138
g_free( mime_caches_monitor );
140
mime_type_finalize();
142
g_hash_table_destroy( mime_hash );
145
FmMimeType* fm_mime_type_get_for_file_name( const char* ufile_name )
147
FmMimeType* mime_type;
150
type = g_content_type_guess( ufile_name, NULL, 0, &uncertain );
151
mime_type = fm_mime_type_get_for_type( type );
157
static FmMimeType* fm_mime_type_get_internal(char* type)
163
FmMimeType* fm_mime_type_get_for_native_file( const char* file_path,
164
const char* base_name,
167
FmMimeType* mime_type;
173
if( stat( file_path, &st ) == -1 )
177
if( S_ISREG(pstat->st_mode) )
179
if( pstat->st_size == 0 ) /* empty file = text file with 0 characters in it. */
180
return fm_mime_type_get_for_type( "text/plain" );
184
char* type = g_content_type_guess( base_name, NULL, 0, &uncertain );
189
fd = open(file_path, O_RDONLY);
193
len = read(fd, buf, 4096);
195
type = g_content_type_guess( NULL, buf, len, &uncertain );
198
mime_type = fm_mime_type_get_for_type( type );
204
if( S_ISDIR(pstat->st_mode) )
205
return fm_mime_type_get_for_type( "inode/directory" );
206
if (S_ISCHR(pstat->st_mode))
207
return fm_mime_type_get_for_type( "inode/chardevice" );
208
if (S_ISBLK(pstat->st_mode))
209
return fm_mime_type_get_for_type( "inode/blockdevice" );
210
if (S_ISFIFO(pstat->st_mode))
211
return fm_mime_type_get_for_type( "inode/fifo" );
212
if (S_ISLNK(pstat->st_mode))
213
return fm_mime_type_get_for_type( "inode/symlink" );
215
if (S_ISSOCK(pstat->st_mode))
216
return fm_mime_type_get_for_type( "inode/socket" );
219
g_error( "Invalid stat mode: %s", base_name );
223
FmMimeType* fm_mime_type_get_for_type( const char* type )
225
FmMimeType * mime_type;
228
mime_type = g_hash_table_lookup( mime_hash, type );
231
mime_type = fm_mime_type_new( type );
232
g_hash_table_insert( mime_hash, mime_type->type, mime_type );
234
G_UNLOCK( mime_hash );
235
fm_mime_type_ref( mime_type );
239
FmMimeType* fm_mime_type_new( const char* type_name )
241
FmMimeType * mime_type = g_slice_new0( FmMimeType );
243
mime_type->type = g_strdup( type_name );
244
mime_type->n_ref = 1;
246
gicon = g_content_type_get_icon(mime_type->type);
247
if( strcmp(mime_type->type, "inode/directory") == 0 )
248
g_themed_icon_prepend_name(gicon, "folder");
249
else if( g_content_type_can_be_executable(mime_type->type) )
250
g_themed_icon_append_name(gicon, "application-x-executable");
252
mime_type->icon = fm_icon_from_gicon(gicon);
253
g_object_unref(gicon);
256
/* TODO: Special case desktop dir? That could be expensive with xdg dirs... */
257
if (strcmp (path, g_get_home_dir ()) == 0)
258
type_icon = "user-home";
260
type_icon = "text-x-generic";
266
FmMimeType* fm_mime_type_ref( FmMimeType* mime_type )
268
g_atomic_int_inc(&mime_type->n_ref);
272
void fm_mime_type_unref( gpointer mime_type_ )
274
FmMimeType* mime_type = (FmMimeType*)mime_type_;
275
if ( g_atomic_int_dec_and_test(&mime_type->n_ref) )
277
g_free( mime_type->type );
278
if ( mime_type->icon )
279
fm_icon_unref( mime_type->icon );
280
g_slice_free( FmMimeType, mime_type );
284
FmIcon* fm_mime_type_get_icon( FmMimeType* mime_type )
286
return mime_type->icon;
288
GdkPixbuf * icon = NULL;
290
char icon_name[ 100 ];
291
GtkIconTheme *icon_theme;
296
if ( G_LIKELY( mime_type->big_icon ) ) /* big icon */
297
return gdk_pixbuf_ref( mime_type->big_icon );
298
size = big_icon_size;
300
else /* small icon */
302
if ( G_LIKELY( mime_type->small_icon ) )
303
return gdk_pixbuf_ref( mime_type->small_icon );
304
size = small_icon_size;
307
icon_theme = gtk_icon_theme_get_default ();
309
if ( G_UNLIKELY( 0 == strcmp( mime_type->type, XDG_MIME_TYPE_DIRECTORY ) ) )
311
icon = vfs_load_icon ( icon_theme, "folder", size );
312
if( G_UNLIKELY(! icon) )
313
icon = vfs_load_icon ( icon_theme, "gnome-fs-directory", size );
315
mime_type->big_icon = icon;
317
mime_type->small_icon = icon;
318
return icon ? gdk_pixbuf_ref( icon ) : NULL;
321
sep = strchr( mime_type->type, '/' );
324
/* convert mime-type foo/bar to foo-bar */
325
strcpy( icon_name, mime_type->type );
326
icon_name[ (sep - mime_type->type) ] = '-';
327
/* is there an icon named foo-bar? */
328
icon = vfs_load_icon ( icon_theme, icon_name, size );
331
/* maybe we can find a legacy icon named gnome-mime-foo-bar */
332
strcpy( icon_name, "gnome-mime-" );
333
strncat( icon_name, mime_type->type, ( sep - mime_type->type ) );
334
strcat( icon_name, "-" );
335
strcat( icon_name, sep + 1 );
336
icon = vfs_load_icon ( icon_theme, icon_name, size );
338
/* try gnome-mime-foo */
339
if ( G_UNLIKELY( ! icon ) )
341
icon_name[ 11 ] = '\0'; /* strlen("gnome-mime-") = 11 */
342
strncat( icon_name, mime_type->type, ( sep - mime_type->type ) );
343
icon = vfs_load_icon ( icon_theme, icon_name, size );
345
/* try foo-x-generic */
346
if ( G_UNLIKELY( ! icon ) )
348
strncpy( icon_name, mime_type->type, ( sep - mime_type->type ) );
349
icon_name[ (sep - mime_type->type) ] = '\0';
350
strcat( icon_name, "-x-generic" );
351
icon = vfs_load_icon ( icon_theme, icon_name, size );
355
if( G_UNLIKELY( !icon ) )
357
/* prevent endless recursion of XDG_MIME_TYPE_UNKNOWN */
358
if( G_LIKELY( strcmp(mime_type->type, XDG_MIME_TYPE_UNKNOWN) ) )
360
/* FIXME: fallback to icon of parent mime-type */
362
unknown = fm_mime_type_get_from_type( XDG_MIME_TYPE_UNKNOWN );
363
icon = fm_mime_type_get_icon( unknown, big );
364
fm_mime_type_unref( unknown );
368
icon = vfs_load_icon ( icon_theme, "unknown", size );
373
mime_type->big_icon = icon;
375
mime_type->small_icon = icon;
376
return icon ? gdk_pixbuf_ref( icon ) : NULL;
380
const char* fm_mime_type_get_type( FmMimeType* mime_type )
382
return mime_type->type;
385
/* Get human-readable description of mime type */
386
const char* fm_mime_type_get_desc( FmMimeType* mime_type )
388
/* FIXME: is locking needed here or not? */
389
if ( G_UNLIKELY( ! mime_type->description ) )
391
mime_type->description = g_content_type_get_description( mime_type->type );
392
/* FIXME: should handle this better */
393
if ( G_UNLIKELY( ! mime_type->description || ! *mime_type->description ) )
394
mime_type->description = g_content_type_get_description( mime_type->type );
396
return mime_type->description;
402
* Join two string vector containing app lists to generate a new one.
403
* Duplicated app will be removed.
405
char** fm_mime_type_join_actions( char** list1, gsize len1,
406
char** list2, gsize len2 )
411
if ( len1 > 0 || len2 > 0 )
412
ret = g_new0( char*, len1 + len2 + 1 );
413
for ( i = 0; i < len1; ++i )
415
ret[ i ] = g_strdup( list1[ i ] );
417
for ( j = 0, k = 0; j < len2; ++j )
419
for ( i = 0; i < len1; ++i )
421
if ( 0 == strcmp( ret[ i ], list2[ j ] ) )
426
ret[ len1 + k ] = g_strdup( list2[ j ] );
433
char** fm_mime_type_get_actions( FmMimeType* mime_type )
435
return (char**)mime_type_get_actions( mime_type->type );
438
char* fm_mime_type_get_default_action( FmMimeType* mime_type )
440
char* def = (char*)mime_type_get_default_action( mime_type->type );
442
* If default app is not set, choose one from all availble actions.
443
* Is there any better way to do this?
444
* Should we put this fallback handling here, or at API of higher level?
448
char** actions = mime_type_get_actions( mime_type->type );
451
def = g_strdup( actions[0] );
452
g_strfreev( actions );
459
* Set default app.desktop for specified file.
460
* app can be the name of the desktop file or a command line.
462
void fm_mime_type_set_default_action( FmMimeType* mime_type,
463
const char* desktop_id )
465
char* cust_desktop = NULL;
467
if( ! g_str_has_suffix( desktop_id, ".desktop" ) )
470
fm_mime_type_add_action( mime_type, desktop_id, &cust_desktop );
472
desktop_id = cust_desktop;
473
mime_type_set_default_action( mime_type->type, desktop_id );
475
g_free( cust_desktop );
478
/* If user-custom desktop file is created, it's returned in custom_desktop. */
479
void fm_mime_type_add_action( FmMimeType* mime_type,
480
const char* desktop_id,
481
char** custom_desktop )
483
mime_type_add_action( mime_type->type, desktop_id, custom_desktop );
486
GList* fm_mime_type_add_reload_cb( GFreeFunc cb, gpointer user_data )
488
FmMimeReloadCbEnt* ent = g_slice_new( FmMimeReloadCbEnt );
490
ent->user_data = user_data;
491
reload_cb = g_list_append( reload_cb, ent );
492
return g_list_last( reload_cb );
495
void fm_mime_type_remove_reload_cb( GList* cb )
497
g_slice_free( FmMimeReloadCbEnt, cb->data );
498
reload_cb = g_list_delete_link( reload_cb, cb );