1 /*
2  * Copyright (C) 2002-2012 Edscott Wilson Garcia
3  * EMail: edscott@users.sf.net
4  *
5  *
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 3 of the License, or
9  * (at your option) any later version.
10  *
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.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program;
18  */
19 
20 #define RFM_PRIMARY_C
21 
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25 
26 #include "rfm.h"
27 #include "rfm_modules.h"
28 
29 // static functions:
30 #include "primary.i"
31 #include "primary-ls.i"
32 
33 gchar *
rfm_default_url_mount_point(const gchar * url)34 rfm_default_url_mount_point(const gchar *url){
35     gchar *computer=NULL;
36     gchar *remote_path=NULL;
37     gchar *u = NULL;
38     if (url){
39 	u = g_strdup(url);
40 	gchar *p = strstr(u, "://");
41 	if (p) {
42 	    p = p+strlen("://");
43 	    if (strchr(p,'/')){
44 		*strchr(p,'/') = 0;
45 		computer = g_strdup(p);
46 		remote_path = g_strdup_printf("/%s",p + strlen(p) + 1);
47 	    }
48 	}
49 	g_free(u);
50     }
51 
52     	gchar *user = g_path_get_basename(g_get_home_dir ());
53 	gchar *dir;
54 	gchar *device = NULL;
55 	if (remote_path) {
56 	    device = g_path_is_absolute(remote_path)? remote_path+1: remote_path;
57 	}
58 	if (computer && remote_path) {
59 	    dir = g_strdup_printf("%s-%s", computer, device);
60 	} else {
61 	    dir = g_strdup((computer)? computer : device);
62 	}
63 	gchar *default_value =
64 	    g_build_filename (g_get_tmp_dir (), user, "mnt", dir, NULL);
65 	g_free(user);
66 	g_free(dir);
67 	g_free(computer);
68 	g_free(remote_path);
69 	return default_value;
70 
71 }
72 
73 static GSList *valid_view_list = NULL;
74 static pthread_rwlock_t valid_view_rwlock;
75 static pthread_once_t valid_view_once_control = PTHREAD_ONCE_INIT;
76 static pthread_mutex_t view_list_mutex = PTHREAD_MUTEX_INITIALIZER;
77 
78 static
valid_view_init(void)79 void valid_view_init(void){
80     pthread_rwlockattr_t Attr;
81     pthread_rwlockattr_init(&Attr);
82     pthread_rwlock_init(&valid_view_rwlock, &Attr);
83 }
84 
85 
86 #if defined DEBUG_TRACE || defined DEBUG
87 static gint mutex_count = 0;
88 #endif
89 
90 void
rfm_view_list_unlock(const gchar * source)91 rfm_view_list_unlock(const gchar *source){
92     NOOP("- %s -- unlocking view_list now... %d\n",source, --mutex_count);
93 #if defined DEBUG || defined DEBUG_TRACE
94     if (mutex_count < 0) g_error("unlocking non locked RWlock\n");
95 #endif
96     pthread_rwlock_unlock(&valid_view_rwlock);
97 
98 }
99 
100 static GSList **
view_list_lock(view_t * view_p,gboolean w,const gchar * source)101 view_list_lock(view_t *view_p, gboolean w, const gchar *source){
102     NOOP("+++ attempting %s lock for view_list... (%d)\n",
103             (w)?"write":"read", mutex_count);
104 
105     pthread_once(&valid_view_once_control, valid_view_init);
106     if (w) {
107         pthread_rwlock_wrlock(&valid_view_rwlock);
108     } else {
109         pthread_rwlock_rdlock(&valid_view_rwlock);
110     }
111     NOOP("+ %s ++ locking view_list now (%s)... %d\n", source,
112             (w)?"write":"read", ++mutex_count);
113     if (!view_p) return &valid_view_list;
114 
115     pthread_mutex_lock(&view_list_mutex);
116     void *data = g_slist_find(valid_view_list, view_p);
117     pthread_mutex_unlock(&view_list_mutex);
118 
119     if (data){
120 	return &valid_view_list;
121     }
122     NOOP("view %p not found in list\n", view_p);
123     rfm_view_list_unlock("view_list_lock");
124     return NULL;
125 }
126 
127 GSList **
rfm_view_list_lock(view_t * view_p,const gchar * source)128 rfm_view_list_lock(view_t *view_p, const gchar *source){
129     GSList **p=view_list_lock(view_p, FALSE, source);
130     return p;
131 }
132 
133 
134 void
rfm_add_view(view_t * view_p)135 rfm_add_view(view_t *view_p){
136     TRACE("rfm_add_view()...\n");
137     // write lock on valid views.
138     view_list_lock(NULL, FALSE, "rfm_add_view");
139     pthread_mutex_lock(&view_list_mutex);
140     void *data = g_slist_find(valid_view_list, view_p);
141     pthread_mutex_unlock(&view_list_mutex);
142     if (data){
143             DBG("view %p already in viewlist\n", view_p);
144             rfm_view_list_unlock("rfm_add_view");
145             return;
146     }
147     TRACE("adding view %p to list\n", view_p);
148     pthread_mutex_lock(&view_list_mutex);
149     valid_view_list = g_slist_prepend(valid_view_list, view_p);
150     pthread_mutex_unlock(&view_list_mutex);
151     rfm_view_list_unlock("rfm_add_view");
152     return;
153 }
154 
155 void
rfm_rm_view(view_t * view_p)156 rfm_rm_view(view_t *view_p){
157     TRACE("rfm_rm_view()...\n");
158     // write lock on valid views.
159     view_list_lock(NULL, TRUE, "rfm_rm_view");
160 
161     pthread_mutex_lock(&view_list_mutex);
162     void *data = g_slist_find(valid_view_list, view_p);
163     pthread_mutex_unlock(&view_list_mutex);
164 
165     if (data){
166         pthread_mutex_lock(&view_list_mutex);
167         valid_view_list = g_slist_remove(valid_view_list, view_p);
168         pthread_mutex_unlock(&view_list_mutex);
169         rfm_view_list_unlock("rfm_rm_view");
170         return;
171     }
172     rfm_view_list_unlock("rfm_rm_view");
173     DBG("view %p not in viewlist\n", view_p);
174     return;
175 }
176 
177 
178 
179 extern GThread *main_loop_thread;
180 
181 
182 static gboolean
main_context_function_f(gpointer data)183 main_context_function_f(gpointer data){
184     void **arg = data;
185     void * (*function)(gpointer) = arg[0];
186     gpointer function_data = arg[1];
187     GMutex *mutex = arg[2];
188     GCond *signal = arg[3];
189     void **result_p = arg[4];
190     void *result = (*function)(function_data);
191     g_mutex_lock(mutex);
192     *result_p = result;
193     g_cond_signal(signal);
194     g_mutex_unlock(mutex);
195     return FALSE;
196 }
197 
198 void *
rfm_context_function(void * (* function)(gpointer),void * function_data)199 rfm_context_function(void * (*function)(gpointer), void * function_data){
200     void *arg[5];
201     GMutex *mutex = NULL;
202     rfm_mutex_init(mutex);
203     GCond *signal;
204     rfm_cond_init(signal);
205     void *result=GINT_TO_POINTER(-1);
206     arg[0] = function;
207     arg[1] = function_data;
208     arg[2] = mutex;
209     arg[3] = signal;
210     arg[4] = &result;
211 
212     if (!rfm_get_gtk_thread()){
213         g_warning("gtk_main_thread not set. You can expect problems.\n");
214     }
215     if (rfm_get_gtk_thread()== g_thread_self()){
216         NOOP("main call to context function\n");
217 	main_context_function_f(arg);
218     } else {
219         NOOP("thread call to context function\n");
220 	g_main_context_invoke(NULL, main_context_function_f, arg);
221 	g_mutex_lock(mutex);
222 	if (result == GINT_TO_POINTER(-1)) g_cond_wait(signal, mutex);
223 	g_mutex_unlock(mutex);
224     }
225     rfm_mutex_free(mutex);
226     rfm_cond_free(signal);
227     return result;
228 }
229 
230 gboolean
rfm_main_context_check(view_t * view_p,gboolean view_entry)231 rfm_main_context_check(view_t *view_p, gboolean view_entry){
232     // Check if window is not in exit mode
233     rfm_global_t *rfm_global_p = rfm_global();
234     g_mutex_lock(rfm_global_p->status_mutex);
235     gint status = rfm_global_p->status;
236     g_mutex_unlock(rfm_global_p->status_mutex);
237     if(status == STATUS_EXIT) return FALSE;
238     if (!view_p) return TRUE;
239 
240     // Check if view is still listed
241     TRACE("rfm_main_context_check()...\n");
242     if (!rfm_view_list_lock(view_p, "rfm_main_context_check")) return FALSE;
243 
244     // Check if view is not in exit mode
245     status = view_p->flags.status;
246     if(status == STATUS_EXIT) goto done;
247 
248     // Check if view->en is still valid (move callbacks.i function to rfm lib)
249     if (view_entry &&
250 	    !rfm_entry_available(&(view_p->widgets), view_p->en)){
251 	goto done;
252     }
253     rfm_view_list_unlock("rfm_main_context_check1");
254     return TRUE;
255 done:
256     rfm_view_list_unlock("rfm_main_context_check2");
257     return FALSE;
258 }
259 
260 RFM_RW_LOCK_INIT(drag_info_lock);
261 
262 //#define LOCK_TRACE
263 
264 #ifdef LOCK_TRACE
265 /*static GHashTable *thread_hash = NULL;
266 static GHashTable *lock_ref_hash = NULL;*/
267 static gint pop_lock_count = 0;
268 
269 /*const gchar *
270 get_thread_text(GThread *thread){
271     if (rfm_get_gtk_thread() == thread) return "main thread";
272     gchar *text = g_hash_table_lookup(thread_hash, thread);
273     return (const gchar *)text;
274 }*/
275 
276 
277 /*gint thread_has_lock(GThread *thread){
278     return GPOINTER_TO_INT(g_hash_table_lookup(lock_ref_hash, thread));
279 }*/
280 
inc_RW(gboolean increment,const gchar * source)281 static void inc_RW(gboolean increment, const gchar *source){
282     static pthread_mutex_t lock_ref_mutex=PTHREAD_MUTEX_INITIALIZER;
283 	pthread_mutex_lock(&lock_ref_mutex);
284 	/*if (!lock_ref_hash){
285 	    lock_ref_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
286 	}*/
287 	//gint count = GPOINTER_TO_INT(g_hash_table_lookup(lock_ref_hash, g_thread_self()));
288         // These two commented out TRACE instructions will really vomit output
289         // and turn application into a snail. Use with care.
290 	if (increment) {
291 	    //count++;
292             pop_lock_count++;
293 		//if (get_thread_text(g_thread_self()))
294     			DBG("lock...%s: %d\n", source, pop_lock_count);
295     			//DBG("lock...%s\n",  get_thread_text(g_thread_self()));
296 	} else {
297 	    //count--;
298             pop_lock_count--;
299 		//if (get_thread_text(g_thread_self()))
300     			DBG("unlock...%s: %d\n", source, pop_lock_count);
301     			//DBG("unlock...%s\n", get_thread_text(g_thread_self()));
302 	}
303 	//g_hash_table_replace(lock_ref_hash, g_thread_self(), GINT_TO_POINTER(count));
304 	pthread_mutex_unlock(&lock_ref_mutex);
305     }
306 #endif
307 
308 //#define FILTER if (!strstr(source, "rect"))
309 //#define FILTER if (0)
310 #define FILTER if (0)
311 void
rfm_population_read_unlock(view_t * view_p,const gchar * source)312 rfm_population_read_unlock (view_t * view_p, const gchar *source) {
313     FILTER DBG("--- rfm_population_read_unlock() from  %s\n", source);
314     rfm_rw_lock_reader_unlock(&(view_p->mutexes.population_rwlock));
315 #ifdef LOCK_TRACE
316     inc_RW(FALSE, source);
317 #endif
318 }
319 void
rfm_population_write_unlock(view_t * view_p,const gchar * source)320 rfm_population_write_unlock (view_t * view_p, const gchar *source) {
321     FILTER DBG("--- rfm_population_write_unlock() from  %s\n", source);
322     rfm_rw_lock_writer_unlock(&(view_p->mutexes.population_rwlock));
323 #ifdef LOCK_TRACE
324     inc_RW(FALSE, source);
325 #endif
326 }
327 
328 
329 gboolean
rfm_population_write_lock(view_t * view_p,const gchar * source)330 rfm_population_write_lock (view_t * view_p, const gchar *source) {
331 
332     // This should only return false on serial mismatch.
333     // Write lock on exit condition is necessary for view cleanup
334     gint population_serial = view_p->flags.population_serial;
335 
336 
337     FILTER DBG("... rfm_population_write_lock() request from  %s\n", source);
338     rfm_rw_lock_writer_lock(&(view_p->mutexes.population_rwlock));
339 
340     FILTER DBG("+++ rfm_population_write_lock() obtained for  %s\n", source);
341     gint obtained_serial = view_p->flags.population_serial;
342     if (obtained_serial != population_serial) {
343 	rfm_rw_lock_writer_unlock(&(view_p->mutexes.population_rwlock));
344 	return FALSE;
345     }
346 #ifdef LOCK_TRACE
347     inc_RW(TRUE, source);
348 #endif
349     return TRUE;
350 }
351 
352 
353 gboolean
rfm_population_read_lock(view_t * view_p,const gchar * source)354 rfm_population_read_lock (view_t * view_p, const gchar *source) {
355     gboolean status = view_p->flags.status;
356     if (status == STATUS_EXIT){
357 	TRACE( "rfm_population_read_lock STATUS_EXIT\n");
358 	return FALSE;
359     }
360     gint population_serial = view_p->flags.population_serial;
361 
362     NOOP("... rfm_population_read_lock() request from  %s\n", source);
363     rfm_rw_lock_reader_lock(&(view_p->mutexes.population_rwlock));
364     FILTER DBG("ooo rfm_population_reader_lock() obtained for  %s\n", source);
365     gint obtained_serial = view_p->flags.population_serial;
366     if (obtained_serial != population_serial) {
367 	DBG( "population serial mismatch: %d != %d read lock cancelled.\n",
368 	    obtained_serial, population_serial);
369 	rfm_rw_lock_reader_unlock(&(view_p->mutexes.population_rwlock));
370 	return FALSE;
371     }
372 #ifdef LOCK_TRACE
373     inc_RW(TRUE, source);
374 #endif
375 
376     return TRUE;
377 }
378 
379 gboolean
rfm_population_try_read_lock(view_t * view_p,const gchar * source)380 rfm_population_try_read_lock (view_t * view_p, const gchar *source) {
381     gboolean status = view_p->flags.status;
382     if (status == STATUS_EXIT) return FALSE;
383     gint population_serial = view_p->flags.population_serial;
384 
385     NOOP("... rfm_population_try_read_lock() request from  %s\n", source);
386     gboolean result = rfm_rw_lock_reader_trylock(&(view_p->mutexes.population_rwlock));
387 
388     if (!result){
389 	FILTER DBG("~~~ rfm_population_try_reader_lock() NOT obtained for  %s\n",
390                 source);
391         return FALSE;
392     }
393 
394     FILTER DBG("ooo rfm_population_try_reader_lock() obtained for  %s\n", source);
395 
396     gint obtained_serial = view_p->flags.population_serial;
397     if (obtained_serial != population_serial) {
398 	rfm_rw_lock_reader_unlock(&(view_p->mutexes.population_rwlock));
399 	return FALSE;
400     }
401 #ifdef LOCK_TRACE
402     inc_RW(TRUE, source);
403 #endif
404 
405     return TRUE;
406 }
407 //#undef LOCK_TRACE
408 
409 static gint thread_count = 0;
410 
411 static void
inc_dec_view_ref(view_t * view_p,gboolean increment)412 inc_dec_view_ref(view_t *view_p, gboolean increment){
413     RfmRWLock *lock;
414     rfm_global_t *rfm_global_p = rfm_global();
415     if (view_p) {
416 	lock = &(view_p->mutexes.view_lock);
417     } else {
418 	if (!rfm_global_p) {
419 	    TRACE("increment/decrement view_ref(): invalid view_p (rfm_global_p==NULL)\n");
420 	    return;
421 	}
422 	NOOP( "*+*+ global window lock\n");
423 	lock = &(rfm_global_p->window_lock);
424     }
425     // XXX since this is main thread business (as well
426     //     as child thread), reader lock may block...
427     //     but this is very rare condition.
428     if (increment){
429 	if (rfm_get_gtk_thread() == g_thread_self()){
430 	    while (!rfm_rw_lock_reader_trylock(lock)) gtk_main_iteration();
431 	} else 	rfm_rw_lock_reader_lock(lock);
432     }
433     else rfm_rw_lock_reader_unlock(lock);
434     static pthread_mutex_t inc_dec_mutex=PTHREAD_MUTEX_INITIALIZER;
435 
436     pthread_mutex_lock(&inc_dec_mutex);
437     if (increment) {
438 	if (view_p) view_p->flags.thread_count++;
439 	thread_count++;
440     } else {
441  	if (view_p) view_p->flags.thread_count--;
442 	thread_count--;
443     }
444     gint view_count = 1;
445     if (view_p) view_count = view_p->flags.thread_count;
446     if (thread_count == 0 || view_count == 0){
447 	// Janitor signal (This wakes up the janitor).
448 	TRACE("thread unreference sending janitor the signal. (%d,%d)\n",
449 		thread_count, view_count);
450 	g_cond_signal(rfm_global_p->janitor_signal);
451     }
452     pthread_mutex_unlock(&inc_dec_mutex);
453 }
454 
455 static void *
wait_f(gpointer data)456 wait_f(gpointer data){
457     void **argv =data;
458     view_t *view_p = argv[0];
459     GThread *thread = argv[1];
460     g_free(argv);
461     g_thread_join(thread);
462     rfm_thread_unreference(view_p, thread);
463     return NULL;
464 }
465 
466 #ifdef DEBUG_TRACE
467 #define DEBUG_THREADS
468 #endif
469 
470 #ifdef DEBUG_THREADS
471 static GSList *view_ref_list = NULL;
472 static pthread_mutex_t *
get_view_ref_mutex(void)473 get_view_ref_mutex(void){
474     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
475     return &mutex;
476 }
477 
478 void
rfm_thread_unreference_quiet(view_t * view_p,GThread * thread)479 rfm_thread_unreference_quiet(view_t *view_p, GThread *thread){
480     if (thread) inc_dec_view_ref(view_p, FALSE);
481 }
482 #else
483 void
rfm_thread_unreference_quiet(view_t * view_p,GThread * thread)484 rfm_thread_unreference_quiet(view_t *view_p, GThread *thread){
485     return rfm_thread_unreference(view_p, thread);
486 }
487 #endif
488 
489 
490 void
rfm_thread_unreference(view_t * view_p,GThread * thread)491 rfm_thread_unreference(view_t *view_p, GThread *thread){
492     if (thread) inc_dec_view_ref(view_p, FALSE);
493 #ifdef DEBUG_THREADS
494     pthread_mutex_t *view_ref_mutex=get_view_ref_mutex();
495     pthread_mutex_lock(view_ref_mutex);
496     const gchar *removed_text = get_thread_text(thread);
497 
498     TRACE( "- view decrement: 0x%x (%s) view ref = %d\n",
499 	    GPOINTER_TO_INT(thread), removed_text,
500 	    g_slist_length(view_ref_list)-1);
501 
502     view_ref_list = g_slist_remove(view_ref_list, thread);
503     g_hash_table_remove(thread_hash, thread);
504 
505 #ifdef DEBUG_TRACE
506     gchar *trace_text = NULL;
507 
508     GSList *t = view_ref_list;
509     if (g_slist_length(view_ref_list)) for(;t && t->data; t=t->next){
510 	gchar *dbg_text = g_hash_table_lookup(thread_hash, t->data);
511 	if (dbg_text) {
512 	    gchar *g =
513 		g_strdup_printf("%s 0x%x (%s)",
514 			trace_text?trace_text:"- decrement pending:",
515 			GPOINTER_TO_INT(t->data), dbg_text);
516 	    g_free(trace_text);
517 	    trace_text = g;
518 	}
519     } else {
520 	trace_text = g_strdup("- view decrement: no threads pending");
521     }
522     if (trace_text) {
523 	TRACE( "%s (%d)\n", trace_text, g_slist_length(view_ref_list));
524 	g_free(trace_text);
525     }
526 #endif
527     pthread_mutex_unlock(view_ref_mutex);
528 
529 #endif
530 }
531 
532 void
rfm_thread_reference(view_t * view_p,GThread * thread,const gchar * dbg_text)533 rfm_thread_reference(view_t *view_p, GThread *thread, const gchar *dbg_text){
534 	if (thread) inc_dec_view_ref(view_p, TRUE);
535 #ifdef DEBUG_THREADS
536 	if (dbg_text){
537 	    TRACE( "- view increment: 0x%x:0x%x (%s) view ref = %d\n",
538 		    GPOINTER_TO_INT(view_p), GPOINTER_TO_INT(thread), dbg_text, g_slist_length(view_ref_list)+1);
539 	    pthread_mutex_t *view_ref_mutex=get_view_ref_mutex();
540 	    pthread_mutex_lock(view_ref_mutex);
541 	    if (!thread_hash){
542 		thread_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
543 	    }
544 	    view_ref_list = g_slist_append(view_ref_list, thread);
545 	    g_hash_table_replace(thread_hash, thread, (void *)dbg_text);
546 	    pthread_mutex_unlock(view_ref_mutex);
547 	}
548 #endif
549 }
550 
551 // If view is NULL, the lock is on the window, not any particular view.
552 GThread *
rfm_view_thread_create(view_t * view_p,gpointer (* thread_f)(gpointer),gpointer data,const gchar * dbg_text)553 rfm_view_thread_create(
554 	view_t *view_p,
555 	gpointer(*thread_f)(gpointer), gpointer data,
556 	const gchar *dbg_text){
557     rfm_global_t *rfm_global_p = rfm_global();
558 
559     if (rfm_global_p){
560         g_mutex_lock(rfm_global_p->status_mutex);
561         gint status = rfm_global_p->status;
562         g_mutex_unlock(rfm_global_p->status_mutex);
563 	if (status == STATUS_EXIT) {
564 	    DBG("rfm_view_thread_create: rfm_global_p->status == STATUS_EXIT\n");
565 	    return NULL;
566 	}
567     }
568 
569     if (view_p){
570         TRACE("rfm_view_thread_create()...\n");
571         if (!rfm_view_list_lock(view_p, "rfm_view_thread_create")) {
572             return NULL;
573         }
574 	gint status = view_p->flags.status;
575 	if (status == STATUS_EXIT) {
576             rfm_view_list_unlock("rfm_view_thread_create");
577 	    return NULL;
578 	}
579     }
580 
581 #ifdef DEBUG_TRACE
582     if (dbg_text){
583 	TRACE("Thread: %s\n", dbg_text);
584     }
585 #endif
586     GThread *thread = rfm_thread_create (dbg_text, thread_f, data, TRUE);
587     if (thread){
588 	rfm_thread_reference(view_p, thread, dbg_text);
589 	void **arg = (void **)malloc(2*sizeof(void *));
590 	arg[0] = view_p;
591 	arg[1] = thread;
592 	rfm_thread_create ("wait_f", wait_f, arg, FALSE);
593     }
594     if (view_p) rfm_view_list_unlock("rfm_view_thread_create");
595     return thread;
596 }
597 
598 ///////////////////////////////////////////////////////////////////
599 // Main thread threadpool queue creation
600 
601 GThreadPool *
rfm_thread_queue_new(void (* signal_pool_f)(void *,void *),void * pool_data,gint max_threads)602 rfm_thread_queue_new(void (*signal_pool_f)(void *, void *), void *pool_data, gint max_threads){
603     GThreadPool *thread_pool;
604     GError *error = NULL;
605     thread_pool = g_thread_pool_new (signal_pool_f,
606                pool_data, max_threads, TRUE, &error);
607     if (error){
608 	g_error("g_thread_pool_new: %s\n", error->message);
609     }
610    // g_thread_pool_set_max_idle_time (miliseconds);
611     return thread_pool;
612 }
613 
614 gboolean
rfm_threadqueue_push(GThreadPool * thread_pool,gint signal_enum,view_t * view_p,void * data)615 rfm_threadqueue_push(GThreadPool *thread_pool, gint signal_enum, view_t *view_p, void *data){
616     rfm_global_t *rfm_global_p = rfm_global();
617     if (!thread_pool) {
618 	DBG("rfm_global_p->thread_queue not yet initialized.\n");
619 	return FALSE;
620     }
621     g_mutex_lock(rfm_global_p->status_mutex);
622     gint status = rfm_global_p->status;
623     g_mutex_unlock(rfm_global_p->status_mutex);
624     if (status == STATUS_EXIT) return FALSE;
625 
626     void **arg = (void **)malloc(3*sizeof(void *));
627     if (!arg) g_error("malloc: %s\n", strerror(errno));
628     arg[0] = GINT_TO_POINTER(signal_enum);
629     arg[1] = view_p;
630     arg[2] = data;
631 
632     NOOP(stderr, "rfm_threadqueue_push: g_thread_pool_push, signal %d\n", signal_enum);
633     g_thread_pool_push(thread_pool, arg, NULL);
634     return TRUE;
635 }
636 
637 #define PASTE_SHM_NAME paste_shm_name()
638 
639 static const gchar*
paste_shm_name(void)640 paste_shm_name(void){
641     static const gchar *name=NULL;
642     if (!name){
643 	name = g_strdup_printf("/%d-rfm-pasteboard", (gint)geteuid());
644     }
645     return name;
646 }
647 
648 gchar *
rfm_get_hash_key(const gchar * key,gint size)649 rfm_get_hash_key (const gchar * key, gint size) {
650     gchar *hash_key = NULL;
651     GString *gs = g_string_new (key);
652     if (size <=0) {
653 	hash_key = g_strdup_printf ("%010u", g_string_hash (gs));
654     } else {
655 	gint usize = 999;
656 	if (size <= 999) usize = size;
657 	hash_key = g_strdup_printf ("%010u-%d", g_string_hash (gs), usize);
658     }
659     g_string_free (gs, TRUE);
660     NOOP("%s: hashkey=%s\n", key, hash_key);
661     return hash_key;
662 }
663 
664 
665 void
rfm_set_widget(void * widget,const gchar * name)666 rfm_set_widget (void *widget, const gchar * name){
667     if(!name) g_error ("rfm_set_widget invalid call");
668     rfm_global_t *rfm_global_p = rfm_global();
669     if (!rfm_global_p || !rfm_global_p->window){
670 	NOOP("rfm_set_widget invalid parameters\n");
671 	return;
672     }
673     g_object_set_data (G_OBJECT (rfm_global_p->window), name, widget);
674     NOOP(stderr, "set %s: 0x%x\n", name, GPOINTER_TO_INT(widget));
675     return;
676 }
677 
678 void *
rfm_get_widget(const gchar * name)679 rfm_get_widget (const gchar * name) {
680     if(!name) {
681 	DBG ("rfm_get_widget: !name\n");
682 	return NULL;
683     }
684     rfm_global_t *rfm_global_p = rfm_global();
685     if (!rfm_global_p || !rfm_global_p->window){
686 	g_error("invalid call to rfm_get_widget: %s\n", name);
687     }
688     void *pointer = g_object_get_data (G_OBJECT (rfm_global_p->window), name);
689     if(!pointer) {
690 	// This may be not set yet, and may not indicate any error.
691 	NOOP("Cannot find widget (or pointer) associated to \"%s\"\n", name);
692 	TRACE("Cannot find widget (or pointer) associated to \"%s\"\n", name);
693     }
694     return pointer;
695 
696 }
697 
698 gchar *
rfm_esc_string(const gchar * string)699 rfm_esc_string (const gchar * string) {
700     int i,
701       j,
702       k;
703     char *charset = "\\\"\' ()|<>";
704 
705     for(j = 0, i = 0; i < strlen (string); i++) {
706         for(k = 0; k < strlen (charset); k++) {
707             if(string[i] == charset[k])
708                 j++;
709         }
710     }
711     gchar *estring = (gchar *) malloc (strlen (string) + j + 1);
712     memset (estring, 0, strlen (string) + j + 1);
713     for(j = 0, i = 0; i < strlen (string); i++, j++) {
714         for(k = 0; k < strlen (charset); k++) {
715             if(string[i] == charset[k])
716                 estring[j++] = '\\';
717         }
718         estring[j] = string[i];
719     }
720     NOOP ("ESC:estring=%s\n", estring);
721     return estring;
722 }
723 
724 #define MAX_PATH_LABEL 40
725 #define MAX_PATH_START_LABEL 18
726 const gchar *
rfm_chop_excess(gchar * b)727 rfm_chop_excess (gchar * b) {
728     // chop excess length...
729 
730     const gchar *home = g_get_home_dir();
731     gchar *bb;
732     if (strncmp(home, b, strlen(home))==0){
733         if (strlen(home) == strlen(b)) return b;
734         bb = g_strconcat("~/", b + strlen(home)+1, NULL);
735     } else {
736         bb = g_strdup(b);
737     }
738 
739     int len = strlen (bb);
740 
741     if(len < MAX_PATH_LABEL) {
742         strcpy(b, bb);
743         g_free(bb);
744         return (b);
745     }
746 
747     bb[MAX_PATH_START_LABEL - 3] = 0;
748 
749     gchar *g = g_strconcat(bb, "...", b + (len - MAX_PATH_LABEL + MAX_PATH_START_LABEL), NULL);
750     strcpy (b, g);
751     g_free(bb);
752     g_free(g);
753 
754     return b;
755 }
756 
757 /*
758  * This function converts a time value specified in seconds since 1970-01-01
759  * to a string representation. It shows the date and the time if the point
760  * if less then six month in the past and only the date otherwise.
761  * The exact format must be provided by a translation string.
762  *
763  * The function should be thread-save since there are not used static
764  * (or even global) variables if the system provided a localtime_r() function.
765  *
766  * Arguments:     when:    time in seconds since 1970-01-01
767  *                string:  the result char-array in which the string is placed
768  *                length:  the length of the string-array
769  *
770  * Return value:  string on success, NULL on failure
771  */
772 gchar *
rfm_time_to_string(time_t when)773 rfm_time_to_string (time_t when) {
774     time_t now = time (NULL);
775 #ifdef HAVE_LOCALTIME_R
776     struct tm timestruct;
777 #endif
778     struct tm *timestruct_ptr;
779     char *formatstring;
780     gchar *s = NULL;
781     gchar string[64];
782 
783 #ifdef HAVE_MEMSET
784     memset (string, 0, 64);
785 #else
786     string[0] = 0;
787 #endif
788 
789     formatstring = difftime (now, when) > 24 * 60 * 60 * 30 * 6
790         /* strftime format for non-recent files (older than 6 months)  */
791         ? _("%b %e  %Y")
792         /* strftime format for recent files */
793         : _("%b %e %H:%M");
794 
795 #ifdef HAVE_LOCALTIME_R
796     timestruct_ptr = &timestruct;
797     localtime_r (&when, timestruct_ptr);
798 #else
799     timestruct_ptr = localtime (&when);
800 #endif
801 
802     if(strftime (string, 64, formatstring, localtime (&when)) == 0) {
803         return NULL;
804     }
805     s = rfm_utf_string (string);
806     return s;
807 }
808 
809 gchar *
rfm_mode_string(mode_t mode)810 rfm_mode_string (mode_t mode) {
811     return mode_string (mode);
812 }
813 
814 static pthread_mutex_t user_string_mutex=PTHREAD_MUTEX_INITIALIZER;
815 
816 gchar *
rfm_user_string(struct stat * st)817 rfm_user_string (struct stat *st) {
818     pthread_mutex_lock(&user_string_mutex);
819     struct passwd *p;
820     gchar *user_string;
821     if((p = getpwuid (st->st_uid)) != NULL)
822             user_string = g_strdup(p->pw_name);
823         else if((gint)st->st_uid < 0)
824             user_string = g_strdup("");
825         else
826             user_string = g_strdup_printf("%d", (gint)st->st_uid);
827     pthread_mutex_unlock(&user_string_mutex);
828     return user_string;
829 }
830 
831 static pthread_mutex_t group_string_mutex=PTHREAD_MUTEX_INITIALIZER;
832 
833 gchar *
rfm_group_string(struct stat * st)834 rfm_group_string (struct stat *st) {
835     pthread_mutex_lock(&group_string_mutex);
836     struct group *g;
837     gchar *group_string;
838     if((g =  getgrgid(st->st_gid)) != NULL)
839             group_string = g_strdup(g->gr_name);
840     else
841         group_string = g_strdup_printf("%d", (gint)st->st_gid);
842     pthread_mutex_unlock(&group_string_mutex);
843     return group_string;
844 }
845 
846 static pthread_mutex_t date_string_mutex=PTHREAD_MUTEX_INITIALIZER;
847 gchar *
rfm_date_string(time_t the_time)848 rfm_date_string (time_t the_time) {
849     pthread_mutex_lock(&date_string_mutex);
850 
851 #ifdef HAVE_LOCALTIME_R
852         struct tm t_r;
853 #endif
854         struct tm *t;
855 
856 #ifdef HAVE_LOCALTIME_R
857         t = localtime_r (&the_time, &t_r);
858 #else
859         t = localtime (&the_time);
860 #endif
861         gchar *date_string=
862 	    g_strdup_printf ("%04d/%02d/%02d  %02d:%02d", t->tm_year + 1900,
863                  t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min);
864     pthread_mutex_unlock(&date_string_mutex);
865 
866     return date_string;
867 }
868 
869 gchar *
rfm_utf_string(const gchar * t)870 rfm_utf_string (const gchar * t) {
871     if(!t) {
872 	NOOP("rfm_utf_string(): string == NULL!\n");
873         return g_strdup ("");
874     }
875 
876     if(g_utf8_validate (t, -1, NULL)) {
877         return g_strdup (t);
878     }
879     /* so we got a non-UTF-8 */
880     /* but is it a valid locale string? */
881     gchar *actual_tag;
882     actual_tag = g_locale_to_utf8 (t, -1, NULL, NULL, NULL);
883     if(actual_tag)
884         return actual_tag;
885     /* So it is not even a valid locale string...
886      * Let us get valid utf-8 caracters then: */
887     const gchar *p;
888     actual_tag = g_strdup ("");
889     for(p = t; *p; p++) {
890         // short circuit end of string:
891         gchar *r = g_locale_to_utf8 (p, -1, NULL, NULL, NULL);
892         if(g_utf8_validate (p, -1, NULL)) {
893             gchar *qq = g_strconcat (actual_tag, p, NULL);
894             g_free (actual_tag);
895             actual_tag = qq;
896             break;
897         } else if(r) {
898             gchar *qq = g_strconcat (actual_tag, r, NULL);
899             g_free (r);
900             g_free (actual_tag);
901             actual_tag = qq;
902             break;
903         }
904         // convert caracter to utf-8 valid.
905         gunichar gu = g_utf8_get_char_validated (p, 2);
906         if(gu == (gunichar) - 1) {
907             //DBG("gu=%d\n",(int)gu);
908             gu = g_utf8_get_char_validated ("?", -1);
909         }
910         gchar outbuf[8];
911         memset (outbuf, 0, 8);
912         gint outbuf_len = g_unichar_to_utf8 (gu, outbuf);
913         if(outbuf_len < 0) {
914             DBG ("unichar=%d char =%c outbuf_len=%d\n", gu, p[0], outbuf_len);
915         }
916         gchar *qq = g_strconcat (actual_tag, outbuf, NULL);
917         g_free (actual_tag);
918         actual_tag = qq;
919     }
920     return actual_tag;
921 }
922 
923 gchar *
rfm_sizetag(off_t tama,gint count)924 rfm_sizetag (off_t tama, gint count) {
925     gchar *tag = _("bytes");
926     gchar *buf = NULL;
927     double utama = tama;
928 
929     buf = NULL;
930     if(utama > 0) {
931         if(utama >= (off_t)1000 * 1000 * 1000) {
932             utama /= ((off_t)1000 * 1000 * 1000);
933             tag = _("Gigabytes");
934         } else if(utama >= 1000 * 1000) {
935             utama /= (1000 * 1000);
936             tag = _("Megabytes");
937         } else if(utama >= 1000) {
938             utama /= 1000;
939             tag = _("Kilobytes");
940         }
941         if(count <= 0) {
942             /* format for size column of regular files */
943             buf = g_strdup_printf ("%.2lf %s", utama, tag);
944         } else {
945             gchar *plural_text=
946                 g_strdup_printf (ngettext ("%'u item", "%'u items",
947                             count),count);
948 	    if (tama < 1000) {
949 		buf = g_strdup_printf ("%s: %.0lf %s.", plural_text,
950                     utama, tag);
951 	    } else {
952 		buf = g_strdup_printf ("%s: %.2lf %s.", plural_text,
953                     utama, tag);
954 	    }
955             g_free(plural_text);
956 
957         }
958     } else {
959         if(count <=0) {
960             buf = g_strdup_printf (_("The location is empty."));
961         } else {
962             buf=
963                 g_strdup_printf (ngettext ("%'u item", "%'u items", count),
964                         count);
965         }
966     }
967     return buf;
968 }
969 
970 /**
971  * rfm_host_name:
972  * @xid: Window X id
973  *
974  * This gets the hostname of the window client. This is different from
975  * #g_get_host_name() which gets the name of the window server. This may be
976  * different when the window is displayed on a remote server.
977  *
978  **/
979 gchar *
rfm_host_name(Window xid)980 rfm_host_name (Window xid) {
981     char *name = NULL;
982     unsigned char *property_data;
983     unsigned long items,
984       remaining;
985     int actual_format;
986     Atom atomo,
987       actual_atom;
988     if (rfm_get_gtk_thread() != g_thread_self())
989 	g_error("rfm_host_name(): only to be called by gtk thread\n");
990     rfm_global_t *rfm_global_p = rfm_global();
991     if (!rfm_global_p) return "localhost";
992     atomo = XInternAtom (rfm_global_p->Xdisplay, "WM_CLIENT_MACHINE", FALSE);
993     if(XGetWindowProperty (rfm_global_p->Xdisplay,
994 		xid,
995                 atomo, 0, 255, FALSE, XA_STRING,
996                 &actual_atom, &actual_format, &items,
997 		&remaining, &property_data) == Success) {
998         NOOP ("property_data=%s", ((property_data) ? property_data : (unsigned char *)"null"));
999         if(!property_data)
1000             name = g_strdup (g_get_host_name ());
1001         else {
1002             name = g_strdup ((const gchar *)property_data);
1003             XFree (property_data);
1004         }
1005     } else
1006         name = g_strdup (g_get_host_name ());
1007     return name;
1008 }
1009 
1010 gint
rfm_mkdir(const gchar * dir)1011 rfm_mkdir (const gchar * dir) {
1012     if(rfm_g_file_test (dir, G_FILE_TEST_EXISTS)) {
1013         gchar *message;
1014         if(!rfm_g_file_test (dir, G_FILE_TEST_IS_DIR)) {
1015             message = g_strdup_printf ("%s: %s (ENOTDIR: %s)", dir, strerror (EEXIST), strerror (ENOTDIR));
1016         } else {
1017             message = g_strdup_printf ("%s: %s", dir, strerror (EEXIST));
1018         }
1019         rfm_confirm (NULL, GTK_MESSAGE_ERROR, message, NULL, NULL);
1020         g_free (message);
1021         return 0;
1022     }
1023     if(g_mkdir_with_parents (dir, 0700) < 0) {
1024         //DBG("cannot create %s: %s\n", dir, strerror(errno));
1025     }
1026     if(rfm_g_file_test (dir, G_FILE_TEST_EXISTS)) {
1027         if(rfm_g_file_test (dir, G_FILE_TEST_IS_DIR)) {
1028             return 0;
1029         }
1030     }
1031     NOOP ("!rfm_g_file_test(%s, G_FILE_TEST_IS_DIR\n", dir);
1032     return -1;
1033 }
1034 
1035 void
rfm_save_to_go_history(char * p)1036 rfm_save_to_go_history (char *p) {
1037     gchar *f = g_build_filename (GOTO_DBH_FILE, NULL);
1038     COMBOBOX_save_to_history (f, p);
1039     g_free (f);
1040 }
1041 
1042 void
rfm_init_env(void)1043 rfm_init_env (void) {
1044     gint i;
1045     static gsize initialized = 0;
1046     if (g_once_init_enter (&initialized)){
1047       environ_t *environ_v = rfm_get_environ();
1048       // XXX: This initialization may now be obsolete:
1049       //      must check.
1050       for(i = 0; environ_v[i].env_var; i++) {
1051         // copy default values from static memory to heap
1052         if(strcmp (environ_v[i].env_var, "SUDO_ASKPASS") == 0 ||
1053 	    strcmp(environ_v[i].env_var, "SSH_ASKPASS") == 0 ) {
1054             environ_v[i].env_string = g_find_program_in_path ("rodent-getpass");
1055 	}
1056 	else if(environ_v[i].env_string) {
1057             environ_v[i].env_string = g_strdup (environ_v[i].env_string);
1058             NOOP ("ENV: %s %s\n", environ_v[i].env_var, environ_v[i].env_string);
1059         }
1060       }
1061       g_once_init_leave (&initialized, 1);
1062     }
1063     return;
1064 }
1065 
1066 
1067 void
rfm_setenv(const gchar * name,gchar * value,gboolean verbose)1068 rfm_setenv (const gchar *name, gchar *value, gboolean verbose) {
1069     rfm_init_env();
1070     NOOP(stderr, "setting %s to %s\n", name, value);
1071     gint which;
1072     gboolean go_ahead = FALSE;
1073     environ_t *environ_v = rfm_get_environ();
1074     for(which = 0; environ_v[which].env_var; which++){
1075         if(strcmp (name, environ_v[which].env_var) == 0){
1076             break;
1077 	}
1078     }
1079     if(!environ_v[which].env_var) return;
1080     if(!value || !strlen (value)) {
1081         g_free (environ_v[which].env_string);
1082 #ifdef HAVE_UNSETENV
1083         environ_v[which].env_string = NULL;
1084         unsetenv (name);
1085 #else
1086         environ_v[which].env_string = g_strconcat (name, "=", NULL);
1087         putenv (environ_v[which].env_string);
1088 #endif
1089         /*if(verbose) {
1090             if(strcmp (name, "SMB_USER") == 0) {
1091                 TRACE("Mcs manager changed rfm environment: %s\n", name);
1092             } else {
1093                 TRACE("Mcs manager changed rfm environment: %s=%s\n", name, ((value) ? value : " "));
1094             }
1095         }*/
1096         return;
1097     }
1098     if(strcmp (name, "RFM_MAX_PREVIEW_SIZE") == 0) {
1099         if(is_number (value))
1100             go_ahead = TRUE;
1101     } else if(strcmp (name, "TERMCMD") == 0) {
1102         if(value && strlen (value)) {
1103             gchar *c;
1104             gchar *t = g_strdup (value);
1105             t = g_strstrip (t);
1106             if(strchr (t, ' '))
1107                 t = strtok (t, " ");
1108             c = g_find_program_in_path (t);
1109             if(c && access (c, X_OK) == 0) {
1110                 go_ahead = TRUE;
1111             }
1112             g_free (c);
1113             c = NULL;
1114             g_free (t);
1115             t = NULL;
1116         }
1117     } else
1118         go_ahead = TRUE;
1119     if(go_ahead) {
1120         g_free (environ_v[which].env_string);
1121 	gchar *getpass = NULL;
1122 	gchar *editor = NULL;
1123 	if (strcmp (name, "EDITOR") == 0){
1124 	    editor = rfm_get_text_editor_envar(value);
1125 	    NOOP(stderr, "Setting text editor to %s\n", editor);
1126 	}
1127 	if (editor){
1128 	    value = editor;
1129 	} else {
1130 	    if (strcmp (name, "SUDO_ASKPASS") == 0 ||
1131 		strcmp(name, "SSH_ASKPASS") == 0 ){
1132 		if (!g_file_test(value, G_FILE_TEST_EXISTS)){
1133 		    getpass = g_find_program_in_path ("rodent-getpass");
1134 		}
1135 	    }
1136 	}
1137 	if (getpass) value = getpass;
1138 
1139 
1140 	NOOP(stderr, "rfm_setenv(): setting %s -> %s \n", name, value);
1141 #ifdef HAVE_SETENV
1142         environ_v[which].env_string = g_strdup (value);
1143         setenv (name, environ_v[which].env_string, 1);
1144 #else
1145         environ_v[which].env_string = g_strconcat (name, "=", value, NULL);
1146         putenv (environ_v[which].env_string);
1147 #endif
1148 	g_free(editor);
1149     } else {                    /* not go_ahead */
1150         DBG ("failed to change rfm environment: %s\n", name);
1151     }
1152     return;
1153 }
1154 
1155 void
rfm_threadwait(void)1156 rfm_threadwait (void) {
1157     struct timespec thread_wait = {
1158         0, 100000000
1159     };
1160     nanosleep (&thread_wait, NULL);
1161 }
1162 
1163 #ifdef DEBUG_NOOP
1164 # include <sys/times.h>
1165 static struct tms *last_clock = NULL;
1166 
1167 const gchar *
profile(void)1168 profile (void) {
1169     struct tms this_clock;
1170     static gchar *p = NULL;
1171     g_free (p);
1172     if(!last_clock) {
1173         last_clock = (struct tms *)malloc (sizeof (struct tms));
1174         times (last_clock);
1175     }
1176     times (&this_clock);
1177     p = g_strdup_printf ("\n\tPROFILE*** user=%ld, system=%ld",
1178                          (long)(this_clock.tms_utime - last_clock->tms_utime),
1179                          (long)(this_clock.tms_stime - last_clock->tms_stime));
1180     memcpy (last_clock, &this_clock, sizeof (struct tms));
1181 
1182     return (const gchar *)p;
1183 }
1184 #endif
1185 static gboolean
rect_OK(view_t * view_p,GdkRectangle * rect_p)1186 rect_OK(view_t * view_p, GdkRectangle * rect_p){
1187     // nope return TRUE;
1188     if (view_p->flags.type == DESKVIEW_TYPE) return TRUE;
1189     GtkScrolledWindow *scrolled_window = g_object_get_data(G_OBJECT(view_p->widgets.paper), "scrolled_window");
1190     if (!scrolled_window || !GTK_IS_SCROLLED_WINDOW(scrolled_window)) return FALSE;
1191     if (!GTK_IS_ADJUSTMENT(
1192 	gtk_scrolled_window_get_vadjustment (scrolled_window)
1193 	)) return TRUE;
1194     g_mutex_lock(view_p->mutexes.status_mutex);
1195     gint  status = view_p->flags.status;
1196     g_mutex_unlock(view_p->mutexes.status_mutex);
1197     if (status == STATUS_EXIT) return FALSE;
1198 
1199     gdouble position=gtk_adjustment_get_value (
1200 	    gtk_scrolled_window_get_vadjustment (scrolled_window));
1201     gdouble page=gtk_adjustment_get_page_size (
1202 	    gtk_scrolled_window_get_vadjustment (scrolled_window));
1203     if (rect_p->y >= position && rect_p->y <= position + page + 0.9){
1204 	return TRUE;
1205     }
1206     if (rect_p->y + rect_p->height >= position &&
1207 	    rect_p->y + rect_p->height <= position + page + 0.9){
1208 	return TRUE;
1209     }
1210     NOOP(stderr, "y=(%d,%d), page=%lf position=%lf\n",
1211 	    rect_p->y, rect_p->y + rect_p->height,
1212 	    page, position);
1213     return FALSE;
1214 }
1215 
1216 
1217 typedef struct expose_rect_t {
1218     view_t *view_p;
1219     GdkRectangle rect;
1220 } expose_rect_t;
1221 
1222 static void
expose_it(expose_rect_t * expose_rect_p)1223 expose_it(expose_rect_t *expose_rect_p){
1224      NOOP(stderr, "expose_it %d,%d %d,%d --\n",
1225 	     expose_rect_p->rect.x, expose_rect_p->rect.y,
1226 	     expose_rect_p->rect.width, expose_rect_p->rect.height);
1227     GdkWindow * window;
1228     window = gtk_widget_get_window(expose_rect_p->view_p->widgets.paper);
1229     gdk_window_invalidate_rect (window, &(expose_rect_p->rect), TRUE);
1230 }
1231 
1232 // main context function
1233 static gboolean
expose_rect_f(gpointer data)1234 expose_rect_f(gpointer data){
1235     expose_rect_t *expose_rect_p = data;
1236     if (rfm_main_context_check(expose_rect_p->view_p, FALSE)){
1237 	expose_it(expose_rect_p);
1238     }
1239     g_free(data);
1240     return FALSE;
1241 }
1242 
1243 
1244 void
rfm_expose_rect(view_t * view_p,const GdkRectangle * rect_p)1245 rfm_expose_rect (view_t * view_p, const GdkRectangle *rect_p) {
1246     TRACE("rfm_expose_rect()...\n");
1247     if (!rfm_view_list_lock(view_p, "rfm_expose_rect")) return;
1248     if (rfm_get_gtk_thread() == g_thread_self()) {
1249 	expose_rect_t expose_rect_v;
1250 	expose_rect_v.view_p = view_p;
1251 	memcpy(&(expose_rect_v.rect), rect_p, sizeof(GdkRectangle));
1252 	expose_it(&expose_rect_v);
1253         rfm_view_list_unlock("rfm_expose_rect");
1254 	return;
1255     }
1256     expose_rect_t *expose_rect_p = (expose_rect_t *)malloc(sizeof(expose_rect_t));
1257     if (!expose_rect_p) g_error("malloc: %s\n", strerror(errno));
1258     expose_rect_p->view_p = view_p;
1259     memcpy(&(expose_rect_p->rect), rect_p, sizeof(GdkRectangle));
1260     // don't wait here
1261     g_main_context_invoke (NULL, expose_rect_f, expose_rect_p);
1262     rfm_view_list_unlock("rfm_expose_rect");
1263     return;
1264 }
1265 
1266 
1267 // non threaded:
1268 
1269 // the  +5 refers to the red outline and the cute shadow and is less
1270 // than the empty space between icon and text (< TEXTSPACING)
1271 void
rfm_expose_icon(view_t * view_p,const population_t * population_p)1272 rfm_expose_icon (view_t * view_p, const population_t * population_p) {
1273     NOOP (">> rfm_expose_icon\n");
1274     GdkRectangle rect;
1275     if (!population_p) return;
1276     if (!rfm_get_population_rect(view_p, population_p, &rect))return;
1277     gint icon_size = ICON_SIZE(view_p);
1278     if (icon_size >= SMALL_ICON_SIZE) {
1279 	rect.width = CELLWIDTH(view_p);
1280 	rect.height = icon_size + 5;
1281     } else  if (icon_size >= TINY_ICON_SIZE){
1282 	rect.width = rect.height = icon_size+2;
1283     }
1284     else {
1285 	rect.width = rect.height = TINY_ICON_SIZE+2;
1286 
1287     }
1288 
1289     if (!rect_OK(view_p, &rect)){
1290 	NOOP(stderr, "icon for %s is out of sight!\n",
1291 		(population_p->en)?population_p->en->path:"null");
1292 	return;
1293     }
1294     rfm_expose_rect(view_p, &rect);
1295     //gdk_window_invalidate_rect (gtk_widget_get_window(view_p->widgets.paper), &rect, TRUE);
1296     return;
1297 }
1298 
1299 // This should only be for saturated case, otherwise a plain expose item.
1300 void
rfm_expose_label(view_t * view_p,const population_t * population_p)1301 rfm_expose_label (view_t * view_p, const population_t * population_p) {
1302     if (!population_p) return;
1303     NOOP (">> rfm_expose_label (full)\n");
1304     GdkRectangle rect;
1305 
1306     if (ICON_SIZE_ID(view_p) == 0) {
1307  	if (!rfm_get_population_rect(view_p, population_p, &rect)){
1308 	    return;
1309 	}
1310 
1311    } else {
1312 	if (!rfm_get_population_label_rect_full(view_p, population_p, &rect)){
1313 	    return;
1314 	}
1315 	if (ICON_SIZE(view_p) >= SMALL_ICON_SIZE){
1316 	    // I wonder where the 2 comes from (required).
1317 	    rect.height = CELLHEIGHT(view_p) - (TEXTSPACING + ICON_SIZE(view_p) + 2);
1318 	}
1319     }
1320 
1321     // Expose double height to erase non saturated text (hack!)
1322     // rect.height *= 2;
1323 
1324     if (!rect_OK(view_p, &rect)){
1325 	TRACE( "label for %s is out of sight!\n",
1326 		(population_p->en)?population_p->en->path:"null");
1327 	return;
1328     }
1329     NOOP(stderr, "expose label: %s x: %d-%d y: %d-%d\n",
1330 	    population_p->label,
1331 	    rect.x, rect.x+rect.width, rect.y, rect.y+rect.height);
1332     rfm_expose_rect(view_p, &rect);
1333     //gdk_window_invalidate_rect (gtk_widget_get_window(view_p->widgets.paper), &rect, TRUE);
1334     return;
1335 }
1336 
1337 void
rfm_expose_item(view_t * view_p,const population_t * population_p)1338 rfm_expose_item (view_t * view_p, const population_t * population_p) {
1339     GdkRectangle rect;
1340     if (!rfm_get_population_rect(view_p, population_p, &rect)) return;
1341     NOOP ("1>> rfm_expose_item %d,%d %d,%d\n", rect.x, rect.y, rect.width, rect.height);
1342     NOOP ("2>> rfm_expose_item %d,%d %d,%d\n", rect.x, rect.y, rect.width, rect.height);
1343     if (!rect_OK(view_p, &rect)){
1344 	NOOP(stderr, "item for %s is out of sight!\n",
1345 		(population_p->en)?population_p->en->path:"null");
1346 	NOOP (stderr, "2>> rfm_expose_item %d,%d %d,%d\n", rect.x, rect.y, rect.width, rect.height);
1347 	return;
1348     }
1349     rfm_expose_rect(view_p, &rect);
1350     //gdk_window_invalidate_rect (gtk_widget_get_window(view_p->widgets.paper), &rect, TRUE);
1351     return;
1352 }
1353 
1354 // client based pasteboard (with MIT-shm)
1355 // no interclient communication, but then again,
1356 // I really never found any use for it
1357 
1358 
1359 void
rfm_clear_paste_buffer(void)1360 rfm_clear_paste_buffer(void){
1361     rfm_rw_lock_writer_lock(&drag_info_lock);
1362     shm_unlink (PASTE_SHM_NAME);
1363     rfm_rw_lock_writer_unlock(&drag_info_lock);
1364 }
1365 
1366 void
rfm_store_paste_buffer(gchar * buffer,gint len)1367 rfm_store_paste_buffer(gchar *buffer, gint len){
1368     rfm_rw_lock_writer_lock(&drag_info_lock);
1369 
1370     // Remove old MIT-shm  pasteboard.
1371     shm_unlink (PASTE_SHM_NAME);
1372 
1373     gint fd = shm_open (PASTE_SHM_NAME, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
1374     if(fd < 0){
1375         g_error ("rfm_store_paste_buffer(): shm_open(%s): %s", PASTE_SHM_NAME, strerror (errno));
1376     }
1377     // Get writelock .
1378 
1379     // Truncate to necessary memory size to allocate.
1380     if(ftruncate (fd, sizeof(gint)+strlen(buffer)+1) < 0) {
1381         g_error ("rfm_store_paste_buffer(): ftruncate(%s): %s", PASTE_SHM_NAME, strerror (errno));
1382     }
1383 
1384     // Get a shared memory pointer.
1385     void *p = mmap (NULL, sizeof(gint)+strlen(buffer)+1,
1386 	    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1387    // Close file descriptor
1388     close(fd);
1389     // Save size as first sizeof(gint) bytes.
1390     gint *len_p = p;
1391     *len_p=sizeof(gint)+strlen(buffer)+1;
1392     // Save text buffer now.
1393     gchar *buffer_p = p + sizeof(gint);
1394     strcpy(buffer_p, buffer);
1395     // Put in shared memory.
1396     if(msync (p, sizeof(gint)+strlen(buffer)+1, MS_SYNC) < 0){
1397         DBG ("rfm_store_paste_buffer(): msync(%s): %s\n", PASTE_SHM_NAME, strerror (errno));
1398     }
1399     // Release map so other processes may shm_unlink.
1400     munmap (p, sizeof(gint)+strlen(buffer)+1);
1401 
1402 
1403     // release writelock on shm_name file descriptor
1404     rfm_rw_lock_writer_unlock(&drag_info_lock);
1405 }
1406 
1407 static
get_paste_length()1408 gint get_paste_length(){
1409     gint fd = shm_open (PASTE_SHM_NAME, O_RDONLY, S_IRUSR | S_IWUSR);
1410     if(fd < 0){
1411 	return 0;
1412     }
1413     // Get readlock on shm_name file descriptor.
1414     rfm_rw_lock_reader_lock(&drag_info_lock);
1415 
1416     // Figure out the size.
1417     void *p = mmap (NULL, sizeof(gint),
1418     //void *p = mmap (NULL, 133,
1419 	    PROT_READ, MAP_SHARED, fd, 0);
1420     close(fd);
1421 
1422     gint len = *((gint *)p);
1423     if(msync (p, sizeof(gint), MS_SYNC) < 0){
1424         DBG ("msync(%s): %s\n", PASTE_SHM_NAME, strerror (errno));
1425     }
1426     munmap (p, sizeof(gint));
1427     // release writelock on shm_name file descriptor
1428     rfm_rw_lock_reader_unlock(&drag_info_lock);
1429     return len;
1430 }
1431 
1432 
1433 gchar *
rfm_get_paste_buffer(void)1434 rfm_get_paste_buffer (void ) {
1435     // Get readlock on shm_name file descriptor.
1436     rfm_rw_lock_reader_lock(&drag_info_lock);
1437     gint len=get_paste_length();
1438     if (len==0) {
1439 	rfm_rw_lock_reader_unlock(&drag_info_lock);
1440 	return NULL;
1441     }
1442     int fd = shm_open (PASTE_SHM_NAME, O_RDONLY, S_IRUSR | S_IWUSR);
1443     if(fd < 0){
1444 	rfm_rw_lock_reader_unlock(&drag_info_lock);
1445 	return NULL;
1446     }
1447 
1448 
1449     void *pp = mmap (NULL, len, PROT_READ, MAP_SHARED, fd, 0);
1450     gchar *buffer_p = pp + sizeof(gint);
1451     gchar *buffer = g_strdup(buffer_p);
1452     munmap (pp, len);
1453     // release writelock on shm_name file descriptor
1454     close(fd);
1455     rfm_rw_lock_reader_unlock(&drag_info_lock);
1456     return buffer;
1457 }
1458 
1459 
1460 int
rfm_pasteboard_status(view_t * view_p)1461 rfm_pasteboard_status (view_t * view_p) {
1462     if(!view_p) return 0;
1463     // This is cool with client side pasteboard because
1464     // nobody is going to mess with pasteboard.
1465     // With server side, this is not so, because somebody
1466     // might mess it up and rodent will still think the pasteboard
1467     // is valid...
1468 
1469     // This crashes with multithread server side pasteboard:
1470     rfm_update_pasteboard (view_p);
1471 
1472     gchar *b = view_p->xbuffer;
1473     if(!b || !strlen (b)) return 0;
1474 
1475     const gchar *cut = "#xfvalid_buffer:cut";
1476     const gchar *copy = "#xfvalid_buffer:copy";
1477     if(strncmp (b, copy, strlen (copy)) == 0)
1478         return 1;
1479     if(strncmp (b, cut, strlen (cut)) == 0)
1480         return 2;
1481     return 0;
1482 }
1483 
1484 gchar **
rfm_pasteboard_v(view_t * view_p)1485 rfm_pasteboard_v(view_t * view_p){
1486     if (!rfm_pasteboard_status (view_p)) return NULL;
1487     // this is to skip the valid buffer line:
1488     gchar *search = strchr (view_p->xbuffer, '\n');
1489     if(!search)return NULL;
1490     search++;
1491     gchar **v = g_strsplit(search, "\n", -1);
1492     return v;
1493 }
1494 
1495 
1496 /* returns 0 if not in pasteboard, 1 if in copy pasteboard or 2 if
1497  * in cut pasteboard */
1498 int
rfm_in_pasteboard(view_t * view_p,record_entry_t * en)1499 rfm_in_pasteboard (view_t * view_p, record_entry_t * en) {
1500     if(!en || !en->path) return FALSE;
1501     if(IS_ROOT_TYPE (en->type) && !IS_SDIR(en->type)) return FALSE;
1502 
1503     gchar **pasteboard_v = rfm_pasteboard_v(view_p);
1504     gchar **p = pasteboard_v;
1505     gint retval=0;
1506     gint status = rfm_pasteboard_status (view_p);
1507     for(; p && *p; p++){
1508         if(strcmp (*p, en->path) == 0) {
1509             retval = status;
1510             break;
1511         }
1512     }
1513     g_strfreev(pasteboard_v);
1514     return retval;
1515 #if 0
1516     obsolete...
1517     int paste_type = rfm_valid_pasteboard (view_p);
1518     if(!paste_type) return FALSE;
1519     gchar *b = g_strdup (view_p->xbuffer);
1520 
1521     // this is the valid buffer line:
1522     gchar *search = strtok (b, "\n");
1523     if(!search) {
1524         g_free (b);
1525         return 0;
1526     }
1527     // now we check line per line for the path.
1528     search = strtok (NULL, "\n");
1529     while(search) {
1530         if(strcmp (search, en->path) == 0) {
1531             NOOP ("PASTE type =%d %s\n", paste_type, en->path);
1532             g_free (b);
1533             return paste_type;
1534         }
1535         search = strtok (NULL, "\n");
1536     }
1537     g_free (b);
1538     return 0;
1539 #endif
1540 }
1541 
1542 gboolean
rfm_update_pasteboard(view_t * view_p)1543 rfm_update_pasteboard (view_t * view_p) {
1544     if(!view_p->xbuffer) view_p->xbuffer = rfm_get_paste_buffer ();
1545     gchar *current_xbuffer = rfm_get_paste_buffer ();
1546     if (!current_xbuffer && !view_p->xbuffer) return FALSE;
1547     if(!view_p->xbuffer && current_xbuffer){
1548         view_p->xbuffer = current_xbuffer;
1549         return TRUE;
1550     }
1551     if(view_p->xbuffer && !current_xbuffer){
1552         g_free (view_p->xbuffer);
1553         view_p->xbuffer = NULL;
1554         return TRUE;
1555     }
1556     // here both pointers are valid
1557     if(strcmp (current_xbuffer, view_p->xbuffer)) {
1558         NOOP ("XBUFFER: xbuffer has changed! %s\n", current_xbuffer);
1559         g_free (view_p->xbuffer);
1560         view_p->xbuffer = current_xbuffer;
1561         return TRUE;
1562     } else {
1563         NOOP ("XBUFFER: xbuffer OK!\n");
1564         g_free (current_xbuffer);
1565         return FALSE;
1566     }
1567 
1568 }
1569 
1570 ////////////////////////////////////////////////////////////////////////////////////////////
1571 
1572 gboolean
rfm_write_ok_path(const gchar * target_path)1573 rfm_write_ok_path(const gchar *target_path){
1574     if (!target_path) return FALSE;
1575     if (!g_path_is_absolute(target_path)){
1576 	DBG("rfm_write_ok_path() is FALSE: %s is not absolute!\n",
1577 		target_path);
1578 	return FALSE;
1579     }
1580 
1581     gchar *dirname = NULL;
1582     if (rfm_g_file_test (target_path, G_FILE_TEST_IS_DIR)) {
1583 	dirname =g_strdup(target_path);
1584     } else {
1585 	dirname = g_path_get_dirname(target_path);
1586     }
1587     gboolean result = access(dirname, W_OK);
1588     g_free(dirname);
1589     return (result<0)?0:1;
1590 }
1591 
1592 
1593 gboolean
rfm_read_ok_path(const gchar * target_path)1594 rfm_read_ok_path(const gchar *target_path){
1595     if (!target_path) return FALSE;
1596     if (!g_path_is_absolute(target_path)){
1597 	DBG("rfm_read_ok_path() is FALSE: %s is not absolute!\n",
1598 		target_path);
1599 	return FALSE;
1600     }
1601 
1602 
1603     gboolean result = access(target_path, R_OK);
1604     return (result<0)?0:1;
1605 
1606 }
1607 
1608 static
1609 gchar *
default_shell(void)1610 default_shell(void){
1611     gchar *shell=NULL;
1612     if(!shell)
1613         shell = g_find_program_in_path ("bash");
1614     if(!shell)
1615         shell = g_find_program_in_path ("zsh");
1616     if(!shell)
1617         shell = g_find_program_in_path ("sh");
1618     if (!shell && rfm_void(PLUGIN_DIR, "ps", "module_active")) {
1619 	shell = g_find_program_in_path ("tcsh");
1620 	if(!shell)
1621 	    shell = g_find_program_in_path ("csh");
1622     }
1623     if(!shell)
1624         shell = g_find_program_in_path ("ksh");
1625     if(!shell)
1626         shell = g_find_program_in_path ("sash");
1627     if(!shell)
1628         shell = g_find_program_in_path ("ash");
1629     if(!shell){
1630 	DBG("unable to find a valid shell\n");
1631     }
1632 
1633     return shell;
1634 }
1635 
1636     // dash is OK now.
1637     // Only csh/tcsh is broken, since it will not
1638     // pass on SIGTERM when controler gets SIGUSR1
1639     // This is only a problem if rodent_ps is not
1640     // loadable.
1641     // gchar *
1642 static gchar *
check_shell(gchar * shell)1643 check_shell(gchar *shell){
1644     if (!shell) return NULL;
1645     // This is because the stop process button will not work with csh.
1646     if (!rfm_void(PLUGIN_DIR, "ps", "module_active") && strstr(shell, "csh")) {
1647 	g_free(shell);
1648 	shell = NULL;
1649     }
1650     return shell;
1651 }
1652 
1653 gchar *
rfm_shell(void)1654 rfm_shell(void){
1655     gchar *shell=NULL;
1656     if(getenv ("SHELL") && strlen (getenv ("SHELL"))) {
1657         shell = g_find_program_in_path (getenv ("SHELL"));
1658     }
1659 
1660     if(!shell && getenv ("XTERM_SHELL") && strlen (getenv ("XTERM_SHELL"))) {
1661         shell = g_find_program_in_path (getenv ("XTERM_SHELL"));
1662     }
1663     shell = check_shell(shell);
1664 
1665     if (!shell){
1666 	shell = default_shell();
1667     }
1668     return shell;
1669 }
1670 
1671 gchar *
rfm_xterm_shell(void)1672 rfm_xterm_shell(void){
1673     gchar *shell=NULL;
1674 
1675     if(!shell && getenv ("XTERM_SHELL") && strlen (getenv ("XTERM_SHELL"))) {
1676         shell = g_find_program_in_path (getenv ("XTERM_SHELL"));
1677     }
1678     if(!shell && getenv ("SHELL") && strlen (getenv ("SHELL"))) {
1679         shell = g_find_program_in_path (getenv ("SHELL"));
1680     }
1681     shell = check_shell(shell);
1682     if (!shell){
1683 	shell = default_shell();
1684     }
1685     return shell;
1686 }
1687 
1688 
1689 // Quickie test
1690 population_t *
rfm_locate_path(view_t * view_p,const gchar * pathname)1691 rfm_locate_path(view_t *view_p, const gchar *pathname){
1692     if (!view_p || ! view_p->en || !view_p->population_pp) return NULL;
1693     if (!rfm_population_read_lock(view_p, "rfm_locate_path")) return NULL;
1694     population_t **pp = view_p->population_pp;
1695     for (;pp && *pp; pp++){
1696 	population_t *p = *pp;
1697 	if (p->en && strcmp(p->en->path, pathname)==0){
1698 	    rfm_population_read_unlock(view_p, "rfm_locate_path");
1699 	    return p;
1700 	}
1701     }
1702     rfm_population_read_unlock(view_p, "rfm_locate_path");
1703     return NULL;
1704 }
1705 
1706 
1707 /////////////////   timeout functinality  ////////////////////////////////7
1708 //#define DISABLE_FILE_TEST_WITH_TIMEOUT 1
1709 #ifdef DISABLE_FILE_TEST_WITH_TIMEOUT
1710 gboolean
rfm_g_file_test_with_wait(const gchar * path,GFileTest test)1711 rfm_g_file_test_with_wait(const gchar *path, GFileTest test){
1712     return rfm_g_file_test(path, test);
1713 }
1714 
1715 #else
1716 typedef struct heartbeat_t{
1717     gboolean condition;
1718     GMutex *mutex;
1719     GCond *signal;
1720     GThread *thread;
1721     gchar *path;
1722     GFileTest test;
1723 } heartbeat_t;
1724 
1725 static void *
heartbeat_g_file_test(gpointer data)1726 heartbeat_g_file_test(gpointer data){
1727     heartbeat_t *heartbeat_p = data;
1728 
1729     // This function call may block
1730     NOOP("heartbeat doing stat %s\n", heartbeat_p->path);
1731     struct stat st;
1732     if (lstat(heartbeat_p->path, &st) < 0) return NULL;
1733 
1734     // If test is not for symlink, and item is a symlink,
1735     // then follow the symlink for the test.
1736     if (S_ISLNK(st.st_mode)){
1737 	if (heartbeat_p->test == G_FILE_TEST_IS_SYMLINK){
1738 	    return GINT_TO_POINTER(TRUE);
1739 	}
1740 	if (stat(heartbeat_p->path, &st) < 0) return NULL;
1741     }
1742 
1743     gboolean retval = FALSE;
1744     switch (heartbeat_p->test){
1745 	case G_FILE_TEST_EXISTS: retval = TRUE; break;
1746 	case G_FILE_TEST_IS_REGULAR: retval = S_ISREG(st.st_mode); break;
1747 	case G_FILE_TEST_IS_EXECUTABLE:
1748 	    retval = ((st.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) && S_ISREG(st.st_mode));
1749 	    break;
1750 	case G_FILE_TEST_IS_SYMLINK: retval = S_ISLNK(st.st_mode); break;
1751 	case G_FILE_TEST_IS_DIR: retval = S_ISDIR (st.st_mode); break;
1752 
1753     }
1754 
1755     g_mutex_lock(heartbeat_p->mutex);
1756     heartbeat_p->condition = TRUE;
1757     g_mutex_unlock(heartbeat_p->mutex);
1758     NOOP("heartbeat signal %d\n", retval);
1759     g_cond_signal(heartbeat_p->signal);
1760     return GINT_TO_POINTER(retval);
1761 
1762 }
1763 
1764 static
wait_on_thread(gpointer data)1765 void *wait_on_thread(gpointer data){
1766     heartbeat_t *heartbeat_p = data;
1767     void *value = g_thread_join(heartbeat_p->thread);
1768 
1769     rfm_mutex_free(heartbeat_p->mutex);
1770     rfm_cond_free(heartbeat_p->signal);
1771     g_free (heartbeat_p->path);
1772     g_free (heartbeat_p);
1773     return value;
1774 }
1775 
1776 // g_file_test_with_timeout
1777 gboolean
rfm_g_file_test_with_wait(const gchar * path,GFileTest test)1778 rfm_g_file_test_with_wait(const gchar *path, GFileTest test){
1779     if (!path) return FALSE;
1780     if (!g_path_is_absolute(path)) return FALSE;
1781     NOOP(stderr, "rfm_g_file_test_with_wait: %s\n", path);
1782 
1783     heartbeat_t *heartbeat_p = (heartbeat_t *)malloc(sizeof(heartbeat_t));
1784     if (!heartbeat_p) g_error("malloc heartbeat_p: %s\n",strerror(errno));
1785     memset(heartbeat_p, 0, sizeof(heartbeat_t));
1786 
1787     rfm_mutex_init(heartbeat_p->mutex);
1788     rfm_cond_init(heartbeat_p->signal);
1789     heartbeat_p->condition = 0;
1790     heartbeat_p->path = g_strdup(path);
1791     heartbeat_p->test = test;
1792 
1793     g_mutex_lock(heartbeat_p->mutex);
1794     NOOP("Creating wait thread for heartbeat_g_file_test_with_timeout\n");
1795     // This thread does not affect view nor window.
1796     heartbeat_p->thread =
1797 	rfm_thread_create ("heartbeat_g_file_test", heartbeat_g_file_test, heartbeat_p, TRUE);
1798     if (heartbeat_p->thread && !heartbeat_p->condition) {
1799 	if (!rfm_cond_timed_wait(heartbeat_p->signal, heartbeat_p->mutex, 2)) {
1800 	    g_mutex_unlock(heartbeat_p->mutex);
1801 	    NOOP("dead heartbeat: rfm_g_file_test\n");
1802 	    // Dead heartbeat:
1803 	    // Fire off a wait and cleanup thread.
1804 	    rfm_thread_create ("wait_on_thread", wait_on_thread, heartbeat_p, FALSE);
1805 	    return FALSE;
1806 	}
1807     }
1808     g_mutex_unlock(heartbeat_p->mutex);
1809     return (GPOINTER_TO_INT(wait_on_thread(heartbeat_p)));
1810 
1811 }
1812 #endif
1813 gboolean
rfm_g_file_test(const gchar * path,GFileTest test)1814 rfm_g_file_test(const gchar *path, GFileTest test){
1815     if (!path) return FALSE;
1816     if (!g_path_is_absolute(path)) return FALSE;
1817     return g_file_test(path, test);
1818 }
1819 
1820 
1821