brewery
notashelf /
e1f534d0e6c317bfae0dfd4c6b00703bdcfc136b

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/utils.cC493 lines10.9 KB
1
#include <ctype.h>
2
#include <errno.h>
3
#include <libgen.h>
4
#include <pwd.h>
5
#include <signal.h>
6
#include <stdio.h>
7
#include <stdlib.h>
8
#include <string.h>
9
#include <sys/stat.h>
10
#include <sys/time.h>
11
#include <sys/types.h>
12
#include <time.h>
13
#include <unistd.h>
14
15
#include "../include/chroma.h"
16
17
// Global state pointer for signal handling
18
static chroma_state_t *g_state = NULL;
19
static char *g_config_file = NULL;
20
21
// Signal handler implementation
22
static void signal_handler_impl(int sig) {
23
  switch (sig) {
24
  case SIGTERM:
25
  case SIGINT:
26
    chroma_log("INFO", "Received signal %d (%s), shutting down gracefully", sig,
27
               (sig == SIGTERM) ? "SIGTERM" : "SIGINT");
28
    chroma_should_quit = 1;
29
    if (g_state) {
30
      g_state->running = false;
31
    }
32
    break;
33
  case SIGHUP:
34
    chroma_log("INFO", "Received SIGHUP, reloading configuration");
35
    if (g_state && g_config_file) {
36
      chroma_reload_config(g_state, g_config_file);
37
    }
38
    break;
39
  case SIGPIPE:
40
    // Ignore SIGPIPE - we'll handle broken pipes in read/write calls
41
    break;
42
  default:
43
    chroma_log("WARN", "Received unexpected signal: %d", sig);
44
    break;
45
  }
46
}
47
48
// Set up signal handlers
49
void chroma_handle_signals(void) {
50
  struct sigaction sa;
51
52
  memset(&sa, 0, sizeof(sa));
53
  sa.sa_handler = signal_handler_impl;
54
  sigemptyset(&sa.sa_mask);
55
  sa.sa_flags = SA_RESTART;
56
57
  // Install signal handlers
58
  if (sigaction(SIGTERM, &sa, NULL) == -1) {
59
    chroma_log("ERROR", "Failed to install SIGTERM handler: %s",
60
               strerror(errno));
61
  }
62
63
  if (sigaction(SIGINT, &sa, NULL) == -1) {
64
    chroma_log("ERROR", "Failed to install SIGINT handler: %s",
65
               strerror(errno));
66
  }
67
68
  if (sigaction(SIGHUP, &sa, NULL) == -1) {
69
    chroma_log("ERROR", "Failed to install SIGHUP handler: %s",
70
               strerror(errno));
71
  }
72
73
  // Ignore SIGPIPE
74
  sa.sa_handler = SIG_IGN;
75
  if (sigaction(SIGPIPE, &sa, NULL) == -1) {
76
    chroma_log("ERROR", "Failed to ignore SIGPIPE: %s", strerror(errno));
77
  }
78
79
  chroma_log("DEBUG", "Signal handlers installed");
80
}
81
82
// Set global state for signal handling
83
void chroma_set_signal_state(chroma_state_t *state, const char *config_file) {
84
  g_state = state;
85
86
  free(g_config_file);
87
  g_config_file = config_file ? strdup(config_file) : NULL;
88
}
89
90
// Clean up signal handling resources
91
void chroma_cleanup_signals(void) {
92
  g_state = NULL;
93
  free(g_config_file);
94
  g_config_file = NULL;
95
}
96
97
// Expand environment variables in a string
98
static char *expand_env_vars(const char *str) {
99
  if (!str || strchr(str, '$') == NULL) {
100
    return strdup(str);
101
  }
102
103
  char *result = strdup("");
104
  if (!result) {
105
    return NULL;
106
  }
107
108
  const char *p = str;
109
  while (*p) {
110
    if (*p == '$') {
111
      p++;
112
      if (*p == '{') {
113
        // ${VAR} format
114
        p++;
115
        const char *end = strchr(p, '}');
116
        if (!end) {
117
          // No closing brace, treat as literal
118
          char *tmp = realloc(result, strlen(result) + 2);
119
          if (!tmp) {
120
            free(result);
121
            return NULL;
122
          }
123
          result = tmp;
124
          strcat(result, "${");
125
          break;
126
        }
127
128
        size_t var_len = (size_t)(end - p);
129
        char *var_name = malloc(var_len + 1);
130
        if (!var_name) {
131
          free(result);
132
          return NULL;
133
        }
134
        strncpy(var_name, p, var_len);
135
        var_name[var_len] = '\0';
136
137
        const char *var_value = getenv(var_name);
138
        if (var_value) {
139
          char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
140
          if (!tmp) {
141
            free(var_name);
142
            free(result);
143
            return NULL;
144
          }
145
          result = tmp;
146
          strcat(result, var_value);
147
        }
148
149
        free(var_name);
150
        p = end + 1;
151
      } else {
152
        // $VAR format
153
        const char *start = p;
154
        while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
155
          p++;
156
        }
157
158
        if (p == start) {
159
          // Not a valid variable name, treat $ as literal
160
          char *tmp = realloc(result, strlen(result) + 2);
161
          if (!tmp) {
162
            free(result);
163
            return NULL;
164
          }
165
          result = tmp;
166
          strcat(result, "$");
167
        } else {
168
          size_t var_len = (size_t)(p - start);
169
          char *var_name = malloc(var_len + 1);
170
          if (!var_name) {
171
            free(result);
172
            return NULL;
173
          }
174
          strncpy(var_name, start, var_len);
175
          var_name[var_len] = '\0';
176
177
          const char *var_value = getenv(var_name);
178
          if (var_value) {
179
            char *tmp = realloc(result, strlen(result) + strlen(var_value) + 1);
180
            if (!tmp) {
181
              free(var_name);
182
              free(result);
183
              return NULL;
184
            }
185
            result = tmp;
186
            strcat(result, var_value);
187
          }
188
189
          free(var_name);
190
        }
191
      }
192
    } else {
193
      // Regular character
194
      size_t len = strlen(result);
195
      char *tmp = realloc(result, len + 2);
196
      if (!tmp) {
197
        free(result);
198
        return NULL;
199
      }
200
      result = tmp;
201
      result[len] = *p;
202
      result[len + 1] = '\0';
203
      p++;
204
    }
205
  }
206
207
  return result;
208
}
209
210
// Expand tilde and environment variables in path
211
char *chroma_expand_path(const char *path) {
212
  if (!path) {
213
    return NULL;
214
  }
215
216
  // First expand environment variables
217
  char *env_expanded = expand_env_vars(path);
218
  if (!env_expanded) {
219
    return NULL;
220
  }
221
222
  // Then expand tilde if present
223
  if (env_expanded[0] != '~') {
224
    return env_expanded;
225
  }
226
227
  const char *home;
228
  if (env_expanded[1] == '/' || env_expanded[1] == '\0') {
229
    // ~/... or just ~
230
    home = getenv("HOME");
231
    if (!home) {
232
      struct passwd *pw = getpwuid(getuid());
233
      if (pw) {
234
        home = pw->pw_dir;
235
      }
236
    }
237
    if (!home) {
238
      chroma_log("ERROR", "Could not determine home directory");
239
      free(env_expanded);
240
      return strdup(path); // Return original path as fallback
241
    }
242
243
    size_t home_len = strlen(home);
244
    size_t path_len = strlen(env_expanded);
245
    char *expanded = malloc(home_len + path_len); // -1 for ~ +1 for \0
246
    if (!expanded) {
247
      chroma_log("ERROR", "Failed to allocate memory for path expansion");
248
      free(env_expanded);
249
      return strdup(path);
250
    }
251
252
    strcpy(expanded, home);
253
    if (env_expanded[1] == '/') {
254
      strcat(expanded, env_expanded + 1);
255
    }
256
257
    free(env_expanded);
258
    return expanded;
259
  } else {
260
    // ~user/...
261
    const char *slash = strchr(env_expanded, '/');
262
    size_t user_len =
263
        slash ? (size_t)(slash - env_expanded - 1) : strlen(env_expanded) - 1;
264
265
    char *username = malloc(user_len + 1);
266
    if (!username) {
267
      free(env_expanded);
268
      return strdup(path);
269
    }
270
271
    strncpy(username, env_expanded + 1, user_len);
272
    username[user_len] = '\0';
273
274
    struct passwd *pw = getpwnam(username);
275
276
    if (!pw) {
277
      chroma_log("ERROR", "User not found: %s", username);
278
      free(username);
279
      free(env_expanded);
280
      return strdup(path);
281
    }
282
283
    free(username);
284
285
    size_t home_len = strlen(pw->pw_dir);
286
    size_t remaining_len = slash ? strlen(slash) : 0;
287
    char *expanded = malloc(home_len + remaining_len + 1);
288
    if (!expanded) {
289
      free(env_expanded);
290
      return strdup(path);
291
    }
292
293
    strcpy(expanded, pw->pw_dir);
294
    if (slash) {
295
      strcat(expanded, slash);
296
    }
297
298
    free(env_expanded);
299
    return expanded;
300
  }
301
}
302
303
// Create directory recursively
304
int chroma_mkdir_recursive(const char *path, mode_t mode) {
305
  if (!path) {
306
    return -1;
307
  }
308
309
  char *path_copy = strdup(path);
310
  if (!path_copy) {
311
    return -1;
312
  }
313
314
  char *p = path_copy;
315
316
  // Skip leading slashes
317
  while (*p == '/') {
318
    p++;
319
  }
320
321
  while (*p) {
322
    // Find next slash
323
    while (*p && *p != '/') {
324
      p++;
325
    }
326
327
    if (*p) {
328
      *p = '\0';
329
330
      // Create directory
331
      if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
332
        chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
333
                   strerror(errno));
334
        free(path_copy);
335
        return -1;
336
      }
337
338
      *p = '/';
339
      p++;
340
    }
341
  }
342
343
  // Create final directory
344
  if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
345
    chroma_log("ERROR", "Failed to create directory %s: %s", path_copy,
346
               strerror(errno));
347
    free(path_copy);
348
    return -1;
349
  }
350
351
  free(path_copy);
352
  return 0;
353
}
354
355
// Get configuration directory
356
char *chroma_get_config_dir(void) {
357
  const char *xdg_config = getenv("XDG_CONFIG_HOME");
358
359
  if (xdg_config) {
360
    char *config_dir = malloc(strlen(xdg_config) + strlen("/chroma") + 1);
361
    if (config_dir) {
362
      sprintf(config_dir, "%s/chroma", xdg_config);
363
      return config_dir;
364
    }
365
  }
366
367
  const char *home = getenv("HOME");
368
  if (home) {
369
    char *config_dir = malloc(strlen(home) + strlen("/.config/chroma") + 1);
370
    if (config_dir) {
371
      sprintf(config_dir, "%s/.config/chroma", home);
372
      return config_dir;
373
    }
374
  }
375
376
  return strdup("/etc/chroma"); // Fallback
377
}
378
379
// Check if path exists
380
bool chroma_path_exists(const char *path) {
381
  if (!path) {
382
    return false;
383
  }
384
385
  struct stat st;
386
  return (stat(path, &st) == 0);
387
}
388
389
// Check if path is a regular file
390
bool chroma_is_regular_file(const char *path) {
391
  if (!path) {
392
    return false;
393
  }
394
395
  struct stat st;
396
  if (stat(path, &st) != 0) {
397
    return false;
398
  }
399
400
  return S_ISREG(st.st_mode);
401
}
402
403
// Check if path is a directory
404
bool chroma_is_directory(const char *path) {
405
  if (!path) {
406
    return false;
407
  }
408
409
  struct stat st;
410
  if (stat(path, &st) != 0) {
411
    return false;
412
  }
413
414
  return S_ISDIR(st.st_mode);
415
}
416
417
// Get file size
418
long chroma_get_file_size(const char *path) {
419
  if (!path) {
420
    return -1;
421
  }
422
423
  struct stat st;
424
  if (stat(path, &st) != 0) {
425
    return -1;
426
  }
427
428
  return st.st_size;
429
}
430
431
// Get file extension
432
const char *chroma_get_file_extension(const char *path) {
433
  if (!path) {
434
    return NULL;
435
  }
436
437
  const char *last_dot = strrchr(path, '.');
438
  if (!last_dot || last_dot == path) {
439
    return NULL;
440
  }
441
442
  return last_dot + 1;
443
}
444
445
// Get current time in milliseconds
446
long long chroma_get_time_ms(void) {
447
  struct timeval tv;
448
  if (gettimeofday(&tv, NULL) != 0) {
449
    return 0;
450
  }
451
452
  return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
453
}
454
455
// Sleep for specified milliseconds
456
void chroma_sleep_ms(long ms) {
457
  if (ms <= 0) {
458
    return;
459
  }
460
461
  struct timespec ts;
462
  ts.tv_sec = ms / 1000;
463
  ts.tv_nsec = (ms % 1000) * 1000000;
464
465
  nanosleep(&ts, NULL);
466
}
467
468
// Format memory size in human readable format
469
void chroma_format_memory_size(size_t bytes, char *buffer, size_t buffer_size) {
470
  if (!buffer || buffer_size == 0) {
471
    return;
472
  }
473
474
  const char *units[] = {"B", "KB", "MB", "GB", "TB"};
475
  const int num_units = sizeof(units) / sizeof(units[0]);
476
477
  double size = (double)bytes;
478
  int unit_index = 0;
479
480
  while (size >= 1024.0 && unit_index < num_units - 1) {
481
    size /= 1024.0;
482
    unit_index++;
483
  }
484
485
  if (unit_index == 0) {
486
    snprintf(buffer, buffer_size, "%.0f %s", size, units[unit_index]);
487
  } else {
488
    snprintf(buffer, buffer_size, "%.2f %s", size, units[unit_index]);
489
  }
490
}
491
492
// Cleanup utility functions
493
void chroma_utils_cleanup(void) { chroma_cleanup_signals(); }