mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-06-27 16:29:24 +00:00
Compare commits
35 Commits
bb08b19c0d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | |||
| 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
|
||||||
@@ -1,6 +1,24 @@
|
|||||||
ZoiteChat ChangeLog
|
ZoiteChat ChangeLog
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
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,29 @@
|
|||||||
<id>zoitechat.desktop</id>
|
<id>zoitechat.desktop</id>
|
||||||
</provides>
|
</provides>
|
||||||
<releases>
|
<releases>
|
||||||
|
<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.2',
|
||||||
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.2
|
||||||
|
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.2'
|
||||||
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)
|
||||||
|
|||||||
@@ -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},
|
||||||
@@ -791,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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -540,6 +540,9 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
|
|||||||
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
||||||
X509_verify_cert_error_string (err), err);
|
X509_verify_cert_error_string (err), err);
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
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;
|
return ok;
|
||||||
@@ -663,11 +666,15 @@ ssl_do_connect (server * serv)
|
|||||||
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
||||||
hostname_err);
|
hostname_err);
|
||||||
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);
|
||||||
|
if (!serv->accept_invalid_cert)
|
||||||
goto conn_fail;
|
goto conn_fail;
|
||||||
}
|
}
|
||||||
break;
|
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);
|
||||||
@@ -925,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,
|
||||||
@@ -1099,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);
|
||||||
@@ -1814,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);
|
||||||
@@ -1848,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;
|
||||||
@@ -1978,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);
|
||||||
|
|||||||
@@ -350,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 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. */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -793,7 +793,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;
|
||||||
@@ -386,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 */
|
||||||
@@ -427,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;
|
||||||
@@ -492,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);
|
||||||
@@ -541,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" */
|
||||||
@@ -603,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -890,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)
|
||||||
{
|
{
|
||||||
@@ -1128,6 +1260,8 @@ key_dialog_show ()
|
|||||||
NULL, _("Delete"));
|
NULL, _("Delete"));
|
||||||
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
|
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
|
||||||
gtk_widget_set_sensitive (delete_button, FALSE);
|
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,
|
||||||
@@ -1235,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))
|
||||||
{
|
{
|
||||||
@@ -1283,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);
|
||||||
|
|
||||||
@@ -1313,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1329,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1346,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1366,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)
|
||||||
@@ -1389,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;
|
||||||
}
|
}
|
||||||
@@ -1398,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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -3328,7 +3492,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 +3506,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 +3521,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 +3548,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 +4700,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 +4735,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 +5367,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 */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -833,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)
|
||||||
{
|
{
|
||||||
@@ -859,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,
|
||||||
@@ -2286,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))
|
||||||
@@ -2300,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))
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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.2
|
||||||
|
|||||||
Reference in New Issue
Block a user