mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-09 13:29:24 +00:00
Compare commits
62 Commits
62672ade04
...
gtk3-consi
| Author | SHA1 | Date | |
|---|---|---|---|
| a767e8eacc | |||
|
|
8466e70424 | ||
| b85229f1a1 | |||
|
|
d03298a048 | ||
|
|
798e0b487e | ||
| 49444d72b4 | |||
|
|
c0e0fe4dcb | ||
|
|
9a189848ea | ||
| 96b09bfc1d | |||
| 9a6c913d89 | |||
| 42010594be | |||
| 5469c30b75 | |||
| fe9a2ee202 | |||
|
|
5e27dc7e9f | ||
|
|
69d083e3c0 | ||
| 2b95f9724e | |||
| 4066a07892 | |||
|
|
443ab58f65 | ||
| e0116acbe4 | |||
|
|
91e9bf0207 | ||
| aff0335f7e | |||
|
|
4d8baaa19b | ||
| 65452d80f1 | |||
|
|
df4d432151 | ||
|
|
1390f2c783 | ||
| be99d0e900 | |||
| 97fcf1d5fc | |||
| c9b14d7c45 | |||
| 3d0dda0347 | |||
|
|
8d69b35d67 | ||
| 7ba142a9f2 | |||
|
|
56ac55806d | ||
| 47f53c14db | |||
| a47d40b914 | |||
| be33ae30bb | |||
| 6d5f1306bf | |||
| ad3dc716e7 | |||
|
|
089b125a89 | ||
| dfd4554a11 | |||
| 1b98297881 | |||
|
|
cacc8fcafe | ||
| ac07d39db8 | |||
|
|
e27aa77cec | ||
| cbc9f68dd6 | |||
| 944d0f5a70 | |||
| 9ded7ef830 | |||
| adb18b7ce3 | |||
|
|
8220e60257 | ||
| b09af655e7 | |||
| 5833f8a3d8 | |||
|
|
72132d5e88 | ||
|
|
4167c4db78 | ||
| 3e1f6b9137 | |||
|
|
bb08b19c0d | ||
|
|
7497919e8f | ||
|
|
41cbe7b31c | ||
| 425e951341 | |||
| 2e4a0b92fc | |||
| 9f839579e2 | |||
| 900066b9d4 | |||
| 97534b0670 | |||
| d1707d3c72 |
132
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
132
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a problem with ZoiteChat
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------------------------------------------------------+
|
||||||
|
| ZoiteChat Bug Report |
|
||||||
|
| Please fill this out so the bug goblin has enough crumbs. |
|
||||||
|
+------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
What went wrong?
|
||||||
|
|
||||||
|
```text
|
||||||
|
Describe the bug clearly and briefly.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
```text
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Result
|
||||||
|
|
||||||
|
```text
|
||||||
|
What should have happened?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Actual Result
|
||||||
|
|
||||||
|
```text
|
||||||
|
What happened instead?
|
||||||
|
```
|
||||||
|
|
||||||
|
## ZoiteChat Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
ZoiteChat version:
|
||||||
|
Build/source: GitHub release / Fedora package / source build / Windows installer / other
|
||||||
|
Commit hash, if built from source:
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Information
|
||||||
|
|
||||||
|
```text
|
||||||
|
OS:
|
||||||
|
OS version:
|
||||||
|
Desktop environment:
|
||||||
|
Display server: Wayland / X11 / Windows / other
|
||||||
|
CPU architecture: x86_64 / aarch64 / other
|
||||||
|
```
|
||||||
|
|
||||||
|
## IRC / Network Details
|
||||||
|
|
||||||
|
```text
|
||||||
|
IRC network/server:
|
||||||
|
Port:
|
||||||
|
TLS/SSL enabled: yes / no
|
||||||
|
SASL enabled: yes / no
|
||||||
|
Using bouncer/ZNC: yes / no
|
||||||
|
Proxy/VPN involved: yes / no
|
||||||
|
IRCv3 capabilities involved, if known:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugins, Scripts, and Config
|
||||||
|
|
||||||
|
```text
|
||||||
|
Plugins enabled:
|
||||||
|
Scripts loaded:
|
||||||
|
Theme/custom CSS:
|
||||||
|
Custom config changes:
|
||||||
|
Does the bug still happen with a clean config? yes / no / untested
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs / Terminal Output
|
||||||
|
|
||||||
|
Paste relevant output below.
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Raw IRC Lines, If Relevant
|
||||||
|
|
||||||
|
Remove passwords, tokens, IPs, private channels, private messages, certs, and account details before posting.
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Crash Details, If Relevant
|
||||||
|
|
||||||
|
```text
|
||||||
|
Did ZoiteChat crash? yes / no
|
||||||
|
Crash reporter output:
|
||||||
|
Backtrace:
|
||||||
|
Core dump available: yes / no
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
Attach screenshots or screen recordings if the issue is visual.
|
||||||
|
|
||||||
|
## Regression Check
|
||||||
|
|
||||||
|
```text
|
||||||
|
Did this work in an older version? yes / no / unknown
|
||||||
|
Last known working version:
|
||||||
|
First broken version:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
* [ ] I searched existing issues first.
|
||||||
|
* [ ] I tested with the latest ZoiteChat version available to me.
|
||||||
|
* [ ] I included steps to reproduce the issue.
|
||||||
|
* [ ] I removed private information from logs and raw IRC lines.
|
||||||
|
* [ ] I included system and network details where relevant.
|
||||||
|
|
||||||
|
## Extra Notes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Anything else that might help.
|
||||||
|
```
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
||||||
|
```
|
||||||
32
COPYING
32
COPYING
@@ -1,8 +1,8 @@
|
|||||||
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.
|
||||||
|
|
||||||
@@ -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,7 +55,7 @@ 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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -278,7 +278,7 @@ 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
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -1,6 +1,35 @@
|
|||||||
ZoiteChat ChangeLog
|
ZoiteChat ChangeLog
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
2.18.3 (2026-06-29)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Added IRCv3 support for message tags, echo-message, typing notifications, and replies.
|
||||||
|
- Restored the accept-invalid-cert TLS bypass behavior.
|
||||||
|
- Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.
|
||||||
|
- Snapshot Python hooks during plugin unload for safer plugin shutdown.
|
||||||
|
- Fixed Windows GtkStatusIcon tray menu popup behavior.
|
||||||
|
- Fixed self-echoed private messages routing to the target tab.
|
||||||
|
- Fixed the lag ping timeout window.
|
||||||
|
|
||||||
|
2.18.2 (2026-06-20)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Improved security and connection handling with stronger TLS defaults,
|
||||||
|
safer certificate/hostname validation, FiSHLiM OpenSSL fixes, keyring support,
|
||||||
|
and getaddrinfo-based DCC lookups.
|
||||||
|
- Expanded FiSHLiM usability with a GTK key manager, context-menu access, and clearer
|
||||||
|
channel/private-message key handling.
|
||||||
|
- Improved keyboard and navigation behavior with customizable keybinds, reset support,
|
||||||
|
channel-name switching, /server -noproxy, and configurable stale-link ping checks.
|
||||||
|
- Refined the GTK interface with native file chooser dialogs, topic layout preferences,
|
||||||
|
timestamp/date hover tooltips, better restore-down relayout, text scroll speed controls,
|
||||||
|
and aligned network meters.
|
||||||
|
- Added privacy and display options, including hiding join/part hostmasks and improved
|
||||||
|
topic/multiline mode layout controls.
|
||||||
|
- Fixed several platform/UI edge cases, including tray restore from iconified state, DBus unload
|
||||||
|
handling, GTK3 theme refresh/saving, and tab switcher compatibility.
|
||||||
|
|
||||||
2.18.1 (2026-05-21)
|
2.18.1 (2026-05-21)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,42 @@
|
|||||||
<id>zoitechat.desktop</id>
|
<id>zoitechat.desktop</id>
|
||||||
</provides>
|
</provides>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2026-06-29" version="2.18.3">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Added IRCv3 support for message tags, echo-message, typing notifications, and replies.</li>
|
||||||
|
<li>Restored the accept-invalid-cert TLS bypass behavior.</li>
|
||||||
|
<li>Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.</li>
|
||||||
|
<li>Snapshot Python hooks during plugin unload for safer plugin shutdown.</li>
|
||||||
|
<li>Fixed Windows GtkStatusIcon tray menu popup behavior.</li>
|
||||||
|
<li>Fixed self-echoed private messages routing to the target tab.</li>
|
||||||
|
<li>Fixed the lag ping timeout window.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release date="2026-06-20" version="2.18.2">
|
||||||
|
<description>
|
||||||
|
<p>Security and connections:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved TLS, certificate and hostname validation, FiSHLiM OpenSSL handling, keyring support, and DCC host lookups.</li>
|
||||||
|
</ul>
|
||||||
|
<p>FiSHLiM:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added a GTK key manager with context-menu access and clearer channel/private-message key handling.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Interface and preferences:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved keybind customization, reset support, channel-name switching, <code>/server -noproxy</code>, and stale-link ping checks.</li>
|
||||||
|
<li>Refined GTK behavior with native file choosers, topic layout preferences, timestamp/date tooltips, text scroll controls, and aligned network meters.</li>
|
||||||
|
<li>Added privacy and display options for hiding join/part hostmasks and controlling topic/multiline layout.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed tray restore, DBus unload handling, GTK3 theme refresh/saving, topic relayout, and tab switcher compatibility.</li>
|
||||||
|
<li>Updated project documentation.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release date="2026-05-21" version="2.18.1">
|
<release date="2026-05-21" version="2.18.1">
|
||||||
<description>
|
<description>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('zoitechat', 'c',
|
project('zoitechat', 'c',
|
||||||
version: '2.18.1',
|
version: '2.18.3',
|
||||||
meson_version: '>= 0.55.0',
|
meson_version: '>= 0.55.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c17',
|
'c_std=c17',
|
||||||
|
|||||||
102
packaging/fedora/zoitechat.spec
Normal file
102
packaging/fedora/zoitechat.spec
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
Name: zoitechat
|
||||||
|
Version: 2.18.3
|
||||||
|
Release: %autorelease
|
||||||
|
Summary: HexChat-based IRC client
|
||||||
|
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
|
||||||
|
URL: https://github.com/ZoiteChat/zoitechat
|
||||||
|
Source0: %{url}/archive/refs/tags/%{name}-%{version}.tar.gz
|
||||||
|
|
||||||
|
BuildRequires: desktop-file-utils
|
||||||
|
BuildRequires: gcc
|
||||||
|
BuildRequires: gettext
|
||||||
|
BuildRequires: libappstream-glib
|
||||||
|
BuildRequires: meson >= 0.55.0
|
||||||
|
BuildRequires: perl
|
||||||
|
BuildRequires: perl-devel
|
||||||
|
BuildRequires: python3
|
||||||
|
BuildRequires: python3-cffi
|
||||||
|
BuildRequires: publicsuffix-list
|
||||||
|
BuildRequires: xwayland-run
|
||||||
|
BuildRequires: weston
|
||||||
|
BuildRequires: pkgconfig(ayatana-appindicator3-0.1)
|
||||||
|
BuildRequires: pkgconfig(dbus-glib-1)
|
||||||
|
BuildRequires: pkgconfig(gio-2.0) >= 2.36.0
|
||||||
|
BuildRequires: pkgconfig(gmodule-2.0)
|
||||||
|
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
||||||
|
BuildRequires: pkgconfig(iso-codes)
|
||||||
|
BuildRequires: pkgconfig(libarchive)
|
||||||
|
BuildRequires: pkgconfig(libcanberra) >= 0.22
|
||||||
|
BuildRequires: pkgconfig(libpci)
|
||||||
|
BuildRequires: pkgconfig(lua)
|
||||||
|
BuildRequires: pkgconfig(openssl) >= 0.9.8
|
||||||
|
BuildRequires: pkgconfig(python3)
|
||||||
|
|
||||||
|
Requires: hicolor-icon-theme
|
||||||
|
Requires: iso-codes
|
||||||
|
|
||||||
|
%package devel
|
||||||
|
Summary: Development files for ZoiteChat plugins
|
||||||
|
Requires: %{name}%{?_isa} = %{version}-%{release}
|
||||||
|
|
||||||
|
%description devel
|
||||||
|
Development files for building ZoiteChat plugins.
|
||||||
|
|
||||||
|
%description
|
||||||
|
ZoiteChat is a HexChat-based IRC client for Windows and UNIX-like operating
|
||||||
|
systems.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -C
|
||||||
|
|
||||||
|
%build
|
||||||
|
%meson \
|
||||||
|
-Dtext-frontend=false \
|
||||||
|
-Dwith-checksum=true \
|
||||||
|
-Dwith-fishlim=true \
|
||||||
|
-Dwith-lua=lua \
|
||||||
|
-Dwith-perl=perl \
|
||||||
|
-Dwith-python=python3 \
|
||||||
|
-Dwith-sysinfo=true \
|
||||||
|
-Dinstall-appdata=true \
|
||||||
|
-Dinstall-plugin-metainfo=true
|
||||||
|
%meson_build
|
||||||
|
|
||||||
|
%install
|
||||||
|
%meson_install
|
||||||
|
%find_lang %{name}
|
||||||
|
|
||||||
|
%check
|
||||||
|
desktop-file-validate %{buildroot}%{_datadir}/applications/net.zoite.Zoitechat.desktop
|
||||||
|
|
||||||
|
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
|
||||||
|
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
|
||||||
|
|
||||||
|
xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs \
|
||||||
|
"Theme Manager Dispatch Routing Tests" \
|
||||||
|
"Validate net.zoite.Zoitechat.desktop" \
|
||||||
|
"Validate translations" \
|
||||||
|
"Fishlim Tests"
|
||||||
|
|
||||||
|
%files -f %{name}.lang
|
||||||
|
%license COPYING
|
||||||
|
%doc readme.md troubleshooting.md
|
||||||
|
%{_bindir}/zoitechat
|
||||||
|
%{_datadir}/applications/net.zoite.Zoitechat.desktop
|
||||||
|
%{_datadir}/dbus-1/services/org.zoitechat.service.service
|
||||||
|
%{_datadir}/icons/hicolor/48x48/apps/net.zoite.Zoitechat.png
|
||||||
|
%{_datadir}/icons/hicolor/scalable/apps/net.zoite.Zoitechat.svg
|
||||||
|
%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
|
||||||
|
%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
|
||||||
|
%dir %{_libdir}/zoitechat
|
||||||
|
%dir %{_libdir}/zoitechat/plugins
|
||||||
|
%dir %{_libdir}/zoitechat/python
|
||||||
|
%{_libdir}/zoitechat/plugins/*.so
|
||||||
|
%{_libdir}/zoitechat/python/*.py
|
||||||
|
%{_mandir}/man1/zoitechat.1*
|
||||||
|
|
||||||
|
%files devel
|
||||||
|
%{_includedir}/zoitechat-plugin.h
|
||||||
|
%{_libdir}/pkgconfig/zoitechat-plugin.pc
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
%autochangelog
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(OpenSslInclude);$(Glib);..\..\src\common;$(ZoiteChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(OpenSslInclude);$(Glib);$(Gtk);..\..\src\common;$(ZoiteChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<ModuleDefinitionFile>fishlim.def</ModuleDefinitionFile>
|
<ModuleDefinitionFile>fishlim.def</ModuleDefinitionFile>
|
||||||
|
|||||||
@@ -194,6 +194,18 @@ static gboolean keyfile_save_to_file (GKeyFile *keyfile, char *filename) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
gchar **keystore_get_targets(gsize *length) {
|
||||||
|
GKeyFile *keyfile;
|
||||||
|
gchar **groups;
|
||||||
|
|
||||||
|
keyfile = getConfigFile();
|
||||||
|
groups = g_key_file_get_groups(keyfile, length);
|
||||||
|
g_key_file_free(keyfile);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the key store file to disk.
|
* Writes the key store file to disk.
|
||||||
*/
|
*/
|
||||||
@@ -217,6 +229,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
/**
|
/**
|
||||||
* Sets a key in the key store file.
|
* Sets a key in the key store file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
|
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
|
||||||
const char *password;
|
const char *password;
|
||||||
char *encrypted;
|
char *encrypted;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
char *keystore_get_key(const char *nick, enum fish_mode *mode);
|
char *keystore_get_key(const char *nick, enum fish_mode *mode);
|
||||||
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
|
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
|
||||||
gboolean keystore_delete_nick(const char *nick);
|
gboolean keystore_delete_nick(const char *nick);
|
||||||
|
gchar **keystore_get_targets(gsize *length);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fishlim_sources = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
shared_module('fishlim', fishlim_sources,
|
shared_module('fishlim', fishlim_sources,
|
||||||
dependencies: [libgio_dep, zoitechat_plugin_dep, libssl_dep],
|
dependencies: [libgio_dep, gtk_dep, zoitechat_plugin_dep, libssl_dep],
|
||||||
c_args: ['-DOPENSSL_API_COMPAT=0x10100000L'],
|
c_args: ['-DOPENSSL_API_COMPAT=0x10100000L'],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: plugindir,
|
install_dir: plugindir,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "zoitechat-plugin.h"
|
#include "zoitechat-plugin.h"
|
||||||
@@ -44,7 +45,7 @@ static const char plugin_version[] = "1.0.0";
|
|||||||
|
|
||||||
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
|
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
|
||||||
static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
|
static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
|
||||||
static const char usage_keyx[] = "Usage: KEYX [<nick>], performs DH1080 key-exchange with <nick>";
|
static const char usage_keyx[] = "Usage: KEYX [<nick>] [ECB|CBC], performs DH1080 key-exchange with <nick>";
|
||||||
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
|
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
|
||||||
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
|
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
|
||||||
static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
||||||
@@ -52,6 +53,13 @@ static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
|||||||
|
|
||||||
static zoitechat_plugin *ph;
|
static zoitechat_plugin *ph;
|
||||||
static GHashTable *pending_exchanges;
|
static GHashTable *pending_exchanges;
|
||||||
|
static GtkWidget *fishlim_dialog;
|
||||||
|
static GtkWidget *fishlim_target_entry;
|
||||||
|
static GtkWidget *fishlim_key_entry;
|
||||||
|
static GtkWidget *fishlim_mode_combo;
|
||||||
|
static GtkWidget *fishlim_status_label;
|
||||||
|
static GtkListStore *fishlim_store;
|
||||||
|
static GtkWidget *fishlim_view;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -455,6 +463,314 @@ cleanup:
|
|||||||
return ZOITECHAT_EAT_ALL;
|
return ZOITECHAT_EAT_ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char *fishlim_gui_target(void) {
|
||||||
|
const char *target;
|
||||||
|
|
||||||
|
target = gtk_entry_get_text(GTK_ENTRY(fishlim_target_entry));
|
||||||
|
if (target && *target)
|
||||||
|
return target;
|
||||||
|
|
||||||
|
return zoitechat_get_info(ph, "channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_refresh(void) {
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gchar **targets;
|
||||||
|
gsize length, i;
|
||||||
|
const char *target;
|
||||||
|
char *key;
|
||||||
|
enum fish_mode mode;
|
||||||
|
|
||||||
|
if (!fishlim_dialog)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gtk_list_store_clear(fishlim_store);
|
||||||
|
targets = keystore_get_targets(&length);
|
||||||
|
|
||||||
|
for (i = 0; targets && i < length; i++) {
|
||||||
|
key = keystore_get_key(targets[i], &mode);
|
||||||
|
gtk_list_store_append(fishlim_store, &iter);
|
||||||
|
gtk_list_store_set(fishlim_store, &iter, 0, targets[i], 1, fish_modes[mode], 2, key ? "Stored" : "Unreadable", -1);
|
||||||
|
g_free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev(targets);
|
||||||
|
|
||||||
|
target = fishlim_gui_target();
|
||||||
|
key = target ? keystore_get_key(target, &mode) : NULL;
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
char *text = g_strdup_printf("Key stored for %s (%s)", target, fish_modes[mode]);
|
||||||
|
gtk_label_set_text(GTK_LABEL(fishlim_status_label), text);
|
||||||
|
gtk_combo_box_set_active(GTK_COMBO_BOX(fishlim_mode_combo), mode == FISH_CBC_MODE ? 1 : 0);
|
||||||
|
g_free(text);
|
||||||
|
g_free(key);
|
||||||
|
} else if (target && *target) {
|
||||||
|
char *text = g_strdup_printf("No key for %s", target);
|
||||||
|
gtk_label_set_text(GTK_LABEL(fishlim_status_label), text);
|
||||||
|
g_free(text);
|
||||||
|
} else {
|
||||||
|
gtk_label_set_text(GTK_LABEL(fishlim_status_label), "No target selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_refresh_cb(GtkWidget *widget, gpointer data) {
|
||||||
|
fishlim_gui_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_send_channel_key(const char *channel, const char *mode, const char *key) {
|
||||||
|
GtkWidget *dialog, *content, *entry, *label;
|
||||||
|
char **users;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!channel || !*channel || irc_is_query(channel))
|
||||||
|
return;
|
||||||
|
|
||||||
|
dialog = gtk_dialog_new_with_buttons("Share Channel Key", GTK_WINDOW(fishlim_dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, "_Skip", GTK_RESPONSE_CANCEL, "_Send", GTK_RESPONSE_OK, NULL);
|
||||||
|
content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
label = gtk_label_new("Send this channel key to users by private message. Enter users separated by commas.");
|
||||||
|
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 6);
|
||||||
|
entry = gtk_entry_new();
|
||||||
|
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "nick1, nick2, nick3");
|
||||||
|
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), entry, FALSE, FALSE, 6);
|
||||||
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
|
||||||
|
gtk_widget_show_all(dialog);
|
||||||
|
|
||||||
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
|
||||||
|
users = g_strsplit(gtk_entry_get_text(GTK_ENTRY(entry)), ",", -1);
|
||||||
|
for (i = 0; users && users[i]; i++) {
|
||||||
|
char *user = g_strstrip(users[i]);
|
||||||
|
if (*user)
|
||||||
|
zoitechat_commandf(ph, "msg %s Encrypted chat invite: join %s, open FiSHLiM Key Manager, add channel %s with %s key: %s. Key exchange is for private messages only.", user, channel, channel, mode, key);
|
||||||
|
}
|
||||||
|
g_strfreev(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_set(GtkWidget *widget, gpointer data) {
|
||||||
|
const char *target = fishlim_gui_target();
|
||||||
|
const char *key = gtk_entry_get_text(GTK_ENTRY(fishlim_key_entry));
|
||||||
|
const char *mode = gtk_combo_box_get_active(GTK_COMBO_BOX(fishlim_mode_combo)) == 1 ? "CBC" : "ECB";
|
||||||
|
|
||||||
|
if (!target || !*target || !key || !*key) {
|
||||||
|
zoitechat_printf(ph, "%s\n", usage_setkey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoitechat_commandf(ph, "SETKEY %s %s:%s", target, mode, key);
|
||||||
|
fishlim_gui_send_channel_key(target, mode, key);
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(fishlim_key_entry), "");
|
||||||
|
fishlim_gui_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean fishlim_gui_confirm_delete(const char *target) {
|
||||||
|
GtkWidget *dialog;
|
||||||
|
char *message;
|
||||||
|
gboolean confirmed;
|
||||||
|
|
||||||
|
message = g_strdup_printf("Delete the key for %s?", target);
|
||||||
|
dialog = gtk_message_dialog_new(GTK_WINDOW(fishlim_dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", message);
|
||||||
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "This cannot be undone.");
|
||||||
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog), "_Cancel", GTK_RESPONSE_CANCEL, "_Delete", GTK_RESPONSE_ACCEPT, NULL);
|
||||||
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
|
||||||
|
confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
|
||||||
|
gtk_widget_destroy(dialog);
|
||||||
|
g_free(message);
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *fishlim_gui_selected_target(void) {
|
||||||
|
GtkTreeSelection *selection;
|
||||||
|
GtkTreeModel *model;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
char *target = NULL;
|
||||||
|
|
||||||
|
if (!fishlim_view)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(fishlim_view));
|
||||||
|
if (gtk_tree_selection_get_selected(selection, &model, &iter))
|
||||||
|
gtk_tree_model_get(model, &iter, 0, &target, -1);
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_delete(GtkWidget *widget, gpointer data) {
|
||||||
|
char *selected = fishlim_gui_selected_target();
|
||||||
|
const char *target = selected ? selected : fishlim_gui_target();
|
||||||
|
|
||||||
|
if (!target || !*target) {
|
||||||
|
zoitechat_printf(ph, "%s\n", usage_delkey);
|
||||||
|
g_free(selected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fishlim_gui_confirm_delete(target)) {
|
||||||
|
zoitechat_commandf(ph, "DELKEY %s", target);
|
||||||
|
fishlim_gui_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *fishlim_gui_prompt_target(const char *title, const char *initial) {
|
||||||
|
GtkWidget *dialog, *content, *entry;
|
||||||
|
char *target = NULL;
|
||||||
|
|
||||||
|
dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(fishlim_dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, "_Cancel", GTK_RESPONSE_CANCEL, "_OK", GTK_RESPONSE_OK, NULL);
|
||||||
|
content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
entry = gtk_entry_new();
|
||||||
|
if (initial && *initial)
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(entry), initial);
|
||||||
|
gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), gtk_label_new("Private message user"), FALSE, FALSE, 6);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), entry, FALSE, FALSE, 6);
|
||||||
|
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
|
||||||
|
gtk_widget_show_all(dialog);
|
||||||
|
|
||||||
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
|
||||||
|
const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
|
||||||
|
if (text && *text)
|
||||||
|
target = g_strdup(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy(dialog);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_keyx(GtkWidget *widget, gpointer data) {
|
||||||
|
char *target;
|
||||||
|
const char *initial = fishlim_gui_target();
|
||||||
|
const char *mode = gtk_combo_box_get_active(GTK_COMBO_BOX(fishlim_mode_combo)) == 1 ? "CBC" : "ECB";
|
||||||
|
|
||||||
|
if (!initial || !*initial || !irc_is_query(initial))
|
||||||
|
initial = NULL;
|
||||||
|
|
||||||
|
target = fishlim_gui_prompt_target("Key Exchange", initial);
|
||||||
|
if (!target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
zoitechat_commandf(ph, "KEYX %s %s", target, mode);
|
||||||
|
g_free(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) {
|
||||||
|
GtkTreeModel *model = gtk_tree_view_get_model(tree);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
char *target;
|
||||||
|
|
||||||
|
if (!gtk_tree_model_get_iter(model, &iter, path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
gtk_tree_model_get(model, &iter, 0, &target, -1);
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(fishlim_target_entry), target);
|
||||||
|
g_free(target);
|
||||||
|
fishlim_gui_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fishlim_gui_destroy(GtkWidget *widget, gpointer data) {
|
||||||
|
fishlim_dialog = NULL;
|
||||||
|
fishlim_target_entry = NULL;
|
||||||
|
fishlim_key_entry = NULL;
|
||||||
|
fishlim_mode_combo = NULL;
|
||||||
|
fishlim_status_label = NULL;
|
||||||
|
fishlim_store = NULL;
|
||||||
|
fishlim_view = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_fishlim(char *word[], char *word_eol[], void *userdata) {
|
||||||
|
GtkWidget *content, *grid, *view, *scroll, *buttons, *button;
|
||||||
|
GtkCellRenderer *renderer;
|
||||||
|
const char *target;
|
||||||
|
|
||||||
|
target = *word_eol[2] ? word_eol[2] : zoitechat_get_info(ph, "channel");
|
||||||
|
|
||||||
|
if (fishlim_dialog) {
|
||||||
|
if (target)
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(fishlim_target_entry), target);
|
||||||
|
gtk_window_present(GTK_WINDOW(fishlim_dialog));
|
||||||
|
return ZOITECHAT_EAT_ZOITECHAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fishlim_dialog = gtk_dialog_new_with_buttons("FiSHLiM Key Manager", NULL, GTK_DIALOG_DESTROY_WITH_PARENT, "_Close", GTK_RESPONSE_CLOSE, NULL);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(fishlim_dialog), 520, 360);
|
||||||
|
g_signal_connect(fishlim_dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
|
||||||
|
g_signal_connect(fishlim_dialog, "destroy", G_CALLBACK(fishlim_gui_destroy), NULL);
|
||||||
|
|
||||||
|
content = gtk_dialog_get_content_area(GTK_DIALOG(fishlim_dialog));
|
||||||
|
grid = gtk_grid_new();
|
||||||
|
gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
|
||||||
|
gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
|
||||||
|
gtk_box_pack_start(GTK_BOX(content), grid, TRUE, TRUE, 0);
|
||||||
|
|
||||||
|
fishlim_target_entry = gtk_entry_new();
|
||||||
|
if (target)
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(fishlim_target_entry), target);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Target nick or channel"), 0, 0, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), fishlim_target_entry, 1, 0, 2, 1);
|
||||||
|
|
||||||
|
fishlim_mode_combo = gtk_combo_box_text_new();
|
||||||
|
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(fishlim_mode_combo), "ECB");
|
||||||
|
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(fishlim_mode_combo), "CBC");
|
||||||
|
gtk_combo_box_set_active(GTK_COMBO_BOX(fishlim_mode_combo), 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Mode"), 0, 1, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), fishlim_mode_combo, 1, 1, 2, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("CBC may not work with older clients. Key exchange is private-message only."), 1, 2, 2, 1);
|
||||||
|
|
||||||
|
fishlim_key_entry = gtk_entry_new();
|
||||||
|
gtk_entry_set_visibility(GTK_ENTRY(fishlim_key_entry), FALSE);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Key"), 0, 3, 1, 1);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), fishlim_key_entry, 1, 3, 2, 1);
|
||||||
|
|
||||||
|
buttons = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
|
||||||
|
gtk_button_box_set_layout(GTK_BUTTON_BOX(buttons), GTK_BUTTONBOX_END);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), buttons, 0, 4, 3, 1);
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Set Key");
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(fishlim_gui_set), NULL);
|
||||||
|
gtk_container_add(GTK_CONTAINER(buttons), button);
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Delete Key");
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(fishlim_gui_delete), NULL);
|
||||||
|
gtk_container_add(GTK_CONTAINER(buttons), button);
|
||||||
|
|
||||||
|
button = gtk_button_new_with_label("Private Message Key Exchange");
|
||||||
|
g_signal_connect(button, "clicked", G_CALLBACK(fishlim_gui_keyx), NULL);
|
||||||
|
gtk_container_add(GTK_CONTAINER(buttons), button);
|
||||||
|
|
||||||
|
fishlim_status_label = gtk_label_new(NULL);
|
||||||
|
gtk_label_set_xalign(GTK_LABEL(fishlim_status_label), 0.0);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), fishlim_status_label, 0, 5, 3, 1);
|
||||||
|
|
||||||
|
fishlim_store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
|
||||||
|
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(fishlim_store));
|
||||||
|
fishlim_view = view;
|
||||||
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Target", renderer, "text", 0, NULL);
|
||||||
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Mode", renderer, "text", 1, NULL);
|
||||||
|
renderer = gtk_cell_renderer_text_new();
|
||||||
|
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Status", renderer, "text", 2, NULL);
|
||||||
|
g_signal_connect(view, "row-activated", G_CALLBACK(fishlim_gui_row_activated), NULL);
|
||||||
|
|
||||||
|
scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_widget_set_vexpand(scroll, TRUE);
|
||||||
|
gtk_container_add(GTK_CONTAINER(scroll), view);
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), scroll, 0, 6, 3, 1);
|
||||||
|
|
||||||
|
g_signal_connect(fishlim_target_entry, "changed", G_CALLBACK(fishlim_gui_refresh_cb), NULL);
|
||||||
|
fishlim_gui_refresh();
|
||||||
|
|
||||||
|
gtk_widget_show_all(fishlim_dialog);
|
||||||
|
return ZOITECHAT_EAT_ZOITECHAT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command handler for /setkey
|
* Command handler for /setkey
|
||||||
*/
|
*/
|
||||||
@@ -490,6 +806,8 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
|
|||||||
/* Set password */
|
/* Set password */
|
||||||
if (keystore_store_key(nick, key, mode)) {
|
if (keystore_store_key(nick, key, mode)) {
|
||||||
zoitechat_printf(ph, "Stored key for %s (%s)\n", nick, fish_modes[mode]);
|
zoitechat_printf(ph, "Stored key for %s (%s)\n", nick, fish_modes[mode]);
|
||||||
|
if (mode == FISH_CBC_MODE)
|
||||||
|
zoitechat_printf(ph, "Warning: CBC may not work with older clients.\n");
|
||||||
} else {
|
} else {
|
||||||
zoitechat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n");
|
zoitechat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n");
|
||||||
}
|
}
|
||||||
@@ -533,8 +851,18 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
|
|||||||
const char *target = word[2];
|
const char *target = word[2];
|
||||||
zoitechat_context *query_ctx = NULL;
|
zoitechat_context *query_ctx = NULL;
|
||||||
char *pub_key, *priv_key;
|
char *pub_key, *priv_key;
|
||||||
|
enum fish_mode mode = FISH_CBC_MODE;
|
||||||
int ctx_type;
|
int ctx_type;
|
||||||
|
|
||||||
|
if (*word[3]) {
|
||||||
|
if (g_ascii_strcasecmp(word[3], "ECB") == 0)
|
||||||
|
mode = FISH_ECB_MODE;
|
||||||
|
else if (g_ascii_strcasecmp(word[3], "CBC") != 0) {
|
||||||
|
zoitechat_printf(ph, "%s", usage_keyx);
|
||||||
|
return ZOITECHAT_EAT_ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (*target)
|
if (*target)
|
||||||
query_ctx = find_context_on_network(target);
|
query_ctx = find_context_on_network(target);
|
||||||
else {
|
else {
|
||||||
@@ -555,8 +883,10 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
|
|||||||
if (dh1080_generate_key(&priv_key, &pub_key)) {
|
if (dh1080_generate_key(&priv_key, &pub_key)) {
|
||||||
g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key);
|
g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key);
|
||||||
|
|
||||||
zoitechat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s CBC", target, pub_key);
|
zoitechat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s%s", target, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : "");
|
||||||
zoitechat_printf(ph, "Sent public key to %s (CBC), waiting for reply...", target);
|
zoitechat_printf(ph, "Sent public key to %s (%s), waiting for reply...", target, fish_modes[mode]);
|
||||||
|
if (mode == FISH_CBC_MODE)
|
||||||
|
zoitechat_printf(ph, "Warning: CBC may not work with older clients.");
|
||||||
|
|
||||||
g_free(pub_key);
|
g_free(pub_key);
|
||||||
} else {
|
} else {
|
||||||
@@ -789,6 +1119,7 @@ int zoitechat_plugin_init(zoitechat_plugin *plugin_handle,
|
|||||||
*version = plugin_version;
|
*version = plugin_version;
|
||||||
|
|
||||||
/* Register commands */
|
/* Register commands */
|
||||||
|
zoitechat_hook_command(ph, "FISHLIM", ZOITECHAT_PRI_NORM, handle_fishlim, "Usage: FISHLIM, opens the FiSHLiM key manager", NULL);
|
||||||
zoitechat_hook_command(ph, "SETKEY", ZOITECHAT_PRI_NORM, handle_setkey, usage_setkey, NULL);
|
zoitechat_hook_command(ph, "SETKEY", ZOITECHAT_PRI_NORM, handle_setkey, usage_setkey, NULL);
|
||||||
zoitechat_hook_command(ph, "DELKEY", ZOITECHAT_PRI_NORM, handle_delkey, usage_delkey, NULL);
|
zoitechat_hook_command(ph, "DELKEY", ZOITECHAT_PRI_NORM, handle_delkey, usage_delkey, NULL);
|
||||||
zoitechat_hook_command(ph, "KEYX", ZOITECHAT_PRI_NORM, handle_keyx, usage_keyx, NULL);
|
zoitechat_hook_command(ph, "KEYX", ZOITECHAT_PRI_NORM, handle_keyx, usage_keyx, NULL);
|
||||||
@@ -817,12 +1148,19 @@ int zoitechat_plugin_init(zoitechat_plugin *plugin_handle,
|
|||||||
|
|
||||||
pending_exchanges = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
pending_exchanges = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
||||||
|
|
||||||
|
zoitechat_command(ph, "MENU ADD \"Window/FiSHLiM Key Manager\" \"FISHLIM\"");
|
||||||
|
zoitechat_command(ph, "MENU ADD \"$NICK/FiSHLiM Key Manager\" \"FISHLIM %s\"");
|
||||||
|
|
||||||
zoitechat_printf(ph, "%s plugin loaded\n", plugin_name);
|
zoitechat_printf(ph, "%s plugin loaded\n", plugin_name);
|
||||||
/* Return success */
|
/* Return success */
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int zoitechat_plugin_deinit(void) {
|
int zoitechat_plugin_deinit(void) {
|
||||||
|
zoitechat_command(ph, "MENU DEL \"Window/FiSHLiM Key Manager\"");
|
||||||
|
zoitechat_command(ph, "MENU DEL \"$NICK/FiSHLiM Key Manager\"");
|
||||||
|
if (fishlim_dialog)
|
||||||
|
gtk_widget_destroy(fishlim_dialog);
|
||||||
g_clear_pointer(&pending_exchanges, g_hash_table_destroy);
|
g_clear_pointer(&pending_exchanges, g_hash_table_destroy);
|
||||||
dh1080_deinit();
|
dh1080_deinit();
|
||||||
fish_deinit();
|
fish_deinit();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ else:
|
|||||||
if not hasattr(sys, 'argv'):
|
if not hasattr(sys, 'argv'):
|
||||||
sys.argv = ['<zoitechat>']
|
sys.argv = ['<zoitechat>']
|
||||||
|
|
||||||
VERSION = b'2.18.1'
|
VERSION = b'2.18.3'
|
||||||
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
||||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
||||||
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
||||||
@@ -173,7 +173,7 @@ class Plugin:
|
|||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
log('unloading', self.filename)
|
log('unloading', self.filename)
|
||||||
for hook in self.hooks:
|
for hook in list(self.hooks):
|
||||||
if hook.is_unload is True:
|
if hook.is_unload is True:
|
||||||
try:
|
try:
|
||||||
hook.callback(hook.userdata)
|
hook.callback(hook.userdata)
|
||||||
|
|||||||
@@ -428,6 +428,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_lagometer", P_OFFINT (hex_gui_lagometer), TYPE_INT},
|
{"gui_lagometer", P_OFFINT (hex_gui_lagometer), TYPE_INT},
|
||||||
{"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT},
|
{"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT},
|
||||||
{"gui_mode_buttons", P_OFFINT (hex_gui_mode_buttons), TYPE_BOOL},
|
{"gui_mode_buttons", P_OFFINT (hex_gui_mode_buttons), TYPE_BOOL},
|
||||||
|
{"gui_mode_buttons_inline", P_OFFINT (hex_gui_mode_buttons_inline), TYPE_BOOL},
|
||||||
{"gui_pane_divider_position", P_OFFINT (hex_gui_pane_divider_position), TYPE_INT},
|
{"gui_pane_divider_position", P_OFFINT (hex_gui_pane_divider_position), TYPE_INT},
|
||||||
{"gui_pane_left_size", P_OFFINT (hex_gui_pane_left_size), TYPE_INT},
|
{"gui_pane_left_size", P_OFFINT (hex_gui_pane_left_size), TYPE_INT},
|
||||||
{"gui_pane_right_size", P_OFFINT (hex_gui_pane_right_size), TYPE_INT},
|
{"gui_pane_right_size", P_OFFINT (hex_gui_pane_right_size), TYPE_INT},
|
||||||
@@ -458,6 +459,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_tab_utils", P_OFFINT (hex_gui_tab_utils), TYPE_BOOL},
|
{"gui_tab_utils", P_OFFINT (hex_gui_tab_utils), TYPE_BOOL},
|
||||||
{"gui_throttlemeter", P_OFFINT (hex_gui_throttlemeter), TYPE_INT},
|
{"gui_throttlemeter", P_OFFINT (hex_gui_throttlemeter), TYPE_INT},
|
||||||
{"gui_topicbar", P_OFFINT (hex_gui_topicbar), TYPE_BOOL},
|
{"gui_topicbar", P_OFFINT (hex_gui_topicbar), TYPE_BOOL},
|
||||||
|
{"gui_topicbar_multiline", P_OFFINT (hex_gui_topicbar_multiline), TYPE_BOOL},
|
||||||
{"gui_transparency", P_OFFINT (hex_gui_transparency), TYPE_INT},
|
{"gui_transparency", P_OFFINT (hex_gui_transparency), TYPE_INT},
|
||||||
{"gui_tray", P_OFFINT (hex_gui_tray), TYPE_BOOL},
|
{"gui_tray", P_OFFINT (hex_gui_tray), TYPE_BOOL},
|
||||||
{"gui_tray_away", P_OFFINT (hex_gui_tray_away), TYPE_BOOL},
|
{"gui_tray_away", P_OFFINT (hex_gui_tray_away), TYPE_BOOL},
|
||||||
@@ -518,6 +520,7 @@ const struct prefs vars[] =
|
|||||||
{"irc_conf_mode", P_OFFINT (hex_irc_conf_mode), TYPE_BOOL},
|
{"irc_conf_mode", P_OFFINT (hex_irc_conf_mode), TYPE_BOOL},
|
||||||
{"irc_extra_hilight", P_OFFSET (hex_irc_extra_hilight), TYPE_STR},
|
{"irc_extra_hilight", P_OFFSET (hex_irc_extra_hilight), TYPE_STR},
|
||||||
{"irc_hide_nickchange", P_OFFINT (hex_irc_hide_nickchange), TYPE_BOOL},
|
{"irc_hide_nickchange", P_OFFINT (hex_irc_hide_nickchange), TYPE_BOOL},
|
||||||
|
{"irc_hide_join_part_hostmask", P_OFFINT (hex_irc_hide_join_part_hostmask), TYPE_BOOL},
|
||||||
{"irc_hide_version", P_OFFINT (hex_irc_hide_version), TYPE_BOOL},
|
{"irc_hide_version", P_OFFINT (hex_irc_hide_version), TYPE_BOOL},
|
||||||
{"irc_hidehost", P_OFFINT (hex_irc_hidehost), TYPE_BOOL},
|
{"irc_hidehost", P_OFFINT (hex_irc_hidehost), TYPE_BOOL},
|
||||||
{"irc_id_ntext", P_OFFSET (hex_irc_id_ntext), TYPE_STR},
|
{"irc_id_ntext", P_OFFSET (hex_irc_id_ntext), TYPE_STR},
|
||||||
@@ -790,6 +793,7 @@ load_default_config(void)
|
|||||||
prefs.hex_gui_tab_scrollchans = 1;
|
prefs.hex_gui_tab_scrollchans = 1;
|
||||||
prefs.hex_gui_mouse_scroll_speed = 10;
|
prefs.hex_gui_mouse_scroll_speed = 10;
|
||||||
prefs.hex_gui_topicbar = 1;
|
prefs.hex_gui_topicbar = 1;
|
||||||
|
prefs.hex_gui_topicbar_multiline = 1;
|
||||||
prefs.hex_gui_transparency = 255;
|
prefs.hex_gui_transparency = 255;
|
||||||
prefs.hex_gui_tray = 1;
|
prefs.hex_gui_tray = 1;
|
||||||
prefs.hex_gui_tray_blink = 1;
|
prefs.hex_gui_tray_blink = 1;
|
||||||
|
|||||||
@@ -1090,13 +1090,19 @@ clients_find_filename_foreach (gpointer key,
|
|||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
RemoteObject *obj = value;
|
RemoteObject *obj = value;
|
||||||
return g_str_equal (obj->filename, (char *)user_data);
|
|
||||||
|
return obj->filename != NULL && g_str_equal (obj->filename, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
|
unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
|
||||||
{
|
{
|
||||||
RemoteObject *obj = g_hash_table_find (clients, clients_find_filename_foreach, word[2]);
|
RemoteObject *obj;
|
||||||
|
|
||||||
|
if (word[2][0] == 0)
|
||||||
|
return ZOITECHAT_EAT_NONE;
|
||||||
|
|
||||||
|
obj = g_hash_table_find (clients, clients_find_filename_foreach, word[2]);
|
||||||
|
|
||||||
if (obj != NULL)
|
if (obj != NULL)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ int fe_input_add (int sok, int flags, void *func, void *data);
|
|||||||
void fe_input_remove (int tag);
|
void fe_input_remove (int tag);
|
||||||
void fe_idle_add (void *func, void *data);
|
void fe_idle_add (void *func, void *data);
|
||||||
void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
|
void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
|
||||||
|
void fe_set_typing (struct session *sess, const char *nick, const char *state);
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
FE_COLOR_NONE = 0,
|
FE_COLOR_NONE = 0,
|
||||||
|
|||||||
@@ -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,12 +685,42 @@ zoitechat_gtk3_theme_service_discover (void)
|
|||||||
|
|
||||||
g_mkdir_with_parents (user_dir, 0700);
|
g_mkdir_with_parents (user_dir, 0700);
|
||||||
|
|
||||||
|
/* 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);
|
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);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
|
||||||
|
|
||||||
|
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 ();
|
system_data_dirs = g_get_system_data_dirs ();
|
||||||
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||||||
{
|
{
|
||||||
@@ -645,6 +728,8 @@ zoitechat_gtk3_theme_service_discover (void)
|
|||||||
add_theme_root (system_roots, seen_system_roots, system_themes);
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||||||
g_free (system_themes);
|
g_free (system_themes);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < system_roots->len; i++)
|
for (i = 0; i < system_roots->len; i++)
|
||||||
discover_dir (themes, seen_theme_roots, g_ptr_array_index (system_roots, i), ZOITECHAT_GTK3_THEME_SOURCE_SYSTEM);
|
discover_dir (themes, seen_theme_roots, g_ptr_array_index (system_roots, i), ZOITECHAT_GTK3_THEME_SOURCE_SYSTEM);
|
||||||
@@ -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,16 +1015,12 @@ 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,
|
|
||||||
"Invalid GTK3 theme at '%s': missing required index.theme at '%s'.",
|
|
||||||
theme_root, index_theme);
|
|
||||||
g_free (index_theme);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
keyfile = g_key_file_new ();
|
keyfile = g_key_file_new ();
|
||||||
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
||||||
{
|
{
|
||||||
@@ -961,6 +1042,7 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
||||||
if (!css_dir)
|
if (!css_dir)
|
||||||
@@ -968,6 +1050,7 @@ 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);
|
||||||
|
if (keyfile)
|
||||||
g_key_file_unref (keyfile);
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -981,6 +1064,7 @@ 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);
|
||||||
|
if (keyfile)
|
||||||
g_key_file_unref (keyfile);
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -988,8 +1072,12 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
g_free (css_path);
|
g_free (css_path);
|
||||||
g_free (css_dir);
|
g_free (css_dir);
|
||||||
|
|
||||||
|
raw_inherits = NULL;
|
||||||
|
if (keyfile)
|
||||||
|
{
|
||||||
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
||||||
g_key_file_unref (keyfile);
|
g_key_file_unref (keyfile);
|
||||||
|
}
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
if (!raw_inherits)
|
if (!raw_inherits)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|||||||
@@ -46,6 +46,155 @@
|
|||||||
#include "servlist.h"
|
#include "servlist.h"
|
||||||
#include "sts.h"
|
#include "sts.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
|
||||||
|
void reply_state_clear (session *sess);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
reply_msgid_valid (const char *msgid)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (!msgid || !*msgid || *msgid == ':')
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
for (p = msgid; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p == ' ' || *p == '\r' || *p == '\n')
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_item_free (reply_item *item)
|
||||||
|
{
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
g_free (item->msgid);
|
||||||
|
g_free (item->nick);
|
||||||
|
g_free (item->text);
|
||||||
|
g_free (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_cache_free (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
g_slist_free_full (sess->reply_items, (GDestroyNotify) reply_item_free);
|
||||||
|
sess->reply_items = NULL;
|
||||||
|
reply_state_clear (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_item *
|
||||||
|
reply_cache_find (session *sess, const char *msgid)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!sess || !reply_msgid_valid (msgid))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (list = sess->reply_items; list; list = list->next)
|
||||||
|
{
|
||||||
|
reply_item *item = list->data;
|
||||||
|
if (!strcmp (item->msgid, msgid))
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_item *
|
||||||
|
reply_cache_latest_from (session *sess, const char *nick)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!sess || !nick || !*nick)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (list = sess->reply_items; list; list = list->next)
|
||||||
|
{
|
||||||
|
reply_item *item = list->data;
|
||||||
|
if (!sess->server->p_cmp (item->nick, nick))
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_state_clear (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
g_clear_pointer (&sess->reply_msgid, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_target, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_nick, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_text, g_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_state_set (session *sess, const char *msgid, const char *target, const char *nick, const char *text)
|
||||||
|
{
|
||||||
|
if (!sess || !reply_msgid_valid (msgid))
|
||||||
|
return;
|
||||||
|
reply_state_clear (sess);
|
||||||
|
sess->reply_msgid = g_strdup (msgid);
|
||||||
|
sess->reply_target = g_strdup (target && *target ? target : sess->channel);
|
||||||
|
sess->reply_nick = g_strdup (nick && *nick ? nick : _("message"));
|
||||||
|
sess->reply_text = g_strdup (text && *text ? text : _("Original message unavailable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_cache_add (session *sess, const char *msgid, const char *nick, const char *text, time_t timestamp)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
if (!sess || !reply_msgid_valid (msgid) || !nick || !text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item = reply_cache_find (sess, msgid);
|
||||||
|
if (item)
|
||||||
|
{
|
||||||
|
g_free (item->nick);
|
||||||
|
g_free (item->text);
|
||||||
|
item->nick = g_strdup (nick);
|
||||||
|
item->text = g_strdup (text);
|
||||||
|
item->timestamp = timestamp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = g_new0 (reply_item, 1);
|
||||||
|
item->msgid = g_strdup (msgid);
|
||||||
|
item->nick = g_strdup (nick);
|
||||||
|
item->text = g_strdup (text);
|
||||||
|
item->timestamp = timestamp;
|
||||||
|
sess->reply_items = g_slist_prepend (sess->reply_items, item);
|
||||||
|
|
||||||
|
if (g_slist_length (sess->reply_items) > 200)
|
||||||
|
{
|
||||||
|
GSList *last = g_slist_last (sess->reply_items);
|
||||||
|
item = last->data;
|
||||||
|
sess->reply_items = g_slist_delete_link (sess->reply_items, last);
|
||||||
|
reply_item_free (item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_context_print (session *sess, const message_tags_data *tags_data)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
if (!sess || !tags_data || !tags_data->reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item = reply_cache_find (sess, tags_data->reply);
|
||||||
|
if (item)
|
||||||
|
PrintTextTimeStampf (sess, tags_data->timestamp, "\00314│ ↪ %s: %.180s\017\n", item->nick, item->text);
|
||||||
|
else
|
||||||
|
PrintTextTimeStamp (sess, "\00314│ ↪ Original message unavailable\017\n", tags_data->timestamp);
|
||||||
|
}
|
||||||
#include "ctcp.h"
|
#include "ctcp.h"
|
||||||
#include "zoitechatc.h"
|
#include "zoitechatc.h"
|
||||||
#include "chanopt.h"
|
#include "chanopt.h"
|
||||||
@@ -450,6 +599,9 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text,
|
|||||||
fromme = TRUE;
|
fromme = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fromme)
|
||||||
|
fe_set_typing (sess, from, "done");
|
||||||
|
|
||||||
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
|
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
|
||||||
|
|
||||||
if (!fromme && !privaction)
|
if (!fromme && !privaction)
|
||||||
@@ -518,12 +670,17 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
fromme = TRUE;
|
fromme = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fromme)
|
||||||
|
fe_set_typing (sess, from, "done");
|
||||||
|
|
||||||
if (fromme)
|
if (fromme)
|
||||||
{
|
{
|
||||||
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
|
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
|
||||||
sess->server->p_set_back (sess->server);
|
sess->server->p_set_back (sess->server);
|
||||||
|
reply_context_print (sess, tags_data);
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
|
||||||
0, tags_data->timestamp);
|
0, tags_data->timestamp);
|
||||||
|
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +689,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
if (is_hilight (from, text, sess, serv))
|
if (is_hilight (from, text, sess, serv))
|
||||||
hilight = TRUE;
|
hilight = TRUE;
|
||||||
|
|
||||||
|
reply_context_print (sess, tags_data);
|
||||||
|
|
||||||
if (sess->type == SESS_DIALOG)
|
if (sess->type == SESS_DIALOG)
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
|
||||||
tags_data->timestamp);
|
tags_data->timestamp);
|
||||||
@@ -541,6 +700,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
else
|
else
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
|
||||||
0, tags_data->timestamp);
|
0, tags_data->timestamp);
|
||||||
|
|
||||||
|
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1483,7 +1644,7 @@ inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data)
|
|||||||
{
|
{
|
||||||
sess->server->dcc_ip = addr.s_addr;
|
sess->server->dcc_ip = addr.s_addr;
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
|
||||||
inet_ntoa (addr),
|
net_ip (ntohl (addr.s_addr)),
|
||||||
NULL, NULL, NULL, 0, tags_data->timestamp);
|
NULL, NULL, NULL, 0, tags_data->timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1654,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);
|
||||||
@@ -1742,6 +1903,10 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
|
|||||||
serv->have_awaynotify = enable;
|
serv->have_awaynotify = enable;
|
||||||
else if (!strcmp (extension, "account-tag"))
|
else if (!strcmp (extension, "account-tag"))
|
||||||
serv->have_account_tag = enable;
|
serv->have_account_tag = enable;
|
||||||
|
else if (!strcmp (extension, "message-tags"))
|
||||||
|
serv->have_message_tags = enable;
|
||||||
|
else if (!strcmp (extension, "echo-message"))
|
||||||
|
serv->have_echo_message = enable;
|
||||||
else if (!strcmp (extension, "sasl"))
|
else if (!strcmp (extension, "sasl"))
|
||||||
{
|
{
|
||||||
serv->have_sasl = enable;
|
serv->have_sasl = enable;
|
||||||
@@ -1873,6 +2038,8 @@ static const char * const supported_caps[] = {
|
|||||||
"account-tag",
|
"account-tag",
|
||||||
"extended-monitor",
|
"extended-monitor",
|
||||||
"standard-replies",
|
"standard-replies",
|
||||||
|
"message-tags",
|
||||||
|
"echo-message",
|
||||||
|
|
||||||
/* ZNC */
|
/* ZNC */
|
||||||
"znc.in/server-time-iso",
|
"znc.in/server-time-iso",
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ void inbound_login_end (session *sess, char *text,
|
|||||||
void inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
void inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
||||||
char *text, char fromme, int id,
|
char *text, char fromme, int id,
|
||||||
const message_tags_data *tags_data);
|
const message_tags_data *tags_data);
|
||||||
|
gboolean reply_msgid_valid (const char *msgid);
|
||||||
|
void reply_cache_free (session *sess);
|
||||||
|
reply_item *reply_cache_find (session *sess, const char *msgid);
|
||||||
|
reply_item *reply_cache_latest_from (session *sess, const char *nick);
|
||||||
|
void reply_state_set (session *sess, const char *msgid, const char *target,
|
||||||
|
const char *nick, const char *text);
|
||||||
|
void reply_state_clear (session *sess);
|
||||||
void clear_channel (session *sess);
|
void clear_channel (session *sess);
|
||||||
void set_topic (session *sess, char *topic, char *stripped_topic);
|
void set_topic (session *sess, char *topic, char *stripped_topic);
|
||||||
void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
|
void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
|
||||||
|
|||||||
@@ -907,6 +907,10 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
|
|||||||
{
|
{
|
||||||
if (g_strcmp0 (tokvalue, "ascii") == 0)
|
if (g_strcmp0 (tokvalue, "ascii") == 0)
|
||||||
serv->p_cmp = (void *)g_ascii_strcasecmp;
|
serv->p_cmp = (void *)g_ascii_strcasecmp;
|
||||||
|
} else if (g_strcmp0 (tokname, "CLIENTTAGDENY") == 0)
|
||||||
|
{
|
||||||
|
g_free (serv->clienttagdeny);
|
||||||
|
serv->clienttagdeny = tokadding ? g_strdup (tokvalue) : NULL;
|
||||||
} else if (g_strcmp0 (tokname, "CHARSET") == 0)
|
} else if (g_strcmp0 (tokname, "CHARSET") == 0)
|
||||||
{
|
{
|
||||||
if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
|
if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
|
||||||
|
|||||||
@@ -90,10 +90,26 @@ net_set_socket_options (int sok)
|
|||||||
char *
|
char *
|
||||||
net_ip (uint32_t addr)
|
net_ip (uint32_t addr)
|
||||||
{
|
{
|
||||||
|
static char buf[INET_ADDRSTRLEN];
|
||||||
struct in_addr ia;
|
struct in_addr ia;
|
||||||
|
|
||||||
ia.s_addr = htonl (addr);
|
ia.s_addr = htonl (addr);
|
||||||
return inet_ntoa (ia);
|
if (!inet_ntop (AF_INET, &ia, buf, sizeof (buf)))
|
||||||
|
buf[0] = 0;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
net_parse_ipv4 (const char *hostname, uint32_t *addr)
|
||||||
|
{
|
||||||
|
struct in_addr ia;
|
||||||
|
|
||||||
|
if (inet_pton (AF_INET, hostname, &ia) != 1)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
*addr = ia.s_addr;
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ int net_connect (netstore *ns, int sok4, int sok6, int *sok_return);
|
|||||||
char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
|
char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
|
||||||
void net_bind (netstore *tobindto, int sok4, int sok6);
|
void net_bind (netstore *tobindto, int sok4, int sok6);
|
||||||
char *net_ip (uint32_t addr);
|
char *net_ip (uint32_t addr);
|
||||||
|
int net_parse_ipv4 (const char *hostname, uint32_t *addr);
|
||||||
int net_lookup_ipv4 (const char *hostname, uint32_t *addr);
|
int net_lookup_ipv4 (const char *hostname, uint32_t *addr);
|
||||||
void net_sockets (int *sok4, int *sok6);
|
void net_sockets (int *sok4, int *sok6);
|
||||||
|
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
|
|||||||
type = prefs.hex_irc_ban_type;
|
type = prefs.hex_irc_ban_type;
|
||||||
|
|
||||||
buf[0] = 0;
|
buf[0] = 0;
|
||||||
if (inet_addr (fullhost) != (guint32) -1) /* "fullhost" is really a IP number */
|
if (net_parse_ipv4 (fullhost, &(guint32){0})) /* "fullhost" is really a IP number */
|
||||||
{
|
{
|
||||||
lastdot = strrchr (fullhost, '.');
|
lastdot = strrchr (fullhost, '.');
|
||||||
if (!lastdot)
|
if (!lastdot)
|
||||||
@@ -2744,7 +2744,7 @@ cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
|||||||
while ((split_text = split_up_text (sess, act + offset, cmd_length, split_text)))
|
while ((split_text = split_up_text (sess, act + offset, cmd_length, split_text)))
|
||||||
{
|
{
|
||||||
sess->server->p_action (sess->server, sess->channel, split_text);
|
sess->server->p_action (sess->server, sess->channel, split_text);
|
||||||
/* print it to screen */
|
if (!sess->server->have_echo_message)
|
||||||
inbound_action (sess, sess->channel, sess->server->nick, "",
|
inbound_action (sess, sess->channel, sess->server->nick, "",
|
||||||
split_text, TRUE, FALSE,
|
split_text, TRUE, FALSE,
|
||||||
&no_tags);
|
&no_tags);
|
||||||
@@ -2756,7 +2756,7 @@ cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
sess->server->p_action (sess->server, sess->channel, act + offset);
|
sess->server->p_action (sess->server, sess->channel, act + offset);
|
||||||
/* print it to screen */
|
if (!sess->server->have_echo_message)
|
||||||
inbound_action (sess, sess->channel, sess->server->nick, "",
|
inbound_action (sess, sess->channel, sess->server->nick, "",
|
||||||
act + offset, TRUE, FALSE, &no_tags);
|
act + offset, TRUE, FALSE, &no_tags);
|
||||||
} else
|
} else
|
||||||
@@ -2821,6 +2821,137 @@ cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
client_tag_allowed (server *serv, const char *tag)
|
||||||
|
{
|
||||||
|
char **deny;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!serv->have_message_tags)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!serv->clienttagdeny || !*serv->clienttagdeny)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
deny = g_strsplit (serv->clienttagdeny, ",", 0);
|
||||||
|
for (i = 0; deny[i]; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
|
||||||
|
{
|
||||||
|
g_strfreev (deny);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev (deny);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
client_tag_escape (const char *text)
|
||||||
|
{
|
||||||
|
GString *out;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
out = g_string_sized_new (strlen (text));
|
||||||
|
for (p = text; *p; p++)
|
||||||
|
{
|
||||||
|
switch (*p)
|
||||||
|
{
|
||||||
|
case ';':
|
||||||
|
g_string_append (out, "\\:");
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
g_string_append (out, "\\s");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
g_string_append (out, "\\\\");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
g_string_append (out, "\\r");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
g_string_append (out, "\\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_string_append_c (out, *p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_string_free (out, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmd_reply (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||||
|
{
|
||||||
|
char *msgid = word[2];
|
||||||
|
char *target = sess->channel;
|
||||||
|
char *text = word_eol[3];
|
||||||
|
char *escaped;
|
||||||
|
char *tags;
|
||||||
|
|
||||||
|
if (!reply_msgid_valid (msgid) || !*target || !*text)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (*text == ':')
|
||||||
|
text++;
|
||||||
|
|
||||||
|
if (!sess->server->connected || !client_tag_allowed (sess->server, "reply"))
|
||||||
|
{
|
||||||
|
notc_msg (sess);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped = client_tag_escape (msgid);
|
||||||
|
tags = g_strdup_printf ("+reply=%s", escaped);
|
||||||
|
sess->server->p_message_tagged (sess->server, tags, target, text);
|
||||||
|
if (!sess->server->have_echo_message)
|
||||||
|
{
|
||||||
|
session *target_sess = find_dialog (sess->server, target);
|
||||||
|
message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
|
||||||
|
no_tags.reply = msgid;
|
||||||
|
|
||||||
|
if (!target_sess)
|
||||||
|
target_sess = find_channel (sess->server, target);
|
||||||
|
if (target_sess)
|
||||||
|
inbound_chanmsg (target_sess->server, target_sess, target_sess->channel, target_sess->server->nick, text, TRUE, FALSE, &no_tags);
|
||||||
|
}
|
||||||
|
g_free (tags);
|
||||||
|
g_free (escaped);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmd_typing (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||||
|
{
|
||||||
|
char *state = word[2];
|
||||||
|
char *target = word[3];
|
||||||
|
char tags[32];
|
||||||
|
|
||||||
|
if (!*state)
|
||||||
|
state = "active";
|
||||||
|
|
||||||
|
if (!*target)
|
||||||
|
target = sess->channel;
|
||||||
|
|
||||||
|
if (!*target || (strcmp (state, "active") && strcmp (state, "paused") && strcmp (state, "done")))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!sess->server->connected || !client_tag_allowed (sess->server, "typing"))
|
||||||
|
{
|
||||||
|
notc_msg (sess);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
|
||||||
|
sess->server->p_tagmsg (sess->server, tags, target);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||||
{
|
{
|
||||||
@@ -2875,7 +3006,7 @@ cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
|||||||
newsess = find_dialog (sess->server, nick);
|
newsess = find_dialog (sess->server, nick);
|
||||||
if (!newsess)
|
if (!newsess)
|
||||||
newsess = find_channel (sess->server, nick);
|
newsess = find_channel (sess->server, nick);
|
||||||
if (newsess)
|
if (newsess && !sess->server->have_echo_message)
|
||||||
{
|
{
|
||||||
message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
|
message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
|
||||||
|
|
||||||
@@ -4139,9 +4270,11 @@ const struct commands xc_cmds[] = {
|
|||||||
#endif
|
#endif
|
||||||
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
|
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
|
||||||
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
|
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
|
||||||
|
{"REPLY", cmd_reply, 0, 0, 1, N_("REPLY <msgid> <message>, sends a reply-tagged message to the current channel or dialog")},
|
||||||
{"SAY", cmd_say, 0, 0, 1,
|
{"SAY", cmd_say, 0, 0, 1,
|
||||||
N_("SAY <text>, sends the text to the object in the current window")},
|
N_("SAY <text>, sends the text to the object in the current window")},
|
||||||
{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
|
{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
|
||||||
|
{"TYPING", cmd_typing, 0, 0, 1, N_("TYPING [active|paused|done] [target], sends a typing notification")},
|
||||||
#ifdef USE_OPENSSL
|
#ifdef USE_OPENSSL
|
||||||
{"SERVCHAN", cmd_servchan, 0, 0, 1,
|
{"SERVCHAN", cmd_servchan, 0, 0, 1,
|
||||||
N_("SERVCHAN [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
|
N_("SERVCHAN [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
|
||||||
@@ -4674,6 +4807,7 @@ handle_say (session *sess, char *text, int check_spch)
|
|||||||
|
|
||||||
while ((split_text = split_up_text (sess, text + offset, cmd_length, split_text)))
|
while ((split_text = split_up_text (sess, text + offset, cmd_length, split_text)))
|
||||||
{
|
{
|
||||||
|
if (!sess->server->have_echo_message)
|
||||||
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
|
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
|
||||||
split_text, TRUE, FALSE, &no_tags);
|
split_text, TRUE, FALSE, &no_tags);
|
||||||
sess->server->p_message (sess->server, sess->channel, split_text);
|
sess->server->p_message (sess->server, sess->channel, split_text);
|
||||||
@@ -4684,6 +4818,7 @@ handle_say (session *sess, char *text, int check_spch)
|
|||||||
g_free (split_text);
|
g_free (split_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sess->server->have_echo_message)
|
||||||
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
|
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
|
||||||
text + offset, TRUE, FALSE, &no_tags);
|
text + offset, TRUE, FALSE, &no_tags);
|
||||||
sess->server->p_message (sess->server, sess->channel, text + offset);
|
sess->server->p_message (sess->server, sess->channel, text + offset);
|
||||||
@@ -4733,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;
|
||||||
|
|||||||
@@ -362,6 +362,18 @@ irc_message (server *serv, char *channel, char *text)
|
|||||||
tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
|
tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_message_tagged (server *serv, char *tags, char *channel, char *text)
|
||||||
|
{
|
||||||
|
tcp_sendf (serv, "@%s PRIVMSG %s :%s\r\n", tags, channel, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_tagmsg (server *serv, char *tags, char *target)
|
||||||
|
{
|
||||||
|
tcp_sendf (serv, "@%s TAGMSG %s\r\n", tags, target);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_action (server *serv, char *channel, char *act)
|
irc_action (server *serv, char *channel, char *act)
|
||||||
{
|
{
|
||||||
@@ -425,7 +437,7 @@ static int
|
|||||||
irc_raw (server *serv, char *raw)
|
irc_raw (server *serv, char *raw)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
char tbuf[4096];
|
char tbuf[8704];
|
||||||
if (*raw)
|
if (*raw)
|
||||||
{
|
{
|
||||||
len = strlen (raw);
|
len = strlen (raw);
|
||||||
@@ -1009,6 +1021,40 @@ process_numeric (session * sess, int n,
|
|||||||
|
|
||||||
/* handle named messages that starts with a ':' */
|
/* handle named messages that starts with a ':' */
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
emit_standard_reply (session *sess, const char *type, char *command, char *code, char *text, const message_tags_data *tags_data)
|
||||||
|
{
|
||||||
|
int is_global = g_strcmp0 (command, "*") == 0;
|
||||||
|
|
||||||
|
if (!text)
|
||||||
|
text = "";
|
||||||
|
if (*text == ':')
|
||||||
|
text++;
|
||||||
|
|
||||||
|
if (!strcmp (type, "FAIL"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
else if (!strcmp (type, "WARN"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
else if (!strcmp (type, "NOTE"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
||||||
const message_tags_data *tags_data)
|
const message_tags_data *tags_data)
|
||||||
@@ -1322,11 +1368,24 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
|||||||
{
|
{
|
||||||
if (ignore_check (word[1], IG_PRIV))
|
if (ignore_check (word[1], IG_PRIV))
|
||||||
return;
|
return;
|
||||||
|
if (serv->have_echo_message && !serv->p_cmp (nick, serv->nick))
|
||||||
|
{
|
||||||
|
session *target_sess = find_dialog (serv, to);
|
||||||
|
|
||||||
|
if (!target_sess)
|
||||||
|
target_sess = find_channel (serv, to);
|
||||||
|
if (target_sess)
|
||||||
|
inbound_chanmsg (serv, target_sess, target_sess->channel, nick, text, TRUE, tags_data->identified, tags_data);
|
||||||
|
else if (serv->front_session)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_MSGSEND, serv->front_session, to, text, NULL, NULL, 0, tags_data->timestamp);
|
||||||
|
} else
|
||||||
|
{
|
||||||
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
|
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case WORDL('T','O','P','I'):
|
case WORDL('T','O','P','I'):
|
||||||
@@ -1335,6 +1394,29 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
|||||||
tags_data);
|
tags_data);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case WORDL('T','A','G','M'):
|
||||||
|
if (tags_data->typing)
|
||||||
|
{
|
||||||
|
session *typing_sess = NULL;
|
||||||
|
char *to = word[3];
|
||||||
|
|
||||||
|
if (is_channel (serv, to))
|
||||||
|
typing_sess = find_channel (serv, to);
|
||||||
|
else if (!serv->p_cmp (to, serv->nick))
|
||||||
|
typing_sess = find_dialog (serv, nick);
|
||||||
|
if (!typing_sess)
|
||||||
|
typing_sess = sess;
|
||||||
|
if (!serv->p_cmp (nick, serv->nick))
|
||||||
|
return;
|
||||||
|
if (!strcmp (tags_data->typing, "done"))
|
||||||
|
fe_set_typing (typing_sess, nick, "done");
|
||||||
|
else if (!strcmp (tags_data->typing, "paused"))
|
||||||
|
fe_set_typing (typing_sess, nick, "paused");
|
||||||
|
else if (!strcmp (tags_data->typing, "active"))
|
||||||
|
fe_set_typing (typing_sess, nick, "active");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
case WORDL('W','A','L','L'):
|
case WORDL('W','A','L','L'):
|
||||||
text = word_eol[3];
|
text = word_eol[3];
|
||||||
if (*text == ':')
|
if (*text == ':')
|
||||||
@@ -1431,6 +1513,11 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol
|
|||||||
inbound_sasl_authenticate (sess->server, word_eol[2]);
|
inbound_sasl_authenticate (sess->server, word_eol[2]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!strncmp (buf, "FAIL ", 5) || !strncmp (buf, "WARN ", 5) || !strncmp (buf, "NOTE ", 5))
|
||||||
|
{
|
||||||
|
emit_standard_reply (sess, word_eol[1], word_eol[2], word_eol[3], word_eol[4], tags_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, buf, sess->server->servername,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, buf, sess->server->servername,
|
||||||
rawname, NULL, 0, tags_data->timestamp);
|
rawname, NULL, 0, tags_data->timestamp);
|
||||||
@@ -1533,39 +1620,130 @@ handle_message_tag_time (const char *time, message_tags_data *tags_data)
|
|||||||
*
|
*
|
||||||
* See http://ircv3.atheme.org/specification/message-tags-3.2
|
* See http://ircv3.atheme.org/specification/message-tags-3.2
|
||||||
*/
|
*/
|
||||||
|
static char *
|
||||||
|
message_tag_unescape (const char *value)
|
||||||
|
{
|
||||||
|
GString *out;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (!*value)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
out = g_string_sized_new (strlen (value));
|
||||||
|
|
||||||
|
for (p = value; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p != '\\')
|
||||||
|
{
|
||||||
|
g_string_append_c (out, *p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
p++;
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (*p)
|
||||||
|
{
|
||||||
|
case ':':
|
||||||
|
g_string_append_c (out, ';');
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
g_string_append_c (out, ' ');
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
g_string_append_c (out, '\\');
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
g_string_append_c (out, '\r');
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
g_string_append_c (out, '\n');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_string_append_c (out, *p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = out->str;
|
||||||
|
if (!g_utf8_validate (value, -1, NULL))
|
||||||
|
{
|
||||||
|
g_string_free (out, TRUE);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_string_free (out, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
handle_message_tags (server *serv, const char *tags_str,
|
handle_message_tags (server *serv, const char *tags_str,
|
||||||
message_tags_data *tags_data)
|
message_tags_data *tags_data)
|
||||||
{
|
{
|
||||||
char **tags;
|
char **tags;
|
||||||
|
char *time = NULL;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* FIXME We might want to avoid the allocation overhead here since
|
|
||||||
* this might be called for every message from the server.
|
|
||||||
*/
|
|
||||||
tags = g_strsplit (tags_str, ";", 0);
|
tags = g_strsplit (tags_str, ";", 0);
|
||||||
|
|
||||||
for (i=0; tags[i]; i++)
|
for (i = 0; tags[i]; i++)
|
||||||
{
|
{
|
||||||
char *key = tags[i];
|
char *key = tags[i];
|
||||||
char *value = strchr (tags[i], '=');
|
char *raw_value = strchr (tags[i], '=');
|
||||||
|
char *value = NULL;
|
||||||
|
|
||||||
if (!value)
|
if (!*key)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
*value = '\0';
|
if (raw_value)
|
||||||
value++;
|
{
|
||||||
|
*raw_value = '\0';
|
||||||
if (serv->have_account_tag && !strcmp (key, "account"))
|
raw_value++;
|
||||||
tags_data->account = g_strdup (value);
|
value = message_tag_unescape (raw_value);
|
||||||
|
|
||||||
if (serv->have_idmsg && strcmp (key, "solanum.chat/identified"))
|
|
||||||
tags_data->identified = TRUE;
|
|
||||||
|
|
||||||
if (serv->have_server_time && !strcmp (key, "time"))
|
|
||||||
handle_message_tag_time (value, tags_data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serv->have_account_tag && !strcmp (key, "account"))
|
||||||
|
{
|
||||||
|
g_free (tags_data->account);
|
||||||
|
tags_data->account = value;
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
else if (serv->have_idmsg && !strcmp (key, "solanum.chat/identified"))
|
||||||
|
{
|
||||||
|
tags_data->identified = TRUE;
|
||||||
|
}
|
||||||
|
else if (serv->have_server_time && !strcmp (key, "time"))
|
||||||
|
{
|
||||||
|
g_free (time);
|
||||||
|
time = value;
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, "msgid"))
|
||||||
|
{
|
||||||
|
g_free (tags_data->msgid);
|
||||||
|
tags_data->msgid = value;
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, "+reply"))
|
||||||
|
{
|
||||||
|
g_free (tags_data->reply);
|
||||||
|
tags_data->reply = value;
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
else if (!strcmp (key, "+typing"))
|
||||||
|
{
|
||||||
|
g_free (tags_data->typing);
|
||||||
|
tags_data->typing = value;
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time)
|
||||||
|
handle_message_tag_time (time, tags_data);
|
||||||
|
|
||||||
|
g_free (time);
|
||||||
g_strfreev (tags);
|
g_strfreev (tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1667,6 +1845,9 @@ void
|
|||||||
message_tags_data_free (message_tags_data *tags_data)
|
message_tags_data_free (message_tags_data *tags_data)
|
||||||
{
|
{
|
||||||
g_clear_pointer (&tags_data->account, g_free);
|
g_clear_pointer (&tags_data->account, g_free);
|
||||||
|
g_clear_pointer (&tags_data->msgid, g_free);
|
||||||
|
g_clear_pointer (&tags_data->reply, g_free);
|
||||||
|
g_clear_pointer (&tags_data->typing, g_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1696,6 +1877,8 @@ proto_fill_her_up (server *serv)
|
|||||||
serv->p_set_back = irc_set_back;
|
serv->p_set_back = irc_set_back;
|
||||||
serv->p_set_away = irc_set_away;
|
serv->p_set_away = irc_set_away;
|
||||||
serv->p_message = irc_message;
|
serv->p_message = irc_message;
|
||||||
|
serv->p_message_tagged = irc_message_tagged;
|
||||||
|
serv->p_tagmsg = irc_tagmsg;
|
||||||
serv->p_action = irc_action;
|
serv->p_action = irc_action;
|
||||||
serv->p_notice = irc_notice;
|
serv->p_notice = irc_notice;
|
||||||
serv->p_topic = irc_topic;
|
serv->p_topic = irc_topic;
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
NULL, /* account name */ \
|
NULL, /* account name */ \
|
||||||
FALSE, /* identified to nick */ \
|
FALSE, /* identified to nick */ \
|
||||||
(time_t)0, /* timestamp */ \
|
(time_t)0, /* timestamp */ \
|
||||||
|
NULL, \
|
||||||
|
NULL, \
|
||||||
|
NULL, \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define STRIP_COLON(word, word_eol, idx) (word)[(idx)][0] == ':' ? (word_eol)[(idx)]+1 : (word)[(idx)]
|
#define STRIP_COLON(word, word_eol, idx) (word)[(idx)][0] == ':' ? (word_eol)[(idx)]+1 : (word)[(idx)]
|
||||||
@@ -41,6 +44,9 @@ typedef struct
|
|||||||
char *account;
|
char *account;
|
||||||
gboolean identified;
|
gboolean identified;
|
||||||
time_t timestamp;
|
time_t timestamp;
|
||||||
|
char *msgid;
|
||||||
|
char *reply;
|
||||||
|
char *typing;
|
||||||
} message_tags_data;
|
} message_tags_data;
|
||||||
|
|
||||||
void message_tags_data_free (message_tags_data *tags_data);
|
void message_tags_data_free (message_tags_data *tags_data);
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
|
|||||||
X509 *current_cert = X509_STORE_CTX_get_current_cert (ctx);
|
X509 *current_cert = X509_STORE_CTX_get_current_cert (ctx);
|
||||||
|
|
||||||
if (!current_cert)
|
if (!current_cert)
|
||||||
return TRUE;
|
return ok;
|
||||||
|
|
||||||
X509_NAME_oneline (X509_get_subject_name (current_cert),
|
X509_NAME_oneline (X509_get_subject_name (current_cert),
|
||||||
subject, sizeof (subject));
|
subject, sizeof (subject));
|
||||||
@@ -534,13 +534,24 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
|
|||||||
g_snprintf (buf, sizeof (buf), "* Issuer: %s", issuer);
|
g_snprintf (buf, sizeof (buf), "* Issuer: %s", issuer);
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
||||||
|
|
||||||
return TRUE;
|
if (!ok)
|
||||||
|
{
|
||||||
|
int err = X509_STORE_CTX_get_error (ctx);
|
||||||
|
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
||||||
|
X509_verify_cert_error_string (err), err);
|
||||||
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
||||||
|
|
||||||
|
if (g_sess && g_sess->server->accept_invalid_cert)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ssl_do_connect (server * serv)
|
ssl_do_connect (server * serv)
|
||||||
{
|
{
|
||||||
char buf[256]; // ERR_error_string() MUST have this size
|
char buf[256];
|
||||||
|
|
||||||
g_sess = serv->server_session;
|
g_sess = serv->server_session;
|
||||||
|
|
||||||
@@ -559,9 +570,10 @@ ssl_do_connect (server * serv)
|
|||||||
if (SSL_connect (serv->ssl) <= 0)
|
if (SSL_connect (serv->ssl) <= 0)
|
||||||
{
|
{
|
||||||
char err_buf[128];
|
char err_buf[128];
|
||||||
int err;
|
int err, ssl_err;
|
||||||
|
|
||||||
g_sess = NULL;
|
g_sess = NULL;
|
||||||
|
ssl_err = SSL_get_error (serv->ssl, -1);
|
||||||
if ((err = ERR_get_error ()) > 0)
|
if ((err = ERR_get_error ()) > 0)
|
||||||
{
|
{
|
||||||
ERR_error_string (err, err_buf);
|
ERR_error_string (err, err_buf);
|
||||||
@@ -571,6 +583,8 @@ ssl_do_connect (server * serv)
|
|||||||
|
|
||||||
if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER)
|
if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER)
|
||||||
PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n"));
|
PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n"));
|
||||||
|
else if (ssl_err == SSL_ERROR_SSL)
|
||||||
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, "* TLS handshake rejected by protocol/certificate/cipher policy", NULL, NULL, NULL, 0);
|
||||||
|
|
||||||
server_cleanup (serv);
|
server_cleanup (serv);
|
||||||
|
|
||||||
@@ -649,30 +663,18 @@ ssl_do_connect (server * serv)
|
|||||||
int hostname_err;
|
int hostname_err;
|
||||||
if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0)
|
if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0)
|
||||||
{
|
{
|
||||||
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname? (%d)%s",
|
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
||||||
hostname_err, serv->accept_invalid_cert ? " -- Ignored" : "");
|
hostname_err);
|
||||||
if (serv->accept_invalid_cert)
|
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
|
||||||
else
|
if (!serv->accept_invalid_cert)
|
||||||
goto conn_fail;
|
goto conn_fail;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
|
||||||
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
|
|
||||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
|
||||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
|
||||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
|
||||||
if (serv->accept_invalid_cert)
|
|
||||||
{
|
|
||||||
g_snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored",
|
|
||||||
X509_verify_cert_error_string (verify_error),
|
|
||||||
verify_error);
|
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
|
|
||||||
NULL, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
|
if (serv->accept_invalid_cert)
|
||||||
|
break;
|
||||||
|
|
||||||
g_snprintf (buf, sizeof (buf), "%s.? (%d)",
|
g_snprintf (buf, sizeof (buf), "%s.? (%d)",
|
||||||
X509_verify_cert_error_string (verify_error),
|
X509_verify_cert_error_string (verify_error),
|
||||||
verify_error);
|
verify_error);
|
||||||
@@ -930,7 +932,7 @@ server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
|
|||||||
break;
|
break;
|
||||||
case '5': /* prefs ip discovered */
|
case '5': /* prefs ip discovered */
|
||||||
waitline2 (source, tbuf, sizeof tbuf);
|
waitline2 (source, tbuf, sizeof tbuf);
|
||||||
prefs.local_ip = inet_addr (tbuf);
|
net_parse_ipv4 (tbuf, &prefs.local_ip);
|
||||||
break;
|
break;
|
||||||
case '7': /* prefs.hex_net_bind_host resolve failed */
|
case '7': /* prefs.hex_net_bind_host resolve failed */
|
||||||
sprintf (outbuf,
|
sprintf (outbuf,
|
||||||
@@ -1104,7 +1106,7 @@ traverse_socks (int print_fd, int sok, char *serverAddr, int port)
|
|||||||
sc.version = 4;
|
sc.version = 4;
|
||||||
sc.type = 1;
|
sc.type = 1;
|
||||||
sc.port = htons (port);
|
sc.port = htons (port);
|
||||||
sc.address = inet_addr (serverAddr);
|
net_parse_ipv4 (serverAddr, &sc.address);
|
||||||
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
|
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
|
||||||
|
|
||||||
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
|
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
|
||||||
@@ -1819,6 +1821,7 @@ void
|
|||||||
server_set_defaults (server *serv)
|
server_set_defaults (server *serv)
|
||||||
{
|
{
|
||||||
g_free (serv->chantypes);
|
g_free (serv->chantypes);
|
||||||
|
g_clear_pointer (&serv->clienttagdeny, g_free);
|
||||||
g_free (serv->chanmodes);
|
g_free (serv->chanmodes);
|
||||||
g_free (serv->nick_prefixes);
|
g_free (serv->nick_prefixes);
|
||||||
g_free (serv->nick_modes);
|
g_free (serv->nick_modes);
|
||||||
@@ -1853,6 +1856,8 @@ server_set_defaults (server *serv)
|
|||||||
serv->have_extjoin = FALSE;
|
serv->have_extjoin = FALSE;
|
||||||
serv->have_account_tag = FALSE;
|
serv->have_account_tag = FALSE;
|
||||||
serv->have_server_time = FALSE;
|
serv->have_server_time = FALSE;
|
||||||
|
serv->have_message_tags = FALSE;
|
||||||
|
serv->have_echo_message = FALSE;
|
||||||
serv->have_sasl = FALSE;
|
serv->have_sasl = FALSE;
|
||||||
serv->have_except = FALSE;
|
serv->have_except = FALSE;
|
||||||
serv->have_invite = FALSE;
|
serv->have_invite = FALSE;
|
||||||
@@ -1983,6 +1988,7 @@ server_free (server *serv)
|
|||||||
g_free (serv->nick_prefixes);
|
g_free (serv->nick_prefixes);
|
||||||
g_free (serv->chanmodes);
|
g_free (serv->chanmodes);
|
||||||
g_free (serv->chantypes);
|
g_free (serv->chantypes);
|
||||||
|
g_free (serv->clienttagdeny);
|
||||||
g_free (serv->bad_nick_prefixes);
|
g_free (serv->bad_nick_prefixes);
|
||||||
g_free (serv->last_away_reason);
|
g_free (serv->last_away_reason);
|
||||||
g_free (serv->encoding);
|
g_free (serv->encoding);
|
||||||
|
|||||||
@@ -86,15 +86,17 @@ _SSL_context_init (void (*info_cb_func))
|
|||||||
|
|
||||||
SSLeay_add_ssl_algorithms ();
|
SSLeay_add_ssl_algorithms ();
|
||||||
SSL_load_error_strings ();
|
SSL_load_error_strings ();
|
||||||
ctx = SSL_CTX_new (SSLv23_client_method ());
|
ctx = SSL_CTX_new (TLS_client_method ());
|
||||||
|
|
||||||
SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
|
SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
|
||||||
SSL_CTX_set_timeout (ctx, 300);
|
SSL_CTX_set_timeout (ctx, 300);
|
||||||
SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3
|
SSL_CTX_set_options (ctx, SSL_OP_NO_COMPRESSION
|
||||||
|SSL_OP_NO_COMPRESSION
|
|
||||||
|SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE
|
|SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE
|
||||||
|SSL_OP_NO_TICKET
|
|SSL_OP_NO_TICKET
|
||||||
|SSL_OP_CIPHER_SERVER_PREFERENCE);
|
|SSL_OP_NO_RENEGOTIATION);
|
||||||
|
SSL_CTX_set_min_proto_version (ctx, TLS1_2_VERSION);
|
||||||
|
SSL_CTX_set_cipher_list (ctx, "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!DSS");
|
||||||
|
SSL_CTX_set_ciphersuites (ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256");
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */
|
#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */
|
||||||
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
|
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
|
||||||
@@ -152,7 +154,7 @@ int
|
|||||||
_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
|
_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
|
||||||
{
|
{
|
||||||
X509 *peer_cert;
|
X509 *peer_cert;
|
||||||
X509_PUBKEY *key;
|
const X509_PUBKEY *key;
|
||||||
X509_ALGOR *algor = NULL;
|
X509_ALGOR *algor = NULL;
|
||||||
EVP_PKEY *peer_pkey;
|
EVP_PKEY *peer_pkey;
|
||||||
char notBefore[64];
|
char notBefore[64];
|
||||||
@@ -311,7 +313,7 @@ _SSL_socket (SSL_CTX *ctx, int sd)
|
|||||||
#else
|
#else
|
||||||
method = SSL_CTX_get_ssl_method (ctx);
|
method = SSL_CTX_get_ssl_method (ctx);
|
||||||
#endif
|
#endif
|
||||||
if (method == SSLv23_client_method())
|
if (method == TLS_client_method())
|
||||||
SSL_set_connect_state (ssl);
|
SSL_set_connect_state (ssl);
|
||||||
else
|
else
|
||||||
SSL_set_accept_state(ssl);
|
SSL_set_accept_state(ssl);
|
||||||
@@ -348,14 +350,13 @@ _SSL_close (SSL * ssl)
|
|||||||
{
|
{
|
||||||
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
||||||
SSL_free (ssl);
|
SSL_free (ssl);
|
||||||
|
#if !defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
#ifdef HAVE_ERR_REMOVE_THREAD_STATE
|
#ifdef HAVE_ERR_REMOVE_THREAD_STATE
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
||||||
/* OpenSSL handles this itself in 1.1+ and this is a no-op */
|
|
||||||
ERR_remove_thread_state (NULL);
|
ERR_remove_thread_state (NULL);
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
ERR_remove_state (0);
|
ERR_remove_state (0);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hostname validation code based on OpenBSD's libtls. */
|
/* Hostname validation code based on OpenBSD's libtls. */
|
||||||
@@ -506,9 +507,12 @@ _SSL_check_subject_altname (X509 *cert, const char *host)
|
|||||||
static int
|
static int
|
||||||
_SSL_check_common_name (X509 *cert, const char *host)
|
_SSL_check_common_name (X509 *cert, const char *host)
|
||||||
{
|
{
|
||||||
X509_NAME *name;
|
const X509_NAME *name;
|
||||||
char *common_name = NULL;
|
const X509_NAME_ENTRY *entry;
|
||||||
|
const ASN1_STRING *common_name_asn1;
|
||||||
|
const unsigned char *common_name;
|
||||||
int common_name_len;
|
int common_name_len;
|
||||||
|
int common_name_index;
|
||||||
int rv = -1;
|
int rv = -1;
|
||||||
GInetAddress *addr;
|
GInetAddress *addr;
|
||||||
|
|
||||||
@@ -516,16 +520,27 @@ _SSL_check_common_name (X509 *cert, const char *host)
|
|||||||
if (name == NULL)
|
if (name == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0);
|
common_name_index = X509_NAME_get_index_by_NID (name, NID_commonName, -1);
|
||||||
if (common_name_len < 0)
|
if (common_name_index < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
common_name = g_malloc0 (common_name_len + 1);
|
entry = X509_NAME_get_entry (name, common_name_index);
|
||||||
|
if (entry == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1);
|
common_name_asn1 = X509_NAME_ENTRY_get_data (entry);
|
||||||
|
if (common_name_asn1 == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
#ifdef HAVE_ASN1_STRING_GET0_DATA
|
||||||
|
common_name = ASN1_STRING_get0_data (common_name_asn1);
|
||||||
|
#else
|
||||||
|
common_name = ASN1_STRING_data (common_name_asn1);
|
||||||
|
#endif
|
||||||
|
common_name_len = ASN1_STRING_length (common_name_asn1);
|
||||||
|
|
||||||
/* NUL bytes in CN? */
|
/* NUL bytes in CN? */
|
||||||
if (common_name_len != (int)strlen(common_name))
|
if (common_name_len != (int)strlen((const char *)common_name))
|
||||||
{
|
{
|
||||||
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
|
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
|
||||||
rv = -2;
|
rv = -2;
|
||||||
@@ -538,18 +553,17 @@ _SSL_check_common_name (X509 *cert, const char *host)
|
|||||||
* We don't want to attempt wildcard matching against IP
|
* We don't want to attempt wildcard matching against IP
|
||||||
* addresses, so perform a simple comparison here.
|
* addresses, so perform a simple comparison here.
|
||||||
*/
|
*/
|
||||||
if (g_strcmp0 (common_name, host) == 0)
|
if (g_strcmp0 ((const char *)common_name, host) == 0)
|
||||||
rv = 0;
|
rv = 0;
|
||||||
else
|
else
|
||||||
rv = -1;
|
rv = -1;
|
||||||
|
|
||||||
g_object_unref (addr);
|
g_object_unref (addr);
|
||||||
}
|
}
|
||||||
else if (_SSL_match_hostname (common_name, host) == 0)
|
else if (_SSL_match_hostname ((const char *)common_name, host) == 0)
|
||||||
rv = 0;
|
rv = 0;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
g_free(common_name);
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 ();
|
||||||
|
|||||||
@@ -1809,12 +1809,97 @@ format_event (session *sess, int index, char **args, char *o, gsize sizeofo, uns
|
|||||||
o[0] = 0;
|
o[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
text_event_without_hostmask_format (const char *format, int host_arg)
|
||||||
|
{
|
||||||
|
char token[3];
|
||||||
|
const char *arg;
|
||||||
|
const char *open;
|
||||||
|
const char *close;
|
||||||
|
const char *start;
|
||||||
|
char *out;
|
||||||
|
gsize prefix_len;
|
||||||
|
|
||||||
|
g_snprintf (token, sizeof (token), "$%d", host_arg);
|
||||||
|
arg = strstr (format, token);
|
||||||
|
if (!arg)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
open = arg;
|
||||||
|
while (open > format && *open != '(' && *open != '\n')
|
||||||
|
open--;
|
||||||
|
|
||||||
|
close = arg + strlen (token);
|
||||||
|
while (*close && *close != ')' && *close != '\n')
|
||||||
|
close++;
|
||||||
|
|
||||||
|
if (*open != '(' || *close != ')')
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
start = open;
|
||||||
|
if (start > format && start[-1] == ' ')
|
||||||
|
start--;
|
||||||
|
|
||||||
|
prefix_len = start - format;
|
||||||
|
out = g_malloc (prefix_len + strlen (close + 1) + 1);
|
||||||
|
memcpy (out, format, prefix_len);
|
||||||
|
strcpy (out + prefix_len, close + 1);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
display_event_string (session *sess, int event, char **args, char *format,
|
||||||
|
unsigned int stripcolor_args, time_t timestamp)
|
||||||
|
{
|
||||||
|
char *compiled;
|
||||||
|
char *saved;
|
||||||
|
char o[4096];
|
||||||
|
int max_arg;
|
||||||
|
|
||||||
|
if (pevt_build_string (format, &compiled, &max_arg) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
saved = pntevts[event];
|
||||||
|
pntevts[event] = compiled;
|
||||||
|
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
|
||||||
|
pntevts[event] = saved;
|
||||||
|
g_free (compiled);
|
||||||
|
|
||||||
|
if (o[0])
|
||||||
|
PrintTextTimeStamp (sess, o, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
display_event (session *sess, int event, char **args,
|
display_event (session *sess, int event, char **args,
|
||||||
unsigned int stripcolor_args, time_t timestamp)
|
unsigned int stripcolor_args, time_t timestamp)
|
||||||
{
|
{
|
||||||
char o[4096];
|
char o[4096];
|
||||||
|
char *format;
|
||||||
|
char *host;
|
||||||
|
int host_arg;
|
||||||
|
|
||||||
|
if (prefs.hex_irc_hide_join_part_hostmask &&
|
||||||
|
(event == XP_TE_JOIN || event == XP_TE_PART || event == XP_TE_PARTREASON))
|
||||||
|
{
|
||||||
|
host_arg = event == XP_TE_JOIN ? 3 : 2;
|
||||||
|
format = text_event_without_hostmask_format (pntevts_text[event], host_arg);
|
||||||
|
if (format)
|
||||||
|
{
|
||||||
|
display_event_string (sess, event, args, format, stripcolor_args, timestamp);
|
||||||
|
g_free (format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
host = args[host_arg];
|
||||||
|
args[host_arg] = "";
|
||||||
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
|
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
|
||||||
|
args[host_arg] = host;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
|
||||||
|
}
|
||||||
|
|
||||||
if (o[0])
|
if (o[0])
|
||||||
PrintTextTimeStamp (sess, o, timestamp);
|
PrintTextTimeStamp (sess, o, timestamp);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,7 @@ struct User
|
|||||||
char *servername;
|
char *servername;
|
||||||
char *account;
|
char *account;
|
||||||
time_t lasttalk;
|
time_t lasttalk;
|
||||||
|
time_t typing_time;
|
||||||
unsigned int access; /* axs bit field */
|
unsigned int access; /* axs bit field */
|
||||||
char prefix[2]; /* @ + % */
|
char prefix[2]; /* @ + % */
|
||||||
unsigned int op:1;
|
unsigned int op:1;
|
||||||
@@ -39,6 +40,7 @@ struct User
|
|||||||
unsigned int me:1;
|
unsigned int me:1;
|
||||||
unsigned int away:1;
|
unsigned int away:1;
|
||||||
unsigned int selected:1;
|
unsigned int selected:1;
|
||||||
|
unsigned int typing:2;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define USERACCESS_SIZE 12
|
#define USERACCESS_SIZE 12
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ lag_check (void)
|
|||||||
time_t now = time (0);
|
time_t now = time (0);
|
||||||
time_t lag;
|
time_t lag;
|
||||||
time_t ping_age;
|
time_t ping_age;
|
||||||
|
unsigned long ping_timeout;
|
||||||
|
|
||||||
tim = make_ping_time ();
|
tim = make_ping_time ();
|
||||||
|
|
||||||
@@ -389,8 +390,11 @@ lag_check (void)
|
|||||||
if (serv->connected && serv->end_of_motd)
|
if (serv->connected && serv->end_of_motd)
|
||||||
{
|
{
|
||||||
lag = now - serv->ping_recv;
|
lag = now - serv->ping_recv;
|
||||||
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0 && lag > prefs.hex_net_ping_timeout && lag > 0)
|
ping_timeout = (unsigned long) prefs.hex_net_ping_timeout * 1000;
|
||||||
|
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0
|
||||||
|
&& tim - serv->lag_sent > ping_timeout)
|
||||||
{
|
{
|
||||||
|
lag = (tim - serv->lag_sent) / 1000;
|
||||||
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
|
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
|
||||||
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
|
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
|
||||||
NULL, NULL, 0);
|
NULL, NULL, 0);
|
||||||
@@ -793,7 +797,13 @@ session_free (session *killsess)
|
|||||||
|
|
||||||
send_quit_or_part (killsess);
|
send_quit_or_part (killsess);
|
||||||
|
|
||||||
|
if (killsess->typing_timeout_tag)
|
||||||
|
fe_timeout_remove (killsess->typing_timeout_tag);
|
||||||
|
if (killsess->typing_animation_tag)
|
||||||
|
fe_timeout_remove (killsess->typing_animation_tag);
|
||||||
|
|
||||||
history_free (&killsess->history);
|
history_free (&killsess->history);
|
||||||
|
reply_cache_free (killsess);
|
||||||
g_free (killsess->topic);
|
g_free (killsess->topic);
|
||||||
g_free (killsess->current_modes);
|
g_free (killsess->current_modes);
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_gui_input_style;
|
unsigned int hex_gui_input_style;
|
||||||
unsigned int hex_gui_join_dialog;
|
unsigned int hex_gui_join_dialog;
|
||||||
unsigned int hex_gui_mode_buttons;
|
unsigned int hex_gui_mode_buttons;
|
||||||
|
unsigned int hex_gui_mode_buttons_inline;
|
||||||
unsigned int hex_gui_quit_dialog;
|
unsigned int hex_gui_quit_dialog;
|
||||||
/* unsigned int hex_gui_single; */
|
/* unsigned int hex_gui_single; */
|
||||||
unsigned int hex_gui_slist_fav;
|
unsigned int hex_gui_slist_fav;
|
||||||
@@ -148,6 +149,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_gui_tab_sort;
|
unsigned int hex_gui_tab_sort;
|
||||||
unsigned int hex_gui_tab_utils;
|
unsigned int hex_gui_tab_utils;
|
||||||
unsigned int hex_gui_topicbar;
|
unsigned int hex_gui_topicbar;
|
||||||
|
unsigned int hex_gui_topicbar_multiline;
|
||||||
unsigned int hex_gui_tray;
|
unsigned int hex_gui_tray;
|
||||||
unsigned int hex_gui_tray_away;
|
unsigned int hex_gui_tray_away;
|
||||||
unsigned int hex_gui_tray_blink;
|
unsigned int hex_gui_tray_blink;
|
||||||
@@ -188,6 +190,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_irc_conf_mode;
|
unsigned int hex_irc_conf_mode;
|
||||||
unsigned int hex_irc_hidehost;
|
unsigned int hex_irc_hidehost;
|
||||||
unsigned int hex_irc_hide_nickchange;
|
unsigned int hex_irc_hide_nickchange;
|
||||||
|
unsigned int hex_irc_hide_join_part_hostmask;
|
||||||
unsigned int hex_irc_hide_version;
|
unsigned int hex_irc_hide_version;
|
||||||
unsigned int hex_irc_invisible;
|
unsigned int hex_irc_invisible;
|
||||||
unsigned int hex_irc_logging;
|
unsigned int hex_irc_logging;
|
||||||
@@ -385,6 +388,14 @@ typedef enum {
|
|||||||
TAB_STATE_NEW_HILIGHT = (1 << 2),
|
TAB_STATE_NEW_HILIGHT = (1 << 2),
|
||||||
} tab_state_flags;
|
} tab_state_flags;
|
||||||
|
|
||||||
|
typedef struct reply_item
|
||||||
|
{
|
||||||
|
char *msgid;
|
||||||
|
char *nick;
|
||||||
|
char *text;
|
||||||
|
time_t timestamp;
|
||||||
|
} reply_item;
|
||||||
|
|
||||||
typedef struct session
|
typedef struct session
|
||||||
{
|
{
|
||||||
/* Per-Channel Alerts */
|
/* Per-Channel Alerts */
|
||||||
@@ -426,8 +437,17 @@ typedef struct session
|
|||||||
char *quitreason;
|
char *quitreason;
|
||||||
char *topic;
|
char *topic;
|
||||||
char *current_modes; /* free() me */
|
char *current_modes; /* free() me */
|
||||||
|
GSList *reply_items;
|
||||||
|
char *reply_msgid;
|
||||||
|
char *reply_target;
|
||||||
|
char *reply_nick;
|
||||||
|
char *reply_text;
|
||||||
|
|
||||||
int mode_timeout_tag;
|
int mode_timeout_tag;
|
||||||
|
int typing_timeout_tag;
|
||||||
|
int typing_status;
|
||||||
|
int typing_animation_tag;
|
||||||
|
int typing_animation_frame;
|
||||||
|
|
||||||
struct session *lastlog_sess;
|
struct session *lastlog_sess;
|
||||||
struct nbexec *running_exec;
|
struct nbexec *running_exec;
|
||||||
@@ -491,6 +511,8 @@ typedef struct server
|
|||||||
void (*p_set_back)(struct server *);
|
void (*p_set_back)(struct server *);
|
||||||
void (*p_set_away)(struct server *, char *reason);
|
void (*p_set_away)(struct server *, char *reason);
|
||||||
void (*p_message)(struct server *, char *channel, char *text);
|
void (*p_message)(struct server *, char *channel, char *text);
|
||||||
|
void (*p_message_tagged)(struct server *, char *tags, char *channel, char *text);
|
||||||
|
void (*p_tagmsg)(struct server *, char *tags, char *target);
|
||||||
void (*p_action)(struct server *, char *channel, char *act);
|
void (*p_action)(struct server *, char *channel, char *act);
|
||||||
void (*p_notice)(struct server *, char *channel, char *text);
|
void (*p_notice)(struct server *, char *channel, char *text);
|
||||||
void (*p_topic)(struct server *, char *channel, char *topic);
|
void (*p_topic)(struct server *, char *channel, char *topic);
|
||||||
@@ -540,6 +562,7 @@ typedef struct server
|
|||||||
int loginmethod; /* see login_types[] */
|
int loginmethod; /* see login_types[] */
|
||||||
|
|
||||||
char *chantypes; /* for 005 numeric - free me */
|
char *chantypes; /* for 005 numeric - free me */
|
||||||
|
char *clienttagdeny;
|
||||||
char *chanmodes; /* for 005 numeric - free me */
|
char *chanmodes; /* for 005 numeric - free me */
|
||||||
char *nick_prefixes; /* e.g. "*@%+" */
|
char *nick_prefixes; /* e.g. "*@%+" */
|
||||||
char *nick_modes; /* e.g. "aohv" */
|
char *nick_modes; /* e.g. "aohv" */
|
||||||
@@ -602,6 +625,8 @@ typedef struct server
|
|||||||
unsigned int have_extjoin:1; /* cap extended-join */
|
unsigned int have_extjoin:1; /* cap extended-join */
|
||||||
unsigned int have_account_tag:1; /* cap account-tag */
|
unsigned int have_account_tag:1; /* cap account-tag */
|
||||||
unsigned int have_server_time:1; /* cap server-time */
|
unsigned int have_server_time:1; /* cap server-time */
|
||||||
|
unsigned int have_message_tags:1;
|
||||||
|
unsigned int have_echo_message:1;
|
||||||
unsigned int have_sasl:1; /* SASL capability */
|
unsigned int have_sasl:1; /* SASL capability */
|
||||||
unsigned int have_except:1; /* ban exemptions +e */
|
unsigned int have_except:1; /* ban exemptions +e */
|
||||||
unsigned int have_invite:1; /* invite exemptions +I */
|
unsigned int have_invite:1; /* invite exemptions +I */
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ cv_tabs_init (chanview *cv)
|
|||||||
GtkWidget *box;
|
GtkWidget *box;
|
||||||
GtkWidget *viewport;
|
GtkWidget *viewport;
|
||||||
GtkWidget *outer;
|
GtkWidget *outer;
|
||||||
|
GtkWidget *tree;
|
||||||
|
|
||||||
if (cv->vertical)
|
if (cv->vertical)
|
||||||
{
|
{
|
||||||
@@ -385,6 +386,11 @@ cv_tabs_init (chanview *cv)
|
|||||||
gtk_container_add (GTK_CONTAINER (viewport), box);
|
gtk_container_add (GTK_CONTAINER (viewport), box);
|
||||||
gtk_widget_show (box);
|
gtk_widget_show (box);
|
||||||
|
|
||||||
|
tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
|
||||||
|
gtk_widget_set_name (tree, "zoitechat-tree");
|
||||||
|
gtk_widget_set_no_show_all (tree, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (outer), tree, 0, 0, 0);
|
||||||
|
|
||||||
gtk_container_add (GTK_CONTAINER (cv->box), outer);
|
gtk_container_add (GTK_CONTAINER (cv->box), outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -812,18 +812,19 @@ fe_set_topic (session *sess, char *topic, char *stripped_topic)
|
|||||||
{
|
{
|
||||||
if (!sess->gui->is_tab || sess == current_tab)
|
if (!sess->gui->is_tab || sess == current_tab)
|
||||||
{
|
{
|
||||||
|
GtkTextBuffer *topic_buffer;
|
||||||
|
GtkTextIter start;
|
||||||
|
|
||||||
|
topic_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry));
|
||||||
if (prefs.hex_text_stripcolor_topic)
|
if (prefs.hex_text_stripcolor_topic)
|
||||||
{
|
gtk_text_buffer_set_text (topic_buffer, stripped_topic, -1);
|
||||||
gtk_text_buffer_set_text (
|
|
||||||
gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry)),
|
|
||||||
stripped_topic, -1);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
gtk_text_buffer_set_text (topic_buffer, topic, -1);
|
||||||
gtk_text_buffer_set_text (
|
|
||||||
gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry)),
|
gtk_text_buffer_get_start_iter (topic_buffer, &start);
|
||||||
topic, -1);
|
gtk_text_buffer_place_cursor (topic_buffer, &start);
|
||||||
}
|
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (sess->gui->topic_entry),
|
||||||
|
&start, 0.0, FALSE, 0.0, 0.0);
|
||||||
mg_set_topic_tip (sess);
|
mg_set_topic_tip (sess);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1356,10 +1357,6 @@ 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);
|
|
||||||
if (!opened)
|
|
||||||
{
|
|
||||||
g_clear_error (&error);
|
|
||||||
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
||||||
|
|
||||||
if (url_utf16 != NULL)
|
if (url_utf16 != NULL)
|
||||||
@@ -1367,6 +1364,12 @@ fe_open_url_inner (const char *url)
|
|||||||
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
||||||
g_free (url_utf16);
|
g_free (url_utf16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!opened)
|
||||||
|
{
|
||||||
|
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
|
||||||
|
if (!opened)
|
||||||
|
g_clear_error (&error);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ typedef struct session_gui
|
|||||||
*op_xpm, /* icon to the left of nickname */
|
*op_xpm, /* icon to the left of nickname */
|
||||||
*namelistinfo, /* label above userlist */
|
*namelistinfo, /* label above userlist */
|
||||||
*input_box,
|
*input_box,
|
||||||
|
*reply_box,
|
||||||
|
*reply_label,
|
||||||
*flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */
|
*flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */
|
||||||
*limit_entry, /* +l */
|
*limit_entry, /* +l */
|
||||||
*key_entry; /* +k */
|
*key_entry; /* +k */
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
#define ICON_FKEYS_DELETE "edit-delete"
|
#define ICON_FKEYS_DELETE "edit-delete"
|
||||||
#define ICON_FKEYS_CANCEL "dialog-cancel"
|
#define ICON_FKEYS_CANCEL "dialog-cancel"
|
||||||
#define ICON_FKEYS_SAVE "document-save"
|
#define ICON_FKEYS_SAVE "document-save"
|
||||||
|
#define ICON_FKEYS_RESET "edit-undo"
|
||||||
|
|
||||||
static void replace_handle (GtkWidget * wid);
|
static void replace_handle (GtkWidget * wid);
|
||||||
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
||||||
@@ -80,7 +81,7 @@ void key_action_tab_clean (void);
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Remember that the *number* of actions is this *plus* 1 --AGL */
|
/* Remember that the *number* of actions is this *plus* 1 --AGL */
|
||||||
#define KEY_MAX_ACTIONS 14
|
#define KEY_MAX_ACTIONS 16
|
||||||
|
|
||||||
struct key_binding
|
struct key_binding
|
||||||
{
|
{
|
||||||
@@ -105,7 +106,10 @@ struct gcomp_data
|
|||||||
};
|
};
|
||||||
|
|
||||||
static int key_load_kbs (void);
|
static int key_load_kbs (void);
|
||||||
|
static int key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list);
|
||||||
static int key_save_kbs (void);
|
static int key_save_kbs (void);
|
||||||
|
static void key_dialog_load (GtkListStore *store);
|
||||||
|
static void key_dialog_reset (GtkWidget *wid, gpointer userdata);
|
||||||
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
|
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
|
||||||
char *d1, char *d2,
|
char *d1, char *d2,
|
||||||
struct session *sess);
|
struct session *sess);
|
||||||
@@ -142,6 +146,13 @@ static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
|
|||||||
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
|
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
|
||||||
char *d1, char *d2,
|
char *d1, char *d2,
|
||||||
struct session *sess);
|
struct session *sess);
|
||||||
|
static int key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt,
|
||||||
|
char *d1, char *d2,
|
||||||
|
struct session *sess);
|
||||||
|
static int key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt,
|
||||||
|
char *d1, char *d2,
|
||||||
|
struct session *sess);
|
||||||
|
|
||||||
|
|
||||||
static GSList *keybind_list = NULL;
|
static GSList *keybind_list = NULL;
|
||||||
|
|
||||||
@@ -260,7 +271,7 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
{key_action_handle_command, "Run Command",
|
{key_action_handle_command, "Run Command",
|
||||||
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")},
|
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")},
|
||||||
{key_action_page_switch, "Change Page",
|
{key_action_page_switch, "Change Page",
|
||||||
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position. Set Data 1 to auto to switch to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
|
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page number, channel name, network/channel, or auto. If Data 2 is set to anything then numeric switches are relative to the current position. Auto switches to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
|
||||||
{key_action_insert, "Insert in Buffer",
|
{key_action_insert, "Insert in Buffer",
|
||||||
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")},
|
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")},
|
||||||
{key_action_scroll_page, "Scroll Page",
|
{key_action_scroll_page, "Scroll Page",
|
||||||
@@ -287,6 +298,10 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
N_("This command moves the current tab family to the right")},
|
N_("This command moves the current tab family to the right")},
|
||||||
{key_action_put_history, "Push input line into history",
|
{key_action_put_history, "Push input line into history",
|
||||||
N_("Push input line into history but doesn't send to server")},
|
N_("Push input line into history but doesn't send to server")},
|
||||||
|
{key_action_menu_shortcut, "Menu Shortcut",
|
||||||
|
N_("Runs one of the built-in menu shortcuts. Set Data 1 to: network-list, new-server-tab, new-server-window, close, quit, menu-toggle, user-list-toggle, fullscreen-toggle, away-toggle, reset-marker, move-marker, copy-selection, search-text, search-next, search-previous or contents.")},
|
||||||
|
{key_action_reopen_closed_tab, "Reopen Closed Tab",
|
||||||
|
N_("Reopens the most recently closed channel tab")},
|
||||||
};
|
};
|
||||||
|
|
||||||
#define default_kb_cfg \
|
#define default_kb_cfg \
|
||||||
@@ -329,7 +344,86 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
|
"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
|
"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
|
"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=F9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
|
"ACCEL=<Primary>s\nMenu Shortcut\nD1:network-list\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>t\nMenu Shortcut\nD1:new-server-tab\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>n\nMenu Shortcut\nD1:new-server-window\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>w\nMenu Shortcut\nD1:close\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>q\nMenu Shortcut\nD1:quit\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>F9\nMenu Shortcut\nD1:menu-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=F7\nMenu Shortcut\nD1:user-list-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=F11\nMenu Shortcut\nD1:fullscreen-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=<Alt>a\nMenu Shortcut\nD1:away-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>m\nMenu Shortcut\nD1:reset-marker\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>M\nMenu Shortcut\nD1:move-marker\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>C\nMenu Shortcut\nD1:copy-selection\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>f\nMenu Shortcut\nD1:search-text\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>g\nMenu Shortcut\nD1:search-next\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>G\nMenu Shortcut\nD1:search-previous\nD2!\n\n"\
|
||||||
|
"ACCEL=F1\nMenu Shortcut\nD1:contents\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>T\nReopen Closed Tab\nD1!\nD2!\n\n"
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
key_builtin_data_match (char *line, char *data)
|
||||||
|
{
|
||||||
|
if (line[2] == '!')
|
||||||
|
return data == NULL || data[0] == 0;
|
||||||
|
|
||||||
|
if (line[2] == ':')
|
||||||
|
return !strcmp (&line[3], data ? data : "");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
key_binding_is_builtin (struct key_binding *kb)
|
||||||
|
{
|
||||||
|
char *buf, *ibuf;
|
||||||
|
char *action;
|
||||||
|
int pnt = 0;
|
||||||
|
int state = 0;
|
||||||
|
gboolean match = FALSE;
|
||||||
|
gboolean d1_match = FALSE;
|
||||||
|
off_t size;
|
||||||
|
|
||||||
|
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
action = key_actions[kb->action].name;
|
||||||
|
ibuf = g_strdup (default_kb_cfg);
|
||||||
|
size = strlen (default_kb_cfg);
|
||||||
|
|
||||||
|
while (buf_get_line (ibuf, &buf, &pnt, size))
|
||||||
|
{
|
||||||
|
if (strlen (buf) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
state = 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
match = !strcmp (buf, action);
|
||||||
|
state = 2;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
d1_match = match && key_builtin_data_match (buf, kb->data1);
|
||||||
|
state = 3;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (d1_match && key_builtin_data_match (buf, kb->data2))
|
||||||
|
{
|
||||||
|
g_free (ibuf);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
state = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (ibuf);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
key_init ()
|
key_init ()
|
||||||
@@ -443,28 +537,6 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
|
|||||||
if (!list)
|
if (!list)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
current_sess = sess;
|
current_sess = sess;
|
||||||
if ((evt->state & GDK_CONTROL_MASK) &&
|
|
||||||
!(evt->state & (GDK_MOD1_MASK | GDK_META_MASK)))
|
|
||||||
{
|
|
||||||
if (!(evt->state & GDK_SHIFT_MASK) &&
|
|
||||||
(evt->keyval == GDK_KEY_w || evt->keyval == GDK_KEY_W))
|
|
||||||
{
|
|
||||||
if (sess->type == SESS_CHANNEL)
|
|
||||||
{
|
|
||||||
fe_close_window (sess);
|
|
||||||
g_signal_stop_emission_by_name (G_OBJECT (wid), "key-press-event");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((evt->state & GDK_SHIFT_MASK) &&
|
|
||||||
(evt->keyval == GDK_KEY_t || evt->keyval == GDK_KEY_T))
|
|
||||||
{
|
|
||||||
mg_reopen_closed_channel_tab ();
|
|
||||||
g_signal_stop_emission_by_name (G_OBJECT (wid), "key-press-event");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
|
if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -508,6 +580,31 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod)
|
||||||
|
{
|
||||||
|
struct key_binding *kb;
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
list = keybind_list;
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
kb = (struct key_binding*)list->data;
|
||||||
|
if (kb->action >= 0 && kb->action <= KEY_MAX_ACTIONS && kb->keyval != 0 && !strcmp (key_actions[kb->action].name, "Menu Shortcut") && kb->data1 && !strcmp (kb->data1, name))
|
||||||
|
{
|
||||||
|
*keyval = kb->keyval;
|
||||||
|
*mod = kb->mod;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
list = g_slist_next (list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ***** GUI code here ******************* */
|
/* ***** GUI code here ******************* */
|
||||||
|
|
||||||
@@ -518,6 +615,7 @@ enum
|
|||||||
ACTION_COLUMN,
|
ACTION_COLUMN,
|
||||||
D1_COLUMN,
|
D1_COLUMN,
|
||||||
D2_COLUMN,
|
D2_COLUMN,
|
||||||
|
CUSTOM_COLUMN,
|
||||||
N_COLUMNS
|
N_COLUMNS
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -642,15 +740,31 @@ key_dialog_keypress (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
|
|||||||
|
|
||||||
if (handled)
|
if (handled)
|
||||||
{
|
{
|
||||||
|
gboolean custom1, custom2;
|
||||||
|
|
||||||
sel = gtk_tree_view_get_selection (view);
|
sel = gtk_tree_view_get_selection (view);
|
||||||
gtk_tree_selection_get_selected (sel, &store, &iter1);
|
if (!gtk_tree_selection_get_selected (sel, &store, &iter1))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
path = gtk_tree_model_get_path (store, &iter1);
|
path = gtk_tree_model_get_path (store, &iter1);
|
||||||
if (delta == 1)
|
if (delta == 1)
|
||||||
gtk_tree_path_next (path);
|
gtk_tree_path_next (path);
|
||||||
else
|
else if (!gtk_tree_path_prev (path))
|
||||||
gtk_tree_path_prev (path);
|
{
|
||||||
gtk_tree_model_get_iter (store, &iter2, path);
|
|
||||||
gtk_tree_path_free (path);
|
gtk_tree_path_free (path);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gtk_tree_model_get_iter (store, &iter2, path))
|
||||||
|
{
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
gtk_tree_model_get (store, &iter1, CUSTOM_COLUMN, &custom1, -1);
|
||||||
|
gtk_tree_model_get (store, &iter2, CUSTOM_COLUMN, &custom2, -1);
|
||||||
|
if (custom1 && custom2)
|
||||||
gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
|
gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,14 +777,23 @@ key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
|
|||||||
GtkTreeModel *model;
|
GtkTreeModel *model;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
GtkXText *xtext;
|
GtkXText *xtext;
|
||||||
|
GtkWidget *delete_button;
|
||||||
char *actiontext;
|
char *actiontext;
|
||||||
|
gboolean custom;
|
||||||
int action;
|
int action;
|
||||||
|
|
||||||
|
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
|
||||||
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
|
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
|
||||||
|
{
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
||||||
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, -1);
|
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, CUSTOM_COLUMN, &custom, -1);
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, custom);
|
||||||
|
|
||||||
if (actiontext)
|
if (actiontext)
|
||||||
{
|
{
|
||||||
@@ -745,7 +868,10 @@ key_dialog_save (GtkWidget *wid, gpointer userdata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key_save_kbs () == 0)
|
if (key_save_kbs () == 0)
|
||||||
|
{
|
||||||
|
menu_update_quit_accel ();
|
||||||
key_dialog_close (wid, NULL);
|
key_dialog_close (wid, NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -758,6 +884,7 @@ key_dialog_add (GtkWidget *wid, gpointer userdata)
|
|||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
|
||||||
gtk_list_store_append (store, &iter);
|
gtk_list_store_append (store, &iter);
|
||||||
|
gtk_list_store_set (store, &iter, CUSTOM_COLUMN, TRUE, -1);
|
||||||
|
|
||||||
/* make sure the new row is visible and selected */
|
/* make sure the new row is visible and selected */
|
||||||
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
||||||
@@ -767,6 +894,134 @@ key_dialog_add (GtkWidget *wid, gpointer userdata)
|
|||||||
gtk_tree_path_free (path);
|
gtk_tree_path_free (path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
key_binding_signature (const char *action, const char *data1, const char *data2)
|
||||||
|
{
|
||||||
|
return g_strdup_printf ("%s\\n%s\\n%s", action ? action : "", data1 ? data1 : "", data2 ? data2 : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_dialog_reset_count (GHashTable *table, const char *key)
|
||||||
|
{
|
||||||
|
return GPOINTER_TO_INT (g_hash_table_lookup (table, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_dialog_reset_increment (GHashTable *table, char *key)
|
||||||
|
{
|
||||||
|
g_hash_table_replace (table, key, GINT_TO_POINTER (key_dialog_reset_count (table, key) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_dialog_reset (GtkWidget *wid, gpointer userdata)
|
||||||
|
{
|
||||||
|
GtkListStore *store = GTK_LIST_STORE (get_store ());
|
||||||
|
GtkListStore *custom_store;
|
||||||
|
GtkTreeIter iter, custom_iter;
|
||||||
|
GtkWidget *delete_button;
|
||||||
|
GHashTable *default_counts, *seen_counts;
|
||||||
|
GSList *list = NULL, *old_list, *default_iter;
|
||||||
|
struct key_binding *kb;
|
||||||
|
gboolean custom, keep;
|
||||||
|
char *key, *accel, *action, *data1, *data2, *signature;
|
||||||
|
|
||||||
|
if (key_load_kbs_from_buffer (g_strdup (default_kb_cfg), strlen (default_kb_cfg), &list) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
default_counts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
seen_counts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
for (default_iter = list; default_iter; default_iter = g_slist_next (default_iter))
|
||||||
|
{
|
||||||
|
kb = default_iter->data;
|
||||||
|
signature = key_binding_signature (key_actions[kb->action].name, kb->data1, kb->data2);
|
||||||
|
key_dialog_reset_increment (default_counts, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
custom_store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
|
||||||
|
KEY_COLUMN, &key,
|
||||||
|
ACCEL_COLUMN, &accel,
|
||||||
|
ACTION_COLUMN, &action,
|
||||||
|
D1_COLUMN, &data1,
|
||||||
|
D2_COLUMN, &data2,
|
||||||
|
CUSTOM_COLUMN, &custom,
|
||||||
|
-1);
|
||||||
|
signature = key_binding_signature (action, data1, data2);
|
||||||
|
keep = custom || key_dialog_reset_count (seen_counts, signature) >= key_dialog_reset_count (default_counts, signature);
|
||||||
|
if (!custom)
|
||||||
|
key_dialog_reset_increment (seen_counts, g_strdup (signature));
|
||||||
|
if (keep)
|
||||||
|
{
|
||||||
|
gtk_list_store_append (custom_store, &custom_iter);
|
||||||
|
gtk_list_store_set (custom_store, &custom_iter,
|
||||||
|
KEY_COLUMN, key,
|
||||||
|
ACCEL_COLUMN, accel,
|
||||||
|
ACTION_COLUMN, action,
|
||||||
|
D1_COLUMN, data1,
|
||||||
|
D2_COLUMN, data2,
|
||||||
|
CUSTOM_COLUMN, TRUE,
|
||||||
|
-1);
|
||||||
|
}
|
||||||
|
g_free (signature);
|
||||||
|
g_free (key);
|
||||||
|
g_free (accel);
|
||||||
|
g_free (action);
|
||||||
|
g_free (data1);
|
||||||
|
g_free (data2);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
old_list = keybind_list;
|
||||||
|
keybind_list = list;
|
||||||
|
gtk_list_store_clear (store);
|
||||||
|
key_dialog_load (store);
|
||||||
|
keybind_list = old_list;
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (custom_store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (custom_store), &iter,
|
||||||
|
KEY_COLUMN, &key,
|
||||||
|
ACCEL_COLUMN, &accel,
|
||||||
|
ACTION_COLUMN, &action,
|
||||||
|
D1_COLUMN, &data1,
|
||||||
|
D2_COLUMN, &data2,
|
||||||
|
-1);
|
||||||
|
gtk_list_store_append (store, &custom_iter);
|
||||||
|
gtk_list_store_set (store, &custom_iter,
|
||||||
|
KEY_COLUMN, key,
|
||||||
|
ACCEL_COLUMN, accel,
|
||||||
|
ACTION_COLUMN, action,
|
||||||
|
D1_COLUMN, data1,
|
||||||
|
D2_COLUMN, data2,
|
||||||
|
CUSTOM_COLUMN, TRUE,
|
||||||
|
-1);
|
||||||
|
g_free (key);
|
||||||
|
g_free (accel);
|
||||||
|
g_free (action);
|
||||||
|
g_free (data1);
|
||||||
|
g_free (data2);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (custom_store), &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
|
g_hash_table_destroy (default_counts);
|
||||||
|
g_hash_table_destroy (seen_counts);
|
||||||
|
g_object_unref (custom_store);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
||||||
{
|
{
|
||||||
@@ -774,9 +1029,14 @@ key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
|||||||
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
gboolean custom;
|
||||||
|
|
||||||
if (gtkutil_treeview_get_selected (view, &iter, -1))
|
if (gtkutil_treeview_get_selected (view, &iter, -1))
|
||||||
{
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, CUSTOM_COLUMN, &custom, -1);
|
||||||
|
if (!custom)
|
||||||
|
return;
|
||||||
|
|
||||||
/* delete this row, select next one */
|
/* delete this row, select next one */
|
||||||
if (gtk_list_store_remove (store, &iter))
|
if (gtk_list_store_remove (store, &iter))
|
||||||
{
|
{
|
||||||
@@ -803,13 +1063,13 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
|
||||||
|
|
||||||
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
G_TYPE_STRING, G_TYPE_STRING);
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||||
g_return_val_if_fail (store != NULL, NULL);
|
g_return_val_if_fail (store != NULL, NULL);
|
||||||
|
|
||||||
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
||||||
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
|
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
|
||||||
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
|
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
|
||||||
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
|
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), FALSE);
|
||||||
|
|
||||||
g_signal_connect (G_OBJECT (view), "key-press-event",
|
g_signal_connect (G_OBJECT (view), "key-press-event",
|
||||||
G_CALLBACK (key_dialog_keypress), NULL);
|
G_CALLBACK (key_dialog_keypress), NULL);
|
||||||
@@ -880,6 +1140,7 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
|
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
|
||||||
"Action", render,
|
"Action", render,
|
||||||
"text", ACTION_COLUMN,
|
"text", ACTION_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
render = gtk_cell_renderer_text_new ();
|
render = gtk_cell_renderer_text_new ();
|
||||||
@@ -890,6 +1151,7 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
GTK_TREE_VIEW (view), D1_COLUMN,
|
GTK_TREE_VIEW (view), D1_COLUMN,
|
||||||
"Data1", render,
|
"Data1", render,
|
||||||
"text", D1_COLUMN,
|
"text", D1_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
render = gtk_cell_renderer_text_new ();
|
render = gtk_cell_renderer_text_new ();
|
||||||
@@ -900,6 +1162,7 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
GTK_TREE_VIEW (view), D2_COLUMN,
|
GTK_TREE_VIEW (view), D2_COLUMN,
|
||||||
"Data2", render,
|
"Data2", render,
|
||||||
"text", D2_COLUMN,
|
"text", D2_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
|
||||||
@@ -945,7 +1208,8 @@ key_dialog_load (GtkListStore *store)
|
|||||||
ACCEL_COLUMN, accel_text,
|
ACCEL_COLUMN, accel_text,
|
||||||
ACTION_COLUMN, key_actions[kb->action].name,
|
ACTION_COLUMN, key_actions[kb->action].name,
|
||||||
D1_COLUMN, kb->data1,
|
D1_COLUMN, kb->data1,
|
||||||
D2_COLUMN, kb->data2, -1);
|
D2_COLUMN, kb->data2,
|
||||||
|
CUSTOM_COLUMN, !key_binding_is_builtin (kb), -1);
|
||||||
|
|
||||||
g_free (accel_text);
|
g_free (accel_text);
|
||||||
g_free (label_text);
|
g_free (label_text);
|
||||||
@@ -958,7 +1222,7 @@ void
|
|||||||
key_dialog_show ()
|
key_dialog_show ()
|
||||||
{
|
{
|
||||||
GtkWidget *vbox, *box;
|
GtkWidget *vbox, *box;
|
||||||
GtkWidget *view, *xtext;
|
GtkWidget *view, *xtext, *delete_button;
|
||||||
GtkListStore *store;
|
GtkListStore *store;
|
||||||
XTextColor xtext_palette[XTEXT_COLS];
|
XTextColor xtext_palette[XTEXT_COLS];
|
||||||
char buf[128];
|
char buf[128];
|
||||||
@@ -992,8 +1256,12 @@ key_dialog_show ()
|
|||||||
|
|
||||||
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
|
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
|
||||||
NULL, _("Add"));
|
NULL, _("Add"));
|
||||||
gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
|
delete_button = gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
|
||||||
NULL, _("Delete"));
|
NULL, _("Delete"));
|
||||||
|
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
|
gtkutil_button (box, ICON_FKEYS_RESET, NULL, key_dialog_reset,
|
||||||
|
NULL, _("Reset"));
|
||||||
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
|
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
|
||||||
NULL, _("Cancel"));
|
NULL, _("Cancel"));
|
||||||
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
|
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
|
||||||
@@ -1101,41 +1369,14 @@ key_load_kbs_helper_mod (char *buf, GdkModifierType *out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
key_load_kbs (void)
|
key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list)
|
||||||
{
|
{
|
||||||
char *buf, *ibuf;
|
char *buf;
|
||||||
struct stat st;
|
|
||||||
struct key_binding *kb = NULL;
|
struct key_binding *kb = NULL;
|
||||||
int fd, len, state = 0, pnt = 0;
|
int len, state = 0, pnt = 0;
|
||||||
guint keyval;
|
guint keyval;
|
||||||
GdkModifierType mod = 0;
|
GdkModifierType mod = 0;
|
||||||
off_t size;
|
GSList *list = NULL;
|
||||||
|
|
||||||
fd = zoitechat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
ibuf = g_strdup (default_kb_cfg);
|
|
||||||
size = strlen (default_kb_cfg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (fstat (fd, &st) != 0)
|
|
||||||
{
|
|
||||||
close (fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ibuf = g_malloc(st.st_size);
|
|
||||||
read (fd, ibuf, st.st_size);
|
|
||||||
size = st.st_size;
|
|
||||||
close (fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keybind_list)
|
|
||||||
{
|
|
||||||
g_slist_free_full (keybind_list, key_free);
|
|
||||||
keybind_list = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (buf_get_line (ibuf, &buf, &pnt, size))
|
while (buf_get_line (ibuf, &buf, &pnt, size))
|
||||||
{
|
{
|
||||||
@@ -1149,14 +1390,12 @@ key_load_kbs (void)
|
|||||||
case KBSTATE_MOD:
|
case KBSTATE_MOD:
|
||||||
kb = g_new0 (struct key_binding, 1);
|
kb = g_new0 (struct key_binding, 1);
|
||||||
|
|
||||||
/* New format */
|
|
||||||
if (strncmp (buf, "ACCEL=", 6) == 0)
|
if (strncmp (buf, "ACCEL=", 6) == 0)
|
||||||
{
|
{
|
||||||
buf += 6;
|
buf += 6;
|
||||||
|
|
||||||
gtk_accelerator_parse (buf, &keyval, &mod);
|
gtk_accelerator_parse (buf, &keyval, &mod);
|
||||||
|
|
||||||
|
|
||||||
kb->keyval = keyval;
|
kb->keyval = keyval;
|
||||||
kb->mod = key_modifier_get_valid (mod);
|
kb->mod = key_modifier_get_valid (mod);
|
||||||
|
|
||||||
@@ -1179,6 +1418,8 @@ key_load_kbs (void)
|
|||||||
if (keyval == 0)
|
if (keyval == 0)
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1195,6 +1436,8 @@ key_load_kbs (void)
|
|||||||
if (kb->action == KEY_MAX_ACTIONS + 1)
|
if (kb->action == KEY_MAX_ACTIONS + 1)
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1212,6 +1455,8 @@ key_load_kbs (void)
|
|||||||
if (buf[0] != 'D')
|
if (buf[0] != 'D')
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,7 +1477,6 @@ key_load_kbs (void)
|
|||||||
if (buf[2] == ':')
|
if (buf[2] == ':')
|
||||||
{
|
{
|
||||||
len = strlen (buf);
|
len = strlen (buf);
|
||||||
/* Add one for the NULL, subtract 3 for the "Dx:" */
|
|
||||||
len++;
|
len++;
|
||||||
len -= 3;
|
len -= 3;
|
||||||
if (state == KBSTATE_DT1)
|
if (state == KBSTATE_DT1)
|
||||||
@@ -1255,7 +1499,8 @@ key_load_kbs (void)
|
|||||||
continue;
|
continue;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
keybind_list = g_slist_append (keybind_list, kb);
|
list = g_slist_append (list, kb);
|
||||||
|
kb = NULL;
|
||||||
|
|
||||||
state = KBSTATE_MOD;
|
state = KBSTATE_MOD;
|
||||||
}
|
}
|
||||||
@@ -1264,14 +1509,56 @@ key_load_kbs (void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
*out_list = list;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
corrupt_file:
|
corrupt_file:
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
g_free (kb);
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_load_kbs (void)
|
||||||
|
{
|
||||||
|
char *ibuf;
|
||||||
|
struct stat st;
|
||||||
|
int fd, result;
|
||||||
|
off_t size;
|
||||||
|
GSList *list = NULL;
|
||||||
|
|
||||||
|
fd = zoitechat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
ibuf = g_strdup (default_kb_cfg);
|
||||||
|
size = strlen (default_kb_cfg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fstat (fd, &st) != 0)
|
||||||
|
{
|
||||||
|
close (fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ibuf = g_malloc(st.st_size);
|
||||||
|
read (fd, ibuf, st.st_size);
|
||||||
|
size = st.st_size;
|
||||||
|
close (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = key_load_kbs_from_buffer (ibuf, size, &list);
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (keybind_list)
|
||||||
|
g_slist_free_full (keybind_list, key_free);
|
||||||
|
keybind_list = list;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
char *d2, struct session *sess)
|
char *d2, struct session *sess)
|
||||||
@@ -1289,6 +1576,24 @@ key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
|
char *d2, struct session *sess)
|
||||||
|
{
|
||||||
|
if (menu_key_action (d1, evt->keyval, evt->state))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
|
char *d2, struct session *sess)
|
||||||
|
{
|
||||||
|
mg_reopen_closed_channel_tab ();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the given session is inside the main window. This predicate
|
* Check if the given session is inside the main window. This predicate
|
||||||
* is passed to lastact_getfirst() as a way to filter out detached sessions.
|
* is passed to lastact_getfirst() as a way to filter out detached sessions.
|
||||||
@@ -1307,6 +1612,9 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
char *d2, struct session *sess)
|
char *d2, struct session *sess)
|
||||||
{
|
{
|
||||||
session *newsess;
|
session *newsess;
|
||||||
|
char *network;
|
||||||
|
char *channel;
|
||||||
|
char *slash;
|
||||||
int len, i, num;
|
int len, i, num;
|
||||||
|
|
||||||
if (!d1)
|
if (!d1)
|
||||||
@@ -1318,21 +1626,13 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
|
|
||||||
if (g_ascii_strcasecmp(d1, "auto") == 0)
|
if (g_ascii_strcasecmp(d1, "auto") == 0)
|
||||||
{
|
{
|
||||||
/* Auto switch makes no sense in detached sessions */
|
|
||||||
if (!sess->gui->is_tab)
|
if (!sess->gui->is_tab)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
/* Obtain a session with recent activity */
|
|
||||||
newsess = lastact_getfirst(session_check_is_tab);
|
newsess = lastact_getfirst(session_check_is_tab);
|
||||||
|
|
||||||
if (newsess)
|
if (newsess)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* Only sessions in the current window should be considered (i.e.
|
|
||||||
* we don't want to move the focus on a different window). This
|
|
||||||
* call could, in theory, do this, but we checked before that
|
|
||||||
* newsess->gui->is_tab and sess->gui->is_tab.
|
|
||||||
*/
|
|
||||||
mg_bring_tofront_sess(newsess);
|
mg_bring_tofront_sess(newsess);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1346,7 +1646,26 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
{
|
{
|
||||||
if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
|
if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
|
||||||
continue;
|
continue;
|
||||||
else
|
|
||||||
|
network = NULL;
|
||||||
|
channel = d1;
|
||||||
|
slash = strchr (d1, '/');
|
||||||
|
|
||||||
|
if (slash && slash[1])
|
||||||
|
{
|
||||||
|
network = g_strndup (d1, slash - d1);
|
||||||
|
channel = slash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newsess = plugin_find_context (network, channel, sess->server);
|
||||||
|
g_free (network);
|
||||||
|
|
||||||
|
if (newsess && newsess->gui)
|
||||||
|
{
|
||||||
|
mg_bring_tofront_sess(newsess);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
|
|||||||
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
|
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
|
||||||
session *sess);
|
session *sess);
|
||||||
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
||||||
|
gboolean key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ enum
|
|||||||
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
||||||
|
|
||||||
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
||||||
|
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
||||||
|
|
||||||
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
||||||
|
|
||||||
@@ -731,6 +732,164 @@ mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
mg_client_tag_allowed (server *serv, const char *tag)
|
||||||
|
{
|
||||||
|
char **deny;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!serv->have_message_tags)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!serv->clienttagdeny || !*serv->clienttagdeny)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
deny = g_strsplit (serv->clienttagdeny, ",", 0);
|
||||||
|
for (i = 0; deny[i]; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
|
||||||
|
{
|
||||||
|
g_strfreev (deny);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev (deny);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_send_typing (session *sess, const char *state)
|
||||||
|
{
|
||||||
|
char tags[32];
|
||||||
|
|
||||||
|
if (!sess || !sess->server->connected || !mg_client_tag_allowed (sess->server, "typing") || !sess->channel[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->type != SESS_CHANNEL && sess->type != SESS_DIALOG)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
|
||||||
|
sess->server->p_tagmsg (sess->server, tags, sess->channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mg_typing_pause_cb (session *sess)
|
||||||
|
{
|
||||||
|
sess->typing_timeout_tag = 0;
|
||||||
|
if (sess->typing_status == 1)
|
||||||
|
{
|
||||||
|
mg_send_typing (sess, "paused");
|
||||||
|
sess->typing_status = 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_typing_update (session *sess, const char *text)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->typing_timeout_tag)
|
||||||
|
{
|
||||||
|
fe_timeout_remove (sess->typing_timeout_tag);
|
||||||
|
sess->typing_timeout_tag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text || !*text || text[0] == prefs.hex_input_command_char[0])
|
||||||
|
{
|
||||||
|
if (sess->typing_status)
|
||||||
|
mg_send_typing (sess, "done");
|
||||||
|
sess->typing_status = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sess->typing_status != 1)
|
||||||
|
{
|
||||||
|
mg_send_typing (sess, "active");
|
||||||
|
sess->typing_status = 1;
|
||||||
|
}
|
||||||
|
sess->typing_timeout_tag = fe_timeout_add_seconds (6, mg_typing_pause_cb, sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_reply_show_child (GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
gtk_widget_show (widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mg_reply_update (session *sess)
|
||||||
|
{
|
||||||
|
char *nick;
|
||||||
|
char *text;
|
||||||
|
char *markup;
|
||||||
|
|
||||||
|
if (!sess || !sess->gui || !sess->gui->reply_box || !sess->gui->reply_label)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!sess->reply_msgid)
|
||||||
|
{
|
||||||
|
gtk_widget_hide (sess->gui->reply_box);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nick = g_markup_escape_text (sess->reply_nick ? sess->reply_nick : _("message"), -1);
|
||||||
|
text = g_markup_escape_text (sess->reply_text ? sess->reply_text : _("Original message unavailable"), -1);
|
||||||
|
markup = g_strdup_printf ("<span foreground='#7d8790'>↪ Replying to <b>%s</b> · %.160s</span>", nick, text);
|
||||||
|
gtk_label_set_markup (GTK_LABEL (sess->gui->reply_label), markup);
|
||||||
|
gtk_container_foreach (GTK_CONTAINER (sess->gui->reply_box), mg_reply_show_child, NULL);
|
||||||
|
gtk_widget_show (sess->gui->reply_box);
|
||||||
|
g_free (markup);
|
||||||
|
g_free (text);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_reply_cancel_cb (GtkWidget *wid, session *sess)
|
||||||
|
{
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_send_reply_or_text (session *sess, char *cmd)
|
||||||
|
{
|
||||||
|
char *reply_cmd;
|
||||||
|
|
||||||
|
if (!sess->reply_msgid || cmd[0] == prefs.hex_input_command_char[0])
|
||||||
|
{
|
||||||
|
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sess->server->connected || !mg_client_tag_allowed (sess->server, "reply"))
|
||||||
|
{
|
||||||
|
PrintText (sess, _("Replies are not supported on this server. Sending normally.\n"));
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_cmd = g_strdup_printf ("%cREPLY %s %s", prefs.hex_input_command_char[0], sess->reply_msgid, cmd);
|
||||||
|
handle_multiline (sess, reply_cmd, TRUE, FALSE);
|
||||||
|
g_free (reply_cmd);
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_inputbox_changed (GtkEditable *editable, session_gui *gui)
|
||||||
|
{
|
||||||
|
key_check_replace_on_change (editable, NULL);
|
||||||
|
if (current_sess && current_sess->gui == gui)
|
||||||
|
mg_typing_update (current_sess, gtk_entry_get_text (GTK_ENTRY (editable)));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
||||||
{
|
{
|
||||||
@@ -772,7 +931,7 @@ mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sess)
|
if (sess)
|
||||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
mg_send_reply_or_text (sess, cmd);
|
||||||
|
|
||||||
g_free (cmd);
|
g_free (cmd);
|
||||||
}
|
}
|
||||||
@@ -3042,7 +3201,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gui->key_entry = gtk_entry_new ();
|
gui->key_entry = gtk_entry_new ();
|
||||||
gtk_widget_set_name (gui->key_entry, "zoitechat-inputbox");
|
gtk_widget_set_name (gui->key_entry, "zoitechat-inputbox");
|
||||||
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
||||||
gtk_widget_set_size_request (gui->key_entry, 115, 11);
|
gtk_widget_set_size_request (gui->key_entry, 58, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->key_entry);
|
mg_apply_emoji_fallback_widget (gui->key_entry);
|
||||||
mg_apply_compact_mode_css (gui->key_entry);
|
mg_apply_compact_mode_css (gui->key_entry);
|
||||||
@@ -3059,7 +3218,8 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gui->limit_entry = gtk_entry_new ();
|
gui->limit_entry = gtk_entry_new ();
|
||||||
gtk_widget_set_name (gui->limit_entry, "zoitechat-inputbox");
|
gtk_widget_set_name (gui->limit_entry, "zoitechat-inputbox");
|
||||||
gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
|
gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
|
||||||
gtk_widget_set_size_request (gui->limit_entry, 30, 11);
|
gtk_entry_set_width_chars (GTK_ENTRY (gui->limit_entry), 5);
|
||||||
|
gtk_widget_set_size_request (gui->limit_entry, 45, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->limit_entry);
|
mg_apply_emoji_fallback_widget (gui->limit_entry);
|
||||||
mg_apply_compact_mode_css (gui->limit_entry);
|
mg_apply_compact_mode_css (gui->limit_entry);
|
||||||
@@ -3193,8 +3353,11 @@ mg_topicbar_update_height (GtkWidget *topic)
|
|||||||
width -= margin_left + margin_right;
|
width -= margin_left + margin_right;
|
||||||
if (width < 1)
|
if (width < 1)
|
||||||
width = 1;
|
width = 1;
|
||||||
|
if (prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline)
|
||||||
|
{
|
||||||
pango_layout_set_width (layout, width * PANGO_SCALE);
|
pango_layout_set_width (layout, width * PANGO_SCALE);
|
||||||
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
|
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
context = gtk_widget_get_pango_context (topic);
|
context = gtk_widget_get_pango_context (topic);
|
||||||
metrics = pango_context_get_metrics (context,
|
metrics = pango_context_get_metrics (context,
|
||||||
@@ -3206,7 +3369,8 @@ mg_topicbar_update_height (GtkWidget *topic)
|
|||||||
if (line_height <= 0)
|
if (line_height <= 0)
|
||||||
line_height = 16;
|
line_height = 16;
|
||||||
|
|
||||||
line_count = pango_layout_get_line_count (layout);
|
line_count = prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline ?
|
||||||
|
pango_layout_get_line_count (layout) : 1;
|
||||||
if (line_count <= 0)
|
if (line_count <= 0)
|
||||||
line_count = 1;
|
line_count = 1;
|
||||||
|
|
||||||
@@ -3224,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3328,7 +3501,7 @@ mg_apply_session_font_prefs (session_gui *gui)
|
|||||||
static void
|
static void
|
||||||
mg_create_topicbar (session *sess, GtkWidget *box)
|
mg_create_topicbar (session *sess, GtkWidget *box)
|
||||||
{
|
{
|
||||||
GtkWidget *vbox, *hbox, *mode_hbox, *topic, *bbox;
|
GtkWidget *vbox, *hbox, *mode_hbox, *topic, *topic_scroll, *bbox;
|
||||||
session_gui *gui = sess->gui;
|
session_gui *gui = sess->gui;
|
||||||
|
|
||||||
gui->topic_bar = vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
gui->topic_bar = vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
||||||
@@ -3342,7 +3515,9 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
|
|
||||||
gui->topic_entry = topic = gtk_text_view_new ();
|
gui->topic_entry = topic = gtk_text_view_new ();
|
||||||
gtk_widget_set_name (topic, "zoitechat-topicbox");
|
gtk_widget_set_name (topic, "zoitechat-topicbox");
|
||||||
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic), GTK_WRAP_WORD_CHAR);
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic),
|
||||||
|
prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline ?
|
||||||
|
GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
|
||||||
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
@@ -3355,8 +3530,17 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
G_CALLBACK (mg_topicbar_buffer_changed_cb), topic);
|
G_CALLBACK (mg_topicbar_buffer_changed_cb), topic);
|
||||||
g_signal_connect (G_OBJECT (topic), "size-allocate",
|
g_signal_connect (G_OBJECT (topic), "size-allocate",
|
||||||
G_CALLBACK (mg_topicbar_size_allocate_cb), NULL);
|
G_CALLBACK (mg_topicbar_size_allocate_cb), NULL);
|
||||||
|
topic_scroll = gtk_scrolled_window_new (NULL, NULL);
|
||||||
|
gtk_widget_set_hexpand (topic_scroll, TRUE);
|
||||||
|
gtk_widget_set_size_request (topic_scroll, 1, -1);
|
||||||
|
gtk_widget_set_size_request (topic, 1, -1);
|
||||||
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (topic_scroll),
|
||||||
|
GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
|
||||||
|
gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (topic_scroll), FALSE);
|
||||||
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (topic_scroll), GTK_SHADOW_NONE);
|
||||||
|
gtk_container_add (GTK_CONTAINER (topic_scroll), topic);
|
||||||
mg_topicbar_update_height (topic);
|
mg_topicbar_update_height (topic);
|
||||||
gtk_box_pack_start (GTK_BOX (hbox), topic, TRUE, TRUE, 0);
|
gtk_box_pack_start (GTK_BOX (hbox), topic_scroll, TRUE, TRUE, 0);
|
||||||
gtk_widget_add_events (topic, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
gtk_widget_add_events (topic, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
||||||
GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
|
GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
|
||||||
g_signal_connect (G_OBJECT (topic), "key-press-event",
|
g_signal_connect (G_OBJECT (topic), "key-press-event",
|
||||||
@@ -3373,9 +3557,13 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
mg_create_dialogbuttons (bbox);
|
mg_create_dialogbuttons (bbox);
|
||||||
|
|
||||||
mode_hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
mode_hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
|
if (prefs.hex_gui_mode_buttons_inline)
|
||||||
|
gtk_box_pack_start (GTK_BOX (hbox), mode_hbox, 0, 0, 0);
|
||||||
|
else
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), mode_hbox, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (vbox), mode_hbox, 0, 0, 0);
|
||||||
|
|
||||||
gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
|
gtk_widget_set_valign (bbox, GTK_ALIGN_CENTER);
|
||||||
gtk_box_pack_end (GTK_BOX (mode_hbox), bbox, 0, 0, 0);
|
gtk_box_pack_end (GTK_BOX (mode_hbox), bbox, 0, 0, 0);
|
||||||
mg_create_chanmodebuttons (gui, bbox);
|
mg_create_chanmodebuttons (gui, bbox);
|
||||||
}
|
}
|
||||||
@@ -4521,6 +4709,20 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
};
|
};
|
||||||
const char *emoji_fallback_icon_name;
|
const char *emoji_fallback_icon_name;
|
||||||
|
|
||||||
|
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||||
|
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
|
||||||
|
gtk_widget_set_no_show_all (gui->reply_box, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (box), gui->reply_box, 0, 0, 0);
|
||||||
|
gui->reply_label = gtk_label_new ("");
|
||||||
|
gtk_label_set_ellipsize (GTK_LABEL (gui->reply_label), PANGO_ELLIPSIZE_END);
|
||||||
|
gtk_box_pack_start (GTK_BOX (gui->reply_box), gui->reply_label, TRUE, TRUE, 8);
|
||||||
|
but = gtk_button_new_with_label ("×");
|
||||||
|
gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
|
||||||
|
gtk_widget_set_can_focus (but, FALSE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (gui->reply_box), but, FALSE, FALSE, 0);
|
||||||
|
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (mg_reply_cancel_cb), sess);
|
||||||
|
gtk_widget_hide (gui->reply_box);
|
||||||
|
|
||||||
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
||||||
|
|
||||||
@@ -4542,7 +4744,7 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
g_signal_connect (G_OBJECT (entry), "activate",
|
g_signal_connect (G_OBJECT (entry), "activate",
|
||||||
G_CALLBACK (mg_inputbox_cb), gui);
|
G_CALLBACK (mg_inputbox_cb), gui);
|
||||||
g_signal_connect (G_OBJECT (entry), "changed",
|
g_signal_connect (G_OBJECT (entry), "changed",
|
||||||
G_CALLBACK (key_check_replace_on_change), NULL);
|
G_CALLBACK (mg_inputbox_changed), gui);
|
||||||
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
|
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
|
||||||
|
|
||||||
gtk_widget_set_name (entry, "zoitechat-inputbox");
|
gtk_widget_set_name (entry, "zoitechat-inputbox");
|
||||||
@@ -5174,6 +5376,12 @@ fe_set_channel (session *sess)
|
|||||||
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_set_typing (session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
fe_userlist_set_typing (sess, nick, state);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
|
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ void mg_dnd_drop_file (session *sess, char *target, char *uri);
|
|||||||
void mg_change_layout (int type);
|
void mg_change_layout (int type);
|
||||||
void mg_update_meters (session_gui *);
|
void mg_update_meters (session_gui *);
|
||||||
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
||||||
|
void mg_reply_update (session *sess);
|
||||||
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
||||||
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
||||||
/* DND */
|
/* DND */
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "../common/zoitechatc.h"
|
#include "../common/zoitechatc.h"
|
||||||
#include "../common/cfgfiles.h"
|
#include "../common/cfgfiles.h"
|
||||||
#include "../common/outbound.h"
|
#include "../common/outbound.h"
|
||||||
|
#include "../common/inbound.h"
|
||||||
#include "../common/ignore.h"
|
#include "../common/ignore.h"
|
||||||
#include "../common/fe.h"
|
#include "../common/fe.h"
|
||||||
#include "../common/server.h"
|
#include "../common/server.h"
|
||||||
@@ -784,6 +785,25 @@ fe_userlist_update (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_reply_to_latest_cb (GtkWidget *wid, gpointer data)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
item = reply_cache_latest_from (current_sess, str_copy);
|
||||||
|
if (!item)
|
||||||
|
{
|
||||||
|
PrintText (current_sess, _("No recent message to reply to.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text);
|
||||||
|
mg_reply_update (current_sess);
|
||||||
|
if (current_sess->gui && current_sess->gui->input_box)
|
||||||
|
gtk_widget_grab_focus (current_sess->gui->input_box);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||||
{
|
{
|
||||||
@@ -827,6 +847,12 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
|||||||
else
|
else
|
||||||
menu_create (menu, popup_list, str_copy, FALSE);
|
menu_create (menu, popup_list, str_copy, FALSE);
|
||||||
|
|
||||||
|
if (num_sel <= 1)
|
||||||
|
{
|
||||||
|
menu_quick_item_with_callback (menu_reply_to_latest_cb, _("Reply"), menu, 0);
|
||||||
|
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (num_sel == 0) /* xtext click */
|
if (num_sel == 0) /* xtext click */
|
||||||
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
||||||
else /* userlist treeview click */
|
else /* userlist treeview click */
|
||||||
@@ -1755,44 +1781,6 @@ menu_change_layout (void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
menu_update_quit_accel (void)
|
|
||||||
{
|
|
||||||
GSList *list;
|
|
||||||
|
|
||||||
list = sess_list;
|
|
||||||
while (list)
|
|
||||||
{
|
|
||||||
session *sess = list->data;
|
|
||||||
session_gui *gui = sess->gui;
|
|
||||||
GtkWidget *item;
|
|
||||||
GtkAccelGroup *accel_group;
|
|
||||||
int enabled;
|
|
||||||
|
|
||||||
list = list->next;
|
|
||||||
if (!gui)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
item = gui->menu_item[MENU_ID_QUIT];
|
|
||||||
if (!item)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
enabled = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "zc-ctrlq-enabled"));
|
|
||||||
if (enabled == (int)prefs.hex_gui_ctrlq_quit)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
accel_group = g_object_get_data (G_OBJECT (item), "zc-quit-accel-group");
|
|
||||||
if (!accel_group)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (prefs.hex_gui_ctrlq_quit)
|
|
||||||
gtk_widget_add_accelerator (item, "activate", accel_group, GDK_KEY_q, STATE_CTRL, GTK_ACCEL_VISIBLE);
|
|
||||||
else
|
|
||||||
gtk_widget_remove_accelerator (item, accel_group, GDK_KEY_q, STATE_CTRL);
|
|
||||||
g_object_set_data (G_OBJECT (item), "zc-ctrlq-enabled", GINT_TO_POINTER (prefs.hex_gui_ctrlq_quit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_layout_cb (GtkWidget *item, gpointer none)
|
menu_layout_cb (GtkWidget *item, gpointer none)
|
||||||
{
|
{
|
||||||
@@ -1939,13 +1927,13 @@ menu_about (GtkWidget *wid, gpointer sess)
|
|||||||
|
|
||||||
static struct mymenu mymenu[] = {
|
static struct mymenu mymenu[] = {
|
||||||
{N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1},
|
{N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1},
|
||||||
{N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_s},
|
{N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
|
|
||||||
{N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1},
|
{N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||||
{N_("Server Tab"), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_t},
|
{N_("Server Tab"), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Channel Tab"), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Channel Tab"), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Server Window"), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_n},
|
{N_("Server Window"), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Channel Window"), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Channel Window"), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_END, 0, 0, 0},
|
{0, 0, 0, M_END, 0, 0, 0},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
@@ -1957,13 +1945,13 @@ static struct mymenu mymenu[] = {
|
|||||||
#define CLOSE_OFFSET (13)
|
#define CLOSE_OFFSET (13)
|
||||||
{0, menu_close, 0, M_MENUITEM, 0, 0, 1},
|
{0, menu_close, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1, GDK_KEY_q}, /* 15 */
|
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1}, /* 15 */
|
||||||
|
|
||||||
{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
|
{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||||
#define MENUBAR_OFFSET (17)
|
#define MENUBAR_OFFSET (17)
|
||||||
{N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_KEY_F9},
|
{N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1},
|
||||||
{N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1},
|
{N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1},
|
||||||
{N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_KEY_F7},
|
{N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1},
|
||||||
{N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
|
{N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
|
||||||
{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
|
{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
@@ -1980,7 +1968,7 @@ static struct mymenu mymenu[] = {
|
|||||||
{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
|
{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
|
||||||
{0, 0, 0, M_END, 0, 0, 0}, /* 32 */
|
{0, 0, 0, M_END, 0, 0, 0}, /* 32 */
|
||||||
{ 0, 0, 0, M_SEP, 0, 0, 0 },
|
{ 0, 0, 0, M_SEP, 0, 0, 0 },
|
||||||
{N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1, GDK_KEY_F11},
|
{N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1},
|
||||||
|
|
||||||
{N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1},
|
{N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||||
{N_("_Disconnect"), menu_disconnect, 0, M_MENUITEM, MENU_ID_DISCONNECT, 0, 1},
|
{N_("_Disconnect"), menu_disconnect, 0, M_MENUITEM, MENU_ID_DISCONNECT, 0, 1},
|
||||||
@@ -1989,7 +1977,7 @@ static struct mymenu mymenu[] = {
|
|||||||
{N_("Channel _List"), menu_chanlist, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Channel _List"), menu_chanlist, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
#define AWAY_OFFSET (41)
|
#define AWAY_OFFSET (41)
|
||||||
{N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1, GDK_KEY_a},
|
{N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1},
|
||||||
|
|
||||||
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */
|
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */
|
||||||
|
|
||||||
@@ -2017,25 +2005,191 @@ static struct mymenu mymenu[] = {
|
|||||||
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */
|
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */
|
||||||
{N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1},
|
{N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||||
{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_m},
|
{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Move to Marker Line"), menu_movetomarker, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_M},
|
{N_("Move to Marker Line"), menu_movetomarker, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_C},
|
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
|
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||||
#define SEARCH_OFFSET (70)
|
#define SEARCH_OFFSET (70)
|
||||||
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
|
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||||
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_f},
|
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_g},
|
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_G},
|
{N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{0, 0, 0, M_END, 0, 0, 0},
|
{0, 0, 0, M_END, 0, 0, 0},
|
||||||
|
|
||||||
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */
|
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */
|
||||||
{N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_F1},
|
{N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1},
|
{N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1},
|
||||||
|
|
||||||
{0, 0, 0, M_END, 0, 0, 0},
|
{0, 0, 0, M_END, 0, 0, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
menu_get_key_action_name (int index)
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return "network-list";
|
||||||
|
case 4:
|
||||||
|
return "new-server-tab";
|
||||||
|
case 6:
|
||||||
|
return "new-server-window";
|
||||||
|
case CLOSE_OFFSET:
|
||||||
|
return "close";
|
||||||
|
case 15:
|
||||||
|
return "quit";
|
||||||
|
case MENUBAR_OFFSET:
|
||||||
|
return "menu-toggle";
|
||||||
|
case MENUBAR_OFFSET + 2:
|
||||||
|
return "user-list-toggle";
|
||||||
|
case 34:
|
||||||
|
return "fullscreen-toggle";
|
||||||
|
case AWAY_OFFSET:
|
||||||
|
return "away-toggle";
|
||||||
|
case 65:
|
||||||
|
return "reset-marker";
|
||||||
|
case 66:
|
||||||
|
return "move-marker";
|
||||||
|
case 67:
|
||||||
|
return "copy-selection";
|
||||||
|
case SEARCH_OFFSET + 1:
|
||||||
|
return "search-text";
|
||||||
|
case SEARCH_OFFSET + 2:
|
||||||
|
return "search-next";
|
||||||
|
case SEARCH_OFFSET + 3:
|
||||||
|
return "search-previous";
|
||||||
|
case 75:
|
||||||
|
return "contents";
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_add_keybinding_accel (GtkWidget *item, GtkAccelGroup *accel_group, const char *name)
|
||||||
|
{
|
||||||
|
guint keyval;
|
||||||
|
GdkModifierType mod;
|
||||||
|
|
||||||
|
if (!accel_group || !key_get_menu_accel (name, &keyval, &mod))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!strcmp (name, "quit") && !prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q && mod == STATE_CTRL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gtk_widget_add_accelerator (item, "activate", accel_group, keyval, mod, GTK_ACCEL_VISIBLE);
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-accel-key", GUINT_TO_POINTER (keyval));
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-accel-mod", GUINT_TO_POINTER (mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_refresh_keybinding_accels (GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
GtkAccelGroup *accel_group = data;
|
||||||
|
const char *name;
|
||||||
|
guint keyval;
|
||||||
|
GdkModifierType mod;
|
||||||
|
GtkWidget *submenu;
|
||||||
|
GList *children, *list;
|
||||||
|
|
||||||
|
if (GTK_IS_MENU_ITEM (widget))
|
||||||
|
{
|
||||||
|
keyval = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "zc-key-accel-key"));
|
||||||
|
mod = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "zc-key-accel-mod"));
|
||||||
|
if (keyval != 0)
|
||||||
|
{
|
||||||
|
gtk_widget_remove_accelerator (widget, accel_group, keyval, mod);
|
||||||
|
g_object_set_data (G_OBJECT (widget), "zc-key-accel-key", NULL);
|
||||||
|
g_object_set_data (G_OBJECT (widget), "zc-key-accel-mod", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
name = g_object_get_data (G_OBJECT (widget), "zc-key-action");
|
||||||
|
menu_add_keybinding_accel (widget, accel_group, name);
|
||||||
|
|
||||||
|
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
|
||||||
|
if (submenu)
|
||||||
|
menu_refresh_keybinding_accels (submenu, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GTK_IS_CONTAINER (widget))
|
||||||
|
{
|
||||||
|
children = gtk_container_get_children (GTK_CONTAINER (widget));
|
||||||
|
for (list = children; list; list = g_list_next (list))
|
||||||
|
menu_refresh_keybinding_accels (list->data, data);
|
||||||
|
g_list_free (children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
menu_update_quit_accel (void)
|
||||||
|
{
|
||||||
|
session *sess;
|
||||||
|
GSList *list;
|
||||||
|
GtkAccelGroup *accel_group;
|
||||||
|
|
||||||
|
list = sess_list;
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
sess = list->data;
|
||||||
|
if (sess && sess->gui && sess->gui->menu)
|
||||||
|
{
|
||||||
|
accel_group = g_object_get_data (G_OBJECT (sess->gui->menu), "accel");
|
||||||
|
if (accel_group)
|
||||||
|
menu_refresh_keybinding_accels (sess->gui->menu, accel_group);
|
||||||
|
}
|
||||||
|
list = g_slist_next (list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
menu_key_action (const char *name, guint keyval, GdkModifierType state)
|
||||||
|
{
|
||||||
|
if (!name)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!strcmp (name, "network-list"))
|
||||||
|
menu_open_server_list (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "new-server-tab"))
|
||||||
|
menu_newserver_tab (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "new-server-window"))
|
||||||
|
menu_newserver_window (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "close"))
|
||||||
|
menu_close (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "quit"))
|
||||||
|
{
|
||||||
|
if (!prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q && (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == STATE_CTRL)
|
||||||
|
return FALSE;
|
||||||
|
menu_quit (NULL, NULL);
|
||||||
|
}
|
||||||
|
else if (!strcmp (name, "menu-toggle"))
|
||||||
|
menu_bar_toggle_cb ();
|
||||||
|
else if (!strcmp (name, "user-list-toggle"))
|
||||||
|
menu_userlist_toggle (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "fullscreen-toggle"))
|
||||||
|
menu_fullscreen_toggle (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "away-toggle"))
|
||||||
|
menu_away_toggle (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "reset-marker"))
|
||||||
|
menu_resetmarker (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "move-marker"))
|
||||||
|
menu_movetomarker (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "copy-selection"))
|
||||||
|
menu_copy_selection (NULL, NULL);
|
||||||
|
else if (!strcmp (name, "search-text"))
|
||||||
|
menu_search ();
|
||||||
|
else if (!strcmp (name, "search-next"))
|
||||||
|
menu_search_next (NULL);
|
||||||
|
else if (!strcmp (name, "search-previous"))
|
||||||
|
menu_search_prev (NULL);
|
||||||
|
else if (!strcmp (name, "contents"))
|
||||||
|
menu_docs (NULL, NULL);
|
||||||
|
else
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_set_away (session_gui *gui, int away)
|
menu_set_away (session_gui *gui, int away)
|
||||||
{
|
{
|
||||||
@@ -2641,6 +2795,8 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
case M_MENUITEM:
|
case M_MENUITEM:
|
||||||
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
normalitem:
|
normalitem:
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
||||||
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
||||||
gtk_widget_add_accelerator (item, "activate", accel_group,
|
gtk_widget_add_accelerator (item, "activate", accel_group,
|
||||||
mymenu[i].key,
|
mymenu[i].key,
|
||||||
@@ -2669,6 +2825,8 @@ normalitem:
|
|||||||
case M_MENUTOG:
|
case M_MENUTOG:
|
||||||
item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
togitem:
|
togitem:
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
||||||
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
/* must avoid callback for Radio buttons */
|
/* must avoid callback for Radio buttons */
|
||||||
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state);
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state);
|
||||||
/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ void menu_bar_toggle (void);
|
|||||||
void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
|
void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
|
||||||
void menu_change_layout (void);
|
void menu_change_layout (void);
|
||||||
void menu_update_quit_accel (void);
|
void menu_update_quit_accel (void);
|
||||||
|
gboolean menu_key_action (const char *name, guint keyval, GdkModifierType state);
|
||||||
|
|
||||||
void menu_set_away (session_gui *gui, int away);
|
void menu_set_away (session_gui *gui, int away);
|
||||||
void menu_set_fullscreen (session_gui *gui, int fullscreen);
|
void menu_set_fullscreen (session_gui *gui, int fullscreen);
|
||||||
|
|||||||
@@ -227,21 +227,6 @@ test('Theme Access Routing Tests', theme_access_tests,
|
|||||||
timeout: 120,
|
timeout: 120,
|
||||||
)
|
)
|
||||||
|
|
||||||
theme_gtk3_settings_tests = executable('theme_gtk3_settings_tests',
|
|
||||||
[
|
|
||||||
'theme/tests/test-theme-gtk3-settings.c',
|
|
||||||
'theme/theme-gtk3.c',
|
|
||||||
],
|
|
||||||
include_directories: [config_h_include],
|
|
||||||
dependencies: [gtk_dep],
|
|
||||||
)
|
|
||||||
|
|
||||||
test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
|
|
||||||
protocol: 'tap',
|
|
||||||
timeout: 120,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
||||||
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
||||||
include_directories: [config_h_include],
|
include_directories: [config_h_include],
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ typedef struct _GtkStatusIcon GtkStatusIcon;
|
|||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <windows.h>
|
||||||
|
#include <gdk/gdkwin32.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum /* current icon status */
|
typedef enum /* current icon status */
|
||||||
@@ -855,6 +858,7 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
static int maximized;
|
static int maximized;
|
||||||
static int fullscreen;
|
static int fullscreen;
|
||||||
GtkWindow *win;
|
GtkWindow *win;
|
||||||
|
WinStatus status;
|
||||||
|
|
||||||
if (!tray_backend_active)
|
if (!tray_backend_active)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -870,7 +874,9 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
if (!win)
|
if (!win)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win)))
|
status = tray_get_window_status ();
|
||||||
|
|
||||||
|
if (force_hide || status != WS_HIDDEN)
|
||||||
{
|
{
|
||||||
if (prefs.hex_gui_tray_away)
|
if (prefs.hex_gui_tray_away)
|
||||||
zoitechat_command (ph, "ALLSERV AWAY");
|
zoitechat_command (ph, "ALLSERV AWAY");
|
||||||
@@ -890,8 +896,8 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
gtk_window_maximize (win);
|
gtk_window_maximize (win);
|
||||||
if (fullscreen)
|
if (fullscreen)
|
||||||
gtk_window_fullscreen (win);
|
gtk_window_fullscreen (win);
|
||||||
gtk_widget_show (GTK_WIDGET (win));
|
|
||||||
gtk_window_deiconify (win);
|
gtk_window_deiconify (win);
|
||||||
|
gtk_widget_show (GTK_WIDGET (win));
|
||||||
gtk_window_present (win);
|
gtk_window_present (win);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1089,6 +1095,119 @@ tray_menu_settings (GtkWidget * wid, gpointer none)
|
|||||||
setup_open ();
|
setup_open ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#define TRAY_WIN32_HIDE 1
|
||||||
|
#define TRAY_WIN32_AWAY 2
|
||||||
|
#define TRAY_WIN32_BACK 3
|
||||||
|
#define TRAY_WIN32_PREFS 4
|
||||||
|
#define TRAY_WIN32_QUIT 5
|
||||||
|
|
||||||
|
static WCHAR *
|
||||||
|
tray_win32_menu_label (const char *label)
|
||||||
|
{
|
||||||
|
char *plain;
|
||||||
|
char *src;
|
||||||
|
char *dst;
|
||||||
|
WCHAR *wide;
|
||||||
|
|
||||||
|
plain = g_strdup (label ? label : "");
|
||||||
|
for (src = plain, dst = plain; *src; src++)
|
||||||
|
{
|
||||||
|
if (*src == '_')
|
||||||
|
continue;
|
||||||
|
*dst++ = *src;
|
||||||
|
}
|
||||||
|
*dst = 0;
|
||||||
|
wide = g_utf8_to_utf16 (plain, -1, NULL, NULL, NULL);
|
||||||
|
g_free (plain);
|
||||||
|
|
||||||
|
return wide;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tray_win32_append_item (HMENU menu, UINT id, const char *label, gboolean enabled)
|
||||||
|
{
|
||||||
|
WCHAR *wide;
|
||||||
|
|
||||||
|
wide = tray_win32_menu_label (label);
|
||||||
|
AppendMenuW (menu, MF_STRING | (enabled ? MF_ENABLED : MF_GRAYED), id, wide);
|
||||||
|
g_free (wide);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HWND
|
||||||
|
tray_win32_get_hwnd (void)
|
||||||
|
{
|
||||||
|
GtkWindow *win;
|
||||||
|
GdkWindow *gdk_win;
|
||||||
|
|
||||||
|
win = GTK_WINDOW (zoitechat_get_info (ph, "gtkwin_ptr"));
|
||||||
|
if (!win)
|
||||||
|
return GetActiveWindow ();
|
||||||
|
|
||||||
|
gdk_win = gtk_widget_get_window (GTK_WIDGET (win));
|
||||||
|
if (!gdk_win)
|
||||||
|
return GetActiveWindow ();
|
||||||
|
|
||||||
|
return gdk_win32_window_get_handle (gdk_win);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tray_win32_menu_cb (void)
|
||||||
|
{
|
||||||
|
HMENU menu;
|
||||||
|
POINT point;
|
||||||
|
UINT command;
|
||||||
|
HWND hwnd;
|
||||||
|
int away_status;
|
||||||
|
|
||||||
|
zoitechat_set_context (ph, zoitechat_find_context (ph, NULL, NULL));
|
||||||
|
|
||||||
|
menu = CreatePopupMenu ();
|
||||||
|
if (!menu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
away_status = tray_find_away_status ();
|
||||||
|
tray_win32_append_item (menu, TRAY_WIN32_HIDE,
|
||||||
|
tray_get_window_status () == WS_HIDDEN ? _("_Restore Window") : _("_Hide Window"), TRUE);
|
||||||
|
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||||
|
tray_win32_append_item (menu, TRAY_WIN32_AWAY, _("_Away"), away_status != 1);
|
||||||
|
tray_win32_append_item (menu, TRAY_WIN32_BACK, _("_Back"), away_status != 2);
|
||||||
|
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||||
|
tray_win32_append_item (menu, TRAY_WIN32_PREFS, _("_Preferences"), TRUE);
|
||||||
|
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||||
|
tray_win32_append_item (menu, TRAY_WIN32_QUIT, _("_Quit"), TRUE);
|
||||||
|
|
||||||
|
GetCursorPos (&point);
|
||||||
|
hwnd = tray_win32_get_hwnd ();
|
||||||
|
if (hwnd)
|
||||||
|
SetForegroundWindow (hwnd);
|
||||||
|
command = TrackPopupMenu (menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
|
||||||
|
point.x, point.y, 0, hwnd, NULL);
|
||||||
|
DestroyMenu (menu);
|
||||||
|
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case TRAY_WIN32_HIDE:
|
||||||
|
tray_toggle_visibility (FALSE);
|
||||||
|
break;
|
||||||
|
case TRAY_WIN32_AWAY:
|
||||||
|
tray_foreach_server (NULL, "away");
|
||||||
|
break;
|
||||||
|
case TRAY_WIN32_BACK:
|
||||||
|
tray_foreach_server (NULL, "back");
|
||||||
|
break;
|
||||||
|
case TRAY_WIN32_PREFS:
|
||||||
|
tray_menu_settings (NULL, NULL);
|
||||||
|
break;
|
||||||
|
case TRAY_WIN32_QUIT:
|
||||||
|
tray_menu_quit_cb (NULL, NULL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tray_menu_populate (GtkWidget *menu)
|
tray_menu_populate (GtkWidget *menu)
|
||||||
{
|
{
|
||||||
@@ -1216,6 +1335,15 @@ tray_window_visibility_cb (GtkWidget *widget, gpointer userdata)
|
|||||||
static void
|
static void
|
||||||
tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
||||||
{
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
(void)widget;
|
||||||
|
(void)button;
|
||||||
|
(void)time;
|
||||||
|
(void)userdata;
|
||||||
|
|
||||||
|
tray_win32_menu_cb ();
|
||||||
|
return;
|
||||||
|
#else
|
||||||
static GtkWidget *menu;
|
static GtkWidget *menu;
|
||||||
|
|
||||||
(void)button;
|
(void)button;
|
||||||
@@ -1255,6 +1383,7 @@ tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
|||||||
if (event)
|
if (event)
|
||||||
gdk_event_free (event);
|
gdk_event_free (event);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1410,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)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ static gboolean color_change;
|
|||||||
static struct zoitechatprefs setup_prefs;
|
static struct zoitechatprefs setup_prefs;
|
||||||
static GtkWidget *cancel_button;
|
static GtkWidget *cancel_button;
|
||||||
static GtkWidget *font_dialog = NULL;
|
static GtkWidget *font_dialog = NULL;
|
||||||
|
static GtkWidget *setup_topicbar_multiline_toggle = NULL;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@@ -185,6 +186,10 @@ static const setting appearance_settings[] =
|
|||||||
{ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0},
|
{ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0},
|
||||||
{ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0},
|
{ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0},
|
||||||
|
|
||||||
|
{ST_HEADER, N_("Topic Bar"),0,0,0},
|
||||||
|
{ST_TOGGLE, N_("Place mode buttons beside the topic"), P_OFFINTNL(hex_gui_mode_buttons_inline), 0, 0, 0},
|
||||||
|
{ST_TOGGLE, N_("Allow multi-line topics"), P_OFFINTNL(hex_gui_topicbar_multiline), 0, 0, 0},
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -546,6 +551,7 @@ static const setting general_settings[] =
|
|||||||
{ST_TOGGLE, N_("WHOIS on notify"), P_OFFINTNL(hex_notify_whois_online), N_("Sends a /WHOIS when a user comes online in your notify list."), 0, 0},
|
{ST_TOGGLE, N_("WHOIS on notify"), P_OFFINTNL(hex_notify_whois_online), N_("Sends a /WHOIS when a user comes online in your notify list."), 0, 0},
|
||||||
{ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
|
{ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
|
||||||
{ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
|
{ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
|
||||||
|
{ST_TOGGLE, N_("Hide hostmasks in join and part messages"), P_OFFINTNL(hex_irc_hide_join_part_hostmask), 0, 0, 0},
|
||||||
{ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
|
{ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
@@ -832,6 +838,16 @@ setup_toggle_sensitive_cb (GtkToggleButton *but, GtkWidget *wid)
|
|||||||
gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (but));
|
gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (but));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_topicbar_inline_toggled_cb (GtkToggleButton *but, gpointer userdata)
|
||||||
|
{
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
if (setup_topicbar_multiline_toggle)
|
||||||
|
gtk_widget_set_sensitive (setup_topicbar_multiline_toggle,
|
||||||
|
!gtk_toggle_button_get_active (but));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
||||||
{
|
{
|
||||||
@@ -858,6 +874,14 @@ setup_create_toggleL (GtkWidget *tab, int row, const setting *set)
|
|||||||
setup_get_int (&setup_prefs, set));
|
setup_get_int (&setup_prefs, set));
|
||||||
g_signal_connect (G_OBJECT (wid), "toggled",
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_mode_buttons_inline))
|
||||||
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
|
G_CALLBACK (setup_topicbar_inline_toggled_cb), NULL);
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_topicbar_multiline))
|
||||||
|
{
|
||||||
|
setup_topicbar_multiline_toggle = wid;
|
||||||
|
gtk_widget_set_sensitive (wid, !setup_prefs.hex_gui_mode_buttons_inline);
|
||||||
|
}
|
||||||
if (set->tooltip)
|
if (set->tooltip)
|
||||||
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
||||||
setup_table_attach (tab, wid, 2, row==6 ? 6 : 4, row, row + 1, FALSE, FALSE,
|
setup_table_attach (tab, wid, 2, row==6 ? 6 : 4, row, row + 1, FALSE, FALSE,
|
||||||
@@ -2285,6 +2309,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_lagometer))
|
if (DIFF (hex_gui_lagometer))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
|
if (DIFF (hex_gui_mode_buttons_inline))
|
||||||
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_icons))
|
if (DIFF (hex_gui_tab_icons))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_closebuttons))
|
if (DIFF (hex_gui_tab_closebuttons))
|
||||||
@@ -2299,6 +2325,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_throttlemeter))
|
if (DIFF (hex_gui_throttlemeter))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
|
if (DIFF (hex_gui_topicbar_multiline))
|
||||||
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_count))
|
if (DIFF (hex_gui_ulist_count))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_icons))
|
if (DIFF (hex_gui_ulist_icons))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ struct zoitechatprefs prefs;
|
|||||||
|
|
||||||
static gboolean gtk_available;
|
static gboolean gtk_available;
|
||||||
static char *temp_root;
|
static char *temp_root;
|
||||||
|
static char *xdg_data_home;
|
||||||
static char *theme_parent_root;
|
static char *theme_parent_root;
|
||||||
static char *theme_child_root;
|
static char *theme_child_root;
|
||||||
static char *theme_switch_root;
|
static char *theme_switch_root;
|
||||||
@@ -198,6 +199,14 @@ get_int_setting (const char *name)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
has_default_seat (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_themes (void)
|
setup_themes (void)
|
||||||
{
|
{
|
||||||
@@ -205,6 +214,10 @@ setup_themes (void)
|
|||||||
|
|
||||||
temp_root = g_dir_make_tmp ("zoitechat-theme-gtk3-settings-XXXXXX", NULL);
|
temp_root = g_dir_make_tmp ("zoitechat-theme-gtk3-settings-XXXXXX", NULL);
|
||||||
g_assert_nonnull (temp_root);
|
g_assert_nonnull (temp_root);
|
||||||
|
xdg_data_home = g_build_filename (temp_root, "data", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (xdg_data_home, 0700), ==, 0);
|
||||||
|
g_setenv ("XDG_DATA_HOME", xdg_data_home, TRUE);
|
||||||
|
|
||||||
theme_parent_root = g_build_filename (temp_root, "parent", NULL);
|
theme_parent_root = g_build_filename (temp_root, "parent", NULL);
|
||||||
theme_child_root = g_build_filename (temp_root, "child", NULL);
|
theme_child_root = g_build_filename (temp_root, "child", NULL);
|
||||||
theme_switch_root = g_build_filename (temp_root, "switch", NULL);
|
theme_switch_root = g_build_filename (temp_root, "switch", NULL);
|
||||||
@@ -253,10 +266,12 @@ teardown_themes (void)
|
|||||||
g_free (theme_parent_root);
|
g_free (theme_parent_root);
|
||||||
g_free (theme_child_root);
|
g_free (theme_child_root);
|
||||||
g_free (theme_switch_root);
|
g_free (theme_switch_root);
|
||||||
|
g_free (xdg_data_home);
|
||||||
g_free (temp_root);
|
g_free (temp_root);
|
||||||
theme_parent_root = NULL;
|
theme_parent_root = NULL;
|
||||||
theme_child_root = NULL;
|
theme_child_root = NULL;
|
||||||
theme_switch_root = NULL;
|
theme_switch_root = NULL;
|
||||||
|
xdg_data_home = NULL;
|
||||||
temp_root = NULL;
|
temp_root = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,10 +317,13 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
g_assert_no_error (error);
|
g_assert_no_error (error);
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
||||||
|
if (has_default_seat ())
|
||||||
|
{
|
||||||
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
g_assert_cmpstr (active_theme_name, ==, "child");
|
g_assert_cmpstr (active_theme_name, ==, "child");
|
||||||
g_free (active_theme_name);
|
g_free (active_theme_name);
|
||||||
active_theme_name = NULL;
|
active_theme_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
g_assert_no_error (error);
|
g_assert_no_error (error);
|
||||||
@@ -315,9 +333,12 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
theme_gtk3_disable ();
|
theme_gtk3_disable ();
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
||||||
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
||||||
|
if (has_default_seat ())
|
||||||
|
{
|
||||||
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
g_assert_cmpstr (active_theme_name, ==, default_theme_name);
|
g_assert_cmpstr (active_theme_name, ==, default_theme_name);
|
||||||
g_free (active_theme_name);
|
g_free (active_theme_name);
|
||||||
|
}
|
||||||
g_free (default_theme_name);
|
g_free (default_theme_name);
|
||||||
g_assert_false (theme_gtk3_is_active ());
|
g_assert_false (theme_gtk3_is_active ());
|
||||||
}
|
}
|
||||||
@@ -328,8 +349,8 @@ main (int argc, char **argv)
|
|||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
gtk_available = gtk_init_check (&argc, &argv);
|
|
||||||
setup_themes ();
|
setup_themes ();
|
||||||
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
|
|
||||||
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
||||||
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
if (mode == ZOITECHAT_DARK_MODE_AUTO)
|
||||||
return stub_policy_dark;
|
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));
|
||||||
|
|||||||
@@ -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, >k_map))
|
||||||
|
{
|
||||||
|
theme_runtime_get_widget_style_values_mapped (>k_map, &style_values);
|
||||||
|
theme_runtime_get_xtext_colors_mapped (>k_map, palette, palette_len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
theme_runtime_get_widget_style_values (&style_values);
|
||||||
theme_runtime_get_xtext_colors (palette, palette_len);
|
theme_runtime_get_xtext_colors (palette, palette_len);
|
||||||
has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
|
}
|
||||||
|
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;
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,8 +135,12 @@ settings_defaults_table (void)
|
|||||||
static void
|
static void
|
||||||
settings_rescan_icon_theme (void)
|
settings_rescan_icon_theme (void)
|
||||||
{
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
GtkIconTheme *icon_theme;
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
icon_theme = gtk_icon_theme_get_default ();
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme)
|
if (!icon_theme)
|
||||||
return;
|
return;
|
||||||
@@ -156,8 +160,13 @@ theme_gtk3_reset_widgets (void)
|
|||||||
static void
|
static void
|
||||||
settings_capture_icon_search_path (void)
|
settings_capture_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -168,8 +177,13 @@ settings_capture_icon_search_path (void)
|
|||||||
static void
|
static void
|
||||||
settings_append_icon_search_path (const char *path)
|
settings_append_icon_search_path (const char *path)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -199,8 +213,13 @@ settings_apply_icon_paths (const char *theme_root)
|
|||||||
static void
|
static void
|
||||||
settings_restore_icon_search_path (void)
|
settings_restore_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -394,6 +413,14 @@ settings_theme_link_search_path (const char *theme_root, const char *theme_name)
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
settings_default_seat_available (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
settings_apply_theme_name (const char *theme_root)
|
settings_apply_theme_name (const char *theme_root)
|
||||||
{
|
{
|
||||||
@@ -404,7 +431,7 @@ settings_apply_theme_name (const char *theme_root)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
settings = gtk_settings_get_default ();
|
settings = gtk_settings_get_default ();
|
||||||
if (!settings)
|
if (!settings || !settings_default_seat_available ())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_name = g_path_get_basename (theme_root);
|
theme_name = g_path_get_basename (theme_root);
|
||||||
|
|||||||
@@ -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", >k_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 ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
gboolean prefer_dark = FALSE;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
|
||||||
|
if (fe_win32_high_contrast_is_enabled ())
|
||||||
return FALSE;
|
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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "fe-gtk.h"
|
#include "fe-gtk.h"
|
||||||
|
|
||||||
@@ -53,6 +54,36 @@ enum
|
|||||||
|
|
||||||
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
|
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
userlist_typing_suffix (session *sess, struct User *user)
|
||||||
|
{
|
||||||
|
static const char *active[] = { " [✎]", " [✎.]", " [✎..]" };
|
||||||
|
|
||||||
|
if (!user || !user->typing)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (user->typing == 2)
|
||||||
|
return " [✎…]";
|
||||||
|
|
||||||
|
return active[sess->typing_animation_frame % G_N_ELEMENTS (active)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
userlist_nick_markup (session *sess, struct User *user)
|
||||||
|
{
|
||||||
|
char *nick = g_markup_escape_text (user->nick, -1);
|
||||||
|
const char *typing = userlist_typing_suffix (sess, user);
|
||||||
|
|
||||||
|
if (*typing)
|
||||||
|
{
|
||||||
|
char *marked = g_strdup_printf ("%s%s", nick, typing);
|
||||||
|
g_free (nick);
|
||||||
|
return marked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
userlist_prefix_color (char prefix)
|
userlist_prefix_color (char prefix)
|
||||||
{
|
{
|
||||||
@@ -463,6 +494,87 @@ fe_userlist_remove (session *sess, struct User *user)
|
|||||||
return sel;
|
return sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
userlist_typing_tick (session *sess)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gboolean valid;
|
||||||
|
gboolean keep = FALSE;
|
||||||
|
time_t now = time (NULL);
|
||||||
|
|
||||||
|
if (!sess || !sess->res || !sess->res->user_model)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
sess->typing_animation_frame++;
|
||||||
|
model = GTK_TREE_MODEL (sess->res->user_model);
|
||||||
|
valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||||
|
|
||||||
|
while (valid)
|
||||||
|
{
|
||||||
|
struct User *user = NULL;
|
||||||
|
|
||||||
|
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
|
||||||
|
if (user && user->typing)
|
||||||
|
{
|
||||||
|
char *nick;
|
||||||
|
|
||||||
|
if ((user->typing == 1 && now - user->typing_time >= 6) || (user->typing == 2 && now - user->typing_time >= 30))
|
||||||
|
user->typing = 0;
|
||||||
|
|
||||||
|
nick = userlist_nick_markup (sess, user);
|
||||||
|
gtk_list_store_set (sess->res->user_model, &iter, COL_NICK, nick, -1);
|
||||||
|
g_free (nick);
|
||||||
|
if (user->typing)
|
||||||
|
keep = TRUE;
|
||||||
|
}
|
||||||
|
valid = gtk_tree_model_iter_next (model, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep)
|
||||||
|
{
|
||||||
|
sess->typing_animation_tag = 0;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_userlist_set_typing (session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
struct User *user;
|
||||||
|
GtkTreeIter *iter;
|
||||||
|
int sel;
|
||||||
|
|
||||||
|
if (!sess || !nick || !sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
user = userlist_find (sess, nick);
|
||||||
|
if (!user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!strcmp (state, "active"))
|
||||||
|
user->typing = 1;
|
||||||
|
else if (!strcmp (state, "paused"))
|
||||||
|
user->typing = 2;
|
||||||
|
else
|
||||||
|
user->typing = 0;
|
||||||
|
user->typing_time = time (NULL);
|
||||||
|
|
||||||
|
iter = find_row (sess, GTK_TREE_VIEW (sess->gui->user_tree), GTK_TREE_MODEL (sess->res->user_model), user, &sel);
|
||||||
|
if (iter)
|
||||||
|
{
|
||||||
|
char *nick = userlist_nick_markup (sess, user);
|
||||||
|
gtk_list_store_set (sess->res->user_model, iter, COL_NICK, nick, -1);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user->typing && !sess->typing_animation_tag)
|
||||||
|
sess->typing_animation_tag = fe_timeout_add (350, userlist_typing_tick, sess);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_userlist_rehash (session *sess, struct User *user)
|
fe_userlist_rehash (session *sess, struct User *user)
|
||||||
{
|
{
|
||||||
@@ -493,9 +605,14 @@ fe_userlist_rehash (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char *nick = userlist_nick_markup (sess, user);
|
||||||
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
||||||
|
COL_NICK, nick,
|
||||||
COL_HOST, user->hostname,
|
COL_HOST, user->hostname,
|
||||||
-1);
|
-1);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
|
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,7 +623,6 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
|
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
char *nick;
|
char *nick;
|
||||||
char *nick_escaped;
|
|
||||||
char *prefix = NULL;
|
char *prefix = NULL;
|
||||||
char *prefix_escaped;
|
char *prefix_escaped;
|
||||||
char prefix_text[2];
|
char prefix_text[2];
|
||||||
@@ -530,8 +646,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nick_escaped = g_markup_escape_text (newuser->nick, -1);
|
nick = userlist_nick_markup (sess, newuser);
|
||||||
nick = nick_escaped;
|
|
||||||
if (!prefs.hex_gui_ulist_icons)
|
if (!prefs.hex_gui_ulist_icons)
|
||||||
{
|
{
|
||||||
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
|
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
|
||||||
@@ -559,7 +674,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
|
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
|
||||||
|
|
||||||
g_free (prefix);
|
g_free (prefix);
|
||||||
g_free (nick_escaped);
|
g_free (nick);
|
||||||
|
|
||||||
userlist_row_map_set (sess, model, newuser, &iter);
|
userlist_row_map_set (sess, model, newuser, &iter);
|
||||||
|
|
||||||
@@ -757,6 +872,7 @@ userlist_add_columns (GtkTreeView * treeview)
|
|||||||
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
||||||
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
|
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
|
||||||
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
|
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
|
||||||
|
|
||||||
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
|
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
|
||||||
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
|
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
|
||||||
gtk_tree_view_column_set_expand (column, TRUE);
|
gtk_tree_view_column_set_expand (column, TRUE);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ GtkWidget *userlist_create (GtkWidget *box);
|
|||||||
GtkListStore *userlist_create_model (session *sess);
|
GtkListStore *userlist_create_model (session *sess);
|
||||||
void userlist_show (session *sess);
|
void userlist_show (session *sess);
|
||||||
void userlist_select (session *sess, char *name);
|
void userlist_select (session *sess, char *name);
|
||||||
|
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
||||||
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
|
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
|
||||||
GdkPixbuf *get_user_icon (server *serv, struct User *user);
|
GdkPixbuf *get_user_icon (server *serv, struct User *user);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -647,6 +647,11 @@ void
|
|||||||
fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
|
fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_set_typing (struct session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
}
|
||||||
void
|
void
|
||||||
fe_cleanup (void)
|
fe_cleanup (void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.18.1
|
2.18.3
|
||||||
|
|||||||
Reference in New Issue
Block a user