206 Commits

Author SHA1 Message Date
44dbbdc56b offline docs by default. 2026-07-08 11:54:44 -06:00
deepend-tildeclub
cfb19820fa Merge pull request #330 from ZoiteChat/Windows/python38-support
Add Python 3.8 support for older windows versions.
2026-07-08 10:00:31 -06:00
deepend-tildeclub
e05ca13608 Merge pull request #335 from ZoiteChat/color-tooltips
Add mouse over tooltips for each color selection.
2026-07-08 09:44:10 -06:00
deepend-tildeclub
15cf96c01f Merge pull request #336 from ZoiteChat/win32-taskbar-fix
Fix Windows taskbar click not minimizing the window
2026-07-08 09:31:04 -06:00
2caec91a0f Improve color tooltip design 2026-07-08 00:58:34 -06:00
9465a1e0a8 Fix Windows taskbar click not minimizing the window 2026-07-07 23:44:22 -06:00
251272c708 Applying: Fix Windows 7 crash after connecting when Python plugin init fails 2026-07-07 19:04:51 -06:00
f068a0e0fa Fix Windows Python plugin DLL selection 2026-07-07 15:34:11 -06:00
d0034cd112 Fixed missing comma in color tooltips 2026-07-07 14:14:36 -06:00
ae6d5ebeef Add mouse over tooltips for each color selection. 2026-07-07 09:08:00 -06:00
deepend-tildeclub
8466e70424 Merge pull request #329 from ZoiteChat/implement-issue-326
Adjust default text-event colors
2026-07-06 23:46:48 -06:00
c3d8f27f48 Add Python 3.8 support for older windows versions. 2026-07-06 01:37:59 -06:00
b85229f1a1 Adjust default text-event colors 2026-07-05 23:54:49 -06:00
deepend-tildeclub
d03298a048 Merge pull request #325 from ZoiteChat/fix-chat-scrollbar-stepper-scrolling
Fix chat scrollbar step increment for arrow scrolling
2026-07-05 15:07:25 -06:00
deepend-tildeclub
798e0b487e Merge pull request #324 from ZoiteChat/fix-nickserv-pass-auth
Fix NickServ auth to use decrypted active password
2026-07-05 13:50:48 -06:00
49444d72b4 Fix chat scrollbar step increment for arrow scrolling 2026-07-05 13:38:26 -06:00
deepend-tildeclub
c0e0fe4dcb Merge pull request #319 from ZoiteChat/css-use-after-free-fix
Fix channel tree font ownership/lifetime
2026-07-04 20:07:58 -06:00
deepend-tildeclub
9a189848ea Merge pull request #318 from ZoiteChat/windows-browser-preference
Prefer ShellExecuteW for Windows URL opens
2026-07-04 20:07:37 -06:00
96b09bfc1d Fix NickServ auth to use decrypted active password 2026-07-04 20:04:11 -06:00
9a6c913d89 Fix channel tree font ownership/lifetime 2026-07-02 13:08:48 -06:00
42010594be Prefer ShellExecuteW for Windows URL opens 2026-07-02 13:02:09 -06:00
5469c30b75 Update GPLv2 text, Add feature request template. 2026-07-01 18:25:48 -06:00
fe9a2ee202 Preparation for 2.18.3 2026-06-29 20:49:41 -06:00
deepend-tildeclub
5e27dc7e9f Merge pull request #315 from ZoiteChat/python-script-compatibility-fix
Snapshot Python hooks during plugin unload
2026-06-29 20:41:00 -06:00
deepend-tildeclub
69d083e3c0 Merge pull request #314 from ZoiteChat/openssl4-build-fixes
Support OpenSSL 4 APIs
2026-06-29 20:38:39 -06:00
2b95f9724e Snapshot Python hooks during plugin unload 2026-06-29 19:13:18 -06:00
4066a07892 Support OpenSSL 4 API 2026-06-29 18:47:46 -06:00
deepend-tildeclub
443ab58f65 Merge pull request #313 from ZoiteChat/fix-lag-ping-timeout-window
Fix lag ping timeout window.
2026-06-27 11:56:38 -06:00
e0116acbe4 Fix lag ping timeout window. 2026-06-27 11:46:51 -06:00
deepend-tildeclub
91e9bf0207 Merge pull request #311 from ZoiteChat/fix-pm-echo-routing
Fix self-echoed PM routing to target tab
2026-06-27 01:59:20 -06:00
aff0335f7e Fix self-echoed PM routing to target tab 2026-06-27 01:42:23 -06:00
deepend-tildeclub
4d8baaa19b Merge pull request #310 from ZoiteChat/fix-tray-right-click-menu
Fix Windows GtkStatusIcon tray menu popup
2026-06-26 14:47:23 -06:00
65452d80f1 Use native Win32 tray context menu 2026-06-26 14:32:12 -06:00
deepend-tildeclub
df4d432151 Merge pull request #308 from ZoiteChat/replace-depreciated-ipv4-conversion-calls
Replace legacy IPv4 APIs
2026-06-25 21:34:39 -06:00
deepend-tildeclub
1390f2c783 Merge pull request #307 from ZoiteChat/ircv3-typing-reply-messagetags-echo-message
IRCv3 +message-tags, +echo-message, +typing +replies
2026-06-25 20:32:24 -06:00
be99d0e900 Add reply cache, composer bar, and nick-menu replies 2026-06-25 14:51:04 -06:00
97fcf1d5fc Replace legacy IPv4 APIs 2026-06-24 14:24:43 -06:00
c9b14d7c45 fix github bug template 2026-06-22 16:20:46 -06:00
3d0dda0347 Add github bug report template 2026-06-22 16:17:24 -06:00
deepend-tildeclub
8d69b35d67 Merge pull request #303 from ZoiteChat/fix-invalid-cert-setting
Restore accept-invalid-cert TLS bypass behavior
2026-06-22 16:00:55 -06:00
7ba142a9f2 Restore accept-invalid-cert TLS bypass behavior 2026-06-20 19:07:19 -06:00
deepend-tildeclub
56ac55806d Merge pull request #302 from ZoiteChat/theme-gtk3-tests-revert
Revert previous fixes merged to master without forcing.
2026-06-20 11:04:25 -06:00
47f53c14db Adjust Fedora package checks for GTK3 test stability 2026-06-20 10:59:10 -06:00
a47d40b914 Guard GTK3 theme tests for headless Xwayland 2026-06-20 10:25:02 -06:00
be33ae30bb Guard GTK theme ops when screen/seat missing 2026-06-20 09:51:16 -06:00
6d5f1306bf Guard legacy OpenSSL cleanup for 4.x builds 2026-06-20 09:24:05 -06:00
ad3dc716e7 2.18.2 release preparation 2026-06-20 08:44:47 -06:00
deepend-tildeclub
089b125a89 Merge pull request #301 from ZoiteChat/mode-button-multi-line-settings
Add topic layout prefs for inline modes/multiline topics
2026-06-20 07:58:25 -06:00
dfd4554a11 Move Topic Bar prefs under Appearance 2026-06-20 04:48:47 -06:00
1b98297881 Add topic layout prefs for inline modes/multiline topics 2026-06-20 04:09:45 -06:00
deepend-tildeclub
cacc8fcafe Merge pull request #299 from ZoiteChat/fix-unload-crash
Fix DBus unload null filename path
2026-06-18 19:45:38 -06:00
ac07d39db8 Fix DBus unload null filename path 2026-06-18 19:25:11 -06:00
deepend-tildeclub
e27aa77cec Merge pull request #296 from ZoiteChat/fishlim-manager
Add FiSHLiM manager and context menu hooks
2026-06-18 19:11:11 -06:00
cbc9f68dd6 Isolate GTK3 settings test data home + use xwayland-run 2026-06-18 18:57:49 -06:00
944d0f5a70 adding fedora packaging spec file. 2026-06-17 04:00:30 -06:00
9ded7ef830 Clarify FiSHLiM key manager invite wording 2026-06-16 15:47:56 -06:00
adb18b7ce3 Improve FiSHLiM key delete flow + CBC warnings 2026-06-15 23:26:59 -06:00
deepend-tildeclub
8220e60257 Merge pull request #297 from ZoiteChat/fix-flatpak-tabs-tree-errors
Expose hidden channel tree for tab switcher compatibility
2026-06-15 23:01:03 -06:00
b09af655e7 Add FiSHLiM GTK key manager + context menu access 2026-06-15 22:56:50 -06:00
5833f8a3d8 Expose hidden channel tree for tab switcher compatibility 2026-06-15 22:52:20 -06:00
deepend-tildeclub
72132d5e88 Merge pull request #283 from ZoiteChat/tray-iconified-restore
Fix tray restore for iconified windows
2026-06-15 14:22:47 -06:00
deepend-tildeclub
4167c4db78 Merge pull request #293 from ZoiteChat/feat-reset-keybinds
Add keybind reset while preserving custom rows
2026-06-12 10:37:36 -06:00
3e1f6b9137 Add keybind reset while preserving custom rows 2026-06-12 08:22:03 -06:00
deepend-tildeclub
bb08b19c0d Merge pull request #291 from ZoiteChat/extend-keybind-change-page-targeting
Allow keybinds to switch by channel name
2026-06-10 16:28:18 -06:00
deepend-tildeclub
7497919e8f Merge pull request #290 from ZoiteChat/ssl-init--modern-apis
Harden TLS defaults, cert checks, and hostname failures
2026-06-10 16:24:19 -06:00
deepend-tildeclub
41cbe7b31c Merge pull request #287 from ZoiteChat/customize-all-keybinds
Customize all keybinds
2026-06-10 16:22:52 -06:00
425e951341 Allow keybinds to switch by channel name 2026-06-10 16:19:01 -06:00
2e4a0b92fc Harden TLS defaults, cert checks, and hostname failures 2026-06-10 16:13:59 -06:00
9f839579e2 Show live menu accelerators from keybinds 2026-06-10 15:57:23 -06:00
900066b9d4 Add option to hide join/part hostmasks 2026-06-10 15:09:47 -06:00
97534b0670 Add option to hide join/part hostmasks 2026-06-10 15:05:00 -06:00
deepend-tildeclub
62672ade04 Merge pull request #285 from ZoiteChat/no_proxy_option
Add -noproxy to /server
2026-06-10 09:05:39 -06:00
b22e1c1ccc Add -noproxy to /server 2026-06-10 08:47:52 -06:00
deepend-tildeclub
1b8e60c26d Merge pull request #284 from ZoiteChat/windows-taskbar-improvement
Limit Win32 taskbar workaround to size state changes
2026-06-10 08:20:33 -06:00
035dfdd332 Limit Win32 taskbar workaround to size state changes 2026-06-09 14:02:41 -06:00
d1707d3c72 Fix tray restore for iconified windows 2026-06-09 13:55:43 -06:00
deepend-tildeclub
c9bed097ba Merge pull request #282 from ZoiteChat/improve-theme-switching-consistency
Fix GTK3 theme background refresh/saving
2026-06-09 11:37:44 -06:00
5acb90025f Fix GTK3 theme background refresh/saving 2026-06-09 11:23:38 -06:00
deepend-tildeclub
2d12da79b0 Merge pull request #279 from ZoiteChat/resolver-api-updates
Replace DCC IPv4 lookups with getaddrinfo
2026-06-06 20:21:42 -06:00
deepend-tildeclub
bede379021 Merge pull request #267 from ZoiteChat/keyring-support
Keyring support
2026-06-06 15:54:47 -06:00
38acde1b20 Replace DCC IPv4 lookups with getaddrinfo 2026-06-06 15:50:52 -06:00
d0c4a5addd Fix keyring mode switching and password reveal state 2026-06-05 10:38:16 -06:00
deepend-tildeclub
b8a7ccf005 Merge pull request #277 from ZoiteChat/fix-window-restore
Defer topic relayout after restore-down
2026-06-05 09:15:10 -06:00
68fc53585e Defer topic relayout after restore-down 2026-06-04 15:24:37 -06:00
26afc125c7 Add optional keyring + encrypted password fallback 2026-06-02 15:57:28 -06:00
deepend-tildeclub
090b39f78b Merge pull request #266 from ZoiteChat/network-meter-alignment
Align text network meter columns
2026-06-02 15:41:57 -06:00
3722de6b13 Align text network meter columns 2026-06-02 14:49:29 -06:00
deepend-tildeclub
d93c9aa780 Merge pull request #255 from ZoiteChat/kde-dialog-native-controls
Use native file chooser dialogs everywhere
2026-05-26 13:14:24 -06:00
ea56504aee Use native file chooser dialogs everywhere 2026-05-26 02:24:06 -06:00
46b91edfdf Bundle Perl paths into AppImage runtime 2026-05-25 23:08:41 -06:00
deepend-tildeclub
d2dfde519d Merge pull request #253 from ZoiteChat/timedate-tooltop
Add timestamp/date hover tooltips
2026-05-25 15:58:40 -06:00
1eac56f22c Add timestamp/date hover tooltips 2026-05-25 14:49:30 -06:00
deepend-tildeclub
fb491a6bb2 Merge pull request #249 from ZoiteChat/lost-connection-fixes
Add configurable stale-link ping checks
2026-05-25 12:21:45 -06:00
3da7c89b66 Add configurable stale-link ping checks 2026-05-25 11:43:10 -06:00
deepend-tildeclub
e842cf3a57 Merge pull request #248 from ZoiteChat/scroll-speed-fix
Fix xtext wheel scroll speed handling + prefs slider
2026-05-25 10:29:40 -06:00
85b0e8f1a6 Fix xtext wheel scroll speed handling + prefs slider 2026-05-25 09:45:22 -06:00
deepend-tildeclub
b8e03ff6c1 Merge pull request #247 from ZoiteChat/fishlim-fixes
Fix FiSHLiM OpenSSL provider/context init issues
2026-05-25 02:24:32 -06:00
54b1703d67 Make FiSHLiM OpenSSL provider loading non-fatal 2026-05-25 02:13:24 -06:00
15d647a0ec Fix detached tab reattach UAF crash path 2026-05-22 14:50:44 -06:00
deepend-tildeclub
353558ddb3 Merge pull request #243 from sney/project-readme
Project readme updates
2026-05-22 09:24:37 -06:00
Jesse Rhodes
9f58d30050 Add more short description. 2026-05-22 09:47:13 -04:00
Jesse Rhodes
4f0632cdf1 Update existing links. 2026-05-22 09:44:29 -04:00
Jesse Rhodes
ff8ba71948 Remove link to incomplete/outdated (?) troubleshooting guide. 2026-05-22 09:41:23 -04:00
Jesse Rhodes
a367591327 Fix download link 404. 2026-05-22 09:38:07 -04:00
Jesse Rhodes
c91925fbc2 Match intro to distro package short descriptions. 2026-05-22 09:36:44 -04:00
479f1649ef Bump release metadata to 2.18.1 2026-05-21 18:11:33 -06:00
deepend-tildeclub
06f69184b6 Merge pull request #239 from ZoiteChat/size_t_int_fixes
Size t int fixes
2026-05-21 17:10:07 -06:00
deepend-tildeclub
3471d9a57c Merge pull request #241 from ZoiteChat/win32-relax-arch-check
Use x64compatible for Windows installer arch checks
2026-05-21 17:09:51 -06:00
28d4035477 Use x64compatible for Windows installer arch checks 2026-05-21 02:50:49 -06:00
deepend-tildeclub
3e1d151efd Merge pull request #240 from ZoiteChat/win32-dependency-update
Use new GTK3 bundle zip in Windows CI
2026-05-21 02:21:51 -06:00
556cfc3036 Use new GTK3 bundle zip in Windows CI 2026-05-20 21:25:37 -06:00
c49b757be6 Clamp Win32 sysinfo UTF length casts 2026-05-20 15:51:07 -06:00
cec7e2caf3 Use size_t for strlen length temporaries 2026-05-20 15:34:19 -06:00
9a0c07a461 Clean up Win32 size/length casts 2026-05-20 15:22:06 -06:00
c7064c18b9 Clamp spell-provider length casts on Win32 2026-05-20 14:51:06 -06:00
deepend-tildeclub
fb897310c8 Merge pull request #238 from ZoiteChat/win32-taskbar-icon-identity
Set Windows AppUserModelID before GTK startup
2026-05-20 11:56:38 -06:00
5944849326 Guard fullscreen menu sync against null session 2026-05-20 10:34:35 -06:00
1255f1e6c7 Fix Win taskbar toggle restore check 2026-05-20 10:19:51 -06:00
d7bc09d859 Fix Win minimize-to-tray taskbar bounce 2026-05-20 09:46:33 -06:00
f84a448351 Fix Win tray restore timer loop/reentry 2026-05-19 21:39:02 -06:00
7e34690e0c Set Windows AppUserModelID before GTK startup 2026-05-19 17:20:08 -06:00
deepend-tildeclub
6f6d378600 Merge pull request #237 from ZoiteChat/ddh-null-gdkwindow
Guard GTK drag/drop handlers against null GdkWindow
2026-05-19 15:55:21 -06:00
deepend-tildeclub
216b463b8f Merge pull request #236 from ZoiteChat/native-dialog-box
Make native file chooser modal on Windows
2026-05-19 15:55:01 -06:00
4ad84cb5e5 Guard GTK drag/drop handlers against null GdkWindow 2026-05-19 15:46:06 -06:00
19e0946717 Make native file chooser modal on Windows 2026-05-19 15:11:55 -06:00
0de1ad06cd Null-safe /set string preference rendering 2026-05-19 15:07:21 -06:00
deepend-tildeclub
72427006dd Merge pull request #231 from mlt/py-eol
fix(python): concatenate bytes in compile_line of py console
2026-05-12 15:34:31 -06:00
deepend-tildeclub
0e5f702651 Merge pull request #233 from ZoiteChat/userlist-button-meter-layout-fix
Tighten userlist button/meter layout
2026-05-11 08:17:02 -06:00
4f1b0fc838 Tighten userlist button/meter layout 2026-05-11 01:35:57 -06:00
Mikhail Titov
23d0963c2d fix(python): decode the console input to str in _on_say_command 2026-05-10 23:57:43 -05:00
deepend-tildeclub
dcb35fb80f Merge pull request #230 from ZoiteChat/fix-scrollbar-consistency
UI Consistency: Use native GTK scrollbars across main panes
2026-05-10 08:31:15 -06:00
b1768854c3 Tighten userlist count/pane width behavior 2026-05-09 12:53:35 -06:00
4ed4eaf8e8 fix FSF address. 2026-05-08 20:50:51 -06:00
deepend-tildeclub
d167b53b17 Merge pull request #227 from ZoiteChat/fix-tab-scroll-skip
Consume handled tab wheel events
2026-05-08 07:52:37 -06:00
18eff80a30 UI Consistency: Use native GTK scrollbars across main panes 2026-05-07 13:29:22 -06:00
a44ec5f624 Consume handled tab wheel events 2026-05-07 12:03:41 -06:00
deepend-tildeclub
5da518f50e Merge pull request #226 from mlt/close-file-stream
fix(checksum): Make sure file stream is closed
2026-05-07 09:21:28 -06:00
Mikhail Titov
ebb11a8ac5 fix(checksum): Make sure file stream is closed 2026-05-07 09:11:10 -05:00
deepend-tildeclub
dd9c5db1aa Merge pull request #224 from ZoiteChat/channel_switcher_mouse_wheel_fix
Fix and Enable mouse wheel channel switching by default
2026-05-06 13:35:13 -06:00
984ac9763e Fix and Enable mouse wheel channel switching by default 2026-05-06 13:10:30 -06:00
deepend-tildeclub
ad67af2f8f Merge pull request #222 from ZoiteChat/fix-autocorrect-replace
Fix auto-replace to respect whole-word matches
2026-05-05 17:32:29 -06:00
cc8460d366 Fix auto-replace to respect whole-word matches 2026-05-05 17:22:56 -06:00
1dc9b9c956 Use Lua 5.4 deps in CI 2026-04-29 16:21:01 -06:00
deepend-tildeclub
20b552ad9d Merge pull request #214 from ZoiteChat/bump_perl_version_5_42_2
flatpak: bump perl to 5.42.2
2026-04-29 16:15:42 -06:00
deepend-tildeclub
12f4885ad7 Merge pull request #217 from ZoiteChat/remove-winamp-plugin
Remove Winamp Plugin.
2026-04-29 16:14:59 -06:00
9c1fdef286 Default Lua pkg-config target to lua-5.4 2026-04-29 16:10:06 -06:00
c125a154da More missed Winamp references. 2026-04-29 16:08:42 -06:00
028d768b23 Remove another Winamp reference. 2026-04-29 15:57:19 -06:00
42972e01b0 Remove Winamp Plugin. 2026-04-29 15:42:11 -06:00
1bd37af7d0 Refresh menu sizing after theme switch 2026-04-27 15:36:22 -06:00
5fad2d0c02 flatpak: bump perl to 5.42.2 2026-04-27 13:43:13 -06:00
deepend-tildeclub
bd9adeb795 Merge pull request #211 from ZoiteChat/gtk-state-mismatch
Fix palette color reads to preserve base GTK state
2026-04-27 13:40:28 -06:00
deepend-tildeclub
5fd3c7c9d9 Merge pull request #210 from ZoiteChat/dbus_gdbus_migration
Migrate to gdbus.
2026-04-27 13:38:52 -06:00
937184429f Drop leftover dbus-glib refs/deps 2026-04-27 13:30:24 -06:00
e31ccff3f8 Fix palette color reads to preserve base GTK state 2026-04-27 13:26:53 -06:00
edaafba7eb Drop dbus-glib deps, port example to GDBus 2026-04-27 13:13:16 -06:00
87018fa7fe Migrate to gdbus. 2026-04-27 11:04:04 -06:00
8fcec519d0 Fix tab section scrolling on overflow 2026-04-27 10:44:12 -06:00
aa52236c01 New Release: 2.18.0 2026-04-20 11:56:03 -06:00
cd5b61d9d0 Use KiB/MiB units for DCC speed limits 2026-04-20 10:38:22 -06:00
4eefdd6011 Stop palette CSS from styling radio rings 2026-04-20 10:32:54 -06:00
deepend-tildeclub
21748b3133 Merge pull request #204 from ZoiteChat/flatpak-improvements
Tighten Flatpak perms, pin lgi source
2026-04-16 10:53:49 -06:00
deepend-tildeclub
e7db202f88 Merge pull request #207 from ZoiteChat/domain-url-parsing
Validate host links with PSL rules
2026-04-15 02:36:09 -06:00
a93e76eaf4 Add PSL fetch step to Flatpak CI 2026-04-14 15:56:43 -06:00
3c5bdb2ab2 Revert Meson PSL input wiring, keep generator fallback logic 2026-04-14 09:53:16 -06:00
8f7c40caf1 Validate host links with PSL rules 2026-04-13 20:10:28 -06:00
150ad62771 Fix GTK populate test stub linkage 2026-04-13 14:54:59 -06:00
deepend-tildeclub
5e3715dc31 Merge pull request #206 from ZoiteChat/close-undo-close-tabs
Add Ctrl+W close + Ctrl+Shift+T tab reopen
2026-04-13 11:44:44 -06:00
05648ba8e3 Add Ctrl+W close + Ctrl+Shift+T tab reopen 2026-04-13 10:55:59 -06:00
deepend-tildeclub
0956471a10 Merge pull request #205 from ZoiteChat/fix-nickname-in-topic-linking
Tighten topic link detect/open rules
2026-04-13 07:57:26 -06:00
1f79e62f53 Tighten topic link detect/open rules 2026-04-13 00:08:00 -06:00
deepend-tildeclub
c123e93704 Merge pull request #203 from ZoiteChat/fix-network-tree-collapse-channel-focus
Ignore collapsed parent selects in chanview tree
2026-04-12 23:50:59 -06:00
e49333340f Tighten Flatpak perms, pin lgi source 2026-04-12 23:43:12 -06:00
7210f16dfc Ignore collapsed parent selects in chanview tree 2026-04-12 23:09:52 -06:00
deepend-tildeclub
2825e992df Merge pull request #201 from ZoiteChat/fix-notification-icon
fix notification icon + fix flatpak screenshot
2026-04-12 21:00:37 -06:00
8e8f534c21 fix notification icon + fix flatpak screenshot 2026-04-12 20:46:47 -06:00
d3d5ac905c Enabled colored nicknames in the userlist by default 2026-04-12 15:41:20 -06:00
deepend-tildeclub
4cca23a283 Merge pull request #198 from ZoiteChat/Expand_palette_and_selection_CSS
Expand palette and selection css
2026-04-11 09:07:21 -06:00
0f3fffc728 Broaden GTK3 palette/state selector coveragez even more. 2026-04-10 15:40:58 -06:00
7261d8637d Expand palette CSS + selection state coverage 2026-04-10 14:12:14 -06:00
deepend-tildeclub
8a1e82986a Merge pull request #197 from ZoiteChat/pevents_import_from_hct
Import pevents from .hct too, show clearer result dialog
2026-04-09 13:23:17 -06:00
deepend-tildeclub
e8d421b3df Merge pull request #196 from ZoiteChat/topic_bar_height
Auto-size topic bar to wrapped text
2026-04-09 13:23:03 -06:00
cb2c09b2ba Import pevents from .hct too, show clearer result dialog 2026-04-09 12:30:23 -06:00
a14026f0d1 Add vertical padding to topic text view 2026-04-09 10:56:39 -06:00
e237df31f4 Auto-size topic bar to wrapped text 2026-04-09 10:03:59 -06:00
deepend-tildeclub
a9421c63a9 Merge pull request #195 from ZoiteChat/hct_import_support
Add .hct support to colors.conf import
2026-04-09 08:55:48 -06:00
4f2938d957 Add .hct support to colors.conf import 2026-04-08 17:47:51 -06:00
deepend-tildeclub
8fa9d3678b Merge pull request #194 from ZoiteChat/add-python-perl-lua-versions-to-installer
Label installer runtimes with exact versions
2026-04-07 09:16:41 -06:00
4bbc23c185 Label installer runtimes with exact versions 2026-04-07 08:42:23 -06:00
deepend-tildeclub
73733e4176 Merge pull request #193 from ZoiteChat/pasted_path_fix
Refine cmd-char parsing for pasted path text
2026-04-06 15:17:37 -06:00
9f8faf0a6e Refine cmd-char parsing for pasted path text mentioned at https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=897426 2026-04-06 14:26:47 -06:00
deepend-tildeclub
5ff704459c Merge pull request #192 from sney/use-appstream
Improve appstream metainfo validation
2026-04-06 14:00:52 -06:00
Jesse Rhodes
0ccc730d85 test: Validate plugin metainfo. 2026-04-05 10:35:54 -04:00
Jesse Rhodes
093ffe791e test: Switch to appstreamcli for metainfo validation. 2026-04-05 10:31:19 -04:00
b9ff051043 Drop forced bold from userlist prefixes 2026-04-03 08:15:38 -06:00
10b6d5927b Tighten tab layout, right-align close button 2026-04-03 07:54:01 -06:00
0052b6589e updated svg. removed unused svg files 2026-04-01 22:52:27 -06:00
51e9ac15df fix zoitechat.svg 2026-04-01 00:21:44 -06:00
deepend-tildeclub
4df579e0d3 Merge pull request #185 from ZoiteChat/improve-appimage-URI-folder-opening
Sanitize Linux open env for AppImage, add safer fallbacks
2026-04-01 00:15:53 -06:00
2cf69d2e67 Sanitize Linux open env for AppImage, add safer fallbacks 2026-03-31 23:26:40 -06:00
deepend-tildeclub
64bffd0dc8 Merge pull request #184 from ZoiteChat/add-gtk3-themes-windows-installer
Theme: Adding themes users can select at install time.
2026-03-31 19:38:40 -06:00
deepend-tildeclub
b451240dfd Merge pull request #181 from ZoiteChat/prefs-option-disable-tab-close-button
Add pref for tab close buttons
2026-03-31 19:30:26 -06:00
41cc1f617f Theme: Adding themes users can select at install time. 2026-03-31 19:28:45 -06:00
ed671eac25 Add pref for tab close buttons 2026-03-31 18:10:28 -06:00
9f2529adb6 Update all links to new domain. 2026-03-30 18:25:26 -06:00
deepend-tildeclub
3903d7b9e5 Change official documentation link in README
Updated the link for the official documentation from zoitechat.zoite.net to zoitechat.org.
2026-03-30 17:16:01 -06:00
121 changed files with 7128 additions and 2331 deletions

132
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,57 @@
---
name: Feature request
about: Suggest an improvement for ZoiteChat
title: "[Feature]: "
labels: enhancement
assignees: ""
---
## Summary
```text
Describe the feature or enhancement clearly and briefly.
```
## Problem
```text
What problem would this solve?
```
## Proposed Solution
```text
How should ZoiteChat behave?
```
## Alternatives Considered
```text
Other approaches or workarounds you considered.
```
## Use Case
```text
Who benefits from this, and when would they use it?
```
## Platform Details
```text
OS:
ZoiteChat version:
Build/source: GitHub release / Fedora package / source build / Windows installer / other
```
## Checklist
* [ ] I searched existing issues first.
* [ ] I checked whether this already exists in the latest ZoiteChat version available to me.
* [ ] I explained the problem this would solve.
## Extra Notes
```text
Anything else that might help.
```

View File

@@ -28,15 +28,16 @@ jobs:
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
build-essential pkg-config meson ninja-build cmake \ build-essential pkg-config meson ninja-build cmake \
gettext \ gettext \
libcanberra-dev libdbus-glib-1-dev libglib2.0-dev \ libcanberra-dev libglib2.0-dev \
libarchive-dev \ libarchive-dev \
libgtk-3-dev \ libgtk-3-dev \
libwayland-client0 libwayland-cursor0 libwayland-egl1 \ libwayland-client0 libwayland-cursor0 libwayland-egl1 \
libxkbcommon0 \ libxkbcommon0 \
libgtk-3-bin libglib2.0-bin shared-mime-info gsettings-desktop-schemas \ libgtk-3-bin libglib2.0-bin shared-mime-info gsettings-desktop-schemas \
libluajit-5.1-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \ liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \ perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
fonts-noto-color-emoji breeze-gtk-theme \ fonts-noto-color-emoji breeze-gtk-theme \
python3-venv \
patchelf file curl patchelf file curl
- name: Configure - name: Configure
@@ -86,11 +87,21 @@ jobs:
cp -a /usr/lib/x86_64-linux-gnu/python3/dist-packages AppDir/usr/lib/x86_64-linux-gnu/python3/ cp -a /usr/lib/x86_64-linux-gnu/python3/dist-packages AppDir/usr/lib/x86_64-linux-gnu/python3/
fi fi
if [ -d "/usr/lib/x86_64-linux-gnu/perl-base" ]; then
install -d AppDir/usr/lib/x86_64-linux-gnu
cp -a /usr/lib/x86_64-linux-gnu/perl-base AppDir/usr/lib/x86_64-linux-gnu/
fi
if [ -d "/usr/lib/x86_64-linux-gnu/perl" ]; then if [ -d "/usr/lib/x86_64-linux-gnu/perl" ]; then
install -d AppDir/usr/lib/x86_64-linux-gnu install -d AppDir/usr/lib/x86_64-linux-gnu
cp -a /usr/lib/x86_64-linux-gnu/perl AppDir/usr/lib/x86_64-linux-gnu/ cp -a /usr/lib/x86_64-linux-gnu/perl AppDir/usr/lib/x86_64-linux-gnu/
fi fi
if [ -d "/usr/lib/x86_64-linux-gnu/perl5" ]; then
install -d AppDir/usr/lib/x86_64-linux-gnu
cp -a /usr/lib/x86_64-linux-gnu/perl5 AppDir/usr/lib/x86_64-linux-gnu/
fi
if [ -d "/usr/share/perl" ]; then if [ -d "/usr/share/perl" ]; then
install -d AppDir/usr/share install -d AppDir/usr/share
cp -a /usr/share/perl AppDir/usr/share/ cp -a /usr/share/perl AppDir/usr/share/
@@ -100,6 +111,10 @@ jobs:
install -d AppDir/usr/share install -d AppDir/usr/share
cp -a /usr/share/perl5 AppDir/usr/share/ cp -a /usr/share/perl5 AppDir/usr/share/
fi fi
perl -MFile::Spec -e 'print "Build host File::Spec: $INC{\"File/Spec.pm\"}\n"'
find AppDir/usr -path '*/File/Spec.pm' -print -quit | grep -q .
if compgen -G '/usr/lib/x86_64-linux-gnu/libpython3*.so*' > /dev/null; then if compgen -G '/usr/lib/x86_64-linux-gnu/libpython3*.so*' > /dev/null; then
install -d AppDir/usr/lib/x86_64-linux-gnu install -d AppDir/usr/lib/x86_64-linux-gnu
cp -a /usr/lib/x86_64-linux-gnu/libpython3*.so* AppDir/usr/lib/x86_64-linux-gnu/ cp -a /usr/lib/x86_64-linux-gnu/libpython3*.so* AppDir/usr/lib/x86_64-linux-gnu/
@@ -162,7 +177,7 @@ jobs:
APPDIR="${APPDIR:-$(dirname "$(readlink -f "$0")")}" APPDIR="${APPDIR:-$(dirname "$(readlink -f "$0")")}"
export PATH="${PATH:-/usr/bin:/bin}:$APPDIR/usr/bin" export PATH="$APPDIR/usr/bin:${PATH:-/usr/bin:/bin}"
export LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}" export LD_LIBRARY_PATH="$APPDIR/usr/lib:$APPDIR/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}"
export XDG_DATA_DIRS="$APPDIR/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" export XDG_DATA_DIRS="$APPDIR/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
export GTK_EXE_PREFIX="$APPDIR/usr" export GTK_EXE_PREFIX="$APPDIR/usr"
@@ -192,6 +207,10 @@ jobs:
export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/zoitechat/plugins" export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/zoitechat/plugins"
fi fi
if [ -f "$APPDIR/usr/share/doc/zoitechat/html/index.html" ]; then
export ZOITECHAT_DOCDIR="$APPDIR/usr/share/doc/zoitechat/html"
fi
if [ -d "$APPDIR/usr/share/glib-2.0/schemas" ]; then if [ -d "$APPDIR/usr/share/glib-2.0/schemas" ]; then
export GSETTINGS_SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}" export GSETTINGS_SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
fi fi
@@ -211,6 +230,23 @@ jobs:
unset GTK_MODULES unset GTK_MODULES
perl5lib_entries=""
for dir in \
"$APPDIR/usr/lib/x86_64-linux-gnu/perl-base" \
"$APPDIR/usr/lib/x86_64-linux-gnu/perl"/* \
"$APPDIR/usr/lib/x86_64-linux-gnu/perl5"/* \
"$APPDIR/usr/share/perl"/* \
"$APPDIR/usr/share/perl5"
do
if [ -d "$dir" ]; then
perl5lib_entries="${perl5lib_entries:+$perl5lib_entries:}$dir"
fi
done
if [ -n "$perl5lib_entries" ]; then
export PERL5LIB="$perl5lib_entries${PERL5LIB:+:$PERL5LIB}"
fi
export PYTHONHOME="$APPDIR/usr" export PYTHONHOME="$APPDIR/usr"
python_stdlib_dir="$(find "$APPDIR/usr/lib" -maxdepth 1 -type d -name 'python3.*' | head -n 1 || true)" python_stdlib_dir="$(find "$APPDIR/usr/lib" -maxdepth 1 -type d -name 'python3.*' | head -n 1 || true)"
pythonpath_entries="" pythonpath_entries=""
@@ -256,7 +292,6 @@ jobs:
EOF EOF
chmod +x AppRun chmod +x AppRun
VERSION="$(git describe --tags --always)" VERSION="$(git describe --tags --always)"
./linuxdeploy-x86_64.AppImage \ ./linuxdeploy-x86_64.AppImage \

View File

@@ -25,6 +25,9 @@ jobs:
with: with:
submodules: true submodules: true
- name: Generate PSL list
run: curl -fsSL https://publicsuffix.org/list/public_suffix_list.dat -o src/common/public_suffix_list.dat
- name: Build Flatpak - name: Build Flatpak
id: flatpak_builder id: flatpak_builder
uses: flatpak/flatpak-github-actions/flatpak-builder@v6 uses: flatpak/flatpak-github-actions/flatpak-builder@v6

View File

@@ -32,10 +32,8 @@ jobs:
glib2-devel \ glib2-devel \
gtk3 \ gtk3 \
openssl \ openssl \
dbus-glib \
libcanberra \ libcanberra \
libayatana-appindicator \ libayatana-appindicator \
luajit \
iso-codes \ iso-codes \
lua \ lua \
perl \ perl \

View File

@@ -65,8 +65,8 @@ jobs:
Download-WithRetry -Url https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe -OutFile deps\innosetup-unicode.exe Download-WithRetry -Url https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe -OutFile deps\innosetup-unicode.exe
& deps\innosetup-unicode.exe /VERYSILENT | Out-Null & deps\innosetup-unicode.exe /VERYSILENT | Out-Null
Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.0-pre1/GTK3_Gvsbuild_zoitechat-2.18.0-pre1_${{ matrix.platform }}.7z -OutFile deps\gtk-${{ matrix.arch }}.7z Download-WithRetry -Url https://github.com/ZoiteChat/gvsbuild/releases/download/zoitechat-2.18.1/GTK3_Gvsbuild_zoitechat-2.18.1_x64.zip -OutFile deps\gtk-${{ matrix.arch }}.zip
& 7z.exe x deps\gtk-${{ matrix.arch }}.7z -oC:\gtk-build\gtk\x64\release Expand-Archive -LiteralPath deps\gtk-${{ matrix.arch }}.zip -DestinationPath C:\gtk-build\gtk\x64\release -Force
Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-hicolor-icon-theme-0.18-1-any.pkg.tar.zst -OutFile deps\hicolor-icon-theme.pkg.tar.zst Download-WithRetry -Url https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-hicolor-icon-theme-0.18-1-any.pkg.tar.zst -OutFile deps\hicolor-icon-theme.pkg.tar.zst
python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\hicolor-icon-theme.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith('mingw64/share/icons/hicolor/')];tf.close();reader.close();f.close()" python -c "import tarfile,zstandard,pathlib;archive=pathlib.Path(r'deps\\hicolor-icon-theme.pkg.tar.zst');target=pathlib.Path(r'C:\\gtk-build\\gtk\\x64\\release');dctx=zstandard.ZstdDecompressor();f=archive.open('rb');reader=dctx.stream_reader(f);tf=tarfile.open(fileobj=reader,mode='r|');[tf.extract(m,path=target) for m in tf if m.name.startswith('mingw64/share/icons/hicolor/')];tf.close();reader.close();f.close()"
@@ -113,6 +113,33 @@ jobs:
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
} }
# Runs while Python 3.14 is still the active interpreter; the installer
# packages rel\offline-docs as the "Offline Documentation" component and
# skips it if this step could not build the docs.
- name: Build offline documentation
run: |
python data\misc\build_offline_docs.py --output ..\zoitechat-build\${{ matrix.platform }}\rel\offline-docs
- uses: actions/setup-python@v6
with:
python-version: '3.8.10'
architecture: ${{ matrix.arch }}
- name: Prepare Python 3.8
run: |
python -m pip install --upgrade pip
python -m pip install cffi
$pyRoot = $env:pythonLocation
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
foreach ($pyDir in @("C:\gtk-build\python-3.8.10", "C:\gtk-build\python-3.8")) {
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
$target = Join-Path $pyDir "${{ matrix.platform }}"
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
}
- name: Build - name: Build
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
@@ -124,12 +151,33 @@ jobs:
exit /b 1 exit /b 1
) )
set "PYTHON38_DIR=C:\gtk-build\python-3.8\${{ matrix.platform }}"
if not exist "%PYTHON38_DIR%\libs\python38.lib" (
echo Missing %PYTHON38_DIR%\libs\python38.lib
dir "%PYTHON38_DIR%\libs"
exit /b 1
)
if not exist "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" (
echo Missing %PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd
exit /b 1
)
set "LIB=%PYTHON_DIR%\libs;%LIB%" set "LIB=%PYTHON_DIR%\libs;%LIB%"
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%" set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }" powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }"
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
if errorlevel 1 exit /b 1
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\plugins\hcpython38.dll" (
echo hcpython38.dll was not built
exit /b 1
)
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\_cffi_backend.cp38*.pyd" (
echo _cffi_backend for Python 3.8 was not staged for the installer
exit /b 1
)
shell: cmd shell: cmd
- name: Preparing Artifacts - name: Preparing Artifacts

46
COPYING
View File

@@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 2, June 1991 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc. Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
Preamble Preamble
The licenses for most software are designed to take away your The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to the GNU Lesser General Public License instead.) You can apply it to
your programs, too. your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains 0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on does not normally print such an announcement, your work based on
the Program is not required to print an announcement.) the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program, identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not distribution of the source code, even though third parties are not
compelled to copy the source along with the object code. compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program 4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License. be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in 8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally. of promoting the sharing and reuse of software generally.
NO WARRANTY NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it possible use to the public, the best way to achieve this is to make it
@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author> Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -303,17 +303,15 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License along
along with this program; if not, write to the Free Software with this program; if not, see <https://www.gnu.org/licenses/>.
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this If the program is interactive, make it output a short notice like this
when it starts in an interactive mode: when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.
@@ -330,11 +328,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker. `Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989 <signature of Moe Ghoul>, 1 April 1989
Ty Coon, President of Vice Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. Public License instead of this License.

View File

@@ -1,7 +1,75 @@
ZoiteChat ChangeLog ZoiteChat ChangeLog
================= =================
2.18.3 (2026-06-29)
-------------------
- Added IRCv3 support for message tags, echo-message, typing notifications, and replies.
- Restored the accept-invalid-cert TLS bypass behavior.
- Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.
- Snapshot Python hooks during plugin unload for safer plugin shutdown.
- Fixed Windows GtkStatusIcon tray menu popup behavior.
- Fixed self-echoed private messages routing to the target tab.
- Fixed the lag ping timeout window.
2.18.2 (2026-06-20)
-------------------
- Improved security and connection handling with stronger TLS defaults,
safer certificate/hostname validation, FiSHLiM OpenSSL fixes, keyring support,
and getaddrinfo-based DCC lookups.
- Expanded FiSHLiM usability with a GTK key manager, context-menu access, and clearer
channel/private-message key handling.
- Improved keyboard and navigation behavior with customizable keybinds, reset support,
channel-name switching, /server -noproxy, and configurable stale-link ping checks.
- Refined the GTK interface with native file chooser dialogs, topic layout preferences,
timestamp/date hover tooltips, better restore-down relayout, text scroll speed controls,
and aligned network meters.
- Added privacy and display options, including hiding join/part hostmasks and improved
topic/multiline mode layout controls.
- Fixed several platform/UI edge cases, including tray restore from iconified state, DBus unload
handling, GTK3 theme refresh/saving, and tab switcher compatibility.
2.18.1 (2026-05-21)
-------------------
- Migrated D-Bus handling to GDBus.
- Enabled mouse wheel channel switching by default and consumed handled tab wheel events.
- Switched main panes to native GTK scrollbars.
- Tightened userlist button and meter layout.
- Made the native Windows file chooser modal.
- Set the Windows AppUserModelID before GTK startup.
- Updated Windows CI to use the new GTK3 bundle zip.
- Updated Windows installer architecture checks to use x64-compatible detection.
- Bumped the Flatpak Perl runtime to 5.42.2.
- Fixed palette color reads to preserve base GTK state.
- Fixed auto-replace whole-word matching.
- Fixed checksum file stream cleanup.
- Fixed byte handling in the Python console.
- Guarded GTK drag-and-drop handlers against null windows.
- Fixed size_t and integer handling issues.
- Removed the Winamp plugin.
2.18.0 (2026-04-20)
-------------------
- Added optional close buttons on tabs.
- Added Ctrl+W to close tabs and Ctrl+Shift+T to reopen recently closed tabs.
- Expanded theme import support with .hct support in colors.conf import.
- Added pevent import support from .hct theme files and improved import result messaging.
- Expanded palette and selection CSS styling for better theme consistency.
- Auto-sizes the topic bar to wrapped text for better multi-line topic handling.
- Refined command-character parsing when pasting path-like text.
- Tightened topic link detection and opening rules.
- Added Public Suffix List validation for host links to reduce bad link matches.
- Improved channel tree behavior by ignoring collapsed parent selections.
- Sanitized the Linux open environment for AppImage builds and added safer fallback handling.
- Labeled Windows installer runtimes with exact versions.
- Fixed the notification icon and corrected the Flatpak screenshot asset.
- Improved AppStream metainfo validation.
2.18.0~pre6 (2026-03-30) 2.18.0~pre6 (2026-03-30)
------------------------
- Applied app theme CSS to the menubar consistently across the app. - Applied app theme CSS to the menubar consistently across the app.
- Restored horizontal separator lines in menus. - Restored horizontal separator lines in menus.

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
version="1.1"
width="0.213333in" height="0.213333in"
viewBox="0 0 64 64">
<defs>
</defs>
<image id="raster0"
x="0"
y="0"
width="64"
height="64"
opacity="1.000000"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAADJpJREFUeNrdm310lNWdxz/3eZnMJJMJeSeBIO9CbCtlQUWksuvaetSuLVrkWLqIbttj7fGsaGu71V2ptbYe+3KsdNetdd2tdUUqKos9aHcLHiwKCSu0yItSggSESSIJyWTen/vbP2aSzJCZyUwSAuzvnOfceZ7nzr33972/t/u791GcLbrngK0wvCIC4gT4yezY2RiGOqONP9jqRrgcLfMQuQiR2Wi5AJEatIAIkiyT9360HEFkPyJ70NKM1tt48hPh8wYA9b3jNYjcjMj1aPlLROw+ZlMYJQsAZKgbQ8sWRDYispan5vjPSQDUI/6rEbkTkc+ixRiCqUIASC01IhvRsoZn5r5+TgCgHm2/AZEHEZlDFmZ8CmrdCq8NHlthGWCboBBEAC3EHU0sqolENcGwQ0fAoS3sZAMCRHahZTW/mvfy2QHg6+9+TFWWrqHE86lMMzbZragpUfg8Bm5bDcyqkPI7CUDGdxCPaXoCMT46FeNgZ4y4k1FKtiJyJ7++5E9jAoB6tM2SA8f+Ecv4tppeb6UO2gvM8CmqSw1clsrKmCR/qyEASAXKiQudXVFa/BH8Ied0NYmj5RFEf5fnF8TPHAB37mmgJ/wCjr5MzagDrwe0UKyExjKTGp+BoZIDdoRgSNMbcgiENcGIpiOiCcZ12iwaCHW2wmOB16XwFhkUewxsU6UBQFJVEKGrK8r7x8MJINLVYjsiX2DtgtbRB+Cruy+nJ7weoZZxxajp9SBCY4liUoWJZUA8rjnZ7dAWcDjc64zICI53KSaUmZSXWrhdagAABDSIaNraI+w+FiLqpP3fj8gS1l2+bfQAuLX5M4RiL6GUB6VQsydQ6ivmk1UGPo8iGNIc64zzXsBJMFSAZc/HC0wuNmiotCnzWv0AJHWJSNhhf0svrYF46n+CiCzhNwtfGzkAtzZfQzD6CuDCMMBbxKS5k2msMYnHhcMdcf7cm9Naj5obrC9STB9fhK/E6gcAEcQRPjgaZE9bJPU/UURuYP0Vm4YPwK3NCwlEfgd4MBQYBjM+Xsv0WVUc74yzu8sZym+PdhwAIsz0mUypd2P3G9qEepzwh2huDaXWDSFyNS8t+kPhAKzcOYmecBMiNSgFhmJGhYuGRdPY2wn+iM57wKMNAFooNmBug5txPivNPrS3h9l+OJhatw2R+bzyqSOZ2DQyMv/ICYtA+AUcXdP3aKrPomq8lzc7wB8TzjYFHeHNliBHj4cSrjRJ1ZVu5tYXpVatAV7gb7ZY+QPwzp/vJ+Zc2t+o28DrMXkrYHAO8J5Gu05EOfhBbyK2SFJ9XTGzKu3UapeC+k5+KrB8+2wCkV2AC6VQhmLyOBctPXHU7IlQXppFLDU48cJEOV8VyNmGAhQzfSYzJpegJOEhdFyzc+8p/MG+oElH0VzMxsX7U9kdLBa9kcfQ4sJIYOMxFS2nYmAo8BRlmQfhyN+VM7HKNaaz//6hD1n0D7vxmxW81yW4jgaZPKE4IdqGonFKCf53u/vm2gXyGHB9dhW4edtCYvra03Wtn4rsjAN5cLo15sy3Hj3B8vvW48cDKjFZe9qjtHcMpA5KSmwuqkkdl1zHdZsXZgcgGv92To9pZjYZX5hfPKbMt3V0svJb69ihZoJdkqbJTa0hQqGB5cDEOg9WuqJ/KzMAy9+eTMy5LmuvRmaPeUedYnaDe8yY7+4OcM/qdfxPZDq4yweZMS1w4HCw3zPYtkljbVG6FFz7+8mDAeiJ3IoUHjKtvHTsZr83GObeh57n2RP1UFwNKrNEHg06dJwcUIWaqqJ0ToQVgwHQclPO3vVgdD7vgzlTx2b2Y7E4P3j8JX7RUg0l47My30f7joXRSfvldls0eM1UKbgxfV5v3jaR7nArhqIv6kv7nSzVwsY0tyTxGHS0QE97ynMGL2PT7vtcWOL5Py0o5zt3XIVtW1mZiTsOP17zEve9ocB3AWDmFS1eeoGH6ooiEKGtLciOlt7U/iey6apjiV5D0UV5TUM0BikDVaYFVVOhcsoQ4a9OZ1wLiOarDRbf/MokbNvM2e0zz73OfVscGDcNlJVRGjPR4RPhBABAqXeQB1sEPG8kxfuSvAEYZBuMxGVkKZUBykxchtn/7kt1Nj+8rYHiotzMr3vlDb788ikonwaGXZDa+MOaYDAxZo/HwlRphmzegA0QZuUXgI9Oev6vfYof3VafXNZmp9c2N7P03z+E8hlgDi/OONUd61f2iaVpdmDWAABaj8+nMekOjZj5S9zw9O3jqSrLzdCO/93HNY/vg8rhMw/QdmpAakvcadJWPwCAaUzIq7XO3hExP8UUfn17LRNrcnuOP+09xKKH3oaqWWCNzMu0Bpz+hZKdHhFVDwDg6Mq8Wotr6AkOayAGwovLK5k2wZPbcB05zrKHthCtagTbM3J9E4hEnGQslwZAbaoNyL+9k93DGsXWZeXMmVGas9ax4+3c8sBv2ettBJd31DauolGdNbTrU4FT+StVDzi6oAFsuLaUBRf5cmtXVzdf+96rvOWaDW5f/wJnNMhJBkRa0ma6LVUFjuTdmhboyB+v5670cP1l5Tnr9ASCrHr4RTZEpoGn/IxtWsfj2QAwVHtBAn28MxHcDKF7T8xzsXRxbvMSjkS5/7H1PNMxEUqqzuiOfW/YSdWA46k2YH9hUGrE35mzysONFl+5pirbIjIR38fjPPYvG3n8cBWUTRgyvh+pMTwaSAWAAwMAKJoKbtB/CsLRjK++McXkns9VY5nZuXe05omnN/HALg+Mm5SIFEXS8v2JLSCdvRSdlwV3uQxC4Tjx9BC6aSAlZhpbieuCEZXWDtTU9BjqthrFA0sqE5ujuQxT3GHZDQtZ9nlXch5kUPtZbtJWiDc9sIOmiC+n6tgug67OyOmPtw4A8F+LW7j294eAqQWBEIoi/k5UbcLILS1X/GhZBV6Pmces2NTVlo9Iqnfs3EfTyQCUlGWtU2orXLaJ/2SatO5n01Wt6fkAxdphjeKjAHT1coUbfrq0PLl/Nzb0y7U7wF2R02VO8FlEIg4fdKcZwHWDEyKm+exwB3JxdxfPLvMxvsIeM+b37m/hX99VYOWOFsvLbNo6wukaoviPwQBsuHIvii2FDqTKhOdWTWFS7djlBQFe2NAEJXU5PYcCiost9vpT9V+9zm//6mDmrLBSDxdqCTfdfQGzp5aNKfNHj7WxenM3FOUOrS+stGlrDxNLt+/fz53q/OyWrSh1RaaUWCLJMfBsfilcMzMM8a701gwT5XKlGHAZKEmmyJKlpN6n1hOyPBPeaw2w9kQVFJUPpNwyZKTmN7hpOhxMTZ/9NxsXX03OnSFD3YWwM5+QrKkXmt5xJfYfT88n2haqrDixl5AjbycynK2xWuhb20tmF9ngNWltTxF9EQe4e+jN0VeufAfFT8j3hI2ywLTTL8MFYiLdUYiSSGiYLrCylENdxumlndPyKyDiCCdCOlW9H2Xj4j357Q5bxv3A3tEIPyUYQXqCEHPGzEYI0NbHfLENM2t3gawu7IDEkq0XIjRjKG82G5A9jW5kfmcaKMtM/JaRHZAYso7XlYhSa8pPEQzPlbsrDxV+RObGNz8NvIpS1qgA0F8aKEONPgAuE2p9qJpyKPOCIko4+hn5etmW4R+SuukPK1D8G0qp0QNAofraUgpIAuFoiDuJMh8A3DYUu1BeN/hKEucW+/oBh0Doi7Kqcu3Ij8kt3bYCeArDsEYdgExtKZXMN6jkqi85UtMEywTLAJc9oJan/1cRpTf0t7Kqau3oHZS8+a1Po9SLKOU94wCkqErufjIAINJN3Pmc3DVuc37J2nxp7YLXUcwH9nGukqP/SCjyF/kyXxgAAP952X5MNQ944pxiXESIx3/Mhx2XyT1VBxmT4/JfaroCpX6OUh8/qyoA7xCL3yHfqN7OmH8wsXLnhWj2nxUA4CAi35V7q341EhZGlr3QcssZ/u4qU59b0M4/y7H2dfzsYyM+tTh8AL62p4hA+Mtjwr/IDpANROU5ub+uZTSbtkaQZL8dqMujZhD4BYrpwBTgwsQRj8wJd+AAwiHgj2jdhKPflNUNH3FOfTa3orkCLe+jVMUQcUAvhrqOJxrfSOv03vfdKFWNUhWJqE0+EqGdH06LcF58OLm86TfAjUMEQq0YaglrLmrmHKbCt2K+uGMVMnDKKgus6zHUJ8915guXgFu2r0R4OrvrMvZiqm/y5Cde5Tyh/I3gsrfvQstPB2ViFIJSr6HUkzx18cucZ2TlwXgJWq9B6xWJwIQg8C6G2o1SmzHN3/HLOe2cpzS0Cvz9eybhcBVKCWXF7fxgmvD/iP4Ps1PbE8ZtV64AAAAASUVORK5CYIIA" />
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
version="1.1"
width="0.213333in" height="0.213333in"
viewBox="0 0 64 64">
<defs>
</defs>
<image id="raster0"
x="0"
y="0"
width="64"
height="64"
opacity="1.000000"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAADJpJREFUeNrdm310lNWdxz/3eZnMJJMJeSeBIO9CbCtlQUWksuvaetSuLVrkWLqIbttj7fGsaGu71V2ptbYe+3KsdNetdd2tdUUqKos9aHcLHiwKCSu0yItSggSESSIJyWTen/vbP2aSzJCZyUwSAuzvnOfceZ7nzr33972/t/u791GcLbrngK0wvCIC4gT4yezY2RiGOqONP9jqRrgcLfMQuQiR2Wi5AJEatIAIkiyT9360HEFkPyJ70NKM1tt48hPh8wYA9b3jNYjcjMj1aPlLROw+ZlMYJQsAZKgbQ8sWRDYispan5vjPSQDUI/6rEbkTkc+ixRiCqUIASC01IhvRsoZn5r5+TgCgHm2/AZEHEZlDFmZ8CmrdCq8NHlthGWCboBBEAC3EHU0sqolENcGwQ0fAoS3sZAMCRHahZTW/mvfy2QHg6+9+TFWWrqHE86lMMzbZragpUfg8Bm5bDcyqkPI7CUDGdxCPaXoCMT46FeNgZ4y4k1FKtiJyJ7++5E9jAoB6tM2SA8f+Ecv4tppeb6UO2gvM8CmqSw1clsrKmCR/qyEASAXKiQudXVFa/BH8Ied0NYmj5RFEf5fnF8TPHAB37mmgJ/wCjr5MzagDrwe0UKyExjKTGp+BoZIDdoRgSNMbcgiENcGIpiOiCcZ12iwaCHW2wmOB16XwFhkUewxsU6UBQFJVEKGrK8r7x8MJINLVYjsiX2DtgtbRB+Cruy+nJ7weoZZxxajp9SBCY4liUoWJZUA8rjnZ7dAWcDjc64zICI53KSaUmZSXWrhdagAABDSIaNraI+w+FiLqpP3fj8gS1l2+bfQAuLX5M4RiL6GUB6VQsydQ6ivmk1UGPo8iGNIc64zzXsBJMFSAZc/HC0wuNmiotCnzWv0AJHWJSNhhf0svrYF46n+CiCzhNwtfGzkAtzZfQzD6CuDCMMBbxKS5k2msMYnHhcMdcf7cm9Naj5obrC9STB9fhK/E6gcAEcQRPjgaZE9bJPU/UURuYP0Vm4YPwK3NCwlEfgd4MBQYBjM+Xsv0WVUc74yzu8sZym+PdhwAIsz0mUypd2P3G9qEepzwh2huDaXWDSFyNS8t+kPhAKzcOYmecBMiNSgFhmJGhYuGRdPY2wn+iM57wKMNAFooNmBug5txPivNPrS3h9l+OJhatw2R+bzyqSOZ2DQyMv/ICYtA+AUcXdP3aKrPomq8lzc7wB8TzjYFHeHNliBHj4cSrjRJ1ZVu5tYXpVatAV7gb7ZY+QPwzp/vJ+Zc2t+o28DrMXkrYHAO8J5Gu05EOfhBbyK2SFJ9XTGzKu3UapeC+k5+KrB8+2wCkV2AC6VQhmLyOBctPXHU7IlQXppFLDU48cJEOV8VyNmGAhQzfSYzJpegJOEhdFyzc+8p/MG+oElH0VzMxsX7U9kdLBa9kcfQ4sJIYOMxFS2nYmAo8BRlmQfhyN+VM7HKNaaz//6hD1n0D7vxmxW81yW4jgaZPKE4IdqGonFKCf53u/vm2gXyGHB9dhW4edtCYvra03Wtn4rsjAN5cLo15sy3Hj3B8vvW48cDKjFZe9qjtHcMpA5KSmwuqkkdl1zHdZsXZgcgGv92To9pZjYZX5hfPKbMt3V0svJb69ihZoJdkqbJTa0hQqGB5cDEOg9WuqJ/KzMAy9+eTMy5LmuvRmaPeUedYnaDe8yY7+4OcM/qdfxPZDq4yweZMS1w4HCw3zPYtkljbVG6FFz7+8mDAeiJ3IoUHjKtvHTsZr83GObeh57n2RP1UFwNKrNEHg06dJwcUIWaqqJ0ToQVgwHQclPO3vVgdD7vgzlTx2b2Y7E4P3j8JX7RUg0l47My30f7joXRSfvldls0eM1UKbgxfV5v3jaR7nArhqIv6kv7nSzVwsY0tyTxGHS0QE97ynMGL2PT7vtcWOL5Py0o5zt3XIVtW1mZiTsOP17zEve9ocB3AWDmFS1eeoGH6ooiEKGtLciOlt7U/iey6apjiV5D0UV5TUM0BikDVaYFVVOhcsoQ4a9OZ1wLiOarDRbf/MokbNvM2e0zz73OfVscGDcNlJVRGjPR4RPhBABAqXeQB1sEPG8kxfuSvAEYZBuMxGVkKZUBykxchtn/7kt1Nj+8rYHiotzMr3vlDb788ikonwaGXZDa+MOaYDAxZo/HwlRphmzegA0QZuUXgI9Oev6vfYof3VafXNZmp9c2N7P03z+E8hlgDi/OONUd61f2iaVpdmDWAABaj8+nMekOjZj5S9zw9O3jqSrLzdCO/93HNY/vg8rhMw/QdmpAakvcadJWPwCAaUzIq7XO3hExP8UUfn17LRNrcnuOP+09xKKH3oaqWWCNzMu0Bpz+hZKdHhFVDwDg6Mq8Wotr6AkOayAGwovLK5k2wZPbcB05zrKHthCtagTbM3J9E4hEnGQslwZAbaoNyL+9k93DGsXWZeXMmVGas9ax4+3c8sBv2ettBJd31DauolGdNbTrU4FT+StVDzi6oAFsuLaUBRf5cmtXVzdf+96rvOWaDW5f/wJnNMhJBkRa0ma6LVUFjuTdmhboyB+v5670cP1l5Tnr9ASCrHr4RTZEpoGn/IxtWsfj2QAwVHtBAn28MxHcDKF7T8xzsXRxbvMSjkS5/7H1PNMxEUqqzuiOfW/YSdWA46k2YH9hUGrE35mzysONFl+5pirbIjIR38fjPPYvG3n8cBWUTRgyvh+pMTwaSAWAAwMAKJoKbtB/CsLRjK++McXkns9VY5nZuXe05omnN/HALg+Mm5SIFEXS8v2JLSCdvRSdlwV3uQxC4Tjx9BC6aSAlZhpbieuCEZXWDtTU9BjqthrFA0sqE5ujuQxT3GHZDQtZ9nlXch5kUPtZbtJWiDc9sIOmiC+n6tgug67OyOmPtw4A8F+LW7j294eAqQWBEIoi/k5UbcLILS1X/GhZBV6Pmces2NTVlo9Iqnfs3EfTyQCUlGWtU2orXLaJ/2SatO5n01Wt6fkAxdphjeKjAHT1coUbfrq0PLl/Nzb0y7U7wF2R02VO8FlEIg4fdKcZwHWDEyKm+exwB3JxdxfPLvMxvsIeM+b37m/hX99VYOWOFsvLbNo6wukaoviPwQBsuHIvii2FDqTKhOdWTWFS7djlBQFe2NAEJXU5PYcCiost9vpT9V+9zm//6mDmrLBSDxdqCTfdfQGzp5aNKfNHj7WxenM3FOUOrS+stGlrDxNLt+/fz53q/OyWrSh1RaaUWCLJMfBsfilcMzMM8a701gwT5XKlGHAZKEmmyJKlpN6n1hOyPBPeaw2w9kQVFJUPpNwyZKTmN7hpOhxMTZ/9NxsXX03OnSFD3YWwM5+QrKkXmt5xJfYfT88n2haqrDixl5AjbycynK2xWuhb20tmF9ngNWltTxF9EQe4e+jN0VeufAfFT8j3hI2ywLTTL8MFYiLdUYiSSGiYLrCylENdxumlndPyKyDiCCdCOlW9H2Xj4j357Q5bxv3A3tEIPyUYQXqCEHPGzEYI0NbHfLENM2t3gawu7IDEkq0XIjRjKG82G5A9jW5kfmcaKMtM/JaRHZAYso7XlYhSa8pPEQzPlbsrDxV+RObGNz8NvIpS1qgA0F8aKEONPgAuE2p9qJpyKPOCIko4+hn5etmW4R+SuukPK1D8G0qp0QNAofraUgpIAuFoiDuJMh8A3DYUu1BeN/hKEucW+/oBh0Doi7Kqcu3Ij8kt3bYCeArDsEYdgExtKZXMN6jkqi85UtMEywTLAJc9oJan/1cRpTf0t7Kqau3oHZS8+a1Po9SLKOU94wCkqErufjIAINJN3Pmc3DVuc37J2nxp7YLXUcwH9nGukqP/SCjyF/kyXxgAAP952X5MNQ944pxiXESIx3/Mhx2XyT1VBxmT4/JfaroCpX6OUh8/qyoA7xCL3yHfqN7OmH8wsXLnhWj2nxUA4CAi35V7q341EhZGlr3QcssZ/u4qU59b0M4/y7H2dfzsYyM+tTh8AL62p4hA+Mtjwr/IDpANROU5ub+uZTSbtkaQZL8dqMujZhD4BYrpwBTgwsQRj8wJd+AAwiHgj2jdhKPflNUNH3FOfTa3orkCLe+jVMUQcUAvhrqOJxrfSOv03vfdKFWNUhWJqE0+EqGdH06LcF58OLm86TfAjUMEQq0YaglrLmrmHKbCt2K+uGMVMnDKKgus6zHUJ8915guXgFu2r0R4OrvrMvZiqm/y5Cde5Tyh/I3gsrfvQstPB2ViFIJSr6HUkzx18cucZ2TlwXgJWq9B6xWJwIQg8C6G2o1SmzHN3/HLOe2cpzS0Cvz9eybhcBVKCWXF7fxgmvD/iP4Ps1PbE8ZtV64AAAAASUVORK5CYIIA" />
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -6,4 +6,29 @@ if get_option('gtk-frontend')
subdir('icons') subdir('icons')
subdir('misc') subdir('misc')
subdir('man') subdir('man')
# Fetch and build the offline manual so Help->Contents works without
# connectivity. 'auto' skips with a warning when the docs cannot be
# fetched (e.g. network-less distro builds, which ship docs themselves);
# 'enabled' makes that a hard error.
offline_docs_opt = get_option('offline-docs')
if not offline_docs_opt.disabled()
offline_docs_args = [
'--repo', get_option('offline-docs-repo'),
'--output', join_paths(meson.current_build_dir(), 'offline-docs'),
'--stamp', '@OUTPUT@',
]
if offline_docs_opt.enabled()
offline_docs_args += ['--strict']
endif
custom_target('offline-docs',
output: 'offline-docs.stamp',
command: [find_program('python3'), files('misc/build_offline_docs.py')] + offline_docs_args,
build_by_default: true,
)
meson.add_install_script(files('misc/install_offline_docs.py'),
join_paths(meson.current_build_dir(), 'offline-docs'),
offline_docs_dir,
)
endif
endif endif

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Build ZoiteChat's offline HTML documentation for bundling with releases.
Clones the ZoiteChat/documentation repository, preferring a tag that
matches this source tree's version so the bundled manual matches the
application, and builds it with Sphinx into --output. Used by the
meson build (offline-docs option) and by the Windows CI workflow.
Sphinx is installed into a throwaway virtualenv so the invoking Python
(which the Windows build also bundles pieces of) is left untouched.
Unless --strict is given, any failure prints a warning (GitHub Actions
annotation syntax) and exits 0: builds then simply fall back to opening
the online manual instead of failing outright.
"""
import argparse
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
DEFAULT_REPO = "https://github.com/ZoiteChat/documentation.git"
def project_version() -> str:
meson_build = pathlib.Path(__file__).resolve().parents[2] / "meson.build"
match = re.search(r"version\s*:\s*'([^']+)'", meson_build.read_text(encoding="utf-8"))
if not match:
raise RuntimeError(f"could not parse project version from {meson_build}")
return match.group(1)
def run(argv, **kwargs) -> None:
print("+", " ".join(str(arg) for arg in argv), flush=True)
subprocess.run(argv, check=True, **kwargs)
def clone_docs(repo: str, version: str, target: pathlib.Path) -> str:
# A missing tag falls through to the default branch so the docs may be
# newer than the app, which beats shipping none at all.
for ref in (f"v{version}", version, None):
cmd = ["git", "clone", "--depth", "1"]
if ref:
cmd += ["--branch", ref]
cmd += [repo, str(target)]
try:
run(cmd)
return ref or "default branch"
except subprocess.CalledProcessError:
shutil.rmtree(target, ignore_errors=True)
raise RuntimeError(f"could not clone {repo}")
def find_source_dir(checkout: pathlib.Path) -> pathlib.Path:
for candidate in (checkout, checkout / "docs", checkout / "doc"):
if (candidate / "conf.py").is_file():
return candidate
raise RuntimeError(f"no Sphinx conf.py found in {checkout}")
def venv_python(venv_dir: pathlib.Path) -> pathlib.Path:
if sys.platform == "win32":
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def build(output: pathlib.Path, repo: str, version: str) -> None:
with tempfile.TemporaryDirectory(prefix="zoitechat-docs-") as tmp:
tmp_dir = pathlib.Path(tmp)
checkout = tmp_dir / "documentation"
html_dir = tmp_dir / "html"
venv_dir = tmp_dir / "venv"
ref = clone_docs(repo, version, checkout)
print(f"building documentation from {ref}", flush=True)
source_dir = find_source_dir(checkout)
run([sys.executable, "-m", "venv", str(venv_dir)])
python = venv_python(venv_dir)
requirements = source_dir / "requirements.txt"
if not requirements.is_file():
requirements = checkout / "requirements.txt"
if requirements.is_file():
run([python, "-m", "pip", "install", "--quiet", "-r", str(requirements)])
run([python, "-m", "pip", "install", "--quiet", "sphinx"])
else:
run([python, "-m", "pip", "install", "--quiet", "sphinx", "sphinx_rtd_theme"])
run([python, "-m", "sphinx", "-b", "html", str(source_dir), str(html_dir)])
if not (html_dir / "index.html").is_file():
raise RuntimeError("Sphinx build produced no index.html")
if output.exists():
shutil.rmtree(output)
shutil.copytree(html_dir, output,
ignore=shutil.ignore_patterns(".doctrees", ".buildinfo"))
file_count = sum(1 for path in output.rglob("*") if path.is_file())
print(f"offline documentation staged at {output} ({file_count} files)", flush=True)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", required=True, type=pathlib.Path,
help="directory to stage the built HTML tree into")
parser.add_argument("--repo", default=DEFAULT_REPO,
help="documentation git repository to build from")
parser.add_argument("--version", default=None,
help="app version used to pick a matching docs tag "
"(default: parsed from meson.build)")
parser.add_argument("--strict", action="store_true",
help="fail on error instead of emitting a warning")
parser.add_argument("--stamp", type=pathlib.Path, default=None,
help="file to touch on completion (meson target output)")
args = parser.parse_args()
try:
build(args.output, args.repo, args.version or project_version())
except Exception as error: # noqa: BLE001 - degrade to website-only help
if args.strict:
raise
print(f"::warning::offline documentation not bundled: {error}", flush=True)
if args.stamp:
args.stamp.write_text("ok\n", encoding="utf-8")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""Meson install helper: copy the built offline docs into the install tree.
Silently skips when the docs were not built (offline-docs=auto without
network); Help->Contents then falls back to the online manual.
"""
import os
import pathlib
import shutil
import sys
src = pathlib.Path(sys.argv[1])
dest_arg = pathlib.PurePath(sys.argv[2])
if not (src / "index.html").is_file():
print("offline documentation was not built; skipping install")
sys.exit(0)
if dest_arg.is_absolute():
# An absolute offline-docs-dir lands outside the prefix; honour DESTDIR
destdir = os.environ.get("DESTDIR", "")
dest = pathlib.Path(destdir + str(dest_arg)) if destdir else pathlib.Path(dest_arg)
else:
dest = pathlib.Path(os.environ["MESON_INSTALL_DESTDIR_PREFIX"]) / dest_arg
if dest.exists():
shutil.rmtree(dest)
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(src, dest)
print(f"Installed offline documentation to {dest}")

View File

@@ -13,16 +13,16 @@ if get_option('gtk-frontend')
install_dir: metainfodir install_dir: metainfodir
) )
appstream_util = find_program('appstream-util', required: false) appstreamcli = find_program('appstreamcli', required: false)
if appstream_util.found() if appstreamcli.found()
test('Validate net.zoite.Zoitechat.appdata.xml', appstream_util, test('Validate net.zoite.Zoitechat.appdata.xml', appstreamcli,
args: ['validate-relax', zoitechat_appdata] args: ['validate', zoitechat_appdata]
) )
endif endif
endif endif
desktop_conf = configuration_data() desktop_conf = configuration_data()
if dbus_glib_dep.found() if dbus_dep.found()
desktop_conf.set('exec_command', 'zoitechat --existing %U') desktop_conf.set('exec_command', 'zoitechat --existing %U')
else else
desktop_conf.set('exec_command', 'zoitechat %U') desktop_conf.set('exec_command', 'zoitechat %U')
@@ -97,11 +97,18 @@ if get_option('plugin')
conf.set('SUMMARY', metainfo[1]) conf.set('SUMMARY', metainfo[1])
conf.set('LICENSE', metainfo[2]) conf.set('LICENSE', metainfo[2])
configure_file( plugin_appdata = configure_file(
input: 'net.zoite.Zoitechat.Plugin.metainfo.xml.in', input: 'net.zoite.Zoitechat.Plugin.metainfo.xml.in',
output: 'net.zoite.Zoitechat.Plugin.@0@.metainfo.xml'.format(name), output: 'net.zoite.Zoitechat.Plugin.@0@.metainfo.xml'.format(name),
configuration: conf, configuration: conf,
install_dir: get_option('install-plugin-metainfo') ? metainfodir : '', install_dir: get_option('install-plugin-metainfo') ? metainfodir : '',
) )
if appstreamcli.found()
test('Validate net.zoite.Zoitechat.Plugin.@0@.metainfo.xml'.format(name), appstreamcli,
args: ['validate', plugin_appdata]
)
endif
endforeach endforeach
endif endif

View File

@@ -4,7 +4,7 @@
<extends>net.zoite.Zoitechat</extends> <extends>net.zoite.Zoitechat</extends>
<name>@NAME@ Plugin</name> <name>@NAME@ Plugin</name>
<summary>@SUMMARY@</summary> <summary>@SUMMARY@</summary>
<url type="homepage">https://zoitechat.zoite.net/</url> <url type="homepage">https://zoitechat.org/</url>
<project_license>@LICENSE@</project_license> <project_license>@LICENSE@</project_license>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<update_contact>deepend_AT_zoite.net</update_contact> <update_contact>deepend_AT_zoite.net</update_contact>

View File

@@ -16,12 +16,12 @@
<p>ZoiteChat is an easy to use yet extensible IRC Client based on Hexchat. It allows you to securely join multiple networks and talk to users privately or in channels using a customizable interface. You can even transfer files.</p> <p>ZoiteChat is an easy to use yet extensible IRC Client based on Hexchat. It allows you to securely join multiple networks and talk to users privately or in channels using a customizable interface. You can even transfer files.</p>
<p>ZoiteChat supports features such as: DCC, SASL, proxies, spellcheck, alerts, logging, custom themes, and Python/Perl scripts.</p> <p>ZoiteChat supports features such as: DCC, SASL, proxies, spellcheck, alerts, logging, custom themes, and Python/Perl scripts.</p>
</description> </description>
<url type="homepage">https://zoitechat.zoite.net</url> <url type="homepage">https://zoitechat.org</url>
<url type="bugtracker">https://github.com/zoitechat/zoitechat</url> <url type="bugtracker">https://github.com/zoitechat/zoitechat</url>
<url type="help">https://docs.zoitechat.zoite.net/en/latest/</url> <url type="help">https://docs.zoitechat.org/en/latest/</url>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://zoitechat.zoite.net/assets/ZoiteChat.png</image> <image>https://zoitechat.org/assets/img/zoitechat-screenshot.png</image>
<caption>Main Chat Window</caption> <caption>Main Chat Window</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>
@@ -29,6 +29,94 @@
<id>zoitechat.desktop</id> <id>zoitechat.desktop</id>
</provides> </provides>
<releases> <releases>
<release date="2026-06-29" version="2.18.3">
<description>
<ul>
<li>Added IRCv3 support for message tags, echo-message, typing notifications, and replies.</li>
<li>Restored the accept-invalid-cert TLS bypass behavior.</li>
<li>Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.</li>
<li>Snapshot Python hooks during plugin unload for safer plugin shutdown.</li>
<li>Fixed Windows GtkStatusIcon tray menu popup behavior.</li>
<li>Fixed self-echoed private messages routing to the target tab.</li>
<li>Fixed the lag ping timeout window.</li>
</ul>
</description>
</release>
<release date="2026-06-20" version="2.18.2">
<description>
<p>Security and connections:</p>
<ul>
<li>Improved TLS, certificate and hostname validation, FiSHLiM OpenSSL handling, keyring support, and DCC host lookups.</li>
</ul>
<p>FiSHLiM:</p>
<ul>
<li>Added a GTK key manager with context-menu access and clearer channel/private-message key handling.</li>
</ul>
<p>Interface and preferences:</p>
<ul>
<li>Improved keybind customization, reset support, channel-name switching, <code>/server -noproxy</code>, and stale-link ping checks.</li>
<li>Refined GTK behavior with native file choosers, topic layout preferences, timestamp/date tooltips, text scroll controls, and aligned network meters.</li>
<li>Added privacy and display options for hiding join/part hostmasks and controlling topic/multiline layout.</li>
</ul>
<p>Fixes:</p>
<ul>
<li>Fixed tray restore, DBus unload handling, GTK3 theme refresh/saving, topic relayout, and tab switcher compatibility.</li>
<li>Updated project documentation.</li>
</ul>
</description>
</release>
<release date="2026-05-21" version="2.18.1">
<description>
<ul>
<li>Migrated D-Bus handling to GDBus.</li>
<li>Enabled mouse wheel channel switching by default and consumed handled tab wheel events.</li>
<li>Switched main panes to native GTK scrollbars.</li>
<li>Tightened userlist button and meter layout.</li>
<li>Made the native Windows file chooser modal.</li>
<li>Set the Windows AppUserModelID before GTK startup.</li>
<li>Updated Windows CI to use the new GTK3 bundle zip.</li>
<li>Updated Windows installer architecture checks to use x64-compatible detection.</li>
<li>Bumped the Flatpak Perl runtime to 5.42.2.</li>
<li>Fixed palette color reads to preserve base GTK state.</li>
<li>Fixed auto-replace whole-word matching.</li>
<li>Fixed checksum file stream cleanup.</li>
<li>Fixed byte handling in the Python console.</li>
<li>Guarded GTK drag-and-drop handlers against null windows.</li>
<li>Fixed size_t and integer handling issues.</li>
<li>Removed the Winamp plugin.</li>
</ul>
</description>
</release>
<release date="2026-04-20" version="2.18.0">
<description>
<p>Tabs and navigation:</p>
<ul>
<li>Added optional close buttons on tabs.</li>
<li>Added Ctrl+W to close tabs and Ctrl+Shift+T to reopen recently closed tabs.</li>
<li>Improved channel tree behavior by ignoring collapsed parent selections.</li>
</ul>
<p>Themes and appearance:</p>
<ul>
<li>Expanded theme import support with .hct support in colors.conf import.</li>
<li>Added pevent import support from .hct theme files and improved import result messaging.</li>
<li>Expanded palette and selection CSS styling for better theme consistency.</li>
<li>Auto-sizes the topic bar to wrapped text for better multi-line topic handling.</li>
</ul>
<p>Links and text handling:</p>
<ul>
<li>Refined command-character parsing when pasting path-like text.</li>
<li>Tightened topic link detection and opening rules.</li>
<li>Added Public Suffix List validation for host links to reduce bad link matches.</li>
</ul>
<p>Packaging and platform integration:</p>
<ul>
<li>Sanitized the Linux open environment for AppImage builds and added safer fallback handling.</li>
<li>Labeled Windows installer runtimes with exact versions.</li>
<li>Fixed the notification icon and corrected the Flatpak screenshot asset.</li>
<li>Improved AppStream metainfo validation.</li>
</ul>
</description>
</release>
<release date="2026-03-30" version="2.18.0~pre6"> <release date="2026-03-30" version="2.18.0~pre6">
<description> <description>
<p>GTK theme and UI:</p> <p>GTK theme and UI:</p>
@@ -100,14 +188,6 @@
</ul> </ul>
</description> </description>
</release> </release>
<release date="2026-03-22" version="2.18.0~pre5">
<description>
<p>Version metadata update:</p>
<ul>
<li>Bumped release version references to <code>2.18.0~pre5</code> across build and packaging files.</li>
</ul>
</description>
</release>
<release date="2026-03-14" version="2.18.0~pre4"> <release date="2026-03-14" version="2.18.0~pre4">
<description> <description>
<p>UI fixes, topic bar improvements, and selection styling updates:</p> <p>UI fixes, topic bar improvements, and selection styling updates:</p>

View File

@@ -7,14 +7,13 @@
"command": "zoitechat", "command": "zoitechat",
"finish-args": [ "finish-args": [
"--share=ipc", "--share=ipc",
"--socket=x11", "--socket=wayland",
"--socket=fallback-x11",
"--share=network", "--share=network",
"--socket=pulseaudio", "--socket=pulseaudio",
"--filesystem=xdg-download", "--filesystem=xdg-download",
"--filesystem=xdg-data/themes:ro", "--filesystem=xdg-data/themes:ro",
"--filesystem=xdg-data/icons:ro", "--filesystem=xdg-data/icons:ro",
"--filesystem=~/.themes:ro",
"--filesystem=~/.icons:ro",
"--filesystem=xdg-run/tray-icon:create", "--filesystem=xdg-run/tray-icon:create",
"--env=GTK_CSD=1", "--env=GTK_CSD=1",
"--talk-name=org.freedesktop.Notifications", "--talk-name=org.freedesktop.Notifications",
@@ -34,7 +33,6 @@
} }
}, },
"modules": [ "modules": [
"shared-modules/dbus-glib/dbus-glib.json",
"shared-modules/lua5.4/lua-5.4.json", "shared-modules/lua5.4/lua-5.4.json",
"shared-modules/libcanberra/libcanberra.json", "shared-modules/libcanberra/libcanberra.json",
"shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json", "shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json",
@@ -45,9 +43,9 @@
"buildsystem": "meson", "buildsystem": "meson",
"sources": [ "sources": [
{ {
"type": "git", "type": "archive",
"url": "https://github.com/pavouk/lgi.git", "url": "https://github.com/pavouk/lgi/archive/c9b8e4473c6421f2a215d8c06c0d94b86eb0b26a.tar.gz",
"commit": "c9b8e4473c6421f2a215d8c06c0d94b86eb0b26a" "sha256": "db67b2b7ee89fa566f783486d56be7203552a997bc55f35020b57dd2776b9943"
} }
] ]
}, },

View File

@@ -13,8 +13,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://www.cpan.org/src/5.0/perl-5.40.1.tar.xz", "url": "https://www.cpan.org/src/5.0/perl-5.42.2.tar.xz",
"sha256": "dfa20c2eef2b4af133525610bbb65dd13777ecf998c9c5b1ccf0d308e732ee3f" "sha256": "0a585eeb9e363c0f80482ddb3571625250c2c86aeb408853e8ea50805cfb14bb"
} }
] ]
} }

View File

@@ -1,5 +1,5 @@
project('zoitechat', 'c', project('zoitechat', 'c',
version: '2.18.0~pre6', version: '2.18.3',
meson_version: '>= 0.55.0', meson_version: '>= 0.55.0',
default_options: [ default_options: [
'c_std=c17', 'c_std=c17',
@@ -18,7 +18,8 @@ libgmodule_dep = dependency('gmodule-2.0')
libcanberra_dep = dependency('libcanberra', version: '>= 0.22', libcanberra_dep = dependency('libcanberra', version: '>= 0.22',
required: get_option('libcanberra')) required: get_option('libcanberra'))
dbus_glib_dep = dependency('dbus-glib-1', required: get_option('dbus')) dbus_dep = dependency('gio-2.0', required: get_option('dbus'))
libsecret_dep = dependency('libsecret-1', required: false)
global_deps = [] global_deps = []
if cc.get_id() == 'msvc' if cc.get_id() == 'msvc'
@@ -34,12 +35,23 @@ config_h.set_quoted('PACKAGE_NAME', meson.project_name())
config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat') config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat')
config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'),
get_option('datadir'), 'locale')) get_option('datadir'), 'locale'))
# Where Help->Contents looks for locally installed documentation before
# falling back to the online manual. Packagers that ship the HTML docs
# elsewhere can point this at their doc directory.
offline_docs_dir = get_option('offline-docs-dir')
if offline_docs_dir == ''
offline_docs_dir = join_paths(get_option('datadir'), 'doc', 'zoitechat', 'html')
endif
config_h.set_quoted('ZOITECHATDOCDIR', join_paths(get_option('prefix'), offline_docs_dir))
config_h.set10('ENABLE_NLS', true) config_h.set10('ENABLE_NLS', true)
# Optional features # Optional features
config_h.set('USE_OPENSSL', libssl_dep.found()) config_h.set('USE_OPENSSL', libssl_dep.found())
config_h.set('USE_LIBCANBERRA', libcanberra_dep.found()) config_h.set('USE_LIBCANBERRA', libcanberra_dep.found())
config_h.set('USE_DBUS', dbus_glib_dep.found()) config_h.set('USE_DBUS', dbus_dep.found())
config_h.set('HAVE_LIBSECRET', libsecret_dep.found())
config_h.set('USE_PLUGIN', get_option('plugin')) config_h.set('USE_PLUGIN', get_option('plugin'))
config_h.set('USE_GTK_FRONTEND', get_option('gtk-frontend')) config_h.set('USE_GTK_FRONTEND', get_option('gtk-frontend'))
@@ -181,7 +193,7 @@ if meson.version().version_compare('>= 0.55.0')
'GTK Frontend': get_option('gtk-frontend') ? 'enabled (GTK+ 3.22+)' : 'disabled', 'GTK Frontend': get_option('gtk-frontend') ? 'enabled (GTK+ 3.22+)' : 'disabled',
'TLS (openssl)': libssl_dep.found(), 'TLS (openssl)': libssl_dep.found(),
'Plugin Support': get_option('plugin'), 'Plugin Support': get_option('plugin'),
'DBus Support': dbus_glib_dep.found(), 'DBus Support': dbus_dep.found(),
'libcanberra': libcanberra_dep.found(), 'libcanberra': libcanberra_dep.found(),
}, section: 'Features') }, section: 'Features')

View File

@@ -33,6 +33,15 @@ option('install-appdata', type: 'boolean',
option('install-plugin-metainfo', type: 'boolean', value: false, option('install-plugin-metainfo', type: 'boolean', value: false,
description: 'Installs metainfo files for enabled plugins, useful when distros create split packages' description: 'Installs metainfo files for enabled plugins, useful when distros create split packages'
) )
option('offline-docs', type: 'feature', value: 'auto',
description: 'Fetch, build and install the offline HTML documentation (needs network at build time); auto skips with a warning when unavailable'
)
option('offline-docs-dir', type: 'string', value: '',
description: 'Directory searched for offline HTML documentation (index.html), absolute or relative to prefix. Defaults to $datadir/doc/zoitechat/html'
)
option('offline-docs-repo', type: 'string', value: 'https://github.com/ZoiteChat/documentation.git',
description: 'Git repository the offline documentation is built from'
)
# Plugins # Plugins
option('with-checksum', type: 'boolean', option('with-checksum', type: 'boolean',
@@ -44,7 +53,7 @@ option('with-exec', type: 'boolean',
option('with-fishlim', type: 'boolean', option('with-fishlim', type: 'boolean',
description: 'Fish encryption plugin, requires openssl' description: 'Fish encryption plugin, requires openssl'
) )
option('with-lua', type: 'string', value: 'luajit', option('with-lua', type: 'string', value: 'lua-5.4',
description: 'Lua scripting plugin, value is pkg-config name to use or "false"' description: 'Lua scripting plugin, value is pkg-config name to use or "false"'
) )
option('with-perl', type: 'string', value: 'perl', option('with-perl', type: 'string', value: 'perl',
@@ -59,9 +68,6 @@ option('with-sysinfo', type: 'boolean',
option('with-upd', type: 'boolean', option('with-upd', type: 'boolean',
description: 'Update plugin, Windows only' description: 'Update plugin, Windows only'
) )
option('with-winamp', type: 'boolean',
description: 'Winamp plugin, Windows only'
)
option('with-perl-legacy-api', type: 'boolean', value: false, option('with-perl-legacy-api', type: 'boolean', value: false,
description: 'Enables the legacy IRC perl module for compatibility with old scripts' description: 'Enables the legacy IRC perl module for compatibility with old scripts'
) )

View File

@@ -0,0 +1,102 @@
Name: zoitechat
Version: 2.18.3
Release: %autorelease
Summary: HexChat-based IRC client
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
URL: https://github.com/ZoiteChat/zoitechat
Source0: %{url}/archive/refs/tags/%{name}-%{version}.tar.gz
BuildRequires: desktop-file-utils
BuildRequires: gcc
BuildRequires: gettext
BuildRequires: libappstream-glib
BuildRequires: meson >= 0.55.0
BuildRequires: perl
BuildRequires: perl-devel
BuildRequires: python3
BuildRequires: python3-cffi
BuildRequires: publicsuffix-list
BuildRequires: xwayland-run
BuildRequires: weston
BuildRequires: pkgconfig(ayatana-appindicator3-0.1)
BuildRequires: pkgconfig(dbus-glib-1)
BuildRequires: pkgconfig(gio-2.0) >= 2.36.0
BuildRequires: pkgconfig(gmodule-2.0)
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
BuildRequires: pkgconfig(iso-codes)
BuildRequires: pkgconfig(libarchive)
BuildRequires: pkgconfig(libcanberra) >= 0.22
BuildRequires: pkgconfig(libpci)
BuildRequires: pkgconfig(lua)
BuildRequires: pkgconfig(openssl) >= 0.9.8
BuildRequires: pkgconfig(python3)
Requires: hicolor-icon-theme
Requires: iso-codes
%package devel
Summary: Development files for ZoiteChat plugins
Requires: %{name}%{?_isa} = %{version}-%{release}
%description devel
Development files for building ZoiteChat plugins.
%description
ZoiteChat is a HexChat-based IRC client for Windows and UNIX-like operating
systems.
%prep
%autosetup -C
%build
%meson \
-Dtext-frontend=false \
-Dwith-checksum=true \
-Dwith-fishlim=true \
-Dwith-lua=lua \
-Dwith-perl=perl \
-Dwith-python=python3 \
-Dwith-sysinfo=true \
-Dinstall-appdata=true \
-Dinstall-plugin-metainfo=true
%meson_build
%install
%meson_install
%find_lang %{name}
%check
desktop-file-validate %{buildroot}%{_datadir}/applications/net.zoite.Zoitechat.desktop
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs \
"Theme Manager Dispatch Routing Tests" \
"Validate net.zoite.Zoitechat.desktop" \
"Validate translations" \
"Fishlim Tests"
%files -f %{name}.lang
%license COPYING
%doc readme.md troubleshooting.md
%{_bindir}/zoitechat
%{_datadir}/applications/net.zoite.Zoitechat.desktop
%{_datadir}/dbus-1/services/org.zoitechat.service.service
%{_datadir}/icons/hicolor/48x48/apps/net.zoite.Zoitechat.png
%{_datadir}/icons/hicolor/scalable/apps/net.zoite.Zoitechat.svg
%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
%dir %{_libdir}/zoitechat
%dir %{_libdir}/zoitechat/plugins
%dir %{_libdir}/zoitechat/python
%{_libdir}/zoitechat/plugins/*.so
%{_libdir}/zoitechat/python/*.py
%{_mandir}/man1/zoitechat.1*
%files devel
%{_includedir}/zoitechat-plugin.h
%{_libdir}/pkgconfig/zoitechat-plugin.pc
%changelog
%autochangelog

View File

@@ -8,7 +8,6 @@ arch=('x86_64')
url='https://github.com/zoitechat/zoitechat' url='https://github.com/zoitechat/zoitechat'
license=('GPL-2.0-or-later') license=('GPL-2.0-or-later')
depends=( depends=(
'dbus-glib'
'glib2' 'glib2'
'gtk3' 'gtk3'
'iso-codes' 'iso-codes'

View File

@@ -104,12 +104,14 @@ thread_sha256_file (GTask *task, GFile *file, gpointer task_data, GCancellable *
g_checksum_update (checksum, buffer, ret); g_checksum_update (checksum, buffer, ret);
if (error) { if (error) {
g_checksum_free (checksum);
g_task_return_error (task, error); g_task_return_error (task, error);
return; goto cleanup;
} }
g_task_return_pointer (task, g_strdup (g_checksum_get_string (checksum)), g_free); g_task_return_pointer (task, g_strdup (g_checksum_get_string (checksum)), g_free);
cleanup:
g_input_stream_close(G_INPUT_STREAM(istream), NULL, NULL);
g_object_unref(istream);
g_checksum_free (checksum); g_checksum_free (checksum);
} }

View File

@@ -91,27 +91,13 @@ static const signed char fish_unbase64[256] = {
#include <openssl/provider.h> #include <openssl/provider.h>
static OSSL_PROVIDER *legacy_provider; static OSSL_PROVIDER *legacy_provider;
static OSSL_PROVIDER *default_provider; static OSSL_PROVIDER *default_provider;
static OSSL_LIB_CTX *ossl_ctx;
#endif #endif
int fish_init(void) int fish_init(void)
{ {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L #if OPENSSL_VERSION_NUMBER >= 0x30000000L
ossl_ctx = OSSL_LIB_CTX_new(); legacy_provider = OSSL_PROVIDER_load(NULL, "legacy");
if (!ossl_ctx) default_provider = OSSL_PROVIDER_load(NULL, "default");
return 0;
legacy_provider = OSSL_PROVIDER_load(ossl_ctx, "legacy");
if (!legacy_provider) {
fish_deinit();
return 0;
}
default_provider = OSSL_PROVIDER_load(ossl_ctx, "default");
if (!default_provider) {
fish_deinit();
return 0;
}
#endif #endif
return 1; return 1;
} }
@@ -129,10 +115,6 @@ void fish_deinit(void)
default_provider = NULL; default_provider = NULL;
} }
if (ossl_ctx) {
OSSL_LIB_CTX_free(ossl_ctx);
ossl_ctx = NULL;
}
#endif #endif
} }
@@ -278,7 +260,9 @@ char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key,
} }
#if OPENSSL_VERSION_NUMBER >= 0x30000000L #if OPENSSL_VERSION_NUMBER >= 0x30000000L
cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-CBC", NULL); cipher = EVP_CIPHER_fetch(NULL, "BF-CBC", NULL);
if (!cipher)
cipher = (EVP_CIPHER *) EVP_bf_cbc();
#else #else
cipher = (EVP_CIPHER *) EVP_bf_cbc(); cipher = (EVP_CIPHER *) EVP_bf_cbc();
#endif #endif
@@ -286,7 +270,9 @@ char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key,
} else if (mode == EVP_CIPH_ECB_MODE) { } else if (mode == EVP_CIPH_ECB_MODE) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L #if OPENSSL_VERSION_NUMBER >= 0x30000000L
cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-ECB", NULL); cipher = EVP_CIPHER_fetch(NULL, "BF-ECB", NULL);
if (!cipher)
cipher = (EVP_CIPHER *) EVP_bf_ecb();
#else #else
cipher = (EVP_CIPHER *) EVP_bf_ecb(); cipher = (EVP_CIPHER *) EVP_bf_ecb();
#endif #endif

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
/** /**
@@ -421,7 +429,7 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
zoitechat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s%s", sender, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : ""); zoitechat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s%s", sender, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : "");
g_free(pub_key); g_free(pub_key);
} else { } else {
zoitechat_print(ph, "Failed to generate keys"); zoitechat_printf(ph, "Failed to generate keys");
goto cleanup; goto cleanup;
} }
} else if (!strcmp (dh_message, "DH1080_FINISH")) { } else if (!strcmp (dh_message, "DH1080_FINISH")) {
@@ -446,7 +454,7 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
zoitechat_printf(ph, "Stored new key for %s (%s)", sender, fish_modes[mode]); zoitechat_printf(ph, "Stored new key for %s (%s)", sender, fish_modes[mode]);
g_free(secret_key); g_free(secret_key);
} else { } else {
zoitechat_print(ph, "Failed to create secret key!"); zoitechat_printf(ph, "Failed to create secret key!");
} }
cleanup: cleanup:
@@ -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 {
@@ -548,19 +876,21 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
} }
if ((query_ctx && ctx_type != 3) || (!query_ctx && !irc_is_query(target))) { if ((query_ctx && ctx_type != 3) || (!query_ctx && !irc_is_query(target))) {
zoitechat_print(ph, "You can only exchange keys with individuals"); zoitechat_printf(ph, "You can only exchange keys with individuals");
return ZOITECHAT_EAT_ALL; return ZOITECHAT_EAT_ALL;
} }
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 {
zoitechat_print(ph, "Failed to generate keys"); zoitechat_printf(ph, "Failed to generate keys");
} }
return ZOITECHAT_EAT_ALL; return ZOITECHAT_EAT_ALL;
@@ -577,7 +907,7 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) {
GSList *encrypted_list; GSList *encrypted_list;
if (!*topic) { if (!*topic) {
zoitechat_print(ph, usage_topic); zoitechat_printf(ph, "%s", usage_topic);
return ZOITECHAT_EAT_ALL; return ZOITECHAT_EAT_ALL;
} }
@@ -624,7 +954,7 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) {
GSList *encrypted_list, *encrypted_item; GSList *encrypted_list, *encrypted_item;
if (!*target || !*notice) { if (!*target || !*notice) {
zoitechat_print(ph, usage_notice); zoitechat_printf(ph, "%s", usage_notice);
return ZOITECHAT_EAT_ALL; return ZOITECHAT_EAT_ALL;
} }
@@ -676,7 +1006,7 @@ static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) {
GSList *encrypted_list, *encrypted_item; GSList *encrypted_list, *encrypted_item;
if (!*target || !*message) { if (!*target || !*message) {
zoitechat_print(ph, usage_msg); zoitechat_printf(ph, "%s", usage_msg);
return ZOITECHAT_EAT_ALL; return ZOITECHAT_EAT_ALL;
} }
@@ -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);
@@ -805,20 +1136,31 @@ int zoitechat_plugin_init(zoitechat_plugin *plugin_handle,
zoitechat_hook_server_attrs(ph, "TOPIC", ZOITECHAT_PRI_NORM, handle_incoming, NULL); zoitechat_hook_server_attrs(ph, "TOPIC", ZOITECHAT_PRI_NORM, handle_incoming, NULL);
zoitechat_hook_server_attrs(ph, "332", ZOITECHAT_PRI_NORM, handle_incoming, NULL); zoitechat_hook_server_attrs(ph, "332", ZOITECHAT_PRI_NORM, handle_incoming, NULL);
if (!fish_init()) if (!fish_init()) {
zoitechat_printf(ph, "FiSHLiM failed to initialize crypto backend");
return 0; return 0;
}
if (!dh1080_init()) if (!dh1080_init()) {
zoitechat_printf(ph, "FiSHLiM failed to initialize DH1080");
return 0; return 0;
}
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();

View File

@@ -1,7 +1,19 @@
if cc.get_id() == 'msvc' if cc.get_id() == 'msvc'
lua_dep = cc.find_library('lua51') lua_dep = cc.find_library('lua51')
else else
lua_dep = dependency(get_option('with-lua')) lua_opt = get_option('with-lua')
lua_dep = dependency(lua_opt, required: false)
if not lua_dep.found() and lua_opt == 'lua-5.4'
foreach lua_name : ['lua5.4', 'lua-5.3', 'lua5.3', 'lua']
lua_dep = dependency(lua_name, required: false)
if lua_dep.found()
break
endif
endforeach
endif
if not lua_dep.found()
error('Dependency "' + lua_opt + '" not found')
endif
endif endif
shared_module('lua', 'lua.c', shared_module('lua', 'lua.c',

View File

@@ -9,9 +9,6 @@ if host_machine.system() == 'windows'
subdir('upd') subdir('upd')
endif endif
if get_option('with-winamp')
subdir('winamp')
endif
endif endif
if get_option('with-checksum') if get_option('with-checksum')

View File

@@ -1457,14 +1457,14 @@ perl_load_file (char *filename)
"You must have a Visual C++ build of Perl " "You must have a Visual C++ build of Perl "
PERL_REQUIRED_VERSION " installed in order to\n" PERL_REQUIRED_VERSION " installed in order to\n"
"run Perl scripts. A reboot may be required.\n\n" "run Perl scripts. A reboot may be required.\n\n"
"http://zoitechat.zoite.net/downloads.html\n\n" "http://zoitechat.org/downloads.html\n\n"
"I have found Perl 5.6, but that is too old."); "I have found Perl 5.6, but that is too old.");
} else { } else {
thread_mbox ("Cannot open " PERL_DLL "!\n\n" thread_mbox ("Cannot open " PERL_DLL "!\n\n"
"You must have a Visual C++ build of Perl " "You must have a Visual C++ build of Perl "
PERL_REQUIRED_VERSION " installed in order to\n" PERL_REQUIRED_VERSION " installed in order to\n"
"run Perl scripts. A reboot may be required.\n\n" "run Perl scripts. A reboot may be required.\n\n"
"http://zoitechat.zoite.net/downloads.html\n\n" "http://zoitechat.org/downloads.html\n\n"
"Make sure Perl's bin directory is in your PATH."); "Make sure Perl's bin directory is in your PATH.");
} }
} }

View File

@@ -53,6 +53,16 @@ builder.set_source('_zoitechat_embedded', '''
#include "config.h" #include "config.h"
#include "zoitechat-plugin.h" #include "zoitechat-plugin.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
static zoitechat_plugin *ph; static zoitechat_plugin *ph;
CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *); CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *);
CFFI_DLLEXPORT int _on_plugin_deinit(void); CFFI_DLLEXPORT int _on_plugin_deinit(void);
@@ -61,6 +71,20 @@ int zoitechat_plugin_init(zoitechat_plugin *plugin_handle,
char **name_out, char **description_out, char **name_out, char **description_out,
char **version_out, char *arg) char **version_out, char *arg)
{ {
#ifdef _WIN32
/* CPython cannot be removed from a process once Py_Initialize has run.
If the embedded interpreter fails to start (e.g. _cffi_backend is
missing), the host responds to our 0 return by g_module_close()ing
this plugin, which would also drop the last reference to pythonXY.dll
and unload it mid-flight; the leftover process-global state then
crashes the process later, e.g. when a server connection spawns the
next thread. Pin this module so FreeLibrary can never unload it. */
HMODULE self_handle;
GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_PIN |
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCWSTR)&zoitechat_plugin_init, &self_handle);
#endif
if (ph != NULL) if (ph != NULL)
{ {
puts ("Python plugin already loaded\\n"); puts ("Python plugin already loaded\\n");

View File

@@ -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.0~pre6' VERSION = b'2.18.3'
PLUGIN_NAME = ffi.new('char[]', b'Python') PLUGIN_NAME = ffi.new('char[]', b'Python')
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1])) PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
PLUGIN_VERSION = ffi.new('char[]', VERSION) PLUGIN_VERSION = ffi.new('char[]', VERSION)
@@ -173,7 +173,7 @@ class Plugin:
def __del__(self): def __del__(self):
log('unloading', self.filename) log('unloading', self.filename)
for hook in self.hooks: for hook in list(self.hooks):
if hook.is_unload is True: if hook.is_unload is True:
try: try:
hook.callback(hook.userdata) hook.callback(hook.userdata)
@@ -320,9 +320,9 @@ def _on_say_command(word, word_eol, userdata):
return 0 return 0
try: try:
python = _cstr(word_eol[1]) python = __decode(_cstr(word_eol[1]))
except Exception: except Exception:
python = b'' python = ''
if not python: if not python:
return 1 return 1

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
<PlatformToolset>v142</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{D58924D2-1492-4AC3-B058-7A214424EEDC}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>python38</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\win32\zoitechat.props" />
<PropertyGroup>
<TargetName>$(Python38Output)</TargetName>
<OutDir>$(ZoiteChatRel)plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;PYTHON_EXPORTS;Py_NO_LINK_LIB;$(OwnFlags);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UndefinePreprocessorDefinitions>_DEBUG;Py_DEBUG;Py_REF_DEBUG;Py_TRACE_REFS;%(UndefinePreprocessorDefinitions)</UndefinePreprocessorDefinitions>
<AdditionalOptions>/U_DEBUG /UPy_DEBUG /UPy_REF_DEBUG /UPy_TRACE_REFS %(AdditionalOptions)</AdditionalOptions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<AdditionalIncludeDirectories>$(Glib);$(Python38Path)\include;..\..\src\common;$(ZoiteChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<ModuleDefinitionFile>python.def</ModuleDefinitionFile>
<AdditionalDependencies>"$(Python38Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python38Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>"$(Python38Path)\python.exe" generate_plugin.py ..\..\src\common\zoitechat-plugin.h python.py "$(IntDir)python.c"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="generate_plugin.py" />
<None Include="zoitechat.py" />
<None Include="python.def" />
<None Include="python.py" />
<None Include="xchat.py" />
<None Include="hexchat.py" />
<None Include="_zoitechat.py" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -24,7 +24,7 @@
#include "zoitechat-plugin.h" #include "zoitechat-plugin.h"
#define APPCAST_URL "https://zoitechat.zoite.net/appcast.xml" #define APPCAST_URL "https://zoitechat.org/appcast.xml"
static zoitechat_plugin *ph; static zoitechat_plugin *ph;
static char name[] = "Update Checker"; static char name[] = "Update Checker";

View File

@@ -1,7 +0,0 @@
shared_module('winamp', 'winamp.c',
dependencies: [libgio_dep, zoitechat_plugin_dep],
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'winamp.def',
)

View File

@@ -1,153 +0,0 @@
/********************* Winamp Plugin 0.3******************************
*
* Distribution: GPL
*
* Originally written by: Leo - leo.nard@free.fr
* Modified by: SilvereX - SilvereX@karklas.mif.vu.lt
* Modified again by: Derek Buitenhuis - daemon404@gmail.com
* Modified yet again by: Berke Viktor - berkeviktor@aol.com
*********************************************************************/
#include "windows.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "zoitechat-plugin.h"
#define PLAYING 1
#define PAUSED 3
static zoitechat_plugin *ph;
static int
winamp(char *word[], char *word_eol[], void *userdata)
{
HWND hwndWinamp = FindWindowW(L"Winamp v1.x",NULL);
if (hwndWinamp)
{
if (!stricmp("PAUSE", word[2]))
{
if (SendMessage(hwndWinamp,WM_USER, 0, 104))
{
SendMessage(hwndWinamp, WM_COMMAND, 40046, 0);
if (SendMessage(hwndWinamp, WM_USER, 0, 104) == PLAYING)
zoitechat_printf(ph, "Winamp: playing");
else
zoitechat_printf(ph, "Winamp: paused");
}
}
else if (!stricmp("STOP", word[2]))
{
SendMessage(hwndWinamp, WM_COMMAND, 40047, 0);
zoitechat_printf(ph, "Winamp: stopped");
}
else if (!stricmp("PLAY", word[2]))
{
SendMessage(hwndWinamp, WM_COMMAND, 40045, 0);
zoitechat_printf(ph, "Winamp: playing");
}
else if (!stricmp("NEXT", word[2]))
{
SendMessage(hwndWinamp, WM_COMMAND, 40048, 0);
zoitechat_printf(ph, "Winamp: next playlist entry");
}
else if (!stricmp("PREV", word[2]))
{
SendMessage(hwndWinamp, WM_COMMAND, 40044, 0);
zoitechat_printf(ph, "Winamp: previous playlist entry");
}
else if (!stricmp("START", word[2]))
{
SendMessage(hwndWinamp, WM_COMMAND, 40154, 0);
zoitechat_printf(ph, "Winamp: playlist start");
}
else if (!word_eol[2][0])
{
wchar_t wcurrent_play[2048];
char *current_play, *p;
int len = GetWindowTextW(hwndWinamp, wcurrent_play, G_N_ELEMENTS(wcurrent_play));
current_play = g_utf16_to_utf8 (wcurrent_play, len, NULL, NULL, NULL);
if (!current_play)
{
zoitechat_print (ph, "Winamp: Error getting song information.");
return ZOITECHAT_EAT_ALL;
}
if (strchr(current_play, '-'))
{
/* Remove any trailing text and whitespace */
p = current_play + strlen(current_play) - 8;
while (p >= current_play)
{
if (!strnicmp(p, "- Winamp", 8))
break;
p--;
}
if (p >= current_play)
p--;
while (p >= current_play && *p == ' ')
p--;
*++p = '\0';
/* Ignore any leading track number */
p = strstr (current_play, ". ");
if (p)
p += 2;
else
p = current_play;
if (*p != '\0')
zoitechat_commandf (ph, "me is now playing: %s", p);
else
zoitechat_print (ph, "Winamp: No song information found.");
g_free (current_play);
}
else
zoitechat_print(ph, "Winamp: Nothing being played.");
}
else
zoitechat_printf(ph, "Usage: /WINAMP [PAUSE|PLAY|STOP|NEXT|PREV|START]\n");
}
else
{
zoitechat_print(ph, "Winamp not found.\n");
}
return ZOITECHAT_EAT_ALL;
}
int
zoitechat_plugin_init(zoitechat_plugin *plugin_handle,
char **plugin_name,
char **plugin_desc,
char **plugin_version,
char *arg)
{
ph = plugin_handle;
*plugin_name = "Winamp";
*plugin_desc = "Winamp plugin for ZoiteChat";
*plugin_version = "0.6";
zoitechat_hook_command (ph, "WINAMP", ZOITECHAT_PRI_NORM, winamp, "Usage: /WINAMP [PAUSE|PLAY|STOP|NEXT|PREV|START] - control Winamp or show what's currently playing", 0);
zoitechat_command (ph, "MENU -ishare\\music.png ADD \"Window/Display Current Song (Winamp)\" \"WINAMP\"");
zoitechat_print (ph, "Winamp plugin loaded\n");
return 1;
}
int
zoitechat_plugin_deinit(void)
{
zoitechat_command (ph, "MENU DEL \"Window/Display Current Song (Winamp)\"");
zoitechat_print (ph, "Winamp plugin unloaded\n");
return 1;
}

View File

@@ -1,3 +0,0 @@
EXPORTS
zoitechat_plugin_init
zoitechat_plugin_deinit

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
<PlatformToolset>v142</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{E78C0D9A-798E-4BF6-B0CC-6FECB8CA2FCE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>winamp</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\win32\zoitechat.props" />
<PropertyGroup>
<TargetName>hcwinamp</TargetName>
<OutDir>$(ZoiteChatRel)plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;WINAMP_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(OpenSslInclude);$(Glib);..\..\src\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<ModuleDefinitionFile>winamp.def</ModuleDefinitionFile>
<AdditionalDependencies>$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="winamp.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="winamp.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="winamp.def">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ClCompile Include="winamp.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@
[![Version][github-version-img]][github-version-uri] [![Downloads][github-downloads-img]][github-downloads-uri] [![Size][github-size-img]][github-size-img] [![Last Commit][github-commit-img]][github-commit-img] [![Contributors][contribs-all-img]](#contributors-) [![Version][github-version-img]][github-version-uri] [![Downloads][github-downloads-img]][github-downloads-uri] [![Size][github-size-img]][github-size-img] [![Last Commit][github-commit-img]][github-commit-img] [![Contributors][contribs-all-img]](#contributors-)
[![View Official Documentation](https://img.shields.io/badge/View_Official_Documentation-526CFE?style=for-the-badge&logo=MaterialForMkDocs&logoColor=white)](https://docs.zoitechat.zoite.net) [![View Official Documentation](https://img.shields.io/badge/View_Official_Documentation-526CFE?style=for-the-badge&logo=MaterialForMkDocs&logoColor=white)](https://docs.zoitechat.org)
</div> </div>
@@ -31,12 +31,19 @@
<br /> <br />
ZoiteChat is an HexChat based IRC client for Windows and UNIX-like operating systems. ZoiteChat is a GTK3 IRC client based on HexChat, available for Windows and UNIX-like operating systems.
See [IRCHelp.org](http://irchelp.org) for information about IRC in general.
For more information on ZoiteChat please read our [documentation](https://docs.zoitechat.zoite.net/):
- [Downloads](https://zoitechat.zoite.net/download)
- [Troubleshooting](troubleshooting.md) Features include HexChat-compatible Python, Perl and Lua scripting support, a plugin API,
multiple server/channel windows, spell checking, multiple authentication methods including SASL,
and customizable notifications.
See [IRCHelp.org](http://irchelp.org) for information about IRC in general.
For more information on ZoiteChat:
- [Main Documentation](https://docs.zoitechat.org/) and [FAQ](https://docs.zoitechat.org/en/latest/faq.html)
- [Downloads](https://zoitechat.org/download.php)
--- ---

View File

@@ -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},
@@ -445,7 +446,9 @@ const struct prefs vars[] =
{"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_INT}, {"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_INT},
{"gui_gtk3_variant", P_OFFINT (hex_gui_gtk3_variant), TYPE_INT}, {"gui_gtk3_variant", P_OFFINT (hex_gui_gtk3_variant), TYPE_INT},
{"gui_tab_layout", P_OFFINT (hex_gui_tab_layout), TYPE_INT}, {"gui_tab_layout", P_OFFINT (hex_gui_tab_layout), TYPE_INT},
{"gui_tab_closebuttons", P_OFFINT (hex_gui_tab_closebuttons), TYPE_BOOL},
{"gui_tab_middleclose", P_OFFINT (hex_gui_tab_middleclose), TYPE_BOOL}, {"gui_tab_middleclose", P_OFFINT (hex_gui_tab_middleclose), TYPE_BOOL},
{"gui_mouse_scroll_speed", P_OFFINT (hex_gui_mouse_scroll_speed), TYPE_INT},
{"gui_tab_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT}, {"gui_tab_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT},
{"gui_tab_pos", P_OFFINT (hex_gui_tab_pos), TYPE_INT}, {"gui_tab_pos", P_OFFINT (hex_gui_tab_pos), TYPE_INT},
{"gui_tab_scrollchans", P_OFFINT (hex_gui_tab_scrollchans), TYPE_BOOL}, {"gui_tab_scrollchans", P_OFFINT (hex_gui_tab_scrollchans), TYPE_BOOL},
@@ -456,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},
@@ -516,6 +520,7 @@ const struct prefs vars[] =
{"irc_conf_mode", P_OFFINT (hex_irc_conf_mode), TYPE_BOOL}, {"irc_conf_mode", P_OFFINT (hex_irc_conf_mode), TYPE_BOOL},
{"irc_extra_hilight", P_OFFSET (hex_irc_extra_hilight), TYPE_STR}, {"irc_extra_hilight", P_OFFSET (hex_irc_extra_hilight), TYPE_STR},
{"irc_hide_nickchange", P_OFFINT (hex_irc_hide_nickchange), TYPE_BOOL}, {"irc_hide_nickchange", P_OFFINT (hex_irc_hide_nickchange), TYPE_BOOL},
{"irc_hide_join_part_hostmask", P_OFFINT (hex_irc_hide_join_part_hostmask), TYPE_BOOL},
{"irc_hide_version", P_OFFINT (hex_irc_hide_version), TYPE_BOOL}, {"irc_hide_version", P_OFFINT (hex_irc_hide_version), TYPE_BOOL},
{"irc_hidehost", P_OFFINT (hex_irc_hidehost), TYPE_BOOL}, {"irc_hidehost", P_OFFINT (hex_irc_hidehost), TYPE_BOOL},
{"irc_id_ntext", P_OFFSET (hex_irc_id_ntext), TYPE_STR}, {"irc_id_ntext", P_OFFSET (hex_irc_id_ntext), TYPE_STR},
@@ -547,6 +552,10 @@ const struct prefs vars[] =
#endif #endif
{"net_bind_host", P_OFFSET (hex_net_bind_host), TYPE_STR}, {"net_bind_host", P_OFFSET (hex_net_bind_host), TYPE_STR},
{"net_ping_timeout", P_OFFINT (hex_net_ping_timeout), TYPE_INT, zoitechat_reinit_timers}, {"net_ping_timeout", P_OFFINT (hex_net_ping_timeout), TYPE_INT, zoitechat_reinit_timers},
{"net_lag_check", P_OFFINT (hex_net_lag_check), TYPE_INT, zoitechat_reinit_timers},
{"net_keepalive_idle", P_OFFINT (hex_net_keepalive_idle), TYPE_INT},
{"net_keepalive_interval", P_OFFINT (hex_net_keepalive_interval), TYPE_INT},
{"net_keepalive_count", P_OFFINT (hex_net_keepalive_count), TYPE_INT},
{"net_proxy_auth", P_OFFINT (hex_net_proxy_auth), TYPE_BOOL}, {"net_proxy_auth", P_OFFINT (hex_net_proxy_auth), TYPE_BOOL},
{"net_proxy_host", P_OFFSET (hex_net_proxy_host), TYPE_STR}, {"net_proxy_host", P_OFFSET (hex_net_proxy_host), TYPE_STR},
{"net_proxy_pass", P_OFFSET (hex_net_proxy_pass), TYPE_STR}, {"net_proxy_pass", P_OFFSET (hex_net_proxy_pass), TYPE_STR},
@@ -777,13 +786,18 @@ load_default_config(void)
prefs.hex_gui_tab_chans = 1; prefs.hex_gui_tab_chans = 1;
prefs.hex_gui_tab_dialogs = 1; prefs.hex_gui_tab_dialogs = 1;
prefs.hex_gui_tab_icons = 1; prefs.hex_gui_tab_icons = 1;
prefs.hex_gui_tab_closebuttons = 1;
prefs.hex_gui_tab_middleclose = 1; prefs.hex_gui_tab_middleclose = 1;
prefs.hex_gui_tab_server = 1; prefs.hex_gui_tab_server = 1;
prefs.hex_gui_tab_sort = 1; prefs.hex_gui_tab_sort = 1;
prefs.hex_gui_tab_scrollchans = 1;
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;
prefs.hex_gui_ulist_color = 1;
prefs.hex_gui_ulist_count = 1; prefs.hex_gui_ulist_count = 1;
prefs.hex_gui_ulist_icons = 1; prefs.hex_gui_ulist_icons = 1;
prefs.hex_gui_ulist_style = 1; prefs.hex_gui_ulist_style = 1;
@@ -854,6 +868,10 @@ load_default_config(void)
prefs.hex_irc_ban_type = 1; prefs.hex_irc_ban_type = 1;
prefs.hex_irc_join_delay = 5; prefs.hex_irc_join_delay = 5;
prefs.hex_net_ping_timeout = 60; prefs.hex_net_ping_timeout = 60;
prefs.hex_net_lag_check = 60;
prefs.hex_net_keepalive_idle = 60;
prefs.hex_net_keepalive_interval = 20;
prefs.hex_net_keepalive_count = 3;
prefs.hex_net_reconnect_delay = 10; prefs.hex_net_reconnect_delay = 10;
prefs.hex_notify_timeout = 15; prefs.hex_notify_timeout = 15;
prefs.hex_text_max_indent = 256; prefs.hex_text_max_indent = 256;
@@ -1164,8 +1182,11 @@ set_showval (session *sess, const struct prefs *var, char *tbuf)
switch (var->type) switch (var->type)
{ {
case TYPE_STR: case TYPE_STR:
sprintf (tbuf + len, "\0033:\017 %s\n", (char *) &prefs + var->offset); {
break; const char *value = (char *) &prefs + var->offset;
sprintf (tbuf + len, "\0033:\017 %s\n", value ? value : "");
}
break;
case TYPE_INT: case TYPE_INT:
sprintf (tbuf + len, "\0033:\017 %d\n", *((int *) &prefs + var->offset)); sprintf (tbuf + len, "\0033:\017 %d\n", *((int *) &prefs + var->offset));
break; break;

View File

@@ -29,8 +29,10 @@
<ClInclude Include="plugin-timer.h" /> <ClInclude Include="plugin-timer.h" />
<ClInclude Include="plugin.h" /> <ClInclude Include="plugin.h" />
<ClInclude Include="proto-irc.h" /> <ClInclude Include="proto-irc.h" />
<ClInclude Include="public_suffix_data.h" />
<ClInclude Include="server.h" /> <ClInclude Include="server.h" />
<ClInclude Include="servlist.h" /> <ClInclude Include="servlist.h" />
<ClInclude Include="secretstore.h" />
<ClInclude Include="ssl.h" /> <ClInclude Include="ssl.h" />
<ClInclude Include="scram.h" /> <ClInclude Include="scram.h" />
<ClInclude Include="sysinfo\sysinfo.h" /> <ClInclude Include="sysinfo\sysinfo.h" />
@@ -67,6 +69,7 @@
<ClCompile Include="proto-irc.c" /> <ClCompile Include="proto-irc.c" />
<ClCompile Include="server.c" /> <ClCompile Include="server.c" />
<ClCompile Include="servlist.c" /> <ClCompile Include="servlist.c" />
<ClCompile Include="secretstore.c" />
<ClCompile Include="ssl.c" /> <ClCompile Include="ssl.c" />
<ClCompile Include="scram.c" /> <ClCompile Include="scram.c" />
<ClCompile Include="sts.c" /> <ClCompile Include="sts.c" />
@@ -109,6 +112,7 @@
<Command><![CDATA[ <Command><![CDATA[
SET SOLUTIONDIR=$(SolutionDir)..\ SET SOLUTIONDIR=$(SolutionDir)..\
"$(Python3Path)\python.exe" $(ProjectDir)make-te.py "$(ProjectDir)textevents.in" "$(ZoiteChatLib)textevents.h" "$(ZoiteChatLib)textenums.h" "$(Python3Path)\python.exe" $(ProjectDir)make-te.py "$(ProjectDir)textevents.in" "$(ZoiteChatLib)textevents.h" "$(ZoiteChatLib)textenums.h"
"$(Python3Path)\python.exe" $(ProjectDir)gen-public-suffix.py "$(ZoiteChatLib)public_suffix_data.h"
powershell -File "$(SolutionDir)..\win32\version-template.ps1" "$(SolutionDir)..\win32\config.h.tt" "$(ZoiteChatLib)config.h" powershell -File "$(SolutionDir)..\win32\version-template.ps1" "$(SolutionDir)..\win32\config.h.tt" "$(ZoiteChatLib)config.h"
$(GlibGenMarshal) --prefix=_zoitechat_marshal --header "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.h" $(GlibGenMarshal) --prefix=_zoitechat_marshal --header "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.h"
$(GlibGenMarshal) --prefix=_zoitechat_marshal --body "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.c" $(GlibGenMarshal) --prefix=_zoitechat_marshal --body "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.c"

View File

@@ -65,12 +65,18 @@
<ClInclude Include="proto-irc.h"> <ClInclude Include="proto-irc.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="public_suffix_data.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="server.h"> <ClInclude Include="server.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="servlist.h"> <ClInclude Include="servlist.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="secretstore.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ssl.h"> <ClInclude Include="ssl.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@@ -175,6 +181,9 @@
<ClCompile Include="servlist.c"> <ClCompile Include="servlist.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="secretstore.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ssl.c"> <ClCompile Include="ssl.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>

View File

@@ -82,10 +82,6 @@ has_theme_argument (void)
void void
zoitechat_remote (void) zoitechat_remote (void)
/* TODO: dbus_g_connection_unref (connection) are commented because it makes
* dbus to crash. Fixed in dbus >=0.70 ?!?
* https://launchpad.net/distros/ubuntu/+source/dbus/+bug/54375
*/
{ {
GDBusConnection *connection; GDBusConnection *connection;
GDBusProxy *dbus = NULL; GDBusProxy *dbus = NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -1,203 +1,122 @@
/* example.c - program to demonstrate some D-BUS stuffs. #include <gio/gio.h>
* Copyright (C) 2006 Claessens Xavier #include <glib.h>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Claessens Xavier
* xclaesse@gmail.com
*/
#include <config.h>
#include <dbus/dbus-glib.h>
#include <stdlib.h> #include <stdlib.h>
#include "../marshal.c"
#define DBUS_SERVICE "org.zoitechat.service" #define DBUS_SERVICE "org.zoitechat.service"
#define DBUS_REMOTE "/org/zoitechat/Remote" #define DBUS_CONNECTION_PATH "/org/zoitechat"
#define DBUS_REMOTE_CONNECTION_INTERFACE "org.zoitechat.connection" #define DBUS_CONNECTION_INTERFACE "org.zoitechat.connection"
#define DBUS_REMOTE_PLUGIN_INTERFACE "org.zoitechat.plugin" #define DBUS_PLUGIN_INTERFACE "org.zoitechat.plugin"
guint command_id; static gboolean
guint server_id; call_sync (GDBusProxy *proxy, const char *method, GVariant *params, GVariant **out)
static void
write_error (const char *message,
GError **error)
{
if (error == NULL || *error == NULL) {
return;
}
g_printerr ("%s: %s\n", message, (*error)->message);
g_clear_error (error);
}
static void
test_server_cb (DBusGProxy *proxy,
char *word[],
char *word_eol[],
guint hook_id,
guint context_id,
gpointer user_data)
{
if (hook_id == server_id) {
g_print ("message: %s\n", word_eol[0]);
}
}
static void
test_command_cb (DBusGProxy *proxy,
char *word[],
char *word_eol[],
guint hook_id,
guint context_id,
gpointer user_data)
{ {
GError *error = NULL; GError *error = NULL;
GVariant *result;
if (hook_id == command_id) { result = g_dbus_proxy_call_sync (proxy,
if (!dbus_g_proxy_call (proxy, "Unhook", method,
&error, params,
G_TYPE_UINT, hook_id, G_DBUS_CALL_FLAGS_NONE,
G_TYPE_INVALID, G_TYPE_INVALID)) { -1,
write_error ("Failed to complete unhook", &error); NULL,
} &error);
/* Now if you write "/test blah" again in the ZoiteChat window if (!result)
* you'll get a "Unknown command" error message */ {
g_print ("test command received: %s\n", word_eol[1]); g_printerr ("%s failed: %s\n", method, error->message);
if (!dbus_g_proxy_call (proxy, "Print", g_clear_error (&error);
&error, return FALSE;
G_TYPE_STRING, "test command succeed",
G_TYPE_INVALID,
G_TYPE_INVALID)) {
write_error ("Failed to complete Print", &error);
}
} }
}
static void if (out)
unload_cb (void) *out = result;
{ else
g_print ("Good bye !\n"); g_variant_unref (result);
exit (EXIT_SUCCESS);
return TRUE;
} }
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {
DBusGConnection *connection; GDBusConnection *connection;
DBusGProxy *remote_object; GDBusProxy *connection_proxy;
GMainLoop *mainloop; GDBusProxy *plugin_proxy;
gchar *path; GVariant *connect_result = NULL;
gchar *remote_path = NULL;
gchar *command = NULL;
const char *path_tmp;
int status = EXIT_FAILURE;
GError *error = NULL; GError *error = NULL;
#if ! GLIB_CHECK_VERSION (2, 36, 0) connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_type_init (); if (!connection)
#endif {
g_printerr ("Bus connection failed: %s\n", error->message);
connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); g_clear_error (&error);
if (connection == NULL) {
write_error ("Couldn't connect to session bus", &error);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
remote_object = dbus_g_proxy_new_for_name (connection, connection_proxy = g_dbus_proxy_new_sync (connection,
DBUS_SERVICE, G_DBUS_PROXY_FLAGS_NONE,
DBUS_REMOTE, NULL,
DBUS_REMOTE_CONNECTION_INTERFACE); DBUS_SERVICE,
if (!dbus_g_proxy_call (remote_object, "Connect", DBUS_CONNECTION_PATH,
&error, DBUS_CONNECTION_INTERFACE,
G_TYPE_STRING, argv[0], NULL,
G_TYPE_STRING, "example", &error);
G_TYPE_STRING, "Example of a D-Bus client", if (!connection_proxy)
G_TYPE_STRING, "1.0", {
G_TYPE_INVALID, g_printerr ("Connection proxy failed: %s\n", error->message);
G_TYPE_STRING, &path, G_TYPE_INVALID)) { g_clear_error (&error);
write_error ("Failed to complete Connect", &error); g_object_unref (connection);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
g_object_unref (remote_object);
remote_object = dbus_g_proxy_new_for_name (connection, if (!call_sync (connection_proxy,
DBUS_SERVICE, "Connect",
path, g_variant_new ("(ssss)", "example", "example", "GDBus example", "1.0"),
DBUS_REMOTE_PLUGIN_INTERFACE); &connect_result))
g_free (path); goto cleanup;
if (!dbus_g_proxy_call (remote_object, "HookCommand", g_variant_get (connect_result, "(&s)", &path_tmp);
&error, remote_path = g_strdup (path_tmp);
G_TYPE_STRING, "test", g_variant_unref (connect_result);
G_TYPE_INT, 0, connect_result = NULL;
G_TYPE_STRING, "Simple D-BUS example",
G_TYPE_INT, 1, G_TYPE_INVALID, plugin_proxy = g_dbus_proxy_new_sync (connection,
G_TYPE_UINT, &command_id, G_TYPE_INVALID)) { G_DBUS_PROXY_FLAGS_NONE,
write_error ("Failed to complete HookCommand", &error); NULL,
return EXIT_FAILURE; DBUS_SERVICE,
remote_path,
DBUS_PLUGIN_INTERFACE,
NULL,
&error);
if (!plugin_proxy)
{
g_printerr ("Plugin proxy failed: %s\n", error->message);
g_clear_error (&error);
goto cleanup;
} }
g_print ("Command hook id=%d\n", command_id);
if (!dbus_g_proxy_call (remote_object, "HookServer", if (argc > 1)
&error, command = g_strjoinv (" ", &argv[1]);
G_TYPE_STRING, "RAW LINE", else
G_TYPE_INT, 0, command = g_strdup ("gui focus");
G_TYPE_INT, 0, G_TYPE_INVALID,
G_TYPE_UINT, &server_id, G_TYPE_INVALID)) { if (!call_sync (plugin_proxy, "Command", g_variant_new ("(s)", command), NULL))
write_error ("Failed to complete HookServer", &error); {
return EXIT_FAILURE; g_object_unref (plugin_proxy);
goto cleanup;
} }
g_print ("Server hook id=%d\n", server_id);
dbus_g_object_register_marshaller ( call_sync (connection_proxy, "Disconnect", g_variant_new ("()"), NULL);
_zoitechat_marshal_VOID__POINTER_POINTER_UINT_UINT, status = EXIT_SUCCESS;
G_TYPE_NONE, g_object_unref (plugin_proxy);
G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT,
G_TYPE_INVALID);
dbus_g_object_register_marshaller ( cleanup:
g_cclosure_marshal_VOID__VOID, g_free (command);
G_TYPE_NONE, g_free (remote_path);
G_TYPE_INVALID); g_object_unref (connection_proxy);
g_object_unref (connection);
dbus_g_proxy_add_signal (remote_object, "CommandSignal", return status;
G_TYPE_STRV,
G_TYPE_STRV,
G_TYPE_UINT,
G_TYPE_UINT,
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (remote_object, "CommandSignal",
G_CALLBACK (test_command_cb),
NULL, NULL);
dbus_g_proxy_add_signal (remote_object, "ServerSignal",
G_TYPE_STRV,
G_TYPE_STRV,
G_TYPE_UINT,
G_TYPE_UINT,
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (remote_object, "ServerSignal",
G_CALLBACK (test_server_cb),
NULL, NULL);
dbus_g_proxy_add_signal (remote_object, "UnloadSignal",
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (remote_object, "UnloadSignal",
G_CALLBACK (unload_cb),
NULL, NULL);
/* Now you can write on the ZoiteChat windows: "/test arg1 arg2 ..." */
mainloop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (mainloop);
return EXIT_SUCCESS;
} }

View File

@@ -1,6 +1,4 @@
dbus_deps = [ dbus_deps = []
dbus_glib_dep
]
dbus_sources = [ dbus_sources = [
'dbus-plugin.c', 'dbus-plugin.c',
@@ -35,16 +33,8 @@ configure_file(
install_dir: dbus_service_dir install_dir: dbus_service_dir
) )
dbus_binding_tool = find_program('dbus-binding-tool')
dbus_remote_object = custom_target('remote-object-glue',
input: 'remote-object.xml',
output: 'remote-object-glue.h',
command: [dbus_binding_tool, '--prefix=remote_object', '--mode=glib-server',
'--output=@OUTPUT@', '@INPUT@']
)
zoitechat_dbus = static_library('zoitechatdbus', zoitechat_dbus = static_library('zoitechatdbus',
sources: [dbus_remote_object, marshal] + dbus_sources, sources: marshal + dbus_sources,
c_args: dbus_cargs, c_args: dbus_cargs,
dependencies: common_deps + dbus_deps, dependencies: common_deps + dbus_deps,
include_directories: dbus_includes, include_directories: dbus_includes,

View File

@@ -304,29 +304,24 @@ dcc_check_timeouts (void)
static int static int
dcc_lookup_proxy (char *host, struct sockaddr_in *addr) dcc_lookup_proxy (char *host, struct sockaddr_in *addr)
{ {
struct hostent *h;
static char *cache_host = NULL; static char *cache_host = NULL;
static guint32 cache_addr; static guint32 cache_addr;
/* too lazy to thread this, so we cache results */
if (cache_host) if (cache_host)
{ {
if (strcmp (host, cache_host) == 0) if (strcmp (host, cache_host) == 0)
{ {
memcpy (&addr->sin_addr, &cache_addr, 4); addr->sin_addr.s_addr = cache_addr;
return TRUE; return TRUE;
} }
g_free (cache_host); g_free (cache_host);
cache_host = NULL; cache_host = NULL;
} }
h = gethostbyname (host); if (net_lookup_ipv4 (host, &addr->sin_addr.s_addr))
if (h != NULL && h->h_length == 4 && h->h_addr_list[0] != NULL)
{ {
memcpy (&addr->sin_addr, h->h_addr_list[0], 4); cache_addr = addr->sin_addr.s_addr;
memcpy (&cache_addr, h->h_addr_list[0], 4);
cache_host = g_strdup (host); cache_host = g_strdup (host);
/* cppcheck-suppress memleak */
return TRUE; return TRUE;
} }
@@ -1614,25 +1609,14 @@ dcc_accept (GIOChannel *source, GIOCondition condition, struct DCC *dcc)
} }
guint32 guint32
dcc_get_my_address (session *sess) /* the address we'll tell the other person */ dcc_get_my_address (session *sess)
{ {
struct hostent *dns_query;
guint32 addr = 0; guint32 addr = 0;
if (prefs.hex_dcc_ip_from_server && sess->server->dcc_ip) if (prefs.hex_dcc_ip_from_server && sess->server->dcc_ip)
addr = sess->server->dcc_ip; addr = sess->server->dcc_ip;
else if (prefs.hex_dcc_ip[0]) else if (prefs.hex_dcc_ip[0])
{ net_lookup_ipv4 ((const char *) prefs.hex_dcc_ip, &addr);
dns_query = gethostbyname ((const char *) prefs.hex_dcc_ip);
if (dns_query != NULL &&
dns_query->h_length == 4 &&
dns_query->h_addr_list[0] != NULL)
{
/*we're offered at least one IPv4 address: we take the first*/
addr = *((guint32*) dns_query->h_addr_list[0]);
}
}
return addr; return addr;
} }

View File

@@ -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,

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import sys
import urllib.request
from pathlib import Path
URLS = (
"https://raw.githubusercontent.com/publicsuffix/list/main/public_suffix_list.dat",
"https://publicsuffix.org/list/public_suffix_list.dat",
)
def parse_rules(text: str):
rules = []
for raw in text.splitlines():
line = raw.strip()
if not line or line.startswith("//"):
continue
if " " in line or "\t" in line:
line = line.split()[0]
rules.append(line.lower())
return sorted(set(rules))
def emit_header(path: str, rules):
with open(path, "w", encoding="utf-8", newline="\n") as out:
out.write("#pragma once\n")
out.write("static const char * const public_suffix_rules[] = {\n")
for rule in rules:
escaped = rule.replace("\\", "\\\\").replace('"', '\\"')
out.write(f'\t"{escaped}",\n')
out.write("};\n")
out.write(
"static const unsigned int public_suffix_rules_len = sizeof(public_suffix_rules) / sizeof(public_suffix_rules[0]);\n"
)
def main():
if len(sys.argv) not in (2, 3):
raise SystemExit("usage: gen-public-suffix.py <output> [source]")
output = Path(sys.argv[1])
sources = []
if len(sys.argv) == 3:
sources.append(Path(sys.argv[2]))
sources.extend(
[
Path(__file__).with_name("public_suffix_list.dat"),
Path("/usr/share/publicsuffix/public_suffix_list.dat"),
Path("/app/share/publicsuffix/public_suffix_list.dat"),
]
)
data = None
for url in URLS:
try:
with urllib.request.urlopen(url, timeout=30) as resp:
data = resp.read().decode("utf-8")
break
except Exception:
pass
if data is None:
for source in sources:
if source.exists():
data = source.read_text(encoding="utf-8")
break
if data is None:
raise SystemExit("unable to load public suffix list")
rules = parse_rules(data)
emit_header(str(output), rules)
if __name__ == "__main__":
main()

View File

@@ -1221,6 +1221,94 @@ extract_archive (const char *source, GError **error)
#endif #endif
} }
static char *
path_find_first_file_recursive (const char *root, const char *name, int depth)
{
GDir *dir;
const char *entry;
if (!root || !name || depth < 0 || !g_file_test (root, G_FILE_TEST_IS_DIR))
return NULL;
{
char *candidate = g_build_filename (root, name, NULL);
if (g_file_test (candidate, G_FILE_TEST_IS_REGULAR))
return candidate;
g_free (candidate);
}
if (depth == 0)
return NULL;
dir = g_dir_open (root, 0, NULL);
if (!dir)
return NULL;
while ((entry = g_dir_read_name (dir)) != NULL)
{
char *child = g_build_filename (root, entry, NULL);
char *found = NULL;
if (g_file_test (child, G_FILE_TEST_IS_DIR))
found = path_find_first_file_recursive (child, name, depth - 1);
g_free (child);
if (found)
{
g_dir_close (dir);
return found;
}
}
g_dir_close (dir);
return NULL;
}
gboolean
zoitechat_gtk3_theme_service_read_archive_text_file (const char *archive_path, const char *name, char **contents, GError **error)
{
char *root;
char *path;
gboolean ok;
GError *local_error = NULL;
if (contents)
*contents = NULL;
if (!archive_path || !*archive_path)
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "No archive path provided."), FALSE;
if (!name || !*name)
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "No file name provided."), FALSE;
if (!contents)
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "No output buffer provided."), FALSE;
root = extract_archive (archive_path, error);
if (!root)
return FALSE;
path = path_find_first_file_recursive (root, name, 8);
if (!path)
{
remove_tree (root);
g_free (root);
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Requested file was not found in archive."), FALSE;
}
ok = g_file_get_contents (path, contents, NULL, &local_error);
g_free (path);
remove_tree (root);
g_free (root);
if (!ok)
{
if (error)
*error = local_error;
else
g_clear_error (&local_error);
return FALSE;
}
return TRUE;
}
gboolean gboolean
zoitechat_gtk3_theme_service_import (const char *source_path, char **imported_id, GError **error) zoitechat_gtk3_theme_service_import (const char *source_path, char **imported_id, GError **error)
{ {

View File

@@ -48,5 +48,6 @@ gboolean zoitechat_gtk3_theme_service_remove_user_theme (const char *theme_id, G
char *zoitechat_gtk3_theme_pick_css_dir_for_minor (const char *theme_root, int preferred_minor); char *zoitechat_gtk3_theme_pick_css_dir_for_minor (const char *theme_root, int preferred_minor);
char *zoitechat_gtk3_theme_pick_css_dir (const char *theme_root); char *zoitechat_gtk3_theme_pick_css_dir (const char *theme_root);
GPtrArray *zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root); GPtrArray *zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root);
gboolean zoitechat_gtk3_theme_service_read_archive_text_file (const char *archive_path, const char *name, char **contents, GError **error);
#endif #endif

View File

@@ -38,6 +38,7 @@
#include "ignore.h" #include "ignore.h"
#include "fe.h" #include "fe.h"
#include "modes.h" #include "modes.h"
#include "network.h"
#include "notify.h" #include "notify.h"
#include "outbound.h" #include "outbound.h"
#include "inbound.h" #include "inbound.h"
@@ -45,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"
@@ -449,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)
@@ -517,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;
} }
@@ -531,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);
@@ -540,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
@@ -1476,14 +1638,13 @@ inbound_uback (server *serv, const message_tags_data *tags_data)
void void
inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data) inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data)
{ {
struct hostent *HostAddr; struct in_addr addr;
HostAddr = gethostbyname (ip); if (net_lookup_ipv4 (ip, &addr.s_addr))
if (HostAddr)
{ {
sess->server->dcc_ip = ((struct in_addr *) HostAddr->h_addr_list[0])->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 (*((struct in_addr *) HostAddr->h_addr_list[0])), net_ip (ntohl (addr.s_addr)),
NULL, NULL, NULL, 0, tags_data->timestamp); NULL, NULL, NULL, 0, tags_data->timestamp);
} }
} }
@@ -1654,14 +1815,14 @@ inbound_login_end (session *sess, char *text, const message_tags_data *tags_data
return; return;
/* send nickserv password */ /* send nickserv password */
if (net && net->pass && inbound_nickserv_login (serv)) if (net && serv->password[0] && inbound_nickserv_login (serv))
{ {
serv->p_ns_identify (serv, net->pass); serv->p_ns_identify (serv, serv->password);
} }
/* wait for join if command or nickserv set */ /* wait for join if command or nickserv set */
if (net && prefs.hex_irc_join_delay if (net && prefs.hex_irc_join_delay
&& ((net->pass && inbound_nickserv_login (serv)) && ((serv->password[0] && inbound_nickserv_login (serv))
|| net->commandlist)) || net->commandlist))
{ {
serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv); serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv);
@@ -1742,6 +1903,10 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
serv->have_awaynotify = enable; serv->have_awaynotify = enable;
else if (!strcmp (extension, "account-tag")) else if (!strcmp (extension, "account-tag"))
serv->have_account_tag = enable; serv->have_account_tag = enable;
else if (!strcmp (extension, "message-tags"))
serv->have_message_tags = enable;
else if (!strcmp (extension, "echo-message"))
serv->have_echo_message = enable;
else if (!strcmp (extension, "sasl")) else if (!strcmp (extension, "sasl"))
{ {
serv->have_sasl = enable; serv->have_sasl = enable;
@@ -1873,6 +2038,8 @@ static const char * const supported_caps[] = {
"account-tag", "account-tag",
"extended-monitor", "extended-monitor",
"standard-replies", "standard-replies",
"message-tags",
"echo-message",
/* ZNC */ /* ZNC */
"znc.in/server-time-iso", "znc.in/server-time-iso",

View File

@@ -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,

View File

@@ -27,6 +27,8 @@ common_sources = [
'util.c' 'util.c'
] ]
secretstore_sources = files('secretstore.c')
common_sysinfo_deps = [] common_sysinfo_deps = []
libarchive_dep = dependency('libarchive', required: host_machine.system() != 'windows') libarchive_dep = dependency('libarchive', required: host_machine.system() != 'windows')
@@ -38,6 +40,9 @@ common_deps = [
if libarchive_dep.found() if libarchive_dep.found()
common_deps += libarchive_dep common_deps += libarchive_dep
endif endif
if libsecret_dep.found()
common_deps += libsecret_dep
endif
common_includes = [ common_includes = [
config_h_include, config_h_include,
@@ -96,6 +101,14 @@ marshal = [
make_te = find_program('make-te.py') make_te = find_program('make-te.py')
python3 = find_program('python3', required: true)
public_suffix_data = custom_target('public_suffix_data_h',
output: 'public_suffix_data.h',
command: [python3, files('gen-public-suffix.py'), '@OUTPUT@']
)
textevents = custom_target('textevents', textevents = custom_target('textevents',
input: 'textevents.in', input: 'textevents.in',
output: ['textevents.h', 'textenums.h'], output: ['textevents.h', 'textenums.h'],
@@ -107,7 +120,7 @@ if libssl_dep.found()
common_deps += libssl_dep common_deps += libssl_dep
endif endif
if dbus_glib_dep.found() if dbus_dep.found()
subdir('dbus') subdir('dbus')
common_deps += zoitechat_dbus_dep common_deps += zoitechat_dbus_dep
common_includes += include_directories('dbus') common_includes += include_directories('dbus')
@@ -119,7 +132,7 @@ if get_option('plugin')
endif endif
zoitechat_common = static_library('zoitechatcommon', zoitechat_common = static_library('zoitechatcommon',
sources: [textevents] + marshal + common_sources, sources: [textevents, public_suffix_data] + marshal + common_sources + secretstore_sources,
include_directories: config_h_include, include_directories: config_h_include,
dependencies: common_deps + common_sysinfo_deps, dependencies: common_deps + common_sysinfo_deps,
c_args: common_cflags, c_args: common_cflags,
@@ -127,7 +140,7 @@ zoitechat_common = static_library('zoitechatcommon',
) )
zoitechat_common_dep = declare_dependency( zoitechat_common_dep = declare_dependency(
sources: [textevents] + marshal, sources: [textevents, public_suffix_data] + marshal,
link_with: zoitechat_common, link_with: zoitechat_common,
include_directories: common_includes, include_directories: common_includes,
compile_args: common_cflags, compile_args: common_cflags,

View File

@@ -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)

View File

@@ -34,6 +34,9 @@
#ifndef WIN32 #ifndef WIN32
#include <unistd.h> #include <unistd.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#endif #endif
#define WANTSOCKET #define WANTSOCKET
@@ -43,6 +46,9 @@
#define NETWORK_PRIVATE #define NETWORK_PRIVATE
#include "network.h" #include "network.h"
#include "zoitechat.h"
extern struct zoitechatprefs prefs;
#define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n))) #define RAND_INT(n) ((int)(rand() / (RAND_MAX + 1.0) * (n)))
@@ -58,15 +64,76 @@ net_set_socket_options (int sok)
setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw)); setsockopt (sok, SOL_SOCKET, SO_REUSEADDR, (char *) &sw, sizeof (sw));
sw = 1; sw = 1;
setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw)); setsockopt (sok, SOL_SOCKET, SO_KEEPALIVE, (char *) &sw, sizeof (sw));
#ifdef TCP_KEEPIDLE
{
int keepidle = prefs.hex_net_keepalive_idle;
if (keepidle > 0)
setsockopt (sok, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &keepidle, sizeof (keepidle));
}
#endif
#ifdef TCP_KEEPINTVL
{
int keepintvl = prefs.hex_net_keepalive_interval;
if (keepintvl > 0)
setsockopt (sok, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &keepintvl, sizeof (keepintvl));
}
#endif
#ifdef TCP_KEEPCNT
{
int keepcnt = prefs.hex_net_keepalive_count;
if (keepcnt > 0)
setsockopt (sok, IPPROTO_TCP, TCP_KEEPCNT, (char *) &keepcnt, sizeof (keepcnt));
}
#endif
} }
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
net_lookup_ipv4 (const char *hostname, uint32_t *addr)
{
struct addrinfo hints;
struct addrinfo *res;
struct sockaddr_in *sin;
int ret;
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
ret = getaddrinfo (hostname, NULL, &hints, &res);
if (ret != 0)
return FALSE;
sin = (struct sockaddr_in *) res->ai_addr;
*addr = sin->sin_addr.s_addr;
freeaddrinfo (res);
return TRUE;
} }
void void

View File

@@ -39,6 +39,8 @@ 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);
void net_sockets (int *sok4, int *sok6); void net_sockets (int *sok4, int *sok6);
#endif #endif

View File

@@ -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,8 +2744,8 @@ 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,8 +2756,8 @@ 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;
@@ -3462,28 +3593,45 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
int use_ssl = FALSE; int use_ssl = FALSE;
#endif #endif
int is_url = TRUE; int is_url = TRUE;
int no_proxy = FALSE;
server *serv = sess->server; server *serv = sess->server;
ircnet *net = NULL; ircnet *net = NULL;
while (TRUE)
{
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
/* BitchX uses -ssl, mIRC uses -e, let's support both */ if (g_strcmp0 (word[2 + offset], "-ssl") == 0 || g_strcmp0 (word[2 + offset], "-e") == 0)
if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-e") == 0) {
{ use_ssl = TRUE;
use_ssl = TRUE; use_ssl_noverify = FALSE;
offset++; /* args move up by 1 word */ offset++;
} }
else if (g_strcmp0 (word[2], "-ssl-noverify") == 0) else if (g_strcmp0 (word[2 + offset], "-ssl-noverify") == 0)
{ {
use_ssl = TRUE; use_ssl = TRUE;
use_ssl_noverify = TRUE; use_ssl_noverify = TRUE;
offset++; /* args move up by 1 word */ offset++;
} }
else if (g_strcmp0 (word[2], "-insecure") == 0) else if (g_strcmp0 (word[2 + offset], "-insecure") == 0)
{ {
use_ssl = FALSE; use_ssl = FALSE;
offset++; /* args move up by 1 word */ use_ssl_noverify = FALSE;
} offset++;
}
else
#endif #endif
if (g_strcmp0 (word[2 + offset], "-noproxy") == 0)
{
no_proxy = TRUE;
offset++;
}
else
{
break;
}
}
serv->dont_use_proxy = no_proxy;
if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &key, &use_ssl)) if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &key, &use_ssl))
{ {
@@ -3584,10 +3732,18 @@ cmd_servchan (struct session *sess, char *tbuf, char *word[],
{ {
int offset = 0; int offset = 0;
while (TRUE)
{
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-ssl-noverify") == 0 || g_strcmp0 (word[2], "-insecure") == 0) if (g_strcmp0 (word[2 + offset], "-ssl") == 0 || g_strcmp0 (word[2 + offset], "-ssl-noverify") == 0 || g_strcmp0 (word[2 + offset], "-insecure") == 0)
offset++; offset++;
else
#endif #endif
if (g_strcmp0 (word[2 + offset], "-noproxy") == 0)
offset++;
else
break;
}
if (*word[4 + offset]) if (*word[4 + offset])
{ {
@@ -4070,7 +4226,7 @@ const struct commands xc_cmds[] = {
{"ME", cmd_me, 0, 0, 1, {"ME", cmd_me, 0, 0, 1,
N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")}, N_("ME <action>, sends the action to the current channel (actions are written in the 3rd person, like /me jumps)")},
{"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n" {"MENU", cmd_menu, 0, 0, 1, "MENU [-eX] [-i<ICONFILE>] [-k<mod>,<key>] [-m] [-pX] [-r<X,group>] [-tX] {ADD|DEL} <path> [command] [unselect command]\n"
" See https://docs.zoitechat.zoite.net/en/latest/en/latest/plugins.html#controlling-the-gui for more details."}, " See https://docs.zoitechat.org/en/latest/en/latest/plugins.html#controlling-the-gui for more details."},
{"MHOP", cmd_mhop, 1, 1, 1, {"MHOP", cmd_mhop, 1, 1, 1,
N_("MHOP, Mass hop's all users in the current channel (needs chanop)")}, N_("MHOP, Mass hop's all users in the current channel (needs chanop)")},
{"MKICK", cmd_mkick, 1, 1, 1, {"MKICK", cmd_mkick, 1, 1, 1,
@@ -4114,22 +4270,24 @@ 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 [-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")},
#else #else
{"SERVCHAN", cmd_servchan, 0, 0, 1, {"SERVCHAN", cmd_servchan, 0, 0, 1,
N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")}, N_("SERVCHAN [-noproxy] <host> <port> <channel>, connects and joins a channel")},
#endif #endif
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
{"SERVER", cmd_server, 0, 0, 1, {"SERVER", cmd_server, 0, 0, 1,
N_("SERVER [-insecure|-ssl|-ssl-noverify] <host> [<port>] [<password>], connects to a server using ssl unless otherwise specified, the default port is 6697 for ssl connections and 6667 for insecure connections")}, N_("SERVER [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> [<port>] [<password>], connects to a server using ssl unless otherwise specified, the default port is 6697 for ssl connections and 6667 for insecure connections")},
#else #else
{"SERVER", cmd_server, 0, 0, 1, {"SERVER", cmd_server, 0, 0, 1,
N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")}, N_("SERVER [-noproxy] <host> [<port>] [<password>], connects to a server, the default port is 6667")},
#endif #endif
{"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")}, {"SET", cmd_set, 0, 0, 1, N_("SET [-e] [-off|-on] [-quiet] <variable> [<value>]")},
{"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>, reposition the cursor in the inputbox")}, {"SETCURSOR", cmd_setcursor, 0, 0, 1, N_("SETCURSOR [-|+]<position>, reposition the cursor in the inputbox")},
@@ -4166,10 +4324,10 @@ const struct commands xc_cmds[] = {
static int static int
command_compare (const void *a, const void *b) command_compare (const void *a, const void *b)
{ {
return g_ascii_strcasecmp (a, ((struct commands *)b)->name); return g_ascii_strcasecmp (a, ((const struct commands *)b)->name);
} }
static struct commands * static const struct commands *
find_internal_command (char *name) find_internal_command (char *name)
{ {
/* the "-1" is to skip the NULL terminator */ /* the "-1" is to skip the NULL terminator */
@@ -4205,7 +4363,7 @@ usercommand_show_help (session *sess, char *name)
static void static void
help (session *sess, char *tbuf, char *helpcmd, int quiet) help (session *sess, char *tbuf, char *helpcmd, int quiet)
{ {
struct commands *cmd; const struct commands *cmd;
if (plugin_show_help (sess, helpcmd)) if (plugin_show_help (sess, helpcmd))
return; return;
@@ -4397,7 +4555,7 @@ void
check_special_chars (char *cmd, int do_ascii) /* check for %X */ check_special_chars (char *cmd, int do_ascii) /* check for %X */
{ {
int occur = 0; int occur = 0;
int len = strlen (cmd); size_t len = strlen (cmd);
char *buf, *utf; char *buf, *utf;
char tbuf[4]; char tbuf[4];
int i = 0, j = 0; int i = 0, j = 0;
@@ -4649,8 +4807,9 @@ 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)))
{ {
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick, if (!sess->server->have_echo_message)
split_text, TRUE, FALSE, &no_tags); inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
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);
if (*split_text) if (*split_text)
@@ -4659,7 +4818,8 @@ handle_say (session *sess, char *text, int check_spch)
g_free (split_text); g_free (split_text);
} }
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick, if (!sess->server->have_echo_message)
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);
} else } else
@@ -4708,9 +4868,9 @@ command_insert_vars (session *sess, char *cmd)
break; break;
case 'p': case 'p':
if (mynet->pass) if (sess->server->password[0])
{ {
g_string_append (expanded, mynet->pass); g_string_append (expanded, sess->server->password);
} }
cmd++; cmd++;
break; break;
@@ -4763,7 +4923,7 @@ handle_command (session *sess, char *cmd, int check_spch)
char *word[PDIWORDS+1]; char *word[PDIWORDS+1];
char *word_eol[PDIWORDS+1]; char *word_eol[PDIWORDS+1];
static int command_level = 0; static int command_level = 0;
struct commands *int_cmd; const struct commands *int_cmd;
char *pdibuf; char *pdibuf;
char *tbuf; char *tbuf;
int len; int len;
@@ -4883,6 +5043,9 @@ xit:
static int static int
handle_user_input (session *sess, char *text, int history, int nocommand) handle_user_input (session *sess, char *text, int history, int nocommand)
{ {
char cmd_char = prefs.hex_input_command_char[0];
unsigned int i;
if (*text == '\0') if (*text == '\0')
return 1; return 1;
@@ -4890,51 +5053,23 @@ handle_user_input (session *sess, char *text, int history, int nocommand)
history_add (&sess->history, text); history_add (&sess->history, text);
/* is it NOT a command, just text? */ /* is it NOT a command, just text? */
if (nocommand || text[0] != prefs.hex_input_command_char[0]) if (nocommand || text[0] != cmd_char)
{ {
handle_say (sess, text, TRUE); handle_say (sess, text, TRUE);
return 1; return 1;
} }
/* check for // */ for (i = 1; text[i]; i++)
if (text[0] == prefs.hex_input_command_char[0] && text[1] == prefs.hex_input_command_char[0])
{ {
handle_say (sess, text + 1, TRUE); if (text[i] == cmd_char)
return 1; {
handle_say (sess, text, TRUE);
return 1;
}
if (text[i] == ' ')
break;
} }
#if 0 /* Who would remember all this? */
if (prefs.hex_input_command_char[0] == '/')
{
int i;
const char *unix_dirs [] = {
"/bin/",
"/boot/",
"/dev/",
"/etc/",
"/home/",
"/lib/",
"/lost+found/",
"/mnt/",
"/opt/",
"/proc/",
"/root/",
"/sbin/",
"/tmp/",
"/usr/",
"/var/",
"/gnome/",
NULL
};
for (i = 0; unix_dirs[i] != NULL; i++)
if (strncmp (text, unix_dirs[i], strlen (unix_dirs[i]))==0)
{
handle_say (sess, text, TRUE);
return 1;
}
}
#endif
return handle_command (sess, text + 1, TRUE); return handle_command (sess, text + 1, TRUE);
} }

View File

@@ -25,6 +25,7 @@
#ifdef WIN32 #ifdef WIN32
#include <io.h> #include <io.h>
#include <windows.h>
#else #else
#include <unistd.h> #include <unistd.h>
#endif #endif
@@ -56,6 +57,36 @@ typedef struct session zoitechat_context;
#endif #endif
#define DEBUG(x) {x;} #define DEBUG(x) {x;}
#ifdef WIN32
/* Python >= 3.9 dropped support for Windows versions before 10, so those
* systems get a Python 3.8 build of the plugin instead. RtlGetVersion is
* used because GetVersionEx reports a version capped by the compatibility
* manifest rather than the real one. */
static gboolean
plugin_windows_needs_python38 (void)
{
typedef LONG (WINAPI *rtl_get_version_func) (PRTL_OSVERSIONINFOW);
rtl_get_version_func rtl_get_version;
RTL_OSVERSIONINFOW version;
HMODULE ntdll;
ntdll = GetModuleHandleW (L"ntdll.dll");
if (ntdll == NULL)
return FALSE;
rtl_get_version = (rtl_get_version_func) GetProcAddress (ntdll, "RtlGetVersion");
if (rtl_get_version == NULL)
return FALSE;
memset (&version, 0, sizeof (version));
version.dwOSVersionInfoSize = sizeof (version);
if (rtl_get_version (&version) != 0)
return FALSE;
return version.dwMajorVersion < 10;
}
#endif
/* crafted to be an even 32 bytes */ /* crafted to be an even 32 bytes */
struct _zoitechat_hook struct _zoitechat_hook
@@ -491,9 +522,11 @@ plugin_auto_load (session *sess)
for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb); for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb);
for_files(lib_dir, "hclua.dll", plugin_auto_load_cb); for_files(lib_dir, "hclua.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb); for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb); if (plugin_windows_needs_python38 ())
for_files (lib_dir, "hcpython38.dll", plugin_auto_load_cb);
else
for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcupd.dll", plugin_auto_load_cb); for_files (lib_dir, "hcupd.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcwinamp.dll", plugin_auto_load_cb);
for_files (lib_dir, "hcsysinfo.dll", plugin_auto_load_cb); for_files (lib_dir, "hcsysinfo.dll", plugin_auto_load_cb);
#else #else
for_files (lib_dir, "*."PLUGIN_SUFFIX, plugin_auto_load_cb); for_files (lib_dir, "*."PLUGIN_SUFFIX, plugin_auto_load_cb);
@@ -1214,8 +1247,6 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
case 0x4889ba9b: /* password */ case 0x4889ba9b: /* password */
case 0x438fdf9: /* nickserv */ case 0x438fdf9: /* nickserv */
if (sess->server->network)
return ((ircnet *)sess->server->network)->pass;
return NULL; return NULL;
case 0xca022f43: /* server */ case 0xca022f43: /* server */

View File

@@ -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)
@@ -1017,7 +1063,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
char *account; char *account;
char ip[128], nick[NICKLEN]; char ip[128], nick[NICKLEN];
char *text, *ex; char *text, *ex;
int len = strlen (type); size_t len = strlen (type);
/* fill in the "ip" and "nick" buffers */ /* fill in the "ip" and "nick" buffers */
ex = strchr (word[1], '!'); ex = strchr (word[1], '!');
@@ -1322,7 +1368,20 @@ 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;
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data); 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);
}
} }
} }
} }
@@ -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';
raw_value++;
value = message_tag_unescape (raw_value);
}
if (serv->have_account_tag && !strcmp (key, "account")) if (serv->have_account_tag && !strcmp (key, "account"))
tags_data->account = g_strdup (value); {
g_free (tags_data->account);
if (serv->have_idmsg && strcmp (key, "solanum.chat/identified")) tags_data->account = value;
value = NULL;
}
else if (serv->have_idmsg && !strcmp (key, "solanum.chat/identified"))
{
tags_data->identified = TRUE; 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;
}
if (serv->have_server_time && !strcmp (key, "time")) g_free (value);
handle_message_tag_time (value, tags_data);
} }
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;

View File

@@ -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);

173
src/common/secretstore.c Normal file
View File

@@ -0,0 +1,173 @@
#include "zoitechat.h"
#include "cfgfiles.h"
#include "secretstore.h"
#include <string.h>
#ifdef WIN32
#include <windows.h>
#include <wincred.h>
#endif
#ifdef HAVE_LIBSECRET
#include <libsecret/secret.h>
#endif
static char *secretstore_target (const char *network_name)
{
return g_strdup_printf ("zoitechat/network/%s", network_name ? network_name : "default");
}
int secretstore_is_keyring_available (void)
{
#ifdef WIN32
return TRUE;
#elif defined(HAVE_LIBSECRET)
return TRUE;
#else
return FALSE;
#endif
}
char *secretstore_get_network_password (const char *network_name)
{
char *target;
target = secretstore_target (network_name);
#ifdef WIN32
{
PCREDENTIALA cred = NULL;
char *ret = NULL;
if (CredReadA (target, CRED_TYPE_GENERIC, 0, &cred))
{
ret = g_strndup ((const char *) cred->CredentialBlob, cred->CredentialBlobSize);
CredFree (cred);
}
g_free (target);
return ret;
}
#elif defined(HAVE_LIBSECRET)
{
static const SecretSchema schema = {
"net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE,
{
{ "network", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 },
}
};
char *ret = secret_password_lookup_sync (&schema, NULL, NULL, "network", target, NULL);
g_free (target);
return ret;
}
#else
g_free (target);
return NULL;
#endif
}
int secretstore_set_network_password (const char *network_name, const char *password)
{
char *target;
target = secretstore_target (network_name);
#ifdef WIN32
{
CREDENTIALA cred;
memset (&cred, 0, sizeof (cred));
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = target;
cred.CredentialBlobSize = (DWORD) strlen (password);
cred.CredentialBlob = (LPBYTE) password;
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
cred.UserName = "zoitechat";
if (!CredWriteA (&cred, 0))
{
g_free (target);
return FALSE;
}
g_free (target);
return TRUE;
}
#elif defined(HAVE_LIBSECRET)
{
static const SecretSchema schema = {
"net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE,
{
{ "network", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 },
}
};
gboolean ok = secret_password_store_sync (&schema, SECRET_COLLECTION_DEFAULT,
"ZoiteChat network password", password, NULL, NULL, "network", target, NULL);
g_free (target);
return ok;
}
#else
g_free (target);
return FALSE;
#endif
}
int secretstore_delete_network_password (const char *network_name)
{
char *target;
target = secretstore_target (network_name);
#ifdef WIN32
{
gboolean ok = CredDeleteA (target, CRED_TYPE_GENERIC, 0);
g_free (target);
return ok;
}
#elif defined(HAVE_LIBSECRET)
{
static const SecretSchema schema = {
"net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE,
{
{ "network", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 },
}
};
gboolean ok = secret_password_clear_sync (&schema, NULL, NULL, "network", target, NULL);
g_free (target);
return ok;
}
#else
g_free (target);
return FALSE;
#endif
}
int secretstore_require_unlock (const char *network_name)
{
#ifdef WIN32
return TRUE;
#elif defined(HAVE_LIBSECRET)
{
static const SecretSchema schema = {
"net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE,
{
{ "network", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 },
}
};
char *target;
char *password;
GError *error = NULL;
target = secretstore_target (network_name);
password = secret_password_lookup_sync (&schema, NULL, &error, "network", target, NULL);
g_free (target);
if (password)
{
memset (password, 0, strlen (password));
g_free (password);
return TRUE;
}
if (error)
{
g_error_free (error);
return FALSE;
}
return TRUE;
}
#else
return TRUE;
#endif
}

10
src/common/secretstore.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef ZOITECHAT_SECRETSTORE_H
#define ZOITECHAT_SECRETSTORE_H
char *secretstore_get_network_password (const char *network_name);
int secretstore_set_network_password (const char *network_name, const char *password);
int secretstore_delete_network_password (const char *network_name);
int secretstore_is_keyring_available (void);
int secretstore_require_unlock (const char *network_name);
#endif

View File

@@ -522,7 +522,7 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
X509 *current_cert = X509_STORE_CTX_get_current_cert (ctx); X509 *current_cert = X509_STORE_CTX_get_current_cert (ctx);
if (!current_cert) if (!current_cert)
return TRUE; return ok;
X509_NAME_oneline (X509_get_subject_name (current_cert), X509_NAME_oneline (X509_get_subject_name (current_cert),
subject, sizeof (subject)); subject, sizeof (subject));
@@ -534,13 +534,24 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
g_snprintf (buf, sizeof (buf), "* Issuer: %s", issuer); g_snprintf (buf, sizeof (buf), "* Issuer: %s", issuer);
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0); EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
return TRUE; if (!ok)
{
int err = X509_STORE_CTX_get_error (ctx);
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
X509_verify_cert_error_string (err), err);
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
if (g_sess && g_sess->server->accept_invalid_cert)
return 1;
}
return ok;
} }
static int static int
ssl_do_connect (server * serv) ssl_do_connect (server * serv)
{ {
char buf[256]; // ERR_error_string() MUST have this size char buf[256];
g_sess = serv->server_session; g_sess = serv->server_session;
@@ -559,9 +570,10 @@ ssl_do_connect (server * serv)
if (SSL_connect (serv->ssl) <= 0) if (SSL_connect (serv->ssl) <= 0)
{ {
char err_buf[128]; char err_buf[128];
int err; int err, ssl_err;
g_sess = NULL; g_sess = NULL;
ssl_err = SSL_get_error (serv->ssl, -1);
if ((err = ERR_get_error ()) > 0) if ((err = ERR_get_error ()) > 0)
{ {
ERR_error_string (err, err_buf); ERR_error_string (err, err_buf);
@@ -571,6 +583,8 @@ ssl_do_connect (server * serv)
if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER) if (ERR_GET_REASON (err) == SSL_R_WRONG_VERSION_NUMBER)
PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n")); PrintText (serv->server_session, _("Are you sure this is a SSL capable server and port?\n"));
else if (ssl_err == SSL_ERROR_SSL)
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, "* TLS handshake rejected by protocol/certificate/cipher policy", NULL, NULL, NULL, 0);
server_cleanup (serv); server_cleanup (serv);
@@ -649,30 +663,18 @@ ssl_do_connect (server * serv)
int hostname_err; int hostname_err;
if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0) if ((hostname_err = _SSL_check_hostname(cert, serv->hostname)) != 0)
{ {
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname? (%d)%s", g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
hostname_err, serv->accept_invalid_cert ? " -- Ignored" : ""); hostname_err);
if (serv->accept_invalid_cert) EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0); if (!serv->accept_invalid_cert)
else
goto conn_fail; goto conn_fail;
} }
break; break;
} }
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
case X509_V_ERR_CERT_HAS_EXPIRED:
if (serv->accept_invalid_cert)
{
g_snprintf (buf, sizeof (buf), "* Verify E: %s.? (%d) -- Ignored",
X509_verify_cert_error_string (verify_error),
verify_error);
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL,
NULL, 0);
break;
}
default: default:
if (serv->accept_invalid_cert)
break;
g_snprintf (buf, sizeof (buf), "%s.? (%d)", g_snprintf (buf, sizeof (buf), "%s.? (%d)",
X509_verify_cert_error_string (verify_error), X509_verify_cert_error_string (verify_error),
verify_error); verify_error);
@@ -881,7 +883,7 @@ server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
if (prefs.hex_net_auto_reconnectonfail) if (prefs.hex_net_auto_reconnectonfail)
auto_reconnect (serv, FALSE, -1); auto_reconnect (serv, FALSE, -1);
break; break;
case '3': /* gethostbyname finished */ case '3': /* resolver finished */
waitline2 (source, host, sizeof host); waitline2 (source, host, sizeof host);
waitline2 (source, ip, sizeof ip); waitline2 (source, ip, sizeof ip);
waitline2 (source, outbuf, sizeof outbuf); waitline2 (source, outbuf, sizeof outbuf);
@@ -930,9 +932,9 @@ 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': /* gethostbyname (prefs.hex_net_bind_host) failed */ case '7': /* prefs.hex_net_bind_host resolve failed */
sprintf (outbuf, sprintf (outbuf,
_("Cannot resolve hostname %s\nCheck your IP Settings!\n"), _("Cannot resolve hostname %s\nCheck your IP Settings!\n"),
prefs.hex_net_bind_host); prefs.hex_net_bind_host);
@@ -1104,7 +1106,7 @@ traverse_socks (int print_fd, int sok, char *serverAddr, int port)
sc.version = 4; sc.version = 4;
sc.type = 1; sc.type = 1;
sc.port = htons (port); sc.port = htons (port);
sc.address = inet_addr (serverAddr); net_parse_ipv4 (serverAddr, &sc.address);
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username)); g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0); send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
@@ -1819,6 +1821,7 @@ void
server_set_defaults (server *serv) server_set_defaults (server *serv)
{ {
g_free (serv->chantypes); g_free (serv->chantypes);
g_clear_pointer (&serv->clienttagdeny, g_free);
g_free (serv->chanmodes); g_free (serv->chanmodes);
g_free (serv->nick_prefixes); g_free (serv->nick_prefixes);
g_free (serv->nick_modes); g_free (serv->nick_modes);
@@ -1853,6 +1856,8 @@ server_set_defaults (server *serv)
serv->have_extjoin = FALSE; serv->have_extjoin = FALSE;
serv->have_account_tag = FALSE; serv->have_account_tag = FALSE;
serv->have_server_time = FALSE; serv->have_server_time = FALSE;
serv->have_message_tags = FALSE;
serv->have_echo_message = FALSE;
serv->have_sasl = FALSE; serv->have_sasl = FALSE;
serv->have_except = FALSE; serv->have_except = FALSE;
serv->have_invite = FALSE; serv->have_invite = FALSE;
@@ -1983,6 +1988,7 @@ server_free (server *serv)
g_free (serv->nick_prefixes); g_free (serv->nick_prefixes);
g_free (serv->chanmodes); g_free (serv->chanmodes);
g_free (serv->chantypes); g_free (serv->chantypes);
g_free (serv->clienttagdeny);
g_free (serv->bad_nick_prefixes); g_free (serv->bad_nick_prefixes);
g_free (serv->last_away_reason); g_free (serv->last_away_reason);
g_free (serv->encoding); g_free (serv->encoding);

View File

@@ -33,9 +33,152 @@
#include "text.h" #include "text.h"
#include "util.h" /* token_foreach */ #include "util.h" /* token_foreach */
#include "zoitechatc.h" #include "zoitechatc.h"
#include "secretstore.h"
#include "servlist.h" #include "servlist.h"
#ifdef USE_OPENSSL
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif
char *
servlist_password_encrypt_for_storage (const char *pass)
{
gchar *material;
unsigned char salt[16];
unsigned char iv[16];
unsigned char key[32];
unsigned char *ciphertext;
int outlen1;
int outlen2;
int inlen;
int cipherlen;
EVP_CIPHER_CTX *ctx;
char *b64;
char *ret;
if (!pass || !*pass)
return NULL;
#ifdef USE_OPENSSL
if (RAND_bytes (salt, sizeof (salt)) != 1 || RAND_bytes (iv, sizeof (iv)) != 1)
return NULL;
material = g_strdup_printf ("%s|%s", g_get_user_name (), get_xdir ());
if (!PKCS5_PBKDF2_HMAC (material, -1, salt, sizeof (salt), 300000, EVP_sha256 (), sizeof (key), key))
{
g_free (material);
return NULL;
}
g_free (material);
inlen = (int) strlen (pass);
ciphertext = g_malloc (inlen + EVP_MAX_BLOCK_LENGTH);
ctx = EVP_CIPHER_CTX_new ();
if (!ctx)
{
memset (key, 0, sizeof (key));
g_free (ciphertext);
return NULL;
}
if (EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc (), NULL, key, iv) != 1 ||
EVP_EncryptUpdate (ctx, ciphertext, &outlen1, (const unsigned char *) pass, inlen) != 1 ||
EVP_EncryptFinal_ex (ctx, ciphertext + outlen1, &outlen2) != 1)
{
EVP_CIPHER_CTX_free (ctx);
memset (key, 0, sizeof (key));
g_free (ciphertext);
return NULL;
}
EVP_CIPHER_CTX_free (ctx);
cipherlen = outlen1 + outlen2;
{
gsize payload_len = sizeof (salt) + sizeof (iv) + (gsize) cipherlen;
unsigned char *payload = g_malloc (payload_len);
memcpy (payload, salt, sizeof (salt));
memcpy (payload + sizeof (salt), iv, sizeof (iv));
memcpy (payload + sizeof (salt) + sizeof (iv), ciphertext, cipherlen);
b64 = g_base64_encode (payload, payload_len);
memset (payload, 0, payload_len);
g_free (payload);
}
memset (key, 0, sizeof (key));
memset (ciphertext, 0, inlen + EVP_MAX_BLOCK_LENGTH);
g_free (ciphertext);
#else
b64 = g_base64_encode ((const guchar *) pass, strlen (pass));
#endif
ret = g_strdup_printf ("enc:%s", b64);
g_free (b64);
return ret;
}
gboolean
servlist_password_is_encrypted (const char *pass)
{
return pass && g_str_has_prefix (pass, "enc:");
}
char *
servlist_password_decrypt_for_storage (const char *enc)
{
guchar *raw;
gsize len;
char *ret;
if (!enc || !*enc)
return NULL;
if (!g_str_has_prefix (enc, "enc:"))
return g_strdup (enc);
raw = g_base64_decode (enc + 4, &len);
#ifdef USE_OPENSSL
if (len <= 32)
{
g_free (raw);
return NULL;
}
{
unsigned char *salt = raw;
unsigned char *iv = raw + 16;
unsigned char *ciphertext = raw + 32;
int cipherlen = (int) (len - 32);
unsigned char key[32];
gchar *material = g_strdup_printf ("%s|%s", g_get_user_name (), get_xdir ());
unsigned char *plaintext = g_malloc ((gsize) cipherlen + 1);
int outlen1;
int outlen2;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
if (!ctx || !PKCS5_PBKDF2_HMAC (material, -1, salt, 16, 300000, EVP_sha256 (), sizeof (key), key))
{
g_free (material);
if (ctx)
EVP_CIPHER_CTX_free (ctx);
g_free (plaintext);
g_free (raw);
return NULL;
}
g_free (material);
if (EVP_DecryptInit_ex (ctx, EVP_aes_256_cbc (), NULL, key, iv) != 1 ||
EVP_DecryptUpdate (ctx, plaintext, &outlen1, ciphertext, cipherlen) != 1 ||
EVP_DecryptFinal_ex (ctx, plaintext + outlen1, &outlen2) != 1)
{
EVP_CIPHER_CTX_free (ctx);
memset (key, 0, sizeof (key));
memset (plaintext, 0, (gsize) cipherlen + 1);
g_free (plaintext);
g_free (raw);
return NULL;
}
EVP_CIPHER_CTX_free (ctx);
memset (key, 0, sizeof (key));
plaintext[outlen1 + outlen2] = 0;
ret = g_strdup ((const char *) plaintext);
memset (plaintext, 0, (gsize) cipherlen + 1);
g_free (plaintext);
}
#else
ret = g_strndup ((const char *) raw, len);
#endif
g_free (raw);
return ret;
}
struct defaultserver struct defaultserver
{ {
@@ -344,10 +487,29 @@ servlist_connect (session *sess, ircnet *net, gboolean join)
} }
serv->password[0] = 0; serv->password[0] = 0;
if ((net->flags & FLAG_USE_KEYRING) && net->name)
if (net->pass)
{ {
safe_strcpy (serv->password, net->pass, sizeof (serv->password)); char *stored_pass = secretstore_get_network_password (net->name);
if (stored_pass && *stored_pass)
{
safe_strcpy (serv->password, stored_pass, sizeof (serv->password));
}
if (stored_pass)
{
memset (stored_pass, 0, strlen (stored_pass));
g_free (stored_pass);
}
}
else if (net->pass)
{
char *plain = servlist_password_decrypt_for_storage (net->pass);
if (plain && *plain)
safe_strcpy (serv->password, plain, sizeof (serv->password));
if (plain)
{
memset (plain, 0, strlen (plain));
g_free (plain);
}
} }
if (net->flags & FLAG_USE_GLOBAL) if (net->flags & FLAG_USE_GLOBAL)
@@ -982,24 +1144,6 @@ servlist_load (void)
* *
* Should be removed at some point. * Should be removed at some point.
*/ */
case 'A':
if (!net->pass)
{
net->pass = g_strdup (buf + 2);
if (!net->logintype)
{
net->logintype = LOGIN_SASL;
}
}
case 'B':
if (!net->pass)
{
net->pass = g_strdup (buf + 2);
if (!net->logintype)
{
net->logintype = LOGIN_NICKSERV;
}
}
} }
} }
if (buf[0] == 'N') if (buf[0] == 'N')

View File

@@ -62,7 +62,8 @@ extern GSList *network_list;
#define FLAG_USE_PROXY 16 #define FLAG_USE_PROXY 16
#define FLAG_ALLOW_INVALID 32 #define FLAG_ALLOW_INVALID 32
#define FLAG_FAVORITE 64 #define FLAG_FAVORITE 64
#define FLAG_COUNT 7 #define FLAG_USE_KEYRING 128
#define FLAG_COUNT 8
/* Login methods. Use server password by default - if we had a NickServ password, it'd be set to 2 already by servlist_load() */ /* Login methods. Use server password by default - if we had a NickServ password, it'd be set to 2 already by servlist_load() */
#define LOGIN_DEFAULT_REAL LOGIN_PASS /* this is to set the default login method for unknown servers */ #define LOGIN_DEFAULT_REAL LOGIN_PASS /* this is to set the default login method for unknown servers */
@@ -124,5 +125,8 @@ favchannel *servlist_favchan_copy (favchannel *fav);
GSList *servlist_favchan_listadd (GSList *chanlist, char *channel, char *key); GSList *servlist_favchan_listadd (GSList *chanlist, char *channel, char *key);
gboolean joinlist_is_in_list (server *serv, char *channel); gboolean joinlist_is_in_list (server *serv, char *channel);
char *servlist_password_encrypt_for_storage (const char *pass);
char *servlist_password_decrypt_for_storage (const char *pass);
gboolean servlist_password_is_encrypted (const char *pass);
#endif #endif

View File

@@ -86,15 +86,17 @@ _SSL_context_init (void (*info_cb_func))
SSLeay_add_ssl_algorithms (); SSLeay_add_ssl_algorithms ();
SSL_load_error_strings (); SSL_load_error_strings ();
ctx = SSL_CTX_new (SSLv23_client_method ()); ctx = SSL_CTX_new (TLS_client_method ());
SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH); SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
SSL_CTX_set_timeout (ctx, 300); SSL_CTX_set_timeout (ctx, 300);
SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3 SSL_CTX_set_options (ctx, SSL_OP_NO_COMPRESSION
|SSL_OP_NO_COMPRESSION
|SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE |SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE
|SSL_OP_NO_TICKET |SSL_OP_NO_TICKET
|SSL_OP_CIPHER_SERVER_PREFERENCE); |SSL_OP_NO_RENEGOTIATION);
SSL_CTX_set_min_proto_version (ctx, TLS1_2_VERSION);
SSL_CTX_set_cipher_list (ctx, "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!DSS");
SSL_CTX_set_ciphersuites (ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256");
#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */ #if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
@@ -152,7 +154,7 @@ int
_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl) _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
{ {
X509 *peer_cert; X509 *peer_cert;
X509_PUBKEY *key; const X509_PUBKEY *key;
X509_ALGOR *algor = NULL; X509_ALGOR *algor = NULL;
EVP_PKEY *peer_pkey; EVP_PKEY *peer_pkey;
char notBefore[64]; char notBefore[64];
@@ -311,7 +313,7 @@ _SSL_socket (SSL_CTX *ctx, int sd)
#else #else
method = SSL_CTX_get_ssl_method (ctx); method = SSL_CTX_get_ssl_method (ctx);
#endif #endif
if (method == SSLv23_client_method()) if (method == TLS_client_method())
SSL_set_connect_state (ssl); SSL_set_connect_state (ssl);
else else
SSL_set_accept_state(ssl); SSL_set_accept_state(ssl);
@@ -348,14 +350,13 @@ _SSL_close (SSL * ssl)
{ {
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
SSL_free (ssl); SSL_free (ssl);
#if !defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER < 0x10100000L
#ifdef HAVE_ERR_REMOVE_THREAD_STATE #ifdef HAVE_ERR_REMOVE_THREAD_STATE
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L
/* OpenSSL handles this itself in 1.1+ and this is a no-op */
ERR_remove_thread_state (NULL); ERR_remove_thread_state (NULL);
#endif
#else #else
ERR_remove_state (0); ERR_remove_state (0);
#endif #endif
#endif
} }
/* Hostname validation code based on OpenBSD's libtls. */ /* Hostname validation code based on OpenBSD's libtls. */
@@ -506,9 +507,12 @@ _SSL_check_subject_altname (X509 *cert, const char *host)
static int static int
_SSL_check_common_name (X509 *cert, const char *host) _SSL_check_common_name (X509 *cert, const char *host)
{ {
X509_NAME *name; const X509_NAME *name;
char *common_name = NULL; const X509_NAME_ENTRY *entry;
const ASN1_STRING *common_name_asn1;
const unsigned char *common_name;
int common_name_len; int common_name_len;
int common_name_index;
int rv = -1; int rv = -1;
GInetAddress *addr; GInetAddress *addr;
@@ -516,16 +520,27 @@ _SSL_check_common_name (X509 *cert, const char *host)
if (name == NULL) if (name == NULL)
return -1; return -1;
common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0); common_name_index = X509_NAME_get_index_by_NID (name, NID_commonName, -1);
if (common_name_len < 0) if (common_name_index < 0)
return -1; return -1;
common_name = g_malloc0 (common_name_len + 1); entry = X509_NAME_get_entry (name, common_name_index);
if (entry == NULL)
return -1;
X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1); common_name_asn1 = X509_NAME_ENTRY_get_data (entry);
if (common_name_asn1 == NULL)
return -1;
#ifdef HAVE_ASN1_STRING_GET0_DATA
common_name = ASN1_STRING_get0_data (common_name_asn1);
#else
common_name = ASN1_STRING_data (common_name_asn1);
#endif
common_name_len = ASN1_STRING_length (common_name_asn1);
/* NUL bytes in CN? */ /* NUL bytes in CN? */
if (common_name_len != (int)strlen(common_name)) if (common_name_len != (int)strlen((const char *)common_name))
{ {
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n"); g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
rv = -2; rv = -2;
@@ -538,18 +553,17 @@ _SSL_check_common_name (X509 *cert, const char *host)
* We don't want to attempt wildcard matching against IP * We don't want to attempt wildcard matching against IP
* addresses, so perform a simple comparison here. * addresses, so perform a simple comparison here.
*/ */
if (g_strcmp0 (common_name, host) == 0) if (g_strcmp0 ((const char *)common_name, host) == 0)
rv = 0; rv = 0;
else else
rv = -1; rv = -1;
g_object_unref (addr); g_object_unref (addr);
} }
else if (_SSL_match_hostname (common_name, host) == 0) else if (_SSL_match_hostname ((const char *)common_name, host) == 0)
rv = 0; rv = 0;
out: out:
g_free(common_name);
return rv; return rv;
} }

View File

@@ -22,6 +22,7 @@
#include <ctype.h> #include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@@ -74,6 +75,18 @@ typedef struct
static bool string_builder_init (StringBuilder *builder); static bool string_builder_init (StringBuilder *builder);
static void string_builder_free (StringBuilder *builder); static void string_builder_free (StringBuilder *builder);
static bool string_builder_append (StringBuilder *builder, const char *text); static bool string_builder_append (StringBuilder *builder, const char *text);
static int size_to_int (size_t value);
static int
size_to_int (size_t value)
{
if (value > (size_t) INT_MAX)
{
return INT_MAX;
}
return (int) value;
}
char * char *
sysinfo_get_cpu (void) sysinfo_get_cpu (void)
@@ -511,6 +524,7 @@ static char *read_hdd_info (IWbemClassObject *object)
static char *bstr_to_utf8 (BSTR bstr) static char *bstr_to_utf8 (BSTR bstr)
{ {
int utf8_len; int utf8_len;
int wide_len;
char *utf8; char *utf8;
if (bstr == NULL) if (bstr == NULL)
@@ -518,7 +532,8 @@ static char *bstr_to_utf8 (BSTR bstr)
return NULL; return NULL;
} }
utf8_len = WideCharToMultiByte (CP_UTF8, 0, bstr, SysStringLen (bstr), NULL, 0, NULL, NULL); wide_len = size_to_int ((size_t) SysStringLen (bstr));
utf8_len = WideCharToMultiByte (CP_UTF8, 0, bstr, wide_len, NULL, 0, NULL, NULL);
if (utf8_len <= 0) if (utf8_len <= 0)
{ {
return NULL; return NULL;
@@ -530,7 +545,7 @@ static char *bstr_to_utf8 (BSTR bstr)
return NULL; return NULL;
} }
if (WideCharToMultiByte (CP_UTF8, 0, bstr, SysStringLen (bstr), utf8, utf8_len, NULL, NULL) <= 0) if (WideCharToMultiByte (CP_UTF8, 0, bstr, wide_len, utf8, utf8_len, NULL, NULL) <= 0)
{ {
free (utf8); free (utf8);
return NULL; return NULL;

View File

@@ -1695,6 +1695,97 @@ pevent_load (char *filename)
return 0; return 0;
} }
static gboolean
text_event_uses_color (const char *text, int color)
{
const char *p;
if (!text || color < 0 || color > 31)
return FALSE;
for (p = text; *p; p++)
{
int value;
int digits;
if ((unsigned char) *p == 3)
p++;
else if (*p == '%' && p[1] == 'C')
p += 2;
else
continue;
if (!g_ascii_isdigit (*p))
{
p--;
continue;
}
value = *p - '0';
digits = 1;
if (g_ascii_isdigit (p[1]))
{
value = value * 10 + p[1] - '0';
digits = 2;
}
if (value == color)
return TRUE;
p += digits - 1;
if (*p == ',' && g_ascii_isdigit (p[1]))
{
p++;
value = *p - '0';
if (g_ascii_isdigit (p[1]))
{
value = value * 10 + p[1] - '0';
p++;
}
if (value == color)
return TRUE;
}
}
return FALSE;
}
char **
text_color_event_names (int color, int *count)
{
GPtrArray *names;
int found = 0;
int i;
if (count)
*count = 0;
names = g_ptr_array_new ();
for (i = 0; i < NUM_XP; i++)
{
const char *text = pntevts_text[i] ? pntevts_text[i] : te[i].def;
if (!text_event_uses_color (text, color))
continue;
g_ptr_array_add (names, g_strdup (_(te[i].name)));
found++;
}
if (!found)
{
g_ptr_array_free (names, TRUE);
return NULL;
}
g_ptr_array_add (names, NULL);
if (count)
*count = found;
return (char **) g_ptr_array_free (names, FALSE);
}
static void static void
pevent_check_all_loaded (void) pevent_check_all_loaded (void)
{ {
@@ -1809,12 +1900,97 @@ format_event (session *sess, int index, char **args, char *o, gsize sizeofo, uns
o[0] = 0; o[0] = 0;
} }
static char *
text_event_without_hostmask_format (const char *format, int host_arg)
{
char token[3];
const char *arg;
const char *open;
const char *close;
const char *start;
char *out;
gsize prefix_len;
g_snprintf (token, sizeof (token), "$%d", host_arg);
arg = strstr (format, token);
if (!arg)
return NULL;
open = arg;
while (open > format && *open != '(' && *open != '\n')
open--;
close = arg + strlen (token);
while (*close && *close != ')' && *close != '\n')
close++;
if (*open != '(' || *close != ')')
return NULL;
start = open;
if (start > format && start[-1] == ' ')
start--;
prefix_len = start - format;
out = g_malloc (prefix_len + strlen (close + 1) + 1);
memcpy (out, format, prefix_len);
strcpy (out + prefix_len, close + 1);
return out;
}
static void
display_event_string (session *sess, int event, char **args, char *format,
unsigned int stripcolor_args, time_t timestamp)
{
char *compiled;
char *saved;
char o[4096];
int max_arg;
if (pevt_build_string (format, &compiled, &max_arg) != 0)
return;
saved = pntevts[event];
pntevts[event] = compiled;
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
pntevts[event] = saved;
g_free (compiled);
if (o[0])
PrintTextTimeStamp (sess, o, timestamp);
}
static void static void
display_event (session *sess, int event, char **args, display_event (session *sess, int event, char **args,
unsigned int stripcolor_args, time_t timestamp) unsigned int stripcolor_args, time_t timestamp)
{ {
char o[4096]; char o[4096];
format_event (sess, event, args, o, sizeof (o), stripcolor_args); char *format;
char *host;
int host_arg;
if (prefs.hex_irc_hide_join_part_hostmask &&
(event == XP_TE_JOIN || event == XP_TE_PART || event == XP_TE_PARTREASON))
{
host_arg = event == XP_TE_JOIN ? 3 : 2;
format = text_event_without_hostmask_format (pntevts_text[event], host_arg);
if (format)
{
display_event_string (sess, event, args, format, stripcolor_args, timestamp);
g_free (format);
return;
}
host = args[host_arg];
args[host_arg] = "";
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
args[host_arg] = host;
}
else
{
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
}
if (o[0]) if (o[0])
PrintTextTimeStamp (sess, o, timestamp); PrintTextTimeStamp (sess, o, timestamp);
} }

View File

@@ -53,6 +53,7 @@ int pevt_build_string (const char *input, char **output, int *max_arg);
int pevent_load (char *filename); int pevent_load (char *filename);
void pevent_make_pntevts (void); void pevent_make_pntevts (void);
int text_color_of (char *name); int text_color_of (char *name);
char **text_color_event_names (int color, int *count);
void text_emit (int index, session *sess, char *a, char *b, char *c, char *d, void text_emit (int index, session *sess, char *a, char *b, char *c, char *d,
time_t timestamp); time_t timestamp);
int text_emit_by_name (char *name, session *sess, time_t timestamp, int text_emit_by_name (char *name, session *sess, time_t timestamp,

File diff suppressed because it is too large Load Diff

View File

@@ -20,12 +20,14 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <glib.h>
#include "zoitechat.h" #include "zoitechat.h"
#include "zoitechatc.h" #include "zoitechatc.h"
#include "cfgfiles.h" #include "cfgfiles.h"
#include "fe.h" #include "fe.h"
#include "tree.h" #include "tree.h"
#include "url.h" #include "url.h"
#include "public_suffix_data.h"
#ifdef HAVE_STRINGS_H #ifdef HAVE_STRINGS_H
#include <strings.h> #include <strings.h>
#endif #endif
@@ -35,6 +37,7 @@ GTree *url_btree = NULL;
static gboolean regex_match (const GRegex *re, const char *word, static gboolean regex_match (const GRegex *re, const char *word,
int *start, int *end); int *start, int *end);
static const GRegex *re_url (void); static const GRegex *re_url (void);
static const GRegex *re_url_no_scheme (void);
static const GRegex *re_email (void); static const GRegex *re_email (void);
static const GRegex *re_nick (void); static const GRegex *re_nick (void);
static const GRegex *re_channel (void); static const GRegex *re_channel (void);
@@ -42,6 +45,8 @@ static gboolean match_nick (const char *word, int *start, int *end);
static gboolean match_channel (const char *word, int *start, int *end); static gboolean match_channel (const char *word, int *start, int *end);
static gboolean match_url (const char *word, int *start, int *end); static gboolean match_url (const char *word, int *start, int *end);
static gboolean match_email (const char *word, int *start, int *end); static gboolean match_email (const char *word, int *start, int *end);
static gboolean host_has_public_suffix (const char *host);
static gboolean host_has_public_suffix_range (const char *word, int start, int end);
static int static int
url_free (char *url, void *data) url_free (char *url, void *data)
@@ -266,7 +271,16 @@ match_channel (const char *word, int *start, int *end)
static gboolean static gboolean
match_url (const char *word, int *start, int *end) match_url (const char *word, int *start, int *end)
{ {
return regex_match (re_url (), word, start, end); if (regex_match (re_url (), word, start, end))
return TRUE;
if (!regex_match (re_url_no_scheme (), word, start, end))
return FALSE;
if (*start > 0 && word[*start - 1] == '@')
return FALSE;
return host_has_public_suffix_range (word, *start, *end);
} }
static gboolean static gboolean
@@ -307,7 +321,7 @@ url_check_line (char *buf)
for (i = 0; i < ARRAY_SIZE (commands); i++) for (i = 0; i < ARRAY_SIZE (commands); i++)
{ {
char *cmd = commands[i]; char *cmd = commands[i];
int len = strlen (cmd); size_t len = strlen (cmd);
if (strncmp (cmd, po, len) == 0) if (strncmp (cmd, po, len) == 0)
{ {
@@ -393,6 +407,114 @@ regex_match (const GRegex *re, const char *word, int *start, int *end)
return found; return found;
} }
static gboolean
host_has_public_suffix_range (const char *word, int start, int end)
{
char *candidate;
const char *host_start;
const char *host_end;
const char *host_colon;
gboolean ok;
int host_len;
char *host;
candidate = g_strndup (word + start, end - start);
host_start = candidate;
host_end = candidate + strlen (candidate);
if (*host_start == '[')
{
g_free (candidate);
return FALSE;
}
host_colon = strchr (host_start, ':');
if (host_colon)
host_end = host_colon;
host_colon = strchr (host_start, '/');
if (host_colon && host_colon < host_end)
host_end = host_colon;
host_len = (int)(host_end - host_start);
if (host_len <= 0)
{
g_free (candidate);
return FALSE;
}
host = g_strndup (host_start, host_len);
ok = host_has_public_suffix (host);
g_free (host);
g_free (candidate);
return ok;
}
static GHashTable *
public_suffix_table (void)
{
static GHashTable *table = NULL;
unsigned int i;
if (table)
return table;
table = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0; i < public_suffix_rules_len; i++)
{
g_hash_table_add (table, (gpointer)public_suffix_rules[i]);
}
return table;
}
static gboolean
host_has_public_suffix (const char *host)
{
GHashTable *table;
gchar **labels;
int i;
int n;
gboolean matched = FALSE;
if (!strchr (host, '.'))
return FALSE;
labels = g_strsplit (host, ".", -1);
for (n = 0; labels[n]; n++)
{
if (labels[n][0] == '\0')
{
g_strfreev (labels);
return FALSE;
}
}
table = public_suffix_table ();
for (i = 0; i < n; i++)
{
char *tail = g_strjoinv (".", &labels[i]);
if (g_hash_table_contains (table, tail))
matched = TRUE;
if (i + 1 < n)
{
char *tail_wild = g_strjoinv (".", &labels[i + 1]);
char *wild = g_strconcat ("*.", tail_wild, NULL);
if (g_hash_table_contains (table, wild))
matched = TRUE;
g_free (tail_wild);
g_free (wild);
}
if (i > 0)
{
char *exc = g_strconcat ("!", tail, NULL);
if (g_hash_table_contains (table, exc))
matched = TRUE;
g_free (exc);
}
g_free (tail);
if (matched)
break;
}
g_strfreev (labels);
return matched;
}
/* Miscellaneous description --- */ /* Miscellaneous description --- */
#define DOMAIN_LABEL "[\\pL\\pN](?:[-\\pL\\pN]{0,61}[\\pL\\pN])?" #define DOMAIN_LABEL "[\\pL\\pN](?:[-\\pL\\pN]{0,61}[\\pL\\pN])?"
#define DOMAIN DOMAIN_LABEL "(\\." DOMAIN_LABEL ")*" #define DOMAIN DOMAIN_LABEL "(\\." DOMAIN_LABEL ")*"
@@ -477,6 +599,28 @@ re_url (void)
return url_ret; return url_ret;
} }
static const GRegex *
re_url_no_scheme (void)
{
static GRegex *url_ret = NULL;
GString *grist_gstr;
char *grist;
if (url_ret) return url_ret;
grist_gstr = g_string_new (NULL);
g_string_append (grist_gstr, "(");
g_string_append (grist_gstr, HOST_URL_OPT_TLD OPT_PORT);
g_string_append_printf (grist_gstr, "(/" PATH ")?");
g_string_append (grist_gstr, ")");
grist = g_string_free (grist_gstr, FALSE);
url_ret = make_re (grist);
g_free (grist);
return url_ret;
}
#define EMAIL_LOCAL_ATOM "[\\pL\\pN!#$%&'*+/=?^_`{|}~-]+" #define EMAIL_LOCAL_ATOM "[\\pL\\pN!#$%&'*+/=?^_`{|}~-]+"
#define EMAIL_LOCAL EMAIL_LOCAL_ATOM "(\\." EMAIL_LOCAL_ATOM ")*" #define EMAIL_LOCAL EMAIL_LOCAL_ATOM "(\\." EMAIL_LOCAL_ATOM ")*"
#define EMAIL EMAIL_LOCAL "@" DOMAIN TLD #define EMAIL EMAIL_LOCAL "@" DOMAIN TLD

View File

@@ -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

View File

@@ -98,7 +98,7 @@ path_part (char *file, char *path, int pathlen)
char * /* like strstr(), but nocase */ char * /* like strstr(), but nocase */
nocasestrstr (const char *s, const char *wanted) nocasestrstr (const char *s, const char *wanted)
{ {
register const int len = strlen (wanted); register const size_t len = strlen (wanted);
if (len == 0) if (len == 0)
return (char *)s; return (char *)s;

View File

@@ -2,10 +2,10 @@
* Copyright (C) 1998-2010 Peter Zelezny. * Copyright (C) 1998-2010 Peter Zelezny.
* Copyright (C) 2009-2013 Berke Viktor. * Copyright (C) 2009-2013 Berke Viktor.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or
* it under the terms of the GNU General Public License as published by * modify it under the terms of the GNU General Public License
* the Free Software Foundation; either version 2 of the License, or * as published by the Free Software Foundation; either version 2
* (at your option) any later version. * of the License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,8 +13,8 @@
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software * along with this program; if not, see
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * <https://www.gnu.org/licenses/>.
*/ */
/* You can distribute this header with your plugins for easy compilation */ /* You can distribute this header with your plugins for easy compilation */

View File

@@ -379,6 +379,8 @@ lag_check (void)
char tbuf[128]; char tbuf[128];
time_t now = time (0); time_t now = time (0);
time_t lag; time_t lag;
time_t ping_age;
unsigned long ping_timeout;
tim = make_ping_time (); tim = make_ping_time ();
@@ -388,8 +390,11 @@ lag_check (void)
if (serv->connected && serv->end_of_motd) if (serv->connected && serv->end_of_motd)
{ {
lag = now - serv->ping_recv; lag = now - serv->ping_recv;
if (prefs.hex_net_ping_timeout != 0 && lag > prefs.hex_net_ping_timeout && lag > 0) ping_timeout = (unsigned long) prefs.hex_net_ping_timeout * 1000;
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0
&& tim - serv->lag_sent > ping_timeout)
{ {
lag = (tim - serv->lag_sent) / 1000;
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag); sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL, EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
NULL, NULL, 0); NULL, NULL, 0);
@@ -398,11 +403,11 @@ lag_check (void)
} }
else else
{ {
g_snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim); ping_age = now - serv->ping_recv;
serv->p_ping (serv, "", tbuf); if (!serv->lag_sent && prefs.hex_net_lag_check > 0 && ping_age >= prefs.hex_net_lag_check)
if (!serv->lag_sent)
{ {
g_snprintf (tbuf, sizeof (tbuf), "LAG%lu", tim);
serv->p_ping (serv, "", tbuf);
serv->lag_sent = tim; serv->lag_sent = tim;
fe_set_lag (serv, -1); fe_set_lag (serv, -1);
} }
@@ -525,7 +530,7 @@ zoitechat_reinit_timers (void)
if ((prefs.hex_net_ping_timeout != 0 || prefs.hex_gui_lagometer) if ((prefs.hex_net_ping_timeout != 0 || prefs.hex_gui_lagometer)
&& lag_check_tag == 0) && lag_check_tag == 0)
{ {
lag_check_tag = fe_timeout_add_seconds (30, zoitechat_lag_check, NULL); lag_check_tag = fe_timeout_add_seconds (1, zoitechat_lag_check, NULL);
} }
else if ((!prefs.hex_net_ping_timeout && !prefs.hex_gui_lagometer) else if ((!prefs.hex_net_ping_timeout && !prefs.hex_gui_lagometer)
&& lag_check_tag != 0) && lag_check_tag != 0)
@@ -792,7 +797,13 @@ session_free (session *killsess)
send_quit_or_part (killsess); send_quit_or_part (killsess);
if (killsess->typing_timeout_tag)
fe_timeout_remove (killsess->typing_timeout_tag);
if (killsess->typing_animation_tag)
fe_timeout_remove (killsess->typing_animation_tag);
history_free (&killsess->history); history_free (&killsess->history);
reply_cache_free (killsess);
g_free (killsess->topic); g_free (killsess->topic);
g_free (killsess->current_modes); g_free (killsess->current_modes);

View File

@@ -133,6 +133,7 @@ struct zoitechatprefs
unsigned int hex_gui_input_style; unsigned int hex_gui_input_style;
unsigned int hex_gui_join_dialog; unsigned int hex_gui_join_dialog;
unsigned int hex_gui_mode_buttons; unsigned int hex_gui_mode_buttons;
unsigned int hex_gui_mode_buttons_inline;
unsigned int hex_gui_quit_dialog; unsigned int hex_gui_quit_dialog;
/* unsigned int hex_gui_single; */ /* unsigned int hex_gui_single; */
unsigned int hex_gui_slist_fav; unsigned int hex_gui_slist_fav;
@@ -148,6 +149,7 @@ struct zoitechatprefs
unsigned int hex_gui_tab_sort; unsigned int hex_gui_tab_sort;
unsigned int hex_gui_tab_utils; unsigned int hex_gui_tab_utils;
unsigned int hex_gui_topicbar; unsigned int hex_gui_topicbar;
unsigned int hex_gui_topicbar_multiline;
unsigned int hex_gui_tray; unsigned int hex_gui_tray;
unsigned int hex_gui_tray_away; unsigned int hex_gui_tray_away;
unsigned int hex_gui_tray_blink; unsigned int hex_gui_tray_blink;
@@ -188,6 +190,7 @@ struct zoitechatprefs
unsigned int hex_irc_conf_mode; unsigned int hex_irc_conf_mode;
unsigned int hex_irc_hidehost; unsigned int hex_irc_hidehost;
unsigned int hex_irc_hide_nickchange; unsigned int hex_irc_hide_nickchange;
unsigned int hex_irc_hide_join_part_hostmask;
unsigned int hex_irc_hide_version; unsigned int hex_irc_hide_version;
unsigned int hex_irc_invisible; unsigned int hex_irc_invisible;
unsigned int hex_irc_logging; unsigned int hex_irc_logging;
@@ -265,7 +268,9 @@ struct zoitechatprefs
int hex_gui_search_pos; int hex_gui_search_pos;
int hex_gui_slist_select; int hex_gui_slist_select;
int hex_gui_tab_layout; int hex_gui_tab_layout;
int hex_gui_tab_closebuttons;
int hex_gui_tab_middleclose; int hex_gui_tab_middleclose;
int hex_gui_mouse_scroll_speed;
int hex_gui_tab_newtofront; int hex_gui_tab_newtofront;
int hex_gui_tab_pos; int hex_gui_tab_pos;
int hex_gui_tab_small; int hex_gui_tab_small;
@@ -288,6 +293,10 @@ struct zoitechatprefs
int hex_irc_join_delay; int hex_irc_join_delay;
int hex_irc_notice_pos; int hex_irc_notice_pos;
int hex_net_ping_timeout; int hex_net_ping_timeout;
int hex_net_lag_check;
int hex_net_keepalive_idle;
int hex_net_keepalive_interval;
int hex_net_keepalive_count;
int hex_net_proxy_port; int hex_net_proxy_port;
int hex_net_proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */ int hex_net_proxy_type; /* 0=disabled, 1=wingate 2=socks4, 3=socks5, 4=http */
int hex_net_proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */ int hex_net_proxy_use; /* 0=all 1=IRC_ONLY 2=DCC_ONLY */
@@ -379,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 */
@@ -420,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;
@@ -485,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);
@@ -534,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" */
@@ -596,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 */

View File

@@ -452,7 +452,7 @@ void
fe_add_chan_list (server *serv, char *chan, char *users, char *topic) fe_add_chan_list (server *serv, char *chan, char *users, char *topic)
{ {
chanlistrow *next_row; chanlistrow *next_row;
int len = strlen (chan) + 1; size_t len = strlen (chan) + 1;
/* we allocate the struct and channel string in one go */ /* we allocate the struct and channel string in one go */
next_row = g_malloc (sizeof (chanlistrow) + len); next_row = g_malloc (sizeof (chanlistrow) + len);

View File

@@ -61,28 +61,19 @@ static tab_scroll_animation *tab_right_animation;
*/ */
static inline gint static inline gint
cv_tabs_get_viewport_size (GdkWindow *parent_win, gboolean vertical) cv_tabs_get_viewport_size (GtkAdjustment *adj)
{ {
gint viewport_size = 0; return (gint) gtk_adjustment_get_page_size (adj);
if (vertical)
viewport_size = gdk_window_get_height (parent_win);
else
viewport_size = gdk_window_get_width (parent_win);
return viewport_size;
} }
static void static void
cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv) cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv)
{ {
GdkWindow *parent_win;
GtkAdjustment *adj; GtkAdjustment *adj;
GtkWidget *inner; GtkWidget *inner;
gint viewport_size; gint viewport_size;
inner = ((tabview *)cv)->inner; inner = ((tabview *)cv)->inner;
parent_win = gtk_widget_get_window (gtk_widget_get_parent (inner));
if (cv->vertical) if (cv->vertical)
{ {
@@ -92,16 +83,20 @@ cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv)
adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent (inner))); adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent (inner)));
} }
viewport_size = cv_tabs_get_viewport_size (parent_win, cv->vertical); viewport_size = cv_tabs_get_viewport_size (adj);
if (gtk_adjustment_get_upper (adj) <= viewport_size) if (gtk_adjustment_get_upper (adj) <= viewport_size)
{ {
gtk_widget_hide (((tabview *)cv)->b1); if (((tabview *)cv)->b1)
gtk_widget_hide (((tabview *)cv)->b2); gtk_widget_hide (((tabview *)cv)->b1);
if (((tabview *)cv)->b2)
gtk_widget_hide (((tabview *)cv)->b2);
} else } else
{ {
gtk_widget_show (((tabview *)cv)->b1); if (((tabview *)cv)->b1)
gtk_widget_show (((tabview *)cv)->b2); gtk_widget_show (((tabview *)cv)->b1);
if (((tabview *)cv)->b2)
gtk_widget_show (((tabview *)cv)->b2);
} }
} }
@@ -241,10 +236,8 @@ tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv)
gint viewport_size; gint viewport_size;
gfloat new_value; gfloat new_value;
GtkWidget *inner; GtkWidget *inner;
GdkWindow *parent_win;
inner = ((tabview *)cv)->inner; inner = ((tabview *)cv)->inner;
parent_win = gtk_widget_get_window (gtk_widget_get_parent (inner));
if (cv->vertical) if (cv->vertical)
{ {
@@ -254,7 +247,7 @@ tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv)
adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent(inner))); adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent(inner)));
} }
viewport_size = cv_tabs_get_viewport_size (parent_win, cv->vertical); viewport_size = cv_tabs_get_viewport_size (adj);
new_value = tab_search_offset (inner, gtk_adjustment_get_value (adj), 0, cv->vertical); new_value = tab_search_offset (inner, gtk_adjustment_get_value (adj), 0, cv->vertical);
@@ -279,10 +272,8 @@ tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
gint viewport_size; gint viewport_size;
gfloat new_value; gfloat new_value;
GtkWidget *inner; GtkWidget *inner;
GdkWindow *parent_win;
inner = ((tabview *)cv)->inner; inner = ((tabview *)cv)->inner;
parent_win = gtk_widget_get_window (gtk_widget_get_parent (inner));
if (cv->vertical) if (cv->vertical)
{ {
@@ -292,7 +283,7 @@ tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent(inner))); adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (gtk_widget_get_parent(inner)));
} }
viewport_size = cv_tabs_get_viewport_size (parent_win, cv->vertical); viewport_size = cv_tabs_get_viewport_size (adj);
new_value = tab_search_offset (inner, gtk_adjustment_get_value (adj), 1, cv->vertical); new_value = tab_search_offset (inner, gtk_adjustment_get_value (adj), 1, cv->vertical);
@@ -313,69 +304,44 @@ tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv)
static gboolean static gboolean
tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv) tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv)
{ {
int direction = cv_scroll_direction (event);
int i;
if (prefs.hex_gui_tab_scrollchans) if (prefs.hex_gui_tab_scrollchans)
{ {
if (event->direction == GDK_SCROLL_DOWN) if (direction != 0)
mg_switch_page (1, 1); {
else if (event->direction == GDK_SCROLL_UP) for (i = 0; i < cv_scroll_step_count (); i++)
mg_switch_page (1, -1); mg_switch_page (1, direction);
return TRUE;
}
} }
else else
{ {
/* mouse wheel scrolling */ if (direction < 0)
if (event->direction == GDK_SCROLL_UP) {
tab_scroll_left_up_clicked (widget, cv); for (i = 0; i < cv_scroll_step_count (); i++)
else if (event->direction == GDK_SCROLL_DOWN) tab_scroll_left_up_clicked (widget, cv);
tab_scroll_right_down_clicked (widget, cv); return TRUE;
}
else if (direction > 0)
{
for (i = 0; i < cv_scroll_step_count (); i++)
tab_scroll_right_down_clicked (widget, cv);
return TRUE;
}
} }
return FALSE; return FALSE;
} }
/* make a Scroll (arrow) button */
static GtkWidget *
make_sbutton (GtkArrowType type, void *click_cb, void *userdata)
{
GtkWidget *button, *arrow;
const char *icon_name = "pan-end-symbolic";
button = gtk_button_new ();
switch (type)
{
case GTK_ARROW_UP:
icon_name = "pan-up-symbolic";
break;
case GTK_ARROW_DOWN:
icon_name = "pan-down-symbolic";
break;
case GTK_ARROW_LEFT:
icon_name = "pan-start-symbolic";
break;
case GTK_ARROW_RIGHT:
default:
icon_name = "pan-end-symbolic";
break;
}
arrow = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
gtk_container_add (GTK_CONTAINER (button), arrow);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (click_cb), userdata);
g_signal_connect (G_OBJECT (button), "scroll-event",
G_CALLBACK (tab_scroll_cb), userdata);
gtk_widget_show (arrow);
return button;
}
static void static void
cv_tabs_init (chanview *cv) cv_tabs_init (chanview *cv)
{ {
GtkWidget *box, *hbox = NULL; GtkWidget *box;
GtkWidget *viewport; GtkWidget *viewport;
GtkWidget *outer; GtkWidget *outer;
GtkWidget *tree;
if (cv->vertical) if (cv->vertical)
{ {
@@ -390,12 +356,19 @@ cv_tabs_init (chanview *cv)
G_CALLBACK (cv_tabs_sizealloc), cv); G_CALLBACK (cv_tabs_sizealloc), cv);
gtk_widget_show (outer); gtk_widget_show (outer);
viewport = gtk_viewport_new (0, 0); viewport = gtk_scrolled_window_new (0, 0);
gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (viewport), GTK_SHADOW_NONE);
if (cv->vertical) if (cv->vertical)
gtk_widget_set_size_request (viewport, -1, 1); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
else else
gtk_widget_set_size_request (viewport, 1, -1); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (viewport), 1);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (viewport), 1);
gtk_widget_set_hexpand (viewport, TRUE);
gtk_widget_set_vexpand (viewport, TRUE);
cv_add_scroll_events (viewport);
g_signal_connect (G_OBJECT (viewport), "scroll-event", g_signal_connect (G_OBJECT (viewport), "scroll-event",
G_CALLBACK (tab_scroll_cb), cv); G_CALLBACK (tab_scroll_cb), cv);
gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0); gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0);
@@ -413,34 +386,10 @@ 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);
/* if vertical, the buttons can be side by side */ tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
if (cv->vertical) gtk_widget_set_name (tree, "zoitechat-tree");
{ gtk_widget_set_no_show_all (tree, TRUE);
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); gtk_box_pack_start (GTK_BOX (outer), tree, 0, 0, 0);
gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0);
gtk_widget_show (hbox);
}
/* make the Scroll buttons */
((tabview *)cv)->b2 = make_sbutton (cv->vertical ?
GTK_ARROW_UP : GTK_ARROW_LEFT,
tab_scroll_left_up_clicked,
cv);
((tabview *)cv)->b1 = make_sbutton (cv->vertical ?
GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
tab_scroll_right_down_clicked,
cv);
if (hbox)
{
gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2);
gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1);
} else
{
gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0);
gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0);
}
gtk_container_add (GTK_CONTAINER (cv->box), outer); gtk_container_add (GTK_CONTAINER (cv->box), outer);
} }
@@ -653,7 +602,7 @@ tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch)
if (event->button == 1 && event->type == GDK_BUTTON_PRESS) if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
{ {
close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button"); close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button");
if (close_button && if (prefs.hex_gui_tab_closebuttons && close_button &&
gtk_widget_translate_coordinates (close_button, wid, 0, 0, &close_x, &close_y)) gtk_widget_translate_coordinates (close_button, wid, 0, 0, &close_x, &close_y))
{ {
gtk_widget_get_allocation (close_button, &close_alloc); gtk_widget_get_allocation (close_button, &close_alloc);
@@ -679,7 +628,7 @@ tab_close_motion_cb (GtkWidget *wid, GdkEventMotion *event, chan *ch)
gboolean hover = FALSE; gboolean hover = FALSE;
close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button"); close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button");
if (close_button && if (prefs.hex_gui_tab_closebuttons && close_button &&
gtk_widget_translate_coordinates (close_button, wid, 0, 0, &close_x, &close_y)) gtk_widget_translate_coordinates (close_button, wid, 0, 0, &close_x, &close_y))
{ {
gtk_widget_get_allocation (close_button, &close_alloc); gtk_widget_get_allocation (close_button, &close_alloc);
@@ -714,7 +663,7 @@ tab_close_leave_cb (GtkWidget *wid, GdkEventCrossing *event, chan *ch)
GtkWidget *close_button; GtkWidget *close_button;
close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button"); close_button = g_object_get_data (G_OBJECT (wid), "tab-close-button");
if (close_button) if (prefs.hex_gui_tab_closebuttons && close_button)
gtk_widget_unset_state_flags (close_button, GTK_STATE_FLAG_PRELIGHT); gtk_widget_unset_state_flags (close_button, GTK_STATE_FLAG_PRELIGHT);
if (gtk_widget_get_window (wid)) if (gtk_widget_get_window (wid))
gdk_window_set_cursor (gtk_widget_get_window (wid), NULL); gdk_window_set_cursor (gtk_widget_get_window (wid), NULL);
@@ -744,19 +693,21 @@ cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
but = gtk_toggle_button_new (); but = gtk_toggle_button_new ();
gtk_widget_set_name (but, "zoitechat-tab"); gtk_widget_set_name (but, "zoitechat-tab");
gtk_widget_set_size_request (but, -1, 18); gtk_widget_set_size_request (but, -1, 14);
gtk_widget_add_events (but, GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); gtk_widget_add_events (but, GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); cv_add_scroll_events (but);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
label = gtk_label_new (name); label = gtk_label_new (name);
close_button = gtk_button_new (); close_button = gtk_button_new ();
cv_add_scroll_events (close_button);
gtk_style_context_add_class (gtk_widget_get_style_context (close_button), "flat"); gtk_style_context_add_class (gtk_widget_get_style_context (close_button), "flat");
close_icon = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU); close_icon = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
gtk_image_set_pixel_size (GTK_IMAGE (close_icon), 8); gtk_image_set_pixel_size (GTK_IMAGE (close_icon), 8);
gtk_button_set_always_show_image (GTK_BUTTON (close_button), TRUE); gtk_button_set_always_show_image (GTK_BUTTON (close_button), TRUE);
gtk_widget_set_can_focus (close_button, FALSE); gtk_widget_set_can_focus (close_button, FALSE);
gtk_container_add (GTK_CONTAINER (close_button), close_icon); gtk_container_add (GTK_CONTAINER (close_button), close_icon);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (hbox), close_button, FALSE, FALSE, 0); gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
gtk_container_add (GTK_CONTAINER (but), hbox); gtk_container_add (GTK_CONTAINER (but), hbox);
g_object_set_data (G_OBJECT (but), "tab-label", label); g_object_set_data (G_OBJECT (but), "tab-label", label);
g_object_set_data (G_OBJECT (but), "tab-close-button", close_button); g_object_set_data (G_OBJECT (but), "tab-close-button", close_button);
@@ -764,6 +715,10 @@ cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
/* used to trap right-clicks */ /* used to trap right-clicks */
g_signal_connect (G_OBJECT (but), "button-press-event", g_signal_connect (G_OBJECT (but), "button-press-event",
G_CALLBACK (tab_click_cb), ch); G_CALLBACK (tab_click_cb), ch);
g_signal_connect (G_OBJECT (but), "scroll-event",
G_CALLBACK (tab_scroll_cb), cv);
g_signal_connect (G_OBJECT (close_button), "scroll-event",
G_CALLBACK (tab_scroll_cb), cv);
g_signal_connect (G_OBJECT (but), "motion-notify-event", g_signal_connect (G_OBJECT (but), "motion-notify-event",
G_CALLBACK (tab_close_motion_cb), ch); G_CALLBACK (tab_close_motion_cb), ch);
g_signal_connect (G_OBJECT (but), "leave-notify-event", g_signal_connect (G_OBJECT (but), "leave-notify-event",
@@ -780,6 +735,8 @@ cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
G_CALLBACK (tab_toggled_cb), ch); G_CALLBACK (tab_toggled_cb), ch);
g_object_set_data (G_OBJECT (but), "u", ch->userdata); g_object_set_data (G_OBJECT (but), "u", ch->userdata);
gtk_widget_show_all (hbox); gtk_widget_show_all (hbox);
if (!prefs.hex_gui_tab_closebuttons)
gtk_widget_hide (close_button);
tab_add_real (cv, but, ch); tab_add_real (cv, but, ch);

View File

@@ -45,6 +45,8 @@ cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv)
GtkTreeModel *model; GtkTreeModel *model;
GtkTreeIter prev_iter; GtkTreeIter prev_iter;
GtkTreeIter iter; GtkTreeIter iter;
GtkTreePath *path;
GtkTreeView *view;
chan *ch; chan *ch;
chan *prev_ch; chan *prev_ch;
gboolean has_prev; gboolean has_prev;
@@ -60,6 +62,20 @@ cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv)
if (has_prev) if (has_prev)
{ {
gtk_tree_model_get (model, &prev_iter, COL_CHAN, &prev_ch, -1); gtk_tree_model_get (model, &prev_iter, COL_CHAN, &prev_ch, -1);
if (prev_ch != ch && gtk_tree_store_is_ancestor (cv->store, &iter, &prev_iter))
{
view = gtk_tree_selection_get_tree_view (sel);
path = gtk_tree_model_get_path (model, &iter);
if (path)
{
if (!gtk_tree_view_row_expanded (view, path))
{
gtk_tree_path_free (path);
return;
}
gtk_tree_path_free (path);
}
}
if (prev_ch != ch) if (prev_ch != ch)
gtk_tree_store_set (cv->store, &prev_iter, COL_UNDERLINE, PANGO_UNDERLINE_NONE, -1); gtk_tree_store_set (cv->store, &prev_iter, COL_UNDERLINE, PANGO_UNDERLINE_NONE, -1);
} }
@@ -95,12 +111,14 @@ cv_tree_scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, gpointer user
{ {
if (prefs.hex_gui_tab_scrollchans) if (prefs.hex_gui_tab_scrollchans)
{ {
if (event->direction == GDK_SCROLL_DOWN) int direction = cv_scroll_direction (event);
mg_switch_page (1, 1); int i;
else if (event->direction == GDK_SCROLL_UP)
mg_switch_page (1, -1);
return TRUE; if (direction != 0)
for (i = 0; i < cv_scroll_step_count (); i++)
mg_switch_page (1, direction);
return direction != 0;
} }
return FALSE; return FALSE;
@@ -125,6 +143,7 @@ cv_tree_init (chanview *cv)
win = gtk_scrolled_window_new (0, 0); win = gtk_scrolled_window_new (0, 0);
gtk_widget_set_hexpand (win, TRUE); gtk_widget_set_hexpand (win, TRUE);
gtk_widget_set_vexpand (win, TRUE); gtk_widget_set_vexpand (win, TRUE);
cv_add_scroll_events (win);
/*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/ /*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win), gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win),
@@ -138,6 +157,7 @@ cv_tree_init (chanview *cv)
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store)); view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
gtk_widget_set_hexpand (view, TRUE); gtk_widget_set_hexpand (view, TRUE);
gtk_widget_set_vexpand (view, TRUE); gtk_widget_set_vexpand (view, TRUE);
cv_add_scroll_events (view);
gtk_widget_set_name (view, "zoitechat-tree"); gtk_widget_set_name (view, "zoitechat-tree");
{ {
ThemeWidgetStyleValues style_values; ThemeWidgetStyleValues style_values;

View File

@@ -79,6 +79,8 @@ struct _chanview
guint theme_listener_id; guint theme_listener_id;
}; };
static void chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc);
struct _chan struct _chan
{ {
chanview *cv; /* our owner */ chanview *cv; /* our owner */
@@ -95,6 +97,46 @@ static chan *cv_find_chan_by_number (chanview *cv, int num);
static int cv_find_number_of_chan (chanview *cv, chan *find_ch); static int cv_find_number_of_chan (chanview *cv, chan *find_ch);
static void cv_find_neighbors_for_removal (chanview *cv, chan *find_ch, chan **left_ch, chan **first_ch); static void cv_find_neighbors_for_removal (chanview *cv, chan *find_ch, chan **left_ch, chan **first_ch);
static void
cv_add_scroll_events (GtkWidget *widget)
{
gtk_widget_add_events (widget, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
}
static int
cv_scroll_direction (GdkEventScroll *event)
{
gdouble dx;
gdouble dy;
if (event->direction == GDK_SCROLL_SMOOTH && gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy))
{
if (dy > 0)
return 1;
if (dy < 0)
return -1;
}
switch (event->direction)
{
case GDK_SCROLL_DOWN:
return 1;
case GDK_SCROLL_UP:
return -1;
default:
return 0;
}
}
static int
cv_scroll_step_count (void)
{
int speed = prefs.hex_gui_mouse_scroll_speed;
if (speed < 1)
speed = 1;
return (speed + 9) / 10;
}
/* ======= TABS ======= */ /* ======= TABS ======= */
@@ -129,6 +171,7 @@ chanview_apply_theme (chanview *cv)
w = GTK_WIDGET (tv->tree); w = GTK_WIDGET (tv->tree);
if (input_style) if (input_style)
font = input_style->font_desc; font = input_style->font_desc;
chanview_set_font_desc (cv, font);
theme_manager_apply_channel_tree_style (w, theme_manager_apply_channel_tree_style (w,
theme_manager_get_channel_tree_palette_behavior (font)); theme_manager_get_channel_tree_palette_behavior (font));
@@ -273,6 +316,14 @@ chanview_free_ch (chanview *cv, GtkTreeIter *iter)
g_free (ch); g_free (ch);
} }
static void
chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc)
{
if (cv->font_desc)
pango_font_description_free (cv->font_desc);
cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
}
static void static void
chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */ chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */
{ {
@@ -295,6 +346,9 @@ chanview_destroy (chanview *cv)
if (cv->box) if (cv->box)
gtk_widget_destroy (cv->box); gtk_widget_destroy (cv->box);
if (cv->font_desc)
pango_font_description_free (cv->font_desc);
chanview_destroy_store (cv); chanview_destroy_store (cv);
g_free (cv); g_free (cv);
} }
@@ -316,7 +370,7 @@ chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
cv = g_new0 (chanview, 1); cv = g_new0 (chanview, 1);
cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER, cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER,
PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT); PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT);
cv->font_desc = font_desc; cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
cv->trunc_len = trunc_len; cv->trunc_len = trunc_len;
cv->sorted = sort; cv->sorted = sort;

View File

@@ -124,6 +124,25 @@ create_msg_dialog (gchar *title, gchar *message)
static char *win32_argv0_dir; static char *win32_argv0_dir;
static void
win32_set_appusermodelid (void)
{
HMODULE shell32;
HRESULT (WINAPI *set_appid) (PCWSTR);
shell32 = GetModuleHandleW (L"shell32.dll");
if (!shell32)
shell32 = LoadLibraryW (L"shell32.dll");
if (!shell32)
return;
set_appid = (HRESULT (WINAPI *) (PCWSTR)) GetProcAddress (shell32, "SetCurrentProcessExplicitAppUserModelID");
if (!set_appid)
return;
set_appid (L"ZoiteChat.Desktop.Notify");
}
static void static void
win32_set_gsettings_schema_dir (void) win32_set_gsettings_schema_dir (void)
{ {
@@ -422,6 +441,7 @@ fe_args (int argc, char *argv[])
#ifdef WIN32 #ifdef WIN32
win32_set_gsettings_schema_dir (); win32_set_gsettings_schema_dir ();
win32_set_appusermodelid ();
win32_configure_pixbuf_loaders (); win32_configure_pixbuf_loaders ();
/* this is mainly for irc:// URL handling. When windows calls us from */ /* this is mainly for irc:// URL handling. When windows calls us from */
@@ -792,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
@@ -1308,40 +1329,52 @@ maybe_escape_uri (const char *uri)
return g_strdup (uri); return g_strdup (uri);
} }
#ifndef WIN32
static gchar **
fe_open_url_env_sanitized (void)
{
gchar **env = g_get_environ ();
const char *vars[] = {"LD_LIBRARY_PATH", "LD_PRELOAD", "APPDIR", "APPIMAGE", "ARGV0", NULL};
int i;
for (i = 0; vars[i]; i++)
{
gchar **tmp_env = env;
env = g_environ_unsetenv (tmp_env, vars[i]);
if (env != tmp_env)
g_strfreev (tmp_env);
}
return env;
}
#endif
static void static void
fe_open_url_inner (const char *url) fe_open_url_inner (const char *url)
{ {
GError *error = NULL; GError *error = NULL;
char *escaped_url = maybe_escape_uri (url); char *escaped_url = maybe_escape_uri (url);
gboolean opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error); gboolean opened = FALSE;
#ifdef WIN32
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
if (url_utf16 != NULL)
{
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
g_free (url_utf16);
}
if (!opened) if (!opened)
{ {
g_clear_error (&error); opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
#ifdef WIN32 if (!opened)
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL); g_clear_error (&error);
}
if (url_utf16 != NULL)
{
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
g_free (url_utf16);
}
#else #else
{
gchar *xdg_open_argv[] = {(gchar *) "xdg-open", escaped_url, NULL}; gchar *xdg_open_argv[] = {(gchar *) "xdg-open", escaped_url, NULL};
gchar **spawn_env = NULL; gchar **spawn_env = fe_open_url_env_sanitized ();
spawn_env = g_get_environ ();
{
gchar **tmp_env = spawn_env;
spawn_env = g_environ_unsetenv (tmp_env, "LD_LIBRARY_PATH");
if (spawn_env != tmp_env)
g_strfreev (tmp_env);
tmp_env = spawn_env;
spawn_env = g_environ_unsetenv (tmp_env, "LD_PRELOAD");
if (spawn_env != tmp_env)
g_strfreev (tmp_env);
}
if (g_spawn_async (NULL, xdg_open_argv, spawn_env, if (g_spawn_async (NULL, xdg_open_argv, spawn_env,
G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
@@ -1354,19 +1387,26 @@ fe_open_url_inner (const char *url)
g_clear_error (&error); g_clear_error (&error);
} }
if (!opened && gtk_show_uri_on_window (NULL, escaped_url, GDK_CURRENT_TIME, &error))
{
opened = TRUE;
}
else if (!opened)
{
g_clear_error (&error);
}
g_strfreev (spawn_env); g_strfreev (spawn_env);
#endif
} }
if (!opened)
{
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
if (!opened)
g_clear_error (&error);
}
if (!opened && gtk_show_uri_on_window (NULL, escaped_url, GDK_CURRENT_TIME, &error))
{
opened = TRUE;
}
else if (!opened)
{
g_clear_error (&error);
}
#endif
if (!opened) if (!opened)
{ {
g_warning ("Unable to open URL '%s' using system default application", escaped_url); g_warning ("Unable to open URL '%s' using system default application", escaped_url);

View File

@@ -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 */

View File

@@ -60,6 +60,7 @@
#define ICON_FKEYS_DELETE "edit-delete" #define ICON_FKEYS_DELETE "edit-delete"
#define ICON_FKEYS_CANCEL "dialog-cancel" #define ICON_FKEYS_CANCEL "dialog-cancel"
#define ICON_FKEYS_SAVE "document-save" #define ICON_FKEYS_SAVE "document-save"
#define ICON_FKEYS_RESET "edit-undo"
static void replace_handle (GtkWidget * wid); static void replace_handle (GtkWidget * wid);
void key_check_replace_on_change (GtkEditable *editable, gpointer data); void key_check_replace_on_change (GtkEditable *editable, gpointer data);
@@ -80,7 +81,7 @@ void key_action_tab_clean (void);
*/ */
/* Remember that the *number* of actions is this *plus* 1 --AGL */ /* Remember that the *number* of actions is this *plus* 1 --AGL */
#define KEY_MAX_ACTIONS 14 #define KEY_MAX_ACTIONS 16
struct key_binding struct key_binding
{ {
@@ -105,7 +106,10 @@ struct gcomp_data
}; };
static int key_load_kbs (void); static int key_load_kbs (void);
static int key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list);
static int key_save_kbs (void); static int key_save_kbs (void);
static void key_dialog_load (GtkListStore *store);
static void key_dialog_reset (GtkWidget *wid, gpointer userdata);
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, char *d1, char *d2,
struct session *sess); struct session *sess);
@@ -142,6 +146,13 @@ static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt, static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2, char *d1, char *d2,
struct session *sess); struct session *sess);
static int key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static int key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt,
char *d1, char *d2,
struct session *sess);
static GSList *keybind_list = NULL; static GSList *keybind_list = NULL;
@@ -260,7 +271,7 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
{key_action_handle_command, "Run Command", {key_action_handle_command, "Run Command",
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")}, N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")},
{key_action_page_switch, "Change Page", {key_action_page_switch, "Change Page",
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position. Set Data 1 to auto to switch to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")}, N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page number, channel name, network/channel, or auto. If Data 2 is set to anything then numeric switches are relative to the current position. Auto switches to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
{key_action_insert, "Insert in Buffer", {key_action_insert, "Insert in Buffer",
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")}, N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")},
{key_action_scroll_page, "Scroll Page", {key_action_scroll_page, "Scroll Page",
@@ -287,6 +298,10 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
N_("This command moves the current tab family to the right")}, N_("This command moves the current tab family to the right")},
{key_action_put_history, "Push input line into history", {key_action_put_history, "Push input line into history",
N_("Push input line into history but doesn't send to server")}, N_("Push input line into history but doesn't send to server")},
{key_action_menu_shortcut, "Menu Shortcut",
N_("Runs one of the built-in menu shortcuts. Set Data 1 to: network-list, new-server-tab, new-server-window, close, quit, menu-toggle, user-list-toggle, fullscreen-toggle, away-toggle, reset-marker, move-marker, copy-selection, search-text, search-next, search-previous or contents.")},
{key_action_reopen_closed_tab, "Reopen Closed Tab",
N_("Reopens the most recently closed channel tab")},
}; };
#define default_kb_cfg \ #define default_kb_cfg \
@@ -329,7 +344,86 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\ "ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\ "ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\ "ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
"ACCEL=F9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n" "ACCEL=<Primary>s\nMenu Shortcut\nD1:network-list\nD2!\n\n"\
"ACCEL=<Primary>t\nMenu Shortcut\nD1:new-server-tab\nD2!\n\n"\
"ACCEL=<Primary>n\nMenu Shortcut\nD1:new-server-window\nD2!\n\n"\
"ACCEL=<Primary>w\nMenu Shortcut\nD1:close\nD2!\n\n"\
"ACCEL=<Primary>q\nMenu Shortcut\nD1:quit\nD2!\n\n"\
"ACCEL=<Primary>F9\nMenu Shortcut\nD1:menu-toggle\nD2!\n\n"\
"ACCEL=F7\nMenu Shortcut\nD1:user-list-toggle\nD2!\n\n"\
"ACCEL=F11\nMenu Shortcut\nD1:fullscreen-toggle\nD2!\n\n"\
"ACCEL=<Alt>a\nMenu Shortcut\nD1:away-toggle\nD2!\n\n"\
"ACCEL=<Primary>m\nMenu Shortcut\nD1:reset-marker\nD2!\n\n"\
"ACCEL=<Primary><Shift>M\nMenu Shortcut\nD1:move-marker\nD2!\n\n"\
"ACCEL=<Primary><Shift>C\nMenu Shortcut\nD1:copy-selection\nD2!\n\n"\
"ACCEL=<Primary>f\nMenu Shortcut\nD1:search-text\nD2!\n\n"\
"ACCEL=<Primary>g\nMenu Shortcut\nD1:search-next\nD2!\n\n"\
"ACCEL=<Primary><Shift>G\nMenu Shortcut\nD1:search-previous\nD2!\n\n"\
"ACCEL=F1\nMenu Shortcut\nD1:contents\nD2!\n\n"\
"ACCEL=<Primary><Shift>T\nReopen Closed Tab\nD1!\nD2!\n\n"
static gboolean
key_builtin_data_match (char *line, char *data)
{
if (line[2] == '!')
return data == NULL || data[0] == 0;
if (line[2] == ':')
return !strcmp (&line[3], data ? data : "");
return FALSE;
}
static gboolean
key_binding_is_builtin (struct key_binding *kb)
{
char *buf, *ibuf;
char *action;
int pnt = 0;
int state = 0;
gboolean match = FALSE;
gboolean d1_match = FALSE;
off_t size;
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
return FALSE;
action = key_actions[kb->action].name;
ibuf = g_strdup (default_kb_cfg);
size = strlen (default_kb_cfg);
while (buf_get_line (ibuf, &buf, &pnt, size))
{
if (strlen (buf) == 0)
continue;
switch (state)
{
case 0:
state = 1;
break;
case 1:
match = !strcmp (buf, action);
state = 2;
break;
case 2:
d1_match = match && key_builtin_data_match (buf, kb->data1);
state = 3;
break;
case 3:
if (d1_match && key_builtin_data_match (buf, kb->data2))
{
g_free (ibuf);
return TRUE;
}
state = 0;
break;
}
}
g_free (ibuf);
return FALSE;
}
void void
key_init () key_init ()
@@ -443,7 +537,6 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
if (!list) if (!list)
return FALSE; return FALSE;
current_sess = sess; current_sess = sess;
if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval))) if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
return 1; return 1;
@@ -487,6 +580,31 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
return 0; return 0;
} }
gboolean
key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod)
{
struct key_binding *kb;
GSList *list;
if (!name)
return FALSE;
list = keybind_list;
while (list)
{
kb = (struct key_binding*)list->data;
if (kb->action >= 0 && kb->action <= KEY_MAX_ACTIONS && kb->keyval != 0 && !strcmp (key_actions[kb->action].name, "Menu Shortcut") && kb->data1 && !strcmp (kb->data1, name))
{
*keyval = kb->keyval;
*mod = kb->mod;
return TRUE;
}
list = g_slist_next (list);
}
return FALSE;
}
/* ***** GUI code here ******************* */ /* ***** GUI code here ******************* */
@@ -497,6 +615,7 @@ enum
ACTION_COLUMN, ACTION_COLUMN,
D1_COLUMN, D1_COLUMN,
D2_COLUMN, D2_COLUMN,
CUSTOM_COLUMN,
N_COLUMNS N_COLUMNS
}; };
@@ -621,16 +740,32 @@ key_dialog_keypress (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
if (handled) if (handled)
{ {
gboolean custom1, custom2;
sel = gtk_tree_view_get_selection (view); sel = gtk_tree_view_get_selection (view);
gtk_tree_selection_get_selected (sel, &store, &iter1); if (!gtk_tree_selection_get_selected (sel, &store, &iter1))
return FALSE;
path = gtk_tree_model_get_path (store, &iter1); path = gtk_tree_model_get_path (store, &iter1);
if (delta == 1) if (delta == 1)
gtk_tree_path_next (path); gtk_tree_path_next (path);
else else if (!gtk_tree_path_prev (path))
gtk_tree_path_prev (path); {
gtk_tree_model_get_iter (store, &iter2, path); gtk_tree_path_free (path);
return FALSE;
}
if (!gtk_tree_model_get_iter (store, &iter2, path))
{
gtk_tree_path_free (path);
return FALSE;
}
gtk_tree_path_free (path); gtk_tree_path_free (path);
gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2); gtk_tree_model_get (store, &iter1, CUSTOM_COLUMN, &custom1, -1);
gtk_tree_model_get (store, &iter2, CUSTOM_COLUMN, &custom2, -1);
if (custom1 && custom2)
gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
} }
return handled; return handled;
@@ -642,14 +777,23 @@ key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
GtkTreeModel *model; GtkTreeModel *model;
GtkTreeIter iter; GtkTreeIter iter;
GtkXText *xtext; GtkXText *xtext;
GtkWidget *delete_button;
char *actiontext; char *actiontext;
gboolean custom;
int action; int action;
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL) if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
{
if (delete_button)
gtk_widget_set_sensitive (delete_button, FALSE);
return; return;
}
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext")); xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, -1); gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, CUSTOM_COLUMN, &custom, -1);
if (delete_button)
gtk_widget_set_sensitive (delete_button, custom);
if (actiontext) if (actiontext)
{ {
@@ -724,7 +868,10 @@ key_dialog_save (GtkWidget *wid, gpointer userdata)
} }
if (key_save_kbs () == 0) if (key_save_kbs () == 0)
{
menu_update_quit_accel ();
key_dialog_close (wid, NULL); key_dialog_close (wid, NULL);
}
} }
static void static void
@@ -737,6 +884,7 @@ key_dialog_add (GtkWidget *wid, gpointer userdata)
GtkTreePath *path; GtkTreePath *path;
gtk_list_store_append (store, &iter); gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, CUSTOM_COLUMN, TRUE, -1);
/* make sure the new row is visible and selected */ /* make sure the new row is visible and selected */
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
@@ -746,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)
{ {
@@ -753,9 +1029,14 @@ key_dialog_delete (GtkWidget *wid, gpointer userdata)
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
GtkTreeIter iter; GtkTreeIter iter;
GtkTreePath *path; GtkTreePath *path;
gboolean custom;
if (gtkutil_treeview_get_selected (view, &iter, -1)) if (gtkutil_treeview_get_selected (view, &iter, -1))
{ {
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, CUSTOM_COLUMN, &custom, -1);
if (!custom)
return;
/* delete this row, select next one */ /* delete this row, select next one */
if (gtk_list_store_remove (store, &iter)) if (gtk_list_store_remove (store, &iter))
{ {
@@ -782,13 +1063,13 @@ key_dialog_treeview_new (GtkWidget *box)
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING); G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
g_return_val_if_fail (store != NULL, NULL); g_return_val_if_fail (store != NULL, NULL);
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE); gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE); gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), FALSE);
g_signal_connect (G_OBJECT (view), "key-press-event", g_signal_connect (G_OBJECT (view), "key-press-event",
G_CALLBACK (key_dialog_keypress), NULL); G_CALLBACK (key_dialog_keypress), NULL);
@@ -859,6 +1140,7 @@ key_dialog_treeview_new (GtkWidget *box)
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN, gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
"Action", render, "Action", render,
"text", ACTION_COLUMN, "text", ACTION_COLUMN,
"editable", CUSTOM_COLUMN,
NULL); NULL);
render = gtk_cell_renderer_text_new (); render = gtk_cell_renderer_text_new ();
@@ -869,6 +1151,7 @@ key_dialog_treeview_new (GtkWidget *box)
GTK_TREE_VIEW (view), D1_COLUMN, GTK_TREE_VIEW (view), D1_COLUMN,
"Data1", render, "Data1", render,
"text", D1_COLUMN, "text", D1_COLUMN,
"editable", CUSTOM_COLUMN,
NULL); NULL);
render = gtk_cell_renderer_text_new (); render = gtk_cell_renderer_text_new ();
@@ -879,6 +1162,7 @@ key_dialog_treeview_new (GtkWidget *box)
GTK_TREE_VIEW (view), D2_COLUMN, GTK_TREE_VIEW (view), D2_COLUMN,
"Data2", render, "Data2", render,
"text", D2_COLUMN, "text", D2_COLUMN,
"editable", CUSTOM_COLUMN,
NULL); NULL);
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN); col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
@@ -924,7 +1208,8 @@ key_dialog_load (GtkListStore *store)
ACCEL_COLUMN, accel_text, ACCEL_COLUMN, accel_text,
ACTION_COLUMN, key_actions[kb->action].name, ACTION_COLUMN, key_actions[kb->action].name,
D1_COLUMN, kb->data1, D1_COLUMN, kb->data1,
D2_COLUMN, kb->data2, -1); D2_COLUMN, kb->data2,
CUSTOM_COLUMN, !key_binding_is_builtin (kb), -1);
g_free (accel_text); g_free (accel_text);
g_free (label_text); g_free (label_text);
@@ -937,7 +1222,7 @@ void
key_dialog_show () key_dialog_show ()
{ {
GtkWidget *vbox, *box; GtkWidget *vbox, *box;
GtkWidget *view, *xtext; GtkWidget *view, *xtext, *delete_button;
GtkListStore *store; GtkListStore *store;
XTextColor xtext_palette[XTEXT_COLS]; XTextColor xtext_palette[XTEXT_COLS];
char buf[128]; char buf[128];
@@ -971,8 +1256,12 @@ key_dialog_show ()
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add, gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
NULL, _("Add")); NULL, _("Add"));
gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete, delete_button = gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
NULL, _("Delete")); NULL, _("Delete"));
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
gtk_widget_set_sensitive (delete_button, FALSE);
gtkutil_button (box, ICON_FKEYS_RESET, NULL, key_dialog_reset,
NULL, _("Reset"));
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close, gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
NULL, _("Cancel")); NULL, _("Cancel"));
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save, gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
@@ -1080,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))
{ {
@@ -1128,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);
@@ -1158,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;
} }
@@ -1174,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;
} }
@@ -1191,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;
} }
@@ -1211,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)
@@ -1234,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;
} }
@@ -1243,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)
@@ -1268,6 +1576,24 @@ key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
return 0; return 0;
} }
static int
key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess)
{
if (menu_key_action (d1, evt->keyval, evt->state))
return 2;
return 0;
}
static int
key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess)
{
mg_reopen_closed_channel_tab ();
return 2;
}
/* /*
* Check if the given session is inside the main window. This predicate * Check if the given session is inside the main window. This predicate
* is passed to lastact_getfirst() as a way to filter out detached sessions. * is passed to lastact_getfirst() as a way to filter out detached sessions.
@@ -1286,6 +1612,9 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
char *d2, struct session *sess) char *d2, struct session *sess)
{ {
session *newsess; session *newsess;
char *network;
char *channel;
char *slash;
int len, i, num; int len, i, num;
if (!d1) if (!d1)
@@ -1297,21 +1626,13 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
if (g_ascii_strcasecmp(d1, "auto") == 0) if (g_ascii_strcasecmp(d1, "auto") == 0)
{ {
/* Auto switch makes no sense in detached sessions */
if (!sess->gui->is_tab) if (!sess->gui->is_tab)
return 1; return 1;
/* Obtain a session with recent activity */
newsess = lastact_getfirst(session_check_is_tab); newsess = lastact_getfirst(session_check_is_tab);
if (newsess) if (newsess)
{ {
/*
* Only sessions in the current window should be considered (i.e.
* we don't want to move the focus on a different window). This
* call could, in theory, do this, but we checked before that
* newsess->gui->is_tab and sess->gui->is_tab.
*/
mg_bring_tofront_sess(newsess); mg_bring_tofront_sess(newsess);
return 0; return 0;
} }
@@ -1325,8 +1646,27 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
{ {
if (i == 0 && (d1[i] == '+' || d1[i] == '-')) if (i == 0 && (d1[i] == '+' || d1[i] == '-'))
continue; continue;
else
return 1; network = NULL;
channel = d1;
slash = strchr (d1, '/');
if (slash && slash[1])
{
network = g_strndup (d1, slash - d1);
channel = slash + 1;
}
newsess = plugin_find_context (network, channel, sess->server);
g_free (network);
if (newsess && newsess->gui)
{
mg_bring_tofront_sess(newsess);
return 0;
}
return 1;
} }
} }
@@ -1952,8 +2292,37 @@ replace_handle (GtkWidget *t)
{ {
ptrdiff_t found_offset = found - text; ptrdiff_t found_offset = found - text;
ptrdiff_t found_end_offset = found_offset + (ptrdiff_t) pop_len; ptrdiff_t found_end_offset = found_offset + (ptrdiff_t) pop_len;
gboolean start_ok;
gboolean end_ok;
int rank; int rank;
ptrdiff_t distance; ptrdiff_t distance;
const char *before = found;
const char *after = found + pop_len;
if (before > text)
{
before = g_utf8_find_prev_char (text, before);
start_ok = !before || (!g_unichar_isalnum (g_utf8_get_char (before)) && g_utf8_get_char (before) != '_');
}
else
{
start_ok = TRUE;
}
if (*after != '\0')
{
end_ok = !g_unichar_isalnum (g_utf8_get_char (after)) && g_utf8_get_char (after) != '_';
}
else
{
end_ok = TRUE;
}
if (!start_ok || !end_ok)
{
found++;
continue;
}
if (cursor_byte_offset >= found_offset && cursor_byte_offset <= found_end_offset) if (cursor_byte_offset >= found_offset && cursor_byte_offset <= found_end_offset)
{ {

View File

@@ -35,5 +35,6 @@ int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
session *sess); session *sess);
void key_check_replace_on_change (GtkEditable *editable, gpointer data); void key_check_replace_on_change (GtkEditable *editable, gpointer data);
gboolean key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod);
#endif #endif

View File

@@ -272,13 +272,6 @@ gtkutil_apply_palette (GtkWidget *widget, const GdkRGBA *bg, const GdkRGBA *fg,
theme_manager_apply_palette_widget (widget, bg, fg, font_desc); theme_manager_apply_palette_widget (widget, bg, fg, font_desc);
} }
static void
gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq)
{
freq->callback (freq->userdata, NULL);
g_free (freq);
}
static void static void
gtkutil_check_file (char *filename, struct file_req *freq) gtkutil_check_file (char *filename, struct file_req *freq)
{ {
@@ -390,26 +383,6 @@ gtkutil_file_req_done_chooser (GtkFileChooser *fs, struct file_req *freq)
} }
static void
gtkutil_file_req_done (GtkWidget * wid, struct file_req *freq)
{
gtkutil_file_req_done_chooser (GTK_FILE_CHOOSER (freq->dialog), freq);
gtk_widget_destroy (freq->dialog);
}
static void
gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq)
{
if (res == GTK_RESPONSE_ACCEPT)
{
gtkutil_file_req_done (dialog, freq);
return;
}
gtk_widget_destroy (dialog);
}
#ifdef WIN32
static gboolean static gboolean
gtkutil_native_dialog_unref_idle (gpointer native) gtkutil_native_dialog_unref_idle (gpointer native)
{ {
@@ -423,27 +396,16 @@ gtkutil_native_file_req_response (GtkNativeDialog *dialog, gint res, struct file
if (res == GTK_RESPONSE_ACCEPT) if (res == GTK_RESPONSE_ACCEPT)
gtkutil_file_req_done_chooser (GTK_FILE_CHOOSER (dialog), freq); gtkutil_file_req_done_chooser (GTK_FILE_CHOOSER (dialog), freq);
/* Match gtk dialog flow by always sending NULL to indicate completion. */
freq->callback (freq->userdata, NULL); freq->callback (freq->userdata, NULL);
g_free (freq); g_free (freq);
/*
* Defer unref until idle to avoid disposing the native chooser while
* still in the button-release signal stack on Windows.
*/
g_idle_add (gtkutil_native_dialog_unref_idle, dialog); g_idle_add (gtkutil_native_dialog_unref_idle, dialog);
} }
#endif
void void
gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *userdata, char *filter, char *extensions, gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *userdata, char *filter, char *extensions,
int flags) int flags)
{ {
struct file_req *freq; struct file_req *freq;
GtkWidget *dialog;
GtkFileFilter *filefilter;
char *token;
char *tokenbuffer;
const char *xdir; const char *xdir;
GtkWindow *effective_parent = parent; GtkWindow *effective_parent = parent;
@@ -453,7 +415,6 @@ gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *us
xdir = get_xdir (); xdir = get_xdir ();
#ifdef WIN32
{ {
GtkFileChooserNative *native = gtk_file_chooser_native_new ( GtkFileChooserNative *native = gtk_file_chooser_native_new (
title, title,
@@ -522,110 +483,14 @@ gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *us
g_signal_connect (native, "response", g_signal_connect (native, "response",
G_CALLBACK (gtkutil_native_file_req_response), freq); G_CALLBACK (gtkutil_native_file_req_response), freq);
if (flags & FRF_MODAL)
gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (native), TRUE);
gtk_native_dialog_show (GTK_NATIVE_DIALOG (native)); gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
return; return;
} }
#endif
if (flags & FRF_WRITE)
{
dialog = gtk_file_chooser_dialog_new (title, NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Save"), GTK_RESPONSE_ACCEPT,
NULL);
if (!(flags & FRF_NOASKOVERWRITE))
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
}
else
dialog = gtk_file_chooser_dialog_new (title, NULL,
GTK_FILE_CHOOSER_ACTION_OPEN,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Open"), GTK_RESPONSE_ACCEPT,
NULL);
theme_manager_attach_window (dialog);
if (filter && filter[0] && (flags & FRF_FILTERISINITIAL))
{
if (flags & FRF_WRITE)
{
char temp[1024];
path_part (filter, temp, sizeof (temp));
if (temp[0] && g_file_test (temp, G_FILE_TEST_IS_DIR))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp);
else if (xdir && xdir[0] && g_file_test (xdir, G_FILE_TEST_IS_DIR))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), xdir);
gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter));
}
else
{
if (g_file_test (filter, G_FILE_TEST_IS_DIR))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
else if (xdir && xdir[0] && g_file_test (xdir, G_FILE_TEST_IS_DIR))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), xdir);
}
}
else if (!(flags & FRF_RECENTLYUSED))
{
if (xdir && xdir[0] && g_file_test (xdir, G_FILE_TEST_IS_DIR))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), xdir);
}
if (flags & FRF_MULTIPLE)
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
if (flags & FRF_CHOOSEFOLDER)
gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
if ((flags & FRF_EXTENSIONS || flags & FRF_MIMETYPES) && extensions != NULL)
{
filefilter = gtk_file_filter_new ();
tokenbuffer = g_strdup (extensions);
token = strtok (tokenbuffer, ";");
while (token != NULL)
{
if (flags & FRF_EXTENSIONS)
gtk_file_filter_add_pattern (filefilter, token);
else
gtk_file_filter_add_mime_type (filefilter, token);
token = strtok (NULL, ";");
}
g_free (tokenbuffer);
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filefilter);
}
if (xdir && xdir[0] && g_file_test (xdir, G_FILE_TEST_IS_DIR))
{
GError *shortcut_error = NULL;
gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), xdir, &shortcut_error);
if (shortcut_error)
g_error_free (shortcut_error);
}
freq = g_new (struct file_req, 1);
freq->dialog = dialog;
freq->flags = flags;
freq->callback = callback;
freq->userdata = userdata;
g_signal_connect (G_OBJECT (dialog), "response",
G_CALLBACK (gtkutil_file_req_response), freq);
g_signal_connect (G_OBJECT (dialog), "destroy",
G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq);
if (effective_parent)
gtk_window_set_transient_for (GTK_WINDOW (dialog), effective_parent);
if (flags & FRF_MODAL)
{
g_assert (effective_parent);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
}
gtk_widget_show (dialog);
} }
static gboolean static gboolean
@@ -972,7 +837,7 @@ gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection,
win = gtk_widget_get_toplevel (GTK_WIDGET (widget)); win = gtk_widget_get_toplevel (GTK_WIDGET (widget));
if (gtk_widget_is_toplevel (win)) if (gtk_widget_is_toplevel (win))
{ {
int len = strlen (str); gint len = (gint) strlen (str);
if (selection) if (selection)
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,7 @@ void mg_apply_setup (void);
void mg_apply_session_font_prefs (session_gui *gui); void mg_apply_session_font_prefs (session_gui *gui);
void mg_close_sess (session *); void mg_close_sess (session *);
void mg_tab_close (session *sess); void mg_tab_close (session *sess);
void mg_reopen_closed_channel_tab (void);
void mg_detach (session *sess, int mode); void mg_detach (session *sess, int mode);
void mg_progressbar_create (session_gui *gui); void mg_progressbar_create (session_gui *gui);
void mg_progressbar_destroy (session_gui *gui); void mg_progressbar_destroy (session_gui *gui);
@@ -47,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 */

View File

@@ -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"
@@ -137,7 +138,6 @@ struct mymenu
#define XCMENU_MARKUP 2 #define XCMENU_MARKUP 2
#define XCMENU_MNEMONIC 4 #define XCMENU_MNEMONIC 4
/* execute a userlistbutton/popupmenu command */
static void static void
nick_command (session * sess, char *cmd) nick_command (session * sess, char *cmd)
@@ -148,7 +148,6 @@ nick_command (session * sess, char *cmd)
handle_command (sess, cmd, TRUE); handle_command (sess, cmd, TRUE);
} }
/* fill in the %a %s %n etc and execute the command */
void void
nick_command_parse (session *sess, char *cmd, char *nick, char *allnick) nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
@@ -177,7 +176,6 @@ nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
} }
} }
/* this can't overflow, since popup->cmd is only 256 */
len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512; len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512;
buf = g_malloc (len); buf = g_malloc (len);
@@ -190,7 +188,6 @@ nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
g_free (buf); g_free (buf);
} }
/* userlist button has been clicked */
void void
userlist_button_cb (GtkWidget * button, char *cmd) userlist_button_cb (GtkWidget * button, char *cmd)
@@ -207,7 +204,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
if (sess->type == SESS_DIALOG) if (sess->type == SESS_DIALOG)
{ {
/* fake a selection */
nicks = g_new (char *, 2); nicks = g_new (char *, 2);
nicks[0] = g_strdup (sess->channel); nicks[0] = g_strdup (sess->channel);
nicks[1] = NULL; nicks[1] = NULL;
@@ -215,7 +211,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
} }
else else
{ {
/* find number of selected rows */
nicks = userlist_selection_list (sess->gui->user_tree, &num_sel); nicks = userlist_selection_list (sess->gui->user_tree, &num_sel);
if (num_sel < 1) if (num_sel < 1)
{ {
@@ -226,7 +221,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
} }
} }
/* create "allnicks" string */
allnicks = g_malloc (((NICKLEN + 1) * num_sel) + 1); allnicks = g_malloc (((NICKLEN + 1) * num_sel) + 1);
*allnicks = 0; *allnicks = 0;
@@ -240,7 +234,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
if (!nick) if (!nick)
nick = nicks[0]; nick = nicks[0];
/* if not using "%a", execute the command once for each nickname */
if (!using_allnicks) if (!using_allnicks)
nick_command_parse (sess, cmd, nicks[i], ""); nick_command_parse (sess, cmd, nicks[i], "");
@@ -264,19 +257,16 @@ userlist_button_cb (GtkWidget * button, char *cmd)
g_free (allnicks); g_free (allnicks);
} }
/* a popup-menu-item has been selected */
static void static void
popup_menu_cb (GtkWidget * item, char *cmd) popup_menu_cb (GtkWidget * item, char *cmd)
{ {
char *nick; char *nick;
/* the userdata is set in menu_quick_item() */
nick = g_object_get_data (G_OBJECT (item), "u"); nick = g_object_get_data (G_OBJECT (item), "u");
if (!nick) /* userlist popup menu */ if (!nick) /* userlist popup menu */
{ {
/* treat it just like a userlist button */
userlist_button_cb (NULL, cmd); userlist_button_cb (NULL, cmd);
return; return;
} }
@@ -388,7 +378,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
if (!name) if (!name)
return menu; return menu;
/* Code to add a submenu */
sub_menu = menu_new (); sub_menu = menu_new ();
if (flags & XCMENU_MARKUP) if (flags & XCMENU_MARKUP)
{ {
@@ -410,7 +399,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
*sub_item_ret = sub_item; *sub_item_ret = sub_item;
if (flags & XCMENU_DOLIST) if (flags & XCMENU_DOLIST)
/* We create a new element in the list */
submenu_list = g_slist_prepend (submenu_list, sub_menu); submenu_list = g_slist_prepend (submenu_list, sub_menu);
return sub_menu; return sub_menu;
} }
@@ -418,7 +406,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
static GtkWidget * static GtkWidget *
menu_quick_endsub (void) menu_quick_endsub (void)
{ {
/* Just delete the first element in the linked list pointed to by first */
if (submenu_list) if (submenu_list)
submenu_list = g_slist_remove (submenu_list, submenu_list->data); submenu_list = g_slist_remove (submenu_list, submenu_list->data);
@@ -449,10 +436,7 @@ is_in_path (char *cmd)
char **argv; char **argv;
int argc; int argc;
/* special-case these default entries. */
/* 123456789012345678 */
if (strncmp (prog, "gnome-terminal -x ", 18) == 0) if (strncmp (prog, "gnome-terminal -x ", 18) == 0)
/* don't check for gnome-terminal, but the thing it's executing! */
prog += 18; prog += 18;
if (g_shell_parse_argv (prog, &argc, &argv, NULL)) if (g_shell_parse_argv (prog, &argc, &argv, NULL))
@@ -471,7 +455,6 @@ is_in_path (char *cmd)
return 0; return 0;
} }
/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */
static void static void
menu_extract_icon (char *name, char **label, char **icon) menu_extract_icon (char *name, char **label, char **icon)
@@ -484,7 +467,6 @@ menu_extract_icon (char *name, char **label, char **icon)
{ {
if (*p == '~') if (*p == '~')
{ {
/* escape \~ */
if (p == name || p[-1] != '\\') if (p == name || p[-1] != '\\')
{ {
if (!start) if (!start)
@@ -511,7 +493,6 @@ menu_extract_icon (char *name, char **label, char **icon)
} }
} }
/* append items to "menu" using the (struct popup*) list provided */
void void
menu_create (GtkWidget *menu, GSList *list, char *target, int check_path) menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
@@ -538,14 +519,12 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
} else if (!g_ascii_strncasecmp (pop->name, "ENDSUB", 6)) } else if (!g_ascii_strncasecmp (pop->name, "ENDSUB", 6))
{ {
/* empty sub menu due to no programs in PATH? */
if (check_path && childcount < 1) if (check_path && childcount < 1)
gtk_widget_destroy (subitem); gtk_widget_destroy (subitem);
subitem = NULL; subitem = NULL;
if (tempmenu != menu) if (tempmenu != menu)
tempmenu = menu_quick_endsub (); tempmenu = menu_quick_endsub ();
/* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */
} else if (!g_ascii_strncasecmp (pop->name, "SEP", 3)) } else if (!g_ascii_strncasecmp (pop->name, "SEP", 3))
{ {
@@ -555,10 +534,8 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
{ {
char *icon, *label; char *icon, *label;
/* default command in zoitechat.c */
if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s")) if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s"))
{ {
/* don't create this item if already in notify list */
if (!target || notify_is_in_list (current_sess->server, target)) if (!target || notify_is_in_list (current_sess->server, target))
{ {
list = list->next; list = list->next;
@@ -571,7 +548,6 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
if (!check_path || pop->cmd[0] != '!') if (!check_path || pop->cmd[0] != '!')
{ {
menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon); menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
/* check if the program is in path, if not, leave it out! */
} else if (is_in_path (pop->cmd)) } else if (is_in_path (pop->cmd))
{ {
childcount++; childcount++;
@@ -585,7 +561,6 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
list = list->next; list = list->next;
} }
/* Let's clean up the linked list from mem */
while (submenu_list) while (submenu_list)
submenu_list = g_slist_remove (submenu_list, submenu_list->data); submenu_list = g_slist_remove (submenu_list, submenu_list->data);
} }
@@ -639,10 +614,8 @@ menu_nickinfo_cb (GtkWidget *menu, session *sess)
if (!is_session (sess)) if (!is_session (sess))
return; return;
/* issue a /WHOIS */
g_snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy); g_snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy);
handle_command (sess, buf, FALSE); handle_command (sess, buf, FALSE);
/* and hide the output */
sess->server->skip_next_whois = 1; sess->server->skip_next_whois = 1;
} }
@@ -652,7 +625,6 @@ copy_to_clipboard_cb (GtkWidget *item, char *url)
gtkutil_copy_to_clipboard (item, NULL, url); gtkutil_copy_to_clipboard (item, NULL, url);
} }
/* returns boolean: Some data is missing */
static gboolean static gboolean
menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu) menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
@@ -664,7 +636,6 @@ menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
gboolean missing = FALSE; gboolean missing = FALSE;
GtkWidget *item; GtkWidget *item;
/* let the translators tweak this if need be */
fmt = _("<tt><b>%-11s</b></tt> %s"); fmt = _("<tt><b>%-11s</b></tt> %s");
g_snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown")); g_snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown"));
@@ -755,14 +726,11 @@ fe_userlist_update (session *sess, struct User *user)
if (!nick_submenu || !str_copy) if (!nick_submenu || !str_copy)
return; return;
/* not the same nick as the menu? */
if (sess->server->p_cmp (user->nick, str_copy)) if (sess->server->p_cmp (user->nick, str_copy))
return; return;
/* get rid of the "show" signal */
g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess); g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess);
/* destroy all the old items */
items = gtk_container_get_children (GTK_CONTAINER (nick_submenu)); items = gtk_container_get_children (GTK_CONTAINER (nick_submenu));
iter = items; iter = items;
while (iter) while (iter)
@@ -773,7 +741,6 @@ fe_userlist_update (session *sess, struct User *user)
} }
g_list_free (items); g_list_free (items);
/* and re-create them with new info */
needs_refresh = menu_create_nickinfo_menu (user, nick_submenu) || needs_refresh = menu_create_nickinfo_menu (user, nick_submenu) ||
!user->hostname || !user->realname || !user->servername; !user->hostname || !user->realname || !user->servername;
@@ -784,6 +751,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)
{ {
@@ -796,7 +782,6 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
submenu_list = 0; /* first time through, might not be 0 */ submenu_list = 0; /* first time through, might not be 0 */
/* more than 1 nick selected? */
if (num_sel > 1) if (num_sel > 1)
{ {
g_snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel); g_snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel);
@@ -827,6 +812,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 */
@@ -835,7 +826,6 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
menu_popup (menu, event, NULL); menu_popup (menu, event, NULL);
} }
/* stuff for the View menu */
static void static void
menu_showhide_cb (session *sess) menu_showhide_cb (session *sess)
@@ -999,7 +989,6 @@ menu_fullscreen_toggle (GtkWidget *wid, gpointer ud)
#ifdef WIN32 #ifdef WIN32
if (!prefs.hex_gui_win_state) /* not maximized */ if (!prefs.hex_gui_win_state) /* not maximized */
{ {
/* other window managers seem to handle this */
gtk_window_resize (GTK_WINDOW (parent_window), gtk_window_resize (GTK_WINDOW (parent_window),
prefs.hex_gui_win_width, prefs.hex_gui_win_height); prefs.hex_gui_win_width, prefs.hex_gui_win_height);
gtk_window_move (GTK_WINDOW (parent_window), gtk_window_move (GTK_WINDOW (parent_window),
@@ -1025,7 +1014,6 @@ open_url_cb (GtkWidget *item, char *url)
{ {
char buf[512]; char buf[512];
/* pass this to /URL so it can handle irc:// */
g_snprintf (buf, sizeof (buf), "URL %s", url); g_snprintf (buf, sizeof (buf), "URL %s", url);
handle_command (current_sess, buf, FALSE); handle_command (current_sess, buf, FALSE);
} }
@@ -1040,7 +1028,6 @@ menu_urlmenu (GdkEventButton *event, char *url)
str_copy = g_strdup (url); str_copy = g_strdup (url);
menu = menu_new (); menu = menu_new ();
/* more than 51 chars? Chop it */
if (g_utf8_strlen (str_copy, -1) >= 52) if (g_utf8_strlen (str_copy, -1) >= 52)
{ {
tmp = g_strdup (str_copy); tmp = g_strdup (str_copy);
@@ -1055,14 +1042,12 @@ menu_urlmenu (GdkEventButton *event, char *url)
} }
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
/* Two hardcoded entries */
if (strncmp (str_copy, "irc://", 6) == 0 || if (strncmp (str_copy, "irc://", 6) == 0 ||
strncmp (str_copy, "ircs://",7) == 0) strncmp (str_copy, "ircs://",7) == 0)
menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy); menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
else else
menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy); menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy); menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
/* custom ones from urlhandlers.conf */
menu_create (menu, urlhandler_list, str_copy, TRUE); menu_create (menu, urlhandler_list, str_copy, TRUE);
menu_add_plugin_items (menu, "\x4$URL", str_copy); menu_add_plugin_items (menu, "\x4$URL", str_copy);
menu_popup (menu, event, NULL); menu_popup (menu, event, NULL);
@@ -1330,7 +1315,6 @@ menu_newserver_tab (GtkWidget * wid, gpointer none)
int oldf = prefs.hex_gui_tab_newtofront; int oldf = prefs.hex_gui_tab_newtofront;
prefs.hex_gui_tab_chans = 1; prefs.hex_gui_tab_chans = 1;
/* force focus if setting is "only requested tabs" */
if (prefs.hex_gui_tab_newtofront == 2) if (prefs.hex_gui_tab_newtofront == 2)
prefs.hex_gui_tab_newtofront = 1; prefs.hex_gui_tab_newtofront = 1;
new_ircwindow (NULL, NULL, SESS_SERVER, 0); new_ircwindow (NULL, NULL, SESS_SERVER, 0);
@@ -1714,10 +1698,67 @@ menu_ctcpguiopen (void)
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help); editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
} }
/* Returns the index.html of a locally installed copy of the documentation,
* or NULL if none is available. Callers own the returned string. */
static char *
menu_docs_local_index (void)
{
const char *env_docdir;
char *path;
env_docdir = g_getenv ("ZOITECHAT_DOCDIR");
if (env_docdir && env_docdir[0])
{
path = g_build_filename (env_docdir, "index.html", NULL);
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
return path;
g_free (path);
}
/* Docs the user placed in the config dir; works on every platform,
* including portable installs and Flatpak (host-readable, unlike /app) */
path = g_build_filename (get_xdir (), "offline-docs", "index.html", NULL);
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
return path;
g_free (path);
#ifdef WIN32
/* The installer's "Offline Documentation" component; ZOITECHATDOCDIR is
* relative to the install dir on Windows and must not be resolved
* against the CWD, which depends on how the app was launched */
if (!g_path_is_absolute (ZOITECHATDOCDIR))
{
char *install_dir = g_win32_get_package_installation_directory_of_module (NULL);
if (!install_dir)
return NULL;
path = g_build_filename (install_dir, ZOITECHATDOCDIR, "index.html", NULL);
g_free (install_dir);
}
else
path = g_build_filename (ZOITECHATDOCDIR, "index.html", NULL);
#else
path = g_build_filename (ZOITECHATDOCDIR, "index.html", NULL);
#endif
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
return path;
g_free (path);
return NULL;
}
static void static void
menu_docs (GtkWidget *wid, gpointer none) menu_docs (GtkWidget *wid, gpointer none)
{ {
fe_open_url ("https://docs.zoitechat.zoite.net/en/latest/"); char *local_index = menu_docs_local_index ();
if (local_index)
{
fe_open_url (local_index);
g_free (local_index);
}
else
fe_open_url ("https://docs.zoitechat.org/en/latest/");
} }
/*static void /*static void
@@ -1755,44 +1796,6 @@ menu_change_layout (void)
} }
} }
void
menu_update_quit_accel (void)
{
GSList *list;
list = sess_list;
while (list)
{
session *sess = list->data;
session_gui *gui = sess->gui;
GtkWidget *item;
GtkAccelGroup *accel_group;
int enabled;
list = list->next;
if (!gui)
continue;
item = gui->menu_item[MENU_ID_QUIT];
if (!item)
continue;
enabled = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "zc-ctrlq-enabled"));
if (enabled == (int)prefs.hex_gui_ctrlq_quit)
continue;
accel_group = g_object_get_data (G_OBJECT (item), "zc-quit-accel-group");
if (!accel_group)
continue;
if (prefs.hex_gui_ctrlq_quit)
gtk_widget_add_accelerator (item, "activate", accel_group, GDK_KEY_q, STATE_CTRL, GTK_ACCEL_VISIBLE);
else
gtk_widget_remove_accelerator (item, accel_group, GDK_KEY_q, STATE_CTRL);
g_object_set_data (G_OBJECT (item), "zc-ctrlq-enabled", GINT_TO_POINTER (prefs.hex_gui_ctrlq_quit));
}
}
static void static void
menu_layout_cb (GtkWidget *item, gpointer none) menu_layout_cb (GtkWidget *item, gpointer none)
{ {
@@ -1862,7 +1865,7 @@ about_dialog_response (GtkDialog *dialog, int response, gpointer data)
{ {
if (response == GTK_RESPONSE_HELP) if (response == GTK_RESPONSE_HELP)
{ {
fe_open_url ("http://zoitechat.zoite.net"); fe_open_url ("http://zoitechat.org");
return; return;
} }
if (response == GTK_RESPONSE_APPLY) if (response == GTK_RESPONSE_APPLY)
@@ -1939,13 +1942,13 @@ menu_about (GtkWidget *wid, gpointer sess)
static struct mymenu mymenu[] = { static struct mymenu mymenu[] = {
{N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1}, {N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1},
{N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_s}, {N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
{N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1}, {N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1},
{N_("Server Tab"), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_t}, {N_("Server Tab"), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1},
{N_("Channel Tab"), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1}, {N_("Channel Tab"), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1},
{N_("Server Window"), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_n}, {N_("Server Window"), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1},
{N_("Channel Window"), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1}, {N_("Channel Window"), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0}, {0, 0, 0, M_END, 0, 0, 0},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
@@ -1957,13 +1960,13 @@ static struct mymenu mymenu[] = {
#define CLOSE_OFFSET (13) #define CLOSE_OFFSET (13)
{0, menu_close, 0, M_MENUITEM, 0, 0, 1}, {0, menu_close, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1, GDK_KEY_q}, /* 15 */ {N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1}, /* 15 */
{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1}, {N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
#define MENUBAR_OFFSET (17) #define MENUBAR_OFFSET (17)
{N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_KEY_F9}, {N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1},
{N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1}, {N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1},
{N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_KEY_F7}, {N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1},
{N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1}, {N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1}, {N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
@@ -1980,7 +1983,7 @@ static struct mymenu mymenu[] = {
{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1}, {N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0}, /* 32 */ {0, 0, 0, M_END, 0, 0, 0}, /* 32 */
{ 0, 0, 0, M_SEP, 0, 0, 0 }, { 0, 0, 0, M_SEP, 0, 0, 0 },
{N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1, GDK_KEY_F11}, {N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1},
{N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1}, {N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1},
{N_("_Disconnect"), menu_disconnect, 0, M_MENUITEM, MENU_ID_DISCONNECT, 0, 1}, {N_("_Disconnect"), menu_disconnect, 0, M_MENUITEM, MENU_ID_DISCONNECT, 0, 1},
@@ -1989,7 +1992,7 @@ static struct mymenu mymenu[] = {
{N_("Channel _List"), menu_chanlist, 0, M_MENUITEM, 0, 0, 1}, {N_("Channel _List"), menu_chanlist, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
#define AWAY_OFFSET (41) #define AWAY_OFFSET (41)
{N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1, GDK_KEY_a}, {N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1},
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */ {N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */
@@ -2017,25 +2020,191 @@ static struct mymenu mymenu[] = {
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */ {N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */
{N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1}, {N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, {0, 0, 0, M_SEP, 0, 0, 0},
{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_m}, {N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1},
{N_("Move to Marker Line"), menu_movetomarker, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_M}, {N_("Move to Marker Line"), menu_movetomarker, 0, M_MENUITEM, 0, 0, 1},
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_C}, {N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1}, {N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1}, {N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
#define SEARCH_OFFSET (70) #define SEARCH_OFFSET (70)
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1}, {N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_f}, {N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_g}, {N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
{N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_G}, {N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0}, {0, 0, 0, M_END, 0, 0, 0},
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */ {N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */
{N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1, GDK_KEY_F1}, {N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1},
{N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1}, {N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0}, {0, 0, 0, M_END, 0, 0, 0},
}; };
static const char *
menu_get_key_action_name (int index)
{
switch (index)
{
case 1:
return "network-list";
case 4:
return "new-server-tab";
case 6:
return "new-server-window";
case CLOSE_OFFSET:
return "close";
case 15:
return "quit";
case MENUBAR_OFFSET:
return "menu-toggle";
case MENUBAR_OFFSET + 2:
return "user-list-toggle";
case 34:
return "fullscreen-toggle";
case AWAY_OFFSET:
return "away-toggle";
case 65:
return "reset-marker";
case 66:
return "move-marker";
case 67:
return "copy-selection";
case SEARCH_OFFSET + 1:
return "search-text";
case SEARCH_OFFSET + 2:
return "search-next";
case SEARCH_OFFSET + 3:
return "search-previous";
case 75:
return "contents";
}
return NULL;
}
static void
menu_add_keybinding_accel (GtkWidget *item, GtkAccelGroup *accel_group, const char *name)
{
guint keyval;
GdkModifierType mod;
if (!accel_group || !key_get_menu_accel (name, &keyval, &mod))
return;
if (!strcmp (name, "quit") && !prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q && mod == STATE_CTRL)
return;
gtk_widget_add_accelerator (item, "activate", accel_group, keyval, mod, GTK_ACCEL_VISIBLE);
g_object_set_data (G_OBJECT (item), "zc-key-accel-key", GUINT_TO_POINTER (keyval));
g_object_set_data (G_OBJECT (item), "zc-key-accel-mod", GUINT_TO_POINTER (mod));
}
static void
menu_refresh_keybinding_accels (GtkWidget *widget, gpointer data)
{
GtkAccelGroup *accel_group = data;
const char *name;
guint keyval;
GdkModifierType mod;
GtkWidget *submenu;
GList *children, *list;
if (GTK_IS_MENU_ITEM (widget))
{
keyval = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "zc-key-accel-key"));
mod = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "zc-key-accel-mod"));
if (keyval != 0)
{
gtk_widget_remove_accelerator (widget, accel_group, keyval, mod);
g_object_set_data (G_OBJECT (widget), "zc-key-accel-key", NULL);
g_object_set_data (G_OBJECT (widget), "zc-key-accel-mod", NULL);
}
name = g_object_get_data (G_OBJECT (widget), "zc-key-action");
menu_add_keybinding_accel (widget, accel_group, name);
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
if (submenu)
menu_refresh_keybinding_accels (submenu, data);
}
if (GTK_IS_CONTAINER (widget))
{
children = gtk_container_get_children (GTK_CONTAINER (widget));
for (list = children; list; list = g_list_next (list))
menu_refresh_keybinding_accels (list->data, data);
g_list_free (children);
}
}
void
menu_update_quit_accel (void)
{
session *sess;
GSList *list;
GtkAccelGroup *accel_group;
list = sess_list;
while (list)
{
sess = list->data;
if (sess && sess->gui && sess->gui->menu)
{
accel_group = g_object_get_data (G_OBJECT (sess->gui->menu), "accel");
if (accel_group)
menu_refresh_keybinding_accels (sess->gui->menu, accel_group);
}
list = g_slist_next (list);
}
}
gboolean
menu_key_action (const char *name, guint keyval, GdkModifierType state)
{
if (!name)
return FALSE;
if (!strcmp (name, "network-list"))
menu_open_server_list (NULL, NULL);
else if (!strcmp (name, "new-server-tab"))
menu_newserver_tab (NULL, NULL);
else if (!strcmp (name, "new-server-window"))
menu_newserver_window (NULL, NULL);
else if (!strcmp (name, "close"))
menu_close (NULL, NULL);
else if (!strcmp (name, "quit"))
{
if (!prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q && (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == STATE_CTRL)
return FALSE;
menu_quit (NULL, NULL);
}
else if (!strcmp (name, "menu-toggle"))
menu_bar_toggle_cb ();
else if (!strcmp (name, "user-list-toggle"))
menu_userlist_toggle (NULL, NULL);
else if (!strcmp (name, "fullscreen-toggle"))
menu_fullscreen_toggle (NULL, NULL);
else if (!strcmp (name, "away-toggle"))
menu_away_toggle (NULL, NULL);
else if (!strcmp (name, "reset-marker"))
menu_resetmarker (NULL, NULL);
else if (!strcmp (name, "move-marker"))
menu_movetomarker (NULL, NULL);
else if (!strcmp (name, "copy-selection"))
menu_copy_selection (NULL, NULL);
else if (!strcmp (name, "search-text"))
menu_search ();
else if (!strcmp (name, "search-next"))
menu_search_next (NULL);
else if (!strcmp (name, "search-previous"))
menu_search_prev (NULL);
else if (!strcmp (name, "contents"))
menu_docs (NULL, NULL);
else
return FALSE;
return TRUE;
}
void void
menu_set_away (session_gui *gui, int away) menu_set_away (session_gui *gui, int away)
{ {
@@ -2098,7 +2267,6 @@ menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data)
return gtk_widget_is_sensitive (widget); return gtk_widget_is_sensitive (widget);
} }
/* === STUFF FOR /MENU === */
static GtkMenuItem * static GtkMenuItem *
menu_find_item (GtkWidget *menu, char *name) menu_find_item (GtkWidget *menu, char *name)
@@ -2164,7 +2332,6 @@ menu_find_path (GtkWidget *menu, char *path)
char name[128]; char name[128];
int len; int len;
/* grab the next part of the path */
s = strchr (path, '/'); s = strchr (path, '/');
len = s - path; len = s - path;
if (!s) if (!s)
@@ -2218,7 +2385,6 @@ menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, c
list = list->next; list = list->next;
continue; continue;
} }
/* do it only once for tab sessions, since they share a GUI */
if (!sess->gui->is_tab || !tabdone) if (!sess->gui->is_tab || !tabdone)
{ {
callback (sess->gui->menu, me, NULL); callback (sess->gui->menu, me, NULL);
@@ -2238,13 +2404,11 @@ menu_update_cb (GtkWidget *menu, menu_entry *me, char *target)
if (item) if (item)
{ {
gtk_widget_set_sensitive (item, me->enable); gtk_widget_set_sensitive (item, me->enable);
/* must do it without triggering the callback */
if (GTK_IS_CHECK_MENU_ITEM (item)) if (GTK_IS_CHECK_MENU_ITEM (item))
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), me->state); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), me->state);
} }
} }
/* radio state changed via mouse click */
static void static void
menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me) menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
{ {
@@ -2252,15 +2416,12 @@ menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
if (gtk_check_menu_item_get_active (item)) if (gtk_check_menu_item_get_active (item))
me->state = 1; me->state = 1;
/* update the state, incase this was changed via right-click. */
/* This will update all other windows and menu bars */
menu_foreach_gui (me, menu_update_cb); menu_foreach_gui (me, menu_update_cb);
if (me->state && me->cmd) if (me->state && me->cmd)
handle_command (current_sess, me->cmd, FALSE); handle_command (current_sess, me->cmd, FALSE);
} }
/* toggle state changed via mouse click */
static void static void
menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me) menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
{ {
@@ -2268,8 +2429,6 @@ menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
if (gtk_check_menu_item_get_active (item)) if (gtk_check_menu_item_get_active (item))
me->state = 1; me->state = 1;
/* update the state, incase this was changed via right-click. */
/* This will update all other windows and menu bars */
menu_foreach_gui (me, menu_update_cb); menu_foreach_gui (me, menu_update_cb);
if (me->state) if (me->state)
@@ -2309,7 +2468,7 @@ menu_reorder (GtkMenu *menu, GtkWidget *item, int pos)
if (pos < 0) /* position offset from end/bottom */ if (pos < 0) /* position offset from end/bottom */
{ {
GList *children = gtk_container_get_children (GTK_CONTAINER (menu)); GList *children = gtk_container_get_children (GTK_CONTAINER (menu));
int length = g_list_length (children); gint length = (gint) g_list_length (children);
g_list_free (children); g_list_free (children);
gtk_menu_reorder_child (menu, item, (length + pos) - 1); gtk_menu_reorder_child (menu, item, (length + pos) - 1);
@@ -2381,7 +2540,7 @@ menu_add_sub (GtkWidget *menu, menu_entry *me)
if (pos < 0) /* position offset from end/bottom */ if (pos < 0) /* position offset from end/bottom */
{ {
GList *children = gtk_container_get_children (GTK_CONTAINER (menu)); GList *children = gtk_container_get_children (GTK_CONTAINER (menu));
int length = g_list_length (children); gint length = (gint) g_list_length (children);
g_list_free (children); g_list_free (children);
pos = length + pos; pos = length + pos;
@@ -2440,7 +2599,6 @@ fe_menu_add (menu_entry *me)
if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL)) if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL))
return NULL; return NULL;
/* return the label with markup stripped */
return text; return text;
} }
@@ -2456,7 +2614,6 @@ fe_menu_update (menu_entry *me)
menu_foreach_gui (me, menu_update_cb); menu_foreach_gui (me, menu_update_cb);
} }
/* used to add custom menus to the right-click menu */
static void static void
menu_add_plugin_mainmenu_items (GtkWidget *menu) menu_add_plugin_mainmenu_items (GtkWidget *menu)
@@ -2490,7 +2647,6 @@ menu_add_plugin_items (GtkWidget *menu, char *root, char *target)
} }
} }
/* === END STUFF FOR /MENU === */
GtkWidget * GtkWidget *
menu_create_main (void *accel_group, int bar, int away, int toplevel, menu_create_main (void *accel_group, int bar, int away, int toplevel,
@@ -2523,13 +2679,11 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
else else
menu_bar = menu_new (); menu_bar = menu_new ();
/* /MENU needs to know this later */
g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group); g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group);
g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel", g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel",
G_CALLBACK (menu_canacaccel), 0); G_CALLBACK (menu_canacaccel), 0);
/* set the initial state of toggles */
mymenu[MENUBAR_OFFSET].state = !prefs.hex_gui_hide_menu; mymenu[MENUBAR_OFFSET].state = !prefs.hex_gui_hide_menu;
mymenu[MENUBAR_OFFSET+1].state = prefs.hex_gui_topicbar; mymenu[MENUBAR_OFFSET+1].state = prefs.hex_gui_topicbar;
mymenu[MENUBAR_OFFSET+2].state = !prefs.hex_gui_ulist_hide; mymenu[MENUBAR_OFFSET+2].state = !prefs.hex_gui_ulist_hide;
@@ -2568,7 +2722,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
mymenu[METRE_OFFSET+3].state = 1; mymenu[METRE_OFFSET+3].state = 1;
} }
/* change Close binding to ctrl-shift-w when using emacs keys */
settings = gtk_widget_get_settings (menu_bar); settings = gtk_widget_get_settings (menu_bar);
if (settings) if (settings)
{ {
@@ -2584,7 +2737,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
} }
} }
/* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */
{ {
char *help = _("_Help"); char *help = _("_Help");
char *under = strchr (help, '_'); char *under = strchr (help, '_');
@@ -2621,7 +2773,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
if (mymenu[i].id == MENU_ID_USERMENU) if (mymenu[i].id == MENU_ID_USERMENU)
usermenu = menu; usermenu = menu;
menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
/* record the English name for /menu */
g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text); g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text);
#ifdef HAVE_GTK_MAC /* Added to app menu, see below */ #ifdef HAVE_GTK_MAC /* Added to app menu, see below */
if (!bar || mymenu[i].id != MENU_ID_ZOITECHAT) if (!bar || mymenu[i].id != MENU_ID_ZOITECHAT)
@@ -2641,6 +2792,8 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
case M_MENUITEM: case M_MENUITEM:
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
normalitem: normalitem:
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit)) if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
gtk_widget_add_accelerator (item, "activate", accel_group, gtk_widget_add_accelerator (item, "activate", accel_group,
mymenu[i].key, mymenu[i].key,
@@ -2669,7 +2822,8 @@ normalitem:
case M_MENUTOG: case M_MENUTOG:
item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text)); item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
togitem: togitem:
/* must avoid callback for Radio buttons */ g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state);
/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), /*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
mymenu[i].state);*/ mymenu[i].state);*/
@@ -2709,7 +2863,6 @@ togitem:
group = NULL; group = NULL;
submenu = menu_new (); submenu = menu_new ();
item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE); item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
/* record the English name for /menu */
g_object_set_data (G_OBJECT (item), "name", mymenu[i].text); g_object_set_data (G_OBJECT (item), "name", mymenu[i].text);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
@@ -2731,13 +2884,10 @@ togitem:
submenu = NULL; submenu = NULL;
} }
/* record this GtkWidget * so it's state might be changed later */
if (mymenu[i].id != 0 && menu_widgets) if (mymenu[i].id != 0 && menu_widgets)
/* this ends up in sess->gui->menu_item[MENU_ID_XXX] */
menu_widgets[mymenu[i].id] = item; menu_widgets[mymenu[i].id] = item;
#ifdef HAVE_GTK_MAC #ifdef HAVE_GTK_MAC
/* We want ZoiteChat to be the app menu, not including Quit or ZoiteChat itself */
if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT) if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT)
{ {
if (!submenu || mymenu[i].type == M_MENUSUB) if (!submenu || mymenu[i].type == M_MENUSUB)

View File

@@ -39,6 +39,7 @@ void menu_bar_toggle (void);
void menu_add_plugin_items (GtkWidget *menu, char *root, char *target); void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
void menu_change_layout (void); void menu_change_layout (void);
void menu_update_quit_accel (void); void menu_update_quit_accel (void);
gboolean menu_key_action (const char *name, guint keyval, GdkModifierType state);
void menu_set_away (session_gui *gui, int away); void menu_set_away (session_gui *gui, int away);
void menu_set_fullscreen (session_gui *gui, int fullscreen); void menu_set_fullscreen (session_gui *gui, int fullscreen);

View File

@@ -50,7 +50,7 @@ zoitechat_gtk_deps = [
gtk_dep = dependency('gtk+-3.0', version: '>= 3.22') gtk_dep = dependency('gtk+-3.0', version: '>= 3.22')
zoitechat_theme_deps = [gtk_dep] zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep]
if host_machine.system() != 'windows' if host_machine.system() != 'windows'
appindicator_opt = get_option('appindicator') appindicator_opt = get_option('appindicator')
@@ -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],

View File

@@ -56,7 +56,7 @@ notification_backend_show (const char *title, const char *text)
g_variant_builder_init (&params, G_VARIANT_TYPE ("(susssasa{sv}i)")); g_variant_builder_init (&params, G_VARIANT_TYPE ("(susssasa{sv}i)"));
g_variant_builder_add (&params, "s", "zoitechat"); /* App name */ g_variant_builder_add (&params, "s", "zoitechat"); /* App name */
g_variant_builder_add (&params, "u", 0); /* ID, 0 means don't replace */ g_variant_builder_add (&params, "u", 0); /* ID, 0 means don't replace */
g_variant_builder_add (&params, "s", "io.github.Hexchat"); /* App icon */ g_variant_builder_add (&params, "s", "net.zoite.Zoitechat"); /* App icon */
g_variant_builder_add (&params, "s", title); g_variant_builder_add (&params, "s", title);
g_variant_builder_add (&params, "s", text); g_variant_builder_add (&params, "s", text);
g_variant_builder_add (&params, "as", NULL); /* Actions */ g_variant_builder_add (&params, "as", NULL); /* Actions */
@@ -65,7 +65,7 @@ notification_backend_show (const char *title, const char *text)
g_variant_builder_open (&params, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_open (&params, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_open (&params, G_VARIANT_TYPE ("{sv}")); g_variant_builder_open (&params, G_VARIANT_TYPE ("{sv}"));
g_variant_builder_add (&params, "s", "desktop-entry"); g_variant_builder_add (&params, "s", "desktop-entry");
g_variant_builder_add (&params, "v", g_variant_new_string ("io.github.Hexchat")); g_variant_builder_add (&params, "v", g_variant_new_string ("net.zoite.Zoitechat"));
g_variant_builder_close (&params); g_variant_builder_close (&params);
g_variant_builder_close (&params); g_variant_builder_close (&params);

View File

@@ -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);
} }
@@ -920,7 +926,10 @@ tray_menu_notify_cb (GObject *tray, GParamSpec *pspec, gpointer user_data)
{ {
if (!tray_backend_is_embedded ()) if (!tray_backend_is_embedded ())
{ {
tray_restore_timer = g_timeout_add(500, (GSourceFunc)tray_menu_try_restore, NULL); if (!tray_restore_timer)
{
tray_restore_timer = g_timeout_add (500, (GSourceFunc) tray_menu_try_restore, NULL);
}
} }
else else
{ {
@@ -936,9 +945,10 @@ tray_menu_notify_cb (GObject *tray, GParamSpec *pspec, gpointer user_data)
static gboolean static gboolean
tray_menu_try_restore (void) tray_menu_try_restore (void)
{ {
tray_cleanup(); tray_restore_timer = 0;
tray_init(); tray_cleanup ();
return TRUE; tray_init ();
return G_SOURCE_REMOVE;
} }
static void static void
@@ -1085,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)
{ {
@@ -1212,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;
@@ -1251,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

View File

@@ -29,6 +29,7 @@
#include "../common/servlist.h" #include "../common/servlist.h"
#include "../common/cfgfiles.h" #include "../common/cfgfiles.h"
#include "../common/fe.h" #include "../common/fe.h"
#include "../common/secretstore.h"
#include "../common/util.h" #include "../common/util.h"
#include "fe-gtk.h" #include "fe-gtk.h"
@@ -85,6 +86,12 @@ static GtkWidget *edit_entry_nick2;
static GtkWidget *edit_entry_user; static GtkWidget *edit_entry_user;
static GtkWidget *edit_entry_real; static GtkWidget *edit_entry_real;
static GtkWidget *edit_entry_pass; static GtkWidget *edit_entry_pass;
static GtkWidget *edit_check_show_pass;
static GtkWidget *edit_check_use_keyring;
static GtkWidget *edit_button_encrypt_pass;
static GtkWidget *edit_button_import_pass;
static int edit_pass_changed;
static char *edit_loaded_password;
static GtkWidget *edit_label_nick; static GtkWidget *edit_label_nick;
static GtkWidget *edit_label_nick2; static GtkWidget *edit_label_nick2;
static GtkWidget *edit_label_real; static GtkWidget *edit_label_real;
@@ -103,6 +110,168 @@ static session *servlist_sess;
static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data); static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data);
static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net); static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net);
static void servlist_password_changed_cb (GtkEditable *editable, gpointer userdata);
static void
servlist_update_password_tools (ircnet *net)
{
gboolean has_local;
gboolean use_keyring;
if (!edit_button_encrypt_pass || !edit_button_import_pass)
return;
use_keyring = net && (net->flags & FLAG_USE_KEYRING);
has_local = net && net->pass && *net->pass && !use_keyring && !edit_pass_changed;
gtk_widget_set_sensitive (edit_button_encrypt_pass, has_local && !servlist_password_is_encrypted (net->pass));
gtk_widget_set_sensitive (edit_button_import_pass, has_local);
}
static void
servlist_entry_set_text_silent (GtkWidget *entry, const char *text)
{
g_signal_handlers_block_by_func (G_OBJECT (entry), G_CALLBACK (servlist_password_changed_cb), NULL);
gtk_entry_set_text (GTK_ENTRY (entry), text);
g_signal_handlers_unblock_by_func (G_OBJECT (entry), G_CALLBACK (servlist_password_changed_cb), NULL);
}
static char *
servlist_display_password (ircnet *net)
{
if (!net)
return NULL;
if (edit_pass_changed)
return g_strdup (gtk_entry_get_text (GTK_ENTRY (edit_entry_pass)));
if (edit_loaded_password)
return g_strdup (edit_loaded_password);
if (net->flags & FLAG_USE_KEYRING)
return secretstore_get_network_password (net->name);
return servlist_password_decrypt_for_storage (net->pass);
}
static void
servlist_toggle_show_password_cb (GtkToggleButton *toggle, gpointer userdata)
{
if (gtk_toggle_button_get_active (toggle))
{
char *password = servlist_display_password (selected_net);
if (password)
{
if (edit_loaded_password)
{
memset (edit_loaded_password, 0, strlen (edit_loaded_password));
g_free (edit_loaded_password);
}
edit_loaded_password = g_strdup (password);
servlist_entry_set_text_silent (userdata, password);
memset (password, 0, strlen (password));
g_free (password);
}
gtk_entry_set_visibility (GTK_ENTRY (userdata), TRUE);
}
else
{
gtk_entry_set_visibility (GTK_ENTRY (userdata), FALSE);
if (edit_loaded_password && !edit_pass_changed)
servlist_entry_set_text_silent (userdata, "***");
}
}
static void
servlist_toggle_keyring_cb (GtkToggleButton *toggle, gpointer userdata)
{
servlist_update_password_tools (selected_net);
}
static void
servlist_password_changed_cb (GtkEditable *editable, gpointer userdata)
{
edit_pass_changed = 1;
if (edit_loaded_password && strcmp (gtk_entry_get_text (GTK_ENTRY (editable)), "***"))
{
memset (edit_loaded_password, 0, strlen (edit_loaded_password));
g_free (edit_loaded_password);
edit_loaded_password = NULL;
}
servlist_update_password_tools (selected_net);
}
static void
servlist_encrypt_password_cb (GtkWidget *button, gpointer userdata)
{
ircnet *net = userdata;
char *plain;
char *enc;
if (!net || (net->flags & FLAG_USE_KEYRING) || !net->pass || servlist_password_is_encrypted (net->pass))
return;
plain = servlist_password_decrypt_for_storage (net->pass);
if (!plain || !*plain)
{
if (plain)
{
memset (plain, 0, strlen (plain));
g_free (plain);
}
return;
}
enc = servlist_password_encrypt_for_storage (plain);
memset (plain, 0, strlen (plain));
g_free (plain);
if (!enc)
{
fe_message (_("Could not encrypt this password."), FE_MSG_WARN);
return;
}
g_free (net->pass);
net->pass = enc;
servlist_save ();
servlist_update_password_tools (net);
}
static void
servlist_import_password_cb (GtkWidget *button, gpointer userdata)
{
ircnet *net = userdata;
char *plain;
if (!net || !net->name || (net->flags & FLAG_USE_KEYRING) || !net->pass || !*net->pass)
return;
plain = servlist_password_decrypt_for_storage (net->pass);
if (!plain || !*plain)
{
if (plain)
{
memset (plain, 0, strlen (plain));
g_free (plain);
}
return;
}
if (!secretstore_set_network_password (net->name, plain))
{
memset (plain, 0, strlen (plain));
g_free (plain);
fe_message (_("Could not move this password into the system keyring."), FE_MSG_WARN);
return;
}
memset (plain, 0, strlen (plain));
g_free (plain);
g_free (net->pass);
net->pass = NULL;
net->flags |= FLAG_USE_KEYRING;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring), TRUE);
servlist_entry_set_text_silent (edit_entry_pass, "***");
edit_pass_changed = 0;
servlist_save ();
servlist_update_password_tools (net);
}
static char * static char *
servlist_get_cert_file (ircnet *net) servlist_get_cert_file (ircnet *net)
@@ -1078,14 +1247,79 @@ servlist_update_from_entry (char **str, GtkWidget *entry)
*str = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); *str = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
} }
static char *
servlist_edit_current_password (ircnet *net)
{
if (!net)
return NULL;
if (net->flags & FLAG_USE_KEYRING)
return secretstore_get_network_password (net->name);
return servlist_password_decrypt_for_storage (net->pass);
}
static void static void
servlist_edit_update (ircnet *net) servlist_edit_update (ircnet *net)
{ {
gboolean use_keyring;
gboolean keyring_changed;
char *password = NULL;
servlist_update_from_entry (&net->nick, edit_entry_nick); servlist_update_from_entry (&net->nick, edit_entry_nick);
servlist_update_from_entry (&net->nick2, edit_entry_nick2); servlist_update_from_entry (&net->nick2, edit_entry_nick2);
servlist_update_from_entry (&net->user, edit_entry_user); servlist_update_from_entry (&net->user, edit_entry_user);
servlist_update_from_entry (&net->real, edit_entry_real); servlist_update_from_entry (&net->real, edit_entry_real);
servlist_update_from_entry (&net->pass, edit_entry_pass); if (net && net->name)
{
use_keyring = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring));
keyring_changed = !!(net->flags & FLAG_USE_KEYRING) != !!use_keyring;
if (!edit_pass_changed && !keyring_changed)
return;
if (edit_pass_changed)
password = g_strdup (gtk_entry_get_text (GTK_ENTRY (edit_entry_pass)));
else
password = servlist_edit_current_password (net);
if (use_keyring)
{
if (password && *password)
{
if (!secretstore_set_network_password (net->name, password))
{
fe_message (_("No system keyring is available. ZoiteChat can save this password using local encrypted fallback storage, but it is less protected than your desktop keyring."), FE_MSG_WARN);
memset (password, 0, strlen (password));
g_free (password);
return;
}
}
else
secretstore_delete_network_password (net->name);
net->flags |= FLAG_USE_KEYRING;
g_free (net->pass);
net->pass = NULL;
}
else
{
char *enc = NULL;
if (password && *password)
{
enc = servlist_password_encrypt_for_storage (password);
if (!enc)
{
fe_message (_("Could not encrypt this password."), FE_MSG_WARN);
memset (password, 0, strlen (password));
g_free (password);
return;
}
}
secretstore_delete_network_password (net->name);
net->flags &= ~FLAG_USE_KEYRING;
g_free (net->pass);
net->pass = enc;
}
if (password)
{
memset (password, 0, strlen (password));
g_free (password);
}
}
} }
static void static void
@@ -1093,9 +1327,19 @@ servlist_edit_close_cb (GtkWidget *button, gpointer userdata)
{ {
if (selected_net) if (selected_net)
servlist_edit_update (selected_net); servlist_edit_update (selected_net);
if (edit_loaded_password)
{
memset (edit_loaded_password, 0, strlen (edit_loaded_password));
g_free (edit_loaded_password);
edit_loaded_password = NULL;
}
gtk_widget_destroy (edit_win); gtk_widget_destroy (edit_win);
edit_win = NULL; edit_win = NULL;
edit_entry_pass = NULL;
edit_check_show_pass = NULL;
edit_button_encrypt_pass = NULL;
edit_button_import_pass = NULL;
} }
static gint static gint
@@ -1126,6 +1370,10 @@ servlist_edit_cb (GtkWidget *but, gpointer none)
{ {
if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree)))
return; return;
if (!selected_net || !selected_net->name)
return;
if ((selected_net->flags & FLAG_USE_KEYRING) && !secretstore_require_unlock (selected_net->name))
return;
edit_win = servlist_open_edit (serverlist_win, selected_net); edit_win = servlist_open_edit (serverlist_win, selected_net);
gtkutil_set_icon (edit_win); gtkutil_set_icon (edit_win);
@@ -2317,7 +2565,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
/* Checkboxes and entries */ /* Checkboxes and entries */
table3 = gtkutil_grid_new (14, 2, FALSE); table3 = gtkutil_grid_new (17, 2, FALSE);
gtk_box_pack_start (GTK_BOX (vbox5), table3, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox5), table3, FALSE, FALSE, 0);
gtk_grid_set_row_spacing (GTK_GRID (table3), 2); gtk_grid_set_row_spacing (GTK_GRID (table3), 2);
gtk_grid_set_column_spacing (GTK_GRID (table3), 8); gtk_grid_set_column_spacing (GTK_GRID (table3), 8);
@@ -2336,38 +2584,97 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
#endif #endif
servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, 5, 0, _("Use global user information")); servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, 5, 0, _("Use global user information"));
edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 6, net->nick, &edit_label_nick, 0); edit_check_use_keyring = gtk_check_button_new_with_mnemonic (_("Use system keyring"));
edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 7, net->nick2, &edit_label_nick2, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring), net->flags & FLAG_USE_KEYRING);
edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, &edit_label_real, 0); servlist_table_attach (table3, edit_check_use_keyring, 0, 2, 6, 7,
edit_entry_user = servlist_create_entry (table3, _("_User name:"), 9, net->user, &edit_label_user, 0); FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
g_signal_connect (G_OBJECT (edit_check_use_keyring), "toggled",
G_CALLBACK (servlist_toggle_keyring_cb), NULL);
edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 7, net->nick, &edit_label_nick, 0);
edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 8, net->nick2, &edit_label_nick2, 0);
edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 9, net->real, &edit_label_real, 0);
edit_entry_user = servlist_create_entry (table3, _("_User name:"), 10, net->user, &edit_label_user, 0);
label_logintype = gtk_label_new (_("Login method:")); label_logintype = gtk_label_new (_("Login method:"));
servlist_table_attach (table3, label_logintype, 0, 1, 10, 11, servlist_table_attach (table3, label_logintype, 0, 1, 11, 12,
FALSE, FALSE, FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
SERVLIST_X_PADDING, SERVLIST_Y_PADDING); SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
gtk_widget_set_halign (label_logintype, GTK_ALIGN_START); gtk_widget_set_halign (label_logintype, GTK_ALIGN_START);
gtk_widget_set_valign (label_logintype, GTK_ALIGN_CENTER); gtk_widget_set_valign (label_logintype, GTK_ALIGN_CENTER);
combobox_logintypes = servlist_create_logintypecombo (notebook); combobox_logintypes = servlist_create_logintypecombo (notebook);
servlist_table_attach (table3, combobox_logintypes, 1, 2, 10, 11, servlist_table_attach (table3, combobox_logintypes, 1, 2, 11, 12,
FALSE, FALSE, FALSE, FALSE,
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL,
4, 2); 4, 2);
edit_entry_pass = servlist_create_entry (table3, _("Password:"), 11, net->pass, 0, _("Password used for login. If in doubt, leave blank.")); edit_entry_pass = servlist_create_entry (table3, _("Password:"), 12, NULL, 0, _("Password used for login. If in doubt, leave blank."));
if (edit_loaded_password)
{
memset (edit_loaded_password, 0, strlen (edit_loaded_password));
g_free (edit_loaded_password);
edit_loaded_password = NULL;
}
edit_pass_changed = 0;
g_signal_connect (G_OBJECT (edit_entry_pass), "changed",
G_CALLBACK (servlist_password_changed_cb), NULL);
if (net->flags & FLAG_USE_KEYRING)
{
char *stored = secretstore_get_network_password (net->name);
if (stored && *stored)
{
edit_loaded_password = g_strdup (stored);
servlist_entry_set_text_silent (edit_entry_pass, "***");
}
if (stored)
{
memset (stored, 0, strlen (stored));
g_free (stored);
}
}
else if (net->pass && *net->pass)
{
servlist_entry_set_text_silent (edit_entry_pass, "***");
}
edit_pass_changed = 0;
gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE);
if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL) if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL)
gtk_widget_set_sensitive (edit_entry_pass, FALSE); gtk_widget_set_sensitive (edit_entry_pass, FALSE);
edit_check_show_pass = gtk_check_button_new_with_mnemonic (_("Show password"));
servlist_table_attach (table3, edit_check_show_pass, 0, 2, 13, 14,
FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
4, 2);
g_signal_connect (G_OBJECT (edit_check_show_pass), "toggled",
G_CALLBACK (servlist_toggle_show_password_cb), edit_entry_pass);
edit_button_encrypt_pass = gtk_button_new_with_mnemonic (_("Encrypt saved password"));
servlist_table_attach (table3, edit_button_encrypt_pass, 0, 1, 14, 15,
FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
g_signal_connect (G_OBJECT (edit_button_encrypt_pass), "clicked",
G_CALLBACK (servlist_encrypt_password_cb), net);
edit_button_import_pass = gtk_button_new_with_mnemonic (_("Move password to keyring"));
servlist_table_attach (table3, edit_button_import_pass, 1, 2, 14, 15,
FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
4, 2);
g_signal_connect (G_OBJECT (edit_button_import_pass), "clicked",
G_CALLBACK (servlist_import_password_cb), net);
label34 = gtk_label_new (_("Character set:")); label34 = gtk_label_new (_("Character set:"));
servlist_table_attach (table3, label34, 0, 1, 12, 13, servlist_table_attach (table3, label34, 0, 1, 15, 16,
FALSE, FALSE, FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
SERVLIST_X_PADDING, SERVLIST_Y_PADDING); SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
gtk_widget_set_halign (label34, GTK_ALIGN_START); gtk_widget_set_halign (label34, GTK_ALIGN_START);
gtk_widget_set_valign (label34, GTK_ALIGN_CENTER); gtk_widget_set_valign (label34, GTK_ALIGN_CENTER);
comboboxentry_charset = servlist_create_charsetcombo (); comboboxentry_charset = servlist_create_charsetcombo ();
servlist_table_attach (table3, comboboxentry_charset, 1, 2, 12, 13, servlist_table_attach (table3, comboboxentry_charset, 1, 2, 15, 16,
FALSE, FALSE, FALSE, FALSE,
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL,
4, 2); 4, 2);
@@ -2393,7 +2700,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
G_CALLBACK (servlist_delete_client_cert_cb), net); G_CALLBACK (servlist_delete_client_cert_cb), net);
gtk_box_pack_start (GTK_BOX (hbox_cert_buttons), edit_button_cert_delete, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox_cert_buttons), edit_button_cert_delete, FALSE, FALSE, 0);
servlist_table_attach (table3, hbox_cert_buttons, 0, 2, 13, 14, servlist_table_attach (table3, hbox_cert_buttons, 0, 2, 16, 17,
FALSE, FALSE, FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
SERVLIST_X_PADDING, SERVLIST_Y_PADDING); SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
@@ -2417,6 +2724,8 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
{ {
servlist_toggle_global_user (FALSE); servlist_toggle_global_user (FALSE);
} }
servlist_toggle_keyring_cb (GTK_TOGGLE_BUTTON (edit_check_use_keyring), NULL);
servlist_update_password_tools (net);
gtk_widget_grab_focus (button10); gtk_widget_grab_focus (button10);
gtk_widget_grab_default (button10); gtk_widget_grab_default (button10);

View File

@@ -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}
}; };
@@ -193,6 +198,7 @@ static const setting appearance_advanced_settings[] =
{ST_HEADER, N_("Advanced"),0,0,0}, {ST_HEADER, N_("Advanced"),0,0,0},
{ST_EFILE, N_ ("Background image:"), P_OFFSETNL (hex_text_background), 0, 0, sizeof prefs.hex_text_background}, {ST_EFILE, N_ ("Background image:"), P_OFFSETNL (hex_text_background), 0, 0, sizeof prefs.hex_text_background},
{ST_HSCALE, N_("Window opacity:"), P_OFFINTNL(hex_gui_transparency),0,0,0}, {ST_HSCALE, N_("Window opacity:"), P_OFFINTNL(hex_gui_transparency),0,0,0},
{ST_HSCALE, N_("Mouse wheel scroll speed (Slower ← → Faster):"), P_OFFINTNL(hex_gui_mouse_scroll_speed), 0, 0, 100},
{ST_END, 0, 0, 0, 0, 0} {ST_END, 0, 0, 0, 0, 0}
}; };
@@ -330,6 +336,7 @@ static const setting tabs_settings[] =
{ST_TOGGLE, N_("Show icons in the channel tree"), P_OFFINTNL(hex_gui_tab_icons), 0, 0, 0}, {ST_TOGGLE, N_("Show icons in the channel tree"), P_OFFINTNL(hex_gui_tab_icons), 0, 0, 0},
{ST_TOGGLE, N_("Show dotted lines in the channel tree"), P_OFFINTNL(hex_gui_tab_dots), 0, 0, 0}, {ST_TOGGLE, N_("Show dotted lines in the channel tree"), P_OFFINTNL(hex_gui_tab_dots), 0, 0, 0},
{ST_TOGGLE, N_("Scroll mouse-wheel to change tabs"), P_OFFINTNL (hex_gui_tab_scrollchans), 0, 0, 0}, {ST_TOGGLE, N_("Scroll mouse-wheel to change tabs"), P_OFFINTNL (hex_gui_tab_scrollchans), 0, 0, 0},
{ST_TOGGLE, N_("Show close button on tabs"), P_OFFINTNL(hex_gui_tab_closebuttons), 0, 0, 0},
{ST_TOGGLE, N_("Middle click to close tab"), P_OFFINTNL(hex_gui_tab_middleclose), 0, 0, 0}, {ST_TOGGLE, N_("Middle click to close tab"), P_OFFINTNL(hex_gui_tab_middleclose), 0, 0, 0},
{ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(hex_gui_tab_small), 0, 0, 0}, {ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(hex_gui_tab_small), 0, 0, 0},
{ST_MENU, N_("Focus new tabs:"), P_OFFINTNL(hex_gui_tab_newtofront), 0, focusnewtabsmenu, 0}, {ST_MENU, N_("Focus new tabs:"), P_OFFINTNL(hex_gui_tab_newtofront), 0, focusnewtabsmenu, 0},
@@ -366,7 +373,7 @@ static const setting filexfer_settings[] =
{ST_TOGGLE, N_("Receive window"), P_OFFINTNL(hex_gui_autoopen_recv), 0, 0, 0}, {ST_TOGGLE, N_("Receive window"), P_OFFINTNL(hex_gui_autoopen_recv), 0, 0, 0},
{ST_TOGGLE, N_("Chat window"), P_OFFINTNL(hex_gui_autoopen_chat), 0, 0, 0}, {ST_TOGGLE, N_("Chat window"), P_OFFINTNL(hex_gui_autoopen_chat), 0, 0, 0},
{ST_HEADER, N_("Maximum File Transfer Speeds (Byte per Second)"), 0, 0, 0}, {ST_HEADER, N_("Maximum File Transfer Speeds (KiB/s or MiB/s)"), 0, 0, 0},
{ST_NUMBER, N_("One upload:"), P_OFFINTNL(hex_dcc_max_send_cps), {ST_NUMBER, N_("One upload:"), P_OFFINTNL(hex_dcc_max_send_cps),
N_("Maximum speed for one transfer"), 0, 10000000}, N_("Maximum speed for one transfer"), 0, 10000000},
{ST_NUMBER, N_("One download:"), P_OFFINTNL(hex_dcc_max_get_cps), {ST_NUMBER, N_("One download:"), P_OFFINTNL(hex_dcc_max_get_cps),
@@ -544,6 +551,7 @@ static const setting general_settings[] =
{ST_TOGGLE, N_("WHOIS on notify"), P_OFFINTNL(hex_notify_whois_online), N_("Sends a /WHOIS when a user comes online in your notify list."), 0, 0}, {ST_TOGGLE, N_("WHOIS on notify"), P_OFFINTNL(hex_notify_whois_online), N_("Sends a /WHOIS when a user comes online in your notify list."), 0, 0},
{ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0}, {ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
{ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0}, {ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
{ST_TOGGLE, N_("Hide hostmasks in join and part messages"), P_OFFINTNL(hex_irc_hide_join_part_hostmask), 0, 0, 0},
{ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0}, {ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
{ST_END, 0, 0, 0, 0, 0} {ST_END, 0, 0, 0, 0, 0}
@@ -581,6 +589,7 @@ static const setting advanced_settings[] =
{ST_TOGGLE, N_("Display lists in compact mode"), P_OFFINTNL(hex_gui_compact), N_("Use less spacing between user list/channel tree rows."), 0, 0}, {ST_TOGGLE, N_("Display lists in compact mode"), P_OFFINTNL(hex_gui_compact), N_("Use less spacing between user list/channel tree rows."), 0, 0},
{ST_TOGGLE, N_("Use server time if supported"), P_OFFINTNL(hex_irc_cap_server_time), N_("Display timestamps obtained from server if it supports the time-server extension."), 0, 0}, {ST_TOGGLE, N_("Use server time if supported"), P_OFFINTNL(hex_irc_cap_server_time), N_("Display timestamps obtained from server if it supports the time-server extension."), 0, 0},
{ST_TOGGLE, N_("Automatically reconnect to servers on disconnect"), P_OFFINTNL(hex_net_auto_reconnect), 0, 0, 1}, {ST_TOGGLE, N_("Automatically reconnect to servers on disconnect"), P_OFFINTNL(hex_net_auto_reconnect), 0, 0, 1},
{ST_NUMBER, N_("Lag check interval:"), P_OFFINTNL(hex_net_lag_check), 0, (const char **)N_("seconds."), 9999},
{ST_NUMBER, N_("Auto reconnect delay:"), P_OFFINTNL(hex_net_reconnect_delay), 0, 0, 9999}, {ST_NUMBER, N_("Auto reconnect delay:"), P_OFFINTNL(hex_net_reconnect_delay), 0, 0, 9999},
{ST_NUMBER, N_("Auto join delay:"), P_OFFINTNL(hex_irc_join_delay), 0, 0, 9999}, {ST_NUMBER, N_("Auto join delay:"), P_OFFINTNL(hex_irc_join_delay), 0, 0, 9999},
{ST_MENU, N_("Ban Type:"), P_OFFINTNL(hex_irc_ban_type), N_("Attempt to use this banmask when banning or quieting. (requires irc_who_join)"), bantypemenu, 0}, {ST_MENU, N_("Ban Type:"), P_OFFINTNL(hex_irc_ban_type), N_("Attempt to use this banmask when banning or quieting. (requires irc_who_join)"), bantypemenu, 0},
@@ -654,6 +663,11 @@ static const setting network_settings[] =
{ST_MENU, N_("Type:"), P_OFFINTNL(hex_net_proxy_type), 0, proxytypes, 0}, {ST_MENU, N_("Type:"), P_OFFINTNL(hex_net_proxy_type), 0, proxytypes, 0},
{ST_MENU, N_("Use proxy for:"), P_OFFINTNL(hex_net_proxy_use), 0, proxyuse, 0}, {ST_MENU, N_("Use proxy for:"), P_OFFINTNL(hex_net_proxy_use), 0, proxyuse, 0},
{ST_HEADER, N_("Connection Health"), 0, 0, 0, 0},
{ST_NUMBER, N_("TCP keepalive idle:"), P_OFFINTNL(hex_net_keepalive_idle), 0, (const char **)N_("seconds."), 7200},
{ST_NUMBER, N_("TCP keepalive interval:"), P_OFFINTNL(hex_net_keepalive_interval), 0, (const char **)N_("seconds."), 600},
{ST_NUMBER, N_("TCP keepalive probes:"), P_OFFINTNL(hex_net_keepalive_count), 0, 0, 20},
{ST_HEADER, N_("Proxy Authentication"), 0, 0, 0, 0}, {ST_HEADER, N_("Proxy Authentication"), 0, 0, 0, 0},
{ST_TOGGLE, N_("Use authentication (HTTP or SOCKS5 only)"), P_OFFINTNL(hex_net_proxy_auth), 0, 0, 0}, {ST_TOGGLE, N_("Use authentication (HTTP or SOCKS5 only)"), P_OFFINTNL(hex_net_proxy_auth), 0, 0, 0},
{ST_ENTRY, N_("Username:"), P_OFFSETNL(hex_net_proxy_user), 0, 0, sizeof prefs.hex_net_proxy_user}, {ST_ENTRY, N_("Username:"), P_OFFSETNL(hex_net_proxy_user), 0, 0, sizeof prefs.hex_net_proxy_user},
@@ -824,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)
{ {
@@ -850,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,
@@ -872,6 +904,67 @@ setup_create_italic_label (char *text)
return label; return label;
} }
typedef struct
{
const setting *set;
GtkSpinButton *spin;
GtkComboBox *unit;
} setup_dcc_speed_data;
static gboolean
setup_is_dcc_speed_setting (const setting *set)
{
return set->offset == P_OFFINTNL (hex_dcc_max_send_cps)
|| set->offset == P_OFFINTNL (hex_dcc_max_get_cps)
|| set->offset == P_OFFINTNL (hex_dcc_global_max_send_cps)
|| set->offset == P_OFFINTNL (hex_dcc_global_max_get_cps);
}
static int
setup_dcc_speed_multiplier (GtkComboBox *combo)
{
return gtk_combo_box_get_active (combo) == 1 ? 1024 * 1024 : 1024;
}
static void
setup_dcc_speed_spin_range (setup_dcc_speed_data *data)
{
int max = data->set->extra / setup_dcc_speed_multiplier (data->unit);
gtk_spin_button_set_range (data->spin, 0, max > 0 ? max : 0);
}
static void
setup_dcc_speed_store (setup_dcc_speed_data *data)
{
int value = gtk_spin_button_get_value_as_int (data->spin);
int speed = value * setup_dcc_speed_multiplier (data->unit);
if (speed > data->set->extra)
speed = data->set->extra;
setup_set_int (&setup_prefs, data->set, speed);
}
static void
setup_dcc_speed_spin_cb (GtkSpinButton *spin, setup_dcc_speed_data *data)
{
(void)spin;
setup_dcc_speed_store (data);
}
static void
setup_dcc_speed_unit_cb (GtkComboBox *combo, setup_dcc_speed_data *data)
{
int speed = setup_get_int (&setup_prefs, data->set);
int multiplier;
int value;
(void)combo;
setup_dcc_speed_spin_range (data);
multiplier = setup_dcc_speed_multiplier (data->unit);
value = (speed + (multiplier / 2)) / multiplier;
gtk_spin_button_set_value (data->spin, value);
setup_dcc_speed_store (data);
}
static void static void
setup_spin_cb (GtkSpinButton *spin, const setting *set) setup_spin_cb (GtkSpinButton *spin, const setting *set)
{ {
@@ -901,11 +994,43 @@ setup_create_spin (GtkWidget *table, int row, const setting *set)
g_object_set_data (G_OBJECT (wid), "lbl", label); g_object_set_data (G_OBJECT (wid), "lbl", label);
if (set->tooltip) if (set->tooltip)
gtk_widget_set_tooltip_text (wid, _(set->tooltip)); gtk_widget_set_tooltip_text (wid, _(set->tooltip));
gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), if (setup_is_dcc_speed_setting (set))
{
GtkWidget *unit;
setup_dcc_speed_data *data = g_new0 (setup_dcc_speed_data, 1);
int speed = setup_get_int (&setup_prefs, set);
int use_mib = speed >= (1024 * 1024) && speed % (1024 * 1024) == 0;
int multiplier;
data->set = set;
data->spin = GTK_SPIN_BUTTON (wid);
unit = gtk_combo_box_text_new ();
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (unit), _("KiB/s"));
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (unit), _("MiB/s"));
gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (unit), 1);
gtk_combo_box_set_active (GTK_COMBO_BOX (unit), use_mib ? 1 : 0);
data->unit = GTK_COMBO_BOX (unit);
setup_dcc_speed_spin_range (data);
multiplier = setup_dcc_speed_multiplier (data->unit);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), (speed + (multiplier / 2)) / multiplier);
g_signal_connect (G_OBJECT (wid), "value-changed",
G_CALLBACK (setup_dcc_speed_spin_cb), data);
g_signal_connect (G_OBJECT (unit), "changed",
G_CALLBACK (setup_dcc_speed_unit_cb), data);
gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
gtk_box_pack_start (GTK_BOX (rbox), unit, 0, 0, 6);
} else
{
gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
setup_get_int (&setup_prefs, set)); setup_get_int (&setup_prefs, set));
g_signal_connect (G_OBJECT (wid), "value-changed", g_signal_connect (G_OBJECT (wid), "value-changed",
G_CALLBACK (setup_spin_cb), (gpointer)set); G_CALLBACK (setup_spin_cb), (gpointer)set);
gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0); gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
}
if (set->list) if (set->list)
{ {
@@ -1270,7 +1395,7 @@ setup_entry_cb (GtkEntry *entry, setting *set)
int size; int size;
int pos; int pos;
unsigned char *p = (unsigned char*)gtk_entry_get_text (entry); unsigned char *p = (unsigned char*)gtk_entry_get_text (entry);
int len = strlen (p); size_t len = strlen ((const char *) p);
/* need to truncate? */ /* need to truncate? */
if (len >= set->extra) if (len >= set->extra)
@@ -2086,7 +2211,7 @@ unslash (char *dir)
{ {
if (dir[0]) if (dir[0])
{ {
int len = strlen (dir) - 1; size_t len = strlen (dir) - 1;
#ifdef WIN32 #ifdef WIN32
if (dir[len] == '/' || dir[len] == '\\') if (dir[len] == '/' || dir[len] == '\\')
#else #else
@@ -2184,8 +2309,12 @@ 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))
noapply = TRUE;
if (DIFF (hex_gui_tab_server)) if (DIFF (hex_gui_tab_server))
noapply = TRUE; noapply = TRUE;
if (DIFF (hex_gui_tab_small)) if (DIFF (hex_gui_tab_small))
@@ -2196,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))

Some files were not shown because too many files have changed in this diff Show More