Compare commits

..
Author SHA1 Message Date
deepend a767e8eacc theming: respect system theme by default 2026-07-07 14:00:25 -06:00
deepend-tildeclub 8466e70424 Merge pull request #329 from ZoiteChat/implement-issue-326
Adjust default text-event colors
2026-07-06 23:46:48 -06:00
deepend b85229f1a1 Adjust default text-event colors 2026-07-05 23:54:49 -06:00
deepend-tildeclub d03298a048 Merge pull request #325 from ZoiteChat/fix-chat-scrollbar-stepper-scrolling
Fix chat scrollbar step increment for arrow scrolling
2026-07-05 15:07:25 -06:00
deepend-tildeclub 798e0b487e Merge pull request #324 from ZoiteChat/fix-nickserv-pass-auth
Fix NickServ auth to use decrypted active password
2026-07-05 13:50:48 -06:00
deepend 49444d72b4 Fix chat scrollbar step increment for arrow scrolling 2026-07-05 13:38:26 -06:00
deepend-tildeclub c0e0fe4dcb Merge pull request #319 from ZoiteChat/css-use-after-free-fix
Fix channel tree font ownership/lifetime
2026-07-04 20:07:58 -06:00
deepend-tildeclub 9a189848ea Merge pull request #318 from ZoiteChat/windows-browser-preference
Prefer ShellExecuteW for Windows URL opens
2026-07-04 20:07:37 -06:00
deepend 96b09bfc1d Fix NickServ auth to use decrypted active password 2026-07-04 20:04:11 -06:00
deepend 9a6c913d89 Fix channel tree font ownership/lifetime 2026-07-02 13:08:48 -06:00
deepend 42010594be Prefer ShellExecuteW for Windows URL opens 2026-07-02 13:02:09 -06:00
deepend 5469c30b75 Update GPLv2 text, Add feature request template. 2026-07-01 18:25:48 -06:00
25 changed files with 1157 additions and 374 deletions
+57
View File
@@ -0,0 +1,57 @@
---
name: Feature request
about: Suggest an improvement for ZoiteChat
title: "[Feature]: "
labels: enhancement
assignees: ""
---
## Summary
```text
Describe the feature or enhancement clearly and briefly.
```
## Problem
```text
What problem would this solve?
```
## Proposed Solution
```text
How should ZoiteChat behave?
```
## Alternatives Considered
```text
Other approaches or workarounds you considered.
```
## Use Case
```text
Who benefits from this, and when would they use it?
```
## Platform Details
```text
OS:
ZoiteChat version:
Build/source: GitHub release / Fedora package / source build / Windows installer / other
```
## Checklist
* [ ] I searched existing issues first.
* [ ] I checked whether this already exists in the latest ZoiteChat version available to me.
* [ ] I explained the problem this would solve.
## Extra Notes
```text
Anything else that might help.
```
+22 -24
View File
@@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 2, June 1991 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc. Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
Preamble Preamble
The licenses for most software are designed to take away your The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to the GNU Lesser General Public License instead.) You can apply it to
your programs, too. your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains 0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on does not normally print such an announcement, your work based on
the Program is not required to print an announcement.) the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program, identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not distribution of the source code, even though third parties are not
compelled to copy the source along with the object code. compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program 4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License. be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in 8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally. of promoting the sharing and reuse of software generally.
NO WARRANTY NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it possible use to the public, the best way to achieve this is to make it
@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author> Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -303,17 +303,15 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License along
along with this program; if not, write to the Free Software with this program; if not, see <https://www.gnu.org/licenses/>.
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this If the program is interactive, make it output a short notice like this
when it starts in an interactive mode: when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.
@@ -330,11 +328,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker. `Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989 <signature of Moe Ghoul>, 1 April 1989
Ty Coon, President of Vice Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. Public License instead of this License.
+126 -38
View File
@@ -315,6 +315,59 @@ resolve_parent_theme_root (const char *child_theme_root, const char *parent_name
return home_local; return home_local;
g_free (home_local); g_free (home_local);
{
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
if (xdg_data_home && xdg_data_home[0])
{
candidate = g_build_filename (xdg_data_home, "themes", parent_name, NULL);
if (path_exists_as_dir (candidate))
return candidate;
g_free (candidate);
}
}
candidate = g_build_filename (g_get_user_data_dir (), "themes", parent_name, NULL);
if (path_exists_as_dir (candidate))
return candidate;
g_free (candidate);
{
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
guint i;
if (xdg_data_dirs && xdg_data_dirs[0])
{
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
for (i = 0; data_dirs && data_dirs[i]; i++)
{
if (!data_dirs[i][0])
continue;
candidate = g_build_filename (data_dirs[i], "themes", parent_name, NULL);
if (path_exists_as_dir (candidate))
{
g_strfreev (data_dirs);
return candidate;
}
g_free (candidate);
}
g_strfreev (data_dirs);
}
else
{
const char *const *system_data_dirs = g_get_system_data_dirs ();
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
{
candidate = g_build_filename (system_data_dirs[i], "themes", parent_name, NULL);
if (path_exists_as_dir (candidate))
return candidate;
g_free (candidate);
}
}
}
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir (); user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
candidate = g_build_filename (user_dir, parent_name, NULL); candidate = g_build_filename (user_dir, parent_name, NULL);
g_free (user_dir); g_free (user_dir);
@@ -632,18 +685,50 @@ zoitechat_gtk3_theme_service_discover (void)
g_mkdir_with_parents (user_dir, 0700); g_mkdir_with_parents (user_dir, 0700);
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL); /* Prefer the live XDG environment over GLib's process-cached copies so
* theme lookup matches what GTK resolves for the running session. */
{
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
if (xdg_data_home && xdg_data_home[0])
user_data_themes = g_build_filename (xdg_data_home, "themes", NULL);
else
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
}
add_theme_root (user_roots, seen_user_roots, user_data_themes); add_theme_root (user_roots, seen_user_roots, user_data_themes);
g_free (user_data_themes); g_free (user_data_themes);
add_theme_root (user_roots, seen_user_roots, home_themes); add_theme_root (user_roots, seen_user_roots, home_themes);
add_theme_root (user_roots, seen_user_roots, user_dir); add_theme_root (user_roots, seen_user_roots, user_dir);
system_data_dirs = g_get_system_data_dirs ();
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
{ {
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL); const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
add_theme_root (system_roots, seen_system_roots, system_themes);
g_free (system_themes); if (xdg_data_dirs && xdg_data_dirs[0])
{
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
for (i = 0; data_dirs && data_dirs[i]; i++)
{
char *system_themes;
if (!data_dirs[i][0])
continue;
system_themes = g_build_filename (data_dirs[i], "themes", NULL);
add_theme_root (system_roots, seen_system_roots, system_themes);
g_free (system_themes);
}
g_strfreev (data_dirs);
}
else
{
system_data_dirs = g_get_system_data_dirs ();
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
{
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
add_theme_root (system_roots, seen_system_roots, system_themes);
g_free (system_themes);
}
}
} }
for (i = 0; i < system_roots->len; i++) for (i = 0; i < system_roots->len; i++)
@@ -922,7 +1007,7 @@ static gboolean
validate_theme_root_for_import (const char *theme_root, GError **error) validate_theme_root_for_import (const char *theme_root, GError **error)
{ {
char *index_theme; char *index_theme;
GKeyFile *keyfile; GKeyFile *keyfile = NULL;
char *css_dir; char *css_dir;
char *css_path; char *css_path;
char *raw_inherits; char *raw_inherits;
@@ -930,36 +1015,33 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
guint i; guint i;
GError *load_error = NULL; GError *load_error = NULL;
/* index.theme is optional for GTK3 CSS themes (GTK only needs
* gtk-3.x/gtk.css), but when it is present it must be well formed so
* broken archives are rejected up front. */
index_theme = g_build_filename (theme_root, "index.theme", NULL); index_theme = g_build_filename (theme_root, "index.theme", NULL);
if (!g_file_test (index_theme, G_FILE_TEST_IS_REGULAR)) if (g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
{ {
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, keyfile = g_key_file_new ();
"Invalid GTK3 theme at '%s': missing required index.theme at '%s'.", if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
theme_root, index_theme); {
g_free (index_theme); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
return FALSE; "Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
} theme_root, index_theme, load_error->message);
g_error_free (load_error);
g_key_file_unref (keyfile);
g_free (index_theme);
return FALSE;
}
keyfile = g_key_file_new (); if (!g_key_file_has_group (keyfile, "Desktop Entry"))
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error)) {
{ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.", theme_root, index_theme);
theme_root, index_theme, load_error->message); g_key_file_unref (keyfile);
g_error_free (load_error); g_free (index_theme);
g_key_file_unref (keyfile); return FALSE;
g_free (index_theme); }
return FALSE;
}
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
theme_root, index_theme);
g_key_file_unref (keyfile);
g_free (index_theme);
return FALSE;
} }
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root); css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
@@ -968,7 +1050,8 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).", "Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
theme_root); theme_root);
g_key_file_unref (keyfile); if (keyfile)
g_key_file_unref (keyfile);
g_free (index_theme); g_free (index_theme);
return FALSE; return FALSE;
} }
@@ -981,15 +1064,20 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
theme_root, css_path); theme_root, css_path);
g_free (css_path); g_free (css_path);
g_free (css_dir); g_free (css_dir);
g_key_file_unref (keyfile); if (keyfile)
g_key_file_unref (keyfile);
g_free (index_theme); g_free (index_theme);
return FALSE; return FALSE;
} }
g_free (css_path); g_free (css_path);
g_free (css_dir); g_free (css_dir);
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL); raw_inherits = NULL;
g_key_file_unref (keyfile); if (keyfile)
{
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
g_key_file_unref (keyfile);
}
g_free (index_theme); g_free (index_theme);
if (!raw_inherits) if (!raw_inherits)
return TRUE; return TRUE;
+3 -3
View File
@@ -1815,14 +1815,14 @@ inbound_login_end (session *sess, char *text, const message_tags_data *tags_data
return; return;
/* send nickserv password */ /* send nickserv password */
if (net && net->pass && inbound_nickserv_login (serv)) if (net && serv->password[0] && inbound_nickserv_login (serv))
{ {
serv->p_ns_identify (serv, net->pass); serv->p_ns_identify (serv, serv->password);
} }
/* wait for join if command or nickserv set */ /* wait for join if command or nickserv set */
if (net && prefs.hex_irc_join_delay if (net && prefs.hex_irc_join_delay
&& ((net->pass && inbound_nickserv_login (serv)) && ((serv->password[0] && inbound_nickserv_login (serv))
|| net->commandlist)) || net->commandlist))
{ {
serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv); serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv);
+2 -2
View File
@@ -4868,9 +4868,9 @@ command_insert_vars (session *sess, char *cmd)
break; break;
case 'p': case 'p':
if (mynet->pass) if (sess->server->password[0])
{ {
g_string_append (expanded, mynet->pass); g_string_append (expanded, sess->server->password);
} }
cmd++; cmd++;
break; break;
+18 -10
View File
@@ -381,9 +381,10 @@ test_archive_without_theme_reports_css_error (void)
} }
archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL); archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL);
command = g_strdup_printf ("cd %s && zip -qr %s .", archive_root, archive_path); command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", archive_root, archive_path);
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL)); g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
g_free (command); g_free (command);
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error)); g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error));
g_assert_null (imported_id); g_assert_null (imported_id);
@@ -397,25 +398,31 @@ test_archive_without_theme_reports_css_error (void)
} }
static void static void
test_import_rejects_theme_missing_index_theme (void) test_import_accepts_theme_missing_index_theme (void)
{ {
char *tmp_root; char *tmp_root;
char *src_root; char *src_root;
char *theme_root; char *theme_root;
char *imported_id = NULL; char *imported_id = NULL;
GError *error = NULL; GError *error = NULL;
ZoitechatGtk3Theme *found;
setup_test_xdir (&tmp_root); setup_test_xdir (&tmp_root);
src_root = g_build_filename (tmp_root, "src", NULL); src_root = g_build_filename (tmp_root, "src", NULL);
g_mkdir_with_parents (src_root, 0700); g_mkdir_with_parents (src_root, 0700);
theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE); theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE);
g_assert_false (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error)); /* index.theme is optional for GTK3 CSS themes: gtk-3.x/gtk.css alone
g_assert_null (imported_id); * makes a loadable theme. */
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL); g_assert_true (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error));
g_assert_nonnull (g_strstr_len (error->message, -1, "missing required index.theme")); g_assert_no_error (error);
g_assert_nonnull (g_strstr_len (error->message, -1, "NoIndex")); g_assert_nonnull (imported_id);
g_error_free (error);
found = zoitechat_gtk3_theme_find_by_id (imported_id);
g_assert_nonnull (found);
g_assert_true (g_str_has_suffix (found->path, "NoIndex"));
zoitechat_gtk3_theme_free (found);
g_free (imported_id);
g_free (theme_root); g_free (theme_root);
g_free (src_root); g_free (src_root);
@@ -663,9 +670,10 @@ test_zip_import_nested_root (void)
theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE); theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE);
archive_path = g_build_filename (tmp_root, "themes.zip", NULL); archive_path = g_build_filename (tmp_root, "themes.zip", NULL);
command = g_strdup_printf ("cd %s && zip -qr %s .", zip_root, archive_path); command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", zip_root, archive_path);
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL)); g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
g_free (command); g_free (command);
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL)); g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL));
found = zoitechat_gtk3_theme_find_by_id (imported_id); found = zoitechat_gtk3_theme_find_by_id (imported_id);
@@ -697,7 +705,7 @@ main (int argc, char **argv)
g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root); g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root);
g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error); g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error);
g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error); g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error);
g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_rejects_theme_missing_index_theme); g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_accepts_theme_missing_index_theme);
g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry); g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry);
g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits); g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits);
return g_test_run (); return g_test_run ();
+153 -153
View File
File diff suppressed because it is too large Load Diff
+15 -1
View File
@@ -79,6 +79,8 @@ struct _chanview
guint theme_listener_id; guint theme_listener_id;
}; };
static void chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc);
struct _chan struct _chan
{ {
chanview *cv; /* our owner */ chanview *cv; /* our owner */
@@ -169,6 +171,7 @@ chanview_apply_theme (chanview *cv)
w = GTK_WIDGET (tv->tree); w = GTK_WIDGET (tv->tree);
if (input_style) if (input_style)
font = input_style->font_desc; font = input_style->font_desc;
chanview_set_font_desc (cv, font);
theme_manager_apply_channel_tree_style (w, theme_manager_apply_channel_tree_style (w,
theme_manager_get_channel_tree_palette_behavior (font)); theme_manager_get_channel_tree_palette_behavior (font));
@@ -313,6 +316,14 @@ chanview_free_ch (chanview *cv, GtkTreeIter *iter)
g_free (ch); g_free (ch);
} }
static void
chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc)
{
if (cv->font_desc)
pango_font_description_free (cv->font_desc);
cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
}
static void static void
chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */ chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */
{ {
@@ -335,6 +346,9 @@ chanview_destroy (chanview *cv)
if (cv->box) if (cv->box)
gtk_widget_destroy (cv->box); gtk_widget_destroy (cv->box);
if (cv->font_desc)
pango_font_description_free (cv->font_desc);
chanview_destroy_store (cv); chanview_destroy_store (cv);
g_free (cv); g_free (cv);
} }
@@ -356,7 +370,7 @@ chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
cv = g_new0 (chanview, 1); cv = g_new0 (chanview, 1);
cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER, cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER,
PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT); PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT);
cv->font_desc = font_desc; cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
cv->trunc_len = trunc_len; cv->trunc_len = trunc_len;
cv->sorted = sort; cv->sorted = sort;
+11 -9
View File
@@ -1357,17 +1357,19 @@ fe_open_url_inner (const char *url)
gboolean opened = FALSE; gboolean opened = FALSE;
#ifdef WIN32 #ifdef WIN32
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error); gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
if (url_utf16 != NULL)
{
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
g_free (url_utf16);
}
if (!opened) if (!opened)
{ {
g_clear_error (&error); opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL); if (!opened)
g_clear_error (&error);
if (url_utf16 != NULL)
{
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
g_free (url_utf16);
}
} }
#else #else
{ {
+13 -4
View File
@@ -3388,10 +3388,19 @@ mg_topicbar_update_height (GtkWidget *topic)
if (parent && GTK_IS_SCROLLED_WINDOW (parent)) if (parent && GTK_IS_SCROLLED_WINDOW (parent))
{ {
gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (parent), -1); GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW (parent);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (parent), -1); int max_height = gtk_scrolled_window_get_max_content_height (scrolled);
gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (parent), target_height);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (parent), target_height); if (max_height != -1 && target_height > max_height)
{
gtk_scrolled_window_set_max_content_height (scrolled, target_height);
gtk_scrolled_window_set_min_content_height (scrolled, target_height);
}
else
{
gtk_scrolled_window_set_min_content_height (scrolled, target_height);
gtk_scrolled_window_set_max_content_height (scrolled, target_height);
}
gtk_widget_set_size_request (parent, -1, target_height); gtk_widget_set_size_request (parent, -1, target_height);
} }
} }
+5
View File
@@ -1539,6 +1539,11 @@ tray_cleanup (void)
void void
tray_apply_setup (void) tray_apply_setup (void)
{ {
/* Theme/setup events can be dispatched before the tray plugin has
* been registered; there is nothing to (de)activate yet. */
if (!ph)
return;
if (tray_backend_active) if (tray_backend_active)
{ {
if (!prefs.hex_gui_tray) if (!prefs.hex_gui_tray)
@@ -21,6 +21,7 @@
#include "../../fe-gtk.h" #include "../../fe-gtk.h"
#include "../theme-application.h" #include "../theme-application.h"
#include "../theme-policy.h"
#include "../../maingui.h" #include "../../maingui.h"
#include "../../../common/zoitechat.h" #include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h" #include "../../../common/zoitechatc.h"
@@ -54,6 +55,19 @@ theme_css_reload_input_style (gboolean enabled, const PangoFontDescription *font
css_reload_calls++; css_reload_calls++;
} }
gboolean
theme_policy_system_prefers_dark (void)
{
return FALSE;
}
gboolean
theme_policy_is_dark_mode_active (unsigned int mode)
{
(void) mode;
return FALSE;
}
void void
theme_runtime_load (void) theme_runtime_load (void)
{ {
@@ -104,7 +118,9 @@ test_invalid_font_falls_back_to_sans_11 (void)
g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE); g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE);
g_assert_cmpint (message_calls, ==, 1); g_assert_cmpint (message_calls, ==, 1);
g_assert_cmpint (css_reload_calls, ==, 1); g_assert_cmpint (css_reload_calls, ==, 1);
g_assert_true (css_enabled); /* Input boxes are themed per-widget; the app-global input CSS stays
* off so entries render natively under the active GTK3 theme. */
g_assert_false (css_enabled);
g_assert_nonnull (css_font_desc); g_assert_nonnull (css_font_desc);
if (style->font_desc) if (style->font_desc)
@@ -22,6 +22,7 @@
#include "../theme-manager.h" #include "../theme-manager.h"
#include "../theme-gtk3.h" #include "../theme-gtk3.h"
#include "../theme-policy.h"
#include "../../../common/zoitechat.h" #include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h" #include "../../../common/zoitechatc.h"
@@ -84,6 +85,15 @@ gboolean theme_policy_system_prefers_dark (void)
return stub_system_prefers_dark; return stub_system_prefers_dark;
} }
void theme_policy_init (void)
{
}
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
{
(void) callback;
}
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed) gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
{ {
(void) mode; (void) mode;
@@ -203,6 +213,7 @@ reset_state (void)
idle_add_calls = 0; idle_add_calls = 0;
next_idle_source_id = 33; next_idle_source_id = 33;
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM; prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
prefs.hex_gui_gtk3_theme[0] = '\0';
test_theme_gtk3_stub_reset (); test_theme_gtk3_stub_reset ();
} }
@@ -235,12 +246,14 @@ test_auto_refresh_dispatches_mode_palette_and_style_reasons (void)
} }
static void static void
test_auto_refresh_ignores_non_auto_mode (void) test_auto_refresh_ignores_locked_in_app_theme (void)
{ {
guint listener_id; guint listener_id;
reset_state (); reset_state ();
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK; prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
g_strlcpy (prefs.hex_gui_gtk3_theme, "locked-theme", sizeof (prefs.hex_gui_gtk3_theme));
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
stub_apply_mode_palette_changed = TRUE; stub_apply_mode_palette_changed = TRUE;
listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL); listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL);
theme_manager_set_idle_add_func (immediate_idle_add); theme_manager_set_idle_add_func (immediate_idle_add);
@@ -254,6 +267,7 @@ test_auto_refresh_ignores_non_auto_mode (void)
theme_manager_set_idle_add_func (NULL); theme_manager_set_idle_add_func (NULL);
theme_listener_unregister (listener_id); theme_listener_unregister (listener_id);
prefs.hex_gui_gtk3_theme[0] = '\0';
} }
static void static void
@@ -263,6 +277,7 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
reset_state (); reset_state ();
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK; prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
g_strlcpy (prefs.hex_gui_gtk3_theme, "system-follower", sizeof (prefs.hex_gui_gtk3_theme));
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM; prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL); listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
theme_manager_set_idle_add_func (immediate_idle_add); theme_manager_set_idle_add_func (immediate_idle_add);
@@ -274,6 +289,39 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
g_assert_cmpint (listener_calls, ==, 0); g_assert_cmpint (listener_calls, ==, 0);
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1); g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
theme_manager_set_idle_add_func (NULL);
theme_listener_unregister (listener_id);
prefs.hex_gui_gtk3_theme[0] = '\0';
}
static void
test_auto_refresh_follows_system_theme_without_auto_mode (void)
{
guint listener_id;
reset_state ();
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
listener_id = theme_listener_register ("auto.system", auto_listener, NULL);
theme_manager_set_idle_add_func (immediate_idle_add);
theme_manager_refresh_auto_mode ();
/* No in-app theme is selected, so a system theme change must
* re-resolve the mapped palette even outside auto dark mode. */
g_assert_cmpint (idle_add_calls, ==, 1);
g_assert_cmpint (auto_state_calls, ==, 0);
g_assert_cmpint (listener_calls, ==, 1);
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_PALETTE));
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_WIDGET_STYLE));
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_USERLIST));
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_MODE));
/* Unchanged system state must not dispatch again. */
theme_manager_refresh_auto_mode ();
g_assert_cmpint (idle_add_calls, ==, 2);
g_assert_cmpint (listener_calls, ==, 1);
theme_manager_set_idle_add_func (NULL); theme_manager_set_idle_add_func (NULL);
theme_listener_unregister (listener_id); theme_listener_unregister (listener_id);
} }
@@ -284,9 +332,11 @@ main (int argc, char **argv)
g_test_init (&argc, &argv, NULL); g_test_init (&argc, &argv, NULL);
g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons", g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons",
test_auto_refresh_dispatches_mode_palette_and_style_reasons); test_auto_refresh_dispatches_mode_palette_and_style_reasons);
g_test_add_func ("/theme/manager/auto_refresh_ignores_non_auto_mode", g_test_add_func ("/theme/manager/auto_refresh_ignores_locked_in_app_theme",
test_auto_refresh_ignores_non_auto_mode); test_auto_refresh_ignores_locked_in_app_theme);
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant", g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
test_auto_refresh_reapplies_gtk3_for_follow_system_variant); test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
g_test_add_func ("/theme/manager/auto_refresh_follows_system_theme_without_auto_mode",
test_auto_refresh_follows_system_theme_without_auto_mode);
return g_test_run (); return g_test_run ();
} }
@@ -23,6 +23,7 @@
#include <string.h> #include <string.h>
#include "../theme-manager.h" #include "../theme-manager.h"
#include "../theme-policy.h"
#include "../../../common/zoitechat.h" #include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h" #include "../../../common/zoitechatc.h"
@@ -77,6 +78,15 @@ gboolean theme_policy_system_prefers_dark (void)
return FALSE; return FALSE;
} }
void theme_policy_init (void)
{
}
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
{
(void) callback;
}
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed) gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
{ {
(void) mode; (void) mode;
@@ -22,6 +22,7 @@
#include "../theme-palette.h" #include "../theme-palette.h"
#include "../theme-manager.h" #include "../theme-manager.h"
#include "../theme-policy.h"
#include "../../../common/zoitechat.h" #include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h" #include "../../../common/zoitechatc.h"
@@ -30,6 +31,7 @@ struct session *current_tab;
struct session *lastact_sess; struct session *lastact_sess;
struct zoitechatprefs prefs; struct zoitechatprefs prefs;
static gboolean gtk_available;
static gboolean stub_policy_dark; static gboolean stub_policy_dark;
static unsigned int stub_policy_mode; static unsigned int stub_policy_mode;
static gboolean stub_apply_mode_result; static gboolean stub_apply_mode_result;
@@ -79,7 +81,18 @@ void zoitechat_set_theme_post_apply_callback (zoitechat_theme_post_apply_callbac
gboolean theme_policy_is_dark_mode_active (unsigned int mode) gboolean theme_policy_is_dark_mode_active (unsigned int mode)
{ {
stub_policy_mode = mode; stub_policy_mode = mode;
return stub_policy_dark; if (mode == ZOITECHAT_DARK_MODE_AUTO)
return stub_policy_dark;
return fe_dark_mode_is_enabled_for (mode);
}
void theme_policy_init (void)
{
}
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
{
(void) callback;
} }
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed) gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
@@ -109,11 +122,12 @@ void theme_runtime_reset_mode_colors (gboolean dark_mode)
(void) dark_mode; (void) dark_mode;
} }
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active) gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed)
{ {
(void) mode; (void) mode;
(void) dark_active;
stub_apply_mode_calls++; stub_apply_mode_calls++;
if (palette_changed)
*palette_changed = TRUE;
return TRUE; return TRUE;
} }
@@ -249,10 +263,11 @@ test_manager_set_token_color_routes_by_mode (void)
GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 }; GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 };
gboolean palette_changed = FALSE; gboolean palette_changed = FALSE;
/* All edits target the single user palette; the mode argument is
* accepted for compatibility but no longer selects a dark palette. */
reset_manager_stubs (); reset_manager_stubs ();
stub_policy_dark = FALSE; stub_policy_dark = FALSE;
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed); theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed);
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_LIGHT);
g_assert_cmpint (stub_user_set_calls, ==, 1); g_assert_cmpint (stub_user_set_calls, ==, 1);
g_assert_cmpint (stub_dark_set_calls, ==, 0); g_assert_cmpint (stub_dark_set_calls, ==, 0);
g_assert_cmpint (stub_apply_mode_calls, ==, 1); g_assert_cmpint (stub_apply_mode_calls, ==, 1);
@@ -262,16 +277,14 @@ test_manager_set_token_color_routes_by_mode (void)
reset_manager_stubs (); reset_manager_stubs ();
stub_policy_dark = TRUE; stub_policy_dark = TRUE;
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed); theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed);
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_DARK); g_assert_cmpint (stub_user_set_calls, ==, 1);
g_assert_cmpint (stub_user_set_calls, ==, 0); g_assert_cmpint (stub_dark_set_calls, ==, 0);
g_assert_cmpint (stub_dark_set_calls, ==, 1);
reset_manager_stubs (); reset_manager_stubs ();
stub_policy_dark = TRUE; stub_policy_dark = TRUE;
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed); theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed);
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_AUTO); g_assert_cmpint (stub_user_set_calls, ==, 1);
g_assert_cmpint (stub_user_set_calls, ==, 0); g_assert_cmpint (stub_dark_set_calls, ==, 0);
g_assert_cmpint (stub_dark_set_calls, ==, 1);
} }
@@ -300,9 +313,9 @@ test_manager_set_token_color_routes_setup_indexes (void)
stub_policy_dark = TRUE; stub_policy_dark = TRUE;
palette_changed = FALSE; palette_changed = FALSE;
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed); theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed);
g_assert_cmpint (stub_dark_set_calls, ==, 1); g_assert_cmpint (stub_user_set_calls, ==, 1);
g_assert_cmpint (stub_last_dark_token, ==, def->token); g_assert_cmpint (stub_last_user_token, ==, def->token);
g_assert_cmpint (stub_user_set_calls, ==, 0); g_assert_cmpint (stub_dark_set_calls, ==, 0);
g_assert_true (palette_changed); g_assert_true (palette_changed);
} }
} }
@@ -345,6 +358,12 @@ test_manager_window_attach_detach_idempotence (void)
gulong *second_handler_ptr; gulong *second_handler_ptr;
gulong second_handler_id; gulong second_handler_id;
if (!gtk_available)
{
g_test_message ("GTK display not available");
return;
}
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_assert_nonnull (window); g_assert_nonnull (window);
@@ -395,5 +414,6 @@ main (int argc, char **argv)
test_manager_listener_registration_dispatch_and_unregister); test_manager_listener_registration_dispatch_and_unregister);
g_test_add_func ("/theme/manager/window_attach_detach_idempotence", g_test_add_func ("/theme/manager/window_attach_detach_idempotence",
test_manager_window_attach_detach_idempotence); test_manager_window_attach_detach_idempotence);
gtk_available = gtk_init_check (&argc, &argv);
return g_test_run (); return g_test_run ();
} }
@@ -300,7 +300,7 @@ theme_gtk3_is_active (void)
#include "../theme-preferences.c" #include "../theme-preferences.c"
static void static void
test_removed_selected_theme_commits_fallback_and_applies (void) test_removed_selected_theme_falls_back_to_system_default (void)
{ {
GtkWidget *page; GtkWidget *page;
theme_preferences_ui *ui; theme_preferences_ui *ui;
@@ -317,23 +317,26 @@ test_removed_selected_theme_commits_fallback_and_applies (void)
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme)); g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK; prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
removed_selected = FALSE; removed_selected = FALSE;
apply_current_calls = 0;
applied_theme_id[0] = '\0';
page = theme_preferences_create_page (NULL, &setup_prefs, NULL); page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui"); ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
g_assert_nonnull (ui); g_assert_nonnull (ui);
apply_current_calls = 0;
applied_theme_id[0] = '\0';
g_assert_nonnull (ui->gtk3_remove); g_assert_nonnull (ui->gtk3_remove);
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove)); gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme"); /* Removing the active in-app theme returns to the system GTK3
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "fallback-theme"); * theme ("None") instead of forcing another theme. */
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT); g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "");
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT); g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "");
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
g_assert_cmpint (apply_current_calls, ==, 1); g_assert_cmpint (apply_current_calls, ==, 1);
g_assert_cmpstr (applied_theme_id, ==, "fallback-theme"); g_assert_cmpstr (applied_theme_id, ==, "");
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT); g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
gtk_widget_destroy (page); gtk_widget_destroy (page);
} }
@@ -412,8 +415,8 @@ main (int argc, char **argv)
{ {
g_test_init (&argc, &argv, NULL); g_test_init (&argc, &argv, NULL);
gtk_available = gtk_init_check (&argc, &argv); gtk_available = gtk_init_check (&argc, &argv);
g_test_add_func ("/theme/preferences/gtk3_removed_selection_applies_fallback", g_test_add_func ("/theme/preferences/gtk3_removed_selection_falls_back_to_system",
test_removed_selected_theme_commits_fallback_and_applies); test_removed_selected_theme_falls_back_to_system_default);
g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default", g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default",
test_unset_theme_keeps_system_default_without_apply); test_unset_theme_keeps_system_default_without_apply);
g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme", g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme",
@@ -290,12 +290,12 @@ test_ui_edits_persist_without_legacy_array_mutation (void)
theme_runtime_save (); theme_runtime_save ();
theme_runtime_load (); theme_runtime_load ();
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL); theme_runtime_apply_dark_mode (FALSE);
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded)); g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded));
g_assert_true (gdk_rgba_parse (&light_expected, "#224466")); g_assert_true (gdk_rgba_parse (&light_expected, "#224466"));
g_assert_true (colors_equal (&light_loaded, &light_expected)); g_assert_true (colors_equal (&light_loaded, &light_expected));
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_DARK, NULL); theme_runtime_apply_dark_mode (TRUE);
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded)); g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded));
g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc")); g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc"));
g_assert_true (colors_equal (&dark_loaded, &dark_expected)); g_assert_true (colors_equal (&dark_loaded, &dark_expected));
+74 -18
View File
@@ -21,7 +21,6 @@
#include "theme-access.h" #include "theme-access.h"
#include "theme-runtime.h" #include "theme-runtime.h"
#include "theme-gtk3.h"
@@ -32,7 +31,8 @@ enum
THEME_XTEXT_MARK_BG_INDEX = 100, THEME_XTEXT_MARK_BG_INDEX = 100,
THEME_XTEXT_FG_INDEX = 101, THEME_XTEXT_FG_INDEX = 101,
THEME_XTEXT_BG_INDEX = 102, THEME_XTEXT_BG_INDEX = 102,
THEME_XTEXT_MARKER_INDEX = 103 THEME_XTEXT_MARKER_INDEX = 103,
THEME_XTEXT_MARKER_LEGACY_INDEX = 36
}; };
static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = { static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
@@ -52,14 +52,13 @@ static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
}; };
static void static void
theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len, gboolean apply_base) theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len)
{ {
size_t i; size_t i;
size_t start = apply_base ? 0 : 32;
if (palette_len == 0) if (palette_len == 0)
return; return;
for (i = start; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++) for (i = 32; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++)
{ {
palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0; palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0;
palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0; palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0;
@@ -112,14 +111,49 @@ theme_access_context_get_background_color (GtkStyleContext *context, GtkStateFla
gtk_style_context_restore (context); gtk_style_context_restore (context);
} }
static gboolean
theme_access_lookup_named_color (GtkStyleContext *context, const char *const *names, GdkRGBA *out_color)
{
size_t i;
for (i = 0; names[i] != NULL; i++)
{
if (gtk_style_context_lookup_color (context, names[i], out_color))
return TRUE;
}
return FALSE;
}
static void
theme_access_resolve_map_background (GtkStyleContext *context, GtkStateFlags state,
const char *const *named_fallbacks, GdkRGBA *out_color)
{
GdkRGBA named;
theme_access_context_get_background_color (context, state, out_color);
if (out_color->alpha > 0.0)
return;
/* Unstyled CSS nodes (e.g. the custom chat text widget) report a fully
* transparent background; fall back to the theme's named colors so the
* mapped palette still follows the active GTK3 theme. */
if (theme_access_lookup_named_color (context, named_fallbacks, &named))
*out_color = named;
}
static gboolean static gboolean
theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map) theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map)
{ {
static const char *const base_fallbacks[] = { "theme_base_color", "theme_bg_color", NULL };
static const char *const selected_bg_fallbacks[] = { "theme_selected_bg_color", NULL };
static const char *const selected_fg_fallbacks[] = { "theme_selected_fg_color", NULL };
GtkStyleContext *context; GtkStyleContext *context;
GdkRGBA named;
GdkRGBA accent; GdkRGBA accent;
g_return_val_if_fail (out_map != NULL, FALSE); g_return_val_if_fail (out_map != NULL, FALSE);
if (!theme_gtk3_is_active () || widget == NULL || !GTK_IS_WIDGET (widget)) if (widget == NULL || !GTK_IS_WIDGET (widget))
return FALSE; return FALSE;
context = gtk_widget_get_style_context (widget); context = gtk_widget_get_style_context (widget);
@@ -127,9 +161,14 @@ theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map
return FALSE; return FALSE;
theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground); theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground);
theme_access_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_background); theme_access_resolve_map_background (context, GTK_STATE_FLAG_NORMAL, base_fallbacks,
&out_map->text_background);
theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground); theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground);
theme_access_context_get_background_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_background); theme_access_resolve_map_background (context, GTK_STATE_FLAG_SELECTED, selected_bg_fallbacks,
&out_map->selection_background);
if (gdk_rgba_equal (&out_map->selection_foreground, &out_map->text_foreground) &&
theme_access_lookup_named_color (context, selected_fg_fallbacks, &named))
out_map->selection_foreground = named;
theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent); theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent);
if (accent.alpha <= 0.0) if (accent.alpha <= 0.0)
accent = out_map->selection_background; accent = out_map->selection_background;
@@ -148,11 +187,10 @@ gboolean
theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba) theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba)
{ {
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index); ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
gboolean has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
if (mirc_index >= THEME_XTEXT_MIRC_COLS) if (mirc_index >= THEME_XTEXT_MIRC_COLS)
return FALSE; return FALSE;
if (!has_user_colors || mirc_index >= 32) if (mirc_index >= 32)
{ {
out_rgba->red = theme_default_99_mirc_colors[mirc_index][0] / 255.0; out_rgba->red = theme_default_99_mirc_colors[mirc_index][0] / 255.0;
out_rgba->green = theme_default_99_mirc_colors[mirc_index][1] / 255.0; out_rgba->green = theme_default_99_mirc_colors[mirc_index][1] / 255.0;
@@ -173,11 +211,10 @@ gboolean
theme_get_mirc_color_rgb16 (unsigned int mirc_index, guint16 *red, guint16 *green, guint16 *blue) theme_get_mirc_color_rgb16 (unsigned int mirc_index, guint16 *red, guint16 *green, guint16 *blue)
{ {
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index); ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
gboolean has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
if (mirc_index >= THEME_XTEXT_MIRC_COLS) if (mirc_index >= THEME_XTEXT_MIRC_COLS)
return FALSE; return FALSE;
if (!has_user_colors || mirc_index >= 32) if (mirc_index >= 32)
{ {
*red = (guint16) (theme_default_99_mirc_colors[mirc_index][0] * 257); *red = (guint16) (theme_default_99_mirc_colors[mirc_index][0] * 257);
*green = (guint16) (theme_default_99_mirc_colors[mirc_index][1] * 257); *green = (guint16) (theme_default_99_mirc_colors[mirc_index][1] * 257);
@@ -226,18 +263,37 @@ theme_get_xtext_colors (XTextColor *palette, size_t palette_len)
void void
theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len) theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len)
{ {
ThemeGtkPaletteMap gtk_map = { 0 };
ThemeWidgetStyleValues style_values; ThemeWidgetStyleValues style_values;
gboolean has_user_colors;
GdkRGBA marker_color; GdkRGBA marker_color;
gboolean have_marker = FALSE;
if (!palette) if (!palette)
return; return;
theme_get_widget_style_values_for_widget (widget, &style_values); if (theme_access_get_gtk_palette_map (widget, &gtk_map))
theme_runtime_get_xtext_colors (palette, palette_len); {
has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ()); theme_runtime_get_widget_style_values_mapped (&gtk_map, &style_values);
theme_runtime_get_xtext_colors_mapped (&gtk_map, palette, palette_len);
}
else
{
theme_runtime_get_widget_style_values (&style_values);
theme_runtime_get_xtext_colors (palette, palette_len);
}
if (palette_len > THEME_XTEXT_MARKER_LEGACY_INDEX)
{
/* The marker token lives at its legacy slot before the extended
* mIRC palette overwrites it; keep the (possibly theme-mapped)
* value for the dedicated marker index below. */
marker_color.red = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].red;
marker_color.green = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].green;
marker_color.blue = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].blue;
marker_color.alpha = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].alpha;
have_marker = TRUE;
}
if (palette_len >= THEME_XTEXT_MIRC_COLS) if (palette_len >= THEME_XTEXT_MIRC_COLS)
theme_access_apply_default_99_palette (palette, palette_len, !has_user_colors); theme_access_apply_default_99_palette (palette, palette_len);
if (palette_len > THEME_XTEXT_MARK_FG_INDEX) if (palette_len > THEME_XTEXT_MARK_FG_INDEX)
{ {
palette[THEME_XTEXT_MARK_FG_INDEX].red = style_values.selection_foreground.red; palette[THEME_XTEXT_MARK_FG_INDEX].red = style_values.selection_foreground.red;
@@ -254,7 +310,7 @@ theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_
} }
if (palette_len > THEME_XTEXT_MARKER_INDEX) if (palette_len > THEME_XTEXT_MARKER_INDEX)
{ {
if (!theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color)) if (!have_marker && !theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color))
marker_color = style_values.selection_background; marker_color = style_values.selection_background;
palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red; palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red;
palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green; palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green;
+11
View File
@@ -25,6 +25,7 @@
#include "theme-css.h" #include "theme-css.h"
#include "theme-runtime.h" #include "theme-runtime.h"
#include "theme-gtk3.h" #include "theme-gtk3.h"
#include "theme-policy.h"
#include "../maingui.h" #include "../maingui.h"
#include <gtk/gtk.h> #include <gtk/gtk.h>
@@ -59,6 +60,16 @@ theme_application_apply_toplevel_theme (gboolean dark)
prefer_dark = TRUE; prefer_dark = TRUE;
else if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_LIGHT) else if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_LIGHT)
prefer_dark = FALSE; prefer_dark = FALSE;
else
prefer_dark = theme_policy_system_prefers_dark ();
}
else
{
/* Following the system GTK3 theme: only request the dark
* variant when the dark-mode preference (or the system, in
* auto mode) asks for it, instead of clobbering the value
* that came from the user's GTK configuration. */
prefer_dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
} }
if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings), if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
+5 -59
View File
@@ -29,8 +29,6 @@
static const char *theme_css_selector_input = "#zoitechat-inputbox"; static const char *theme_css_selector_input = "#zoitechat-inputbox";
static const char *theme_css_selector_input_text = "#zoitechat-inputbox text"; static const char *theme_css_selector_input_text = "#zoitechat-inputbox text";
static const char *theme_css_selector_palette_class = "zoitechat-palette"; static const char *theme_css_selector_palette_class = "zoitechat-palette";
static const char *theme_css_selector_dark_class = "zoitechat-dark";
static const char *theme_css_selector_light_class = "zoitechat-light";
static const char *theme_css_palette_provider_key = "zoitechat-palette-provider"; static const char *theme_css_palette_provider_key = "zoitechat-palette-provider";
static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER; static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER;
static const char *theme_css_palette_scope_selectors = static const char *theme_css_palette_scope_selectors =
@@ -362,61 +360,9 @@ theme_css_apply_palette_widget (GtkWidget *widget, const GdkRGBA *bg, const GdkR
char * char *
theme_css_build_toplevel_classes (void) theme_css_build_toplevel_classes (void)
{ {
return g_strdup_printf ( /* Windows, dialogs and menus must render with the colors of the active
"window.%s, window.%s:backdrop, .%s {" * GTK3 theme (system or in-app selected). zoitechat-dark and
"background-color: #202020;" * zoitechat-light are marker classes only; painting fixed colors here
"color: #f0f0f0;" * overrides every theme (issue #334). */
"border-color: #202020;" return g_strdup ("");
"}"
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
"background-color: @theme_bg_color;"
"background-image: none;"
"color: @theme_fg_color;"
"border-color: @theme_bg_color;"
"}"
"window.%s menuitem:hover, window.%s menuitem:selected {"
"background-color: @theme_selected_bg_color;"
"background-image: none;"
"color: @theme_selected_fg_color;"
"border-color: @theme_selected_bg_color;"
"}"
"window.%s, window.%s:backdrop, .%s {"
"background-color: #f6f6f6;"
"color: #101010;"
"border-color: #f6f6f6;"
"}"
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
"background-color: @theme_bg_color;"
"background-image: none;"
"color: @theme_fg_color;"
"border-color: @theme_bg_color;"
"}"
"window.%s menuitem:hover, window.%s menuitem:selected {"
"background-color: @theme_selected_bg_color;"
"background-image: none;"
"color: @theme_selected_fg_color;"
"border-color: @theme_selected_bg_color;"
"}",
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_dark_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class,
theme_css_selector_light_class);
} }
+93 -15
View File
@@ -58,10 +58,52 @@ typedef struct
gboolean resolved_dark_preference; gboolean resolved_dark_preference;
char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme]; char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme];
int gtk3_variant; int gtk3_variant;
char *gtk_theme_name;
} ThemeManagerAutoRefreshCache; } ThemeManagerAutoRefreshCache;
static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache; static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache;
static void
theme_manager_auto_refresh_cache_reset (void)
{
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
memset (&theme_manager_auto_refresh_cache, 0, sizeof (theme_manager_auto_refresh_cache));
}
static void
theme_manager_auto_refresh_cache_store (gboolean resolved_dark_preference, char *gtk_theme_name)
{
theme_manager_auto_refresh_cache.initialized = TRUE;
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
prefs.hex_gui_gtk3_theme,
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
theme_manager_auto_refresh_cache.gtk_theme_name = gtk_theme_name;
}
static char *
theme_manager_dup_gtk_theme_name (void)
{
GtkSettings *settings = gtk_settings_get_default ();
char *gtk_theme_name = NULL;
if (settings)
g_object_get (settings, "gtk-theme-name", &gtk_theme_name, NULL);
return gtk_theme_name;
}
/* Record the state the application started up with so that the first queued
* auto refresh only reacts to real changes; startup already applied the
* current theme, and consumers such as the tray are not ready yet. */
static void
theme_manager_auto_refresh_cache_prime (void)
{
theme_manager_auto_refresh_cache_store (theme_policy_system_prefers_dark (),
theme_manager_dup_gtk_theme_name ());
}
static void theme_manager_apply_platform_window_theme (GtkWidget *window); static void theme_manager_apply_platform_window_theme (GtkWidget *window);
static void static void
@@ -145,7 +187,9 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
gboolean color_change = FALSE; gboolean color_change = FALSE;
gboolean should_refresh_gtk3; gboolean should_refresh_gtk3;
gboolean gtk3_refresh; gboolean gtk3_refresh;
gboolean follows_system_theme;
gboolean resolved_dark_preference; gboolean resolved_dark_preference;
char *gtk_theme_name = NULL;
static gboolean in_handler = FALSE; static gboolean in_handler = FALSE;
(void) settings; (void) settings;
@@ -154,39 +198,47 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
resolved_dark_preference = theme_policy_system_prefers_dark (); resolved_dark_preference = theme_policy_system_prefers_dark ();
gtk3_refresh = theme_manager_should_refresh_gtk3 (); gtk3_refresh = theme_manager_should_refresh_gtk3 ();
follows_system_theme = prefs.hex_gui_gtk3_theme[0] == '\0';
should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO; should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO;
gtk_theme_name = theme_manager_dup_gtk_theme_name ();
if (theme_manager_auto_refresh_cache.initialized && if (theme_manager_auto_refresh_cache.initialized &&
theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference && theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference &&
theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant && theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant &&
g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0) g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0 &&
g_strcmp0 (theme_manager_auto_refresh_cache.gtk_theme_name, gtk_theme_name) == 0)
{
g_free (gtk_theme_name);
return; return;
}
theme_manager_auto_refresh_cache.initialized = TRUE; theme_manager_auto_refresh_cache_store (resolved_dark_preference, gtk_theme_name);
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
prefs.hex_gui_gtk3_theme,
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh) if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh && !follows_system_theme)
return; return;
if (in_handler) if (in_handler)
return; return;
in_handler = TRUE; in_handler = TRUE;
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO) if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
{ {
fe_set_auto_dark_mode_state (resolved_dark_preference);
theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change); theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change);
if (color_change) if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO)
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE); fe_set_auto_dark_mode_state (resolved_dark_preference);
} }
if (should_refresh_gtk3) if (should_refresh_gtk3)
theme_gtk3_apply_current (NULL); theme_gtk3_apply_current (NULL);
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
{
/* The system theme feeds the mapped palette (chat area, user
* list, channel tree), so consumers must re-resolve colors. */
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE);
}
in_handler = FALSE; in_handler = FALSE;
} }
@@ -204,18 +256,28 @@ theme_manager_run_auto_refresh (gpointer data)
static void static void
theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data) theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data)
{ {
guint source_id;
(void) settings; (void) settings;
(void) pspec; (void) pspec;
if (theme_manager_auto_refresh_source != 0) if (theme_manager_auto_refresh_source != 0)
return; return;
theme_manager_auto_refresh_source = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data); theme_manager_auto_refresh_source = 1;
source_id = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data);
/* An injected idle-add may run the refresh synchronously and already
* have cleared the pending marker; only keep the id while pending. */
if (theme_manager_auto_refresh_source != 0)
theme_manager_auto_refresh_source = source_id;
} }
void void
theme_manager_init (void) theme_manager_init (void)
{ {
static gboolean settings_signals_connected = FALSE;
GtkSettings *settings;
if (!theme_manager_listeners) if (!theme_manager_listeners)
theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
theme_listener_free); theme_listener_free);
@@ -223,10 +285,25 @@ theme_manager_init (void)
if (!theme_manager_setup_listener_id) if (!theme_manager_setup_listener_id)
theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL); theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL);
fe_set_auto_dark_mode_state (FALSE); theme_policy_init ();
theme_policy_set_appearance_changed_callback (theme_manager_refresh_auto_mode);
settings = gtk_settings_get_default ();
if (settings && !settings_signals_connected)
{
g_signal_connect (settings, "notify::gtk-theme-name",
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
g_signal_connect (settings, "notify::gtk-application-prefer-dark-theme",
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
settings_signals_connected = TRUE;
}
fe_set_auto_dark_mode_state (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO ?
theme_policy_system_prefers_dark () : FALSE);
theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL); theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL);
theme_gtk3_init (); theme_gtk3_init ();
zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied); zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied);
theme_manager_auto_refresh_cache_prime ();
} }
gboolean gboolean
@@ -519,7 +596,7 @@ theme_manager_apply_platform_window_theme (GtkWidget *window)
dark = theme_policy_system_prefers_dark (); dark = theme_policy_system_prefers_dark ();
} }
else else
dark = theme_runtime_is_dark_active (); dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
if (context) if (context)
{ {
gtk_style_context_remove_class (context, "zoitechat-dark"); gtk_style_context_remove_class (context, "zoitechat-dark");
@@ -712,4 +789,5 @@ theme_manager_set_idle_add_func (ThemeManagerIdleAddFunc idle_add_func)
{ {
theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add; theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add;
theme_manager_auto_refresh_source = 0; theme_manager_auto_refresh_source = 0;
theme_manager_auto_refresh_cache_reset ();
} }
+366 -1
View File
@@ -20,14 +20,379 @@
#include "theme-policy.h" #include "theme-policy.h"
#include <string.h>
#include <gtk/gtk.h>
#include <gio/gio.h>
#include "theme-gtk3.h"
#include "../fe-gtk.h" #include "../fe-gtk.h"
#include "../../common/zoitechat.h" #include "../../common/zoitechat.h"
#include "../../common/zoitechatc.h" #include "../../common/zoitechatc.h"
/* org.freedesktop.appearance color-scheme values (XDG desktop portal) */
enum
{
THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE = 0,
THEME_POLICY_COLOR_SCHEME_PREFER_DARK = 1,
THEME_POLICY_COLOR_SCHEME_PREFER_LIGHT = 2
};
static ThemePolicyAppearanceChangedFunc policy_appearance_changed_cb;
/* GtkSettings state captured before ZoiteChat writes to it, so that values
* coming from the user's own GTK configuration are not mistaken for values
* this application set. */
static gboolean policy_initial_gtk_state_captured;
static gboolean policy_initial_prefer_dark;
static char *policy_initial_theme_name;
#ifndef G_OS_WIN32
static gboolean policy_portal_watch_started;
static gboolean policy_portal_value_valid;
static guint32 policy_portal_color_scheme;
static gboolean policy_gsettings_checked;
static GSettings *policy_gsettings_interface;
#endif
static void
policy_capture_initial_gtk_state (void)
{
GtkSettings *settings;
if (policy_initial_gtk_state_captured)
return;
settings = gtk_settings_get_default ();
if (!settings)
return;
g_object_get (settings,
"gtk-application-prefer-dark-theme", &policy_initial_prefer_dark,
"gtk-theme-name", &policy_initial_theme_name,
NULL);
policy_initial_gtk_state_captured = TRUE;
}
static gboolean
policy_theme_name_is_dark (const char *name)
{
char *lower;
gboolean dark;
if (!name || !name[0])
return FALSE;
lower = g_ascii_strdown (name, -1);
dark = strstr (lower, "dark") != NULL || strstr (lower, "noir") != NULL ||
strstr (lower, "black") != NULL;
g_free (lower);
return dark;
}
#ifndef G_OS_WIN32
static void
policy_notify_appearance_changed (void)
{
if (policy_appearance_changed_cb)
policy_appearance_changed_cb ();
}
static gboolean
policy_color_scheme_from_variant (GVariant *value, guint32 *color_scheme)
{
GVariant *inner;
gboolean ok = FALSE;
if (!value || !color_scheme)
return FALSE;
inner = g_variant_ref (value);
while (g_variant_is_of_type (inner, G_VARIANT_TYPE_VARIANT))
{
GVariant *unwrapped = g_variant_get_variant (inner);
g_variant_unref (inner);
inner = unwrapped;
}
if (g_variant_is_of_type (inner, G_VARIANT_TYPE_UINT32))
{
*color_scheme = g_variant_get_uint32 (inner);
ok = TRUE;
}
g_variant_unref (inner);
return ok;
}
static void
policy_portal_store_color_scheme (guint32 color_scheme)
{
gboolean changed;
changed = !policy_portal_value_valid || policy_portal_color_scheme != color_scheme;
policy_portal_value_valid = TRUE;
policy_portal_color_scheme = color_scheme;
if (changed)
policy_notify_appearance_changed ();
}
static void
policy_portal_setting_changed_cb (GDBusConnection *connection, const char *sender_name,
const char *object_path, const char *interface_name,
const char *signal_name, GVariant *parameters,
gpointer user_data)
{
const char *setting_namespace = NULL;
const char *setting_key = NULL;
GVariant *value = NULL;
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
(void) connection;
(void) sender_name;
(void) object_path;
(void) interface_name;
(void) signal_name;
(void) user_data;
if (!parameters || !g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssv)")))
return;
g_variant_get (parameters, "(&s&sv)", &setting_namespace, &setting_key, &value);
if (g_strcmp0 (setting_namespace, "org.freedesktop.appearance") == 0 &&
g_strcmp0 (setting_key, "color-scheme") == 0 &&
policy_color_scheme_from_variant (value, &color_scheme))
policy_portal_store_color_scheme (color_scheme);
if (value)
g_variant_unref (value);
}
static void
policy_portal_read_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
GVariant *reply;
GVariant *value = NULL;
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
(void) user_data;
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, NULL);
if (!reply)
return;
if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(v)")))
{
g_variant_get (reply, "(v)", &value);
if (policy_color_scheme_from_variant (value, &color_scheme))
policy_portal_store_color_scheme (color_scheme);
if (value)
g_variant_unref (value);
}
g_variant_unref (reply);
}
static void
policy_portal_bus_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
GDBusConnection *connection;
(void) source;
(void) user_data;
connection = g_bus_get_finish (result, NULL);
if (!connection)
return;
g_dbus_connection_signal_subscribe (connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Settings",
"SettingChanged",
"/org/freedesktop/portal/desktop",
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
policy_portal_setting_changed_cb,
NULL,
NULL);
g_dbus_connection_call (connection,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read",
g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
G_VARIANT_TYPE ("(v)"),
G_DBUS_CALL_FLAGS_NONE,
3000,
NULL,
policy_portal_read_ready_cb,
NULL);
g_object_unref (connection);
}
static void
policy_portal_watch_start (void)
{
if (policy_portal_watch_started)
return;
policy_portal_watch_started = TRUE;
g_bus_get (G_BUS_TYPE_SESSION, NULL, policy_portal_bus_ready_cb, NULL);
}
static void
policy_gsettings_changed_cb (GSettings *settings, const char *key, gpointer user_data)
{
(void) settings;
(void) key;
(void) user_data;
policy_notify_appearance_changed ();
}
static GSettings *
policy_get_interface_gsettings (void)
{
GSettingsSchemaSource *source;
GSettingsSchema *schema;
gboolean has_key;
if (policy_gsettings_checked)
return policy_gsettings_interface;
policy_gsettings_checked = TRUE;
source = g_settings_schema_source_get_default ();
if (!source)
return NULL;
schema = g_settings_schema_source_lookup (source, "org.gnome.desktop.interface", TRUE);
if (!schema)
return NULL;
/* Requires GLib 2.40, always satisfied by the GTK 3.22 dependency. */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
has_key = g_settings_schema_has_key (schema, "color-scheme");
G_GNUC_END_IGNORE_DEPRECATIONS
g_settings_schema_unref (schema);
if (!has_key)
return NULL;
policy_gsettings_interface = g_settings_new ("org.gnome.desktop.interface");
g_signal_connect (policy_gsettings_interface, "changed::color-scheme",
G_CALLBACK (policy_gsettings_changed_cb), NULL);
return policy_gsettings_interface;
}
static gboolean
policy_gsettings_color_scheme (gboolean *prefers_dark)
{
GSettings *settings;
char *scheme;
gboolean explicit_preference = FALSE;
settings = policy_get_interface_gsettings ();
if (!settings)
return FALSE;
scheme = g_settings_get_string (settings, "color-scheme");
if (g_strcmp0 (scheme, "prefer-dark") == 0)
{
*prefers_dark = TRUE;
explicit_preference = TRUE;
}
else if (g_strcmp0 (scheme, "prefer-light") == 0)
{
*prefers_dark = FALSE;
explicit_preference = TRUE;
}
g_free (scheme);
return explicit_preference;
}
static gboolean
policy_gtk_heuristic_prefers_dark (void)
{
GtkSettings *settings;
char *theme_name = NULL;
gboolean dark;
policy_capture_initial_gtk_state ();
/* gtk-application-prefer-dark-theme is overwritten by ZoiteChat at
* runtime, so only its captured startup value (settings.ini, XSETTINGS)
* reflects the user's own configuration. */
if (policy_initial_gtk_state_captured && policy_initial_prefer_dark)
return TRUE;
if (theme_gtk3_is_active ())
{
/* gtk-theme-name currently holds the in-app theme override;
* fall back to the captured system theme name. */
theme_name = g_strdup (policy_initial_theme_name);
}
else
{
settings = gtk_settings_get_default ();
if (settings)
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
}
dark = policy_theme_name_is_dark (theme_name);
g_free (theme_name);
return dark;
}
#endif /* !G_OS_WIN32 */
void
theme_policy_init (void)
{
policy_capture_initial_gtk_state ();
#ifndef G_OS_WIN32
policy_portal_watch_start ();
#endif
}
void
theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
{
policy_appearance_changed_cb = callback;
}
gboolean gboolean
theme_policy_system_prefers_dark (void) theme_policy_system_prefers_dark (void)
{ {
return FALSE; #ifdef G_OS_WIN32
gboolean prefer_dark = FALSE;
policy_capture_initial_gtk_state ();
if (fe_win32_high_contrast_is_enabled ())
return FALSE;
if (fe_win32_try_get_system_dark (&prefer_dark))
return prefer_dark;
return policy_theme_name_is_dark (policy_initial_theme_name);
#else
gboolean prefers_dark = FALSE;
policy_capture_initial_gtk_state ();
policy_portal_watch_start ();
if (policy_portal_value_valid &&
policy_portal_color_scheme != THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE)
return policy_portal_color_scheme == THEME_POLICY_COLOR_SCHEME_PREFER_DARK;
if (policy_gsettings_color_scheme (&prefers_dark))
return prefers_dark;
return policy_gtk_heuristic_prefers_dark ();
#endif
} }
gboolean gboolean
+4
View File
@@ -23,6 +23,10 @@
#include "../fe-gtk.h" #include "../fe-gtk.h"
typedef void (*ThemePolicyAppearanceChangedFunc) (void);
void theme_policy_init (void);
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback);
gboolean theme_policy_system_prefers_dark (void); gboolean theme_policy_system_prefers_dark (void);
gboolean theme_policy_is_dark_mode_active (unsigned int mode); gboolean theme_policy_is_dark_mode_active (unsigned int mode);
gboolean theme_policy_is_app_dark_mode_active (void); gboolean theme_policy_is_app_dark_mode_active (void);
+44 -3
View File
@@ -100,6 +100,9 @@ typedef struct
gboolean changed; gboolean changed;
gboolean snapshot_valid[THEME_TOKEN_COUNT]; gboolean snapshot_valid[THEME_TOKEN_COUNT];
gboolean staged_valid[THEME_TOKEN_COUNT]; gboolean staged_valid[THEME_TOKEN_COUNT];
/* Only explicitly edited tokens are written back to the runtime
* palette; untouched tokens keep following the GTK3 theme. */
gboolean staged_dirty[THEME_TOKEN_COUNT];
GdkRGBA snapshot[THEME_TOKEN_COUNT]; GdkRGBA snapshot[THEME_TOKEN_COUNT];
GdkRGBA staged[THEME_TOKEN_COUNT]; GdkRGBA staged[THEME_TOKEN_COUNT];
} theme_preferences_stage_state; } theme_preferences_stage_state;
@@ -146,7 +149,8 @@ theme_preferences_stage_sync_runtime_to_snapshot (void)
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
{ {
if (theme_preferences_stage.snapshot_valid[token]) if (theme_preferences_stage.snapshot_valid[token] &&
theme_preferences_stage.staged_dirty[token])
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
&theme_preferences_stage.snapshot[token], NULL); &theme_preferences_stage.snapshot[token], NULL);
} }
@@ -159,7 +163,8 @@ theme_preferences_stage_sync_runtime_to_staged (void)
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
{ {
if (theme_preferences_stage.staged_valid[token]) if (theme_preferences_stage.staged_valid[token] &&
theme_preferences_stage.staged_dirty[token])
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
&theme_preferences_stage.staged[token], NULL); &theme_preferences_stage.staged[token], NULL);
} }
@@ -178,6 +183,7 @@ theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgb
{ {
theme_preferences_stage.staged[token] = *rgba; theme_preferences_stage.staged[token] = *rgba;
theme_preferences_stage.staged_valid[token] = TRUE; theme_preferences_stage.staged_valid[token] = TRUE;
theme_preferences_stage.staged_dirty[token] = TRUE;
theme_preferences_stage_recompute_changed (); theme_preferences_stage_recompute_changed ();
if (color_change_flag) if (color_change_flag)
*color_change_flag = theme_preferences_stage.changed; *color_change_flag = theme_preferences_stage.changed;
@@ -728,6 +734,10 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
ThemeSemanticToken token; ThemeSemanticToken token;
ThemeWidgetStyleValues style_values; ThemeWidgetStyleValues style_values;
/* The runtime palette was just reset; drop any
* pending edits so the reset is not re-pinned. */
memset (theme_preferences_stage.staged_dirty, 0,
sizeof (theme_preferences_stage.staged_dirty));
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
{ {
GdkRGBA rgba; GdkRGBA rgba;
@@ -742,7 +752,6 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE; theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE;
theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background; theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background;
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE; theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE;
theme_preferences_stage_sync_runtime_to_staged ();
theme_preferences_stage_recompute_changed (); theme_preferences_stage_recompute_changed ();
if (ui->color_change_flag) if (ui->color_change_flag)
*ui->color_change_flag = theme_preferences_stage.changed; *ui->color_change_flag = theme_preferences_stage.changed;
@@ -1418,11 +1427,31 @@ theme_preferences_gtk3_sync_remove_state (theme_preferences_ui *ui)
gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER); gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER);
} }
static void
theme_preferences_stage_rebase_color (ThemeSemanticToken token, const GdkRGBA *rgba)
{
if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba)
return;
if (!theme_preferences_stage.active)
return;
/* The theme changed underneath the dialog: shift the baseline so the
* new theme-provided value is neither treated as a user edit nor
* pinned into the runtime palette. */
theme_preferences_stage.snapshot[token] = *rgba;
theme_preferences_stage.staged[token] = *rgba;
theme_preferences_stage.snapshot_valid[token] = TRUE;
theme_preferences_stage.staged_valid[token] = TRUE;
theme_preferences_stage.staged_dirty[token] = FALSE;
theme_preferences_stage_recompute_changed ();
}
static void static void
theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui) theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
{ {
ThemeWidgetStyleValues style_values; ThemeWidgetStyleValues style_values;
GtkWidget *style_source = NULL; GtkWidget *style_source = NULL;
gboolean follows_system_theme = prefs.hex_gui_gtk3_theme[0] == '\0';
if (ui && ui->parent) if (ui && ui->parent)
style_source = GTK_WIDGET (ui->parent); style_source = GTK_WIDGET (ui->parent);
@@ -1433,6 +1462,18 @@ theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
theme_get_widget_style_values_for_widget (style_source, &style_values); theme_get_widget_style_values_for_widget (style_source, &style_values);
if (follows_system_theme)
{
/* Following the system GTK3 theme: leave the mapped tokens
* unpinned so they keep tracking the theme, and only refresh
* what the dialog shows. */
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_FOREGROUND, &style_values.foreground);
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_BACKGROUND, &style_values.background);
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_FOREGROUND, &style_values.selection_foreground);
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_BACKGROUND, &style_values.selection_background);
return;
}
theme_preferences_staged_set_color (THEME_TOKEN_TEXT_FOREGROUND, theme_preferences_staged_set_color (THEME_TOKEN_TEXT_FOREGROUND,
&style_values.foreground, &style_values.foreground,
NULL, NULL,
+6 -4
View File
@@ -286,10 +286,12 @@ xtext_adj_get_page_increment (GtkAdjustment *adj)
static void static void
xtext_adjustment_apply (GtkAdjustment *adj, gdouble lower, gdouble upper, gdouble value, gdouble page_size) xtext_adjustment_apply (GtkAdjustment *adj, gdouble lower, gdouble upper, gdouble value, gdouble page_size)
{ {
gtk_adjustment_set_lower (adj, lower); gdouble step_increment = page_size * prefs.hex_gui_mouse_scroll_speed / 100.0;
gtk_adjustment_set_upper (adj, upper);
gtk_adjustment_set_page_size (adj, page_size); if (step_increment < 1)
gtk_adjustment_set_value (adj, value); step_increment = 1;
gtk_adjustment_configure (adj, value, lower, upper, step_increment, page_size, page_size);
} }
static inline void static inline void