brewery
notashelf /
ca468ce677b3f6c584b3483120f79f8ed58c9e96

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/config.cC360 lines10.9 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
  chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];
71
72
  strncpy(mapping->output_name, output_name, sizeof(mapping->output_name) - 1);
73
  mapping->output_name[sizeof(mapping->output_name) - 1] = '\0';
74
75
  strncpy(mapping->image_path, image_path, sizeof(mapping->image_path) - 1);
76
  mapping->image_path[sizeof(mapping->image_path) - 1] = '\0';
77
78
  config->mapping_count++;
79
80
  chroma_log("DEBUG", "Added mapping: %s -> %s", output_name, image_path);
81
  chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",
82
             config->mapping_count, output_name, image_path,
83
             strlen(image_path));
84
  return CHROMA_OK;
85
}
86
87
// Initialize configuration with defaults
88
static void init_default_config(chroma_config_t *config) {
89
  if (!config)
90
    return;
91
92
  memset(config, 0, sizeof(chroma_config_t));
93
94
  config->daemon_mode = false;
95
  config->mapping_count = 0;
96
97
  // Set default image path (can be overridden)
98
  const char *home = getenv("HOME");
99
  if (home) {
100
    snprintf(config->default_image, sizeof(config->default_image),
101
             "%s/.config/chroma/default.jpg", home);
102
  } else {
103
    strcpy(config->default_image, "/usr/share/pixmaps/chroma-default.jpg");
104
  }
105
}
106
107
// Parse a single configuration line
108
static int parse_config_line(chroma_config_t *config, char *line,
109
                             int line_number) {
110
  if (!config || !line) {
111
    return CHROMA_ERROR_INIT;
112
  }
113
114
  // Skip empty lines and comments
115
  char *trimmed = trim_whitespace(line);
116
  if (*trimmed == '\0' || *trimmed == '#' || *trimmed == ';') {
117
    return CHROMA_OK;
118
  }
119
120
  // Find the equals sign
121
  char *equals = strchr(trimmed, '=');
122
  if (!equals) {
123
    chroma_log("WARN", "Invalid config line %d: no '=' found", line_number);
124
    return CHROMA_OK; // continue parsing
125
  }
126
127
  *equals = '\0';
128
  char *key = trim_whitespace(trimmed);
129
  char *value = trim_whitespace(equals + 1);
130
131
  value = remove_quotes(value);
132
133
  if (*key == '\0' || *value == '\0') {
134
    chroma_log("WARN", "Invalid config line %d: empty key or value",
135
               line_number);
136
    return CHROMA_OK;
137
  }
138
139
  // Parse configuration options
140
  if (strcasecmp(key, "default_image") == 0) {
141
    strncpy(config->default_image, value, sizeof(config->default_image) - 1);
142
    config->default_image[sizeof(config->default_image) - 1] = '\0';
143
    chroma_log("DEBUG", "Set default image: %s", value);
144
    chroma_log("TRACE", "Default image path set: length=%zu, expanded='%s'",
145
               strlen(value), value);
146
  } else if (strcasecmp(key, "daemon") == 0 ||
147
             strcasecmp(key, "daemon_mode") == 0) {
148
    config->daemon_mode = parse_bool(value);
149
    chroma_log("DEBUG", "Set daemon mode: %s",
150
               config->daemon_mode ? "true" : "false");
151
    chroma_log("TRACE",
152
               "Daemon mode configuration: key='%s', value='%s', parsed=%s",
153
               key, value, config->daemon_mode ? "true" : "false");
154
  } else if (strncasecmp(key, "output.", 7) == 0) {
155
    // Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
156
    const char *output_name = key + 7;
157
    if (*output_name == '\0') {
158
      chroma_log("WARN", "Invalid output mapping line %d: no output name",
159
                 line_number);
160
      return CHROMA_OK;
161
    }
162
163
    // Validate image path
164
    if (chroma_image_validate(value) != CHROMA_OK) {
165
      chroma_log("WARN", "Invalid image path for output %s: %s", output_name,
166
                 value);
167
      return CHROMA_OK;
168
    }
169
170
    if (add_output_mapping(config, output_name, value) != CHROMA_OK) {
171
      chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name,
172
                 value);
173
      return CHROMA_ERROR_CONFIG;
174
    }
175
  } else {
176
    chroma_log("WARN", "Unknown configuration key line %d: %s", line_number,
177
               key);
178
    chroma_log("TRACE", "Unrecognized config line %d: key='%s', value='%s'",
179
               line_number, key, value);
180
  }
181
182
  return CHROMA_OK;
183
}
184
185
// Load configuration from file
186
int chroma_config_load(chroma_config_t *config, const char *config_file) {
187
  if (!config) {
188
    return CHROMA_ERROR_INIT;
189
  }
190
191
  // Initialize with defaults
192
  init_default_config(config);
193
194
  if (!config_file) {
195
    chroma_log("INFO", "No config file specified, using defaults");
196
    return CHROMA_OK;
197
  }
198
199
  FILE *file = fopen(config_file, "r");
200
  if (!file) {
201
    if (errno == ENOENT) {
202
      chroma_log("INFO", "Config file not found: %s (using defaults)",
203
                 config_file);
204
      return CHROMA_OK;
205
    } else {
206
      chroma_log("ERROR", "Failed to open config file %s: %s", config_file,
207
                 strerror(errno));
208
      return CHROMA_ERROR_CONFIG;
209
    }
210
  }
211
212
  chroma_log("INFO", "Loading configuration from: %s", config_file);
213
  chroma_log("TRACE",
214
             "Starting configuration parsing, estimated config size: %ld bytes",
215
             chroma_get_file_size(config_file));
216
217
  char line[1024];
218
  int line_number = 0;
219
  int parse_errors = 0;
220
221
  while (fgets(line, sizeof(line), file)) {
222
    line_number++;
223
224
    char *newline = strchr(line, '\n');
225
    if (newline) {
226
      *newline = '\0';
227
    }
228
229
    if (parse_config_line(config, line, line_number) != CHROMA_OK) {
230
      parse_errors++;
231
    }
232
  }
233
234
  fclose(file);
235
236
  if (parse_errors > 0) {
237
    chroma_log("WARN", "Config file contained %d errors", parse_errors);
238
    // Continue anyway with partial configuration
239
  }
240
241
  chroma_log("INFO",
242
             "Loaded configuration: %d output mappings, default image: %s",
243
             config->mapping_count, config->default_image);
244
245
  // Log configuration memory usage
246
  size_t config_size =
247
      sizeof(chroma_config_t) +
248
      (config->mapping_count * sizeof(chroma_config_mapping_t));
249
  chroma_log_resource_allocation("config_data", config_size,
250
                                 "configuration structure");
251
  chroma_log_memory_stats("post-config-load");
252
253
  return CHROMA_OK;
254
}
255
256
// Free configuration resources
257
void chroma_config_free(chroma_config_t *config) {
258
  if (!config) {
259
    return;
260
  }
261
262
  // Log configuration deallocation
263
  size_t config_size =
264
      sizeof(chroma_config_t) +
265
      (config->mapping_count * sizeof(chroma_config_mapping_t));
266
  chroma_log_resource_deallocation("config_data", config_size,
267
                                   "configuration structure");
268
269
  chroma_log("TRACE", "Clearing %d output mappings from configuration",
270
             config->mapping_count);
271
  memset(config->mappings, 0, sizeof(config->mappings));
272
  config->mapping_count = 0;
273
274
  memset(config->default_image, 0, sizeof(config->default_image));
275
276
  chroma_log("DEBUG", "Freed configuration");
277
}
278
279
// Get image path for specific output
280
const char *chroma_config_get_image_for_output(chroma_config_t *config,
281
                                               const char *output_name) {
282
  if (!config || !output_name) {
283
    return NULL;
284
  }
285
286
  // Look for specific output mapping
287
  for (int i = 0; i < config->mapping_count; i++) {
288
    if (strcmp(config->mappings[i].output_name, output_name) == 0) {
289
      chroma_log("DEBUG", "Found specific mapping for output %s: %s",
290
                 output_name, config->mappings[i].image_path);
291
      return config->mappings[i].image_path;
292
    }
293
  }
294
295
  // Return default image if no specific mapping found
296
  if (strlen(config->default_image) > 0) {
297
    chroma_log("DEBUG", "Using default image for output %s: %s", output_name,
298
               config->default_image);
299
    return config->default_image;
300
  }
301
302
  chroma_log("WARN", "No image configured for output: %s", output_name);
303
  return NULL;
304
}
305
306
// Create a sample configuration file
307
int chroma_config_create_sample(const char *config_file) {
308
  if (!config_file) {
309
    return CHROMA_ERROR_INIT;
310
  }
311
312
  FILE *file = fopen(config_file, "w");
313
  if (!file) {
314
    chroma_log("ERROR", "Failed to create sample config file %s: %s",
315
               config_file, strerror(errno));
316
    return CHROMA_ERROR_CONFIG;
317
  }
318
319
  fprintf(file, "# Chroma Wallpaper Daemon Configuration\n");
320
  fprintf(file, "# Lines starting with # are comments\n\n");
321
322
  fprintf(file, "# Default wallpaper for outputs without specific mapping\n");
323
  fprintf(file, "default_image = ~/.config/chroma/default.jpg\n\n");
324
325
  fprintf(file, "# Output-specific wallpapers\n");
326
  fprintf(file, "# Format: output.OUTPUT_NAME = /path/to/image.jpg\n");
327
  fprintf(file, "# You can find output names using: wlr-randr\n");
328
  fprintf(file, "\n");
329
  fprintf(file, "# Examples:\n");
330
  fprintf(file, "# output.DP-1 = ~/.config/chroma/monitor1.jpg\n");
331
  fprintf(file, "# output.DP-2 = ~/.config/chroma/monitor2.png\n");
332
  fprintf(file, "# output.HDMI-A-1 = ~/.config/chroma/hdmi.jpg\n");
333
334
  fclose(file);
335
336
  chroma_log("INFO", "Created sample configuration file: %s", config_file);
337
  return CHROMA_OK;
338
}
339
340
// Print current configuration for debugging
341
void chroma_config_print(const chroma_config_t *config) {
342
  if (!config) {
343
    return;
344
  }
345
346
  chroma_log("INFO", "=== Configuration ===");
347
  chroma_log("INFO", "Default image: %s", config->default_image);
348
  chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false");
349
  chroma_log("INFO", "Output mappings: %d", config->mapping_count);
350
351
  for (int i = 0; i < config->mapping_count; i++) {
352
    chroma_log("INFO", "  %s -> %s", config->mappings[i].output_name,
353
               config->mappings[i].image_path);
354
    chroma_log(
355
        "TRACE", "  Mapping %d: output='%s', image='%s', path_exists=%s", i,
356
        config->mappings[i].output_name, config->mappings[i].image_path,
357
        chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no");
358
  }
359
  chroma_log("INFO", "====================");
360
}