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