Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/XFCE/src/   (X11 Desktop Version 4.20©)  Datei vom 14.9.2024 mit Größe 56 kB image not shown  

Quelle  xfdesktop-icon-position-configs.c

  Sprache: C
 

/*
 *  xfdesktop - xfce4's desktop manager
 *
 *  Copyright(c) 2024 Brian Tarricone, <brian@tarricone.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */


#include <glib.h>
#include <gio/gio.h>
#include <gdk/gdk.h>
#include <libxfce4util/libxfce4util.h>
#include <libxfce4windowing/libxfce4windowing.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <yaml.h>

#include "xfdesktop-icon-position-configs.h"

#define SAVE_DELAY_S 1

#ifdef DEBUG
static char spaces[512];
#define LOG_EVENT(level, msg) DBG("%.*s-- %s", level * 4, spaces, msg)
#define LOG_EVENT_IN(level, msg) \
    G_STMT_START { \
        DBG("%.*s--> %s", level * 4, spaces, msg); \
        level++; \
    } \
G_STMT_END
#define LOG_EVENT_OUT(level, msg) \
    G_STMT_START { \
        DBG("%.*s<-- %s", level * 4, spaces, msg); \
        level--; \
    } \
G_STMT_END
#define STATE_TRANSITION(level, lhs, new_state) \
    G_STMT_START { \
        ParserState _new_state = (new_state); \
        DBG("%.*s%s -> %s", level * 4, spaces, state_str(lhs), state_str(_new_state)); \
        lhs = _new_state; \
    } \
G_STMT_END
#else
#define LOG_EVENT(...) G_STMT_START{}G_STMT_END
#define LOG_EVENT_IN(...) G_STMT_START{}G_STMT_END
#define LOG_EVENT_OUT(...) G_STMT_START{}G_STMT_END
#define STATE_TRANSITION(level, lhs, new_state) G_STMT_START{ lhs = new_state; }G_STMT_END
#endif

#define PARSE_ERROR(level, state_lhs, configs, event, fmt, ...) \
    G_STMT_START { \
        g_set_error(error, \
                    G_IO_ERROR, \
                    G_IO_ERROR_INVALID_DATA, \
                    "%s:%" G_GSIZE_FORMAT ":%" G_GSIZE_FORMAT ": " fmt, \
                    g_file_peek_path(configs->file), \
                    event.start_mark.line, \
                    event.start_mark.column __VA_OPT__(, ) \
                    __VA_ARGS__); \
        STATE_TRANSITION(level, state_lhs, PARSER_ERROR); \
    } \
G_STMT_END

#define EMITTER_ERROR(emitter, event)

struct _XfdesktopIconPositionConfigs {
    GFile *file;
    GList *configs;  // XfdesktopIconPositionConfig (owner)
    GHashTable *config_to_monitor;  // XfdesktopIconPositionConfig -> XfwMonitor

    guint scheduled_save_id;
};

struct _XfdesktopIconPositionConfig {
    XfdesktopIconPositionLevel level;
    GHashTable *monitors;  // string id (owner) -> XfdesktopIconPositionMonitor (owner)
    GHashTable *icon_positions;  // string id (owner) -> XfdesktopIconPosition (owner)
};

typedef struct _XfdesktopIconPositionMonitor {
    gchar *display_name;
    GdkRectangle geometry;
} XfdesktopIconPositionMonitor;

typedef struct _XfdesktopIconPosition {
    guint row;
    guint col;
    guint64 last_seen;
} XfdesktopIconPosition;

typedef enum {
    PARSER_TOP,
    PARSER_TOPLEVEL_MAP,
    PARSER_CONFIGS,
    PARSER_CONFIGS_ARRAY,
    PARSER_CONFIG_MAP,
    PARSER_CONFIG_LEVEL,
    PARSER_CONFIG_MONITORS,
    PARSER_CONFIG_MONITORS_ARRAY,
    PARSER_CONFIG_MONITOR_MAP,
    PARSER_CONFIG_MONITOR_ID,
    PARSER_CONFIG_MONITOR_DISPLAY_NAME,
    PARSER_CONFIG_MONITOR_GEOMETRY,
    PARSER_CONFIG_MONITOR_GEOMETRY_MAP,
    PARSER_CONFIG_MONITOR_GEOMETRY_MAP_X,
    PARSER_CONFIG_MONITOR_GEOMETRY_MAP_Y,
    PARSER_CONFIG_MONITOR_GEOMETRY_MAP_WIDTH,
    PARSER_CONFIG_MONITOR_GEOMETRY_MAP_HEIGHT,
    PARSER_CONFIG_ICONS,
    PARSER_CONFIG_ICONS_MAP,
    PARSER_CONFIG_ICON,
    PARSER_CONFIG_ICON_MAP,
    PARSER_CONFIG_ICON_MAP_ROW,
    PARSER_CONFIG_ICON_MAP_COL,
    PARSER_CONFIG_ICON_MAP_LAST_SEEN,
    PARSER_DONE,
    PARSER_ERROR,
} ParserState;

#ifdef DEBUG
static const gchar *
state_str(ParserState state) {
#define S(val) case val: \
    return #val;

    switch (state) {
        S(PARSER_TOP)
        S(PARSER_TOPLEVEL_MAP)
        S(PARSER_CONFIGS)
        S(PARSER_CONFIGS_ARRAY)
        S(PARSER_CONFIG_MAP)
        S(PARSER_CONFIG_LEVEL)
        S(PARSER_CONFIG_MONITORS)
        S(PARSER_CONFIG_MONITORS_ARRAY)
        S(PARSER_CONFIG_MONITOR_MAP)
        S(PARSER_CONFIG_MONITOR_ID)
        S(PARSER_CONFIG_MONITOR_DISPLAY_NAME)
        S(PARSER_CONFIG_MONITOR_GEOMETRY)
        S(PARSER_CONFIG_MONITOR_GEOMETRY_MAP)
        S(PARSER_CONFIG_MONITOR_GEOMETRY_MAP_X)
        S(PARSER_CONFIG_MONITOR_GEOMETRY_MAP_Y)
        S(PARSER_CONFIG_MONITOR_GEOMETRY_MAP_WIDTH)
        S(PARSER_CONFIG_MONITOR_GEOMETRY_MAP_HEIGHT)
        S(PARSER_CONFIG_ICONS)
        S(PARSER_CONFIG_ICONS_MAP)
        S(PARSER_CONFIG_ICON)
        S(PARSER_CONFIG_ICON_MAP)
        S(PARSER_CONFIG_ICON_MAP_ROW)
        S(PARSER_CONFIG_ICON_MAP_COL)
        S(PARSER_CONFIG_ICON_MAP_LAST_SEEN)
        S(PARSER_DONE)
        S(PARSER_ERROR)

        default:
            g_assert_not_reached();
            return "INVALID";
    }

#undef S
}
#endif

static const gchar *
libyaml_strerror(yaml_error_type_t error) {
    switch (error) {
        case YAML_NO_ERROR:
            return "Success";
        case YAML_MEMORY_ERROR:
            g_error("Out of memory allocating for YAML parser/emitter");
            abort();
            return "Out of memory";
        case YAML_READER_ERROR:
            return "Cannot read or decode YAML input";
        case YAML_SCANNER_ERROR:
            return "Cannot scan YAML input";
        case YAML_PARSER_ERROR:
            return "Cannot parse YAML input stream";
        case YAML_COMPOSER_ERROR:
            return "Cannot compose YAML document";
        case YAML_WRITER_ERROR:
            return "Cannot write YAML ouptut";
        case YAML_EMITTER_ERROR:
            return "Cannot emit YAML stream";
        default:
            return "Unknown YAML error";
    }
}

static gint
compare_configs_by_level(gconstpointer a, gconstpointer b) {
    const XfdesktopIconPositionConfig *ca = a;
    const XfdesktopIconPositionConfig *cb = b;
    // Sort descending; higher level should be sooner in the list
    return cb->level - ca->level;
}

static gint
compare_candidates(gconstpointer a, gconstpointer b, gpointer data) {
    const XfdesktopIconPositionConfig *ca = a;
    const XfdesktopIconPositionConfig *cb = b;
    XfdesktopIconPositionLevel desired_level = GPOINTER_TO_INT(data);
    if ((ca->level == desired_level && cb->level == desired_level) || ca->level == cb->level) {
        return 0;
    } else if (ca->level == desired_level) {
        return -10;
    } else if (cb->level == desired_level) {
        return 10;
    } else {
        return ABS(ca->level - desired_level) - ABS(cb->level - desired_level);
    }
}

static void
xfdesktop_icon_position_monitor_free(XfdesktopIconPositionMonitor *pos_monitor) {
    if (pos_monitor != NULL) {
        g_free(pos_monitor->display_name);
        g_free(pos_monitor);
    }
}

static XfdesktopIconPositionConfig *
xfdesktop_icon_position_config_new_internal(XfdesktopIconPositionLevel level) {
    g_return_val_if_fail(level >= XFDESKTOP_ICON_POSITION_LEVEL_INVALID && level <= XFDESKTOP_ICON_POSITION_LEVEL_OTHER, NULL);

    XfdesktopIconPositionConfig *config = g_new0(XfdesktopIconPositionConfig, 1);
    config->level = level;
    config->monitors = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)xfdesktop_icon_position_monitor_free);
    config->icon_positions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
    return config;
}

static gboolean
save_icons(XfdesktopIconPositionConfigs *configs, GError **error) {
    yaml_emitter_t emitter;
    if (!yaml_emitter_initialize(&emitter)) {
        g_set_error(error,
                    G_IO_ERROR,
                    G_IO_ERROR_FAILED,
                    "Failed to create YAML emitter: %s",
                    libyaml_strerror(emitter.error));
        return FALSE;
    }

    gchar *new_filename = g_strconcat(g_file_peek_path(configs->file), ".new", NULL);
    FILE *output = fopen(new_filename, "wb");
    if (output == NULL) {
        g_set_error(error,
                    G_IO_ERROR,
                    g_io_error_from_errno(errno),
                    "Failed to open '%s' for writing: %s",
                    new_filename,
                    strerror(errno));
        yaml_emitter_delete(&emitter);
        g_free(new_filename);
        return FALSE;
    }

    fputs("#\n# DO NOT EDIT THIS FILE WHILE XFDESKTOP IS RUNNING\n#\n", output);

    yaml_emitter_set_output_file(&emitter, output);

    yaml_event_t event;

#define EMIT(emitter, event) \
    G_STMT_START { \
        if (!yaml_emitter_emit(&emitter, &event)) { \
            g_set_error(error, \
                        G_IO_ERROR, \
                        G_IO_ERROR_FAILED, \
                        "Failed to emit YAML to file: %s", \
                        libyaml_strerror(emitter.error)); \
            goto out_err; \
        } \
    } \
G_STMT_END

#define YC(str) ((yaml_char_t *)(str))
#define EMIT_QSTR_EVENT(emitter, event, str) \
    G_STMT_START { \
        yaml_scalar_event_initialize(&event, NULL, YC("tag:yaml.org,2002:str"), YC(str), strlen(str), TRUETRUE, YAML_DOUBLE_QUOTED_SCALAR_STYLE); \
        EMIT(emitter, event); \
    } \
G_STMT_END
#define EMIT_UQSTR_EVENT(emitter, event, str) \
    G_STMT_START { \
        yaml_scalar_event_initialize(&event, NULL, YC("tag:yaml.org,2002:str"), YC(str), strlen(str), TRUETRUE, YAML_PLAIN_SCALAR_STYLE); \
        EMIT(emitter, event); \
    } \
G_STMT_END
#define EMIT_INT_EVENT(emitter, event, val) \
    G_STMT_START { \
        gchar *val_str = g_strdup_printf("%d", val); \
        yaml_scalar_event_initialize(&event, NULL, YC("tag:yaml.org,2002:int"), YC(val_str), strlen(val_str), TRUETRUE, YAML_PLAIN_SCALAR_STYLE); \
        EMIT(emitter, event); \
        g_free(val_str); \
    } \
G_STMT_END
#define EMIT_UINT_EVENT(emitter, event, val) \
    G_STMT_START { \
        gchar *val_str = g_strdup_printf("%u", val); \
        yaml_scalar_event_initialize(&event, NULL, YC("tag:yaml.org,2002:int"), YC(val_str), strlen(val_str), TRUETRUE, YAML_PLAIN_SCALAR_STYLE); \
        EMIT(emitter, event); \
        g_free(val_str); \
    } \
G_STMT_END
#define EMIT_UINT64_EVENT(emitter, event, val) \
    G_STMT_START { \
        gchar *val_str = g_strdup_printf("%" G_GUINT64_FORMAT, val); \
        yaml_scalar_event_initialize(&event, NULL, YC("tag:yaml.org,2002:int"), YC(val_str), strlen(val_str), TRUETRUE, YAML_PLAIN_SCALAR_STYLE); \
        EMIT(emitter, event); \
        g_free(val_str); \
    } \
G_STMT_END
#define EMIT_MAP_START_EVENT(emitter, event) \
    G_STMT_START { \
        yaml_mapping_start_event_initialize(&event, NULL, YC("tag:yaml.org,2002:map"), TRUE, YAML_BLOCK_MAPPING_STYLE); \
        EMIT(emitter, event); \
    } \
G_STMT_END
#define EMIT_SEQ_START_EVENT(emitter, event) \
    G_STMT_START { \
        yaml_sequence_start_event_initialize(&event, NULL, YC("tag:yaml.org,2002:seq"), TRUEYAML_BLOCK_SEQUENCE_STYLE); \
        EMIT(emitter, event); \
    } \
G_STMT_END

    yaml_stream_start_event_initialize(&event, YAML_UTF8_ENCODING);
    EMIT(emitter, event);
    {
        yaml_document_start_event_initialize(&event, NULL, NULL, NULL, TRUE);
        EMIT(emitter, event);
        {
            EMIT_MAP_START_EVENT(emitter, event);
            {
                EMIT_UQSTR_EVENT(emitter, event, "configs");
                EMIT_SEQ_START_EVENT(emitter, event);
                {
                    for (GList *l = configs->configs; l != NULL; l = l->next) {
                        XfdesktopIconPositionConfig *config = l->data;

                        EMIT_MAP_START_EVENT(emitter, event);
                        {
                            EMIT_UQSTR_EVENT(emitter, event, "level");
                            EMIT_INT_EVENT(emitter, event, config->level);

                            EMIT_UQSTR_EVENT(emitter, event, "monitors");
                            EMIT_SEQ_START_EVENT(emitter, event);
                            {
                                GHashTableIter iter;
                                g_hash_table_iter_init(&iter, config->monitors);

                                const gchar *id;
                                XfdesktopIconPositionMonitor *pos_monitor;
                                while (g_hash_table_iter_next(&iter, (gpointer)&id, (gpointer)&pos_monitor)) {
                                    EMIT_MAP_START_EVENT(emitter, event);
                                    {
                                        EMIT_UQSTR_EVENT(emitter, event, "id");
                                        EMIT_QSTR_EVENT(emitter, event, id);

                                        EMIT_UQSTR_EVENT(emitter, event, "display_name");
                                        EMIT_QSTR_EVENT(emitter, event, pos_monitor->display_name);

                                        EMIT_UQSTR_EVENT(emitter, event, "geometry");
                                        EMIT_MAP_START_EVENT(emitter, event);
                                        {
                                            EMIT_UQSTR_EVENT(emitter, event, "x");
                                            EMIT_INT_EVENT(emitter, event, pos_monitor->geometry.x);
                                            EMIT_UQSTR_EVENT(emitter, event, "y");
                                            EMIT_INT_EVENT(emitter, event, pos_monitor->geometry.y);
                                            EMIT_UQSTR_EVENT(emitter, event, "width");
                                            EMIT_INT_EVENT(emitter, event, pos_monitor->geometry.width);
                                            EMIT_UQSTR_EVENT(emitter, event, "height");
                                            EMIT_INT_EVENT(emitter, event, pos_monitor->geometry.height);

                                            yaml_mapping_end_event_initialize(&event);
                                            EMIT(emitter, event);
                                        }

                                        yaml_mapping_end_event_initialize(&event);
                                        EMIT(emitter, event);
                                    }
                                }

                                yaml_sequence_end_event_initialize(&event);
                                EMIT(emitter, event);
                            }

                            EMIT_UQSTR_EVENT(emitter, event, "icons");
                            EMIT_MAP_START_EVENT(emitter, event);
                            {
                                GHashTableIter iter;
                                g_hash_table_iter_init(&iter, config->icon_positions);

                                const gchar *id;
                                XfdesktopIconPosition *position;
                                while (g_hash_table_iter_next(&iter, (gpointer)&id, (gpointer)&position)) {
                                    EMIT_QSTR_EVENT(emitter, event, id);
                                    EMIT_MAP_START_EVENT(emitter, event);
                                    {
                                        EMIT_UQSTR_EVENT(emitter, event, "row");
                                        EMIT_UINT_EVENT(emitter, event, position->row);
                                        EMIT_UQSTR_EVENT(emitter, event, "col");
                                        EMIT_UINT_EVENT(emitter, event, position->col);
                                        if (position->last_seen != 0) {
                                            EMIT_UQSTR_EVENT(emitter, event, "last_seen");
                                            EMIT_UINT64_EVENT(emitter, event, position->last_seen);
                                        }

                                        yaml_mapping_end_event_initialize(&event);
                                        EMIT(emitter, event);
                                    }
                                }

                                yaml_mapping_end_event_initialize(&event);
                                EMIT(emitter, event);
                            }

                            yaml_mapping_end_event_initialize(&event);
                            EMIT(emitter, event);
                        }
                    }

                    yaml_sequence_end_event_initialize(&event);
                    EMIT(emitter, event);
                }

                yaml_mapping_end_event_initialize(&event);
                EMIT(emitter, event);
            }

            yaml_document_end_event_initialize(&event, TRUE);
            EMIT(emitter, event);
        }

        yaml_stream_end_event_initialize(&event);
        EMIT(emitter, event);
    }

#undef EMIT_SEQ_START_EVENT
#undef EMIT_MAP_START_EVENT
#undef EMIT_QSTR_EVENT
#undef EMIT_UQSTR_EVENT
#undef EMIT_INT_EVENT
#undef EMIT_UINT_EVENT
#undef EMIT_UINT64_EVENT
#undef EMIT
#undef YC

    yaml_emitter_delete(&emitter);
    int ret = fclose(output);
    output = NULL;

    if (ret) {
        g_set_error(error,
                    G_IO_ERROR,
                    g_io_error_from_errno(errno),
                    "Failed to close written file '%s': %s",
                    new_filename,
                    strerror(errno));
        goto out_err;
    }

    if (rename(new_filename, g_file_peek_path(configs->file))) {
        g_set_error(error,
                    G_IO_ERROR,
                    g_io_error_from_errno(errno),
                    "Failed to rename '%s' to '%s': %s",
                    new_filename,
                    g_file_peek_path(configs->file),
                    strerror(errno));
        goto out_err;
    }

    g_free(new_filename);
    return TRUE;

out_err:
    if (output != NULL) {
        yaml_emitter_delete(&emitter);
        fclose(output);
    }
    unlink(new_filename);
    g_free(new_filename);

    return FALSE;
}

static gboolean
save_icons_idled(gpointer data) {
    XfdesktopIconPositionConfigs *configs = data;
    configs->scheduled_save_id = 0;

    GError *error = NULL;
    if (!save_icons(configs, &error)) {
        g_message("Failed to save desktop icon positions: %s", error->message);
        g_error_free(error);
    }

    return G_SOURCE_REMOVE;
}

static void
schedule_save(XfdesktopIconPositionConfigs *configs) {
    if (configs->scheduled_save_id == 0) {
        configs->scheduled_save_id = g_timeout_add_seconds(SAVE_DELAY_S, save_icons_idled, configs);
    }
}

static gboolean
scalar_equal(yaml_event_t *event, const gchar *value) {
    g_assert(event->type == YAML_SCALAR_EVENT);
    return event->data.scalar.value != NULL
        && strncmp((const char *)event->data.scalar.value, value, event->data.scalar.length) == 0;
}

static gchar *
scalar_to_str(yaml_event_t *event) {
    g_assert(event->type == YAML_SCALAR_EVENT);
    return event->data.scalar.value != NULL
        ? g_strndup((char *)event->data.scalar.value, event->data.scalar.length)
        : NULL;
}

static gchar *  // NULL means success
scalar_to_int(yaml_event_t *event, const gchar *key, gint *value) {
    g_assert(event->type == YAML_SCALAR_EVENT);

    switch (event->data.scalar.style) {
        case YAML_PLAIN_SCALAR_STYLE:
        case YAML_LITERAL_SCALAR_STYLE: {
            errno = 0;
            char *endptr = NULL;
            gchar *str = scalar_to_str(event);
            *value = strtol(str, &endptr, 10);

            if (errno != 0 || endptr == NULL || *endptr != '\0') {
                gchar *err = g_strdup_printf("Invalid value for %s: '%s'", key, str);
                g_free(str);
                return err;
            } else {
                g_free(str);
                return NULL;
            }
            break;
        }

        default:
            return g_strdup_printf("Bad scalar style %d for %s", event->data.scalar.style, key);
            break;
    }
}

static gchar *  // NULL means success
scalar_to_uint(yaml_event_t *event, const gchar *key, guint *value) {
    g_assert(event->type == YAML_SCALAR_EVENT);

    switch (event->data.scalar.style) {
        case YAML_PLAIN_SCALAR_STYLE:
        case YAML_LITERAL_SCALAR_STYLE: {
            errno = 0;
            char *endptr = NULL;
            gchar *str = scalar_to_str(event);
            *value = strtoul(str, &endptr, 10);

            if (errno != 0 || endptr == NULL || *endptr != '\0') {
                gchar *err = g_strdup_printf("Invalid value for %s: '%s'", key, str);
                g_free(str);
                return err;
            } else {
                g_free(str);
                return NULL;
            }
            break;
        }

        default:
            return g_strdup_printf("Bad scalar style %d for %s", event->data.scalar.style, key);
            break;
    }
}

static gchar *  // NULL means success
scalar_to_uint64(yaml_event_t *event, const gchar *key, guint64 *value) {
    g_assert(event->type == YAML_SCALAR_EVENT);

    switch (event->data.scalar.style) {
        case YAML_PLAIN_SCALAR_STYLE:
        case YAML_LITERAL_SCALAR_STYLE: {
            errno = 0;
            char *endptr = NULL;
            gchar *str = scalar_to_str(event);
            *value = strtoull(str, &endptr, 10);

            if (errno != 0 || endptr == NULL || *endptr != '\0') {
                gchar *err = g_strdup_printf("Invalid value for %s: '%s'", key, str);
                g_free(str);
                return err;
            } else {
                g_free(str);
                return NULL;
            }
            break;
        }

        default:
            return g_strdup_printf("Bad scalar style %d for %s", event->data.scalar.style, key);
            break;
    }
}

static const gchar *
validate_position_config(XfdesktopIconPositionConfig *config) {
    if (config->level == XFDESKTOP_ICON_POSITION_LEVEL_INVALID) {
        return "Missing config->level";
    } else {
        return NULL;
    }
}

static const gchar *
validate_monitor_config(XfdesktopIconPositionMonitor *pos_monitor) {
    if (pos_monitor->display_name == NULL || pos_monitor->display_name[0] == '\0') {
        return "Missing or empty config->monitors[]->display_name";
    } else if (pos_monitor->geometry.x == G_MININT) {
        return "Missing config->monitors[]->geometry->x";
    } else if (pos_monitor->geometry.y == G_MININT) {
        return "Missing config->monitors[]->geometry->y";
    } else if (pos_monitor->geometry.width == G_MININT) {
        return "Missing config->monitors[]->geometry->width";
    } else if (pos_monitor->geometry.height == G_MININT) {
        return "Missing config->monitors[]->geometry->height";
    } else {
        return NULL;
    }
}

static const gchar *
validate_position(XfdesktopIconPosition *position) {
    if (position->row == G_MAXINT) {
        return "Missing row in icon position";
    } else if (position->col == G_MAXINT) {
        return "Missing col in icon position";
    } else {
        return NULL;
    }
}

XfdesktopIconPositionConfigs *
xfdesktop_icon_position_configs_new(GFile *file) {
    g_return_val_if_fail(G_IS_FILE(file), NULL);

    XfdesktopIconPositionConfigs *configs = g_new0(XfdesktopIconPositionConfigs, 1);
    configs->file = g_object_ref(file);
    configs->config_to_monitor = g_hash_table_new(g_direct_hash, g_direct_equal);

    return configs;
}

gboolean
xfdesktop_icon_position_configs_load(XfdesktopIconPositionConfigs *configs, GError **error) {
    g_return_val_if_fail(configs != NULL, FALSE);
    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);

    yaml_parser_t parser;
    if (!yaml_parser_initialize(&parser)) {
        g_set_error(error,
                    G_IO_ERROR,
                    G_IO_ERROR_NOT_INITIALIZED,
                    "Failed to initialize YAML parser: %s",
                    libyaml_strerror(parser.error));
        return FALSE;
    }

    const gchar *filename = g_file_peek_path(configs->file);
    FILE *input = fopen(filename, "rb");
    if (input == NULL) {
        g_set_error(error,
                    G_IO_ERROR,
                    g_io_error_from_errno(errno),
                    "Failed to open '%s': %s",
                    filename,
                    strerror(errno));
        yaml_parser_delete(&parser);
        return FALSE;
    }

    yaml_parser_set_input_file(&parser, input);

    g_hash_table_remove_all(configs->config_to_monitor);
    g_list_free_full(configs->configs, (GDestroyNotify)xfdesktop_icon_position_config_free);
    configs->configs = NULL;

#ifdef DEBUG
    guint level = 0;
    memset(spaces, ' 'sizeof(spaces) - 1);
    spaces[sizeof(spaces) - 1] = '\0';
#endif

    ParserState state = PARSER_TOP;
    XfdesktopIconPositionConfig *cur_config = NULL;
    XfdesktopIconPosition *cur_position = NULL;
    gchar *cur_monitor_id = NULL;
    XfdesktopIconPositionMonitor *cur_pos_monitor = NULL;

    while (state != PARSER_DONE && state != PARSER_ERROR) {
        yaml_event_t event;
        if (!yaml_parser_parse(&parser, &event)) {
            g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, libyaml_strerror(parser.error));
            break;
        }

        switch (event.type) {
            case YAML_NO_EVENT:
                break;

            case YAML_STREAM_START_EVENT:
                LOG_EVENT(level, "STREAM_START");
                break;

            case YAML_DOCUMENT_START_EVENT:
                LOG_EVENT_IN(level, "DOC_START");
                break;

            case YAML_DOCUMENT_END_EVENT:
                LOG_EVENT_OUT(level, "DOC_END");
                break;

            case YAML_ALIAS_EVENT:
                LOG_EVENT(level, "ALIAS");
                PARSE_ERROR(level, state, configs, event, "YAML aliases not supported");
                break;

            case YAML_SCALAR_EVENT:
                LOG_EVENT(level, "SCALAR");
                switch (state) {
                    case PARSER_TOPLEVEL_MAP:
                        if (scalar_equal(&event, "configs")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIGS);
                        } else {
                            gchar *key = scalar_to_str(&event);
                            PARSE_ERROR(level, state, configs, event, "Unexpected toplevel map key %s", key);
                            g_free(key);
                        }
                        break;

                    case PARSER_CONFIG_MAP:
                        if (scalar_equal(&event, "level")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_LEVEL);
                        } else if (scalar_equal(&event, "monitors")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITORS);
                        } else if (scalar_equal(&event, "icons")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICONS);
                        } else {
                            gchar *scalar = scalar_to_str(&event);
                            PARSE_ERROR(level, state, configs, event, "Unexpected scalar in config '%s'", scalar);
                            g_free(scalar);
                        }
                        break;

                    case PARSER_CONFIG_LEVEL: {
                        g_assert(cur_config != NULL);
                        gchar *err_msg = scalar_to_int(&event, "level", &cur_config->level);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_MONITOR_MAP:
                        if (scalar_equal(&event, "id")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_ID);
                        } else if (scalar_equal(&event, "display_name")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_DISPLAY_NAME);
                        } else if (scalar_equal(&event, "geometry")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY);
                        } else {
                            gchar *scalar = scalar_to_str(&event);
                            PARSE_ERROR(level, state, configs, event, "Unexpected scalar in monitors '%s'", scalar);
                            g_free(scalar);
                        }
                        break;

                    case PARSER_CONFIG_MONITOR_ID:
                        g_free(cur_monitor_id);
                        cur_monitor_id = scalar_to_str(&event);
                        if (cur_monitor_id == NULL) {
                            PARSE_ERROR(level, state, configs, event, "Empty monitor ID");
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_MAP);
                        }
                        break;

                    case PARSER_CONFIG_MONITOR_DISPLAY_NAME:
                        g_assert(cur_pos_monitor != NULL);
                        g_free(cur_pos_monitor->display_name);
                        cur_pos_monitor->display_name = scalar_to_str(&event);
                        if (cur_pos_monitor->display_name == NULL) {
                            PARSE_ERROR(level, state, configs, event, "Empty monitor display name");
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_MAP);
                        }
                        break;

                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP:
                        if (scalar_equal(&event, "x")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP_X);
                        } else if (scalar_equal(&event, "y")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP_Y);
                        } else if (scalar_equal(&event, "width")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP_WIDTH);
                        } else if (scalar_equal(&event, "height")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP_HEIGHT);
                        } else {
                            gchar *scalar = scalar_to_str(&event);
                            PARSE_ERROR(level, state, configs, event, "Unexpected scalar in monitors[]->geometry '%s'", scalar);
                            g_free(scalar);
                        }
                        break;

                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP_X: {
                        g_assert(cur_pos_monitor != NULL);
                        gchar *err_msg = scalar_to_int(&event, "geometry->x", &cur_pos_monitor->geometry.x);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP_Y: {
                        g_assert(cur_pos_monitor != NULL);
                        gchar *err_msg = scalar_to_int(&event, "geometry->y", &cur_pos_monitor->geometry.y);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP_WIDTH: {
                        g_assert(cur_pos_monitor != NULL);
                        gchar *err_msg = scalar_to_int(&event, "geometry->width", &cur_pos_monitor->geometry.width);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP_HEIGHT: {
                        g_assert(cur_pos_monitor != NULL);
                        gchar *err_msg = scalar_to_int(&event, "geometry->height", &cur_pos_monitor->geometry.height);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_ICONS_MAP:
                        if (cur_position != NULL) {
                            PARSE_ERROR(level, state, configs, event, "Got new icon position when old position not closed");
                        } else {
                            g_assert(cur_config != NULL);
                            gchar *id = scalar_to_str(&event);
                            if (id == NULL) {
                                PARSE_ERROR(level, state, configs, event, "Empty icon ID");
                            } else {
                                cur_position = g_new0(XfdesktopIconPosition, 1);
                                cur_position->row = G_MAXINT;
                                cur_position->col = G_MAXINT;
                                cur_position->last_seen = 0;
                                g_hash_table_insert(cur_config->icon_positions, id, cur_position);
                                STATE_TRANSITION(level, state, PARSER_CONFIG_ICON);
                            }
                        }
                        break;

                    case PARSER_CONFIG_ICON_MAP: {
                        if (scalar_equal(&event, "row")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP_ROW);
                        } else if (scalar_equal(&event, "col")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP_COL);
                        } else if (scalar_equal(&event, "last_seen")) {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP_LAST_SEEN);
                        } else {
                            gchar *scalar = scalar_to_str(&event);
                            PARSE_ERROR(level, state, configs, event, "Unexpected scalar in icon '%s'", scalar);
                            g_free(scalar);
                        }
                        break;
                    }

                    case PARSER_CONFIG_ICON_MAP_ROW: {
                        g_assert(cur_config != NULL);
                        gchar *err_msg = scalar_to_uint(&event, "icon->row", &cur_position->row);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_ICON_MAP_COL: {
                        g_assert(cur_config != NULL);
                        gchar *err_msg = scalar_to_uint(&event, "icon->col", &cur_position->col);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP);
                        }
                        break;
                    }

                    case PARSER_CONFIG_ICON_MAP_LAST_SEEN: {
                        g_assert(cur_config != NULL);
                        gchar *err_msg = scalar_to_uint64(&event, "icon->last_seen", &cur_position->last_seen);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            g_free(err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP);
                        }
                        break;
                    }

                    default: {
                        gchar *scalar = scalar_to_str(&event);
                        PARSE_ERROR(level, state, configs, event, "Unexpected scalar '%s'", scalar);
                        g_free(scalar);
                        break;
                    }
                }
                break;

            case YAML_SEQUENCE_START_EVENT:
                LOG_EVENT_IN(level, "SEQUENCE_START");
                switch (state) {
                    case PARSER_CONFIGS:
                        STATE_TRANSITION(level, state, PARSER_CONFIGS_ARRAY);
                        break;

                    case PARSER_CONFIG_MONITORS:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MONITORS_ARRAY);
                        break;

                    default:
                        PARSE_ERROR(level, state, configs, event, "Unexpected sequence");
                        break;
                }
                break;

            case YAML_SEQUENCE_END_EVENT:
                LOG_EVENT_OUT(level, "SEQUENCE_END");
                switch (state) {
                    case PARSER_CONFIG_MONITORS_ARRAY:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MAP);
                        break;

                    case PARSER_CONFIGS_ARRAY:
                        STATE_TRANSITION(level, state, PARSER_TOPLEVEL_MAP);
                        break;

                    default:
                        PARSE_ERROR(level, state, configs, event, "Unexpected sequence end");
                        break;
                }
                break;

            case YAML_MAPPING_START_EVENT:
                LOG_EVENT_IN(level, "MAPPING_START");
                switch (state) {
                    case PARSER_TOP:
                        STATE_TRANSITION(level, state, PARSER_TOPLEVEL_MAP);
                        break;

                    case PARSER_CONFIGS_ARRAY:
                        if (cur_config != NULL) {
                            PARSE_ERROR(level, state, configs, event, "Got new config when old config not closed");
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MAP);
                            cur_config = xfdesktop_icon_position_config_new_internal(XFDESKTOP_ICON_POSITION_LEVEL_INVALID);
                            configs->configs = g_list_prepend(configs->configs, cur_config);
                        }
                        break;

                    case PARSER_CONFIG_MONITORS_ARRAY:
                        if (cur_pos_monitor != NULL) {
                            PARSE_ERROR(level, state, configs, event, "Got new position monitor when old one not closed");
                        } else {
                            g_assert(cur_config != NULL);
                            cur_pos_monitor = g_new0(XfdesktopIconPositionMonitor, 1);
                            cur_pos_monitor->geometry.x = G_MININT;
                            cur_pos_monitor->geometry.y = G_MININT;
                            cur_pos_monitor->geometry.width = G_MININT;
                            cur_pos_monitor->geometry.height = G_MININT;
                            STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_MAP);
                        }
                        break;

                    case PARSER_CONFIG_MONITOR_GEOMETRY:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_GEOMETRY_MAP);
                        break;

                    case PARSER_CONFIG_ICON:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_ICON_MAP);
                        break;

                    case PARSER_CONFIG_ICONS:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_ICONS_MAP);
                        break;

                    default:
                        PARSE_ERROR(level, state, configs, event, "Unexpected map");
                        break;
                }
                break;

            case YAML_MAPPING_END_EVENT:
                LOG_EVENT_OUT(level, "MAPPING_END");
                switch (state) {
                    case PARSER_CONFIG_MONITOR_GEOMETRY_MAP:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MONITOR_MAP);
                        break;

                    case PARSER_CONFIG_ICON_MAP: {
                        const gchar *err_msg = validate_position(cur_position);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIG_ICONS_MAP);
                            cur_position = NULL;
                        }
                        break;
                    }

                    case PARSER_CONFIG_MONITOR_MAP:
                        g_assert(cur_config != NULL);
                        if (cur_monitor_id == NULL) {
                            PARSE_ERROR(level, state, configs, event, "Monitor is missing ID");
                        } else {
                            const gchar *err_msg = validate_monitor_config(cur_pos_monitor);
                            if (err_msg != NULL) {
                                PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                            } else {
                                g_hash_table_insert(cur_config->monitors, cur_monitor_id, cur_pos_monitor);
                                cur_monitor_id = NULL;
                                cur_pos_monitor = NULL;
                                STATE_TRANSITION(level, state, PARSER_CONFIG_MONITORS_ARRAY);
                            }
                        }
                        break;

                    case PARSER_CONFIG_ICONS_MAP:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MAP);
                        break;

                    case PARSER_CONFIG_MAP: {
                        const gchar *err_msg = validate_position_config(cur_config);
                        if (err_msg != NULL) {
                            PARSE_ERROR(level, state, configs, event, "%s", err_msg);
                        } else {
                            STATE_TRANSITION(level, state, PARSER_CONFIGS_ARRAY);
                            cur_config = NULL;
                        }
                        break;
                    }

                    case PARSER_TOPLEVEL_MAP:
                        STATE_TRANSITION(level, state, PARSER_TOP);
                        break;

                    case PARSER_CONFIG_ICONS:
                        STATE_TRANSITION(level, state, PARSER_CONFIG_MAP);
                        break;

                    default:
                        PARSE_ERROR(level, state, configs, event, "Unexpected end of map");
                        break;
                }
                break;

            case YAML_STREAM_END_EVENT:
                LOG_EVENT(level, "STREAM_END");
                switch (state) {
                    case PARSER_TOP:
                        STATE_TRANSITION(level, state, PARSER_DONE);
                        break;

                    default:
                        PARSE_ERROR(level, state, configs, event, "Unexpected end of stream");
                        break;
                }
                break;
        }

        yaml_event_delete(&event);
    }

    yaml_parser_delete(&parser);
    fclose(input);

    if (state == PARSER_ERROR) {
        g_hash_table_remove_all(configs->config_to_monitor);
        g_list_free_full(configs->configs, (GDestroyNotify)xfdesktop_icon_position_config_free);
        configs->configs = NULL;

        g_free(cur_monitor_id);
        xfdesktop_icon_position_monitor_free(cur_pos_monitor);

        return FALSE;
    } else {
        configs->configs = g_list_sort(configs->configs, compare_configs_by_level);
        return TRUE;
    }
}

gboolean
xfdesktop_icon_position_configs_lookup(XfdesktopIconPositionConfigs *configs,
                                       const gchar *icon_id,
                                       XfwMonitor **monitor,
                                       gint *row,
                                       gint *col)
{
    g_return_val_if_fail(configs != NULL, FALSE);
    g_return_val_if_fail(icon_id != NULL, FALSE);

    gint best_level = XFDESKTOP_ICON_POSITION_LEVEL_INVALID;
    XfwMonitor *best_monitor = NULL;
    XfdesktopIconPosition *best_position = NULL;

    for (GList *l = configs->configs; l != NULL; l = l->next) {
        XfdesktopIconPositionConfig *config = l->data;
        XfwMonitor *config_monitor = g_hash_table_lookup(configs->config_to_monitor, config);
        if (config_monitor != NULL) {
            XfdesktopIconPosition *position = g_hash_table_lookup(config->icon_positions, icon_id);
            if (position != NULL && config->level > best_level) {
                best_level = config->level;
                best_monitor = config_monitor;
                best_position = position;
            }
        }
    }

    if (best_level != XFDESKTOP_ICON_POSITION_LEVEL_INVALID) {
        if (monitor != NULL) {
            *monitor = best_monitor;
        }
        if (row != NULL) {
            *row = best_position->row;
        }
        if (col != NULL) {
            *col = best_position->col;
        }
        return TRUE;
    } else {
        return FALSE;
    }
}

void
xfdesktop_icon_position_configs_remove_icon(XfdesktopIconPositionConfigs *configs,
                                            XfdesktopIconPositionConfig *config,
                                            const gchar *icon_id)
{
    g_return_if_fail(configs != NULL);
    g_return_if_fail(config != NULL);
    g_return_if_fail(icon_id != NULL);

    g_hash_table_remove(config->icon_positions, icon_id);
    schedule_save(configs);
}

void
xfdesktop_icon_position_configs_set_icon_position(XfdesktopIconPositionConfigs *configs,
                                                  XfdesktopIconPositionConfig *config,
                                                  const gchar *identifier,
                                                  guint row,
                                                  guint col,
                                                  guint64 last_seen_timestamp)
{
    g_return_if_fail(configs != NULL);
    g_return_if_fail(config != NULL);
    g_return_if_fail(identifier != NULL);

    XfdesktopIconPosition *position = g_hash_table_lookup(config->icon_positions, identifier);
    if (position == NULL) {
        position = g_new0(XfdesktopIconPosition, 1);
        g_hash_table_insert(config->icon_positions, g_strdup(identifier), position);
    }

    position->row = row;
    position->col = col;
    position->last_seen = last_seen_timestamp;

    for (GList *l = configs->configs; l != NULL; l = l->next) {
        XfdesktopIconPositionConfig *a_config = l->data;
        if (a_config != config && a_config->level >= config->level) {
            DBG("removing icon from higher or equal prio config");
            // XXX: Do we want to do this for all configs, or only assigned
            // configs?  I could make an argument either way.
            g_hash_table_remove(a_config->icon_positions, identifier);
        }
    }

    schedule_save(configs);
}

XfdesktopIconPositionConfig *
xfdesktop_icon_position_configs_add_monitor(XfdesktopIconPositionConfigs *configs,
                                            XfwMonitor *monitor,
                                            XfdesktopIconPositionLevel level,
                                            GList **candidates)
{
    g_return_val_if_fail(configs != NULL, NULL);
    g_return_val_if_fail(XFW_IS_MONITOR(monitor), NULL);
    g_return_val_if_fail(candidates != NULL && *candidates == NULL, NULL);

    const gchar *monitor_id = xfw_monitor_get_identifier(monitor);
    GdkRectangle geom;
    xfw_monitor_get_logical_geometry(monitor, &geom);

    XfdesktopIconPositionConfig *best_config = NULL;

    for (GList *l = configs->configs; l != NULL; l = l->next) {
        XfdesktopIconPositionConfig *config = l->data;
        if (!g_hash_table_contains(configs->config_to_monitor, config)) {
            if (g_hash_table_contains(config->monitors, monitor_id)) {
                // If the monitor ID matches and the config is currently
                // unused, then (almost) certainly we have the right config.
                best_config = config;
                break;
            } else {
                GHashTableIter iter;
                g_hash_table_iter_init(&iter, config->monitors);

                XfdesktopIconPositionMonitor *pos_monitor;
                while (g_hash_table_iter_next(&iter, NULL, (gpointer)&pos_monitor)) {
                    if (pos_monitor->geometry.width == geom.width && pos_monitor->geometry.height == geom.height) {
                        // If it's the same level and width/height, it *could*
                        // match, but we'll (possibly) ask the user to choose.
                        *candidates = g_list_prepend(*candidates, config);
                    }
                }
            }
        }
    }

    if (best_config == NULL) {
        gint n_candidates = g_list_length(*candidates);
        if (n_candidates == 0) {
            // Usually if there are no candidates we'd just create a new config
            // and move on, but for now we want to return nothing and let the
            // migration code have a crack at it.
            //best_config = xfdesktop_icon_position_config_new(level);
        } else if (n_candidates == 1) {
            best_config = g_list_nth_data(*candidates, 0);
        } else {
            // We're going to look over them again, to see if (even though no
            // monitor ID matched) there might be a config that has the exact same
            // geometry (including x/y) as this monitor.
            GList *same_geom_candidates = NULL;
            for (GList *l = *candidates; l != NULL; l = l->next) {
                XfdesktopIconPositionConfig *config = l->data;

                GHashTableIter iter;
                g_hash_table_iter_init(&iter, config->monitors);

                XfdesktopIconPositionMonitor *pos_monitor;
                while (g_hash_table_iter_next(&iter, NULL, (gpointer)&pos_monitor)) {
                    if (gdk_rectangle_equal(&geom, &pos_monitor->geometry)) {
                        same_geom_candidates = g_list_prepend(same_geom_candidates, config);
                    }
                }
            }

            best_config = g_list_nth_data(same_geom_candidates, 0);
            gint n_same_geom_candidates = g_list_length(same_geom_candidates);
            g_list_free(same_geom_candidates);

            if (n_same_geom_candidates != 1) {
                // If more than one candidate has the same geometry, don't
                // auto-assign.
                best_config = NULL;
            }
        }
    }

    if (best_config != NULL) {
        xfdesktop_icon_position_configs_assign_monitor(configs, best_config, monitor);
        g_clear_pointer(candidates, g_list_free);
        return best_config;
    } else {
        *candidates = g_list_sort_with_data(*candidates, compare_candidates, GINT_TO_POINTER(level));
        return NULL;
    }
}

void
xfdesktop_icon_position_configs_assign_monitor(XfdesktopIconPositionConfigs *configs,
                                               XfdesktopIconPositionConfig *config,
                                               XfwMonitor *monitor)
{
    g_return_if_fail(configs != NULL);
    g_return_if_fail(config != NULL);
    g_return_if_fail(XFW_IS_MONITOR(monitor));

    const gchar *monitor_id = xfw_monitor_get_identifier(monitor);
    if (!g_hash_table_contains(config->monitors, monitor_id)) {
        XfdesktopIconPositionMonitor *pos_monitor = g_new0(XfdesktopIconPositionMonitor, 1);
        pos_monitor->display_name = g_strdup(xfw_monitor_get_description(monitor));
        xfw_monitor_get_logical_geometry(monitor, &pos_monitor->geometry);
        g_hash_table_insert(config->monitors, g_strdup(monitor_id), pos_monitor);
    }

    if (g_list_find(configs->configs, config) == NULL) {
        configs->configs = g_list_insert_sorted(configs->configs, config, compare_configs_by_level);
    }
    g_hash_table_insert(configs->config_to_monitor, config, monitor);

    schedule_save(configs);
}

void
xfdesktop_icon_position_configs_unassign_monitor(XfdesktopIconPositionConfigs *configs, XfwMonitor *monitor) {
    g_return_if_fail(configs != NULL);
    g_return_if_fail(XFW_IS_MONITOR(monitor));

    GHashTableIter iter;
    g_hash_table_iter_init(&iter, configs->config_to_monitor);

    XfwMonitor *a_monitor;
    while (g_hash_table_iter_next(&iter, NULL, (gpointer)&a_monitor)) {
        if (a_monitor == monitor) {
            g_hash_table_iter_remove(&iter);
            break;
        }
    }
}

void
xfdesktop_icon_position_configs_delete_config(XfdesktopIconPositionConfigs *configs, XfdesktopIconPositionConfig *config) {
    g_return_if_fail(configs != NULL);
    g_return_if_fail(config != NULL);

    g_hash_table_remove(configs->config_to_monitor, config);
    configs->configs = g_list_remove(configs->configs, config);

    xfdesktop_icon_position_config_free(config);

    schedule_save(configs);
}

gboolean
xfdesktop_icon_position_configs_save(XfdesktopIconPositionConfigs *configs, GError **error) {
    g_return_val_if_fail(configs != NULL, FALSE);
    g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
    return save_icons(configs, error);
}

void
xfdesktop_icon_position_configs_free(XfdesktopIconPositionConfigs *configs) {
    if (configs != NULL) {
        if (configs->scheduled_save_id != 0) {
            g_source_remove(configs->scheduled_save_id);

            GError *error = NULL;
            if (!save_icons(configs, &error)) {
                g_message("Failed to save desktop icon positions: %s", error->message);
                g_error_free(error);
            }
        }

        g_hash_table_destroy(configs->config_to_monitor);
        g_list_free_full(configs->configs, (GDestroyNotify)xfdesktop_icon_position_config_free);
        g_object_unref(configs->file);
        g_free(configs);
    }
}

XfdesktopIconPositionConfig *
xfdesktop_icon_position_config_new(XfdesktopIconPositionLevel level) {
    g_return_val_if_fail(level >= XFDESKTOP_ICON_POSITION_LEVEL_PRIMARY && level <= XFDESKTOP_ICON_POSITION_LEVEL_OTHER, NULL);
    return xfdesktop_icon_position_config_new_internal(level);
}

void
_xfdesktop_icon_position_config_set_icon_position(XfdesktopIconPositionConfig *config,
                                                  const gchar *identifier,
                                                  guint row,
                                                  guint col)
{
    g_return_if_fail(config != NULL);
    g_return_if_fail(identifier != NULL);

    XfdesktopIconPosition *position = g_hash_table_lookup(config->icon_positions, identifier);
    if (position == NULL) {
        position = g_new0(XfdesktopIconPosition, 1);
        g_hash_table_insert(config->icon_positions, g_strdup(identifier), position);
    }

    position->row = row;
    position->col = col;
}

GList *  // caller owned container, callee owned data
xfdesktop_icon_position_config_get_monitor_display_names(XfdesktopIconPositionConfig *config) {
    GHashTableIter iter;
    g_hash_table_iter_init(&iter, config->monitors);

    GList *display_names = NULL;
    XfdesktopIconPositionMonitor *pos_monitor;
    while (g_hash_table_iter_next(&iter, NULL, (gpointer)&pos_monitor)) {
        display_names = g_list_prepend(display_names, pos_monitor->display_name);
    }
    return g_list_reverse(display_names);
}

void
xfdesktop_icon_position_config_free(XfdesktopIconPositionConfig *config) {
    if (config != NULL) {
        g_hash_table_destroy(config->icon_positions);
        g_hash_table_destroy(config->monitors);
        g_free(config);
    }
}

Messung V0.5 in Prozent
C=97 H=94 G=95

¤ Dauer der Verarbeitung: 0.8 Sekunden  (vorverarbeitet am  2026-07-10) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.