brewery
notashelf /
e72da82b32d46daf27c4ff663e5f918058b65fd7

chroma

public

Lightweight wallpaper daemon for Wayland

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