1 // machinestate.c
2 // LiVES
3 // (c) G. Finch 2019 - 2020 <salsaman+lives@gmail.com>
4 // released under the GNU GPL 3 or later
5 // see file ../COPYING for licensing details
6 
7 // functions for dealing with externalities
8 
9 #include <sys/statvfs.h>
10 #include "main.h"
11 #include "callbacks.h"
12 
13 LIVES_LOCAL_INLINE char *mini_popen(char *cmd);
14 
15 #if IS_X86_64
16 
cpuid(unsigned int ax,unsigned int * p)17 static void cpuid(unsigned int ax, unsigned int *p) {
18   __asm __volatile
19   ("movl %%ebx, %%esi\n\tcpuid\n\txchgl %%ebx, %%esi"
20    : "=a"(p[0]), "=S"(p[1]), "=c"(p[2]), "=d"(p[3])
21    : "0"(ax));
22 }
23 
get_cacheline_size(void)24 static int get_cacheline_size(void) {
25   unsigned int cacheline = -1;
26   unsigned int regs[4], regs2[4];
27   cpuid(0x00000000, regs);
28   if (regs[0] >= 0x00000001) {
29     cpuid(0x00000001, regs2);
30     cacheline = ((regs2[1] >> 8) & 0xFF) * 8;
31     //has_sse2 = (regs2[3] & 0x4000000) ? TRUE : FALSE;
32   }
33   return cacheline;
34 }
35 
36 #endif
37 
38 static uint64_t fastrand_val = 0;
39 
fastrand(void)40 LIVES_GLOBAL_INLINE uint64_t fastrand(void) {
41   fastrand_val ^= (fastrand_val << 13); fastrand_val ^= (fastrand_val >> 7);
42   fastrand_val = ((fastrand_val & 0xFFFFFFFF00000000) >> 32) | ((fastrand_val & 0xFFFFFFFF) << 32);
43   fastrand_val ^= (fastrand_val << 17);
44   return fastrand_val;
45 }
46 
fastrand_add(uint64_t entropy)47 LIVES_GLOBAL_INLINE void fastrand_add(uint64_t entropy) {fastrand_val += entropy;}
48 
fastrand_dbl(double range)49 LIVES_GLOBAL_INLINE double fastrand_dbl(double range) {
50   static const double divd = (double)(0xFFFFFFFFFFFFFFFF); return (double)fastrand() / divd * range;
51 }
52 
53 /// pick a pseudo random uint between 0 and range (inclusive)
fastrand_int(uint32_t range)54 LIVES_GLOBAL_INLINE uint32_t fastrand_int(uint32_t range) {return (uint32_t)(fastrand_dbl((double)(++range)));}
55 
lives_srandom(unsigned int seed)56 LIVES_GLOBAL_INLINE void lives_srandom(unsigned int seed) {srandom(seed);}
57 
lives_random(void)58 LIVES_GLOBAL_INLINE uint64_t lives_random(void) {return random();}
59 
lives_get_randbytes(void * ptr,size_t size)60 void lives_get_randbytes(void *ptr, size_t size) {
61   if (size <= 8) {
62     uint64_t rbytes = gen_unique_id();
63     lives_memcpy(ptr, &rbytes, size);
64   }
65 }
66 
67 
gen_unique_id(void)68 uint64_t gen_unique_id(void) {
69   static uint64_t last_rnum = 0;
70   uint64_t rnum;
71 #if HAVE_GETENTROPY
72   int randres = getentropy(&rnum, 8);
73 #else
74   int randres = 1;
75 #endif
76   if (randres) {
77     fastrand_val = lives_random();
78     fastrand();
79     fastrand_val ^= lives_get_current_ticks();
80     rnum = fastrand();
81   }
82   /// if we have a genuine RNG for 64 bits, then the probability of generating
83   // a number < 1 billion is approx. 2 ^ 30 / 2 ^ 64 or about 1 chance in 17 trillion
84   // the chance of it happening the first time is thus minscule
85   // and the chance of it happening twice by chance is so unlikely we should discount it
86   if (rnum < BILLIONS(1) && last_rnum < BILLIONS(1)) abort();
87   last_rnum = rnum;
88   return rnum;
89 }
90 
91 
init_random()92 void init_random() {
93   uint32_t rseed;
94 #ifdef HAVE_GETENTROPY
95   if (getentropy(&rseed, 4)) rseed = (gen_unique_id() & 0xFFFFFFFF);
96 #else
97   rseed = (gen_unique_id() & 0xFFFFFFFF);
98 #endif
99   lives_srandom(rseed);
100   fastrand_val = gen_unique_id();
101 }
102 
103 
104 //// AUTO-TUNING ///////
105 
106 struct _decomp {
107   uint64_t value;
108   int i, j;
109 };
110 
111 struct _decomp_tab {
112   uint64_t value;
113   int i, j;
114   struct _decomp_tab *lower,  *higher;
115 };
116 
117 static struct _decomp_tab nxttbl[64][25];
118 static boolean nxttab_inited = FALSE;
119 
make_nxttab(void)120 void make_nxttab(void) {
121   LiVESList *preplist = NULL, *dccl, *dccl_last = NULL;
122   uint64_t val6 = 1ul, val;
123   struct _decomp *dcc;
124   int max2pow, xi, xj;
125   if (nxttab_inited) return;
126   for (int j = 0; j < 25; j++) {
127     val = val6;
128     max2pow = 64 - ((j * 10 + 7) >> 2);
129     dccl = preplist;
130     for (int i = 0; i < max2pow; i++) {
131       dcc = (struct _decomp *)lives_malloc(sizeof(struct _decomp));
132       dcc->value = val;
133       dcc->i = i;
134       dcc->j = j;
135       if (!preplist) dccl = preplist = lives_list_append(preplist, dcc);
136       else {
137         LiVESList *dccl2 = lives_list_append(NULL, (livespointer)dcc);
138         for (; dccl; dccl = dccl->next) {
139           dcc = (struct _decomp *)dccl->data;
140           if (dcc->value > val) break;
141           dccl_last = dccl;
142         }
143         if (!dccl) {
144           dccl_last->next = dccl2;
145           dccl2->prev = dccl_last;
146           dccl2->next = NULL;
147           dccl = dccl2;
148         } else {
149           dccl2->next = dccl;
150           dccl2->prev = dccl->prev;
151           if (dccl->prev) dccl->prev->next = dccl2;
152           else preplist = dccl2;
153           dccl->prev = dccl2;
154         }
155       }
156       val *= 2;
157     }
158     val6 *= 6;
159   }
160   for (dccl = preplist; dccl; dccl = dccl->next) {
161     dcc = (struct _decomp *)dccl->data;
162     xi = dcc->i;
163     xj = dcc->j;
164     nxttbl[xi][xj].value = dcc->value;
165     nxttbl[xi][xj].i = xi;
166     nxttbl[xi][xj].j = xj;
167     if (dccl->prev) {
168       dcc = (struct _decomp *)dccl->prev->data;
169       nxttbl[xi][xj].lower = &(nxttbl[dcc->i][dcc->j]);
170     } else nxttbl[xi][xj].lower = NULL;
171     if (dccl->next) {
172       dcc = (struct _decomp *)dccl->next->data;
173       nxttbl[xi][xj].higher = &(nxttbl[dcc->i][dcc->j]);
174     } else nxttbl[xi][xj].higher = NULL;
175   }
176   lives_list_free_all(&preplist);
177   nxttab_inited = TRUE;
178 }
179 
180 
181 
autotune_u64(weed_plant_t * tuner,uint64_t min,uint64_t max,int ntrials,double cost)182 void autotune_u64(weed_plant_t *tuner,  uint64_t min, uint64_t max, int ntrials, double cost) {
183   if (tuner) {
184     double tc = cost;
185     int trials = weed_get_int_value(tuner, "trials", NULL);
186     if (trials == 0) {
187       weed_set_int_value(tuner, "ntrials", ntrials);
188       weed_set_int64_value(tuner, "min", min);
189       weed_set_int64_value(tuner, "max", max);
190     } else tc += weed_get_double_value(tuner, "tcost", NULL);
191     weed_set_double_value(tuner, "tcost", tc);
192     weed_set_int64_value(tuner, "tstart", lives_get_current_ticks());
193   }
194 }
195 
196 #define NCYCS 16
197 
nxtval(uint64_t val,uint64_t lim,boolean less)198 uint64_t nxtval(uint64_t val, uint64_t lim, boolean less) {
199   // to avoid only checking powers of 2, we want some number which is (2 ** i) * (6 ** j)
200   // which gives a nice range of results
201   uint64_t oval = val;
202   int i = 0, j = 0;
203   if (!nxttab_inited) make_nxttab();
204   /// decompose val into i, j
205   /// divide by 6 until val mod 6 is non zero
206   if (val & 1) {
207     if (less) val--;
208     else val++;
209   }
210   for (; !(val % 6) && val > 0; j++, val /= 6);
211   /// divide by 2 until we reach 1; if the result of a division is odd we add or subtract 1
212   for (; val > 1; i++, val /= 2) {
213     if (val & 1) {
214       if (less) val--;
215       else val++;
216     }
217   }
218   val = nxttbl[i][j].value;
219   if (less) {
220     if (val == oval) {
221       if (nxttbl[i][j].lower) val = nxttbl[i][j].lower->value;
222     } else {
223       while (nxttbl[i][j].higher->value < oval) {
224         int xi = nxttbl[i][j].higher->i;
225         val = nxttbl[i][j].value;
226         j = nxttbl[i][j].higher->j;
227         i = xi;
228       }
229     }
230     return val > lim ? val : lim;
231   }
232   if (val == oval) {
233     if (nxttbl[i][j].higher) val = nxttbl[i][j].higher->value;
234   } else {
235     while (nxttbl[i][j].lower && nxttbl[i][j].lower->value > oval) {
236       int xi = nxttbl[i][j].lower->i;
237       j = nxttbl[i][j].lower->j;
238       i = xi;
239       val = nxttbl[i][j].value;
240     }
241   }
242   return val < lim ? val : lim;
243 }
244 
245 
get_tunert(int idx)246 static const char *get_tunert(int idx) {
247   switch (idx) {
248   case 2: return "orc_memcpy cutoff";
249   case 3: return "read buffer size (small)";
250   case 4: return "read buffer size (small / medium)";
251   case 5: return "read buffer size (medium)";
252   case 6: return "read buffer size (large)";
253   default: break;
254   }
255   return "unknown variable";
256 }
257 
258 
autotune_u64_end(weed_plant_t ** tuner,uint64_t val)259 uint64_t autotune_u64_end(weed_plant_t **tuner, uint64_t val) {
260   if (!tuner || !*tuner) return val;
261   else {
262     ticks_t tottime = lives_get_current_ticks();
263     int ntrials, trials;
264     int64_t max;
265     int64_t min = weed_get_int64_value(*tuner, "min", NULL);
266 
267     if (val < min) {
268       val = min;
269       weed_set_int_value(*tuner, "trials", 0);
270       weed_set_int64_value(*tuner, "tottime", 0);
271       weed_set_double_value(*tuner, "tcost", 0);
272       return val;
273     }
274     max = weed_get_int64_value(*tuner, "max", NULL);
275     if (val > max) {
276       val = max;
277       weed_set_int_value(*tuner, "trials", 0);
278       weed_set_int64_value(*tuner, "tottime", 0);
279       weed_set_double_value(*tuner, "tcost", 0);
280       return val;
281     }
282 
283     ntrials = weed_get_int_value(*tuner, "ntrials", NULL);
284     trials = weed_get_int_value(*tuner, "trials", NULL);
285 
286     weed_set_int_value(*tuner, "trials", ++trials);
287     tottime += (weed_get_int64_value(*tuner, "tottime", NULL)) - weed_get_int64_value(*tuner, "tstart", NULL);
288     weed_set_int64_value(*tuner, "tottime", tottime);
289 
290     if (trials >= ntrials) {
291       int cycs = weed_get_int_value(*tuner, "cycles", NULL) + 1;
292       if (cycs < NCYCS) {
293         double tcost = (double)weed_get_double_value(*tuner, "tcost", NULL);
294         double totcost = (double)tottime * tcost;
295         double avcost = totcost / (double)(cycs * ntrials);
296         double ccosts, ccostl;
297         boolean smfirst = FALSE;
298         char *key1 = lives_strdup_printf("tottrials_%lu", val);
299         char *key2 = lives_strdup_printf("totcost_%lu", val);
300 
301         weed_set_int_value(*tuner, key1, weed_get_int_value(*tuner, key1, NULL) + trials);
302         weed_set_double_value(*tuner, key2, weed_get_double_value(*tuner, key2, NULL) + totcost);
303 
304         lives_free(key1);
305         lives_free(key2);
306 
307         if (cycs & 1) smfirst = TRUE;
308         weed_set_int_value(*tuner, "cycles", cycs);
309 
310         weed_set_int_value(*tuner, "trials", 0);
311         weed_set_int64_value(*tuner, "tottime", 0);
312         weed_set_double_value(*tuner, "tcost", 0);
313 
314         if (smfirst) {
315           if (val > max || weed_plant_has_leaf(*tuner, "smaller")) {
316             ccosts = weed_get_double_value(*tuner, "smaller", NULL);
317             if (val > max || (ccosts < avcost)) {
318               weed_set_double_value(*tuner, "larger", avcost);
319               weed_leaf_delete(*tuner, "smaller");
320               if (val > max) return max;
321               return nxtval(val, min, TRUE); // TRUE to get smaller val
322             }
323           }
324         }
325 
326         if (val < min || weed_plant_has_leaf(*tuner, "larger")) {
327           ccostl = weed_get_double_value(*tuner, "larger", NULL);
328           if (val < min || (ccostl < avcost)) {
329             weed_set_double_value(*tuner, "smaller", avcost);
330             weed_leaf_delete(*tuner, "larger");
331             if (val < min) return min;
332             return nxtval(val, max, FALSE);
333           }
334         }
335 
336         if (!smfirst) {
337           if (val > max || weed_plant_has_leaf(*tuner, "smaller")) {
338             ccosts = weed_get_double_value(*tuner, "smaller", NULL);
339             if (val > max || (ccosts < avcost)) {
340               weed_set_double_value(*tuner, "larger", avcost);
341               weed_leaf_delete(*tuner, "smaller");
342               if (val > max) return max;
343               return nxtval(val, min, TRUE);
344             }
345           }
346 
347           if (!weed_plant_has_leaf(*tuner, "larger")) {
348             weed_set_double_value(*tuner, "smaller", avcost);
349             weed_leaf_delete(*tuner, "larger");
350             return nxtval(val, max, FALSE);
351           }
352         }
353 
354         if (!weed_plant_has_leaf(*tuner, "smaller")) {
355           weed_set_double_value(*tuner, "larger", avcost);
356           weed_leaf_delete(*tuner, "smaller");
357           return nxtval(val, min, TRUE);
358         }
359 
360         if (smfirst) {
361           if (!weed_plant_has_leaf(*tuner, "larger")) {
362             weed_set_double_value(*tuner, "smaller", avcost);
363             weed_leaf_delete(*tuner, "larger");
364             return nxtval(val, max, FALSE);
365           }
366         }
367 
368         weed_leaf_delete(*tuner, "smaller");
369         weed_leaf_delete(*tuner, "larger");
370         if (!smfirst) {
371           return nxtval(nxtval(val, max, FALSE), max, FALSE);
372         } else {
373           return nxtval(nxtval(val, min, TRUE), min, TRUE);
374         }
375       } else {
376         weed_size_t nleaves;
377         char **res = weed_plant_list_leaves(*tuner, &nleaves);
378         uint64_t bestval = val, xval;
379         const char *key1 = "totcost_";
380         char *key2;
381         double avcost, costmin = 0.;
382         boolean gotcost = FALSE;
383         int j;
384 
385         for (int i = 1; i < nleaves; i++) {
386           if (!strncmp(res[i], key1, 8)) {
387             xval = strtoul((const char *)(res[i] + 8), NULL, 10);
388             key2 = lives_strdup_printf("totrials_%lu", xval);
389             for (j = i + 1; j < nleaves; j++) {
390               if (!strcmp(res[j], key2)) break;
391             }
392             if (j == nleaves) {
393               for (j = 0; j < i; j++) {
394                 if (!strcmp(res[j], key2)) break;
395               }
396             }
397             if ((avcost = weed_get_double_value(*tuner, res[i], NULL)
398                           / (double)weed_get_int_value(*tuner, res[j], NULL)) < costmin
399                 || !gotcost) {
400               costmin = avcost;
401               bestval = xval;
402               gotcost = TRUE;
403             }
404             lives_free(key2);
405           }
406         }
407         val = bestval;
408         if (prefs->show_dev_opts)
409           g_printerr("value of %s tuned to %lu\n",
410                      get_tunert(weed_get_int64_value(*tuner, WEED_LEAF_INDEX, NULL)), val);
411         // TODO: store value so we can recalibrate again later
412         //tuned = (struct tuna *)lives_malloc(sizeof(tuna));
413         //tuna->wptpp = tuner;
414         //tuna->id = weed_get_in
415         //lives_list_prepend(tunables, tuned);
416         weed_plant_free(*tuner);
417         *tuner = NULL;
418         for (j = 0; j < nleaves; lives_free(res[j++]));
419         lives_free(res);
420       }
421       return val;
422     }
423     weed_set_int64_value(*tuner, "tottime", tottime);
424   }
425   return val;
426 }
427 
428 
429 /// susbtitute memory functions. These must be real functions and not #defines since we need fn pointers
430 #define OIL_MEMCPY_MAX_BYTES 12288 // this can be tuned to provide optimal performance
431 
432 #ifdef ENABLE_ORC
lives_orc_memcpy(livespointer dest,livesconstpointer src,size_t n)433 livespointer lives_orc_memcpy(livespointer dest, livesconstpointer src, size_t n) {
434   static size_t maxbytes = OIL_MEMCPY_MAX_BYTES;
435   static weed_plant_t *tuner = NULL;
436   static boolean tuned = FALSE;
437   static pthread_mutex_t tuner_mutex = PTHREAD_MUTEX_INITIALIZER;
438   boolean haslock = FALSE;
439   if (n == 0) return dest;
440   if (n < 32) return memcpy(dest, src, n);
441 
442   if (!mainw->multitrack && !LIVES_IS_PLAYING) {
443     if (!tuned && !tuner) tuner = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 2);
444     if (tuner) {
445       if (!pthread_mutex_trylock(&tuner_mutex)) {
446         haslock = TRUE;
447       }
448     }
449   }
450 
451   if (maxbytes > 0 ? n <= maxbytes : n >= -maxbytes) {
452     /// autotuning: first of all we provide the tuning parameters:
453     /// (opaque) weed_plant_t *tuner, (int64_t)min range, (int64_t)max range, (int)ntrials,(double) cost
454     /// the tuner will time from here until autotune_end and multiply the cost by the time
455     /// we also reveal the value of the variable in autotune_end
456     /// the tuner will run this ntrials times, then select a new value for the variable which is returned
457     /// the costs for each value are totalled and averaged and finally the value with the lowest average cost / time is selected
458     /// in this case what we are tuning is the bytesize threshold to select between one memory allocation function and another
459     /// the cost in both cases is defined is 1.0 / n where n is the block size.
460     /// The cost is the same for both functions - since time is also a factor
461     /// the value should simply be the one with the lowest time per byte
462     /// obviously this is very simplistic since there are many other costs than simply the malloc time
463     /// however, it is a simple matter to adjust the cost calculation
464     if (haslock) autotune_u64(tuner, -1024 * 1024, 1024 * 1024, 32, 1. / (double)n);
465     orc_memcpy((uint8_t *)dest, (const uint8_t *)src, n);
466 
467     if (haslock) {
468       maxbytes = autotune_u64_end(&tuner, maxbytes);
469       if (!tuner) tuned = TRUE;
470       pthread_mutex_unlock(&tuner_mutex);
471     }
472     return dest;
473   }
474   if (haslock) autotune_u64(tuner, -1024 * 1024, 1024 * 1024, 128, -1. / (double)n);
475   memcpy(dest, src, n);
476   if (haslock) {
477     maxbytes = autotune_u64_end(&tuner, maxbytes);
478     if (!tuner) tuned = TRUE;
479     pthread_mutex_unlock(&tuner_mutex);
480   }
481   return dest;
482 }
483 #endif
484 
485 
486 #ifdef ENABLE_OIL
lives_oil_memcpy(livespointer dest,livesconstpointer src,size_t n)487 livespointer lives_oil_memcpy(livespointer dest, livesconstpointer src, size_t n) {
488   static size_t maxbytes = OIL_MEMCPY_MAX_BYTES;
489   static weed_plant_t *tuner = NULL;
490   static boolean tuned = FALSE;
491   static pthread_mutex_t tuner_mutex = PTHREAD_MUTEX_INITIALIZER;
492   boolean haslock = FALSE;
493   if (n == 0) return dest;
494   if (n < 32) return memcpy(dest, src, n);
495 
496   if (!mainw->multitrack && !LIVES_IS_PLAYING) {
497     if (!tuned && !tuner) tuner = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 2);
498     if (tuner) {
499       if (!pthread_mutex_trylock(&tuner_mutex)) {
500         haslock = TRUE;
501       }
502     }
503   }
504 
505   if (maxbytes > 0 ? n <= maxbytes : n >= -maxbytes) {
506     if (haslock) autotune_u64(tuner, -1024 * 1024, 1024 * 1024, 32, 1. / (double)n);
507     oil_memcpy((uint8_t *)dest, (const uint8_t *)src, n);
508 
509     if (haslock) {
510       maxbytes = autotune_u64_end(&tuner, maxbytes);
511       if (!tuner) tuned = TRUE;
512       pthread_mutex_unlock(&tuner_mutex);
513     }
514     return dest;
515   }
516   if (haslock) autotune_u64(tuner, -1024 * 1024, 1024 * 1024, 128, -1. / (double)n);
517   memcpy(dest, src, n);
518   if (haslock) {
519     maxbytes = autotune_u64_end(&tuner, maxbytes);
520     if (!tuner) tuned = TRUE;
521     pthread_mutex_unlock(&tuner_mutex);
522   }
523   return dest;
524 }
525 #endif
526 
527 #define _cpy_if_nonnull(d, s, size) (d ? lives_memcpy(d, s, size) : d)
528 
529 // functions with fixed pointers that we can pass to plugins ///
_ext_malloc(size_t n)530 void *_ext_malloc(size_t n) {
531 #ifdef USE_RPMALLOC
532   return rpmalloc(n);
533 #else
534   return (n == 0 ? NULL : lives_malloc(n));
535 #endif
536 }
537 
538 
_ext_malloc_and_copy(size_t bsize,const void * block)539 void *_ext_malloc_and_copy(size_t bsize, const void *block) {
540   if (!block || bsize == 0) return NULL;
541 #ifdef lives_malloc_and_copy
542   return lives_malloc_and_copy(bsize, block);
543 #endif
544   return (_cpy_if_nonnull(malloc(bsize), block, bsize));
545 }
546 
_ext_unmalloc_and_copy(size_t bsize,void * p)547 void _ext_unmalloc_and_copy(size_t bsize, void *p) {
548   if (!p || bsize == 0) return;
549 #ifdef lives_unmalloc_and_copy
550   lives_unmalloc_and_copy(bsize, p);
551 #else
552   _ext_free(p);
553 #endif
554 }
555 
_ext_free(void * p)556 void _ext_free(void *p) {
557 #ifdef USE_RPMALLOC
558   rpfree(p);
559 #else
560   if (p) lives_free(p);
561 #endif
562 }
563 
564 
_ext_free_and_return(void * p)565 void *_ext_free_and_return(void *p) {_ext_free(p); return NULL;}
566 
_ext_memcpy(void * dest,const void * src,size_t n)567 void *_ext_memcpy(void *dest, const void *src, size_t n) {return lives_memcpy(dest, src, n);}
568 
_ext_memcmp(const void * s1,const void * s2,size_t n)569 int _ext_memcmp(const void *s1, const void *s2, size_t n) {return lives_memcmp(s1, s2, n);}
570 
_ext_memset(void * p,int i,size_t n)571 void *_ext_memset(void *p, int i, size_t n) {return lives_memset(p, i, n);}
572 
_ext_memmove(void * dest,const void * src,size_t n)573 void *_ext_memmove(void *dest, const void *src, size_t n) {return lives_memmove(dest, src, n);}
574 
_ext_realloc(void * p,size_t n)575 void *_ext_realloc(void *p, size_t n) {
576 #ifdef USE_RPMALLOC
577   return rprealloc(p, n);
578 #else
579   return lives_realloc(p, n);
580 }
581 #endif
582 }
583 
_ext_calloc(size_t nmemb,size_t msize)584 void *_ext_calloc(size_t nmemb, size_t msize) {
585 #ifdef USE_RPMALLOC
586   return quick_calloc(nmemb, msize);
587 #else
588   return lives_calloc(nmemb, msize);
589 }
590 #endif
591 }
592 
lives_free_and_return(void * p)593 LIVES_GLOBAL_INLINE void *lives_free_and_return(void *p) {lives_free(p); return NULL;}
594 
595 
get_max_align(size_t req_size,size_t align_max)596 LIVES_GLOBAL_INLINE size_t get_max_align(size_t req_size, size_t align_max) {
597   size_t align = 1;
598   while (align < align_max && !(req_size & align)) align *= 2;
599   return align;
600 }
601 
602 
lives_calloc_safety(size_t nmemb,size_t xsize)603 LIVES_GLOBAL_INLINE void *lives_calloc_safety(size_t nmemb, size_t xsize) {
604   void *p;
605   size_t totsize = nmemb * xsize;
606   if (!totsize) return NULL;
607   if (xsize < DEF_ALIGN) {
608     xsize = DEF_ALIGN;
609     nmemb = (totsize / xsize) + 1;
610   }
611   p = __builtin_assume_aligned(lives_calloc(nmemb + (EXTRA_BYTES / xsize), xsize), DEF_ALIGN);
612   return p;
613 }
614 
lives_recalloc(void * p,size_t nmemb,size_t omemb,size_t xsize)615 LIVES_GLOBAL_INLINE void *lives_recalloc(void *p, size_t nmemb, size_t omemb, size_t xsize) {
616   /// realloc from omemb * size to nmemb * size
617   /// memory allocated via calloc, with DEF_ALIGN alignment and EXTRA_BYTES extra padding
618   void *np = __builtin_assume_aligned(lives_calloc_safety(nmemb, xsize), DEF_ALIGN);
619   void *op = __builtin_assume_aligned(p, DEF_ALIGN);
620   if (omemb > nmemb) omemb = nmemb;
621   lives_memcpy(np, op, omemb * xsize);
622   lives_free(p);
623   return np;
624 }
625 
quick_free(void * p)626 void quick_free(void *p) {rpfree(p);}
627 
quick_calloc(size_t n,size_t s)628 void *quick_calloc(size_t n, size_t s) {return rpaligned_calloc(DEF_ALIGN, n, s);}
629 
init_memfuncs(void)630 boolean init_memfuncs(void) {
631 #ifdef USE_RPMALLOC
632   rpmalloc_initialize();
633 #endif
634   return TRUE;
635 }
636 
637 
init_thread_memfuncs(void)638 boolean init_thread_memfuncs(void) {
639 #ifdef USE_RPMALLOC
640   rpmalloc_thread_initialize();
641 #endif
642   return TRUE;
643 }
644 
645 
get_md5sum(const char * filename)646 char *get_md5sum(const char *filename) {
647   /// for future use
648   char **array;
649   char *md5;
650   char *com = lives_strdup_printf("%s \"%s\"", EXEC_MD5SUM, filename);
651   lives_popen(com, TRUE, mainw->msg, MAINW_MSG_SIZE);
652   lives_free(com);
653   if (THREADVAR(com_failed)) {
654     THREADVAR(com_failed) = FALSE;
655     return NULL;
656   }
657   array = lives_strsplit(mainw->msg, " ", 2);
658   md5 = lives_strdup(array[0]);
659   lives_strfreev(array);
660   return md5;
661 }
662 
663 
lives_format_storage_space_string(uint64_t space)664 char *lives_format_storage_space_string(uint64_t space) {
665   char *fmt;
666 
667   if (space >= lives_10pow(18)) {
668     // TRANSLATORS: Exabytes
669     fmt = lives_strdup_printf(_("%.2f EB"), (double)space / (double)lives_10pow(18));
670   } else if (space >= lives_10pow(15)) {
671     // TRANSLATORS: Petabytes
672     fmt = lives_strdup_printf(_("%.2f PB"), (double)space / (double)lives_10pow(15));
673   } else if (space >= lives_10pow(12)) {
674     // TRANSLATORS: Terabytes
675     fmt = lives_strdup_printf(_("%.2f TB"), (double)space / (double)lives_10pow(12));
676   } else if (space >= lives_10pow(9)) {
677     // TRANSLATORS: Gigabytes
678     fmt = lives_strdup_printf(_("%.2f GB"), (double)space / (double)lives_10pow(9));
679   } else if (space >= lives_10pow(6)) {
680     // TRANSLATORS: Megabytes
681     fmt = lives_strdup_printf(_("%.2f MB"), (double)space / (double)lives_10pow(6));
682   } else if (space >= 1024) {
683     // TRANSLATORS: Kilobytes (1024 bytes)
684     fmt = lives_strdup_printf(_("%.2f KiB"), (double)space / 1024.);
685   } else {
686     fmt = lives_strdup_printf(_("%d bytes"), space);
687   }
688 
689   return fmt;
690 }
691 
692 
get_storage_status(const char * dir,uint64_t warn_level,int64_t * dsval,int64_t ds_resvd)693 lives_storage_status_t get_storage_status(const char *dir, uint64_t warn_level, int64_t *dsval, int64_t ds_resvd) {
694   // WARNING: this will actually create the directory (since we dont know if its parents are needed)
695   // call with dsval set to ds_used to check for OVER_QUOTA
696   // dsval is overwritten and set to ds_free
697   int64_t ds;
698   lives_storage_status_t status = LIVES_STORAGE_STATUS_UNKNOWN;
699   if (dsval && prefs->disk_quota > 0 && *dsval > (int64_t)((double)prefs->disk_quota * prefs->quota_limit / 100.))
700     status = LIVES_STORAGE_STATUS_OVER_QUOTA;
701   if (!is_writeable_dir(dir)) return status;
702   ds = (int64_t)get_ds_free(dir);
703   ds -= ds_resvd;
704   if (dsval) *dsval = ds;
705   if (ds <= 0) return LIVES_STORAGE_STATUS_OVERFLOW;
706   if (ds < prefs->ds_crit_level) return LIVES_STORAGE_STATUS_CRITICAL;
707   if (status != LIVES_STORAGE_STATUS_UNKNOWN) return status;
708   if (ds < warn_level) return LIVES_STORAGE_STATUS_WARNING;
709   return LIVES_STORAGE_STATUS_NORMAL;
710 }
711 
712 static lives_proc_thread_t running = NULL;
713 static char *running_for = NULL;
714 
disk_monitor_running(const char * dir)715 boolean disk_monitor_running(const char *dir) {return (running != NULL && (!dir || !lives_strcmp(dir, running_for)));}
716 
disk_monitor_start(const char * dir)717 lives_proc_thread_t disk_monitor_start(const char *dir) {
718   if (disk_monitor_running(dir)) disk_monitor_forget();
719   running = lives_proc_thread_create(LIVES_THRDATTR_NONE, (lives_funcptr_t)get_dir_size, WEED_SEED_INT64, "s",
720                                      dir);
721   mainw->dsu_valid = TRUE;
722   running_for = lives_strdup(dir);
723   return running;
724 }
725 
disk_monitor_check_result(const char * dir)726 int64_t disk_monitor_check_result(const char *dir) {
727   // caller MUST check if mainw->ds_valid is TRUE, or recheck the results
728   int64_t bytes;
729   if (!disk_monitor_running(dir)) disk_monitor_start(dir);
730   if (!lives_strcmp(dir, running_for)) {
731     if (!lives_proc_thread_check(running)) {
732       return -1;
733     }
734     bytes = lives_proc_thread_join_int64(running);
735     lives_proc_thread_free(running);
736     running = NULL;
737   } else bytes = (int64_t)get_dir_size(dir);
738   return bytes;
739 }
740 
741 
disk_monitor_wait_result(const char * dir,ticks_t timeout)742 LIVES_GLOBAL_INLINE int64_t disk_monitor_wait_result(const char *dir, ticks_t timeout) {
743   // caller MUST check if mainw->ds_valid is TRUE, or recheck the results
744   lives_alarm_t alarm_handle = LIVES_NO_ALARM;
745   int64_t dsval;
746 
747   if (*running_for && !lives_strcmp(dir, running_for)) {
748     if (timeout) return -1;
749     return get_dir_size(dir);
750   }
751 
752   if (timeout < 0) timeout = LIVES_LONGEST_TIMEOUT;
753   if (timeout > 0) alarm_handle = lives_alarm_set(timeout);
754 
755   while ((dsval = disk_monitor_check_result(dir)) < 0
756          && (alarm_handle == LIVES_NO_ALARM || ((timeout = lives_alarm_check(alarm_handle)) > 0))) {
757     lives_nanosleep(1000);
758   }
759   if (alarm_handle != LIVES_NO_ALARM) {
760     lives_alarm_clear(alarm_handle);
761     if (!timeout) {
762       disk_monitor_forget();
763       return -1;
764     }
765   }
766   return dsval;
767 }
768 
disk_monitor_forget(void)769 void disk_monitor_forget(void) {
770   if (!disk_monitor_running(NULL)) return;
771   lives_proc_thread_dontcare(running);
772   running = NULL;
773 }
774 
775 
get_ds_free(const char * dir)776 uint64_t get_ds_free(const char *dir) {
777   // get free space in bytes for volume containing directory dir
778   // return 0 if we cannot create/write to dir
779 
780   // caller should test with is_writeable_dir() first before calling this
781   // since 0 is a valid return value
782 
783   // dir should be in locale encoding
784 
785   // WARNING: this may temporarily create the directory (since we dont know if its parents are needed)
786 
787   struct statvfs sbuf;
788 
789   uint64_t bytes = 0;
790   boolean must_delete = FALSE;
791 
792   if (!lives_file_test(dir, LIVES_FILE_TEST_IS_DIR)) must_delete = TRUE;
793   if (!is_writeable_dir(dir)) goto getfserr;
794 
795   // use statvfs to get fs details
796   if (statvfs(dir, &sbuf) == -1) goto getfserr;
797   if (sbuf.f_flag & ST_RDONLY) goto getfserr;
798 
799   // result is block size * blocks available
800   bytes = sbuf.f_bsize * sbuf.f_bavail;
801   if (!strcmp(dir, prefs->workdir)) {
802     capable->ds_free = bytes;
803     capable->ds_tot = sbuf.f_bsize * sbuf.f_blocks;
804   }
805 
806 getfserr:
807   if (must_delete) lives_rmdir(dir, FALSE);
808 
809   return bytes;
810 }
811 
812 
lives_get_relative_ticks(ticks_t origsecs,ticks_t orignsecs)813 LIVES_GLOBAL_INLINE ticks_t lives_get_relative_ticks(ticks_t origsecs, ticks_t orignsecs) {
814   ticks_t ret = -1;
815 #if _POSIX_TIMERS
816   struct timespec ts;
817   clock_gettime(CLOCK_MONOTONIC, &ts);
818   ret = ((ts.tv_sec * ONE_BILLION + ts.tv_nsec) - (origsecs * ONE_BILLION + orignsecs)) / TICKS_TO_NANOSEC;
819 #else
820 #ifdef USE_MONOTONIC_TIME
821   ret = (lives_get_monotonic_time() - orignsecs) / 10;
822 #else
823   struct timeval tv;
824   gettimeofday(&tv, NULL);
825   ret = ((tv.tv_sec * ONE_MILLLION + tv.tv_usec) - (origsecs * ONE_MILLION + orignsecs / 1000)) * USEC_TO_TICKS;
826 #endif
827 #endif
828   mainw->wall_ticks = ret;
829   if (ret >= 0)
830     mainw->wall_ticks += (origsecs * ONE_BILLION + orignsecs) / TICKS_TO_NANOSEC;
831   return ret;
832 }
833 
834 
lives_get_current_ticks(void)835 LIVES_GLOBAL_INLINE ticks_t lives_get_current_ticks(void) {
836   //  return current (wallclock) time in ticks (units of 10 nanoseconds)
837   return lives_get_relative_ticks(0, 0);
838 }
839 
840 
841 #define SECS_IN_DAY 86400
lives_datetime_rel(const char * datetime)842 char *lives_datetime_rel(const char *datetime) {
843   /// replace date w. yesterday, today
844   char *dtxt;
845   char *today = NULL, *yesterday = NULL;
846   struct timeval otv;
847   gettimeofday(&otv, NULL);
848   today = lives_datetime(otv.tv_sec, TRUE);
849   yesterday = lives_datetime(otv.tv_sec - SECS_IN_DAY, TRUE);
850   if (!lives_strncmp(datetime, today, 10)) dtxt = lives_strdup_printf(_("Today %s"), datetime + 11);
851   else if (!lives_strncmp(datetime, yesterday, 10))
852     dtxt = lives_strdup_printf(_("Yesterday %s"), datetime + 11);
853   else dtxt = (char *)datetime;
854   if (today) lives_free(today);
855   if (yesterday) lives_free(yesterday);
856   return dtxt;
857 }
858 
859 
lives_datetime(uint64_t secs,boolean use_local)860 char *lives_datetime(uint64_t secs, boolean use_local) {
861   char buf[128];
862   char *datetime = NULL;
863   struct tm *gm = use_local ? localtime((time_t *)&secs) : gmtime((time_t *)&secs);
864   ssize_t written;
865 
866   if (gm) {
867     written = (ssize_t)strftime(buf, 128, "%Y-%m-%d    %H:%M:%S", gm);
868     if ((written > 0) && ((size_t)written < 128)) {
869       datetime = lives_strdup(buf);
870     }
871   }
872   return datetime;
873 }
874 
875 
check_dev_busy(char * devstr)876 boolean check_dev_busy(char *devstr) {
877   int ret;
878 #ifdef IS_SOLARIS
879   struct flock lock;
880   lock.l_start = 0;
881   lock.l_whence = SEEK_SET;
882   lock.l_len = 0;
883   lock.l_type = F_WRLCK;
884 #endif
885   int fd = open(devstr, O_RDONLY | O_NONBLOCK);
886   if (fd == -1) return FALSE;
887 #ifdef IS_SOLARIS
888   ret = fcntl(fd, F_SETLK, &lock);
889 #else
890   ret = flock(fd, LOCK_EX | LOCK_NB);
891 #endif
892   close(fd);
893   if (ret == -1) return FALSE;
894   return TRUE;
895 }
896 
897 
compress_files_in_dir(const char * dir,int method,void * data)898 boolean compress_files_in_dir(const char *dir, int method, void *data) {
899   /// compress all files in dir with gzip (directories are not compressed)
900   /// gzip default action is to compress all files, replacing foo.bar with foo.bar.gz
901   /// if a file already has a .gz extension then it will be left uncchanged
902 
903   /// in future, method and data may be used to select compression method
904   /// for now they are ignored
905   char buff[65536];
906   char *com, *cwd;
907   boolean retval = FALSE;
908 
909   if (!check_for_executable(&capable->has_gzip, EXEC_GZIP)) return FALSE;
910   if (lives_file_test(dir, LIVES_FILE_TEST_IS_DIR)) {
911     boolean needs_norem = FALSE;
912     char *norem = lives_build_filename(dir, LIVES_FILENAME_NOREMOVE, NULL);
913     if (lives_file_test(norem, LIVES_FILE_TEST_EXISTS)) {
914       needs_norem = TRUE;
915       lives_rm(norem);
916     }
917     cwd = lives_get_current_dir();
918     THREADVAR(chdir_failed) = FALSE;
919     lives_chdir(dir, TRUE);
920     if (THREADVAR(chdir_failed)) {
921       THREADVAR(chdir_failed) = FALSE;
922       lives_chdir(cwd, TRUE);
923       lives_free(cwd);
924       lives_free(norem);
925       return FALSE;
926     }
927     com = lives_strdup_printf("%s * 2>&1", EXEC_GZIP);
928     lives_popen(com, TRUE, buff, 65536);
929     lives_free(com);
930     if (THREADVAR(com_failed)) THREADVAR(com_failed) = FALSE;
931     else retval = TRUE;
932     lives_chdir(cwd, TRUE);
933     lives_free(cwd);
934     if (needs_norem) {
935       lives_touch(norem);
936       lives_free(norem);
937     }
938   }
939   return retval;
940 }
941 
942 
get_file_size(int fd)943 off_t get_file_size(int fd) {
944   // get the size of file fd
945   struct stat filestat;
946   off_t fsize;
947   lives_file_buffer_t *fbuff;
948   fstat(fd, &filestat);
949   fsize = filestat.st_size;
950   //g_printerr("fssize for %d is %ld\n", fd, fsize);
951   if ((fbuff = find_in_file_buffers(fd)) != NULL) {
952     if (!fbuff->read) {
953       /// because of padding bytes... !!!!
954       off_t f2size;
955       if ((f2size = (off_t)(fbuff->offset + fbuff->bytes)) > fsize) return f2size;
956     }
957   }
958   return fsize;
959 }
960 
961 
sget_file_size(const char * name)962 off_t sget_file_size(const char *name) {
963   off_t res;
964   struct stat xstat;
965   if (!name) return 0;
966   res = stat(name, &xstat);
967   if (res < 0) return res;
968   return xstat.st_size;
969 }
970 
971 
reget_afilesize(int fileno)972 void reget_afilesize(int fileno) {
973   // re-get the audio file size
974   lives_clip_t *sfile = mainw->files[fileno];
975   boolean bad_header = FALSE;
976 
977   if (mainw->multitrack) return; // otherwise achans gets set to 0...
978 
979   sfile->afilesize = reget_afilesize_inner(fileno);
980 
981   if (sfile->afilesize == 0l) {
982     if (!sfile->opening && fileno != mainw->ascrap_file && fileno != mainw->scrap_file) {
983       if (sfile->arate != 0 || sfile->achans != 0 || sfile->asampsize != 0 || sfile->arps != 0) {
984         sfile->arate = sfile->achans = sfile->asampsize = sfile->arps = 0;
985         if (!save_clip_value(fileno, CLIP_DETAILS_ACHANS, &sfile->achans)) bad_header = TRUE;
986         if (!save_clip_value(fileno, CLIP_DETAILS_ARATE, &sfile->arps)) bad_header = TRUE;
987         if (!save_clip_value(fileno, CLIP_DETAILS_PB_ARATE, &sfile->arate)) bad_header = TRUE;
988         if (!save_clip_value(fileno, CLIP_DETAILS_ASAMPS, &sfile->asampsize)) bad_header = TRUE;
989         if (bad_header) do_header_write_error(fileno);
990       }
991     }
992   }
993 
994   if (mainw->is_ready && fileno > 0 && fileno == mainw->current_file) {
995     // force a redraw
996     update_play_times();
997   }
998 }
999 
1000 
reget_afilesize_inner(int fileno)1001 off_t reget_afilesize_inner(int fileno) {
1002   // safe version that just returns the audio file size
1003   off_t filesize;
1004   char *afile = lives_get_audio_file_name(fileno);
1005   lives_sync(1);
1006   filesize = sget_file_size(afile);
1007   lives_free(afile);
1008   if (filesize < 0) filesize = 0;
1009   return filesize;
1010 }
1011 
1012 
is_empty_dir(const char * dirname)1013 boolean is_empty_dir(const char *dirname) {
1014   DIR *tldir;
1015   struct dirent *tdirent;
1016   boolean empty = TRUE;
1017   if (!dirname) return TRUE;
1018   tldir = opendir(dirname);
1019   if (!tldir) return FALSE;
1020   while (empty && (tdirent = readdir(tldir))) {
1021     if (tdirent->d_name[0] == '.'
1022         && (!tdirent->d_name[1] || tdirent->d_name[1] == '.')) continue;
1023     empty = FALSE;
1024   }
1025   closedir(tldir);
1026   return empty;
1027 }
1028 
1029 
get_mountpoint_for(const char * dir)1030 char *get_mountpoint_for(const char *dir) {
1031   char *mp = NULL, *tmp, *com, *res;
1032   size_t lmatch = 0, slen;
1033   int j;
1034 
1035   if (!dir) return NULL;
1036   slen = lives_strlen(dir);
1037 
1038   com = lives_strdup("df -P");
1039   if ((res = mini_popen(com))) {
1040     int lcount = get_token_count(res, '\n');
1041     char **array0 = lives_strsplit(res, "\n", lcount);
1042     for (int l = 0; l < lcount; l++) {
1043       int pccount = get_token_count(array0[l], ' ');
1044       char **array1 = lives_strsplit(array0[l], " ", pccount);
1045       lives_chomp(array1[pccount - 1]);
1046       for (j = 0; array1[pccount - 1][j] && j < slen; j++) if (array1[pccount - 1][j] != dir[j]) break;
1047       if (j > lmatch && !array1[pccount - 1][j]) {
1048         lmatch = j;
1049         if (mp) lives_free(mp);
1050         tmp = lives_strdup(array1[0]);
1051         mp = lives_filename_to_utf8(tmp, -1, NULL, NULL, NULL);
1052         lives_free(tmp);
1053       }
1054       lives_strfreev(array1);
1055     }
1056     lives_strfreev(array0);
1057     lives_free(res);
1058   }
1059   return mp;
1060 }
1061 
1062 
1063 #if defined(IS_FREEBSD) || defined(__DragonFly__)
1064 #define DU_BLOCKSIZE 1024
1065 #else
1066 #define DU_BLOCKSIZE 1
1067 #endif
1068 
get_dir_size(const char * dirname)1069 off_t get_dir_size(const char *dirname) {
1070   off_t dirsize = -1;
1071   if (!dirname || !*dirname || !lives_file_test(dirname, LIVES_FILE_TEST_IS_DIR)) return -1;
1072   if (check_for_executable(&capable->has_du, EXEC_DU)) {
1073     char buff[PATH_MAX * 2];
1074     char *com = lives_strdup_printf("%s -sB %d \"%s\"", EXEC_DU, DU_BLOCKSIZE, dirname);
1075     lives_popen(com, TRUE, buff, PATH_MAX * 2);
1076     lives_free(com);
1077     if (THREADVAR(com_failed)) THREADVAR(com_failed) = FALSE;
1078     else dirsize = atol(buff) / DU_BLOCKSIZE;
1079   }
1080   return dirsize;
1081 }
1082 
1083 
free_fdets_list(LiVESList ** listp)1084 void free_fdets_list(LiVESList **listp) {
1085   LiVESList *list = *listp;
1086   lives_file_dets_t *filedets;
1087   for (; list && list->data; list = list->next) {
1088     filedets = (lives_file_dets_t *)list->data;
1089     lives_struct_free(filedets->lsd);
1090     list->data = NULL;
1091   }
1092   if (*listp) {
1093     lives_list_free(*listp);
1094     *listp = NULL;
1095   }
1096 }
1097 
1098 
stat_to_file_dets(const char * fname,lives_file_dets_t * fdets)1099 int stat_to_file_dets(const char *fname, lives_file_dets_t *fdets) {
1100   struct stat filestat;
1101   int ret = stat(fname, &filestat);
1102   if (ret) {
1103     if (prefs->show_dev_opts) {
1104       char *msg = lives_strdup_printf("\nstat failed for file %s\n", fname);
1105       perror(msg);
1106       lives_free(msg);
1107     }
1108     fdets->size = -2;
1109     fdets->type = LIVES_FILE_TYPE_UNKNOWN;
1110     return ret;
1111   }
1112   fdets->type = (uint64_t)((filestat.st_mode & S_IFMT) >> 12);
1113   fdets->size = filestat.st_size;
1114   fdets->mode = (uint64_t)(filestat.st_mode & 0x0FFF);
1115   fdets->uid = filestat.st_uid;
1116   fdets->gid = filestat.st_gid;
1117   fdets->blk_size = (uint64_t)filestat.st_blksize;
1118   fdets->atime_sec = filestat.st_atim.tv_sec;
1119   fdets->atime_nsec = filestat.st_atim.tv_nsec;
1120   fdets->mtime_sec = filestat.st_mtim.tv_sec;
1121   fdets->mtime_nsec = filestat.st_mtim.tv_nsec;
1122   fdets->ctime_sec = filestat.st_ctim.tv_sec;
1123   fdets->ctime_nsec = filestat.st_ctim.tv_nsec;
1124   return ret;
1125 }
1126 
1127 
file_to_file_details(const char * filename,lives_file_dets_t * fdets,lives_proc_thread_t tinfo,uint64_t extra)1128 static char *file_to_file_details(const char *filename, lives_file_dets_t *fdets, lives_proc_thread_t tinfo, uint64_t extra) {
1129   char *tmp, *tmp2;
1130   char *extra_details = lives_strdup("");
1131 
1132   if (!stat_to_file_dets(filename, fdets)) {
1133     // if stat fails, we have set set size to -2, type to LIVES_FILE_TYPE_UNKNOWN
1134     // and here we set extra_details to ""
1135     if (tinfo && lives_proc_thread_cancelled(tinfo)) {
1136       lives_free(extra_details);
1137       return NULL;
1138     }
1139     if (LIVES_FILE_IS_DIRECTORY(fdets->type)) {
1140       boolean emptyd = FALSE;
1141       if (extra & EXTRA_DETAILS_EMPTY_DIRS) {
1142         if ((emptyd = is_empty_dir(filename))) {
1143           fdets->type |= LIVES_FILE_TYPE_FLAG_EMPTY;
1144           tmp2 = lives_strdup_printf("%s%s%s", extra_details, *extra_details ? ", " : "",
1145                                      (tmp = _("(empty)")));
1146           lives_free(tmp);
1147           lives_free(extra_details);
1148           extra_details = tmp2;
1149         }
1150         if (tinfo && lives_proc_thread_cancelled(tinfo)) {
1151           lives_free(extra_details);
1152           return NULL;
1153         }
1154       }
1155       if ((extra & EXTRA_DETAILS_DIRSIZE) &&
1156           check_for_executable(&capable->has_du, EXEC_DU)
1157           && !emptyd && fdets->type == LIVES_FILE_TYPE_DIRECTORY) {
1158         fdets->size = get_dir_size(filename);
1159       }
1160 
1161       if (!emptyd && (extra & EXTRA_DETAILS_CLIPHDR)) {
1162         int clipno;
1163 
1164         clipno = create_nullvideo_clip("tmp");
1165 
1166         if (clipno && IS_VALID_CLIP(clipno)) {
1167           if (read_headers(clipno, filename, NULL)) {
1168             lives_clip_t *sfile = mainw->files[clipno];
1169             char *name = lives_strdup(sfile->name);
1170             extra_details =
1171               lives_strdup_printf("%s%s%s", extra_details, *extra_details ? ", " : "",
1172                                   (tmp = lives_strdup_printf
1173                                          (_("Source: %s, frames: %d, size: %d X %d, fps: %.3f"),
1174                                           name, sfile->frames, sfile->hsize,
1175                                           sfile->vsize, sfile->fps)));
1176             lives_free(tmp);
1177             lives_free(name);
1178             lives_freep((void **)&mainw->files[clipno]);
1179             if (mainw->first_free_file == ALL_USED || mainw->first_free_file > clipno)
1180               mainw->first_free_file = clipno;
1181           }
1182           if (mainw->hdrs_cache) cached_list_free(&mainw->hdrs_cache);
1183 	  // *INDENT-OFF*
1184 	}}}
1185     if (extra & EXTRA_DETAILS_MD5SUM) {
1186       fdets->md5sum = get_md5sum(filename);
1187     }
1188     if (extra & EXTRA_DETAILS_SYMLINK) {
1189       if (lives_file_test(filename, LIVES_FILE_TEST_IS_SYMLINK))
1190 	fdets->type |= LIVES_FILE_TYPE_FLAG_SYMLINK;
1191     }
1192     if (extra & EXTRA_DETAILS_EXECUTABLE) {
1193       if (lives_file_test(filename, LIVES_FILE_TEST_IS_EXECUTABLE))
1194 	fdets->type |= LIVES_FILE_TYPE_FLAG_EXECUTABLE;
1195     }
1196     /// TODO
1197     /* if (extra & EXTRA_DETAILS_WRITEABLE) { */
1198     /*   if (LIVES_FILE_TEST_IS_EXECUTABLE(filename)) fdets->type |= LIVES_FILE_TYPE_FLAG_EXECUTABLE; */
1199     /* } */
1200     /* if (extra & EXTRA_DETAILS_ACCESSIBLE) { */
1201     /*   if (LIVES_FILE_TEST_IS_EXECUTABLE(filename)) fdets->type |= LIVES_FILE_TYPE_FLAG_EXECUTABLE; */
1202     /* } */
1203   }
1204   // *INDENT-ON*
1205   else {
1206     /// stat failed
1207     if (extra & EXTRA_DETAILS_CHECK_MISSING) {
1208       if (!lives_file_test(filename, LIVES_FILE_TEST_EXISTS)) {
1209         fdets->type |= LIVES_FILE_TYPE_FLAG_MISSING;
1210         tmp2 = lives_strdup_printf("%s%s%s", extra_details, *extra_details ? ", " : "",
1211                                    (tmp = _("(ABSENT)")));
1212         lives_free(tmp);
1213         lives_free(extra_details);
1214         extra_details = tmp2;
1215       }
1216     }
1217   }
1218   return extra_details;
1219 }
1220 
1221 
1222 /**
1223    @brief create a list from a (sub)directory
1224    '.' and '..' are ignored
1225    subdir can be NULL
1226 */
_item_to_file_details(LiVESList ** listp,const char * item,const char * orig_loc,uint64_t extra,int type)1227 void *_item_to_file_details(LiVESList **listp, const char *item,
1228                             const char *orig_loc, uint64_t extra, int type) {
1229   // type 0 = dir
1230   // type 1 = ordfile
1231   lives_file_dets_t *fdets;
1232   lives_proc_thread_t tinfo = NULL;
1233   LiVESList *list;
1234   char *extra_details;
1235   const char *dir = NULL;
1236   char *subdirname;
1237   boolean empty = TRUE;
1238 
1239   tinfo = THREADVAR(tinfo);
1240   if (tinfo) lives_proc_thread_set_cancellable(tinfo);
1241 
1242   switch (type) {
1243   case 0: {
1244     DIR *tldir;
1245     struct dirent *tdirent;
1246     // dir
1247     dir = item;
1248     if (!dir) return NULL;
1249     tldir = opendir(dir);
1250     if (!tldir) {
1251       *listp = lives_list_append(*listp, NULL);
1252       return NULL;
1253     }
1254 
1255     while (1) {
1256       tdirent = readdir(tldir);
1257       if (lives_proc_thread_cancelled(tinfo) || !tdirent) {
1258         closedir(tldir);
1259         if (lives_proc_thread_cancelled(tinfo)) return NULL;
1260         break;
1261       }
1262       if (tdirent->d_name[0] == '.'
1263           && (!tdirent->d_name[1] || tdirent->d_name[1] == '.')) continue;
1264       fdets = (lives_file_dets_t *)struct_from_template(LIVES_STRUCT_FILE_DETS_T);
1265       fdets->name = lives_strdup(tdirent->d_name);
1266       //g_print("GOT %s\n", fdets->name);
1267       fdets->size = -1;
1268       *listp = lives_list_append(*listp, fdets);
1269       if (lives_proc_thread_cancelled(tinfo)) {
1270         closedir(tldir);
1271         return NULL;
1272       }
1273     }
1274     break;
1275   }
1276   case 1: {
1277     FILE *orderfile;
1278     char buff[PATH_MAX];
1279     const char *ofname = item;
1280 
1281     if (!(orderfile = fopen(ofname, "r"))) return NULL;
1282     while (1) {
1283       if (lives_proc_thread_cancelled(tinfo) || !orderfile) {
1284         if (orderfile) {
1285           fclose(orderfile);
1286         }
1287         return NULL;
1288       }
1289       if (!lives_fgets(buff, PATH_MAX, orderfile)) {
1290         fclose(orderfile);
1291         break;
1292       }
1293       lives_chomp(buff);
1294 
1295       fdets = (lives_file_dets_t *)struct_from_template(LIVES_STRUCT_FILE_DETS_T);
1296 
1297       fdets->name = lives_strdup(buff);
1298       fdets->size = -1;
1299       *listp = lives_list_append(*listp, fdets);
1300       if (lives_proc_thread_cancelled(tinfo)) {
1301         fclose(orderfile);
1302         return NULL;
1303       }
1304     }
1305     break;
1306   }
1307   default: return NULL;
1308   }
1309 
1310   if (*listp) empty = FALSE;
1311   *listp = lives_list_append(*listp, NULL);
1312 
1313   if (empty || lives_proc_thread_cancelled(tinfo)) return NULL;
1314 
1315   // listing done, now get details for each entry
1316   list = *listp;
1317   while (list && list->data) {
1318     if (lives_proc_thread_cancelled(tinfo)) return NULL;
1319 
1320     extra_details = lives_strdup("");
1321     fdets = (lives_file_dets_t *)list->data;
1322 
1323     if (orig_loc && *orig_loc) subdirname = lives_build_filename(orig_loc, fdets->name, NULL);
1324     else subdirname = lives_build_path(dir, fdets->name, NULL);
1325 
1326     // need to call even with no extra, because it gets size / type tc.
1327     if (!(extra_details = file_to_file_details(subdirname, fdets, tinfo, extra))) {
1328       lives_free(subdirname);
1329       lives_free(extra_details);
1330       return NULL;
1331     }
1332 
1333     lives_free(subdirname);
1334 
1335     if (tinfo && lives_proc_thread_cancelled(tinfo)) {
1336       lives_free(extra_details);
1337       return NULL;
1338     }
1339     fdets->extra_details = lives_strdup(extra_details);
1340     lives_free(extra_details);
1341     list = list->next;
1342   }
1343 
1344   return NULL;
1345 }
1346 
1347 /**
1348    @brief create a list from a (sub)directory
1349    '.' and '..' are ignored
1350    subdir can be NULL
1351    runs in a proc_htread
1352 */
dir_to_file_details(LiVESList ** listp,const char * dir,const char * orig_loc,uint64_t extra)1353 lives_proc_thread_t dir_to_file_details(LiVESList **listp, const char *dir,
1354                                         const char *orig_loc, uint64_t extra) {
1355   return lives_proc_thread_create(LIVES_THRDATTR_NONE, (lives_funcptr_t)_item_to_file_details, -1, "vssIi",
1356                                   listp, dir, orig_loc, extra, 0);
1357 }
1358 
1359 
ordfile_to_file_details(LiVESList ** listp,const char * ofname,const char * orig_loc,uint64_t extra)1360 lives_proc_thread_t ordfile_to_file_details(LiVESList **listp, const char *ofname,
1361     const char *orig_loc, uint64_t extra) {
1362   return lives_proc_thread_create(LIVES_THRDATTR_NONE, (lives_funcptr_t)_item_to_file_details, -1, "vssIi",
1363                                   listp, ofname, orig_loc, extra, 1);
1364 }
1365 
1366 
1367 #ifdef PRODUCE_LOG
1368 // disabled by default
lives_log(const char * what)1369 void lives_log(const char *what) {
1370   char *lives_log_file = lives_build_filename(prefs->workdir, LIVES_LOG_FILE, NULL);
1371   if (mainw->log_fd < 0) mainw->log_fd = open(lives_log_file, O_WRONLY | O_CREAT, DEF_FILE_PERMS);
1372   if (mainw->log_fd != -1) {
1373     char *msg = lives_strdup("%s|%d|", what, mainw->current_file);
1374     write(mainw->log_fd, msg, strlen(msg));
1375     lives_free(msg);
1376   }
1377   lives_free(lives_log_file);
1378 }
1379 #endif
1380 
1381 
check_for_bad_ffmpeg(void)1382 int check_for_bad_ffmpeg(void) {
1383   int i, fcount;
1384   char *fname_next;
1385   boolean maybeok = FALSE;
1386 
1387   fcount = get_frame_count(mainw->current_file, 1);
1388 
1389   for (i = 1; i <= fcount; i++) {
1390     fname_next = make_image_file_name(cfile, i, get_image_ext_for_type(cfile->img_type));
1391     if (sget_file_size(fname_next) > 0) {
1392       lives_free(fname_next);
1393       maybeok = TRUE;
1394       break;
1395     }
1396     lives_free(fname_next);
1397   }
1398 
1399   if (!maybeok) {
1400     widget_opts.non_modal = TRUE;
1401     do_error_dialog(
1402       _("Your version of mplayer/ffmpeg may be broken !\nSee http://bugzilla.mplayerhq.hu/show_bug.cgi?id=2071\n\n"
1403         "You can work around this temporarily by switching to jpeg output in Preferences/Decoding.\n\n"
1404         "Try running Help/Troubleshoot for more information."));
1405     widget_opts.non_modal = FALSE;
1406     return CANCEL_ERROR;
1407   }
1408   return CANCEL_NONE;
1409 }
1410 
1411 
lives_concat_sep(char * st,const char * sep,char * x)1412 LIVES_GLOBAL_INLINE char *lives_concat_sep(char *st, const char *sep, char *x) {
1413   /// nb: lives strconcat
1414   // uses realloc / memcpy, frees x
1415   char *tmp;
1416   if (st) {
1417     size_t s1 = lives_strlen(st), s2 = lives_strlen(x), s3 = lives_strlen(sep);
1418     tmp = (char *)lives_realloc(st, ++s2 + s1 + s3);
1419     lives_memcpy(tmp + s1, sep, s3);
1420     lives_memcpy(tmp + s1 + s3, x, s2);
1421   } else tmp = lives_strdup(x);
1422   lives_free(x);
1423   return tmp;
1424 }
1425 
lives_concat(char * st,char * x)1426 LIVES_GLOBAL_INLINE char *lives_concat(char *st, char *x) {
1427   /// nb: lives strconcat
1428   // uses realloc / memcpy, frees x
1429   size_t s1 = lives_strlen(st), s2 = lives_strlen(x);
1430   char *tmp = (char *)lives_realloc(st, ++s2 + s1);
1431   lives_memcpy(tmp + s1, x, s2);
1432   lives_free(x);
1433   return tmp;
1434 }
1435 
lives_strappend(const char * string,int len,const char * xnew)1436 LIVES_GLOBAL_INLINE int lives_strappend(const char *string, int len, const char *xnew) {
1437   /// see also: lives_concat()
1438   size_t sz = lives_strlen(string);
1439   int newln = lives_snprintf((char *)(string + sz), len - sz, "%s", xnew);
1440   if (newln > len) newln = len;
1441   return --newln - sz; // returns strlen(xnew)
1442 }
1443 
lives_strappendf(const char * string,int len,const char * fmt,...)1444 LIVES_GLOBAL_INLINE const char *lives_strappendf(const char *string, int len, const char *fmt, ...) {
1445   va_list xargs;
1446   char *text;
1447 
1448   va_start(xargs, fmt);
1449   text = lives_strdup_vprintf(fmt, xargs);
1450   va_end(xargs);
1451 
1452   lives_strappend(string, len, text);
1453   lives_free(text);
1454   return string;
1455 }
1456 
1457 /// each byte B can be thought of as a signed char, subtracting 1 sets bit 7 if B was <= 0, then AND with ~B clears bit 7 if it
1458 /// was already set (i.e B was < 0), thus bit 7 only remains set if the byte started as 0.
1459 #define hasNulByte(x) (((x) - 0x0101010101010101) & ~(x) & 0x8080808080808080)
1460 #define getnulpos(nulmask) ((nulmask & 2155905152ul)	? ((nulmask & 32896ul) ? ((nulmask & 128ul) ? 0 : 1) : \
1461 							   ((nulmask & 8388608ul) ? 2 : 3)) : (nulmask & 141287244169216ul) ? \
1462 			    ((nulmask & 549755813888ul) ? 4 : 5) : ((nulmask & 36028797018963968ul) ? 6 : 7))
1463 
1464 #define getnulpos_be(nulmask) ((nulmask & 9259542121117908992ul) ? ((nulmask & 9259400833873739776ul) ? \
1465 								    ((nulmask & 9223372036854775808ul) ? 0 : 1) : ((nulmask & 140737488355328ul) ? 2 : 3)) \
1466 			       : (nulmask & 2155872256ul) ? ((nulmask & 2147483648ul) ? 4 : 5) : ((nulmask & 32768ul) ? 6 : 7))
1467 
lives_strlen(const char * s)1468 LIVES_GLOBAL_INLINE size_t lives_strlen(const char *s) {
1469   if (!s) return 0;
1470 #ifndef STD_STRINGFUNCS
1471   else {
1472     uint64_t *pi = (uint64_t *)s, nulmask;
1473     if ((void *)pi == (void *)s) {
1474       while (!(nulmask = hasNulByte(*pi))) pi++;
1475       return (char *)pi - s + (capable->byte_order == LIVES_LITTLE_ENDIAN ? getnulpos(nulmask)
1476                                : getnulpos_be(nulmask));
1477     }
1478   }
1479 #endif
1480   return strlen(s);
1481 }
1482 
1483 
lives_strdup_quick(const char * s)1484 LIVES_GLOBAL_INLINE char *lives_strdup_quick(const char *s) {
1485   if (!s) return NULL;
1486 #ifndef STD_STRINGFUNCS
1487   else {
1488     uint64_t *pi = (uint64_t *)s, nulmask, stlen;
1489     if (!s) return NULL;
1490     if ((void *)pi == (void *)s) {
1491       while (!(nulmask = hasNulByte(*pi))) pi++;
1492       stlen = (char *)pi - s + 1
1493               + (capable->byte_order == LIVES_LITTLE_ENDIAN)
1494               ? getnulpos(nulmask) : getnulpos_be(nulmask);
1495       return lives_memcpy(lives_malloc(stlen), s, stlen);
1496     }
1497   }
1498 #endif
1499   return lives_strdup(s);
1500 }
1501 
1502 
1503 
1504 
1505 /// returns FALSE if strings match
lives_strcmp(const char * st1,const char * st2)1506 LIVES_GLOBAL_INLINE boolean lives_strcmp(const char *st1, const char *st2) {
1507   if (!st1 || !st2) return (st1 != st2);
1508   else {
1509 #ifdef STD_STRINGFUNCS
1510     return strcmp(st1, st2);
1511 #endif
1512     uint64_t d1, d2, *ip1 = (uint64_t *)st1, *ip2 = (uint64_t *)st2;
1513     while (1) {
1514       if ((void *)ip1 == (void *)st1 && (void *)ip2 == (void *)st2) {
1515         while (1) {
1516           if ((d1 = *(ip1++)) == (d2 = *(ip2++))) {if (hasNulByte(d1)) return FALSE;}
1517           else {
1518             if (!hasNulByte(d1 | d2)) return TRUE;
1519             break;
1520           }
1521         }
1522         st1 = (const char *)(--ip1); st2 = (const char *)(--ip2);
1523       }
1524       if (*st1 != *(st2++)) return TRUE;
1525       if (!(*(st1++))) return FALSE;
1526     }
1527   }
1528   return FALSE;
1529 }
1530 
lives_strcmp_ordered(const char * st1,const char * st2)1531 LIVES_GLOBAL_INLINE int lives_strcmp_ordered(const char *st1, const char *st2) {
1532   if (!st1 || !st2) return (st1 != st2);
1533   else {
1534 #ifdef STD_STRINGFUNCS
1535     return strcmp(st1, st2);
1536 #endif
1537     uint64_t d1, d2, *ip1 = (uint64_t *)st1, *ip2 = (uint64_t *)st2;
1538     while (1) {
1539       if ((void *)ip1 == (void *)st1 && (void *)ip2 == (void *)st2) {
1540         do {
1541           d1 = *(ip1++);
1542           d2 = *(ip2++);
1543         } while (d1 == d2 && !hasNulByte(d1));
1544         st1 = (const char *)(--ip1); st2 = (const char *)(--ip2);
1545       }
1546       if (*st1 != *st2 || !(*st1)) break;
1547       st1++; st2++;
1548     }
1549   }
1550   return (*st1 > *st2) - (*st1 < *st2);
1551 }
1552 
1553 /// returns FALSE if strings match
lives_strncmp(const char * st1,const char * st2,size_t len)1554 LIVES_GLOBAL_INLINE boolean lives_strncmp(const char *st1, const char *st2, size_t len) {
1555   if (!st1 || !st2) return (st1 != st2);
1556   else {
1557 #ifdef STD_STRINGFUNCS
1558     return strncmp(st1, st2, len);
1559 #endif
1560     size_t xlen = len >> 3;
1561     uint64_t d1, d2, *ip1 = (uint64_t *)st1, *ip2 = (uint64_t *)st2;
1562     while (1) {
1563       if (xlen && (void *)ip1 == (void *)st1 && (void *)ip2 == (void *)st2) {
1564         do {
1565           d1 = *(ip1++);
1566           d2 = *(ip2++);
1567         } while (d1 == d2 && !hasNulByte(d1) && --xlen);
1568         if (xlen) {
1569           if (!hasNulByte(d2)) return TRUE;
1570           ip1--;
1571           ip2--;
1572         }
1573         st1 = (void *)ip1; st2 = (void *)ip2;
1574         len -= ((len >> 3) - xlen) << 3;
1575       }
1576       if (!(len--)) return FALSE;
1577       if (*st1 != *(st2++)) return TRUE;
1578       if (!(*(st1++))) return FALSE;
1579     }
1580   }
1581   return (*st1 != *st2);
1582 }
1583 
1584 #define HASHROOT 5381
lives_string_hash(const char * st)1585 LIVES_GLOBAL_INLINE uint32_t lives_string_hash(const char *st) {
1586   if (st) for (uint32_t hash = HASHROOT;; hash += (hash << 5)
1587                  + * (st++)) if (!(*st)) return hash;
1588   return 0;
1589 }
1590 
1591 
1592 // fast hash from: http://www.azillionmonkeys.com/qed/hash.html
1593 // (c) Paul Hsieh
1594 #define get16bits(d) (*((const uint16_t *) (d)))
1595 
fast_hash(const char * key)1596 LIVES_GLOBAL_INLINE uint32_t fast_hash(const char *key) {
1597   /// approx 5 - 10 % faster than lives_string_hash
1598   if (key && *key) {
1599     int len = lives_strlen(key), rem = len & 3;
1600     uint32_t hash = len + HASHROOT, tmp;
1601     len >>= 2;
1602     for (; len > 0; len--) {
1603       hash  += get16bits(key);
1604       tmp    = (get16bits(key + 2) << 11) ^ hash;
1605       hash   = (hash << 16) ^ tmp;
1606       key  += 4;
1607       hash  += hash >> 11;
1608     }
1609 
1610     /* Handle end cases */
1611     switch (rem) {
1612     case 3: hash += get16bits(key);
1613       hash ^= hash << 16;
1614       hash ^= ((int8_t)key[2]) << 18;
1615       hash += hash >> 11;
1616       break;
1617     case 2: hash += get16bits(key);
1618       hash ^= hash << 11; hash += hash >> 17;
1619       break;
1620     case 1: hash += (int8_t) * key;
1621       hash ^= hash << 10; hash += hash >> 1;
1622       break;
1623     default: break;
1624     }
1625 
1626     /* Force "avalanching" of final 127 bits */
1627     hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4;
1628     hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6;
1629     return hash;
1630   }
1631   return 0;
1632 }
1633 
lives_strstop(char * st,const char term)1634 LIVES_GLOBAL_INLINE char *lives_strstop(char *st, const char term) {
1635   /// truncate st, replacing term with \0
1636   if (st && term) for (int i = 0; st[i]; i++) if (st[i] == term) {st[i] = 0; break;}
1637   return st;
1638 }
1639 
1640 
lives_chomp(char * buff)1641 LIVES_GLOBAL_INLINE char *lives_chomp(char *buff) {
1642   /// see also lives_strchomp() which removes all whitespace
1643   if (buff) {
1644     size_t xs = lives_strlen(buff);
1645     if (xs && buff[xs - 1] == '\n') buff[--xs] = '\0'; // remove trailing newline
1646   }
1647   return buff;
1648 }
1649 
1650 
lives_strtrim(const char * buff)1651 LIVES_GLOBAL_INLINE char *lives_strtrim(const char *buff) {
1652   /// return string with start and end newlines stripped
1653   /// see also lives_strstrip() which removes all whitespace
1654   int i, j;
1655   if (!buff) return NULL;
1656   for (i = 0; buff[i] == '\n'; i++);
1657   for (j = i; buff[j]; j++) if (buff[j] == '\n') break;
1658   return lives_strndup(buff + i, j - i);
1659 }
1660 
1661 
1662 /**
1663    lives  proc_threads API
1664    - the only requirements are to call lives_proc_thread_create() which will generate a lives_proc_thread_t and run it,
1665    and then (depending on the return_type parameter, call one of the lives_proc_thread_join_*() functions
1666 
1667    (see that function for more comments)
1668 */
1669 
1670 typedef weed_plantptr_t lives_proc_thread_t;
1671 
lives_proc_thread_free(lives_proc_thread_t lpt)1672 LIVES_GLOBAL_INLINE void lives_proc_thread_free(lives_proc_thread_t lpt) {weed_plant_free(lpt);}
1673 
_lives_proc_thread_create(lives_thread_attr_t attr,lives_funcptr_t func,int return_type,const char * args_fmt,va_list xargs)1674 static lives_proc_thread_t _lives_proc_thread_create(lives_thread_attr_t attr, lives_funcptr_t func,
1675     int return_type, const char *args_fmt, va_list xargs) {
1676   int p = 0;
1677   const char *c;
1678   weed_plant_t *thread_info = lives_plant_new(LIVES_WEED_SUBTYPE_PROC_THREAD);
1679   if (!thread_info) return NULL;
1680   weed_set_funcptr_value(thread_info, WEED_LEAF_THREADFUNC, func);
1681   if (return_type) {
1682     pthread_mutex_t *dcmutex = (pthread_mutex_t *)lives_malloc(sizeof(pthread_mutex_t));
1683     pthread_mutex_init(dcmutex, NULL);
1684     weed_set_voidptr_value(thread_info, WEED_LEAF_DONTCARE_MUTEX, dcmutex);
1685     weed_set_boolean_value(thread_info, WEED_LEAF_NOTIFY, WEED_TRUE);
1686     if (return_type > 0)  weed_leaf_set(thread_info, WEED_LEAF_RETURN_VALUE, return_type, 0, NULL);
1687   }
1688   c = args_fmt;
1689   for (c = args_fmt; *c; c++) {
1690     char *pkey = lives_strdup_printf("%s%d", WEED_LEAF_THREAD_PARAM, p++);
1691     switch (*c) {
1692     case 'i': weed_set_int_value(thread_info, pkey, va_arg(xargs, int)); break;
1693     case 'd': weed_set_double_value(thread_info, pkey, va_arg(xargs, double)); break;
1694     case 'b': weed_set_boolean_value(thread_info, pkey, va_arg(xargs, int)); break;
1695     case 's': case 'S': weed_set_string_value(thread_info, pkey, va_arg(xargs, char *)); break;
1696     case 'I': weed_set_int64_value(thread_info, pkey, va_arg(xargs, int64_t)); break;
1697     case 'F': weed_set_funcptr_value(thread_info, pkey, va_arg(xargs, weed_funcptr_t)); break;
1698     case 'V': case 'v': weed_set_voidptr_value(thread_info, pkey, va_arg(xargs, void *)); break;
1699     case 'P': weed_set_plantptr_value(thread_info, pkey, va_arg(xargs, weed_plantptr_t)); break;
1700     default: weed_plant_free(thread_info); return NULL;
1701     }
1702     lives_free(pkey);
1703   }
1704 
1705   if (!(attr & LIVES_THRDATTR_FG_THREAD)) {
1706     resubmit_proc_thread(thread_info, attr);
1707     if (!return_type) return NULL;
1708   }
1709   return thread_info;
1710 }
1711 
1712 
1713 /**
1714    create the specific plant which defines a background task to be run
1715    - func is any function of a recognised type, with 0 - 16 parameters,
1716    and a value of type <return type> which may be retrieved by
1717    later calling the appropriate lives_proc_thread_join_*() function
1718    - args_fmt is a 0 terminated string describing the arguments of func, i ==int, d == double, b == boolean (int),
1719    s == string (0 terminated), I == uint64_t, int64_t, P = weed_plant_t *, V / v == (void *), F == weed_funcptr_t
1720    return_type is enumerated, e.g WEED_SEED_INT64. Return_type of 0 indicates no return value (void), then the thread
1721    will free its own resources and NULL is returned from this function (fire and forget)
1722    return_type of -1 has a special meaning, in this case no result is returned, but the thread can be monitored by calling:
1723    lives_proc_thread_check() with the return : - this function is guaranteed to return FALSE whilst the thread is running
1724    and TRUE thereafter, the proc_thread should be freed once TRUE id returned and not before.
1725    for the other return_types, the appropriate join function should be called and it will block until the thread has completed its
1726    task and return a copy of the actual return value of the func
1727    alternatively, if return_type is non-zero,
1728    then the returned value from this function may be reutlised by passing it as the parameter
1729    to run_as_thread(). */
lives_proc_thread_create(lives_thread_attr_t attr,lives_funcptr_t func,int return_type,const char * args_fmt,...)1730 lives_proc_thread_t lives_proc_thread_create(lives_thread_attr_t attr, lives_funcptr_t func,
1731     int return_type, const char *args_fmt, ...) {
1732   lives_proc_thread_t lpt;
1733   va_list xargs;
1734   va_start(xargs, args_fmt);
1735   lpt = _lives_proc_thread_create(attr, func, return_type, args_fmt, xargs);
1736   va_end(xargs);
1737   return lpt;
1738 }
1739 
1740 
main_thread_execute(lives_funcptr_t func,int return_type,void * retval,const char * args_fmt,...)1741 void *main_thread_execute(lives_funcptr_t func, int return_type, void *retval, const char *args_fmt, ...) {
1742   lives_proc_thread_t lpt;
1743   va_list xargs;
1744   void *ret;
1745   va_start(xargs, args_fmt);
1746   lpt = _lives_proc_thread_create(LIVES_THRDATTR_FG_THREAD, func, return_type, args_fmt, xargs);
1747   ret = lives_fg_run(lpt, retval);
1748   va_end(xargs);
1749   return ret;
1750 }
1751 
1752 
call_funcsig(funcsig_t sig,lives_proc_thread_t info)1753 static void call_funcsig(funcsig_t sig, lives_proc_thread_t info) {
1754   /// funcsigs define the signature of any function we may wish to call via lives_proc_thread
1755   /// however since there are almost 3 quadrillion posibilities (nargs < 16 * all return types)
1756   /// it is not feasable to add every one; new funcsigs can be added as needed; then the only remaining thing is to
1757   /// ensure the matching case is handled in the switch statement
1758   uint32_t ret_type = weed_leaf_seed_type(info, _RV_);
1759   allfunc_t *thefunc = (allfunc_t *)lives_malloc(sizeof(allfunc_t));
1760   char *msg;
1761 
1762   thefunc->func = weed_get_funcptr_value(info, WEED_LEAF_THREADFUNC, NULL);
1763 
1764 #define FUNCSIG_VOID				       			0X00000000
1765 #define FUNCSIG_INT 			       				0X00000001
1766 #define FUNCSIG_DOUBLE 				       			0X00000002
1767 #define FUNCSIG_STRING 				       			0X00000004
1768 #define FUNCSIG_VOIDP 				       			0X0000000D
1769 #define FUNCSIG_INT_INT64 			       			0X00000015
1770 #define FUNCSIG_STRING_INT 			      			0X00000041
1771 #define FUNCSIG_STRING_BOOL 			      			0X00000043
1772 #define FUNCSIG_VOIDP_VOIDP 				       		0X000000DD
1773 #define FUNCSIG_VOIDP_DOUBLE 				       		0X000000D2
1774 #define FUNCSIG_PLANTP_BOOL 				       		0X000000E3
1775 #define FUNCSIG_VOIDP_VOIDP_VOIDP 		        		0X00000DDD
1776 #define FUNCSIG_PLANTP_VOIDP_INT64 		        		0X00000ED5
1777   // 4p
1778 #define FUNCSIG_STRING_STRING_VOIDP_INT					0X000044D1
1779 #define FUNCSIG_INT_INT_BOOL_VOIDP					0X0000113D
1780   // 5p
1781 #define FUNCSIG_INT_INT_INT_BOOL_VOIDP					0X0001113D
1782 #define FUNCSIG_VOIDP_STRING_STRING_INT64_INT			       	0X000D4451
1783   // 6p
1784 #define FUNCSIG_STRING_STRING_VOIDP_INT_STRING_VOIDP		       	0X0044D14D
1785 
1786   // Note: C compilers don't care about the type / number of function args., (else it would be impossible to alias any function pointer)
1787   // just the type / number must be correct at runtime;
1788   // However it DOES care about the return type. The funcsigs are a guide so that the correct cast / number of args. can be
1789   // determined in the code., the first argument to the GETARG macro is set by this.
1790   // return_type determines which function flavour to call, e.g func, funcb, funci
1791   /// the second argument to GETARG relates to the internal structure of the lives_proc_thread;
1792 
1793   /// LIVES_PROC_THREADS ////////////////////////////////////////
1794 
1795   /// to make any function usable by lives_proc_thread, the _ONLY REQUIREMENT_ is to ensure that there is a function call
1796   /// corresponding the function arguments (i.e the funcsig) and return value here below
1797   /// (use of the FUNCSIG_* symbols is optional, they exist only to make it clearer what the function parameters should be)
1798 
1799   switch (sig) {
1800   case FUNCSIG_VOID:
1801     switch (ret_type) {
1802     case WEED_SEED_INT64: CALL_0(int64); break;
1803     default: CALL_VOID_0(); break;
1804     }
1805     break;
1806   case FUNCSIG_INT:
1807     switch (ret_type) {
1808     default: CALL_VOID_1(int); break;
1809     }
1810     break;
1811   case FUNCSIG_DOUBLE:
1812     switch (ret_type) {
1813     default: CALL_VOID_1(double); break;
1814     }
1815     break;
1816   case FUNCSIG_STRING:
1817     switch (ret_type) {
1818     case WEED_SEED_STRING: CALL_1(string, string); break;
1819     case WEED_SEED_INT64: CALL_1(int64, string); break;
1820     default: CALL_VOID_1(string); break;
1821     }
1822     break;
1823   case FUNCSIG_VOIDP:
1824     switch (ret_type) {
1825     case WEED_SEED_BOOLEAN: CALL_1(boolean, voidptr); break;
1826     case WEED_SEED_INT: CALL_1(int, voidptr); break;
1827     default: CALL_VOID_1(voidptr); break;
1828     }
1829     break;
1830   case FUNCSIG_INT_INT64:
1831     switch (ret_type) {
1832     default: CALL_VOID_2(int, int64); break;
1833     }
1834     break;
1835   case FUNCSIG_STRING_INT:
1836     switch (ret_type) {
1837     default: CALL_VOID_2(string, int); break;
1838     }
1839     break;
1840   case FUNCSIG_STRING_BOOL:
1841     switch (ret_type) {
1842     default: CALL_VOID_2(string, boolean); break;
1843     }
1844     break;
1845   case FUNCSIG_VOIDP_DOUBLE:
1846     switch (ret_type) {
1847     default: CALL_VOID_2(voidptr, double); break;
1848     }
1849     break;
1850   case FUNCSIG_VOIDP_VOIDP:
1851     switch (ret_type) {
1852     case WEED_SEED_BOOLEAN: CALL_2(boolean, voidptr, voidptr); break;
1853     default: CALL_VOID_2(voidptr, voidptr); break;
1854     }
1855     break;
1856   case FUNCSIG_PLANTP_BOOL:
1857     switch (ret_type) {
1858     default: CALL_VOID_2(plantptr, boolean); break;
1859     }
1860     break;
1861   case FUNCSIG_VOIDP_VOIDP_VOIDP:
1862     switch (ret_type) {
1863     case WEED_SEED_BOOLEAN: CALL_3(boolean, voidptr, voidptr, voidptr); break;
1864     default: CALL_VOID_3(voidptr, voidptr, voidptr); break;
1865     }
1866     break;
1867   case FUNCSIG_PLANTP_VOIDP_INT64:
1868     switch (ret_type) {
1869     case WEED_SEED_BOOLEAN: CALL_3(boolean, plantptr, voidptr, int64); break;
1870     default: CALL_VOID_3(plantptr, voidptr, int64); break;
1871     }
1872     break;
1873   case FUNCSIG_STRING_STRING_VOIDP_INT:
1874     switch (ret_type) {
1875     case WEED_SEED_STRING: CALL_4(string, string, string, voidptr, int); break;
1876     default: CALL_VOID_4(string, string, voidptr, int); break;
1877     }
1878     break;
1879   case FUNCSIG_INT_INT_BOOL_VOIDP:
1880     switch (ret_type) {
1881     case WEED_SEED_BOOLEAN: CALL_4(boolean, int, int, boolean, voidptr); break;
1882     default: CALL_VOID_4(int, int, boolean, voidptr); break;
1883     }
1884     break;
1885   case FUNCSIG_VOIDP_STRING_STRING_INT64_INT:
1886     switch (ret_type) {
1887     default: CALL_VOID_5(voidptr, string, string, int64, int); break;
1888     }
1889     break;
1890   case FUNCSIG_INT_INT_INT_BOOL_VOIDP:
1891     switch (ret_type) {
1892     default: CALL_VOID_5(int, int, int, boolean, voidptr); break;
1893     }
1894     break;
1895   case FUNCSIG_STRING_STRING_VOIDP_INT_STRING_VOIDP:
1896     switch (ret_type) {
1897     case WEED_SEED_STRING: CALL_6(string, string, string, voidptr, int, string, voidptr); break;
1898     default: CALL_VOID_6(string, string, voidptr, int, string, voidptr); break;
1899     }
1900     break;
1901   default:
1902     msg = lives_strdup_printf("Unknown funcsig with tyte 0x%016lX called", sig);
1903     LIVES_FATAL(msg);
1904     lives_free(msg);
1905     break;
1906   }
1907 
1908   lives_free(thefunc);
1909 }
1910 
lives_proc_thread_sync_ready(lives_proc_thread_t tinfo)1911 LIVES_GLOBAL_INLINE void lives_proc_thread_sync_ready(lives_proc_thread_t tinfo) {
1912   volatile boolean *sync_ready = (volatile boolean *)weed_get_voidptr_value(tinfo, "sync_ready", NULL);
1913   if (sync_ready) *sync_ready = TRUE;
1914 }
1915 
lives_proc_thread_check(lives_proc_thread_t tinfo)1916 LIVES_GLOBAL_INLINE boolean lives_proc_thread_check(lives_proc_thread_t tinfo) {
1917   /// returns FALSE while the thread is running, TRUE once it has finished
1918   if (!tinfo) return TRUE;
1919   if (weed_plant_has_leaf(tinfo, WEED_LEAF_NOTIFY) && weed_get_boolean_value(tinfo, WEED_LEAF_DONE, NULL)
1920       == WEED_FALSE)
1921     return FALSE;
1922   return (weed_leaf_num_elements(tinfo, _RV_) > 0
1923           || weed_get_boolean_value(tinfo, WEED_LEAF_DONE, NULL) == WEED_TRUE);
1924 }
1925 
lives_proc_thread_signalled(lives_proc_thread_t tinfo)1926 LIVES_GLOBAL_INLINE int lives_proc_thread_signalled(lives_proc_thread_t tinfo) {
1927   /// returns FALSE while the thread is running, TRUE once it has finished
1928   return (weed_get_int_value(tinfo, WEED_LEAF_SIGNALLED, NULL) == WEED_TRUE);
1929 }
1930 
lives_proc_thread_signalled_idx(lives_proc_thread_t tinfo)1931 LIVES_GLOBAL_INLINE int64_t lives_proc_thread_signalled_idx(lives_proc_thread_t tinfo) {
1932   /// returns FALSE while the thread is running, TRUE once it has finished
1933   lives_thread_data_t *tdata = (lives_thread_data_t *)weed_get_voidptr_value(tinfo, WEED_LEAF_SIGNAL_DATA, NULL);
1934   if (tdata) return tdata->idx;
1935   return 0;
1936 }
1937 
lives_proc_thread_set_cancellable(lives_proc_thread_t tinfo)1938 LIVES_GLOBAL_INLINE void lives_proc_thread_set_cancellable(lives_proc_thread_t tinfo) {
1939   weed_set_boolean_value(tinfo, WEED_LEAF_THREAD_CANCELLABLE, WEED_TRUE);
1940 }
1941 
lives_proc_thread_get_cancellable(lives_proc_thread_t tinfo)1942 LIVES_GLOBAL_INLINE boolean lives_proc_thread_get_cancellable(lives_proc_thread_t tinfo) {
1943   return weed_get_boolean_value(tinfo, WEED_LEAF_THREAD_CANCELLABLE, NULL) == WEED_TRUE ? TRUE : FALSE;
1944 }
1945 
lives_proc_thread_cancel(lives_proc_thread_t tinfo)1946 LIVES_GLOBAL_INLINE boolean lives_proc_thread_cancel(lives_proc_thread_t tinfo) {
1947   if (!lives_proc_thread_get_cancellable(tinfo)) return FALSE;
1948   weed_set_boolean_value(tinfo, WEED_LEAF_THREAD_CANCELLED, WEED_TRUE);
1949   lives_proc_thread_join(tinfo);
1950   return TRUE;
1951 }
1952 
lives_proc_thread_dontcare(lives_proc_thread_t tinfo)1953 boolean lives_proc_thread_dontcare(lives_proc_thread_t tinfo) {
1954   /// if thread is running, tell it we no longer care about return value, so it can free itself
1955   /// if finished we just call lives_proc_thread_join() to free it
1956   /// a mutex is used to ensure the proc_thread does not finish between setting the flag and checking if it has ifnished
1957   pthread_mutex_t *dcmutex = weed_get_voidptr_value(tinfo, WEED_LEAF_DONTCARE_MUTEX, NULL);
1958   if (dcmutex) {
1959     pthread_mutex_lock(dcmutex);
1960     if (!lives_proc_thread_check(tinfo)) {
1961       weed_set_boolean_value(tinfo, WEED_LEAF_DONTCARE, WEED_TRUE);
1962       pthread_mutex_unlock(dcmutex);
1963     } else {
1964       pthread_mutex_unlock(dcmutex);
1965       lives_proc_thread_join(tinfo);
1966     }
1967   }
1968   return TRUE;
1969 }
1970 
lives_proc_thread_cancelled(lives_proc_thread_t tinfo)1971 LIVES_GLOBAL_INLINE boolean lives_proc_thread_cancelled(lives_proc_thread_t tinfo) {
1972   return (tinfo && weed_get_boolean_value(tinfo, WEED_LEAF_THREAD_CANCELLED, NULL) == WEED_TRUE)
1973          ? TRUE : FALSE;
1974 }
1975 
1976 #define _join(stype) lives_nanosleep_until_nonzero(weed_leaf_num_elements(tinfo, _RV_)); \
1977   return weed_get_##stype##_value(tinfo, _RV_, NULL);
1978 
lives_proc_thread_join(lives_proc_thread_t tinfo)1979 LIVES_GLOBAL_INLINE void lives_proc_thread_join(lives_proc_thread_t tinfo) {
1980   // WARNING !! version without a return value will free tinfo !
1981   void *dcmutex;
1982   lives_nanosleep_until_nonzero((weed_get_boolean_value(tinfo, WEED_LEAF_DONE, NULL) == WEED_TRUE));
1983   dcmutex = weed_get_voidptr_value(tinfo, WEED_LEAF_DONTCARE_MUTEX, NULL);
1984   if (dcmutex) lives_free(dcmutex);
1985   weed_plant_free(tinfo);
1986 }
lives_proc_thread_join_int(lives_proc_thread_t tinfo)1987 LIVES_GLOBAL_INLINE int lives_proc_thread_join_int(lives_proc_thread_t tinfo) { _join(int);}
lives_proc_thread_join_double(lives_proc_thread_t tinfo)1988 LIVES_GLOBAL_INLINE double lives_proc_thread_join_double(lives_proc_thread_t tinfo) {_join(double);}
lives_proc_thread_join_boolean(lives_proc_thread_t tinfo)1989 LIVES_GLOBAL_INLINE int lives_proc_thread_join_boolean(lives_proc_thread_t tinfo) { _join(boolean);}
lives_proc_thread_join_int64(lives_proc_thread_t tinfo)1990 LIVES_GLOBAL_INLINE int64_t lives_proc_thread_join_int64(lives_proc_thread_t tinfo) {_join(int64);}
lives_proc_thread_join_string(lives_proc_thread_t tinfo)1991 LIVES_GLOBAL_INLINE char *lives_proc_thread_join_string(lives_proc_thread_t tinfo) {_join(string);}
lives_proc_thread_join_funcptr(lives_proc_thread_t tinfo)1992 LIVES_GLOBAL_INLINE weed_funcptr_t lives_proc_thread_join_funcptr(lives_proc_thread_t tinfo) {_join(funcptr);}
lives_proc_thread_join_voidptr(lives_proc_thread_t tinfo)1993 LIVES_GLOBAL_INLINE void *lives_proc_thread_join_voidptr(lives_proc_thread_t tinfo) {_join(voidptr);}
lives_proc_thread_join_plantptr(lives_proc_thread_t tinfo)1994 LIVES_GLOBAL_INLINE weed_plantptr_t lives_proc_thread_join_plantptr(lives_proc_thread_t tinfo) {_join(plantptr);}
1995 
1996 /**
1997    create a funcsig from a lives_proc_thread_t object
1998    the returned value can be passed to call_funcsig, along with the original lives_proc_thread_t
1999 */
make_funcsig(lives_proc_thread_t func_info)2000 static funcsig_t make_funcsig(lives_proc_thread_t func_info) {
2001   funcsig_t funcsig = 0;
2002   for (register int nargs = 0; nargs < 16; nargs++) {
2003     char *lname = lives_strdup_printf("%s%d", WEED_LEAF_THREAD_PARAM, nargs);
2004     int st = weed_leaf_seed_type(func_info, lname);
2005     lives_free(lname);
2006     if (!st) break;
2007     funcsig <<= 4;  /// 4 bits per argtype, hence up to 16 args in a uint64_t
2008     if (st < 12) funcsig |= st; // 1 == int, 2 == double, 3 == boolean (int), 4 == char *, 5 == int64_t
2009     else {
2010       switch (st) {
2011       case WEED_SEED_FUNCPTR: funcsig |= 0XC; break;
2012       case WEED_SEED_VOIDPTR: funcsig |= 0XD; break;
2013       case WEED_SEED_PLANTPTR: funcsig |= 0XE; break;
2014       default: funcsig |= 0XF; break;
2015       }
2016     }
2017   }
2018   return funcsig;
2019 }
2020 
_plant_thread_func(void * args)2021 static void *_plant_thread_func(void *args) {
2022   lives_proc_thread_t info = (lives_proc_thread_t)args;
2023   uint32_t ret_type = weed_leaf_seed_type(info, _RV_);
2024   funcsig_t sig = make_funcsig(info);
2025   THREADVAR(tinfo) = info;
2026   if (weed_get_boolean_value(info, "no_gui", NULL) == WEED_TRUE) THREADVAR(no_gui) = TRUE;
2027   call_funcsig(sig, info);
2028 
2029   if (weed_get_boolean_value(info, WEED_LEAF_NOTIFY, NULL) == WEED_TRUE) {
2030     boolean dontcare;
2031     pthread_mutex_t *dcmutex = (pthread_mutex_t *)weed_get_voidptr_value(info, WEED_LEAF_DONTCARE_MUTEX, NULL);
2032     pthread_mutex_lock(dcmutex);
2033     dontcare = weed_get_boolean_value(info, WEED_LEAF_DONTCARE, NULL);
2034     weed_set_boolean_value(info, WEED_LEAF_DONE, WEED_TRUE);
2035     pthread_mutex_unlock(dcmutex);
2036     if (dontcare == WEED_TRUE) {
2037       lives_free(dcmutex);
2038       weed_plant_free(info);
2039     }
2040   } else if (!ret_type) weed_plant_free(info);
2041   return NULL;
2042 }
2043 
2044 
fg_run_func(lives_proc_thread_t lpt,void * retval)2045 void *fg_run_func(lives_proc_thread_t lpt, void *retval) {
2046   uint32_t ret_type = weed_leaf_seed_type(lpt, _RV_);
2047   funcsig_t sig = make_funcsig(lpt);
2048 
2049   call_funcsig(sig, lpt);
2050 
2051   switch (ret_type) {
2052   case WEED_SEED_INT: {
2053     int *ival = (int *)retval;
2054     *ival = weed_get_int_value(lpt, _RV_, NULL);
2055     weed_plant_free(lpt);
2056     return (void *)ival;
2057   }
2058   case WEED_SEED_BOOLEAN: {
2059     int *bval = (int *)retval;
2060     *bval = weed_get_boolean_value(lpt, _RV_, NULL);
2061     weed_plant_free(lpt);
2062     return (void *)bval;
2063   }
2064   case WEED_SEED_DOUBLE: {
2065     double *dval = (double *)retval;
2066     *dval = weed_get_double_value(lpt, _RV_, NULL);
2067     weed_plant_free(lpt);
2068     return (void *)dval;
2069   }
2070   case WEED_SEED_STRING: {
2071     char *chval = weed_get_string_value(lpt, _RV_, NULL);
2072     weed_plant_free(lpt);
2073     return (void *)chval;
2074   }
2075   case WEED_SEED_INT64: {
2076     int64_t *i64val = (int64_t *)retval;
2077     *i64val = weed_get_int64_value(lpt, _RV_, NULL);
2078     weed_plant_free(lpt);
2079     return (void *)i64val;
2080   }
2081   case WEED_SEED_VOIDPTR: {
2082     void *val;
2083     val = weed_get_voidptr_value(lpt, _RV_, NULL);
2084     weed_plant_free(lpt);
2085     return val;
2086   }
2087   case WEED_SEED_PLANTPTR: {
2088     weed_plant_t *pval;
2089     pval = weed_get_plantptr_value(lpt, _RV_, NULL);
2090     weed_plant_free(lpt);
2091     return (void *)pval;
2092   }
2093   /// no funcptrs or custom...yet
2094   default:
2095     weed_plant_free(lpt);
2096     break;
2097   }
2098   return NULL;
2099 }
2100 
2101 #undef _RV_
2102 
2103 /// (re)submission point, the function call is added to the threadpool tasklist
2104 /// if we have sufficient threads the task will be run at once, if all threads are busy then MINPOOLTHREADS new threads will be created
2105 /// and added to the pool
resubmit_proc_thread(lives_proc_thread_t thread_info,lives_thread_attr_t attr)2106 void resubmit_proc_thread(lives_proc_thread_t thread_info, lives_thread_attr_t attr) {
2107   /// run any function as a lives_thread
2108   lives_thread_t *thread = (lives_thread_t *)lives_calloc(1, sizeof(lives_thread_t));
2109   thrd_work_t *work;
2110 
2111   /// tell the thread to clean up after itself [but it won't delete thread_info]
2112   attr |= LIVES_THRDATTR_AUTODELETE;
2113   lives_thread_create(thread, attr, _plant_thread_func, (void *)thread_info);
2114   work = (thrd_work_t *)thread->data;
2115   if (attr & LIVES_THRDATTR_WAIT_SYNC) {
2116     weed_set_voidptr_value(thread_info, "sync_ready", (void *) & (work->sync_ready));
2117   }
2118   if (attr & LIVES_THRDATTR_NO_GUI) {
2119     weed_set_boolean_value(thread_info, "no_gui", WEED_TRUE);
2120   }
2121 }
2122 
2123 
2124 //////// worker thread pool //////////////////////////////////////////
2125 
2126 ///////// thread pool ////////////////////////
2127 #ifndef VALGRIND_ON
2128 #define MINPOOLTHREADS 8
2129 #else
2130 #define MINPOOLTHREADS 2
2131 #endif
2132 static int npoolthreads;
2133 static pthread_t **poolthrds;
2134 static pthread_cond_t tcond  = PTHREAD_COND_INITIALIZER;
2135 static pthread_mutex_t tcond_mutex = PTHREAD_MUTEX_INITIALIZER;
2136 static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;
2137 static pthread_mutex_t twork_mutex = PTHREAD_MUTEX_INITIALIZER;
2138 static pthread_mutex_t twork_count_mutex = PTHREAD_MUTEX_INITIALIZER;
2139 static LiVESList *twork_first, *twork_last; /// FIFO list of tasks
2140 static volatile int ntasks;
2141 static boolean threads_die;
2142 
2143 static LiVESList *allctxs = NULL;
2144 
get_thread_data(void)2145 lives_thread_data_t *get_thread_data(void) {
2146   LiVESWidgetContext *ctx = lives_widget_context_get_thread_default();
2147   LiVESList *list = allctxs;
2148   if (!ctx) ctx = lives_widget_context_default();
2149   for (; list; list = list->next) {
2150     if (((lives_thread_data_t *)list->data)->ctx == ctx) return list->data;
2151   }
2152   return NULL;
2153 }
2154 
2155 
get_threadvars(void)2156 LIVES_GLOBAL_INLINE lives_threadvars_t *get_threadvars(void) {
2157   static lives_threadvars_t *dummyvars = NULL;
2158   lives_thread_data_t *thrdat = get_thread_data();
2159   if (!thrdat) {
2160     if (!dummyvars) dummyvars = lives_calloc(1, sizeof(lives_threadvars_t));
2161     return dummyvars;
2162   }
2163   return &thrdat->vars;
2164 }
2165 
get_thread_data_by_id(uint64_t idx)2166 static lives_thread_data_t *get_thread_data_by_id(uint64_t idx) {
2167   LiVESList *list = allctxs;
2168   for (; list; list = list->next) {
2169     if (((lives_thread_data_t *)list->data)->idx == idx) return list->data;
2170   }
2171   return NULL;
2172 }
2173 
lives_thread_data_create(uint64_t idx)2174 lives_thread_data_t *lives_thread_data_create(uint64_t idx) {
2175   lives_thread_data_t *tdata = (lives_thread_data_t *)lives_calloc(1, sizeof(lives_thread_data_t));
2176   if (idx != 0) tdata->ctx = lives_widget_context_new();
2177   else tdata->ctx = lives_widget_context_default();
2178   tdata->idx = idx;
2179   tdata->vars.var_rowstride_alignment = ALIGN_DEF;
2180   tdata->vars.var_last_sws_block = -1;
2181   tdata->vars.var_mydata = tdata;
2182   allctxs = lives_list_prepend(allctxs, (livespointer)tdata);
2183   return tdata;
2184 }
2185 
2186 
gsrc_wrapper(livespointer data)2187 static boolean gsrc_wrapper(livespointer data) {
2188   thrd_work_t *mywork = (thrd_work_t *)data;
2189   (*mywork->func)(mywork->arg);
2190   return FALSE;
2191 }
2192 
2193 
do_something_useful(lives_thread_data_t * tdata)2194 boolean do_something_useful(lives_thread_data_t *tdata) {
2195   /// yes, why don't you lend a hand instead of just lying around nanosleeping...
2196   LiVESList *list;
2197   thrd_work_t *mywork;
2198   uint64_t myflags = 0;
2199 
2200   if (!tdata->idx) abort();
2201 
2202   pthread_mutex_lock(&twork_mutex);
2203   list = twork_last;
2204   if (LIVES_UNLIKELY(!list)) {
2205     pthread_mutex_unlock(&twork_mutex);
2206     return FALSE;
2207   }
2208 
2209   if (twork_first == list) twork_last = twork_first = NULL;
2210   else {
2211     twork_last = list->prev;
2212     twork_last->next = NULL;
2213   }
2214   pthread_mutex_unlock(&twork_mutex);
2215 
2216   mywork = (thrd_work_t *)list->data;
2217   mywork->busy = tdata->idx;
2218   myflags = mywork->flags;
2219 
2220   if (myflags & LIVES_THRDFLAG_WAIT_SYNC) {
2221     lives_nanosleep_until_nonzero(mywork->sync_ready);
2222   }
2223 
2224   lives_widget_context_invoke(tdata->ctx, gsrc_wrapper, mywork);
2225   //(*mywork->func)(mywork->arg);
2226 
2227   if (myflags & LIVES_THRDFLAG_AUTODELETE) {
2228     lives_free(mywork); lives_free(list);
2229   } else mywork->done = tdata->idx;
2230 
2231   pthread_mutex_lock(&twork_count_mutex);
2232   ntasks--;
2233   pthread_mutex_unlock(&twork_count_mutex);
2234   return TRUE;
2235 }
2236 
2237 
thrdpool(void * arg)2238 static void *thrdpool(void *arg) {
2239   boolean skip_wait = FALSE;
2240   lives_thread_data_t *tdata = (lives_thread_data_t *)arg;
2241   if (!rpmalloc_is_thread_initialized()) {
2242     rpmalloc_thread_initialize();
2243   }
2244 
2245   lives_widget_context_push_thread_default(tdata->ctx);
2246 
2247   while (!threads_die) {
2248     if (!skip_wait) {
2249       pthread_mutex_lock(&tcond_mutex);
2250       pthread_cond_wait(&tcond, &tcond_mutex);
2251       pthread_mutex_unlock(&tcond_mutex);
2252     }
2253     if (LIVES_UNLIKELY(threads_die)) break;
2254     skip_wait = do_something_useful(tdata);
2255     if (rpmalloc_is_thread_initialized()) {
2256       rpmalloc_thread_collect();
2257     }
2258   }
2259   if (rpmalloc_is_thread_initialized()) {
2260     rpmalloc_thread_finalize();
2261   }
2262   return NULL;
2263 }
2264 
2265 
lives_plant_new(int subtype)2266 LIVES_GLOBAL_INLINE weed_plant_t *lives_plant_new(int subtype) {
2267   weed_plant_t *plant = weed_plant_new(WEED_PLANT_LIVES);
2268   weed_set_int_value(plant, WEED_LEAF_LIVES_SUBTYPE, subtype);
2269   return plant;
2270 }
2271 
2272 
lives_plant_new_with_index(int subtype,int64_t index)2273 LIVES_GLOBAL_INLINE weed_plant_t *lives_plant_new_with_index(int subtype, int64_t index) {
2274   weed_plant_t *plant = lives_plant_new(subtype);
2275   weed_set_int64_value(plant, WEED_LEAF_INDEX, index);
2276   return plant;
2277 }
2278 
2279 
lives_threadpool_init(void)2280 void lives_threadpool_init(void) {
2281   npoolthreads = MINPOOLTHREADS;
2282   if (prefs->nfx_threads > npoolthreads) npoolthreads = prefs->nfx_threads;
2283   poolthrds = (pthread_t **)lives_calloc(npoolthreads, sizeof(pthread_t *));
2284   threads_die = FALSE;
2285   twork_first = twork_last = NULL;
2286   ntasks = 0;
2287   for (int i = 0; i < npoolthreads; i++) {
2288     lives_thread_data_t *tdata = lives_thread_data_create(i + 1);
2289     poolthrds[i] = (pthread_t *)lives_malloc(sizeof(pthread_t));
2290     pthread_create(poolthrds[i], NULL, thrdpool, tdata);
2291   }
2292 }
2293 
2294 
lives_threadpool_finish(void)2295 void lives_threadpool_finish(void) {
2296   threads_die = TRUE;
2297   pthread_mutex_lock(&tcond_mutex);
2298   pthread_cond_broadcast(&tcond);
2299   pthread_mutex_unlock(&tcond_mutex);
2300   for (int i = 0; i < npoolthreads; i++) {
2301     lives_thread_data_t *tdata = get_thread_data_by_id(i + 1);
2302     pthread_cond_broadcast(&tcond);
2303     pthread_mutex_unlock(&tcond_mutex);
2304     pthread_join(*(poolthrds[i]), NULL);
2305     lives_widget_context_unref(tdata->ctx);
2306     lives_free(tdata);
2307     lives_free(poolthrds[i]);
2308   }
2309   lives_free(poolthrds);
2310   poolthrds = NULL;
2311   npoolthreads = 0;
2312   lives_list_free_all((LiVESList **)&twork_first);
2313   twork_first = twork_last = NULL;
2314   ntasks = 0;
2315 }
2316 
2317 
lives_thread_create(lives_thread_t * thread,lives_thread_attr_t attr,lives_funcptr_t func,void * arg)2318 int lives_thread_create(lives_thread_t *thread, lives_thread_attr_t attr, lives_funcptr_t func, void *arg) {
2319   LiVESList *list = (LiVESList *)thread;
2320   thrd_work_t *work = (thrd_work_t *)lives_calloc(1, sizeof(thrd_work_t));
2321   if (!thread) list = (LiVESList *)lives_calloc(1, sizeof(LiVESList));
2322   else list->next = list->prev = NULL;
2323   list->data = work;
2324   work->func = func;
2325   work->arg = arg;
2326 
2327   if (!thread || (attr & LIVES_THRDATTR_AUTODELETE))
2328     work->flags |= LIVES_THRDFLAG_AUTODELETE;
2329   if (attr & LIVES_THRDATTR_WAIT_SYNC) {
2330     work->flags |= LIVES_THRDFLAG_WAIT_SYNC;
2331     work->sync_ready = FALSE;
2332   }
2333 
2334   pthread_mutex_lock(&twork_mutex);
2335   if (!twork_first) {
2336     twork_first = twork_last = list;
2337   } else {
2338     if (!(attr & LIVES_THRDATTR_PRIORITY)) {
2339       twork_first->prev = list;
2340       list->next = twork_first;
2341       twork_first = list;
2342     } else {
2343       twork_last->next = list;
2344       list->prev = twork_last;
2345       twork_last = list;
2346     }
2347   }
2348   pthread_mutex_unlock(&twork_mutex);
2349   pthread_mutex_lock(&twork_count_mutex);
2350   ntasks++;
2351   pthread_mutex_unlock(&twork_count_mutex);
2352   pthread_mutex_lock(&tcond_mutex);
2353   pthread_cond_signal(&tcond);
2354   pthread_mutex_unlock(&tcond_mutex);
2355   pthread_mutex_lock(&pool_mutex);
2356   if (ntasks >= npoolthreads) {
2357     pthread_mutex_lock(&tcond_mutex);
2358     pthread_cond_broadcast(&tcond);
2359     pthread_mutex_unlock(&tcond_mutex);
2360     poolthrds = (pthread_t **)lives_realloc(poolthrds, (npoolthreads + MINPOOLTHREADS) * sizeof(pthread_t *));
2361     for (int i = npoolthreads; i < npoolthreads + MINPOOLTHREADS; i++) {
2362       lives_thread_data_t *tdata = lives_thread_data_create(i + 1);
2363       poolthrds[i] = (pthread_t *)lives_malloc(sizeof(pthread_t));
2364       pthread_create(poolthrds[i], NULL, thrdpool, tdata);
2365       pthread_mutex_lock(&tcond_mutex);
2366       pthread_cond_signal(&tcond);
2367       pthread_mutex_unlock(&tcond_mutex);
2368     }
2369     npoolthreads += MINPOOLTHREADS;
2370   }
2371   pthread_mutex_unlock(&pool_mutex);
2372   return 0;
2373 }
2374 
2375 
lives_thread_join(lives_thread_t work,void ** retval)2376 uint64_t lives_thread_join(lives_thread_t work, void **retval) {
2377   thrd_work_t *task = (thrd_work_t *)work.data;
2378   uint64_t nthrd = 0;
2379   if (task->flags & LIVES_THRDFLAG_AUTODELETE) {
2380     LIVES_FATAL("lives_thread_join() called on an autodelete thread");
2381     return 0;
2382   }
2383 
2384   while (!task->busy) {
2385     pthread_mutex_lock(&tcond_mutex);
2386     pthread_cond_signal(&tcond);
2387     pthread_mutex_unlock(&tcond_mutex);
2388     if (task->busy) break;
2389     sched_yield();
2390     lives_nanosleep(1000);
2391   }
2392 
2393   if (!task->done) {
2394     pthread_mutex_lock(&tcond_mutex);
2395     pthread_cond_signal(&tcond);
2396     pthread_mutex_unlock(&tcond_mutex);
2397   }
2398 
2399   lives_nanosleep_until_nonzero(task->done);
2400   nthrd = task->done;
2401 
2402   if (retval) *retval = task->ret;
2403   lives_free(task);
2404   return nthrd;
2405 }
2406 
2407 
lives_getpid(void)2408 LIVES_GLOBAL_INLINE pid_t lives_getpid(void) {
2409 #ifdef IS_MINGW
2410   return GetCurrentProcessId(),
2411 #else
2412   return getpid();
2413 #endif
2414 }
2415 
2416 LIVES_GLOBAL_INLINE int lives_getuid(void) {
2417   return geteuid();
2418 }
2419 
2420 LIVES_GLOBAL_INLINE int lives_getgid(void) {
2421   return getegid();
2422 }
2423 
2424 static uint16_t swabtab[65536];
2425 static boolean swabtab_inited = FALSE;
2426 
2427 static void init_swabtab(void) {
2428   for (int i = 0; i < 256; i++) {
2429     int z = i << 8;
2430     for (int j = 0; j < 256; j++) {
2431       swabtab[z++] = (j << 8) + i;
2432     }
2433   }
2434   swabtab_inited = TRUE;
2435 }
2436 
2437 union split8 {
2438   uint64_t u64;
2439   uint32_t u32[2];
2440 };
2441 
2442 union split4 {
2443   uint32_t u32;
2444   uint16_t u16[2];
2445 };
2446 
2447 // gran(ularity) may be 1, or 2
2448 LIVES_GLOBAL_INLINE void swab2(const void *from, const void *to, size_t gran) {
2449   uint16_t *s = (uint16_t *)from;
2450   uint16_t *d = (uint16_t *)to;
2451   if (gran == 2) {
2452     uint16_t tmp = *s;
2453     *s = *d;
2454     *d = tmp;
2455     return;
2456   }
2457   if (!swabtab_inited) init_swabtab();
2458   *d = swabtab[*s];
2459 }
2460 
2461 // gran(ularity) may be 1, 2 or 4
2462 LIVES_GLOBAL_INLINE void swab4(const void *from, const void *to, size_t gran) {
2463   union split4 *d = (union split4 *)to, s;
2464   uint16_t tmp;
2465 
2466   if (gran > 2) {
2467     lives_memcpy((void *)to, from, gran);
2468     return;
2469   }
2470   s.u32 = *(uint32_t *)from;
2471   tmp = s.u16[0];
2472   if (gran == 2) {
2473     d->u16[0] = s.u16[1];
2474     d->u16[1] = tmp;
2475   } else {
2476     swab2(&s.u16[1], &d->u16[0], 1);
2477     swab2(&tmp, &d->u16[1], 1);
2478   }
2479 }
2480 
2481 
2482 // gran(ularity) may be 1, 2 or 4
2483 LIVES_GLOBAL_INLINE void swab8(const void *from, const void *to, size_t gran) {
2484   union split8 *d = (union split8 *)to, s;
2485   uint32_t tmp;
2486   if (gran > 4) {
2487     lives_memcpy((void *)to, from, gran);
2488     return;
2489   }
2490   s.u64 = *(uint64_t *)from;
2491   tmp = s.u32[0];
2492   if (gran == 4) {
2493     d->u32[0] = s.u32[1];
2494     d->u32[1] = tmp;
2495   } else {
2496     swab4(&s.u32[1], &d->u32[0], gran);
2497     swab4(&tmp, &d->u32[1], gran);
2498   }
2499 }
2500 
2501 
2502 LIVES_GLOBAL_INLINE void reverse_bytes(char *buff, size_t count, size_t gran) {
2503   if (count == 2) swab2(buff, buff, 1);
2504   else if (count == 4) swab4(buff, buff, gran);
2505   else if (count == 8) swab8(buff, buff, gran);
2506 }
2507 
2508 
2509 boolean reverse_buffer(uint8_t *buff, size_t count, size_t chunk) {
2510   // reverse chunk sized bytes in buff, count must be a multiple of chunk
2511   ssize_t start = -1, end;
2512   size_t ocount = count;
2513 
2514   if (chunk < 8) {
2515     if ((chunk != 4 && chunk != 2 && chunk != 1) || (count % chunk) != 0) return FALSE;
2516   } else {
2517     if ((chunk & 0x01) || (count % chunk) != 0) return FALSE;
2518     else {
2519 #ifdef USE_RPMALLOC
2520       void *tbuff = rpmalloc(chunk);
2521 #else
2522       void *tbuff = lives_malloc(chunk);
2523 #endif
2524       start++;
2525       end = ocount - 1 - chunk;
2526       while (start + chunk < end) {
2527         lives_memcpy(tbuff, &buff[end], chunk);
2528         lives_memcpy(&buff[end], &buff[start], chunk);
2529         lives_memcpy(&buff[start], tbuff, chunk);
2530         start += chunk;
2531         end -= chunk;
2532       }
2533 #ifdef USE_RPMALLOC
2534       rpfree(tbuff);
2535 #else
2536       lives_free(tbuff);
2537 #endif
2538       return TRUE;
2539     }
2540   }
2541 
2542   /// halve the number of bytes, since we will work forwards and back to meet in the middle
2543   count >>= 1;
2544 
2545   if (count >= 8 && (ocount & 0x07) == 0) {
2546     // start by swapping 8 bytes from each end
2547     uint64_t *buff8 = (uint64_t *)buff;
2548     if ((void *)buff8 == (void *)buff) {
2549       end = ocount  >> 3;
2550       for (; count >= 8; count -= 8) {
2551         /// swap 8 bytes at a time from start and end
2552         uint64_t tmp8 = buff8[--end];
2553         if (chunk == 8) {
2554           buff8[end] = buff8[++start];
2555           buff8[start] = tmp8;
2556         } else {
2557           swab8(&buff8[++start], &buff8[end], chunk);
2558           swab8(&tmp8, &buff8[start], chunk);
2559         }
2560       }
2561       if (count <= chunk / 2) return TRUE;
2562       start = (start + 1) << 3;
2563       start--;
2564     }
2565   }
2566 
2567   /// remainder should be only 6, 4, or 2 bytes in the middle
2568   if (chunk >= 8) return FALSE;
2569 
2570   if (count >= 4 && (ocount & 0x03) == 0) {
2571     uint32_t *buff4 = (uint32_t *)buff;
2572     if ((void *)buff4 == (void *)buff) {
2573       if (start > 0) {
2574         end = (ocount - start) >> 2;
2575         start >>= 2;
2576       } else end = ocount >> 2;
2577       for (; count >= 4; count -= 4) {
2578         /// swap 4 bytes at a time from start and end
2579         uint32_t tmp4 = buff4[--end];
2580         if (chunk == 4) {
2581           buff4[end] = buff4[++start];
2582           buff4[start] = tmp4;
2583         } else {
2584           swab4(&buff4[++start], &buff4[end], chunk);
2585           swab4(&tmp4, &buff4[start], chunk);
2586         }
2587       }
2588       if (count <= chunk / 2) return TRUE;
2589       start = (start + 1) << 2;
2590       start--;
2591     }
2592   }
2593 
2594   /// remainder should be only 6 or 2 bytes in the middle, with a chunk size of 4 or 2 or 1
2595   if (chunk >= 4) return FALSE;
2596 
2597   if (count > 0) {
2598     uint16_t *buff2 = (uint16_t *)buff;
2599     if ((void *)buff2 == (void *)buff) {
2600       if (start > 0) {
2601         end = (ocount - start) >> 1;
2602         start >>= 1;
2603       } else end = ocount >> 1;
2604       for (; count >= chunk / 2; count -= 2) {
2605         /// swap 2 bytes at a time from start and end
2606         uint16_t tmp2 = buff2[--end];
2607         if (chunk >= 2) {
2608           buff2[end] = buff2[++start];
2609           buff2[start] = tmp2;
2610         }
2611         /// swap single bytes
2612         else {
2613           swab2(&buff2[++start], &buff2[end], 1);
2614           swab2(&tmp2, &buff2[start], 1);
2615 	  // *INDENT-OFF*
2616         }}}}
2617   // *INDENT-ON*
2618 
2619   if (count == 0) return TRUE;
2620   return FALSE;
2621 }
2622 
2623 
2624 /// estimate the machine load
2625 static int16_t theflow[EFFORT_RANGE_MAX];
2626 static int flowlen = 0;
2627 static boolean inited = FALSE;
2628 static int struggling = 0;
2629 static int badthingcount = 0;
2630 static int goodthingcount = 0;
2631 
2632 static int pop_flowstate(void) {
2633   int ret = theflow[0];
2634   flowlen--;
2635   for (int i = 0; i < flowlen; i++) {
2636     theflow[i] = theflow[i + 1];
2637   }
2638   return ret;
2639 }
2640 
2641 
2642 void reset_effort(void) {
2643   prefs->pb_quality = future_prefs->pb_quality;
2644   mainw->blend_palette = WEED_PALETTE_END;
2645   lives_memset(theflow, 0, sizeof(theflow));
2646   inited = TRUE;
2647   badthingcount = goodthingcount = 0;
2648   struggling = 0;
2649   if ((mainw->is_rendering || (mainw->multitrack
2650                                && mainw->multitrack->is_rendering)) && !mainw->preview_rendering)
2651     mainw->effort = -EFFORT_RANGE_MAX;
2652   else mainw->effort = 0;
2653 }
2654 
2655 
2656 void update_effort(int nthings, boolean badthings) {
2657   int spcycles;
2658   short pb_quality = prefs->pb_quality;
2659   if (!inited) reset_effort();
2660   if (!nthings) return;
2661 
2662   if (nthings > EFFORT_RANGE_MAX) nthings = EFFORT_RANGE_MAX;
2663 
2664   //g_print("VALS %d %d %d %d %d\n", nthings, badthings, mainw->effort, badthingcount, goodthingcount);
2665   if (badthings)  {
2666     badthingcount += nthings;
2667     goodthingcount = 0;
2668     spcycles = -1;
2669   } else {
2670     spcycles = nthings;
2671     if (spcycles + goodthingcount > EFFORT_RANGE_MAX) spcycles = EFFORT_RANGE_MAX - goodthingcount;
2672     goodthingcount += spcycles;
2673     if (goodthingcount > EFFORT_RANGE_MAX) goodthingcount = EFFORT_RANGE_MAX;
2674     nthings = 1;
2675   }
2676 
2677   while (nthings-- > 0) {
2678     if (flowlen >= EFFORT_RANGE_MAX) {
2679       /// +1 for each badthing, so when it pops out we subtract it
2680       int res = pop_flowstate();
2681       if (res > 0) badthingcount -= res;
2682       else goodthingcount += res;
2683       //g_print("vals %d %d %d  ", res, badthingcount, goodthingcount);
2684     }
2685     /// - all the good things, so when it pops out we add it (i.e subtract the value)
2686     theflow[flowlen] = -spcycles;
2687     flowlen++;
2688   }
2689 
2690   //g_print("vals2x %d %d %d %d\n", mainw->effort, badthingcount, goodthingcount, struggling);
2691 
2692   if (!badthingcount) {
2693     /// no badthings, good
2694     if (goodthingcount > EFFORT_RANGE_MAX) goodthingcount = EFFORT_RANGE_MAX;
2695     if (--mainw->effort < -EFFORT_RANGE_MAX) mainw->effort = -EFFORT_RANGE_MAX;
2696   } else {
2697     if (badthingcount > EFFORT_RANGE_MAX) badthingcount = EFFORT_RANGE_MAX;
2698     mainw->effort = badthingcount;
2699   }
2700   //g_print("vals2 %d %d %d %d\n", mainw->effort, badthingcount, goodthingcount, struggling);
2701 
2702   if (mainw->effort < 0) {
2703     if (struggling > -EFFORT_RANGE_MAX) {
2704       struggling--;
2705     }
2706     if (mainw->effort < -EFFORT_LIMIT_MED) {
2707       if (struggling == -EFFORT_RANGE_MAX && pb_quality < PB_QUALITY_HIGH) {
2708         pb_quality++;
2709       } else if (struggling < -EFFORT_LIMIT_MED && pb_quality < PB_QUALITY_MED) {
2710         pb_quality++;
2711       }
2712     }
2713   }
2714 
2715   if (mainw->effort > 0) {
2716     if (pb_quality > future_prefs->pb_quality) {
2717       pb_quality = future_prefs->pb_quality;
2718       goto tryset;
2719     }
2720     if (!struggling) {
2721       struggling = 1;
2722       return;
2723     }
2724     if (mainw->effort > EFFORT_LIMIT_MED || (struggling > 0 && (mainw->effort > EFFORT_LIMIT_LOW))) {
2725       if (struggling < EFFORT_RANGE_MAX) struggling++;
2726       if (struggling == EFFORT_RANGE_MAX) {
2727         if (pb_quality > PB_QUALITY_LOW) {
2728           pb_quality = PB_QUALITY_LOW;
2729         } else if (mainw->effort > EFFORT_LIMIT_MED) {
2730           if (pb_quality > PB_QUALITY_MED) {
2731             pb_quality--;
2732           }
2733         }
2734       } else {
2735         if (pb_quality > future_prefs->pb_quality) {
2736           pb_quality = future_prefs->pb_quality;
2737         } else if (future_prefs->pb_quality > PB_QUALITY_LOW) {
2738           pb_quality = future_prefs->pb_quality - 1;
2739         }
2740 	// *INDENT-OFF*
2741       }}}
2742   // *INDENT-ON
2743  tryset:
2744   if (pb_quality != prefs->pb_quality && (!mainw->frame_layer_preload || mainw->pred_frame == -1
2745 					  || is_layer_ready(mainw->frame_layer_preload))) {
2746     prefs->pb_quality = pb_quality;
2747     mainw->blend_palette = WEED_PALETTE_END;
2748   }
2749 
2750   //g_print("STRG %d and %d %d\n", struggling, mainw->effort, prefs->pb_quality);
2751 }
2752 
2753 
2754 char *grep_in_cmd(const char *cmd, int mstart, int npieces, const char *mphrase, int ridx, int rlen) {
2755   char **lines, **words, **mwords;
2756   char *match = NULL;
2757   char buff[65536];
2758   size_t nlines, mwlen;
2759   int m, minpieces;
2760 
2761   //break_me("GIC");
2762 
2763   if (!mphrase || npieces < -1 || !npieces || rlen < 1 || (ridx <= mstart && ridx + rlen > mstart)
2764       || (npieces > 0 && (ridx + rlen > npieces || mstart >= npieces))) return NULL;
2765 
2766   mwlen = get_token_count(mphrase, ' ');
2767   if (mstart + mwlen > npieces
2768       || (ridx + rlen > mstart && ridx < mstart + mwlen)) return NULL;
2769 
2770   mwords = lives_strsplit(mphrase, " ", mwlen);
2771 
2772   if (!cmd || !mphrase || !*cmd || !*mphrase) goto grpcln;
2773   lives_popen(cmd, FALSE, buff, 65536);
2774   if (THREADVAR(com_failed)
2775       || (!*buff || !(nlines = get_token_count(buff, '\n')))) {
2776     THREADVAR(com_failed) = FALSE;
2777     goto grpcln;
2778   }
2779 
2780   minpieces = MAX(mstart + mwlen, ridx + rlen);
2781 
2782   lines = lives_strsplit(buff, "\n", nlines);
2783   for (int l = 0; l < nlines; l++) {
2784     if (*lines[l] && get_token_count(lines[l], ' ') >= minpieces) {
2785       words = lives_strsplit(lines[l], " ", npieces);
2786       for (m = 0; m < mwlen; m++) {
2787 	if (lives_strcmp(words[m + mstart], mwords[m])) break;
2788       }
2789       if (m == mwlen) {
2790 	match = lives_strdup(words[ridx]);
2791 	for (int w = 1; w < rlen; w++) {
2792 	  char *tmp = lives_strdup_printf(" %s", words[ridx + w]);
2793 	  match = lives_concat(match, tmp);
2794 	}
2795       }
2796       lives_strfreev(words);
2797     }
2798     if (match) break;
2799   }
2800   lives_strfreev(lines);
2801  grpcln:
2802   lives_strfreev(mwords);
2803   return match;
2804 }
2805 
2806 LIVES_LOCAL_INLINE boolean mini_run(char *cmd) {
2807   if (!cmd) return FALSE;
2808   lives_system(cmd, TRUE);
2809   lives_free(cmd);
2810   if (THREADVAR(com_failed)) return FALSE;
2811   return TRUE;
2812 }
2813 
2814 LIVES_LOCAL_INLINE char *mini_popen(char *cmd) {
2815   if (!cmd) return NULL;
2816   else {
2817     char buff[PATH_MAX];
2818     //char *com = lives_strdup_printf("%s $(%s)", capable->echo_cmd, EXEC_MKTEMP);
2819     lives_popen(cmd, TRUE, buff, PATH_MAX);
2820     lives_free(cmd);
2821     lives_chomp(buff);
2822     return lives_strdup(buff);
2823   }
2824 }
2825 
2826 
2827 LiVESResponseType send_to_trash(const char *item) {
2828   LiVESResponseType resp = LIVES_RESPONSE_NONE;
2829   boolean retval = TRUE;
2830   char *reason = NULL;
2831 #ifndef IMPL_TRASH
2832   do {
2833     resp = LIVES_RESPONSE_NONE;
2834     if (!check_for_executable(&capable->has_gio, EXEC_GIO)) {
2835       reason = lives_strdup_printf(_("%s was not found\n"), EXEC_GIO);
2836       retval = FALSE;
2837     }
2838     else {
2839       char *com = lives_strdup_printf("%s trash \"%s\"", EXEC_GIO, item);
2840       retval = mini_run(com);
2841     }
2842 #else
2843     /// TODO *** - files should be moved to
2844     /// 1) if not $HOME partition, capable->mountpoint/.Trash; also check all toplevels
2845     /// check for sticky bit and also non symlink. Then create uid subdir
2846     /// else try to create mountpoint / .Trash-$uid
2847     /// else (or if in home dir):
2848     /// capable->xdg_data_home/Trash/
2849 
2850     /// create an entry like info/foo1.trashinfo (O_EXCL)
2851 
2852     /// [Trash Info]
2853     /// Path=/home/user/livesprojects/foo1
2854     /// DeletionDate=2020-07-11T14:57:00
2855 
2856     /// then move / copy file or dir to files/foo1
2857     /// - if already exists, append .2, .3 etc.
2858     // see: https://specifications.freedesktop.org/trash-spec/trashspec-latest.html
2859     int vnum = 0;
2860     char *trashdir;
2861     char *mp1 = get_mountpount_for(item);
2862     char *mp2 = get_mountpount_for(capable->home_dir);
2863     if (!lives_strcmp(mp1, mp2)) {
2864       char *localshare = lives_strdup(capable->xdg_data_home);
2865       if (!*localshare) {
2866 	lives_free(localshare);
2867 	localshare = lives_build_path(capable->home_dir, LOCAL_HOME_DIR, "share", NULL);
2868       }
2869       trashdir = lives_build_path(localshare, "Trash", NULL);
2870       trashinfodir = lives_build_path(trashdir, "info", NULL);
2871       trashfilesdir = lives_build_path(trashdir, "files", NULL);
2872       umask = capable->umask;
2873       capable->umask = 0700;
2874       if (!check_dir_access(trashinfodir, TRUE)) {
2875 	retval = FALSE;
2876 	reason = lives_strdup_printf(_("Could not write to %s\n"), trashinfodir);
2877       }
2878       if (retval) {
2879 	if (!check_dir_access(trashfilesdir, TRUE)) {
2880 	  retval = FALSE;
2881 	  reason = lives_strdup_printf(_("Could not write to %s\n"), trashfilesdir);
2882 	}
2883       }
2884       capable->umask = umask;
2885       if (retval) {
2886 	char *trashinfo;
2887 	int fd;
2888 	while (1) {
2889 	  if (!vnum) trashinfo = lives_strdup_printf("%s.trashinfo", basenm);
2890 	  else trashinfo = lives_strdup_printf("%s.%d.trashinfo", basenm, vnum);
2891 	  fname = lives_build_filename(trashinfodir, trashinfo, NULL);
2892 	  fd = lives_open2(fname, O_CREAT | O_EXCL);
2893 	  if (fd) break;
2894 	  vnum++;
2895 	}
2896 	// TODO - write stuff, close, move item
2897 
2898 
2899       }
2900     }
2901     /// TODO...
2902 #endif
2903     if (!retval) {
2904       char *msg = lives_strdup_printf(_("LiVES was unable to send the item to trash.\n%s"), reason ? reason : "");
2905       lives_freep((void **)&reason);
2906       resp = do_abort_cancel_retry_dialog(msg);
2907       lives_free(msg);
2908       if (resp == LIVES_RESPONSE_CANCEL) return resp;
2909     }
2910   } while (resp == LIVES_RESPONSE_RETRY);
2911   return LIVES_RESPONSE_OK;
2912 }
2913 
2914 
2915 /// x11 stuff
2916 
2917 char *get_wid_for_name(const char *wname) {
2918 #ifndef GDK_WINDOWING_X11
2919   return NULL;
2920 #else
2921   char *wid = NULL, *cmd;
2922   if (!wname || !*wname) return NULL;
2923 
2924   if (check_for_executable(&capable->has_wmctrl, EXEC_WMCTRL)) {
2925     cmd = lives_strdup_printf("%s -l", EXEC_WMCTRL);
2926     wid = grep_in_cmd(cmd, 3, 4, wname, 0, 1);
2927     lives_free(cmd);
2928     if (wid) return wid;
2929   }
2930   if (check_for_executable(&capable->has_xwininfo, EXEC_XWININFO)) {
2931     cmd = lives_strdup_printf("%s -name \"%s\" 2>/dev/null", EXEC_XWININFO, wname);
2932     wid = grep_in_cmd(cmd, 1, -1, "Window id:", 3, 1);
2933     lives_free(cmd);
2934     if (wid) return wid;
2935   }
2936   if (check_for_executable(&capable->has_xdotool, EXEC_XDOTOOL)) {
2937     char buff[65536];
2938     size_t nlines;
2939     // returns a list, and we need to check each one
2940     cmd = lives_strdup_printf("%s search \"%s\"", EXEC_XDOTOOL, wname);
2941     lives_popen(cmd, FALSE, buff, 65536);
2942     lives_free(cmd);
2943     if (THREADVAR(com_failed)
2944 	|| (!*buff || !(nlines = get_token_count(buff, '\n')))) {
2945       if (THREADVAR(com_failed)) THREADVAR(com_failed) = FALSE;
2946     }
2947     else {
2948       char buff2[1024];
2949       char **lines = lives_strsplit(buff, "\n", nlines);
2950       for (int l = 0; l < nlines; l++) {
2951 	if (!*lines[l]) continue;
2952 	cmd = lives_strdup_printf("%s getwindowname %s", EXEC_XDOTOOL, lines[l]);
2953 	lives_popen(cmd, FALSE, buff2, 1024);
2954 	lives_free(cmd);
2955 	if (THREADVAR(com_failed)) {
2956 	  THREADVAR(com_failed) = FALSE;
2957 	  break;
2958 	}
2959 	lives_chomp(buff2);
2960 	if (!lives_strcmp(wname, buff2)) {
2961 	  wid = lives_strdup_printf("0x%lX", atol(lines[l]));
2962 	  break;
2963 	}
2964       }
2965       lives_strfreev(lines);
2966     }
2967   }
2968   return wid;
2969 #endif
2970 }
2971 
2972 
2973 boolean hide_x11_window(const char *wid) {
2974   char *cmd = NULL;
2975 #ifndef GDK_WINDOWING_X11
2976   return NULL;
2977 #endif
2978   if (!wid) return FALSE;
2979   if (check_for_executable(&capable->has_xdotool, EXEC_XDOTOOL)) {
2980     cmd = lives_strdup_printf("%s windowminimize \"%s\"", EXEC_XDOTOOL, wid);
2981     return mini_run(cmd);
2982   }
2983   return FALSE;
2984 }
2985 
2986 
2987 boolean unhide_x11_window(const char *wid) {
2988   char *cmd = NULL;
2989 #ifndef GDK_WINDOWING_X11
2990   return FALSE;
2991 #endif
2992   if (!wid) return FALSE;
2993   if (check_for_executable(&capable->has_xdotool, EXEC_XDOTOOL))
2994     cmd = lives_strdup_printf("%s windowmap \"%s\"", EXEC_XDOTOOL, wid);
2995   return mini_run(cmd);
2996 }
2997 
2998 boolean activate_x11_window(const char *wid) {
2999   char *cmd = NULL;
3000 #ifndef GDK_WINDOWING_X11
3001   return FALSE;
3002 #endif
3003   if (!wid) return FALSE;
3004 
3005   if (capable->has_xdotool != MISSING) {
3006     if (check_for_executable(&capable->has_xdotool, EXEC_XDOTOOL))
3007       cmd = lives_strdup_printf("%s windowactivate \"%s\"", EXEC_XDOTOOL, wid);
3008   }
3009   else if (capable->has_wmctrl != MISSING) {
3010     if (check_for_executable(&capable->has_wmctrl, EXEC_WMCTRL))
3011       cmd = lives_strdup_printf("%s -Fa \"%s\"", EXEC_WMCTRL, wid);
3012   }
3013   else return FALSE;
3014   return mini_run(cmd);
3015 }
3016 
3017 
3018 boolean get_wm_caps(void) {
3019   char *wmname;
3020   if (capable->has_wm_caps) return TRUE;
3021   capable->has_wm_caps = TRUE;
3022 
3023 #if IS_MINGW
3024   capable->wm_caps.is_composited = TRUE;
3025   capable->wm_caps.root_window = gdk_screen_get_root_window(mainw->mgeom[widget_opts.monitor].screen);
3026 #else
3027 #ifdef GUI_GTK
3028   capable->wm_caps.is_composited = gdk_screen_is_composited(mainw->mgeom[widget_opts.monitor].screen);
3029   capable->wm_caps.root_window = gdk_screen_get_root_window(mainw->mgeom[widget_opts.monitor].screen);
3030 #else
3031   capable->wm_caps.is_composited = FALSE;
3032   capable->wm_caps.root_window = NULL;
3033 #endif
3034 #endif
3035 
3036   capable->wm_type = getenv(XDG_SESSION_TYPE);
3037 
3038   wmname = getenv(XDG_CURRENT_DESKTOP);
3039 
3040   if (!wmname) {
3041     if (capable->wm_name) wmname = capable->wm_name;
3042   }
3043   if (!wmname) return FALSE;
3044 
3045   capable->has_wm_caps = TRUE;
3046   lives_snprintf(capable->wm_caps.wm_name, 64, "%s", wmname);
3047 
3048   if (!strcmp(capable->wm_caps.wm_name, WM_XFWM4) || !strcmp(capable->wm_name, WM_XFWM4)) {
3049     lives_snprintf(capable->wm_caps.panel, 64, "%s", WM_XFCE4_PANEL);
3050     capable->wm_caps.pan_annoy = ANNOY_DISPLAY | ANNOY_FS;
3051     capable->wm_caps.pan_res = RES_HIDE | RESTYPE_ACTION;
3052     lives_snprintf(capable->wm_caps.ssave, 64, "%s", WM_XFCE4_SSAVE);
3053     lives_snprintf(capable->wm_caps.color_settings, 64, "%s", WM_XFCE4_COLOR);
3054     lives_snprintf(capable->wm_caps.display_settings, 64, "%s", WM_XFCE4_DISP);
3055     lives_snprintf(capable->wm_caps.ssv_settings, 64, "%s", WM_XFCE4_SSAVE);
3056     lives_snprintf(capable->wm_caps.pow_settings, 64, "%s", WM_XFCE4_POW);
3057     lives_snprintf(capable->wm_caps.settings, 64, "%s", WM_XFCE4_SETTINGS);
3058     lives_snprintf(capable->wm_caps.term, 64, "%s", WM_XFCE4_TERMINAL);
3059     lives_snprintf(capable->wm_caps.taskmgr, 64, "%s", WM_XFCE4_TASKMGR);
3060     lives_snprintf(capable->wm_caps.sshot, 64, "%s", WM_XFCE4_SSHOT);
3061     return TRUE;
3062   }
3063   if (!strcmp(capable->wm_caps.wm_name, WM_KWIN) || !strcmp(capable->wm_name, WM_KWIN)) {
3064     lives_snprintf(capable->wm_caps.panel, 64, "%s", WM_KWIN_PANEL);
3065     lives_snprintf(capable->wm_caps.ssave, 64, "%s", WM_KWIN_SSAVE);
3066     lives_snprintf(capable->wm_caps.color_settings, 64, "%s", WM_KWIN_COLOR);
3067     lives_snprintf(capable->wm_caps.display_settings, 64, "%s", WM_KWIN_DISP);
3068     lives_snprintf(capable->wm_caps.ssv_settings, 64, "%s", WM_KWIN_SSAVE);
3069     lives_snprintf(capable->wm_caps.pow_settings, 64, "%s", WM_KWIN_POW);
3070     lives_snprintf(capable->wm_caps.settings, 64, "%s", WM_KWIN_SETTINGS);
3071     lives_snprintf(capable->wm_caps.term, 64, "%s", WM_KWIN_TERMINAL);
3072     lives_snprintf(capable->wm_caps.taskmgr, 64, "%s", WM_KWIN_TASKMGR);
3073     lives_snprintf(capable->wm_caps.sshot, 64, "%s", WM_KWIN_SSHOT);
3074     return TRUE;
3075   }
3076   return FALSE;
3077 }
3078 
3079 
3080 int get_window_stack_level(LiVESXWindow *xwin, int *nwins) {
3081 #ifndef GUI_GTK
3082   if (nwins) *nwins = -1;
3083   return -1;
3084 #else
3085   int mywin = -1, i = 0;
3086   LiVESList *winlist = gdk_screen_get_window_stack(mainw->mgeom[widget_opts.monitor].screen), *list = winlist;
3087   for (; list; list = list->next, i++) {
3088     if ((LiVESXWindow *)list->data == xwin) mywin = i;
3089     lives_widget_object_unref(list->data);
3090   }
3091   lives_list_free(winlist);
3092   if (nwins) *nwins = ++i;
3093   return mywin;
3094 #endif
3095 }
3096 
3097 
3098 boolean show_desktop_panel(void) {
3099   boolean ret = FALSE;
3100 #ifdef GDK_WINDOWING_X11
3101   char *wid = get_wid_for_name(capable->wm_caps.panel);
3102   if (wid) {
3103     ret = unhide_x11_window(wid);
3104     lives_free(wid);
3105   }
3106 #endif
3107   return ret;
3108 }
3109 
3110 boolean hide_desktop_panel(void) {
3111   boolean ret = FALSE;
3112 #ifdef GDK_WINDOWING_X11
3113   char *wid = get_wid_for_name(capable->wm_caps.panel);
3114   if (wid) {
3115     ret = hide_x11_window(wid);
3116     lives_free(wid);
3117   }
3118 #endif
3119   return ret;
3120 }
3121 
3122 
3123 boolean get_x11_visible(const char *wname) {
3124   char *cmd = NULL;
3125 #ifndef GDK_WINDOWING_X11
3126   return FALSE;
3127 #endif
3128   if (!wname || !*wname) return FALSE;
3129   if (check_for_executable(&capable->has_xwininfo, EXEC_XWININFO)) {
3130     char *state;
3131     cmd = lives_strdup_printf("%s -name \"%s\"", EXEC_XWININFO, wname);
3132     state = grep_in_cmd(cmd, 2, -1, "Map State:", 4, 1);
3133     lives_free(cmd);
3134     if (state && !strcmp(state, "IsViewable")) {
3135       lives_free(state);
3136       return TRUE;
3137     }
3138   }
3139   if (wname && check_for_executable(&capable->has_xdotool, EXEC_XDOTOOL)) {
3140     char buff[65536];
3141     size_t nlines;
3142 
3143     // returns a list, and we need to check each one
3144     cmd = lives_strdup_printf("%s search --all --onlyvisible \"%s\" 2>/dev/null", EXEC_XDOTOOL, wname);
3145     lives_popen(cmd, FALSE, buff, 65536);
3146     lives_free(cmd);
3147     if (THREADVAR(com_failed)
3148 	|| (!*buff || !(nlines = get_token_count(buff, '\n')))) {
3149       if (THREADVAR(com_failed)) THREADVAR(com_failed) = FALSE;
3150     }
3151     else {
3152       char *wid = get_wid_for_name(wname);
3153       if (wid) {
3154 	int l;
3155 	char **lines = lives_strsplit(buff, "\n", nlines), *xwid;
3156 	for (l = 0; l < nlines; l++) {
3157 	  if (!*lines[l]) continue;
3158 	  xwid = lives_strdup_printf("0x%08lX", atol(lines[l]));
3159 	  if (!strcmp(xwid, wid)) break;
3160 	}
3161 	lives_strfreev(lines);
3162 	lives_free(wid);
3163 	if (l < nlines)  return TRUE;
3164       }
3165     }
3166   }
3167   return FALSE;
3168 }
3169 
3170 #define XTEMP "XXXXXXXXXX"
3171 
3172 static char *get_systmp_inner(const char *suff, boolean is_dir, const char *prefix) {
3173   /// create a file or dir in /tmp or prefs->workdir
3174   /// check the name returned has the length we expect
3175   /// check it was created
3176   /// ensure it is not a symlink
3177   /// if a directory, ensure we have rw access
3178   char *res = NULL;
3179 
3180   if (!check_for_executable(&capable->has_mktemp, EXEC_MKTEMP)) return NULL;
3181   else {
3182     size_t slen;
3183     char *tmp, *com;
3184     const char *dirflg, *tmpopt;
3185     if (!prefix) {
3186       // no prefix, create in $TMPDIR
3187       if (suff) tmp = lives_strdup_printf("lives-%s-%s", XTEMP, suff);
3188       else tmp = lives_strdup_printf("lives-%s", XTEMP);
3189       tmpopt = "t";
3190       slen = lives_strlen(tmp) + 2;
3191     }
3192     else {
3193       /// suff here is the directory name
3194       char *tmpfile = lives_strdup_printf("%s%s", prefix, XTEMP);
3195       tmp = lives_build_filename(suff, tmpfile, NULL);
3196       lives_free(tmpfile);
3197       tmpopt = "";
3198       slen = lives_strlen(tmp);
3199     }
3200 
3201     if (is_dir) dirflg = "d";
3202     else dirflg = "";
3203 
3204     com = lives_strdup_printf("%s -n $(%s -q%s%s \"%s\")", capable->echo_cmd, EXEC_MKTEMP, tmpopt,
3205 			      dirflg, tmp);
3206     lives_free(tmp);
3207     res = mini_popen(com);
3208     if (!res) return NULL;
3209     if (THREADVAR(com_failed)) {
3210       lives_free(res);
3211       return NULL;
3212     }
3213     if (lives_strlen(res) < slen) {
3214       lives_free(res);
3215       return NULL;
3216     }
3217   }
3218   if (!lives_file_test(res, LIVES_FILE_TEST_EXISTS)
3219       || lives_file_test(res, LIVES_FILE_TEST_IS_SYMLINK)) {
3220       lives_free(res);
3221       return NULL;
3222   }
3223   if (is_dir) {
3224     if (!check_dir_access(res, FALSE)) {
3225       lives_free(res);
3226       return NULL;
3227     }
3228   }
3229   return res;
3230 }
3231 
3232 char *get_systmp(const char *suff, boolean is_dir) {
3233   return get_systmp_inner(suff, is_dir, NULL);
3234 }
3235 
3236 static char *_get_worktmp(const char *prefix, boolean is_dir) {
3237   char *dirname = NULL;
3238   char *tmpdir = get_systmp_inner(prefs->workdir, is_dir, prefix);
3239   if (tmpdir) {
3240     dirname = lives_path_get_basename(tmpdir);
3241     lives_free(tmpdir);
3242   }
3243   return dirname;
3244 }
3245 
3246 char *get_worktmp(const char *prefix) {
3247   if (!prefix) return NULL;
3248   return _get_worktmp(prefix, TRUE);
3249 }
3250 
3251 char *get_worktmpfile(const char *prefix) {
3252   if (!prefix) return NULL;
3253   return _get_worktmp(prefix, FALSE);
3254 }
3255 
3256 
3257 boolean check_snap(const char *prog) {
3258   // not working yet...
3259   if (!check_for_executable(&capable->has_snap, EXEC_SNAP)) return FALSE;
3260   char *com = lives_strdup_printf("%s find %s", EXEC_SNAP, prog);
3261   char *res = grep_in_cmd(com, 0, 1, prog, 0, 1);
3262   if (!res) return FALSE;
3263   lives_free(res);
3264   return TRUE;
3265 }
3266 
3267 
3268 boolean get_distro_dets(void) {
3269 #ifndef IS_LINUX
3270   capable->distro_name = lives_strdup(capable->os_name);
3271   capable->distro_ver = lives_strdup(capable->os_release);
3272 #else
3273 #define LSB_OS_FILE "/etc/lsb-release"
3274   char *com = lives_strdup_printf("%s %s", capable->cat_cmd, LSB_OS_FILE), *ret;
3275   if ((ret = mini_popen(com))) {
3276     int xlen = get_token_count(ret, '=');
3277     char **array = lives_strsplit(ret, "=", xlen);
3278     lives_free(ret);
3279     if (xlen > 1) {
3280       lives_strstop(array[1], '\n');
3281       capable->distro_name = lives_strdup(array[1]);
3282       if (xlen > 2) {
3283 	lives_strstop(array[2], '\n');
3284 	capable->distro_ver = lives_strdup(array[2]);
3285 	if (xlen > 3) {
3286 	  lives_strstop(array[3], '\n');
3287 	  capable->distro_codename = lives_strdup(array[3]);
3288 	}}}
3289     lives_strfreev(array);
3290     return TRUE;
3291   }
3292 #endif
3293   return FALSE;
3294 }
3295 
3296 
3297 int get_num_cpus(void) {
3298 #ifdef IS_DARWIN
3299   kerr = host_processor_info(mach_host_self(), PROCESSOR_BASIC_INFO, &numProcessors, &processorInfo, &numProcessorInfo);
3300   if (kerr == KERN_SUCCESS) {
3301     vm_deallocate(mach_task_self(), (vm_address_t) processorInfo, numProcessorInfo * sizint);
3302   }
3303   return numProcessors;
3304 #else
3305   char buffer[1024];
3306   char command[PATH_MAX];
3307 #if defined(__DragonFly___)
3308   lives_snprintf(command, PATH_MAX, "sysctl -n hw.ncpu");
3309 #elif defined(IS_FREEBSD)
3310   lives_snprintf(command, PATH_MAX, "sysctl -n kern.smp.cpus");
3311 #else
3312   lives_snprintf(command, PATH_MAX, "%s processor /proc/cpuinfo 2>/dev/null | %s -l 2>/dev/null",
3313                  capable->grep_cmd, capable->wc_cmd);
3314 #endif
3315   lives_popen(command, TRUE, buffer, 1024);
3316   return atoi(buffer);
3317 #endif
3318 }
3319 
3320 
3321 boolean get_machine_dets(void) {
3322 #if defined(IS_FREEBSD) || defined(__DragonFly__)
3323   char *com = lives_strdup("sysctl -n hw.model");
3324 #else
3325   char *com = lives_strdup_printf("%s -m1 \"^model name\" /proc/cpuinfo | %s -e \"s/.*: //\" -e \"s:\\s\\+:/:g\"",
3326 				  capable->grep_cmd, capable->sed_cmd);
3327 #endif
3328   capable->cpu_name = mini_popen(com);
3329 
3330   com = lives_strdup("uname -o");
3331   capable->os_name = mini_popen(com);
3332 
3333   com = lives_strdup("uname -r");
3334   capable->os_release = mini_popen(com);
3335 
3336   com = lives_strdup("uname -m");
3337   capable->os_hardware = mini_popen(com);
3338 
3339   capable->cacheline_size = capable->cpu_bits * 8;
3340 
3341 #if IS_X86_64
3342   if (!strcmp(capable->os_hardware, "x86_64")) capable->cacheline_size = get_cacheline_size();
3343 #endif
3344 
3345   com = lives_strdup("uname -n");
3346   capable->mach_name = mini_popen(com);
3347 
3348   com = lives_strdup("whoami");
3349   capable->username = mini_popen(com);
3350 
3351   if (THREADVAR(com_failed)) {
3352     THREADVAR(com_failed) = FALSE;
3353     return FALSE;
3354   }
3355   return TRUE;
3356 }
3357 
3358 
3359 #define DISK_STATS_FILE "/proc/diskstats"
3360 
3361 double get_disk_load(const char *mp) {
3362   if (!mp) return -1;
3363   else {
3364     static ticks_t lticks = 0;
3365     static uint64_t lval = 0;
3366     double ret = -1.;
3367     const char *xmp;
3368     char *com, *res;
3369     if (!lives_strncmp(mp, "/dev/", 5))
3370       xmp = (char *)mp + 5;
3371     else
3372       xmp = mp;
3373     com = lives_strdup_printf("%s -n $(%s %s %s)", capable->echo_cmd, capable->grep_cmd, xmp, DISK_STATS_FILE);
3374     if ((res = mini_popen(com))) {
3375       int xbits = get_token_count(res, ' ');
3376       char **array = lives_strsplit(res, " ", xbits);
3377       lives_free(res);
3378       if (xbits > 13) {
3379 	uint64_t val = atoll(array[13]);
3380 	ticks_t clock_ticks;
3381 	if (LIVES_IS_PLAYING) clock_ticks = mainw->clock_ticks;
3382 	else clock_ticks = lives_get_current_ticks();
3383 	if (lticks > 0 && clock_ticks > lticks) ret = (double)(val - lval) / ((double)(clock_ticks - lticks)
3384 									      / TICKS_PER_SECOND_DBL);
3385 	lticks = clock_ticks;
3386 	lval = val;
3387       }
3388       lives_strfreev(array);
3389     }
3390   return ret;
3391   }
3392   return -1.;
3393 }
3394 
3395 
3396 #define CPU_STATS_FILE "/proc/stat"
3397 
3398 int64_t get_cpu_load(int cpun) {
3399   /// return reported load for CPU cpun (% * 1 million)
3400   /// as a bonus, if cpun == -1, returns boot time
3401   static uint64_t lidle = 0, lsum = 0;
3402   int64_t ret = -1;
3403   char *res, *target, *com;
3404   if (cpun > 0) target = lives_strdup_printf("cpu%d", --cpun);
3405   else if (cpun == 0) target = lives_strdup_printf("cpu");
3406   else target = lives_strdup_printf("btime");
3407   com = lives_strdup_printf("%s -n $(%s %s %s)", capable->echo_cmd, capable->grep_cmd, target, CPU_STATS_FILE);
3408   if ((res = mini_popen(com))) {
3409     int xbits = get_token_count(res, ' ');
3410     char **array = lives_strsplit(res, " ", xbits);
3411     lives_free(res);
3412     if (cpun == -1) {
3413       // get boot time
3414       if (xbits > 1) {
3415 	ret = atoll(array[1]);
3416       }
3417     }
3418     else {
3419       if (xbits > 7) {
3420 	uint64_t user = atoll(array[1]);
3421 	uint64_t nice = atoll(array[2]);
3422 	uint64_t sys = atoll(array[3]);
3423 	uint64_t idle = atoll(array[4]);
3424 	uint64_t iowait = atoll(array[5]);
3425 	uint64_t irq = atoll(array[6]);
3426 	uint64_t softirq = atoll(array[7]);
3427 	uint64_t sum = user + nice + sys + idle + iowait + irq + softirq;
3428 	if (lsum) {
3429 	  double load = 1. - (double)(idle - lidle) / (double)(sum - lsum);
3430 	  ret = load * (double)MILLIONS(1);
3431 	}
3432 	lsum = sum;
3433 	lidle = idle;
3434       }
3435       lives_strfreev(array);
3436     }
3437   }
3438   return ret;
3439 }
3440