brewery
notashelf /
74fed80a26f39bb04966d12c649a55150724965f

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/config.cC405 lines12.2 KB
1
#include <ctype.h>
2
#include <errno.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <string.h>
6
7
#include "../include/chroma.h"
8
9
// Default configuration values
10
11
static char *trim_whitespace(char *str) {
12
  char *end;
13
14
  // Trim leading whitespace
15
  while (isspace((unsigned char)*str))
16
    str++;
17
18
  // All spaces?
19
  if (*str == 0)
20
    return str;
21
22
  // Trim trailing whitespace
23
  end = str + strlen(str) - 1;
24
  while (end > str && isspace((unsigned char)*end))
25
    end--;
26
27
  *(end + 1) = '\0';
28
  return str;
29
}
30
31
// Remove quotes from a string
32
static char *remove_quotes(char *str) {
33
  size_t len = strlen(str);
34
  if (len >= 2 && ((str[0] == '"' && str[len - 1] == '"') ||
35
                   (str[0] == '\'' && str[len - 1] == '\''))) {
36
    str[len - 1] = '\0';
37
    return str + 1;
38
  }
39
  return str;
40
}
41
42
// Parse boolean value from string
43
static bool parse_bool(const char *value) {
44
  if (!value)
45
    return false;
46
47
  if (strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 ||
48
      strcasecmp(value, "1") == 0 || strcasecmp(value, "on") == 0) {
49
    return true;
50
  }
51
52
  return false;
53
}
54
55
// Parse integer value from string
56
57
// Output-to-image mapping
58
static int add_output_mapping(chroma_config_t *config, const char *output_name,
59
                              const char *image_path) {
60
  if (!config || !output_name || !image_path) {
61
    return CHROMA_ERROR_INIT;
62
  }
63
64
  if (config->mapping_count >= MAX_OUTPUTS) {
65
    chroma_log("ERROR", "Maximum number of output mappings reached (%d)",
66
               MAX_OUTPUTS);
67
    return CHROMA_ERROR_MEMORY;
68
  }
69
70
  // Validate string lengths to prevent buffer overflow
71
  size_t output_len = strlen(output_name);
72
  size_t path_len = strlen(image_path);
73
74
  if (output_len >= sizeof(config->mappings[0].output_name)) {
75
    chroma_log("ERROR", "Output name too long: %s (max %zu)", output_name,
76
               sizeof(config->mappings[0].output_name) - 1);
77
    return CHROMA_ERROR_CONFIG;
78
  }
79
80
  if (path_len >= sizeof(config->mappings[0].image_path)) {
81
    chroma_log("ERROR", "Image path too long: %s (max %zu)", image_path,
82
               sizeof(config->mappings[0].image_path) - 1);
83
    return CHROMA_ERROR_CONFIG;
84
  }
85
86
  chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];
87
88
  strcpy(mapping->output_name, output_name);
89
  strcpy(mapping->image_path, image_path);
90
91
  config->mapping_count++;
92
93
  chroma_log("DEBUG", "Added mapping: %s -> %s", output_name, image_path);
94
  chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",
95
             config->mapping_count, output_name, image_path, path_len);
96
  return CHROMA_OK;
97
}
98
99
// Initialize configuration with defaults
100
static void init_default_config(chroma_config_t *config) {
101
  if (!config)
102
    return;
103
104
  memset(config, 0, sizeof(chroma_config_t));
105
106
  config->daemon_mode = false;
107
  config->mapping_count = 0;
108
109
  // Set default image path (can be overridden)
110
  const char *home = getenv("HOME");
111
  if (home) {
112
    snprintf(config->default_image, sizeof(config->default_image),
113
             "%s/.config/chroma/default.jpg", home);
114
  } else {
115
    strcpy(config->default_image, "/usr/share/pixmaps/chroma-default.jpg");
116
  }
117
}
118
119
// Parse a single configuration line
120
static int parse_config_line(chroma_config_t *config, char *line,
121
                             int line_number) {
122
  if (!config || !line) {
123
    return CHROMA_ERROR_INIT;
124
  }
125
126
  // Skip empty lines and comments
127
  char *trimmed = trim_whitespace(line);
128
  if (*trimmed == '\0' || *trimmed == '#' || *trimmed == ';') {
129
    return CHROMA_OK;
130
  }
131
132
  // Find the equals sign
133
  char *equals = strchr(trimmed, '=');
134
  if (!equals) {
135
    chroma_log("WARN", "Invalid config line %d: no '=' found", line_number);
136
    return CHROMA_OK; // continue parsing
137
  }
138
139
  *equals = '\0';
140
  char *key = trim_whitespace(trimmed);
141
  char *value = trim_whitespace(equals + 1);
142
143
  value = remove_quotes(value);
144
145
  if (*key == '\0' || *value == '\0') {
146
    chroma_log("WARN", "Invalid config line %d: empty key or value",
147
               line_number);
148
    return CHROMA_OK;
149
  }
150
151
  // Parse configuration options
152
  if (strcasecmp(key, "default_image") == 0) {
153
    char *expanded_path = chroma_expand_path(value);
154
    const char *path_to_use = expanded_path ? expanded_path : value;
155
    size_t path_len = strlen(path_to_use);
156
157
    if (path_len >= sizeof(config->default_image)) {
158
      chroma_log("ERROR", "Default image path too long: %s (max %zu)",
159
                 path_to_use, sizeof(config->default_image) - 1);
160
      if (expanded_path) {
161
        free(expanded_path);
162
      }
163
      return CHROMA_ERROR_CONFIG;
164
    }
165
166
    strcpy(config->default_image, path_to_use);
167
168
    if (expanded_path) {
169
      chroma_log("DEBUG", "Set default image: %s -> %s", value, expanded_path);
170
      chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",
171
                 path_len, expanded_path);
172
      free(expanded_path);
173
    } else {
174
      chroma_log("WARN", "Failed to expand path, using original: %s", value);
175
    }
176
  } else if (strcasecmp(key, "daemon") == 0 ||
177
             strcasecmp(key, "daemon_mode") == 0) {
178
    config->daemon_mode = parse_bool(value);
179
    chroma_log("DEBUG", "Set daemon mode: %s",
180
               config->daemon_mode ? "true" : "false");
181
    chroma_log("TRACE",
182
               "Daemon mode configuration: key='%s', value='%s', parsed=%s",
183
               key, value, config->daemon_mode ? "true" : "false");
184
  } else if (strncasecmp(key, "output.", 7) == 0) {
185
    // Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
186
    const char *output_name = key + 7;
187
    if (*output_name == '\0') {
188
      chroma_log("WARN", "Invalid output mapping line %d: no output name",
189
                 line_number);
190
      return CHROMA_OK;
191
    }
192
193
    // Expand path before validation and storage
194
    char *expanded_path = chroma_expand_path(value);
195
    const char *path_to_validate = expanded_path ? expanded_path : value;
196
197
    // Validate image path
198
    if (chroma_image_validate(path_to_validate) != CHROMA_OK) {
199
      chroma_log("WARN", "Invalid image path for output %s: %s (expanded: %s)",
200
                 output_name, value, path_to_validate);
201
      if (expanded_path) {
202
        free(expanded_path);
203
      }
204
      return CHROMA_OK;
205
    }
206
207
    if (add_output_mapping(config, output_name, path_to_validate) !=
208
        CHROMA_OK) {
209
      chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,
210
                 path_to_validate);
211
      if (expanded_path) {
212
        free(expanded_path);
213
      }
214
      return CHROMA_ERROR_CONFIG;
215
    }
216
217
    if (expanded_path) {
218
      free(expanded_path);
219
    }
220
  } else {
221
    chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,
222
               key);
223
    chroma_log("TRACE", "Unrecognized config line %d: key='%s', value='%s'",
224
               line_number, key, value);
225
  }
226
227
  return CHROMA_OK;
228
}
229
230
// Load configuration from file
231
int chroma_config_load(chroma_config_t *config, const char *config_file) {
232
  if (!config) {
233
    return CHROMA_ERROR_INIT;
234
  }
235
236
  // Initialize with defaults
237
  init_default_config(config);
238
239
  if (!config_file) {
240
    chroma_log("INFO", "No config file specified, using defaults");
241
    return CHROMA_OK;
242
  }
243
244
  FILE *file = fopen(config_file, "r");
245
  if (!file) {
246
    if (errno == ENOENT) {
247
      chroma_log("INFO", "Config file not found: %s (using defaults)",
248
                 config_file);
249
      return CHROMA_OK;
250
    } else {
251
      chroma_log("ERROR", "Failed to open config file %s: %s", config_file,
252
                 strerror(errno));
253
      return CHROMA_ERROR_CONFIG;
254
    }
255
  }
256
257
  chroma_log("INFO", "Loading configuration from: %s", config_file);
258
  chroma_log("TRACE",
259
             "Starting configuration parsing, estimated config size: %ld bytes",
260
             chroma_get_file_size(config_file));
261
262
  char line[1024];
263
  int line_number = 0;
264
  int parse_errors = 0;
265
266
  while (fgets(line, sizeof(line), file)) {
267
    line_number++;
268
269
    char *newline = strchr(line, '\n');
270
    if (newline) {
271
      *newline = '\0';
272
    }
273
274
    if (parse_config_line(config, line, line_number) != CHROMA_OK) {
275
      parse_errors++;
276
    }
277
  }
278
279
  fclose(file);
280
281
  if (parse_errors > 0) {
282
    chroma_log("WARN", "Config file contained %d errors", parse_errors);
283
    // Continue anyway with partial configuration
284
  }
285
286
  chroma_log("INFO",
287
             "Loaded configuration: %d output mappings, default image: %s",
288
             config->mapping_count, config->default_image);
289
290
  // Log configuration memory usage
291
  size_t config_size =
292
      sizeof(chroma_config_t) +
293
      (config->mapping_count * sizeof(chroma_config_mapping_t));
294
  chroma_log_resource_allocation("config_data", config_size,
295
                                 "configuration structure");
296
  chroma_log_memory_stats("post-config-load");
297
298
  return CHROMA_OK;
299
}
300
301
// Free configuration resources
302
void chroma_config_free(chroma_config_t *config) {
303
  if (!config) {
304
    return;
305
  }
306
307
  // Log configuration deallocation
308
  size_t config_size =
309
      sizeof(chroma_config_t) +
310
      (config->mapping_count * sizeof(chroma_config_mapping_t));
311
  chroma_log_resource_deallocation("config_data", config_size,
312
                                   "configuration structure");
313
314
  chroma_log("TRACE", "Clearing %d output mappings from configuration",
315
             config->mapping_count);
316
  memset(config->mappings, 0, sizeof(config->mappings));
317
  config->mapping_count = 0;
318
319
  memset(config->default_image, 0, sizeof(config->default_image));
320
321
  chroma_log("DEBUG", "Freed configuration");
322
}
323
324
// Get image path for specific output
325
const char *chroma_config_get_image_for_output(chroma_config_t *config,
326
                                               const char *output_name) {
327
  if (!config || !output_name) {
328
    return NULL;
329
  }
330
331
  // Look for specific output mapping
332
  for (int i = 0; i < config->mapping_count; i++) {
333
    if (strcmp(config->mappings[i].output_name, output_name) == 0) {
334
      chroma_log("DEBUG", "Found specific mapping for output %s: %s",
335
                 output_name, config->mappings[i].image_path);
336
      return config->mappings[i].image_path;
337
    }
338
  }
339
340
  // Return default image if no specific mapping found
341
  if (strlen(config->default_image) > 0) {
342
    chroma_log("DEBUG", "Using default image for output %s: %s", output_name,
343
               config->default_image);
344
    return config->default_image;
345
  }
346
347
  chroma_log("WARN", "No image configured for output: %s", output_name);
348
  return NULL;
349
}
350
351
// Create a sample configuration file
352
int chroma_config_create_sample(const char *config_file) {
353
  if (!config_file) {
354
    return CHROMA_ERROR_INIT;
355
  }
356
357
  FILE *file = fopen(config_file, "w");
358
  if (!file) {
359
    chroma_log("ERROR", "Failed to create sample config file %s: %s",
360
               config_file, strerror(errno));
361
    return CHROMA_ERROR_CONFIG;
362
  }
363
364
  fprintf(file, "# Chroma Wallpaper Daemon Configuration\n");
365
  fprintf(file, "# Lines starting with # are comments\n\n");
366
367
  fprintf(file, "# Default wallpaper for outputs without specific mapping\n");
368
  fprintf(file, "default_image = ~/.config/chroma/default.jpg\n\n");
369
370
  fprintf(file, "# Output-specific wallpapers\n");
371
  fprintf(file, "# Format: output.OUTPUT_NAME = /path/to/image.jpg\n");
372
  fprintf(file, "# You can find output names using: wlr-randr\n");
373
  fprintf(file, "\n");
374
  fprintf(file, "# Examples:\n");
375
  fprintf(file, "# output.DP-1 = ~/.config/chroma/monitor1.jpg\n");
376
  fprintf(file, "# output.DP-2 = ~/.config/chroma/monitor2.png\n");
377
  fprintf(file, "# output.HDMI-A-1 = ~/.config/chroma/hdmi.jpg\n");
378
379
  fclose(file);
380
381
  chroma_log("INFO", "Created sample configuration file: %s", config_file);
382
  return CHROMA_OK;
383
}
384
385
// Print current configuration for debugging
386
void chroma_config_print(const chroma_config_t *config) {
387
  if (!config) {
388
    return;
389
  }
390
391
  chroma_log("INFO", "=== Configuration ===");
392
  chroma_log("INFO", "Default image: %s", config->default_image);
393
  chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false");
394
  chroma_log("INFO", "Output mappings: %d", config->mapping_count);
395
396
  for (int i = 0; i < config->mapping_count; i++) {
397
    chroma_log("INFO", "  %s -> %s", config->mappings[i].output_name,
398
               config->mappings[i].image_path);
399
    chroma_log(
400
        "TRACE", "  Mapping %d: output='%s', image='%s', path_exists=%s", i,
401
        config->mappings[i].output_name, config->mappings[i].image_path,
402
        chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no");
403
  }
404
  chroma_log("INFO", "====================");
405
}