1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-25 12:26:40 +02:00
Files
weechat/src/core/core-theme.c
T
2026-06-06 12:23:07 +02:00

1239 lines
33 KiB
C

/*
* SPDX-FileCopyrightText: 2026 Sébastien Helleu <flashcode@flashtux.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat 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 3 of the License, or
* (at your option) any later version.
*
* WeeChat 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <https://www.gnu.org/licenses/>.
*/
/* Themes: named bundles of option overrides applied via /theme command */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "weechat.h"
#include "core-arraylist.h"
#include "core-config.h"
#include "core-config-file.h"
#include "core-dir.h"
#include "core-hashtable.h"
#include "core-hook.h"
#include "core-string.h"
#include "core-theme.h"
#include "core-version.h"
#include "../gui/gui-chat.h"
#include "../gui/gui-color.h"
#include "../gui/gui-window.h"
#include "../plugins/weechat-plugin.h"
struct t_theme *themes = NULL;
struct t_theme *last_theme = NULL;
int theme_applying = 0;
/*
* Searches for a theme by name in the in-memory registry.
*
* Returns pointer to theme found, NULL if not found.
*/
struct t_theme *
theme_search (const char *name)
{
struct t_theme *ptr_theme;
if (!name)
return NULL;
for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme)
{
if (strcmp (ptr_theme->name, name) == 0)
return ptr_theme;
}
return NULL;
}
/*
* Builds a "YYYY-MM-DD HH:MM:SS" timestamp string for "now" (local time).
*
* Returned string is allocated; caller frees.
*/
char *
theme_format_now (void)
{
time_t time_now;
struct tm *local_time;
char buf[32];
time_now = time (NULL);
local_time = localtime (&time_now);
if (!local_time)
return strdup ("");
if (strftime (buf, sizeof (buf), "%Y-%m-%d %H:%M:%S", local_time) == 0)
return strdup ("");
return strdup (buf);
}
/*
* Allocates a new theme with name and empty metadata; does not link it
* into the registry.
*
* Returns the new theme, NULL on error.
*/
struct t_theme *
theme_alloc (const char *name)
{
struct t_theme *new_theme;
new_theme = calloc (1, sizeof (*new_theme));
if (!new_theme)
return NULL;
new_theme->name = strdup (name);
new_theme->description = strdup ("");
new_theme->date = theme_format_now ();
new_theme->weechat_version = strdup (version_get_version ());
if (!new_theme->name || !new_theme->description
|| !new_theme->date || !new_theme->weechat_version)
{
free (new_theme->name);
free (new_theme->description);
free (new_theme->date);
free (new_theme->weechat_version);
free (new_theme);
return NULL;
}
return new_theme;
}
/*
* Frees one contribution (does not unlink from the parent theme list).
*/
void
theme_contribution_free (struct t_theme_contribution *contribution)
{
if (!contribution)
return;
hashtable_free (contribution->overrides);
free (contribution);
}
/*
* Frees a theme (does not unlink from registry; caller handles that).
*/
void
theme_free (struct t_theme *theme)
{
struct t_theme_contribution *ptr_contribution, *next_contribution;
if (!theme)
return;
ptr_contribution = theme->contributions;
while (ptr_contribution)
{
next_contribution = ptr_contribution->next_contribution;
theme_contribution_free (ptr_contribution);
ptr_contribution = next_contribution;
}
free (theme->name);
free (theme->description);
free (theme->date);
free (theme->weechat_version);
free (theme);
}
/*
* Merges entries from src into dst (overwrites duplicate keys).
*/
void
theme_merge_overrides_cb (void *data,
struct t_hashtable *hashtable,
const void *key,
const void *value)
{
struct t_hashtable *dst = (struct t_hashtable *)data;
/* make C compiler happy */
(void) hashtable;
hashtable_set (dst, (const char *)key, (const char *)value);
}
/*
* Searches the contribution belonging to the given (plugin, script) pair
* in a theme's contribution list. Returns NULL if not found.
*/
struct t_theme_contribution *
theme_search_contribution (struct t_theme *theme,
struct t_weechat_plugin *plugin,
const void *script)
{
struct t_theme_contribution *ptr;
if (!theme)
return NULL;
for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution)
{
if ((ptr->plugin == plugin) && (ptr->script == script))
return ptr;
}
return NULL;
}
/*
* Allocates a new contribution and appends it to a theme's list.
*
* Returns the new contribution, NULL on error.
*/
struct t_theme_contribution *
theme_contribution_new (struct t_theme *theme,
struct t_weechat_plugin *plugin,
const void *script)
{
struct t_theme_contribution *new_contribution;
new_contribution = calloc (1, sizeof (*new_contribution));
if (!new_contribution)
return NULL;
new_contribution->plugin = plugin;
new_contribution->script = script;
new_contribution->overrides = hashtable_new (32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_STRING,
NULL, NULL);
if (!new_contribution->overrides)
{
free (new_contribution);
return NULL;
}
new_contribution->prev_contribution = theme->last_contribution;
new_contribution->next_contribution = NULL;
if (theme->last_contribution)
theme->last_contribution->next_contribution = new_contribution;
else
theme->contributions = new_contribution;
theme->last_contribution = new_contribution;
return new_contribution;
}
/*
* Registers a contribution to a theme.
*
* Identity is the (plugin, script) pair:
* - plugin == NULL && script == NULL => core
* - plugin != NULL && script == NULL => plugin-level
* - plugin != NULL && script != NULL => individual script
*
* If a contribution for the same (plugin, script) already exists under
* the named theme, the new overrides are merged into it (later keys
* win). Otherwise a new contribution is appended. Across distinct
* contributors, contributions are applied in list order at apply-time
* (later contributions override earlier ones for duplicate keys).
*
* The "overrides" hashtable passed in is read-only here; the caller
* retains ownership and may free it after the call.
*
* Returns pointer to the theme (existing or newly created), NULL on
* error.
*/
struct t_theme *
theme_register (struct t_weechat_plugin *plugin,
const void *script,
const char *name,
struct t_hashtable *overrides)
{
struct t_theme *theme;
struct t_theme_contribution *contribution;
if (!name || !name[0])
return NULL;
theme = theme_search (name);
if (!theme)
{
theme = theme_alloc (name);
if (!theme)
return NULL;
theme->prev_theme = last_theme;
theme->next_theme = NULL;
if (last_theme)
last_theme->next_theme = theme;
else
themes = theme;
last_theme = theme;
}
if (overrides)
{
contribution = theme_search_contribution (theme, plugin, script);
if (!contribution)
{
contribution = theme_contribution_new (theme, plugin, script);
if (!contribution)
return theme; /* theme exists but contribution failed */
}
hashtable_map (overrides,
&theme_merge_overrides_cb,
contribution->overrides);
}
return theme;
}
/*
* Drops one contribution from a theme (unlinks and frees it).
*/
void
theme_drop_contribution (struct t_theme *theme,
struct t_theme_contribution *contribution)
{
if (!theme || !contribution)
return;
if (contribution->prev_contribution)
{
contribution->prev_contribution->next_contribution =
contribution->next_contribution;
}
else
{
theme->contributions = contribution->next_contribution;
}
if (contribution->next_contribution)
{
contribution->next_contribution->prev_contribution =
contribution->prev_contribution;
}
else
{
theme->last_contribution = contribution->prev_contribution;
}
theme_contribution_free (contribution);
}
/*
* Drops all contributions owned by a given plugin (across every theme).
* Called from the plugin-unload lifecycle (next commit).
*
* Contributions whose script is non-NULL are kept (they belong to
* individual scripts and are cleaned up separately on script unload).
*/
void
theme_unregister_plugin (struct t_weechat_plugin *plugin)
{
struct t_theme *ptr_theme;
struct t_theme_contribution *ptr_contribution, *next_contribution;
if (!plugin)
return;
for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme)
{
ptr_contribution = ptr_theme->contributions;
while (ptr_contribution)
{
next_contribution = ptr_contribution->next_contribution;
if ((ptr_contribution->plugin == plugin)
&& (ptr_contribution->script == NULL))
{
theme_drop_contribution (ptr_theme, ptr_contribution);
}
ptr_contribution = next_contribution;
}
}
}
/*
* Drops all contributions owned by a given script (across every theme).
* Called from the script-unload lifecycle (a later commit).
*/
void
theme_unregister_script (struct t_weechat_plugin *plugin,
const void *script)
{
struct t_theme *ptr_theme;
struct t_theme_contribution *ptr_contribution, *next_contribution;
if (!plugin || !script)
return;
for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme)
{
ptr_contribution = ptr_theme->contributions;
while (ptr_contribution)
{
next_contribution = ptr_contribution->next_contribution;
if ((ptr_contribution->plugin == plugin)
&& (ptr_contribution->script == script))
{
theme_drop_contribution (ptr_theme, ptr_contribution);
}
ptr_contribution = next_contribution;
}
}
}
/*
* Returns the total number of overrides across all contributions of a
* theme. Duplicate keys (across contributions) are counted multiple
* times; the actual merged-unique count is at most this number.
*/
int
theme_overrides_count (struct t_theme *theme)
{
struct t_theme_contribution *ptr;
int n;
if (!theme)
return 0;
n = 0;
for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution)
n += ptr->overrides->items_count;
return n;
}
/*
* Returns the effective value of an option override across the theme's
* contributions (later contributions win). Returns NULL if no
* contribution provides the key.
*/
const char *
theme_get_override (struct t_theme *theme, const char *option_name)
{
struct t_theme_contribution *ptr;
const char *value, *latest;
if (!theme || !option_name)
return NULL;
latest = NULL;
for (ptr = theme->contributions; ptr; ptr = ptr->next_contribution)
{
value = (const char *)hashtable_get (ptr->overrides, option_name);
if (value)
latest = value;
}
return latest;
}
/*
* Compares two themes by name (callback used by arraylist sort).
*
* Returns negative, zero, or positive value (like strcmp).
*/
int
theme_list_cmp_cb (void *data, struct t_arraylist *arraylist,
void *pointer1, void *pointer2)
{
/* make C compiler happy */
(void) data;
(void) arraylist;
return strcmp (((struct t_theme *)pointer1)->name,
((struct t_theme *)pointer2)->name);
}
/*
* Returns an arraylist of t_theme * for all registered themes (built-ins).
*
* The returned arraylist owns no data; callers must not free its items.
* Returns NULL on allocation failure.
*/
struct t_arraylist *
theme_list (void)
{
struct t_arraylist *list;
struct t_theme *ptr_theme;
list = arraylist_new (8, 1, 0, &theme_list_cmp_cb, NULL, NULL, NULL);
if (!list)
return NULL;
for (ptr_theme = themes; ptr_theme; ptr_theme = ptr_theme->next_theme)
arraylist_add (list, ptr_theme);
return list;
}
/*
* Builds the on-disk path for a user theme:
* "<weechat_config_dir>/themes/<name>.theme".
*
* Returned string is allocated; caller frees. Returns NULL on error.
*/
char *
theme_user_file_path (const char *name)
{
char *path = NULL;
if (!name || !name[0])
return NULL;
string_asprintf (&path, "%s/themes/%s.theme",
weechat_config_dir, name);
return path;
}
/*
* Builds a unique backup theme name "backup-YYYYMMDD-HHMMSS-uuuuuu".
*
* Returned string is allocated; caller frees. Returns NULL on error.
*/
char *
theme_make_backup_name (void)
{
struct timeval tv;
struct tm *local_time;
char buf[128];
if (gettimeofday (&tv, NULL) != 0)
return NULL;
local_time = localtime (&tv.tv_sec);
if (!local_time)
return NULL;
snprintf (buf, sizeof (buf),
"backup-%04d%02d%02d-%02d%02d%02d-%06ld",
local_time->tm_year + 1900,
local_time->tm_mon + 1,
local_time->tm_mday,
local_time->tm_hour,
local_time->tm_min,
local_time->tm_sec,
(long)tv.tv_usec);
return strdup (buf);
}
/*
* Writes a snapshot of themable options to a .theme file at
* "<weechat_config_dir>/themes/<name>.theme".
*
* The themes directory is created if missing. The file contains an
* [info] section (name, description, date, weechat version) followed by
* an [options] section.
*
* If "diff_only" is non-zero, only options whose value differs from
* their default (config_file_option_has_changed) are written. If zero,
* every themable option is written (full snapshot).
*
* Returns 1 on success, 0 on error.
*/
int
theme_write_file (const char *name, const char *description, int diff_only)
{
char *path, *dir, *value, *now;
FILE *file;
struct t_config_file *ptr_config;
struct t_config_section *ptr_section;
struct t_config_option *ptr_option;
if (!name || !name[0])
return 0;
path = NULL;
dir = NULL;
string_asprintf (&dir, "%s/themes", weechat_config_dir);
if (!dir)
return 0;
dir_mkdir (dir, 0755);
free (dir);
path = theme_user_file_path (name);
if (!path)
return 0;
file = fopen (path, "w");
free (path);
if (!file)
return 0;
now = theme_format_now ();
fprintf (file, "[info]\n");
fprintf (file, "name = \"%s\"\n", name);
fprintf (file, "description = \"%s\"\n",
(description) ? description : "");
fprintf (file, "date = \"%s\"\n", (now) ? now : "");
fprintf (file, "weechat = \"%s\"\n", version_get_version ());
fprintf (file, "\n[options]\n");
free (now);
for (ptr_config = config_files; ptr_config;
ptr_config = ptr_config->next_config)
{
for (ptr_section = ptr_config->sections; ptr_section;
ptr_section = ptr_section->next_section)
{
for (ptr_option = ptr_section->options; ptr_option;
ptr_option = ptr_option->next_option)
{
if (!ptr_option->themable)
continue;
if (diff_only && !config_file_option_has_changed (ptr_option))
continue;
value = config_file_option_value_to_string (
ptr_option, 0, 0, 1);
fprintf (file, "%s.%s.%s = %s\n",
ptr_config->name, ptr_section->name,
ptr_option->name,
(value) ? value : "\"\"");
free (value);
}
}
}
fclose (file);
return 1;
}
/*
* Creates a timestamped backup theme file with the current themable state.
*
* Returned string is the backup name (caller frees), NULL on failure.
*/
char *
theme_make_backup (void)
{
char *name;
name = theme_make_backup_name ();
if (!name)
return NULL;
if (!theme_write_file (
name,
_("Automatic backup written before /theme apply"),
0)) /* full snapshot: backups must round-trip exactly */
{
free (name);
return NULL;
}
return name;
}
/*
* Applies one override entry (callback for hashtable_map during apply).
*
* Refuses entries pointing to options that do not exist or that are not
* themable, logging a warning to the core buffer; the apply itself still
* proceeds with the remaining entries.
*/
void
theme_apply_set_option_cb (void *data,
struct t_hashtable *hashtable,
const void *key,
const void *value)
{
struct t_config_option *option = NULL;
/* make C compiler happy */
(void) data;
(void) hashtable;
config_file_search_with_string ((const char *)key,
NULL, NULL, &option, NULL);
if (!option)
{
gui_chat_printf (NULL,
_("%sTheme: option \"%s\" not found, skipped"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
(const char *)key);
return;
}
if (!option->themable)
{
gui_chat_printf (
NULL,
_("%sTheme: option \"%s\" is not themable, skipped"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
(const char *)key);
return;
}
config_file_option_set (option, (const char *)value, 1);
}
/*
* Strips one optional pair of matching surrounding quotes (' or ") from
* the in-place string; returns a pointer that may differ from the input
* (advances past an opening quote).
*/
char *
theme_file_strip_quotes (char *value)
{
size_t len;
if (!value)
return value;
len = strlen (value);
if ((len >= 2)
&& (((value[0] == '"') && (value[len - 1] == '"'))
|| ((value[0] == '\'') && (value[len - 1] == '\''))))
{
value[len - 1] = '\0';
return value + 1;
}
return value;
}
/*
* Parses a .theme file into a transient t_theme.
*
* The file uses two INI-like sections: [info] (keys: name, description,
* date, weechat) and [options] (key = full option name like
* "irc.color.input_nick", value = string). Unknown [info] keys produce a
* warning and are ignored; unknown sections produce a warning and the
* lines in them are skipped.
*
* Returns a heap-allocated t_theme (caller frees with theme_free), or
* NULL if the file cannot be opened.
*/
struct t_theme *
theme_file_parse (const char *path)
{
FILE *file;
char line[8192], *ptr, *end, *eq, *key, *value;
int line_number, in_options;
struct t_theme *theme;
struct t_theme_contribution *contribution;
if (!path)
return NULL;
file = fopen (path, "r");
if (!file)
return NULL;
theme = theme_alloc ("");
if (!theme)
{
fclose (file);
return NULL;
}
/* file themes carry a single anonymous (plugin=NULL, script=NULL)
contribution holding everything in the [options] section */
contribution = theme_contribution_new (theme, NULL, NULL);
if (!contribution)
{
theme_free (theme);
fclose (file);
return NULL;
}
/* clear the placeholder name; the file should provide it */
free (theme->name);
theme->name = NULL;
/* description/date/weechat_version come from the file too */
free (theme->description);
theme->description = NULL;
free (theme->date);
theme->date = NULL;
free (theme->weechat_version);
theme->weechat_version = NULL;
line_number = 0;
in_options = 0;
while (fgets (line, sizeof (line) - 1, file))
{
line_number++;
/* trim trailing CR / LF */
end = strchr (line, '\r');
if (end)
*end = '\0';
end = strchr (line, '\n');
if (end)
*end = '\0';
/* skip leading whitespace */
ptr = line;
while ((ptr[0] == ' ') || (ptr[0] == '\t'))
ptr++;
/* skip empty lines and comments */
if (!ptr[0] || (ptr[0] == '#'))
continue;
/* section header */
if (ptr[0] == '[')
{
end = strchr (ptr, ']');
if (!end)
{
gui_chat_printf (
NULL,
_("%s%s: line %d: malformed section header"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
path, line_number);
continue;
}
*end = '\0';
if (strcmp (ptr + 1, "info") == 0)
{
in_options = 0;
}
else if (strcmp (ptr + 1, "options") == 0)
{
in_options = 1;
}
else
{
gui_chat_printf (
NULL,
_("%s%s: line %d: ignoring unknown section \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
path, line_number, ptr + 1);
in_options = -1; /* skip lines until next known section */
}
continue;
}
if (in_options < 0)
continue;
/* "key = value" */
eq = strchr (ptr, '=');
if (!eq)
{
gui_chat_printf (
NULL,
_("%s%s: line %d: missing '=' separator"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
path, line_number);
continue;
}
/* trim key */
key = ptr;
end = eq - 1;
while ((end > key) && ((end[0] == ' ') || (end[0] == '\t')))
end--;
end[1] = '\0';
/* trim value */
value = eq + 1;
while ((value[0] == ' ') || (value[0] == '\t'))
value++;
end = value + strlen (value) - 1;
while ((end > value) && ((end[0] == ' ') || (end[0] == '\t')))
end--;
end[1] = '\0';
value = theme_file_strip_quotes (value);
if (in_options)
{
hashtable_set (contribution->overrides, key, value);
}
else
{
/* [info] section */
if (strcmp (key, "name") == 0)
{
free (theme->name);
theme->name = strdup (value);
}
else if (strcmp (key, "description") == 0)
{
free (theme->description);
theme->description = strdup (value);
}
else if (strcmp (key, "date") == 0)
{
free (theme->date);
theme->date = strdup (value);
}
else if (strcmp (key, "weechat") == 0)
{
free (theme->weechat_version);
theme->weechat_version = strdup (value);
}
else
{
gui_chat_printf (
NULL,
_("%s%s: line %d: ignoring unknown [info] key \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
path, line_number, key);
}
}
}
fclose (file);
if (!theme->name)
theme->name = strdup ("");
if (!theme->description)
theme->description = strdup ("");
if (!theme->date)
theme->date = strdup ("");
if (!theme->weechat_version)
theme->weechat_version = strdup ("");
return theme;
}
/*
* Applies a theme registered in memory.
*
* If weechat.look.theme_backup is on (and the target name does not begin
* with "backup-"), a backup file is written first; on backup failure the
* apply is aborted before any option is changed.
*
* Iterates the theme's overrides with theme_applying=1 so the per-option
* change callbacks skip their gui refresh; a single refresh is performed
* at the end.
*
* Returns WEECHAT_RC_OK on success, WEECHAT_RC_ERROR if the theme name
* is unknown or the backup could not be created.
*/
int
theme_apply (const char *name)
{
struct t_theme *file_theme = NULL;
struct t_theme *theme = NULL;
struct t_theme_contribution *ptr_contribution;
char *path = NULL;
char *backup_name = NULL;
if (!name || !name[0])
return WEECHAT_RC_ERROR;
/* Resolution: a user file with the given name shadows any built-in
of the same name. Read the file transiently (parse, apply, free)
so user themes have no steady-state memory footprint. */
path = theme_user_file_path (name);
if (path && (access (path, R_OK) == 0))
{
file_theme = theme_file_parse (path);
if (!file_theme)
{
gui_chat_printf (NULL,
_("%sFailed to parse theme file \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
path);
free (path);
return WEECHAT_RC_ERROR;
}
theme = file_theme;
}
else
{
theme = theme_search (name);
if (!theme)
{
gui_chat_printf (NULL,
_("%sTheme \"%s\" not found"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
free (path);
return WEECHAT_RC_ERROR;
}
}
free (path);
/* create a backup of current themable state, if enabled */
if (CONFIG_BOOLEAN(config_look_theme_backup)
&& (strncmp (name, "backup-", 7) != 0))
{
backup_name = theme_make_backup ();
if (!backup_name)
{
theme_free (file_theme);
gui_chat_printf (
NULL,
_("%sUnable to create theme backup; aborting apply "
"(disable option weechat.look.theme_backup to force)"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]);
return WEECHAT_RC_ERROR;
}
}
/* Apply each contribution in order; per-option refreshes are
suppressed via the theme_applying flag (see config_change_color).
Later contributions naturally win for duplicate keys because
config_file_option_set is called for each in sequence. */
theme_applying = 1;
for (ptr_contribution = theme->contributions; ptr_contribution;
ptr_contribution = ptr_contribution->next_contribution)
{
hashtable_map (ptr_contribution->overrides,
&theme_apply_set_option_cb, NULL);
}
theme_applying = 0;
/* file_theme (if any) is transient: discard now */
theme_free (file_theme);
/* single refresh at the end */
if (gui_init_ok)
{
gui_color_init_weechat ();
gui_window_ask_refresh (1);
}
/* persist the active theme label */
config_file_option_set (config_look_theme, name, 1);
/* tell the user about the backup */
if (backup_name)
{
gui_chat_printf (
NULL,
_("Previous state saved as theme \"%s\"; to restore: "
"/theme apply %s"),
backup_name, backup_name);
free (backup_name);
}
hook_signal_send ("theme_applied",
WEECHAT_HOOK_SIGNAL_STRING, (char *)name);
return WEECHAT_RC_OK;
}
/*
* Resets every themable option to its default value.
*
* Same backup-first safety as theme_apply: if weechat.look.theme_backup
* is on, a backup file is written before any option is touched, and the
* reset is aborted if the backup cannot be written. The active-theme
* label (weechat.look.theme) is reset to its default (empty string).
*
* Returns WEECHAT_RC_OK on success, WEECHAT_RC_ERROR if the backup is
* required but failed.
*/
int
theme_reset (void)
{
struct t_config_file *ptr_config;
struct t_config_section *ptr_section;
struct t_config_option *ptr_option;
char *backup_name = NULL;
if (CONFIG_BOOLEAN(config_look_theme_backup))
{
backup_name = theme_make_backup ();
if (!backup_name)
{
gui_chat_printf (
NULL,
_("%sUnable to create theme backup; aborting reset "
"(disable option weechat.look.theme_backup to force)"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]);
return WEECHAT_RC_ERROR;
}
}
/* reset every themable option to its default value; per-option gui
refreshes are suppressed via theme_applying */
theme_applying = 1;
for (ptr_config = config_files; ptr_config;
ptr_config = ptr_config->next_config)
{
for (ptr_section = ptr_config->sections; ptr_section;
ptr_section = ptr_section->next_section)
{
for (ptr_option = ptr_section->options; ptr_option;
ptr_option = ptr_option->next_option)
{
if (ptr_option->themable)
config_file_option_reset (ptr_option, 1);
}
}
}
theme_applying = 0;
if (gui_init_ok)
{
gui_color_init_weechat ();
gui_window_ask_refresh (1);
}
/* clear active-theme label */
config_file_option_reset (config_look_theme, 1);
if (backup_name)
{
gui_chat_printf (
NULL,
_("Previous state saved as theme \"%s\"; to restore: "
"/theme apply %s"),
backup_name, backup_name);
free (backup_name);
}
hook_signal_send ("theme_applied",
WEECHAT_HOOK_SIGNAL_STRING, (char *)"");
return WEECHAT_RC_OK;
}
/*
* Saves the current themable options to a user theme file.
*
* Refuses names that match a built-in theme (registered via API) or
* that start with "backup-" (reserved for automatic backups). If
* "full" is non-zero, every themable option is written; otherwise
* only options whose value differs from their default are written.
*
* Returns WEECHAT_RC_OK on success, WEECHAT_RC_ERROR on validation or
* I/O failure.
*/
int
theme_save (const char *name, int full)
{
if (!name || !name[0])
return WEECHAT_RC_ERROR;
if (strncmp (name, "backup-", 7) == 0)
{
gui_chat_printf (
NULL,
_("%sName \"%s\" is reserved for automatic backups"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
return WEECHAT_RC_ERROR;
}
if (theme_search (name))
{
gui_chat_printf (
NULL,
_("%sName \"%s\" is reserved for a built-in theme"),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
return WEECHAT_RC_ERROR;
}
if (!theme_write_file (name, NULL, (full) ? 0 : 1))
{
gui_chat_printf (NULL,
_("%sFailed to save theme \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
return WEECHAT_RC_ERROR;
}
gui_chat_printf (NULL,
_("Theme saved: %s"),
name);
return WEECHAT_RC_OK;
}
/*
* Deletes a user theme file.
*
* Refuses names registered as built-in themes (they have no file).
* Returns WEECHAT_RC_OK on success, WEECHAT_RC_ERROR otherwise.
*/
int
theme_delete (const char *name)
{
char *path;
if (!name || !name[0])
return WEECHAT_RC_ERROR;
if (theme_search (name))
{
gui_chat_printf (
NULL,
_("%sCannot delete built-in theme \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
return WEECHAT_RC_ERROR;
}
path = theme_user_file_path (name);
if (!path)
return WEECHAT_RC_ERROR;
if (unlink (path) != 0)
{
gui_chat_printf (NULL,
_("%sFailed to delete theme \"%s\""),
gui_chat_prefix[GUI_CHAT_PREFIX_ERROR],
name);
free (path);
return WEECHAT_RC_ERROR;
}
gui_chat_printf (NULL,
_("Theme deleted: %s"),
name);
free (path);
return WEECHAT_RC_OK;
}
/*
* Initializes the theme subsystem.
*
* The registry starts empty; built-in themes are registered later (by
* core and by plugins/scripts at their own init time).
*/
void
theme_init (void)
{
themes = NULL;
last_theme = NULL;
theme_applying = 0;
}
/*
* Frees all registered themes and clears the registry.
*/
void
theme_end (void)
{
struct t_theme *ptr_theme, *next_theme;
ptr_theme = themes;
while (ptr_theme)
{
next_theme = ptr_theme->next_theme;
theme_free (ptr_theme);
ptr_theme = next_theme;
}
themes = NULL;
last_theme = NULL;
}