mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-21 10:29:24 +00:00
Compare commits
153 Commits
ad67af2f8f
...
inline-ima
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c1b920b27 | |||
|
|
8150d6c48c | ||
| 05ee0dbdb5 | |||
|
|
2db3076c92 | ||
| 90daed18df | |||
|
|
65dd2c645a | ||
| 89d70a940c | |||
|
|
4944517a0e | ||
| 2a7ee7f0b2 | |||
|
|
6edbd47c9a | ||
| d755c39e4a | |||
|
|
b7f8327209 | ||
| 27c82d8411 | |||
| e0b159c069 | |||
|
|
cfb19820fa | ||
|
|
e05ca13608 | ||
|
|
15cf96c01f | ||
| 2caec91a0f | |||
| 9465a1e0a8 | |||
| 251272c708 | |||
| f068a0e0fa | |||
| d0034cd112 | |||
| ae6d5ebeef | |||
|
|
8466e70424 | ||
| c3d8f27f48 | |||
| b85229f1a1 | |||
|
|
d03298a048 | ||
|
|
798e0b487e | ||
| 49444d72b4 | |||
|
|
c0e0fe4dcb | ||
|
|
9a189848ea | ||
| 96b09bfc1d | |||
| 9a6c913d89 | |||
| 42010594be | |||
| 5469c30b75 | |||
| fe9a2ee202 | |||
|
|
5e27dc7e9f | ||
|
|
69d083e3c0 | ||
| 2b95f9724e | |||
| 4066a07892 | |||
|
|
443ab58f65 | ||
| e0116acbe4 | |||
|
|
91e9bf0207 | ||
| aff0335f7e | |||
|
|
4d8baaa19b | ||
| 65452d80f1 | |||
|
|
df4d432151 | ||
|
|
1390f2c783 | ||
| be99d0e900 | |||
| 97fcf1d5fc | |||
| c9b14d7c45 | |||
| 3d0dda0347 | |||
|
|
8d69b35d67 | ||
| 7ba142a9f2 | |||
|
|
56ac55806d | ||
| 47f53c14db | |||
| a47d40b914 | |||
| be33ae30bb | |||
| 6d5f1306bf | |||
| ad3dc716e7 | |||
|
|
089b125a89 | ||
| dfd4554a11 | |||
| 1b98297881 | |||
|
|
cacc8fcafe | ||
| ac07d39db8 | |||
|
|
e27aa77cec | ||
| cbc9f68dd6 | |||
| 944d0f5a70 | |||
| 9ded7ef830 | |||
| adb18b7ce3 | |||
|
|
8220e60257 | ||
| b09af655e7 | |||
| 5833f8a3d8 | |||
|
|
72132d5e88 | ||
|
|
4167c4db78 | ||
| 3e1f6b9137 | |||
|
|
bb08b19c0d | ||
|
|
7497919e8f | ||
|
|
41cbe7b31c | ||
| 425e951341 | |||
| 2e4a0b92fc | |||
| 9f839579e2 | |||
| 900066b9d4 | |||
| 97534b0670 | |||
|
|
62672ade04 | ||
| b22e1c1ccc | |||
|
|
1b8e60c26d | ||
| 035dfdd332 | |||
| d1707d3c72 | |||
|
|
c9bed097ba | ||
| 5acb90025f | |||
|
|
2d12da79b0 | ||
|
|
bede379021 | ||
| 38acde1b20 | |||
| d0c4a5addd | |||
|
|
b8a7ccf005 | ||
| 68fc53585e | |||
| 26afc125c7 | |||
|
|
090b39f78b | ||
| 3722de6b13 | |||
|
|
d93c9aa780 | ||
| ea56504aee | |||
| 46b91edfdf | |||
|
|
d2dfde519d | ||
| 1eac56f22c | |||
|
|
fb491a6bb2 | ||
| 3da7c89b66 | |||
|
|
e842cf3a57 | ||
| 85b0e8f1a6 | |||
|
|
b8e03ff6c1 | ||
| 54b1703d67 | |||
| 15d647a0ec | |||
|
|
353558ddb3 | ||
|
|
9f58d30050 | ||
|
|
4f0632cdf1 | ||
|
|
ff8ba71948 | ||
|
|
a367591327 | ||
|
|
c91925fbc2 | ||
| 479f1649ef | |||
|
|
06f69184b6 | ||
|
|
3471d9a57c | ||
| 28d4035477 | |||
|
|
3e1d151efd | ||
| 556cfc3036 | |||
| c49b757be6 | |||
| cec7e2caf3 | |||
| 9a0c07a461 | |||
| c7064c18b9 | |||
|
|
fb897310c8 | ||
| 5944849326 | |||
| 1255f1e6c7 | |||
| d7bc09d859 | |||
| f84a448351 | |||
| 7e34690e0c | |||
|
|
6f6d378600 | ||
|
|
216b463b8f | ||
| 4ad84cb5e5 | |||
| 19e0946717 | |||
| 0de1ad06cd | |||
|
|
72427006dd | ||
|
|
0e5f702651 | ||
| 4f1b0fc838 | |||
|
|
23d0963c2d | ||
|
|
dcb35fb80f | ||
| b1768854c3 | |||
| 4ed4eaf8e8 | |||
|
|
d167b53b17 | ||
| 18eff80a30 | |||
| a44ec5f624 | |||
|
|
5da518f50e | ||
|
|
ebb11a8ac5 | ||
|
|
dd9c5db1aa | ||
| 984ac9763e |
132
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
132
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a problem with ZoiteChat
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------------------------------------------------------+
|
||||||
|
| ZoiteChat Bug Report |
|
||||||
|
| Please fill this out so the bug goblin has enough crumbs. |
|
||||||
|
+------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
What went wrong?
|
||||||
|
|
||||||
|
```text
|
||||||
|
Describe the bug clearly and briefly.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
|
||||||
|
```text
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Result
|
||||||
|
|
||||||
|
```text
|
||||||
|
What should have happened?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Actual Result
|
||||||
|
|
||||||
|
```text
|
||||||
|
What happened instead?
|
||||||
|
```
|
||||||
|
|
||||||
|
## ZoiteChat Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
ZoiteChat version:
|
||||||
|
Build/source: GitHub release / Fedora package / source build / Windows installer / other
|
||||||
|
Commit hash, if built from source:
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Information
|
||||||
|
|
||||||
|
```text
|
||||||
|
OS:
|
||||||
|
OS version:
|
||||||
|
Desktop environment:
|
||||||
|
Display server: Wayland / X11 / Windows / other
|
||||||
|
CPU architecture: x86_64 / aarch64 / other
|
||||||
|
```
|
||||||
|
|
||||||
|
## IRC / Network Details
|
||||||
|
|
||||||
|
```text
|
||||||
|
IRC network/server:
|
||||||
|
Port:
|
||||||
|
TLS/SSL enabled: yes / no
|
||||||
|
SASL enabled: yes / no
|
||||||
|
Using bouncer/ZNC: yes / no
|
||||||
|
Proxy/VPN involved: yes / no
|
||||||
|
IRCv3 capabilities involved, if known:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugins, Scripts, and Config
|
||||||
|
|
||||||
|
```text
|
||||||
|
Plugins enabled:
|
||||||
|
Scripts loaded:
|
||||||
|
Theme/custom CSS:
|
||||||
|
Custom config changes:
|
||||||
|
Does the bug still happen with a clean config? yes / no / untested
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs / Terminal Output
|
||||||
|
|
||||||
|
Paste relevant output below.
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Raw IRC Lines, If Relevant
|
||||||
|
|
||||||
|
Remove passwords, tokens, IPs, private channels, private messages, certs, and account details before posting.
|
||||||
|
|
||||||
|
```text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Crash Details, If Relevant
|
||||||
|
|
||||||
|
```text
|
||||||
|
Did ZoiteChat crash? yes / no
|
||||||
|
Crash reporter output:
|
||||||
|
Backtrace:
|
||||||
|
Core dump available: yes / no
|
||||||
|
```
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
Attach screenshots or screen recordings if the issue is visual.
|
||||||
|
|
||||||
|
## Regression Check
|
||||||
|
|
||||||
|
```text
|
||||||
|
Did this work in an older version? yes / no / unknown
|
||||||
|
Last known working version:
|
||||||
|
First broken version:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
* [ ] I searched existing issues first.
|
||||||
|
* [ ] I tested with the latest ZoiteChat version available to me.
|
||||||
|
* [ ] I included steps to reproduce the issue.
|
||||||
|
* [ ] I removed private information from logs and raw IRC lines.
|
||||||
|
* [ ] I included system and network details where relevant.
|
||||||
|
|
||||||
|
## Extra Notes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Anything else that might help.
|
||||||
|
```
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an improvement for ZoiteChat
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
```text
|
||||||
|
Describe the feature or enhancement clearly and briefly.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
```text
|
||||||
|
What problem would this solve?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
|
||||||
|
```text
|
||||||
|
How should ZoiteChat behave?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
```text
|
||||||
|
Other approaches or workarounds you considered.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Case
|
||||||
|
|
||||||
|
```text
|
||||||
|
Who benefits from this, and when would they use it?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform Details
|
||||||
|
|
||||||
|
```text
|
||||||
|
OS:
|
||||||
|
ZoiteChat version:
|
||||||
|
Build/source: GitHub release / Fedora package / source build / Windows installer / other
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
* [ ] I searched existing issues first.
|
||||||
|
* [ ] I checked whether this already exists in the latest ZoiteChat version available to me.
|
||||||
|
* [ ] I explained the problem this would solve.
|
||||||
|
|
||||||
|
## Extra Notes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Anything else that might help.
|
||||||
|
```
|
||||||
34
.github/workflows/appimage-build.yml
vendored
34
.github/workflows/appimage-build.yml
vendored
@@ -86,11 +86,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 +110,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 +176,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"
|
||||||
@@ -211,6 +225,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 +287,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 \
|
||||||
|
|||||||
79
.github/workflows/manjaro-package-build.yml
vendored
79
.github/workflows/manjaro-package-build.yml
vendored
@@ -1,79 +0,0 @@
|
|||||||
name: Manjaro Package Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master, main]
|
|
||||||
pull_request:
|
|
||||||
branches: [master, main]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
manjaro-package:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: manjarolinux/base:latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install packaging toolchain
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
pacman -Syu --noconfirm
|
|
||||||
pacman -S --noconfirm --needed \
|
|
||||||
base-devel \
|
|
||||||
git \
|
|
||||||
namcap \
|
|
||||||
meson \
|
|
||||||
ninja \
|
|
||||||
pkgconf \
|
|
||||||
glib2 \
|
|
||||||
glib2-devel \
|
|
||||||
gtk3 \
|
|
||||||
openssl \
|
|
||||||
libcanberra \
|
|
||||||
libayatana-appindicator \
|
|
||||||
iso-codes \
|
|
||||||
lua \
|
|
||||||
perl \
|
|
||||||
python \
|
|
||||||
python-cffi \
|
|
||||||
pciutils
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Build package and generate .SRCINFO (non-root)
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
id -u builder >/dev/null 2>&1 || useradd -m builder
|
|
||||||
chown -R builder:builder "$GITHUB_WORKSPACE"
|
|
||||||
su builder -c "cd '$GITHUB_WORKSPACE/packaging/manjaro' && makepkg --noconfirm --syncdeps --cleanbuild"
|
|
||||||
su builder -c "cd '$GITHUB_WORKSPACE/packaging/manjaro' && makepkg --printsrcinfo > .SRCINFO"
|
|
||||||
|
|
||||||
- name: Lint PKGBUILD and package with namcap
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
cd "$GITHUB_WORKSPACE/packaging/manjaro"
|
|
||||||
namcap PKGBUILD
|
|
||||||
pkg_file="$(find . -maxdepth 1 -type f -name '*.pkg.tar.*' | head -n1)"
|
|
||||||
namcap "$pkg_file"
|
|
||||||
|
|
||||||
- name: Collect artifacts
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
mkdir -p artifacts
|
|
||||||
cp -v "$GITHUB_WORKSPACE"/packaging/manjaro/*.pkg.tar.* artifacts/
|
|
||||||
cp -v "$GITHUB_WORKSPACE"/packaging/manjaro/PKGBUILD artifacts/
|
|
||||||
cp -v "$GITHUB_WORKSPACE"/packaging/manjaro/.SRCINFO artifacts/
|
|
||||||
|
|
||||||
- name: Upload package artifacts
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: zoitechat-manjaro-package
|
|
||||||
path: artifacts/*
|
|
||||||
if-no-files-found: error
|
|
||||||
45
.github/workflows/windows-build.yml
vendored
45
.github/workflows/windows-build.yml
vendored
@@ -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,26 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- 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 +144,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
46
COPYING
@@ -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.
|
||||||
|
|||||||
@@ -1,7 +1,57 @@
|
|||||||
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)
|
2.18.0 (2026-04-20)
|
||||||
|
-------------------
|
||||||
|
|
||||||
- Added optional close buttons on tabs.
|
- Added optional close buttons on tabs.
|
||||||
- Added Ctrl+W to close tabs and Ctrl+Shift+T to reopen recently closed tabs.
|
- Added Ctrl+W to close tabs and Ctrl+Shift+T to reopen recently closed tabs.
|
||||||
@@ -19,6 +69,7 @@ ZoiteChat ChangeLog
|
|||||||
- Improved AppStream metainfo validation.
|
- 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.
|
||||||
|
|||||||
@@ -29,6 +29,64 @@
|
|||||||
<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">
|
<release date="2026-04-20" version="2.18.0">
|
||||||
<description>
|
<description>
|
||||||
<p>Tabs and navigation:</p>
|
<p>Tabs and navigation:</p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('zoitechat', 'c',
|
project('zoitechat', 'c',
|
||||||
version: '2.18.0',
|
version: '2.18.3',
|
||||||
meson_version: '>= 0.55.0',
|
meson_version: '>= 0.55.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c17',
|
'c_std=c17',
|
||||||
@@ -19,6 +19,7 @@ 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_dep = dependency('gio-2.0', 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'
|
||||||
@@ -40,6 +41,7 @@ config_h.set10('ENABLE_NLS', true)
|
|||||||
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_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'))
|
||||||
|
|
||||||
|
|||||||
102
packaging/fedora/zoitechat.spec
Normal file
102
packaging/fedora/zoitechat.spec
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
Name: zoitechat
|
||||||
|
Version: 2.18.3
|
||||||
|
Release: %autorelease
|
||||||
|
Summary: HexChat-based IRC client
|
||||||
|
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
|
||||||
|
URL: https://github.com/ZoiteChat/zoitechat
|
||||||
|
Source0: %{url}/archive/refs/tags/%{name}-%{version}.tar.gz
|
||||||
|
|
||||||
|
BuildRequires: desktop-file-utils
|
||||||
|
BuildRequires: gcc
|
||||||
|
BuildRequires: gettext
|
||||||
|
BuildRequires: libappstream-glib
|
||||||
|
BuildRequires: meson >= 0.55.0
|
||||||
|
BuildRequires: perl
|
||||||
|
BuildRequires: perl-devel
|
||||||
|
BuildRequires: python3
|
||||||
|
BuildRequires: python3-cffi
|
||||||
|
BuildRequires: publicsuffix-list
|
||||||
|
BuildRequires: xwayland-run
|
||||||
|
BuildRequires: weston
|
||||||
|
BuildRequires: pkgconfig(ayatana-appindicator3-0.1)
|
||||||
|
BuildRequires: pkgconfig(dbus-glib-1)
|
||||||
|
BuildRequires: pkgconfig(gio-2.0) >= 2.36.0
|
||||||
|
BuildRequires: pkgconfig(gmodule-2.0)
|
||||||
|
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
||||||
|
BuildRequires: pkgconfig(iso-codes)
|
||||||
|
BuildRequires: pkgconfig(libarchive)
|
||||||
|
BuildRequires: pkgconfig(libcanberra) >= 0.22
|
||||||
|
BuildRequires: pkgconfig(libpci)
|
||||||
|
BuildRequires: pkgconfig(lua)
|
||||||
|
BuildRequires: pkgconfig(openssl) >= 0.9.8
|
||||||
|
BuildRequires: pkgconfig(python3)
|
||||||
|
|
||||||
|
Requires: hicolor-icon-theme
|
||||||
|
Requires: iso-codes
|
||||||
|
|
||||||
|
%package devel
|
||||||
|
Summary: Development files for ZoiteChat plugins
|
||||||
|
Requires: %{name}%{?_isa} = %{version}-%{release}
|
||||||
|
|
||||||
|
%description devel
|
||||||
|
Development files for building ZoiteChat plugins.
|
||||||
|
|
||||||
|
%description
|
||||||
|
ZoiteChat is a HexChat-based IRC client for Windows and UNIX-like operating
|
||||||
|
systems.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -C
|
||||||
|
|
||||||
|
%build
|
||||||
|
%meson \
|
||||||
|
-Dtext-frontend=false \
|
||||||
|
-Dwith-checksum=true \
|
||||||
|
-Dwith-fishlim=true \
|
||||||
|
-Dwith-lua=lua \
|
||||||
|
-Dwith-perl=perl \
|
||||||
|
-Dwith-python=python3 \
|
||||||
|
-Dwith-sysinfo=true \
|
||||||
|
-Dinstall-appdata=true \
|
||||||
|
-Dinstall-plugin-metainfo=true
|
||||||
|
%meson_build
|
||||||
|
|
||||||
|
%install
|
||||||
|
%meson_install
|
||||||
|
%find_lang %{name}
|
||||||
|
|
||||||
|
%check
|
||||||
|
desktop-file-validate %{buildroot}%{_datadir}/applications/net.zoite.Zoitechat.desktop
|
||||||
|
|
||||||
|
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
|
||||||
|
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
|
||||||
|
|
||||||
|
xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs \
|
||||||
|
"Theme Manager Dispatch Routing Tests" \
|
||||||
|
"Validate net.zoite.Zoitechat.desktop" \
|
||||||
|
"Validate translations" \
|
||||||
|
"Fishlim Tests"
|
||||||
|
|
||||||
|
%files -f %{name}.lang
|
||||||
|
%license COPYING
|
||||||
|
%doc readme.md troubleshooting.md
|
||||||
|
%{_bindir}/zoitechat
|
||||||
|
%{_datadir}/applications/net.zoite.Zoitechat.desktop
|
||||||
|
%{_datadir}/dbus-1/services/org.zoitechat.service.service
|
||||||
|
%{_datadir}/icons/hicolor/48x48/apps/net.zoite.Zoitechat.png
|
||||||
|
%{_datadir}/icons/hicolor/scalable/apps/net.zoite.Zoitechat.svg
|
||||||
|
%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
|
||||||
|
%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
|
||||||
|
%dir %{_libdir}/zoitechat
|
||||||
|
%dir %{_libdir}/zoitechat/plugins
|
||||||
|
%dir %{_libdir}/zoitechat/python
|
||||||
|
%{_libdir}/zoitechat/plugins/*.so
|
||||||
|
%{_libdir}/zoitechat/python/*.py
|
||||||
|
%{_mandir}/man1/zoitechat.1*
|
||||||
|
|
||||||
|
%files devel
|
||||||
|
%{_includedir}/zoitechat-plugin.h
|
||||||
|
%{_libdir}/pkgconfig/zoitechat-plugin.pc
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
%autochangelog
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Maintainer: ZoiteChat Maintainers <zoitechat@users.noreply.github.com>
|
|
||||||
|
|
||||||
pkgname=zoitechat-git
|
|
||||||
pkgver=0
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc='IRC client for GNOME (ZoiteChat development snapshot)'
|
|
||||||
arch=('x86_64')
|
|
||||||
url='https://github.com/zoitechat/zoitechat'
|
|
||||||
license=('GPL-2.0-or-later')
|
|
||||||
depends=(
|
|
||||||
'glib2'
|
|
||||||
'gtk3'
|
|
||||||
'iso-codes'
|
|
||||||
'libayatana-appindicator'
|
|
||||||
'libcanberra'
|
|
||||||
'lua'
|
|
||||||
'openssl'
|
|
||||||
'perl'
|
|
||||||
'python-cffi'
|
|
||||||
)
|
|
||||||
makedepends=(
|
|
||||||
'git'
|
|
||||||
'meson'
|
|
||||||
'ninja'
|
|
||||||
'pkgconf'
|
|
||||||
'python'
|
|
||||||
)
|
|
||||||
optdepends=(
|
|
||||||
'pciutils: sysinfo plugin hardware detection details'
|
|
||||||
)
|
|
||||||
provides=('zoitechat')
|
|
||||||
conflicts=('zoitechat')
|
|
||||||
|
|
||||||
_repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
||||||
source=("zoitechat::git+file://${_repo_root}")
|
|
||||||
sha256sums=('SKIP')
|
|
||||||
|
|
||||||
pkgver() {
|
|
||||||
cd "$srcdir/zoitechat"
|
|
||||||
|
|
||||||
if git describe --long --tags --abbrev=7 >/dev/null 2>&1; then
|
|
||||||
git describe --long --tags --abbrev=7 \
|
|
||||||
| sed 's/^v//' \
|
|
||||||
| sed -E 's/([^-]+)-([0-9]+)-g/\1.r\2.g/' \
|
|
||||||
| sed 's/-/./g'
|
|
||||||
else
|
|
||||||
printf '0.r%s.%s\n' \
|
|
||||||
"$(git rev-list --count HEAD)" \
|
|
||||||
"$(git rev-parse --short=7 HEAD)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
arch-meson zoitechat build \
|
|
||||||
-Dtext-frontend=false \
|
|
||||||
-Dwith-checksum=true \
|
|
||||||
-Dwith-fishlim=true \
|
|
||||||
-Dwith-lua=lua \
|
|
||||||
-Dwith-perl=perl \
|
|
||||||
-Dwith-python=python3 \
|
|
||||||
-Dwith-sysinfo=true \
|
|
||||||
-Dinstall-plugin-metainfo=true
|
|
||||||
|
|
||||||
meson compile -C build
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
meson install -C build --destdir "$pkgdir"
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(OpenSslInclude);$(Glib);..\..\src\common;$(ZoiteChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(OpenSslInclude);$(Glib);$(Gtk);..\..\src\common;$(ZoiteChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<ModuleDefinitionFile>fishlim.def</ModuleDefinitionFile>
|
<ModuleDefinitionFile>fishlim.def</ModuleDefinitionFile>
|
||||||
|
|||||||
@@ -194,6 +194,18 @@ static gboolean keyfile_save_to_file (GKeyFile *keyfile, char *filename) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
gchar **keystore_get_targets(gsize *length) {
|
||||||
|
GKeyFile *keyfile;
|
||||||
|
gchar **groups;
|
||||||
|
|
||||||
|
keyfile = getConfigFile();
|
||||||
|
groups = g_key_file_get_groups(keyfile, length);
|
||||||
|
g_key_file_free(keyfile);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the key store file to disk.
|
* Writes the key store file to disk.
|
||||||
*/
|
*/
|
||||||
@@ -217,6 +229,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
/**
|
/**
|
||||||
* Sets a key in the key store file.
|
* Sets a key in the key store file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
|
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
|
||||||
const char *password;
|
const char *password;
|
||||||
char *encrypted;
|
char *encrypted;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
char *keystore_get_key(const char *nick, enum fish_mode *mode);
|
char *keystore_get_key(const char *nick, enum fish_mode *mode);
|
||||||
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
|
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
|
||||||
gboolean keystore_delete_nick(const char *nick);
|
gboolean keystore_delete_nick(const char *nick);
|
||||||
|
gchar **keystore_get_targets(gsize *length);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fishlim_sources = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
shared_module('fishlim', fishlim_sources,
|
shared_module('fishlim', fishlim_sources,
|
||||||
dependencies: [libgio_dep, zoitechat_plugin_dep, libssl_dep],
|
dependencies: [libgio_dep, gtk_dep, zoitechat_plugin_dep, libssl_dep],
|
||||||
c_args: ['-DOPENSSL_API_COMPAT=0x10100000L'],
|
c_args: ['-DOPENSSL_API_COMPAT=0x10100000L'],
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: plugindir,
|
install_dir: plugindir,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "zoitechat-plugin.h"
|
#include "zoitechat-plugin.h"
|
||||||
@@ -44,7 +45,7 @@ static const char plugin_version[] = "1.0.0";
|
|||||||
|
|
||||||
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
|
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
|
||||||
static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
|
static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
|
||||||
static const char usage_keyx[] = "Usage: KEYX [<nick>], performs DH1080 key-exchange with <nick>";
|
static const char usage_keyx[] = "Usage: KEYX [<nick>] [ECB|CBC], performs DH1080 key-exchange with <nick>";
|
||||||
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
|
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
|
||||||
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
|
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
|
||||||
static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
||||||
@@ -52,6 +53,13 @@ static const char usage_msg[] = "Usage: MSG+ <nick or #channel> <message>";
|
|||||||
|
|
||||||
static zoitechat_plugin *ph;
|
static zoitechat_plugin *ph;
|
||||||
static GHashTable *pending_exchanges;
|
static GHashTable *pending_exchanges;
|
||||||
|
static GtkWidget *fishlim_dialog;
|
||||||
|
static GtkWidget *fishlim_target_entry;
|
||||||
|
static GtkWidget *fishlim_key_entry;
|
||||||
|
static GtkWidget *fishlim_mode_combo;
|
||||||
|
static GtkWidget *fishlim_status_label;
|
||||||
|
static GtkListStore *fishlim_store;
|
||||||
|
static GtkWidget *fishlim_view;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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'
|
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
|
||||||
|
|||||||
64
plugins/python/python38.vcxproj
Normal file
64
plugins/python/python38.vcxproj
Normal 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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ src/fe-gtk/fe-gtk.c
|
|||||||
src/fe-gtk/fkeys.c
|
src/fe-gtk/fkeys.c
|
||||||
src/fe-gtk/gtkutil.c
|
src/fe-gtk/gtkutil.c
|
||||||
src/fe-gtk/ignoregui.c
|
src/fe-gtk/ignoregui.c
|
||||||
|
src/fe-gtk/inline-image.c
|
||||||
src/fe-gtk/joind.c
|
src/fe-gtk/joind.c
|
||||||
src/fe-gtk/maingui.c
|
src/fe-gtk/maingui.c
|
||||||
src/fe-gtk/menu.c
|
src/fe-gtk/menu.c
|
||||||
|
|||||||
17
readme.md
17
readme.md
@@ -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.org/):
|
|
||||||
- [Downloads](https://zoitechat.org/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)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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},
|
||||||
@@ -447,6 +448,7 @@ const struct prefs vars[] =
|
|||||||
{"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_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},
|
||||||
@@ -457,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},
|
||||||
@@ -517,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},
|
||||||
@@ -548,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},
|
||||||
@@ -556,6 +564,7 @@ const struct prefs vars[] =
|
|||||||
{"net_proxy_use", P_OFFINT (hex_net_proxy_use), TYPE_INT},
|
{"net_proxy_use", P_OFFINT (hex_net_proxy_use), TYPE_INT},
|
||||||
{"net_proxy_user", P_OFFSET (hex_net_proxy_user), TYPE_STR},
|
{"net_proxy_user", P_OFFSET (hex_net_proxy_user), TYPE_STR},
|
||||||
{"net_reconnect_delay", P_OFFINT (hex_net_reconnect_delay), TYPE_INT},
|
{"net_reconnect_delay", P_OFFINT (hex_net_reconnect_delay), TYPE_INT},
|
||||||
|
{"net_remote_media", P_OFFINT (hex_net_remote_media), TYPE_BOOL},
|
||||||
{"net_throttle", P_OFFINT (hex_net_throttle), TYPE_BOOL},
|
{"net_throttle", P_OFFINT (hex_net_throttle), TYPE_BOOL},
|
||||||
|
|
||||||
{"notify_timeout", P_OFFINT (hex_notify_timeout), TYPE_INT},
|
{"notify_timeout", P_OFFINT (hex_notify_timeout), TYPE_INT},
|
||||||
@@ -768,6 +777,8 @@ load_default_config(void)
|
|||||||
prefs.hex_gui_hide_menu = 1;
|
prefs.hex_gui_hide_menu = 1;
|
||||||
#endif
|
#endif
|
||||||
prefs.hex_gui_ctrlq_quit = 1;
|
prefs.hex_gui_ctrlq_quit = 1;
|
||||||
|
/* hex_net_remote_media stays 0: loading remote media can reveal the
|
||||||
|
user's IP address, so it is strictly opt-in */
|
||||||
prefs.hex_gui_input_attr = 1;
|
prefs.hex_gui_input_attr = 1;
|
||||||
prefs.hex_gui_input_icon = 1;
|
prefs.hex_gui_input_icon = 1;
|
||||||
prefs.hex_gui_input_nick = 1;
|
prefs.hex_gui_input_nick = 1;
|
||||||
@@ -782,7 +793,10 @@ load_default_config(void)
|
|||||||
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;
|
||||||
@@ -857,6 +871,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;
|
||||||
@@ -1167,8 +1185,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;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
<ClInclude Include="public_suffix_data.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" />
|
||||||
@@ -68,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" />
|
||||||
|
|||||||
@@ -74,6 +74,9 @@
|
|||||||
<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>
|
||||||
@@ -178,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>
|
||||||
|
|||||||
@@ -1090,13 +1090,19 @@ clients_find_filename_foreach (gpointer key,
|
|||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
RemoteObject *obj = value;
|
RemoteObject *obj = value;
|
||||||
return g_str_equal (obj->filename, (char *)user_data);
|
|
||||||
|
return obj->filename != NULL && g_str_equal (obj->filename, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
|
unload_plugin_cb (char *word[], char *word_eol[], void *userdata)
|
||||||
{
|
{
|
||||||
RemoteObject *obj = g_hash_table_find (clients, clients_find_filename_foreach, word[2]);
|
RemoteObject *obj;
|
||||||
|
|
||||||
|
if (word[2][0] == 0)
|
||||||
|
return ZOITECHAT_EAT_NONE;
|
||||||
|
|
||||||
|
obj = g_hash_table_find (clients, clients_find_filename_foreach, word[2]);
|
||||||
|
|
||||||
if (obj != NULL)
|
if (obj != NULL)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -141,6 +142,7 @@ void fe_set_away (server *serv);
|
|||||||
void fe_serverlist_open (session *sess);
|
void fe_serverlist_open (session *sess);
|
||||||
void fe_get_bool (char *title, char *prompt, void *callback, void *userdata);
|
void fe_get_bool (char *title, char *prompt, void *callback, void *userdata);
|
||||||
void fe_get_str (char *prompt, char *def, void *callback, void *ud);
|
void fe_get_str (char *prompt, char *def, void *callback, void *ud);
|
||||||
|
void fe_get_password (char *prompt, void *callback, void *ud);
|
||||||
void fe_get_int (char *prompt, int def, void *callback, void *ud);
|
void fe_get_int (char *prompt, int def, void *callback, void *ud);
|
||||||
#define FRF_WRITE 1 /* save file */
|
#define FRF_WRITE 1 /* save file */
|
||||||
#define FRF_MULTIPLE 2 /* multi-select */
|
#define FRF_MULTIPLE 2 /* multi-select */
|
||||||
|
|||||||
@@ -315,6 +315,59 @@ resolve_parent_theme_root (const char *child_theme_root, const char *parent_name
|
|||||||
return home_local;
|
return home_local;
|
||||||
g_free (home_local);
|
g_free (home_local);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
if (xdg_data_home && xdg_data_home[0])
|
||||||
|
{
|
||||||
|
candidate = g_build_filename (xdg_data_home, "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate = g_build_filename (g_get_user_data_dir (), "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
|
||||||
|
{
|
||||||
|
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
if (xdg_data_dirs && xdg_data_dirs[0])
|
||||||
|
{
|
||||||
|
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
|
||||||
|
|
||||||
|
for (i = 0; data_dirs && data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
if (!data_dirs[i][0])
|
||||||
|
continue;
|
||||||
|
candidate = g_build_filename (data_dirs[i], "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
{
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *const *system_data_dirs = g_get_system_data_dirs ();
|
||||||
|
|
||||||
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
candidate = g_build_filename (system_data_dirs[i], "themes", parent_name, NULL);
|
||||||
|
if (path_exists_as_dir (candidate))
|
||||||
|
return candidate;
|
||||||
|
g_free (candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
||||||
candidate = g_build_filename (user_dir, parent_name, NULL);
|
candidate = g_build_filename (user_dir, parent_name, NULL);
|
||||||
g_free (user_dir);
|
g_free (user_dir);
|
||||||
@@ -632,18 +685,55 @@ zoitechat_gtk3_theme_service_discover (void)
|
|||||||
|
|
||||||
g_mkdir_with_parents (user_dir, 0700);
|
g_mkdir_with_parents (user_dir, 0700);
|
||||||
|
|
||||||
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
|
/* Prefer the live XDG environment over GLib's process-cached copies so
|
||||||
|
* theme lookup matches what GTK resolves for the running session. */
|
||||||
|
{
|
||||||
|
const char *xdg_data_home = g_getenv ("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
if (xdg_data_home && xdg_data_home[0])
|
||||||
|
user_data_themes = g_build_filename (xdg_data_home, "themes", NULL);
|
||||||
|
else
|
||||||
|
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
|
||||||
|
}
|
||||||
|
/* Scan the app's own themes dir first: imported themes get a symlink
|
||||||
|
* under the XDG data themes dir so GTK can resolve gtk-theme-name, and
|
||||||
|
* scanning that link first would make the canonical-root dedup keep
|
||||||
|
* the symlink path - changing the theme's path-derived id and breaking
|
||||||
|
* the saved gui_gtk3_theme reference on the next start. */
|
||||||
|
add_theme_root (user_roots, seen_user_roots, user_dir);
|
||||||
add_theme_root (user_roots, seen_user_roots, user_data_themes);
|
add_theme_root (user_roots, seen_user_roots, user_data_themes);
|
||||||
g_free (user_data_themes);
|
g_free (user_data_themes);
|
||||||
add_theme_root (user_roots, seen_user_roots, home_themes);
|
add_theme_root (user_roots, seen_user_roots, home_themes);
|
||||||
add_theme_root (user_roots, seen_user_roots, user_dir);
|
|
||||||
|
|
||||||
system_data_dirs = g_get_system_data_dirs ();
|
|
||||||
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
|
||||||
{
|
{
|
||||||
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
|
const char *xdg_data_dirs = g_getenv ("XDG_DATA_DIRS");
|
||||||
add_theme_root (system_roots, seen_system_roots, system_themes);
|
|
||||||
g_free (system_themes);
|
if (xdg_data_dirs && xdg_data_dirs[0])
|
||||||
|
{
|
||||||
|
char **data_dirs = g_strsplit (xdg_data_dirs, G_SEARCHPATH_SEPARATOR_S, -1);
|
||||||
|
|
||||||
|
for (i = 0; data_dirs && data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
char *system_themes;
|
||||||
|
|
||||||
|
if (!data_dirs[i][0])
|
||||||
|
continue;
|
||||||
|
system_themes = g_build_filename (data_dirs[i], "themes", NULL);
|
||||||
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||||||
|
g_free (system_themes);
|
||||||
|
}
|
||||||
|
g_strfreev (data_dirs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
system_data_dirs = g_get_system_data_dirs ();
|
||||||
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||||||
|
{
|
||||||
|
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
|
||||||
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||||||
|
g_free (system_themes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < system_roots->len; i++)
|
for (i = 0; i < system_roots->len; i++)
|
||||||
@@ -922,7 +1012,7 @@ static gboolean
|
|||||||
validate_theme_root_for_import (const char *theme_root, GError **error)
|
validate_theme_root_for_import (const char *theme_root, GError **error)
|
||||||
{
|
{
|
||||||
char *index_theme;
|
char *index_theme;
|
||||||
GKeyFile *keyfile;
|
GKeyFile *keyfile = NULL;
|
||||||
char *css_dir;
|
char *css_dir;
|
||||||
char *css_path;
|
char *css_path;
|
||||||
char *raw_inherits;
|
char *raw_inherits;
|
||||||
@@ -930,36 +1020,33 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
guint i;
|
guint i;
|
||||||
GError *load_error = NULL;
|
GError *load_error = NULL;
|
||||||
|
|
||||||
|
/* index.theme is optional for GTK3 CSS themes (GTK only needs
|
||||||
|
* gtk-3.x/gtk.css), but when it is present it must be well formed so
|
||||||
|
* broken archives are rejected up front. */
|
||||||
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
||||||
if (!g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
if (g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
||||||
{
|
{
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
keyfile = g_key_file_new ();
|
||||||
"Invalid GTK3 theme at '%s': missing required index.theme at '%s'.",
|
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
||||||
theme_root, index_theme);
|
{
|
||||||
g_free (index_theme);
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
return FALSE;
|
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
|
||||||
}
|
theme_root, index_theme, load_error->message);
|
||||||
|
g_error_free (load_error);
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
|
g_free (index_theme);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
keyfile = g_key_file_new ();
|
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
|
||||||
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
{
|
||||||
{
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
|
||||||
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
|
theme_root, index_theme);
|
||||||
theme_root, index_theme, load_error->message);
|
g_key_file_unref (keyfile);
|
||||||
g_error_free (load_error);
|
g_free (index_theme);
|
||||||
g_key_file_unref (keyfile);
|
return FALSE;
|
||||||
g_free (index_theme);
|
}
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
|
|
||||||
{
|
|
||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
|
||||||
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
|
|
||||||
theme_root, index_theme);
|
|
||||||
g_key_file_unref (keyfile);
|
|
||||||
g_free (index_theme);
|
|
||||||
return FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
||||||
@@ -968,7 +1055,8 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||||
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
|
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
|
||||||
theme_root);
|
theme_root);
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
@@ -981,15 +1069,20 @@ validate_theme_root_for_import (const char *theme_root, GError **error)
|
|||||||
theme_root, css_path);
|
theme_root, css_path);
|
||||||
g_free (css_path);
|
g_free (css_path);
|
||||||
g_free (css_dir);
|
g_free (css_dir);
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
g_free (css_path);
|
g_free (css_path);
|
||||||
g_free (css_dir);
|
g_free (css_dir);
|
||||||
|
|
||||||
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
raw_inherits = NULL;
|
||||||
g_key_file_unref (keyfile);
|
if (keyfile)
|
||||||
|
{
|
||||||
|
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
||||||
|
g_key_file_unref (keyfile);
|
||||||
|
}
|
||||||
g_free (index_theme);
|
g_free (index_theme);
|
||||||
if (!raw_inherits)
|
if (!raw_inherits)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -127,7 +132,7 @@ if get_option('plugin')
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
zoitechat_common = static_library('zoitechatcommon',
|
zoitechat_common = static_library('zoitechatcommon',
|
||||||
sources: [textevents, public_suffix_data] + 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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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])
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,7 +522,10 @@ 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, "hcsysinfo.dll", plugin_auto_load_cb);
|
for_files (lib_dir, "hcsysinfo.dll", plugin_auto_load_cb);
|
||||||
#else
|
#else
|
||||||
@@ -1213,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 */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
173
src/common/secretstore.c
Normal 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
10
src/common/secretstore.h
Normal 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
|
||||||
@@ -109,6 +109,58 @@ static void server_disconnect (session * sess, int sendquit, int err);
|
|||||||
static int server_cleanup (server * serv);
|
static int server_cleanup (server * serv);
|
||||||
static void server_connect (server *serv, char *hostname, int port, int no_login);
|
static void server_connect (server *serv, char *hostname, int port, int no_login);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
server *serv;
|
||||||
|
char hostname[128];
|
||||||
|
int port;
|
||||||
|
} reconnect_password_prompt;
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
server_login_uses_password_sasl (int logintype)
|
||||||
|
{
|
||||||
|
return logintype == LOGIN_SASL ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_1 ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_256 ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_512;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
server_reconnect_password_cb (int cancel, char *text, void *userdata)
|
||||||
|
{
|
||||||
|
reconnect_password_prompt *prompt = userdata;
|
||||||
|
|
||||||
|
if (!cancel && is_server (prompt->serv) && !prompt->serv->connected && !prompt->serv->connecting && prompt->serv->server_session)
|
||||||
|
{
|
||||||
|
prompt->serv->password[0] = 0;
|
||||||
|
if (text && *text)
|
||||||
|
safe_strcpy (prompt->serv->password, text, sizeof (prompt->serv->password));
|
||||||
|
server_connect (prompt->serv, prompt->hostname, prompt->port, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
server_prompt_reconnect_password (server *serv)
|
||||||
|
{
|
||||||
|
reconnect_password_prompt *prompt;
|
||||||
|
char *msg;
|
||||||
|
ircnet *net = serv->network;
|
||||||
|
|
||||||
|
if (!net || !(net->flags & FLAG_PROMPT_PASSWORD) || !server_login_uses_password_sasl (serv->loginmethod))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
prompt = g_new0 (reconnect_password_prompt, 1);
|
||||||
|
prompt->serv = serv;
|
||||||
|
safe_strcpy (prompt->hostname, serv->hostname, sizeof (prompt->hostname));
|
||||||
|
prompt->port = serv->port;
|
||||||
|
msg = g_strdup_printf (_("Enter SASL password for %s:"), net->name ? net->name : _("this network"));
|
||||||
|
fe_get_password (msg, server_reconnect_password_cb, prompt);
|
||||||
|
g_free (msg);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_error (char *message, GError **error)
|
write_error (char *message, GError **error)
|
||||||
{
|
{
|
||||||
@@ -522,7 +574,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 +586,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 +622,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 +635,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 +715,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);
|
||||||
@@ -732,7 +786,8 @@ timeout_auto_reconnect (server *serv)
|
|||||||
serv->recondelay_tag = 0;
|
serv->recondelay_tag = 0;
|
||||||
if (!serv->connected && !serv->connecting && serv->server_session)
|
if (!serv->connected && !serv->connecting && serv->server_session)
|
||||||
{
|
{
|
||||||
server_connect (serv, serv->hostname, serv->port, FALSE);
|
if (!server_prompt_reconnect_password (serv))
|
||||||
|
server_connect (serv, serv->hostname, serv->port, FALSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0; /* returning 0 should remove the timeout handler */
|
return 0; /* returning 0 should remove the timeout handler */
|
||||||
@@ -881,7 +936,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 +985,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 +1159,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 +1874,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 +1909,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 +2041,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);
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -296,8 +439,99 @@ servlist_favchan_copy (favchannel *fav)
|
|||||||
return newfav;
|
return newfav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void servlist_connect_with_password (session *sess, ircnet *net, gboolean join, const char *password);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
session *sess;
|
||||||
|
ircnet *net;
|
||||||
|
gboolean join;
|
||||||
|
} servlist_password_prompt;
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
servlist_login_uses_password_sasl (int logintype)
|
||||||
|
{
|
||||||
|
return logintype == LOGIN_SASL ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_1 ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_256 ||
|
||||||
|
logintype == LOGIN_SASL_SCRAM_SHA_512;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void servlist_prompt_sasl_password (servlist_password_prompt *prompt);
|
||||||
|
|
||||||
|
static void
|
||||||
|
servlist_connect_without_password_cb (int value, void *userdata)
|
||||||
|
{
|
||||||
|
servlist_password_prompt *prompt = userdata;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
servlist_connect_with_password (prompt->sess, prompt->net, prompt->join, NULL);
|
||||||
|
g_free (prompt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
servlist_prompt_sasl_password (prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
servlist_connect_password_cb (int cancel, char *text, void *userdata)
|
||||||
|
{
|
||||||
|
servlist_password_prompt *prompt = userdata;
|
||||||
|
char *msg;
|
||||||
|
|
||||||
|
if (cancel)
|
||||||
|
{
|
||||||
|
fe_serverlist_open (prompt->sess);
|
||||||
|
g_free (prompt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text && *text)
|
||||||
|
{
|
||||||
|
servlist_connect_with_password (prompt->sess, prompt->net, prompt->join, text);
|
||||||
|
g_free (prompt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = g_strdup_printf (_("Continue connecting to %s without a SASL password?"), prompt->net->name ? prompt->net->name : _("this network"));
|
||||||
|
fe_get_bool (_("No SASL password"), msg, servlist_connect_without_password_cb, prompt);
|
||||||
|
g_free (msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
servlist_prompt_sasl_password (servlist_password_prompt *prompt)
|
||||||
|
{
|
||||||
|
char *msg;
|
||||||
|
|
||||||
|
msg = g_strdup_printf (_("Enter SASL password for %s:"), prompt->net->name ? prompt->net->name : _("this network"));
|
||||||
|
fe_get_password (msg, servlist_connect_password_cb, prompt);
|
||||||
|
g_free (msg);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
servlist_connect (session *sess, ircnet *net, gboolean join)
|
servlist_connect (session *sess, ircnet *net, gboolean join)
|
||||||
|
{
|
||||||
|
int logintype;
|
||||||
|
servlist_password_prompt *prompt;
|
||||||
|
|
||||||
|
logintype = net && net->logintype ? net->logintype : LOGIN_DEFAULT_REAL;
|
||||||
|
if (net && (net->flags & FLAG_PROMPT_PASSWORD) && servlist_login_uses_password_sasl (logintype))
|
||||||
|
{
|
||||||
|
prompt = g_new0 (servlist_password_prompt, 1);
|
||||||
|
prompt->sess = sess;
|
||||||
|
prompt->net = net;
|
||||||
|
prompt->join = join;
|
||||||
|
servlist_prompt_sasl_password (prompt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
servlist_connect_with_password (sess, net, join, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
servlist_connect_with_password (session *sess, ircnet *net, gboolean join, const char *password)
|
||||||
{
|
{
|
||||||
ircserver *ircserv;
|
ircserver *ircserv;
|
||||||
GSList *list;
|
GSList *list;
|
||||||
@@ -344,10 +578,33 @@ servlist_connect (session *sess, ircnet *net, gboolean join)
|
|||||||
}
|
}
|
||||||
|
|
||||||
serv->password[0] = 0;
|
serv->password[0] = 0;
|
||||||
|
if (password && *password)
|
||||||
if (net->pass)
|
|
||||||
{
|
{
|
||||||
safe_strcpy (serv->password, net->pass, sizeof (serv->password));
|
safe_strcpy (serv->password, password, sizeof (serv->password));
|
||||||
|
}
|
||||||
|
else if ((net->flags & FLAG_USE_KEYRING) && net->name)
|
||||||
|
{
|
||||||
|
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 +1239,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')
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ 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_PROMPT_PASSWORD 256
|
||||||
|
#define FLAG_COUNT 9
|
||||||
|
|
||||||
/* 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 +126,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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -381,9 +381,10 @@ test_archive_without_theme_reports_css_error (void)
|
|||||||
}
|
}
|
||||||
archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL);
|
archive_path = g_build_filename (tmp_root, "invalid-theme.zip", NULL);
|
||||||
|
|
||||||
command = g_strdup_printf ("cd %s && zip -qr %s .", archive_root, archive_path);
|
command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", archive_root, archive_path);
|
||||||
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
||||||
g_free (command);
|
g_free (command);
|
||||||
|
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
|
||||||
|
|
||||||
g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error));
|
g_assert_false (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, &error));
|
||||||
g_assert_null (imported_id);
|
g_assert_null (imported_id);
|
||||||
@@ -397,25 +398,31 @@ test_archive_without_theme_reports_css_error (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_import_rejects_theme_missing_index_theme (void)
|
test_import_accepts_theme_missing_index_theme (void)
|
||||||
{
|
{
|
||||||
char *tmp_root;
|
char *tmp_root;
|
||||||
char *src_root;
|
char *src_root;
|
||||||
char *theme_root;
|
char *theme_root;
|
||||||
char *imported_id = NULL;
|
char *imported_id = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
ZoitechatGtk3Theme *found;
|
||||||
|
|
||||||
setup_test_xdir (&tmp_root);
|
setup_test_xdir (&tmp_root);
|
||||||
src_root = g_build_filename (tmp_root, "src", NULL);
|
src_root = g_build_filename (tmp_root, "src", NULL);
|
||||||
g_mkdir_with_parents (src_root, 0700);
|
g_mkdir_with_parents (src_root, 0700);
|
||||||
theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE);
|
theme_root = make_theme_dir (src_root, "NoIndex", FALSE, FALSE);
|
||||||
|
|
||||||
g_assert_false (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error));
|
/* index.theme is optional for GTK3 CSS themes: gtk-3.x/gtk.css alone
|
||||||
g_assert_null (imported_id);
|
* makes a loadable theme. */
|
||||||
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
|
g_assert_true (zoitechat_gtk3_theme_service_import (theme_root, &imported_id, &error));
|
||||||
g_assert_nonnull (g_strstr_len (error->message, -1, "missing required index.theme"));
|
g_assert_no_error (error);
|
||||||
g_assert_nonnull (g_strstr_len (error->message, -1, "NoIndex"));
|
g_assert_nonnull (imported_id);
|
||||||
g_error_free (error);
|
|
||||||
|
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
||||||
|
g_assert_nonnull (found);
|
||||||
|
g_assert_true (g_str_has_suffix (found->path, "NoIndex"));
|
||||||
|
zoitechat_gtk3_theme_free (found);
|
||||||
|
g_free (imported_id);
|
||||||
|
|
||||||
g_free (theme_root);
|
g_free (theme_root);
|
||||||
g_free (src_root);
|
g_free (src_root);
|
||||||
@@ -663,9 +670,10 @@ test_zip_import_nested_root (void)
|
|||||||
theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE);
|
theme = make_theme_dir (nested, "Juno-ocean", TRUE, FALSE);
|
||||||
archive_path = g_build_filename (tmp_root, "themes.zip", NULL);
|
archive_path = g_build_filename (tmp_root, "themes.zip", NULL);
|
||||||
|
|
||||||
command = g_strdup_printf ("cd %s && zip -qr %s .", zip_root, archive_path);
|
command = g_strdup_printf ("sh -c \"cd '%s' && zip -qr '%s' .\"", zip_root, archive_path);
|
||||||
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
g_assert_true (g_spawn_command_line_sync (command, NULL, NULL, NULL, NULL));
|
||||||
g_free (command);
|
g_free (command);
|
||||||
|
g_assert_true (g_file_test (archive_path, G_FILE_TEST_IS_REGULAR));
|
||||||
|
|
||||||
g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL));
|
g_assert_true (zoitechat_gtk3_theme_service_import (archive_path, &imported_id, NULL));
|
||||||
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
found = zoitechat_gtk3_theme_find_by_id (imported_id);
|
||||||
@@ -697,7 +705,7 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root);
|
g_test_add_func ("/gtk3-theme-service/zip-import-nested-root", test_zip_import_nested_root);
|
||||||
g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error);
|
g_test_add_func ("/gtk3-theme-service/invalid-archive-extract-error", test_invalid_archive_reports_extract_error);
|
||||||
g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error);
|
g_test_add_func ("/gtk3-theme-service/archive-without-theme-css-error", test_archive_without_theme_reports_css_error);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_rejects_theme_missing_index_theme);
|
g_test_add_func ("/gtk3-theme-service/import-missing-index-theme", test_import_accepts_theme_missing_index_theme);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry);
|
g_test_add_func ("/gtk3-theme-service/import-missing-desktop-entry", test_import_rejects_index_without_desktop_entry);
|
||||||
g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits);
|
g_test_add_func ("/gtk3-theme-service/import-unresolved-inherits", test_import_rejects_unresolved_inherits);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -321,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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -201,6 +204,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_net_auto_reconnect;
|
unsigned int hex_net_auto_reconnect;
|
||||||
unsigned int hex_net_auto_reconnectonfail;
|
unsigned int hex_net_auto_reconnectonfail;
|
||||||
unsigned int hex_net_proxy_auth;
|
unsigned int hex_net_proxy_auth;
|
||||||
|
unsigned int hex_net_remote_media;
|
||||||
unsigned int hex_net_throttle;
|
unsigned int hex_net_throttle;
|
||||||
unsigned int hex_notify_whois_online;
|
unsigned int hex_notify_whois_online;
|
||||||
unsigned int hex_perl_warnings;
|
unsigned int hex_perl_warnings;
|
||||||
@@ -267,6 +271,7 @@ struct zoitechatprefs
|
|||||||
int hex_gui_tab_layout;
|
int hex_gui_tab_layout;
|
||||||
int hex_gui_tab_closebuttons;
|
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;
|
||||||
@@ -289,6 +294,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 */
|
||||||
@@ -380,6 +389,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 */
|
||||||
@@ -421,8 +438,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;
|
||||||
@@ -486,6 +512,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);
|
||||||
@@ -535,6 +563,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" */
|
||||||
@@ -597,6 +626,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 */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -304,20 +304,32 @@ 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;
|
||||||
@@ -329,6 +341,7 @@ cv_tabs_init (chanview *cv)
|
|||||||
GtkWidget *box;
|
GtkWidget *box;
|
||||||
GtkWidget *viewport;
|
GtkWidget *viewport;
|
||||||
GtkWidget *outer;
|
GtkWidget *outer;
|
||||||
|
GtkWidget *tree;
|
||||||
|
|
||||||
if (cv->vertical)
|
if (cv->vertical)
|
||||||
{
|
{
|
||||||
@@ -345,17 +358,17 @@ cv_tabs_init (chanview *cv)
|
|||||||
|
|
||||||
viewport = gtk_scrolled_window_new (0, 0);
|
viewport = gtk_scrolled_window_new (0, 0);
|
||||||
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (viewport), GTK_SHADOW_NONE);
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (viewport), GTK_SHADOW_NONE);
|
||||||
gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (viewport), FALSE);
|
|
||||||
if (cv->vertical)
|
if (cv->vertical)
|
||||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
|
||||||
GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||||
else
|
else
|
||||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (viewport),
|
||||||
GTK_POLICY_ALWAYS, GTK_POLICY_NEVER);
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
|
||||||
gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (viewport), 1);
|
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_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (viewport), 1);
|
||||||
gtk_widget_set_hexpand (viewport, TRUE);
|
gtk_widget_set_hexpand (viewport, TRUE);
|
||||||
gtk_widget_set_vexpand (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);
|
||||||
@@ -373,6 +386,11 @@ cv_tabs_init (chanview *cv)
|
|||||||
gtk_container_add (GTK_CONTAINER (viewport), box);
|
gtk_container_add (GTK_CONTAINER (viewport), box);
|
||||||
gtk_widget_show (box);
|
gtk_widget_show (box);
|
||||||
|
|
||||||
|
tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store));
|
||||||
|
gtk_widget_set_name (tree, "zoitechat-tree");
|
||||||
|
gtk_widget_set_no_show_all (tree, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (outer), tree, 0, 0, 0);
|
||||||
|
|
||||||
gtk_container_add (GTK_CONTAINER (cv->box), outer);
|
gtk_container_add (GTK_CONTAINER (cv->box), outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,9 +695,11 @@ cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent)
|
|||||||
gtk_widget_set_name (but, "zoitechat-tab");
|
gtk_widget_set_name (but, "zoitechat-tab");
|
||||||
gtk_widget_set_size_request (but, -1, 14);
|
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);
|
||||||
|
cv_add_scroll_events (but);
|
||||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
|
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);
|
||||||
@@ -695,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",
|
||||||
|
|||||||
@@ -111,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;
|
||||||
@@ -141,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),
|
||||||
@@ -154,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;
|
||||||
|
|||||||
@@ -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,9 +171,10 @@ 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 (cv->font_desc));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -264,6 +307,109 @@ chanview_set_impl (chanview *cv, int type)
|
|||||||
cv->func_focus (cv->focused);
|
cv->func_focus (cv->focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_sorted (chanview *cv, gboolean sorted)
|
||||||
|
{
|
||||||
|
cv->sorted = sorted ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_use_icons (chanview *cv, gboolean use_icons)
|
||||||
|
{
|
||||||
|
cv->use_icons = use_icons ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_trunc_len (chanview *cv, int trunc_len)
|
||||||
|
{
|
||||||
|
cv->trunc_len = trunc_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
chan *ch;
|
||||||
|
int old_pos;
|
||||||
|
} chanview_sort_entry;
|
||||||
|
|
||||||
|
/* re-sort the session rows below one parent with cb_compare, leaving
|
||||||
|
* utility rows (tag != 0) in place; mirrors chanview_insert_sorted() */
|
||||||
|
static void
|
||||||
|
chanview_resort_children (chanview *cv, GtkTreeIter *parent)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model = GTK_TREE_MODEL (cv->store);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
chanview_sort_entry *entries;
|
||||||
|
int *positions;
|
||||||
|
gint *new_order;
|
||||||
|
chan *ch;
|
||||||
|
int n, i, j, count;
|
||||||
|
|
||||||
|
n = gtk_tree_model_iter_n_children (model, parent);
|
||||||
|
if (n < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entries = g_new0 (chanview_sort_entry, n);
|
||||||
|
positions = g_new0 (int, n);
|
||||||
|
new_order = g_new0 (gint, n);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
new_order[i] = i;
|
||||||
|
if (!gtk_tree_model_iter_nth_child (model, &iter, parent, i))
|
||||||
|
continue;
|
||||||
|
gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1);
|
||||||
|
if (ch && ch->tag == 0 && ch->userdata)
|
||||||
|
{
|
||||||
|
entries[count].ch = ch;
|
||||||
|
entries[count].old_pos = i;
|
||||||
|
positions[count] = i;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stable insertion sort; tab groups stay small so this is cheap */
|
||||||
|
for (i = 1; i < count; i++)
|
||||||
|
{
|
||||||
|
chanview_sort_entry key = entries[i];
|
||||||
|
|
||||||
|
j = i - 1;
|
||||||
|
while (j >= 0 && cv->cb_compare (entries[j].ch->userdata, key.ch->userdata) > 0)
|
||||||
|
{
|
||||||
|
entries[j + 1] = entries[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
entries[j + 1] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
new_order[positions[i]] = entries[i].old_pos;
|
||||||
|
|
||||||
|
gtk_tree_store_reorder (cv->store, parent, new_order);
|
||||||
|
|
||||||
|
g_free (entries);
|
||||||
|
g_free (positions);
|
||||||
|
g_free (new_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top-level rows keep their order (insertion never sorts them either);
|
||||||
|
* only the children within each family are sorted */
|
||||||
|
void
|
||||||
|
chanview_resort (chanview *cv)
|
||||||
|
{
|
||||||
|
GtkTreeIter iter;
|
||||||
|
|
||||||
|
if (!cv->cb_compare)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
chanview_resort_children (cv, &iter);
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
||||||
{
|
{
|
||||||
@@ -273,6 +419,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 +449,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 +473,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;
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ void chanview_set_callbacks (chanview *cv,
|
|||||||
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
||||||
int (*cb_compare) (void *a, void *b));
|
int (*cb_compare) (void *a, void *b));
|
||||||
void chanview_set_impl (chanview *cv, int type);
|
void chanview_set_impl (chanview *cv, int type);
|
||||||
|
void chanview_set_sorted (chanview *cv, gboolean sorted);
|
||||||
|
void chanview_set_use_icons (chanview *cv, gboolean use_icons);
|
||||||
|
void chanview_set_trunc_len (chanview *cv, int trunc_len);
|
||||||
|
void chanview_resort (chanview *cv);
|
||||||
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
||||||
int chanview_get_size (chanview *cv);
|
int chanview_get_size (chanview *cv);
|
||||||
GtkWidget *chanview_get_box (chanview *cv);
|
GtkWidget *chanview_get_box (chanview *cv);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "chanlist.h"
|
#include "chanlist.h"
|
||||||
#include "joind.h"
|
#include "joind.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
|
#include "inline-image.h"
|
||||||
#include "theme/theme-gtk.h"
|
#include "theme/theme-gtk.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "notifygui.h"
|
#include "notifygui.h"
|
||||||
@@ -124,6 +125,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 +442,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 +813,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
|
||||||
@@ -922,7 +944,13 @@ void
|
|||||||
fe_print_text (struct session *sess, char *text, time_t stamp,
|
fe_print_text (struct session *sess, char *text, time_t stamp,
|
||||||
gboolean no_activity)
|
gboolean no_activity)
|
||||||
{
|
{
|
||||||
PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.hex_text_indent, stamp);
|
/* image links become a "[view image]" placeholder with a hidden URL */
|
||||||
|
char *filtered = inline_image_filter_text (text);
|
||||||
|
|
||||||
|
PrintTextRaw (sess->res->buffer,
|
||||||
|
(unsigned char *)(filtered ? filtered : text),
|
||||||
|
prefs.hex_text_indent, stamp);
|
||||||
|
g_free (filtered);
|
||||||
|
|
||||||
if (no_activity || !sess->gui->is_tab)
|
if (no_activity || !sess->gui->is_tab)
|
||||||
return;
|
return;
|
||||||
@@ -1336,17 +1364,19 @@ fe_open_url_inner (const char *url)
|
|||||||
gboolean opened = FALSE;
|
gboolean opened = FALSE;
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
|
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (url_utf16 != NULL)
|
||||||
|
{
|
||||||
|
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
||||||
|
g_free (url_utf16);
|
||||||
|
}
|
||||||
|
|
||||||
if (!opened)
|
if (!opened)
|
||||||
{
|
{
|
||||||
g_clear_error (&error);
|
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
|
||||||
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
if (!opened)
|
||||||
|
g_clear_error (&error);
|
||||||
if (url_utf16 != NULL)
|
|
||||||
{
|
|
||||||
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
|
||||||
g_free (url_utf16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
|||||||
<ClInclude Include="fkeys.h" />
|
<ClInclude Include="fkeys.h" />
|
||||||
<ClInclude Include="gtkutil.h" />
|
<ClInclude Include="gtkutil.h" />
|
||||||
<ClInclude Include="icon-resolver.h" />
|
<ClInclude Include="icon-resolver.h" />
|
||||||
|
<ClInclude Include="inline-image.h" />
|
||||||
<ClInclude Include="joind.h" />
|
<ClInclude Include="joind.h" />
|
||||||
<ClInclude Include="maingui.h" />
|
<ClInclude Include="maingui.h" />
|
||||||
<ClInclude Include="preferences-persistence.h" />
|
<ClInclude Include="preferences-persistence.h" />
|
||||||
@@ -102,6 +103,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
|||||||
<ClCompile Include="gtkutil.c" />
|
<ClCompile Include="gtkutil.c" />
|
||||||
<ClCompile Include="icon-resolver.c" />
|
<ClCompile Include="icon-resolver.c" />
|
||||||
<ClCompile Include="ignoregui.c" />
|
<ClCompile Include="ignoregui.c" />
|
||||||
|
<ClCompile Include="inline-image.c" />
|
||||||
<ClCompile Include="joind.c" />
|
<ClCompile Include="joind.c" />
|
||||||
<ClCompile Include="maingui.c" />
|
<ClCompile Include="maingui.c" />
|
||||||
<ClCompile Include="menu.c" />
|
<ClCompile Include="menu.c" />
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
<ClInclude Include="icon-resolver.h">
|
<ClInclude Include="icon-resolver.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="inline-image.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="joind.h">
|
<ClInclude Include="joind.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -170,6 +173,9 @@
|
|||||||
<ClCompile Include="ignoregui.c">
|
<ClCompile Include="ignoregui.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="inline-image.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="joind.c">
|
<ClCompile Include="joind.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
#define ICON_FKEYS_DELETE "edit-delete"
|
#define ICON_FKEYS_DELETE "edit-delete"
|
||||||
#define ICON_FKEYS_CANCEL "dialog-cancel"
|
#define ICON_FKEYS_CANCEL "dialog-cancel"
|
||||||
#define ICON_FKEYS_SAVE "document-save"
|
#define ICON_FKEYS_SAVE "document-save"
|
||||||
|
#define ICON_FKEYS_RESET "edit-undo"
|
||||||
|
|
||||||
static void replace_handle (GtkWidget * wid);
|
static void replace_handle (GtkWidget * wid);
|
||||||
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
||||||
@@ -80,7 +81,7 @@ void key_action_tab_clean (void);
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Remember that the *number* of actions is this *plus* 1 --AGL */
|
/* Remember that the *number* of actions is this *plus* 1 --AGL */
|
||||||
#define KEY_MAX_ACTIONS 14
|
#define KEY_MAX_ACTIONS 16
|
||||||
|
|
||||||
struct key_binding
|
struct key_binding
|
||||||
{
|
{
|
||||||
@@ -105,7 +106,10 @@ struct gcomp_data
|
|||||||
};
|
};
|
||||||
|
|
||||||
static int key_load_kbs (void);
|
static int key_load_kbs (void);
|
||||||
|
static int key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list);
|
||||||
static int key_save_kbs (void);
|
static int key_save_kbs (void);
|
||||||
|
static void key_dialog_load (GtkListStore *store);
|
||||||
|
static void key_dialog_reset (GtkWidget *wid, gpointer userdata);
|
||||||
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
|
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
|
||||||
char *d1, char *d2,
|
char *d1, char *d2,
|
||||||
struct session *sess);
|
struct session *sess);
|
||||||
@@ -142,6 +146,13 @@ static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt,
|
|||||||
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
|
static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt,
|
||||||
char *d1, char *d2,
|
char *d1, char *d2,
|
||||||
struct session *sess);
|
struct session *sess);
|
||||||
|
static int key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt,
|
||||||
|
char *d1, char *d2,
|
||||||
|
struct session *sess);
|
||||||
|
static int key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt,
|
||||||
|
char *d1, char *d2,
|
||||||
|
struct session *sess);
|
||||||
|
|
||||||
|
|
||||||
static GSList *keybind_list = NULL;
|
static GSList *keybind_list = NULL;
|
||||||
|
|
||||||
@@ -260,7 +271,7 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
{key_action_handle_command, "Run Command",
|
{key_action_handle_command, "Run Command",
|
||||||
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")},
|
N_("The \002Run Command\002 action runs the data in Data 1 as if it had been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002), so it is possible to run more than one command by using newlines.")},
|
||||||
{key_action_page_switch, "Change Page",
|
{key_action_page_switch, "Change Page",
|
||||||
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position. Set Data 1 to auto to switch to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
|
N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page number, channel name, network/channel, or auto. If Data 2 is set to anything then numeric switches are relative to the current position. Auto switches to the page with the most recent and important activity (queries first, then channels with hilight, channels with dialogue, channels with other data)")},
|
||||||
{key_action_insert, "Insert in Buffer",
|
{key_action_insert, "Insert in Buffer",
|
||||||
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")},
|
N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position. Escapes in Data 1 are interpreted (for example \002\\n\002, \002\\r\002, \002\\t\002, \002\\xNN\002 and \002\\\\\002).")},
|
||||||
{key_action_scroll_page, "Scroll Page",
|
{key_action_scroll_page, "Scroll Page",
|
||||||
@@ -287,6 +298,10 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
N_("This command moves the current tab family to the right")},
|
N_("This command moves the current tab family to the right")},
|
||||||
{key_action_put_history, "Push input line into history",
|
{key_action_put_history, "Push input line into history",
|
||||||
N_("Push input line into history but doesn't send to server")},
|
N_("Push input line into history but doesn't send to server")},
|
||||||
|
{key_action_menu_shortcut, "Menu Shortcut",
|
||||||
|
N_("Runs one of the built-in menu shortcuts. Set Data 1 to: network-list, new-server-tab, new-server-window, close, quit, menu-toggle, user-list-toggle, fullscreen-toggle, away-toggle, reset-marker, move-marker, copy-selection, search-text, search-next, search-previous or contents.")},
|
||||||
|
{key_action_reopen_closed_tab, "Reopen Closed Tab",
|
||||||
|
N_("Reopens the most recently closed channel tab")},
|
||||||
};
|
};
|
||||||
|
|
||||||
#define default_kb_cfg \
|
#define default_kb_cfg \
|
||||||
@@ -329,7 +344,86 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
|||||||
"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
|
"ACCEL=<Alt>Right\nMove front tab right\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
|
"ACCEL=<Primary><Shift>Page_Up\nMove tab family left\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
|
"ACCEL=<Primary><Shift>Page_Down\nMove tab family right\nD1!\nD2!\n\n"\
|
||||||
"ACCEL=F9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n"
|
"ACCEL=<Primary>s\nMenu Shortcut\nD1:network-list\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>t\nMenu Shortcut\nD1:new-server-tab\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>n\nMenu Shortcut\nD1:new-server-window\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>w\nMenu Shortcut\nD1:close\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>q\nMenu Shortcut\nD1:quit\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>F9\nMenu Shortcut\nD1:menu-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=F7\nMenu Shortcut\nD1:user-list-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=F11\nMenu Shortcut\nD1:fullscreen-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=<Alt>a\nMenu Shortcut\nD1:away-toggle\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>m\nMenu Shortcut\nD1:reset-marker\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>M\nMenu Shortcut\nD1:move-marker\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>C\nMenu Shortcut\nD1:copy-selection\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>f\nMenu Shortcut\nD1:search-text\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary>g\nMenu Shortcut\nD1:search-next\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>G\nMenu Shortcut\nD1:search-previous\nD2!\n\n"\
|
||||||
|
"ACCEL=F1\nMenu Shortcut\nD1:contents\nD2!\n\n"\
|
||||||
|
"ACCEL=<Primary><Shift>T\nReopen Closed Tab\nD1!\nD2!\n\n"
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
key_builtin_data_match (char *line, char *data)
|
||||||
|
{
|
||||||
|
if (line[2] == '!')
|
||||||
|
return data == NULL || data[0] == 0;
|
||||||
|
|
||||||
|
if (line[2] == ':')
|
||||||
|
return !strcmp (&line[3], data ? data : "");
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
key_binding_is_builtin (struct key_binding *kb)
|
||||||
|
{
|
||||||
|
char *buf, *ibuf;
|
||||||
|
char *action;
|
||||||
|
int pnt = 0;
|
||||||
|
int state = 0;
|
||||||
|
gboolean match = FALSE;
|
||||||
|
gboolean d1_match = FALSE;
|
||||||
|
off_t size;
|
||||||
|
|
||||||
|
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
action = key_actions[kb->action].name;
|
||||||
|
ibuf = g_strdup (default_kb_cfg);
|
||||||
|
size = strlen (default_kb_cfg);
|
||||||
|
|
||||||
|
while (buf_get_line (ibuf, &buf, &pnt, size))
|
||||||
|
{
|
||||||
|
if (strlen (buf) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
state = 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
match = !strcmp (buf, action);
|
||||||
|
state = 2;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
d1_match = match && key_builtin_data_match (buf, kb->data1);
|
||||||
|
state = 3;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (d1_match && key_builtin_data_match (buf, kb->data2))
|
||||||
|
{
|
||||||
|
g_free (ibuf);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
state = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (ibuf);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
key_init ()
|
key_init ()
|
||||||
@@ -443,28 +537,6 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
|
|||||||
if (!list)
|
if (!list)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
current_sess = sess;
|
current_sess = sess;
|
||||||
if ((evt->state & GDK_CONTROL_MASK) &&
|
|
||||||
!(evt->state & (GDK_MOD1_MASK | GDK_META_MASK)))
|
|
||||||
{
|
|
||||||
if (!(evt->state & GDK_SHIFT_MASK) &&
|
|
||||||
(evt->keyval == GDK_KEY_w || evt->keyval == GDK_KEY_W))
|
|
||||||
{
|
|
||||||
if (sess->type == SESS_CHANNEL)
|
|
||||||
{
|
|
||||||
fe_close_window (sess);
|
|
||||||
g_signal_stop_emission_by_name (G_OBJECT (wid), "key-press-event");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((evt->state & GDK_SHIFT_MASK) &&
|
|
||||||
(evt->keyval == GDK_KEY_t || evt->keyval == GDK_KEY_T))
|
|
||||||
{
|
|
||||||
mg_reopen_closed_channel_tab ();
|
|
||||||
g_signal_stop_emission_by_name (G_OBJECT (wid), "key-press-event");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
|
if (plugin_emit_keypress (sess, evt->state, evt->keyval, gdk_keyval_to_unicode (evt->keyval)))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -508,6 +580,31 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod)
|
||||||
|
{
|
||||||
|
struct key_binding *kb;
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
list = keybind_list;
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
kb = (struct key_binding*)list->data;
|
||||||
|
if (kb->action >= 0 && kb->action <= KEY_MAX_ACTIONS && kb->keyval != 0 && !strcmp (key_actions[kb->action].name, "Menu Shortcut") && kb->data1 && !strcmp (kb->data1, name))
|
||||||
|
{
|
||||||
|
*keyval = kb->keyval;
|
||||||
|
*mod = kb->mod;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
list = g_slist_next (list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ***** GUI code here ******************* */
|
/* ***** GUI code here ******************* */
|
||||||
|
|
||||||
@@ -518,6 +615,7 @@ enum
|
|||||||
ACTION_COLUMN,
|
ACTION_COLUMN,
|
||||||
D1_COLUMN,
|
D1_COLUMN,
|
||||||
D2_COLUMN,
|
D2_COLUMN,
|
||||||
|
CUSTOM_COLUMN,
|
||||||
N_COLUMNS
|
N_COLUMNS
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -642,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;
|
||||||
@@ -663,14 +777,23 @@ key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
|
|||||||
GtkTreeModel *model;
|
GtkTreeModel *model;
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
GtkXText *xtext;
|
GtkXText *xtext;
|
||||||
|
GtkWidget *delete_button;
|
||||||
char *actiontext;
|
char *actiontext;
|
||||||
|
gboolean custom;
|
||||||
int action;
|
int action;
|
||||||
|
|
||||||
|
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
|
||||||
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
|
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
|
||||||
|
{
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
||||||
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, -1);
|
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, CUSTOM_COLUMN, &custom, -1);
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, custom);
|
||||||
|
|
||||||
if (actiontext)
|
if (actiontext)
|
||||||
{
|
{
|
||||||
@@ -745,7 +868,10 @@ key_dialog_save (GtkWidget *wid, gpointer userdata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key_save_kbs () == 0)
|
if (key_save_kbs () == 0)
|
||||||
|
{
|
||||||
|
menu_update_quit_accel ();
|
||||||
key_dialog_close (wid, NULL);
|
key_dialog_close (wid, NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -758,6 +884,7 @@ key_dialog_add (GtkWidget *wid, gpointer userdata)
|
|||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
|
||||||
gtk_list_store_append (store, &iter);
|
gtk_list_store_append (store, &iter);
|
||||||
|
gtk_list_store_set (store, &iter, CUSTOM_COLUMN, TRUE, -1);
|
||||||
|
|
||||||
/* make sure the new row is visible and selected */
|
/* make sure the new row is visible and selected */
|
||||||
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
||||||
@@ -767,6 +894,134 @@ key_dialog_add (GtkWidget *wid, gpointer userdata)
|
|||||||
gtk_tree_path_free (path);
|
gtk_tree_path_free (path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
key_binding_signature (const char *action, const char *data1, const char *data2)
|
||||||
|
{
|
||||||
|
return g_strdup_printf ("%s\\n%s\\n%s", action ? action : "", data1 ? data1 : "", data2 ? data2 : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_dialog_reset_count (GHashTable *table, const char *key)
|
||||||
|
{
|
||||||
|
return GPOINTER_TO_INT (g_hash_table_lookup (table, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_dialog_reset_increment (GHashTable *table, char *key)
|
||||||
|
{
|
||||||
|
g_hash_table_replace (table, key, GINT_TO_POINTER (key_dialog_reset_count (table, key) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
key_dialog_reset (GtkWidget *wid, gpointer userdata)
|
||||||
|
{
|
||||||
|
GtkListStore *store = GTK_LIST_STORE (get_store ());
|
||||||
|
GtkListStore *custom_store;
|
||||||
|
GtkTreeIter iter, custom_iter;
|
||||||
|
GtkWidget *delete_button;
|
||||||
|
GHashTable *default_counts, *seen_counts;
|
||||||
|
GSList *list = NULL, *old_list, *default_iter;
|
||||||
|
struct key_binding *kb;
|
||||||
|
gboolean custom, keep;
|
||||||
|
char *key, *accel, *action, *data1, *data2, *signature;
|
||||||
|
|
||||||
|
if (key_load_kbs_from_buffer (g_strdup (default_kb_cfg), strlen (default_kb_cfg), &list) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
default_counts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
seen_counts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
for (default_iter = list; default_iter; default_iter = g_slist_next (default_iter))
|
||||||
|
{
|
||||||
|
kb = default_iter->data;
|
||||||
|
signature = key_binding_signature (key_actions[kb->action].name, kb->data1, kb->data2);
|
||||||
|
key_dialog_reset_increment (default_counts, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
custom_store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
|
||||||
|
KEY_COLUMN, &key,
|
||||||
|
ACCEL_COLUMN, &accel,
|
||||||
|
ACTION_COLUMN, &action,
|
||||||
|
D1_COLUMN, &data1,
|
||||||
|
D2_COLUMN, &data2,
|
||||||
|
CUSTOM_COLUMN, &custom,
|
||||||
|
-1);
|
||||||
|
signature = key_binding_signature (action, data1, data2);
|
||||||
|
keep = custom || key_dialog_reset_count (seen_counts, signature) >= key_dialog_reset_count (default_counts, signature);
|
||||||
|
if (!custom)
|
||||||
|
key_dialog_reset_increment (seen_counts, g_strdup (signature));
|
||||||
|
if (keep)
|
||||||
|
{
|
||||||
|
gtk_list_store_append (custom_store, &custom_iter);
|
||||||
|
gtk_list_store_set (custom_store, &custom_iter,
|
||||||
|
KEY_COLUMN, key,
|
||||||
|
ACCEL_COLUMN, accel,
|
||||||
|
ACTION_COLUMN, action,
|
||||||
|
D1_COLUMN, data1,
|
||||||
|
D2_COLUMN, data2,
|
||||||
|
CUSTOM_COLUMN, TRUE,
|
||||||
|
-1);
|
||||||
|
}
|
||||||
|
g_free (signature);
|
||||||
|
g_free (key);
|
||||||
|
g_free (accel);
|
||||||
|
g_free (action);
|
||||||
|
g_free (data1);
|
||||||
|
g_free (data2);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
old_list = keybind_list;
|
||||||
|
keybind_list = list;
|
||||||
|
gtk_list_store_clear (store);
|
||||||
|
key_dialog_load (store);
|
||||||
|
keybind_list = old_list;
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (custom_store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (custom_store), &iter,
|
||||||
|
KEY_COLUMN, &key,
|
||||||
|
ACCEL_COLUMN, &accel,
|
||||||
|
ACTION_COLUMN, &action,
|
||||||
|
D1_COLUMN, &data1,
|
||||||
|
D2_COLUMN, &data2,
|
||||||
|
-1);
|
||||||
|
gtk_list_store_append (store, &custom_iter);
|
||||||
|
gtk_list_store_set (store, &custom_iter,
|
||||||
|
KEY_COLUMN, key,
|
||||||
|
ACCEL_COLUMN, accel,
|
||||||
|
ACTION_COLUMN, action,
|
||||||
|
D1_COLUMN, data1,
|
||||||
|
D2_COLUMN, data2,
|
||||||
|
CUSTOM_COLUMN, TRUE,
|
||||||
|
-1);
|
||||||
|
g_free (key);
|
||||||
|
g_free (accel);
|
||||||
|
g_free (action);
|
||||||
|
g_free (data1);
|
||||||
|
g_free (data2);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (custom_store), &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
|
||||||
|
if (delete_button)
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
|
g_hash_table_destroy (default_counts);
|
||||||
|
g_hash_table_destroy (seen_counts);
|
||||||
|
g_object_unref (custom_store);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
||||||
{
|
{
|
||||||
@@ -774,9 +1029,14 @@ key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
|||||||
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
GtkTreePath *path;
|
GtkTreePath *path;
|
||||||
|
gboolean custom;
|
||||||
|
|
||||||
if (gtkutil_treeview_get_selected (view, &iter, -1))
|
if (gtkutil_treeview_get_selected (view, &iter, -1))
|
||||||
{
|
{
|
||||||
|
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, CUSTOM_COLUMN, &custom, -1);
|
||||||
|
if (!custom)
|
||||||
|
return;
|
||||||
|
|
||||||
/* delete this row, select next one */
|
/* delete this row, select next one */
|
||||||
if (gtk_list_store_remove (store, &iter))
|
if (gtk_list_store_remove (store, &iter))
|
||||||
{
|
{
|
||||||
@@ -803,13 +1063,13 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
|
||||||
|
|
||||||
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
G_TYPE_STRING, G_TYPE_STRING);
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||||
g_return_val_if_fail (store != NULL, NULL);
|
g_return_val_if_fail (store != NULL, NULL);
|
||||||
|
|
||||||
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
||||||
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
|
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
|
||||||
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
|
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
|
||||||
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE);
|
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), FALSE);
|
||||||
|
|
||||||
g_signal_connect (G_OBJECT (view), "key-press-event",
|
g_signal_connect (G_OBJECT (view), "key-press-event",
|
||||||
G_CALLBACK (key_dialog_keypress), NULL);
|
G_CALLBACK (key_dialog_keypress), NULL);
|
||||||
@@ -879,7 +1139,8 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
G_CALLBACK (key_dialog_combo_changed), combostore);
|
G_CALLBACK (key_dialog_combo_changed), combostore);
|
||||||
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
|
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
|
||||||
"Action", render,
|
"Action", render,
|
||||||
"text", ACTION_COLUMN,
|
"text", ACTION_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
render = gtk_cell_renderer_text_new ();
|
render = gtk_cell_renderer_text_new ();
|
||||||
@@ -890,6 +1151,7 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
GTK_TREE_VIEW (view), D1_COLUMN,
|
GTK_TREE_VIEW (view), D1_COLUMN,
|
||||||
"Data1", render,
|
"Data1", render,
|
||||||
"text", D1_COLUMN,
|
"text", D1_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
render = gtk_cell_renderer_text_new ();
|
render = gtk_cell_renderer_text_new ();
|
||||||
@@ -900,6 +1162,7 @@ key_dialog_treeview_new (GtkWidget *box)
|
|||||||
GTK_TREE_VIEW (view), D2_COLUMN,
|
GTK_TREE_VIEW (view), D2_COLUMN,
|
||||||
"Data2", render,
|
"Data2", render,
|
||||||
"text", D2_COLUMN,
|
"text", D2_COLUMN,
|
||||||
|
"editable", CUSTOM_COLUMN,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
|
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
|
||||||
@@ -945,7 +1208,8 @@ key_dialog_load (GtkListStore *store)
|
|||||||
ACCEL_COLUMN, accel_text,
|
ACCEL_COLUMN, accel_text,
|
||||||
ACTION_COLUMN, key_actions[kb->action].name,
|
ACTION_COLUMN, key_actions[kb->action].name,
|
||||||
D1_COLUMN, kb->data1,
|
D1_COLUMN, kb->data1,
|
||||||
D2_COLUMN, kb->data2, -1);
|
D2_COLUMN, kb->data2,
|
||||||
|
CUSTOM_COLUMN, !key_binding_is_builtin (kb), -1);
|
||||||
|
|
||||||
g_free (accel_text);
|
g_free (accel_text);
|
||||||
g_free (label_text);
|
g_free (label_text);
|
||||||
@@ -958,7 +1222,7 @@ void
|
|||||||
key_dialog_show ()
|
key_dialog_show ()
|
||||||
{
|
{
|
||||||
GtkWidget *vbox, *box;
|
GtkWidget *vbox, *box;
|
||||||
GtkWidget *view, *xtext;
|
GtkWidget *view, *xtext, *delete_button;
|
||||||
GtkListStore *store;
|
GtkListStore *store;
|
||||||
XTextColor xtext_palette[XTEXT_COLS];
|
XTextColor xtext_palette[XTEXT_COLS];
|
||||||
char buf[128];
|
char buf[128];
|
||||||
@@ -992,8 +1256,12 @@ key_dialog_show ()
|
|||||||
|
|
||||||
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
|
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
|
||||||
NULL, _("Add"));
|
NULL, _("Add"));
|
||||||
gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
|
delete_button = gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
|
||||||
NULL, _("Delete"));
|
NULL, _("Delete"));
|
||||||
|
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
|
||||||
|
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||||
|
gtkutil_button (box, ICON_FKEYS_RESET, NULL, key_dialog_reset,
|
||||||
|
NULL, _("Reset"));
|
||||||
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
|
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
|
||||||
NULL, _("Cancel"));
|
NULL, _("Cancel"));
|
||||||
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
|
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
|
||||||
@@ -1101,41 +1369,14 @@ key_load_kbs_helper_mod (char *buf, GdkModifierType *out)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
key_load_kbs (void)
|
key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list)
|
||||||
{
|
{
|
||||||
char *buf, *ibuf;
|
char *buf;
|
||||||
struct stat st;
|
|
||||||
struct key_binding *kb = NULL;
|
struct key_binding *kb = NULL;
|
||||||
int fd, len, state = 0, pnt = 0;
|
int len, state = 0, pnt = 0;
|
||||||
guint keyval;
|
guint keyval;
|
||||||
GdkModifierType mod = 0;
|
GdkModifierType mod = 0;
|
||||||
off_t size;
|
GSList *list = NULL;
|
||||||
|
|
||||||
fd = zoitechat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
ibuf = g_strdup (default_kb_cfg);
|
|
||||||
size = strlen (default_kb_cfg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (fstat (fd, &st) != 0)
|
|
||||||
{
|
|
||||||
close (fd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ibuf = g_malloc(st.st_size);
|
|
||||||
read (fd, ibuf, st.st_size);
|
|
||||||
size = st.st_size;
|
|
||||||
close (fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keybind_list)
|
|
||||||
{
|
|
||||||
g_slist_free_full (keybind_list, key_free);
|
|
||||||
keybind_list = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (buf_get_line (ibuf, &buf, &pnt, size))
|
while (buf_get_line (ibuf, &buf, &pnt, size))
|
||||||
{
|
{
|
||||||
@@ -1149,14 +1390,12 @@ key_load_kbs (void)
|
|||||||
case KBSTATE_MOD:
|
case KBSTATE_MOD:
|
||||||
kb = g_new0 (struct key_binding, 1);
|
kb = g_new0 (struct key_binding, 1);
|
||||||
|
|
||||||
/* New format */
|
|
||||||
if (strncmp (buf, "ACCEL=", 6) == 0)
|
if (strncmp (buf, "ACCEL=", 6) == 0)
|
||||||
{
|
{
|
||||||
buf += 6;
|
buf += 6;
|
||||||
|
|
||||||
gtk_accelerator_parse (buf, &keyval, &mod);
|
gtk_accelerator_parse (buf, &keyval, &mod);
|
||||||
|
|
||||||
|
|
||||||
kb->keyval = keyval;
|
kb->keyval = keyval;
|
||||||
kb->mod = key_modifier_get_valid (mod);
|
kb->mod = key_modifier_get_valid (mod);
|
||||||
|
|
||||||
@@ -1179,6 +1418,8 @@ key_load_kbs (void)
|
|||||||
if (keyval == 0)
|
if (keyval == 0)
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1195,6 +1436,8 @@ key_load_kbs (void)
|
|||||||
if (kb->action == KEY_MAX_ACTIONS + 1)
|
if (kb->action == KEY_MAX_ACTIONS + 1)
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1212,6 +1455,8 @@ key_load_kbs (void)
|
|||||||
if (buf[0] != 'D')
|
if (buf[0] != 'D')
|
||||||
{
|
{
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,7 +1477,6 @@ key_load_kbs (void)
|
|||||||
if (buf[2] == ':')
|
if (buf[2] == ':')
|
||||||
{
|
{
|
||||||
len = strlen (buf);
|
len = strlen (buf);
|
||||||
/* Add one for the NULL, subtract 3 for the "Dx:" */
|
|
||||||
len++;
|
len++;
|
||||||
len -= 3;
|
len -= 3;
|
||||||
if (state == KBSTATE_DT1)
|
if (state == KBSTATE_DT1)
|
||||||
@@ -1255,7 +1499,8 @@ key_load_kbs (void)
|
|||||||
continue;
|
continue;
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
keybind_list = g_slist_append (keybind_list, kb);
|
list = g_slist_append (list, kb);
|
||||||
|
kb = NULL;
|
||||||
|
|
||||||
state = KBSTATE_MOD;
|
state = KBSTATE_MOD;
|
||||||
}
|
}
|
||||||
@@ -1264,14 +1509,56 @@ key_load_kbs (void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
*out_list = list;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
corrupt_file:
|
corrupt_file:
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
g_free (kb);
|
key_free (kb);
|
||||||
|
g_slist_free_full (list, key_free);
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_load_kbs (void)
|
||||||
|
{
|
||||||
|
char *ibuf;
|
||||||
|
struct stat st;
|
||||||
|
int fd, result;
|
||||||
|
off_t size;
|
||||||
|
GSList *list = NULL;
|
||||||
|
|
||||||
|
fd = zoitechat_open_file ("keybindings.conf", O_RDONLY, 0, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
ibuf = g_strdup (default_kb_cfg);
|
||||||
|
size = strlen (default_kb_cfg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fstat (fd, &st) != 0)
|
||||||
|
{
|
||||||
|
close (fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ibuf = g_malloc(st.st_size);
|
||||||
|
read (fd, ibuf, st.st_size);
|
||||||
|
size = st.st_size;
|
||||||
|
close (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = key_load_kbs_from_buffer (ibuf, size, &list);
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (keybind_list)
|
||||||
|
g_slist_free_full (keybind_list, key_free);
|
||||||
|
keybind_list = list;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
char *d2, struct session *sess)
|
char *d2, struct session *sess)
|
||||||
@@ -1289,6 +1576,24 @@ key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_action_menu_shortcut (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
|
char *d2, struct session *sess)
|
||||||
|
{
|
||||||
|
if (menu_key_action (d1, evt->keyval, evt->state))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
||||||
|
char *d2, struct session *sess)
|
||||||
|
{
|
||||||
|
mg_reopen_closed_channel_tab ();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the given session is inside the main window. This predicate
|
* Check if the given session is inside the main window. This predicate
|
||||||
* is passed to lastact_getfirst() as a way to filter out detached sessions.
|
* is passed to lastact_getfirst() as a way to filter out detached sessions.
|
||||||
@@ -1307,6 +1612,9 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
char *d2, struct session *sess)
|
char *d2, struct session *sess)
|
||||||
{
|
{
|
||||||
session *newsess;
|
session *newsess;
|
||||||
|
char *network;
|
||||||
|
char *channel;
|
||||||
|
char *slash;
|
||||||
int len, i, num;
|
int len, i, num;
|
||||||
|
|
||||||
if (!d1)
|
if (!d1)
|
||||||
@@ -1318,21 +1626,13 @@ key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1,
|
|||||||
|
|
||||||
if (g_ascii_strcasecmp(d1, "auto") == 0)
|
if (g_ascii_strcasecmp(d1, "auto") == 0)
|
||||||
{
|
{
|
||||||
/* Auto switch makes no sense in detached sessions */
|
|
||||||
if (!sess->gui->is_tab)
|
if (!sess->gui->is_tab)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
/* Obtain a session with recent activity */
|
|
||||||
newsess = lastact_getfirst(session_check_is_tab);
|
newsess = lastact_getfirst(session_check_is_tab);
|
||||||
|
|
||||||
if (newsess)
|
if (newsess)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* Only sessions in the current window should be considered (i.e.
|
|
||||||
* we don't want to move the focus on a different window). This
|
|
||||||
* call could, in theory, do this, but we checked before that
|
|
||||||
* newsess->gui->is_tab and sess->gui->is_tab.
|
|
||||||
*/
|
|
||||||
mg_bring_tofront_sess(newsess);
|
mg_bring_tofront_sess(newsess);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1346,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -662,6 +527,43 @@ gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad)
|
|||||||
gtk_widget_destroy (dgad);
|
gtk_widget_destroy (dgad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtkutil_place_dialog (GtkWidget *dialog)
|
||||||
|
{
|
||||||
|
GList *list;
|
||||||
|
GList *item;
|
||||||
|
GtkWindow *anchor = NULL;
|
||||||
|
gint x;
|
||||||
|
gint y;
|
||||||
|
extern GtkWidget *parent_window;
|
||||||
|
|
||||||
|
list = gtk_window_list_toplevels ();
|
||||||
|
for (item = list; item; item = item->next)
|
||||||
|
{
|
||||||
|
if (item->data != dialog && GTK_IS_WINDOW (item->data) &&
|
||||||
|
gtk_widget_get_visible (GTK_WIDGET (item->data)) &&
|
||||||
|
gtk_window_is_active (GTK_WINDOW (item->data)))
|
||||||
|
{
|
||||||
|
anchor = GTK_WINDOW (item->data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_list_free (list);
|
||||||
|
|
||||||
|
if (!anchor && parent_window && parent_window != dialog && GTK_IS_WINDOW (parent_window))
|
||||||
|
anchor = GTK_WINDOW (parent_window);
|
||||||
|
|
||||||
|
if (anchor)
|
||||||
|
{
|
||||||
|
gtk_window_get_position (anchor, &x, &y);
|
||||||
|
gtk_window_move (GTK_WINDOW (dialog), x, y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry)
|
gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry)
|
||||||
{
|
{
|
||||||
@@ -675,15 +577,15 @@ gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry)
|
|||||||
|
|
||||||
switch (arg1)
|
switch (arg1)
|
||||||
{
|
{
|
||||||
case GTK_RESPONSE_REJECT:
|
|
||||||
callback (TRUE, text, user_data);
|
|
||||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
|
||||||
break;
|
|
||||||
case GTK_RESPONSE_ACCEPT:
|
case GTK_RESPONSE_ACCEPT:
|
||||||
callback (FALSE, text, user_data);
|
callback (FALSE, text, user_data);
|
||||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
break;
|
||||||
|
default:
|
||||||
|
callback (TRUE, text, user_data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -692,8 +594,8 @@ gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog)
|
|||||||
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
gtkutil_get_str (char *msg, char *def, void *callback, void *userdata, gboolean visible)
|
||||||
{
|
{
|
||||||
GtkWidget *dialog;
|
GtkWidget *dialog;
|
||||||
GtkWidget *entry;
|
GtkWidget *entry;
|
||||||
@@ -707,18 +609,11 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
|||||||
NULL);
|
NULL);
|
||||||
theme_manager_attach_window (dialog);
|
theme_manager_attach_window (dialog);
|
||||||
|
|
||||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
if (parent_window && GTK_IS_WINDOW (parent_window))
|
||||||
|
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||||
|
gtkutil_place_dialog (dialog);
|
||||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||||
|
|
||||||
if (userdata == (void *)1) /* nick box is usually on the very bottom, make it centered */
|
|
||||||
{
|
|
||||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
|
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
|
||||||
|
|
||||||
g_object_set_data (G_OBJECT (dialog), "cb", callback);
|
g_object_set_data (G_OBJECT (dialog), "cb", callback);
|
||||||
@@ -727,7 +622,8 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
|||||||
entry = gtk_entry_new ();
|
entry = gtk_entry_new ();
|
||||||
g_signal_connect (G_OBJECT (entry), "activate",
|
g_signal_connect (G_OBJECT (entry), "activate",
|
||||||
G_CALLBACK (gtkutil_str_enter), dialog);
|
G_CALLBACK (gtkutil_str_enter), dialog);
|
||||||
gtk_entry_set_text (GTK_ENTRY (entry), def);
|
gtk_entry_set_text (GTK_ENTRY (entry), def ? def : "");
|
||||||
|
gtk_entry_set_visibility (GTK_ENTRY (entry), visible);
|
||||||
gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
|
gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0);
|
||||||
|
|
||||||
label = gtk_label_new (msg);
|
label = gtk_label_new (msg);
|
||||||
@@ -741,6 +637,18 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
|||||||
gtk_widget_show_all (dialog);
|
gtk_widget_show_all (dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
||||||
|
{
|
||||||
|
gtkutil_get_str (msg, def, callback, userdata, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_get_password (char *msg, void *callback, void *userdata)
|
||||||
|
{
|
||||||
|
gtkutil_get_str (msg, "", callback, userdata, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin)
|
gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin)
|
||||||
{
|
{
|
||||||
@@ -776,15 +684,15 @@ gtkutil_get_bool_response (GtkDialog *dialog, gint arg1, gpointer spin)
|
|||||||
|
|
||||||
switch (arg1)
|
switch (arg1)
|
||||||
{
|
{
|
||||||
case GTK_RESPONSE_REJECT:
|
|
||||||
callback (0, user_data);
|
|
||||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
|
||||||
break;
|
|
||||||
case GTK_RESPONSE_ACCEPT:
|
case GTK_RESPONSE_ACCEPT:
|
||||||
callback (1, user_data);
|
callback (1, user_data);
|
||||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
break;
|
||||||
|
default:
|
||||||
|
callback (0, user_data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -802,9 +710,10 @@ fe_get_int (char *msg, int def, void *callback, void *userdata)
|
|||||||
_("_OK"), GTK_RESPONSE_ACCEPT,
|
_("_OK"), GTK_RESPONSE_ACCEPT,
|
||||||
NULL);
|
NULL);
|
||||||
theme_manager_attach_window (dialog);
|
theme_manager_attach_window (dialog);
|
||||||
|
if (parent_window && GTK_IS_WINDOW (parent_window))
|
||||||
|
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||||
|
gtkutil_place_dialog (dialog);
|
||||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
|
||||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
|
||||||
|
|
||||||
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
|
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0);
|
||||||
|
|
||||||
@@ -842,10 +751,10 @@ fe_get_bool (char *title, char *prompt, void *callback, void *userdata)
|
|||||||
_("_Yes"), GTK_RESPONSE_ACCEPT,
|
_("_Yes"), GTK_RESPONSE_ACCEPT,
|
||||||
NULL);
|
NULL);
|
||||||
theme_manager_attach_window (dialog);
|
theme_manager_attach_window (dialog);
|
||||||
|
if (parent_window && GTK_IS_WINDOW (parent_window))
|
||||||
|
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||||
|
gtkutil_place_dialog (dialog);
|
||||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
|
||||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
|
||||||
|
|
||||||
|
|
||||||
g_object_set_data (G_OBJECT (dialog), "cb", callback);
|
g_object_set_data (G_OBJECT (dialog), "cb", callback);
|
||||||
g_object_set_data (G_OBJECT (dialog), "ud", userdata);
|
g_object_set_data (G_OBJECT (dialog), "ud", userdata);
|
||||||
@@ -972,7 +881,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)
|
||||||
{
|
{
|
||||||
|
|||||||
1667
src/fe-gtk/inline-image.c
Normal file
1667
src/fe-gtk/inline-image.c
Normal file
File diff suppressed because it is too large
Load Diff
45
src/fe-gtk/inline-image.h
Normal file
45
src/fe-gtk/inline-image.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/* ZoiteChat
|
||||||
|
* Copyright (C) 2026 ZoiteChat contributors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZOITECHAT_INLINE_IMAGE_H
|
||||||
|
#define ZOITECHAT_INLINE_IMAGE_H
|
||||||
|
|
||||||
|
#include "xtext.h"
|
||||||
|
|
||||||
|
/* does this look like a direct http(s) link to an image file? */
|
||||||
|
gboolean inline_image_is_image_url (const char *url);
|
||||||
|
|
||||||
|
/* show the image behind 'url' inline below 'ent', or hide it again if it is
|
||||||
|
* already shown. Errors are reported to 'sess'. */
|
||||||
|
void inline_image_toggle (session *sess, GtkXText *xtext, textentry *ent,
|
||||||
|
const char *url);
|
||||||
|
|
||||||
|
/* replace image links in a message with a clickable placeholder label that
|
||||||
|
* hides the URL. Returns a newly allocated string, or NULL if the text has
|
||||||
|
* no image links (or the feature is off). */
|
||||||
|
char *inline_image_filter_text (const char *text);
|
||||||
|
|
||||||
|
/* pop up a window with a larger version of the entry's inline image */
|
||||||
|
void inline_image_show_viewer (GtkXText *xtext, textentry *ent);
|
||||||
|
|
||||||
|
/* can downloads go through the proxy configured for ZoiteChat (or is no
|
||||||
|
* proxy configured)? FALSE means loading media would have to bypass the
|
||||||
|
* user's proxy, which is never done. */
|
||||||
|
gboolean inline_image_proxy_usable (void);
|
||||||
|
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,24 @@ GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, i
|
|||||||
void mg_set_title (GtkWidget *button, char *title);
|
void mg_set_title (GtkWidget *button, char *title);
|
||||||
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
||||||
void mg_apply_setup (void);
|
void mg_apply_setup (void);
|
||||||
|
|
||||||
|
/* which parts of the GUI need a live rebuild after a preferences change */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int chanview:1; /* rebuild the tab bar / channel tree */
|
||||||
|
unsigned int tab_resort:1; /* re-sort channel tabs */
|
||||||
|
unsigned int tab_trunc:1; /* re-truncate tab labels */
|
||||||
|
unsigned int ulist_columns:1; /* rebuild user list columns */
|
||||||
|
unsigned int ulist_rows:1; /* re-fill user list rows */
|
||||||
|
unsigned int ulist_sort:1; /* re-apply user list sort order */
|
||||||
|
unsigned int ulist_count:1; /* user count label visibility */
|
||||||
|
unsigned int meters:1; /* rebuild lag/throttle meters */
|
||||||
|
unsigned int input_box:1; /* nick box visibility and access icon */
|
||||||
|
unsigned int topic_bar:1; /* topic wrap mode and mode button placement */
|
||||||
|
unsigned int transparency:1; /* main window opacity */
|
||||||
|
} mg_live_prefs;
|
||||||
|
|
||||||
|
void mg_apply_live_prefs (const mg_live_prefs *changes);
|
||||||
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);
|
||||||
@@ -48,6 +66,7 @@ void mg_dnd_drop_file (session *sess, char *target, char *uri);
|
|||||||
void mg_change_layout (int type);
|
void mg_change_layout (int type);
|
||||||
void mg_update_meters (session_gui *);
|
void mg_update_meters (session_gui *);
|
||||||
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
||||||
|
void mg_reply_update (session *sess);
|
||||||
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
||||||
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
||||||
/* DND */
|
/* DND */
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "../common/zoitechatc.h"
|
#include "../common/zoitechatc.h"
|
||||||
#include "../common/cfgfiles.h"
|
#include "../common/cfgfiles.h"
|
||||||
#include "../common/outbound.h"
|
#include "../common/outbound.h"
|
||||||
|
#include "../common/inbound.h"
|
||||||
#include "../common/ignore.h"
|
#include "../common/ignore.h"
|
||||||
#include "../common/fe.h"
|
#include "../common/fe.h"
|
||||||
#include "../common/server.h"
|
#include "../common/server.h"
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
#include "../common/util.h"
|
#include "../common/util.h"
|
||||||
#include "../common/text.h"
|
#include "../common/text.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
|
#include "inline-image.h"
|
||||||
#include "ascii.h"
|
#include "ascii.h"
|
||||||
#include "banlist.h"
|
#include "banlist.h"
|
||||||
#include "chanlist.h"
|
#include "chanlist.h"
|
||||||
@@ -784,6 +786,25 @@ fe_userlist_update (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_reply_to_latest_cb (GtkWidget *wid, gpointer data)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
item = reply_cache_latest_from (current_sess, str_copy);
|
||||||
|
if (!item)
|
||||||
|
{
|
||||||
|
PrintText (current_sess, _("No recent message to reply to.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text);
|
||||||
|
mg_reply_update (current_sess);
|
||||||
|
if (current_sess->gui && current_sess->gui->input_box)
|
||||||
|
gtk_widget_grab_focus (current_sess->gui->input_box);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||||
{
|
{
|
||||||
@@ -827,6 +848,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 */
|
||||||
@@ -1030,8 +1057,40 @@ open_url_cb (GtkWidget *item, char *url)
|
|||||||
handle_command (current_sess, buf, FALSE);
|
handle_command (current_sess, buf, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the text entry the open URL menu was created for; the widget pointer is
|
||||||
|
nulled automatically if the widget dies while the menu is up */
|
||||||
|
static GtkXText *urlmenu_xtext = NULL;
|
||||||
|
static textentry *urlmenu_ent = NULL;
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_urlmenu_set_entry (GtkWidget *xtext, textentry *ent)
|
||||||
|
{
|
||||||
|
if (urlmenu_xtext)
|
||||||
|
g_object_remove_weak_pointer (G_OBJECT (urlmenu_xtext),
|
||||||
|
(gpointer *) &urlmenu_xtext);
|
||||||
|
urlmenu_xtext = xtext ? GTK_XTEXT (xtext) : NULL;
|
||||||
|
urlmenu_ent = ent;
|
||||||
|
if (urlmenu_xtext)
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (urlmenu_xtext),
|
||||||
|
(gpointer *) &urlmenu_xtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
inline_image_menu_cb (GtkWidget *item, char *url)
|
||||||
|
{
|
||||||
|
if (urlmenu_xtext == NULL || urlmenu_ent == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* the line may have scrolled out of the buffer since the menu opened */
|
||||||
|
if (!gtk_xtext_buffer_contains (urlmenu_xtext, urlmenu_ent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
inline_image_toggle (current_sess, urlmenu_xtext, urlmenu_ent, url);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_urlmenu (GdkEventButton *event, char *url)
|
menu_urlmenu_entry (GdkEventButton *event, char *url, GtkWidget *xtext,
|
||||||
|
textentry *ent)
|
||||||
{
|
{
|
||||||
GtkWidget *menu;
|
GtkWidget *menu;
|
||||||
char *tmp, *chop;
|
char *tmp, *chop;
|
||||||
@@ -1039,6 +1098,14 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
g_free (str_copy);
|
g_free (str_copy);
|
||||||
str_copy = g_strdup (url);
|
str_copy = g_strdup (url);
|
||||||
|
|
||||||
|
if (xtext == NULL || ent == NULL || !prefs.hex_net_remote_media ||
|
||||||
|
!inline_image_is_image_url (str_copy))
|
||||||
|
{
|
||||||
|
xtext = NULL;
|
||||||
|
ent = NULL;
|
||||||
|
}
|
||||||
|
menu_urlmenu_set_entry (xtext, ent);
|
||||||
|
|
||||||
menu = menu_new ();
|
menu = menu_new ();
|
||||||
/* more than 51 chars? Chop it */
|
/* more than 51 chars? Chop it */
|
||||||
if (g_utf8_strlen (str_copy, -1) >= 52)
|
if (g_utf8_strlen (str_copy, -1) >= 52)
|
||||||
@@ -1061,6 +1128,15 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
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);
|
||||||
|
if (ent)
|
||||||
|
{
|
||||||
|
const char *shown = gtk_xtext_entry_get_image_url (GTK_XTEXT (xtext), ent);
|
||||||
|
|
||||||
|
menu_quick_item_with_callback (inline_image_menu_cb,
|
||||||
|
shown && strcmp (shown, str_copy) == 0 ?
|
||||||
|
_("Hide Inline Image") : _("Show Image Inline"),
|
||||||
|
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 */
|
/* custom ones from urlhandlers.conf */
|
||||||
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
||||||
@@ -1068,6 +1144,12 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
menu_popup (menu, event, NULL);
|
menu_popup (menu, event, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
menu_urlmenu (GdkEventButton *event, char *url)
|
||||||
|
{
|
||||||
|
menu_urlmenu_entry (event, url, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_chan_cycle (GtkWidget * menu, char *chan)
|
menu_chan_cycle (GtkWidget * menu, char *chan)
|
||||||
{
|
{
|
||||||
@@ -1755,44 +1837,6 @@ menu_change_layout (void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
menu_update_quit_accel (void)
|
|
||||||
{
|
|
||||||
GSList *list;
|
|
||||||
|
|
||||||
list = sess_list;
|
|
||||||
while (list)
|
|
||||||
{
|
|
||||||
session *sess = list->data;
|
|
||||||
session_gui *gui = sess->gui;
|
|
||||||
GtkWidget *item;
|
|
||||||
GtkAccelGroup *accel_group;
|
|
||||||
int enabled;
|
|
||||||
|
|
||||||
list = list->next;
|
|
||||||
if (!gui)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
item = gui->menu_item[MENU_ID_QUIT];
|
|
||||||
if (!item)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
enabled = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "zc-ctrlq-enabled"));
|
|
||||||
if (enabled == (int)prefs.hex_gui_ctrlq_quit)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
accel_group = g_object_get_data (G_OBJECT (item), "zc-quit-accel-group");
|
|
||||||
if (!accel_group)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (prefs.hex_gui_ctrlq_quit)
|
|
||||||
gtk_widget_add_accelerator (item, "activate", accel_group, GDK_KEY_q, STATE_CTRL, GTK_ACCEL_VISIBLE);
|
|
||||||
else
|
|
||||||
gtk_widget_remove_accelerator (item, accel_group, GDK_KEY_q, STATE_CTRL);
|
|
||||||
g_object_set_data (G_OBJECT (item), "zc-ctrlq-enabled", GINT_TO_POINTER (prefs.hex_gui_ctrlq_quit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_layout_cb (GtkWidget *item, gpointer none)
|
menu_layout_cb (GtkWidget *item, gpointer none)
|
||||||
{
|
{
|
||||||
@@ -1939,13 +1983,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 +2001,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 +2024,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 +2033,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 +2061,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)
|
||||||
{
|
{
|
||||||
@@ -2309,7 +2519,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 +2591,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;
|
||||||
@@ -2641,6 +2851,8 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
case M_MENUITEM:
|
case M_MENUITEM:
|
||||||
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
normalitem:
|
normalitem:
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
||||||
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
||||||
gtk_widget_add_accelerator (item, "activate", accel_group,
|
gtk_widget_add_accelerator (item, "activate", accel_group,
|
||||||
mymenu[i].key,
|
mymenu[i].key,
|
||||||
@@ -2669,6 +2881,8 @@ normalitem:
|
|||||||
case M_MENUTOG:
|
case M_MENUTOG:
|
||||||
item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
togitem:
|
togitem:
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
||||||
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
/* must avoid callback for Radio buttons */
|
/* must avoid callback for Radio buttons */
|
||||||
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state);
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), mymenu[i].state);
|
||||||
/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
/*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
|
||||||
|
|||||||
@@ -20,8 +20,11 @@
|
|||||||
#ifndef ZOITECHAT_MENU_H
|
#ifndef ZOITECHAT_MENU_H
|
||||||
#define ZOITECHAT_MENU_H
|
#define ZOITECHAT_MENU_H
|
||||||
|
|
||||||
|
#include "xtext.h"
|
||||||
|
|
||||||
GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
|
GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets);
|
||||||
void menu_urlmenu (GdkEventButton * event, char *url);
|
void menu_urlmenu (GdkEventButton * event, char *url);
|
||||||
|
void menu_urlmenu_entry (GdkEventButton *event, char *url, GtkWidget *xtext, textentry *ent);
|
||||||
void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
|
void menu_chanmenu (session *sess, GdkEventButton * event, char *chan);
|
||||||
void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel, gboolean istree);
|
void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel, gboolean istree);
|
||||||
void menu_addconnectmenu (server *serv, GtkWidget *menu);
|
void menu_addconnectmenu (server *serv, GtkWidget *menu);
|
||||||
@@ -39,6 +42,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);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ zoitechat_gtk_sources = [
|
|||||||
'gtkutil.c',
|
'gtkutil.c',
|
||||||
'icon-resolver.c',
|
'icon-resolver.c',
|
||||||
'ignoregui.c',
|
'ignoregui.c',
|
||||||
|
'inline-image.c',
|
||||||
'joind.c',
|
'joind.c',
|
||||||
'menu.c',
|
'menu.c',
|
||||||
'maingui.c',
|
'maingui.c',
|
||||||
@@ -50,7 +51,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')
|
||||||
@@ -241,7 +242,6 @@ test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
|
|||||||
timeout: 120,
|
timeout: 120,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
||||||
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
||||||
include_directories: [config_h_include],
|
include_directories: [config_h_include],
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ typedef struct _GtkStatusIcon GtkStatusIcon;
|
|||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <windows.h>
|
||||||
|
#include <gdk/gdkwin32.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum /* current icon status */
|
typedef enum /* current icon status */
|
||||||
@@ -855,6 +858,7 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
static int maximized;
|
static int maximized;
|
||||||
static int fullscreen;
|
static int fullscreen;
|
||||||
GtkWindow *win;
|
GtkWindow *win;
|
||||||
|
WinStatus status;
|
||||||
|
|
||||||
if (!tray_backend_active)
|
if (!tray_backend_active)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -870,7 +874,9 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
if (!win)
|
if (!win)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win)))
|
status = tray_get_window_status ();
|
||||||
|
|
||||||
|
if (force_hide || status != WS_HIDDEN)
|
||||||
{
|
{
|
||||||
if (prefs.hex_gui_tray_away)
|
if (prefs.hex_gui_tray_away)
|
||||||
zoitechat_command (ph, "ALLSERV AWAY");
|
zoitechat_command (ph, "ALLSERV AWAY");
|
||||||
@@ -890,8 +896,8 @@ tray_toggle_visibility (gboolean force_hide)
|
|||||||
gtk_window_maximize (win);
|
gtk_window_maximize (win);
|
||||||
if (fullscreen)
|
if (fullscreen)
|
||||||
gtk_window_fullscreen (win);
|
gtk_window_fullscreen (win);
|
||||||
gtk_widget_show (GTK_WIDGET (win));
|
|
||||||
gtk_window_deiconify (win);
|
gtk_window_deiconify (win);
|
||||||
|
gtk_widget_show (GTK_WIDGET (win));
|
||||||
gtk_window_present (win);
|
gtk_window_present (win);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -1406,6 +1539,11 @@ tray_cleanup (void)
|
|||||||
void
|
void
|
||||||
tray_apply_setup (void)
|
tray_apply_setup (void)
|
||||||
{
|
{
|
||||||
|
/* Theme/setup events can be dispatched before the tray plugin has
|
||||||
|
* been registered; there is nothing to (de)activate yet. */
|
||||||
|
if (!ph)
|
||||||
|
return;
|
||||||
|
|
||||||
if (tray_backend_active)
|
if (tray_backend_active)
|
||||||
{
|
{
|
||||||
if (!prefs.hex_gui_tray)
|
if (!prefs.hex_gui_tray)
|
||||||
|
|||||||
@@ -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,13 @@ 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_check_ask_pass;
|
||||||
|
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 +111,183 @@ 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);
|
||||||
|
if ((net && (net->flags & FLAG_PROMPT_PASSWORD)) ||
|
||||||
|
(edit_check_ask_pass && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass))))
|
||||||
|
use_keyring = FALSE;
|
||||||
|
has_local = net && net->pass && *net->pass && !use_keyring && !edit_pass_changed &&
|
||||||
|
(!edit_check_ask_pass || !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass)));
|
||||||
|
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 void
|
||||||
|
servlist_toggle_ask_pass_cb (GtkToggleButton *toggle, gpointer userdata)
|
||||||
|
{
|
||||||
|
gboolean active = gtk_toggle_button_get_active (toggle);
|
||||||
|
|
||||||
|
gtk_widget_set_sensitive (edit_check_use_keyring, !active);
|
||||||
|
gtk_widget_set_sensitive (edit_entry_pass, !active && (!selected_net || selected_net->logintype != LOGIN_SASLEXTERNAL));
|
||||||
|
gtk_widget_set_sensitive (edit_check_show_pass, !active);
|
||||||
|
servlist_update_password_tools (selected_net);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +1263,95 @@ 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 ask_pass;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
ask_pass = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass));
|
||||||
|
use_keyring = !ask_pass && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring));
|
||||||
|
keyring_changed = !!(net->flags & FLAG_USE_KEYRING) != !!use_keyring;
|
||||||
|
if (ask_pass)
|
||||||
|
{
|
||||||
|
secretstore_delete_network_password (net->name);
|
||||||
|
if (net->pass)
|
||||||
|
{
|
||||||
|
memset (net->pass, 0, strlen (net->pass));
|
||||||
|
g_free (net->pass);
|
||||||
|
net->pass = NULL;
|
||||||
|
}
|
||||||
|
net->flags &= ~FLAG_USE_KEYRING;
|
||||||
|
net->flags |= FLAG_PROMPT_PASSWORD;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
net->flags &= ~FLAG_PROMPT_PASSWORD;
|
||||||
|
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 +1359,20 @@ 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_check_ask_pass = NULL;
|
||||||
|
edit_button_encrypt_pass = NULL;
|
||||||
|
edit_button_import_pass = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
@@ -1126,6 +1403,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 +2598,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 (19, 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 +2617,106 @@ 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_ask_pass = gtk_check_button_new_with_mnemonic (_("Ask for password on connect"));
|
||||||
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_ask_pass), net->flags & FLAG_PROMPT_PASSWORD);
|
||||||
edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, &edit_label_real, 0);
|
servlist_table_attach (table3, edit_check_ask_pass, 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_ask_pass), "toggled",
|
||||||
|
G_CALLBACK (servlist_toggle_ask_pass_cb), NULL);
|
||||||
|
|
||||||
|
edit_check_use_keyring = gtk_check_button_new_with_mnemonic (_("Use system keyring"));
|
||||||
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring), net->flags & FLAG_USE_KEYRING);
|
||||||
|
servlist_table_attach (table3, edit_check_use_keyring, 0, 2, 7, 8,
|
||||||
|
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:"), 8, net->nick, &edit_label_nick, 0);
|
||||||
|
edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 9, net->nick2, &edit_label_nick2, 0);
|
||||||
|
edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 10, net->real, &edit_label_real, 0);
|
||||||
|
edit_entry_user = servlist_create_entry (table3, _("_User name:"), 11, 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, 12, 13,
|
||||||
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, 12, 13,
|
||||||
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:"), 13, 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, 14, 15,
|
||||||
|
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, 15, 16,
|
||||||
|
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, 15, 16,
|
||||||
|
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, 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);
|
||||||
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, 16, 17,
|
||||||
FALSE, FALSE,
|
FALSE, FALSE,
|
||||||
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL,
|
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL,
|
||||||
4, 2);
|
4, 2);
|
||||||
@@ -2393,7 +2742,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, 17, 18,
|
||||||
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 +2766,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);
|
||||||
|
|||||||
@@ -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}
|
||||||
};
|
};
|
||||||
@@ -545,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}
|
||||||
@@ -582,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},
|
||||||
@@ -655,11 +663,21 @@ 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},
|
||||||
{ST_ENTRY, N_("Password:"), P_OFFSETNL(hex_net_proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.hex_net_proxy_pass},
|
{ST_ENTRY, N_("Password:"), P_OFFSETNL(hex_net_proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.hex_net_proxy_pass},
|
||||||
|
|
||||||
|
{ST_HEADER, N_("Remote Media"), 0, 0, 0, 0},
|
||||||
|
{ST_TOGGLE, N_("Allow loading remote media (inline images) when clicked"),
|
||||||
|
P_OFFINTNL(hex_net_remote_media),
|
||||||
|
N_("Loading remote media contacts the site hosting it, which can reveal your IP address and that you viewed the message. You are asked for confirmation before each new domain is contacted."), 0, 0},
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -825,6 +843,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)
|
||||||
{
|
{
|
||||||
@@ -851,6 +879,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,
|
||||||
@@ -1014,28 +1050,30 @@ setup_create_spin (GtkWidget *table, int row, const setting *set)
|
|||||||
return wid;
|
return wid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static guint setup_trans_apply_tag = 0;
|
||||||
|
/* the window opacity the dialog opened with, so cancelling can undo the
|
||||||
|
* live preview done by the slider */
|
||||||
|
static int setup_transparency_snapshot;
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
setup_apply_trans (int *tag)
|
setup_apply_trans (gpointer userdata)
|
||||||
{
|
{
|
||||||
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
||||||
gtk_widget_set_opacity (current_sess->gui->window,
|
if (current_sess)
|
||||||
(prefs.hex_gui_transparency / 255.));
|
gtk_widget_set_opacity (current_sess->gui->window,
|
||||||
|
(prefs.hex_gui_transparency / 255.));
|
||||||
|
|
||||||
*tag = 0;
|
setup_trans_apply_tag = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_hscale_cb (GtkRange *wid, const setting *set)
|
setup_hscale_cb (GtkRange *wid, const setting *set)
|
||||||
{
|
{
|
||||||
static int tag = 0;
|
|
||||||
|
|
||||||
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
||||||
|
|
||||||
if (tag == 0)
|
if (setup_trans_apply_tag == 0)
|
||||||
{
|
setup_trans_apply_tag = g_idle_add ((GSourceFunc) setup_apply_trans, NULL);
|
||||||
tag = g_idle_add ((GSourceFunc) setup_apply_trans, &tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1060,7 +1098,8 @@ setup_create_hscale (GtkWidget *table, int row, const setting *set)
|
|||||||
|
|
||||||
#ifndef WIN32 /* Windows always supports this */
|
#ifndef WIN32 /* Windows always supports this */
|
||||||
/* Only used for transparency currently */
|
/* Only used for transparency currently */
|
||||||
if (!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
if (current_sess &&
|
||||||
|
!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
||||||
gtk_widget_set_sensitive (wid, FALSE);
|
gtk_widget_set_sensitive (wid, FALSE);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1364,7 +1403,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)
|
||||||
@@ -2180,7 +2219,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
|
||||||
@@ -2262,7 +2301,9 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
ThemeChangedEvent event;
|
ThemeChangedEvent event;
|
||||||
struct zoitechatprefs old_prefs = prefs;
|
struct zoitechatprefs old_prefs = prefs;
|
||||||
int old_dark_mode = prefs.hex_gui_dark_mode;
|
int old_dark_mode = prefs.hex_gui_dark_mode;
|
||||||
|
mg_live_prefs live_changes;
|
||||||
|
|
||||||
|
memset (&live_changes, 0, sizeof (live_changes));
|
||||||
|
|
||||||
#define DIFF(a) (pr->a != prefs.a)
|
#define DIFF(a) (pr->a != prefs.a)
|
||||||
|
|
||||||
@@ -2270,36 +2311,49 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
if (DIFF (hex_gui_lang))
|
if (DIFF (hex_gui_lang))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
#endif
|
#endif
|
||||||
if (DIFF (hex_gui_compact))
|
/* server tabs are only created when a connection is opened, so
|
||||||
noapply = TRUE;
|
existing connections keep their current tabs until a restart;
|
||||||
if (DIFF (hex_gui_input_icon))
|
the channel tree indentation is still re-applied below */
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_input_nick))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_lagometer))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_icons))
|
|
||||||
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))
|
live_changes.chanview = TRUE;
|
||||||
noapply = TRUE;
|
}
|
||||||
|
|
||||||
|
if (DIFF (hex_gui_compact))
|
||||||
|
{
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
}
|
||||||
|
if (DIFF (hex_gui_input_icon) || DIFF (hex_gui_input_nick))
|
||||||
|
live_changes.input_box = TRUE;
|
||||||
|
if (DIFF (hex_gui_lagometer) || DIFF (hex_gui_throttlemeter))
|
||||||
|
live_changes.meters = TRUE;
|
||||||
|
if (DIFF (hex_gui_mode_buttons_inline) || DIFF (hex_gui_topicbar_multiline))
|
||||||
|
live_changes.topic_bar = TRUE;
|
||||||
|
if (DIFF (hex_gui_tab_icons) || DIFF (hex_gui_tab_closebuttons) ||
|
||||||
|
DIFF (hex_gui_tab_small))
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
if (DIFF (hex_gui_tab_sort))
|
if (DIFF (hex_gui_tab_sort))
|
||||||
noapply = TRUE;
|
live_changes.tab_resort = TRUE;
|
||||||
if (DIFF (hex_gui_tab_trunc))
|
if (DIFF (hex_gui_tab_trunc))
|
||||||
noapply = TRUE;
|
live_changes.tab_trunc = TRUE;
|
||||||
if (DIFF (hex_gui_throttlemeter))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_ulist_count))
|
if (DIFF (hex_gui_ulist_count))
|
||||||
noapply = TRUE;
|
live_changes.ulist_count = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_icons))
|
if (DIFF (hex_gui_ulist_icons))
|
||||||
noapply = TRUE;
|
live_changes.ulist_rows = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_show_hosts))
|
if (DIFF (hex_gui_ulist_show_hosts))
|
||||||
noapply = TRUE;
|
{
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
live_changes.ulist_rows = TRUE; /* hostnames may be stale */
|
||||||
|
}
|
||||||
if (DIFF (hex_gui_ulist_sort))
|
if (DIFF (hex_gui_ulist_sort))
|
||||||
noapply = TRUE;
|
live_changes.ulist_sort = TRUE;
|
||||||
|
/* compare against the pre-dialog value: the slider previews the
|
||||||
|
opacity live on the current window only, so OK still needs to
|
||||||
|
apply it to the other windows */
|
||||||
|
if (pr->hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
live_changes.transparency = TRUE;
|
||||||
|
|
||||||
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
||||||
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
||||||
@@ -2343,6 +2397,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
|
|
||||||
theme_manager_dispatch_setup_apply (&event);
|
theme_manager_dispatch_setup_apply (&event);
|
||||||
|
|
||||||
|
mg_apply_live_prefs (&live_changes);
|
||||||
|
|
||||||
if (noapply)
|
if (noapply)
|
||||||
fe_message (_("Some settings were changed that require a"
|
fe_message (_("Some settings were changed that require a"
|
||||||
" restart to take full effect."), FE_MSG_WARN);
|
" restart to take full effect."), FE_MSG_WARN);
|
||||||
@@ -2375,6 +2431,9 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win)
|
|||||||
if (save_result.success)
|
if (save_result.success)
|
||||||
{
|
{
|
||||||
theme_preferences_stage_commit ();
|
theme_preferences_stage_commit ();
|
||||||
|
/* the previewed values were accepted, so the close handler
|
||||||
|
must not revert them */
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
gtk_widget_destroy (win);
|
gtk_widget_destroy (win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2437,6 +2496,24 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin)
|
|||||||
{
|
{
|
||||||
*swin = NULL;
|
*swin = NULL;
|
||||||
|
|
||||||
|
/* drop any pending transparency preview and undo the previewed
|
||||||
|
opacity when the dialog is cancelled; after OK the snapshot
|
||||||
|
was updated so this is a no-op */
|
||||||
|
if (setup_trans_apply_tag)
|
||||||
|
{
|
||||||
|
g_source_remove (setup_trans_apply_tag);
|
||||||
|
setup_trans_apply_tag = 0;
|
||||||
|
}
|
||||||
|
if (prefs.hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
{
|
||||||
|
mg_live_prefs revert_changes;
|
||||||
|
|
||||||
|
prefs.hex_gui_transparency = setup_transparency_snapshot;
|
||||||
|
memset (&revert_changes, 0, sizeof (revert_changes));
|
||||||
|
revert_changes.transparency = TRUE;
|
||||||
|
mg_apply_live_prefs (&revert_changes);
|
||||||
|
}
|
||||||
|
|
||||||
theme_preferences_stage_discard ();
|
theme_preferences_stage_discard ();
|
||||||
|
|
||||||
if (font_dialog)
|
if (font_dialog)
|
||||||
@@ -2458,6 +2535,7 @@ setup_open (void)
|
|||||||
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
||||||
|
|
||||||
color_change = FALSE;
|
color_change = FALSE;
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
theme_preferences_stage_begin ();
|
theme_preferences_stage_begin ();
|
||||||
setup_window = setup_window_open ();
|
setup_window = setup_window_open ();
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "../theme-access.h"
|
#include "../theme-access.h"
|
||||||
|
#include "../theme-css.h"
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
#include "../theme-runtime.h"
|
#include "../theme-runtime.h"
|
||||||
#include "../../xtext-color.h"
|
#include "../../xtext-color.h"
|
||||||
@@ -54,6 +55,13 @@ setup_apply_real (const ThemeChangedEvent *event)
|
|||||||
(void) event;
|
(void) event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gtkutil_append_font_css (GString *css, const PangoFontDescription *font_desc)
|
||||||
|
{
|
||||||
|
(void) css;
|
||||||
|
(void) font_desc;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
fe_dark_mode_is_enabled_for (unsigned int mode)
|
fe_dark_mode_is_enabled_for (unsigned int mode)
|
||||||
{
|
{
|
||||||
@@ -173,6 +181,14 @@ theme_gtk3_is_active (void)
|
|||||||
return stub_gtk3_active;
|
return stub_gtk3_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
(void) name;
|
||||||
|
(void) out_color;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
|
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
|
||||||
{
|
{
|
||||||
@@ -336,6 +352,79 @@ test_access_xtext_palette_widget_mapping_when_gtk3_active (void)
|
|||||||
g_object_unref (provider);
|
g_object_unref (provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_access_palette_css_does_not_feed_back_into_sampling (void)
|
||||||
|
{
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *label;
|
||||||
|
GtkCssProvider *theme1;
|
||||||
|
GtkCssProvider *theme2;
|
||||||
|
GdkScreen *screen;
|
||||||
|
XTextColor palette[2] = { 0 };
|
||||||
|
GdkRGBA old_bg;
|
||||||
|
GdkRGBA old_fg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_stubs ();
|
||||||
|
stub_gtk3_active = TRUE;
|
||||||
|
screen = gdk_screen_get_default ();
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
label = gtk_label_new ("mapped");
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), label);
|
||||||
|
gtk_widget_realize (window);
|
||||||
|
|
||||||
|
/* Old GTK3 theme active at screen level. */
|
||||||
|
theme1 = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme1,
|
||||||
|
"label { color: #112233; background-color: #445566; }"
|
||||||
|
"label:selected { color: #778899; background-color: #aabbcc; }",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme1), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
/* The app paints the sampled theme colors onto the widget, like the
|
||||||
|
* user list / channel tree / input box palette provider does. */
|
||||||
|
g_assert_true (gdk_rgba_parse (&old_bg, "#445566"));
|
||||||
|
g_assert_true (gdk_rgba_parse (&old_fg, "#112233"));
|
||||||
|
theme_css_apply_palette_widget (label, &old_bg, &old_fg, NULL);
|
||||||
|
|
||||||
|
/* The user switches to a new GTK3 theme. */
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme1));
|
||||||
|
theme2 = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme2,
|
||||||
|
"label { color: #00ff00; background-color: #ff0000; }"
|
||||||
|
"label:selected { color: #0000ff; background-color: #ffff00; }",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme2), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
gtk_style_context_reset_widgets (screen);
|
||||||
|
|
||||||
|
/* Re-sampling must see the new theme, not the palette colors the
|
||||||
|
* app applied for the old theme. */
|
||||||
|
theme_get_xtext_colors_for_widget (label, palette, G_N_ELEMENTS (palette));
|
||||||
|
|
||||||
|
g_assert_true (stub_last_gtk_map_valid);
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#00ff00"));
|
||||||
|
g_assert_true (rgba_close (&stub_last_gtk_map.text_foreground, &expected));
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#ff0000"));
|
||||||
|
g_assert_true (rgba_close (&stub_last_gtk_map.text_background, &expected));
|
||||||
|
|
||||||
|
/* Sampling must not permanently strip the palette class. */
|
||||||
|
g_assert_true (gtk_style_context_has_class (gtk_widget_get_style_context (label),
|
||||||
|
theme_css_palette_class_name ()));
|
||||||
|
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme2));
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
g_object_unref (theme1);
|
||||||
|
g_object_unref (theme2);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_access_dark_light_switch_affects_token_consumers (void)
|
test_access_dark_light_switch_affects_token_consumers (void)
|
||||||
{
|
{
|
||||||
@@ -363,6 +452,8 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
|
g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
|
||||||
g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
|
g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
|
||||||
test_access_xtext_palette_widget_mapping_when_gtk3_active);
|
test_access_xtext_palette_widget_mapping_when_gtk3_active);
|
||||||
|
g_test_add_func ("/theme/access/palette_css_does_not_feed_back_into_sampling",
|
||||||
|
test_access_palette_css_does_not_feed_back_into_sampling);
|
||||||
g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
|
g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
|
||||||
g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
|
g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
|
||||||
test_access_dark_light_switch_affects_token_consumers);
|
test_access_dark_light_switch_affects_token_consumers);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "../../fe-gtk.h"
|
#include "../../fe-gtk.h"
|
||||||
|
|
||||||
#include "../theme-application.h"
|
#include "../theme-application.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../maingui.h"
|
#include "../../maingui.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
@@ -54,6 +55,19 @@ theme_css_reload_input_style (gboolean enabled, const PangoFontDescription *font
|
|||||||
css_reload_calls++;
|
css_reload_calls++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_policy_system_prefers_dark (void)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_policy_is_dark_mode_active (unsigned int mode)
|
||||||
|
{
|
||||||
|
(void) mode;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_runtime_load (void)
|
theme_runtime_load (void)
|
||||||
{
|
{
|
||||||
@@ -104,7 +118,9 @@ test_invalid_font_falls_back_to_sans_11 (void)
|
|||||||
g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE);
|
g_assert_cmpint (pango_font_description_get_size (style->font_desc), ==, 11 * PANGO_SCALE);
|
||||||
g_assert_cmpint (message_calls, ==, 1);
|
g_assert_cmpint (message_calls, ==, 1);
|
||||||
g_assert_cmpint (css_reload_calls, ==, 1);
|
g_assert_cmpint (css_reload_calls, ==, 1);
|
||||||
g_assert_true (css_enabled);
|
/* Input boxes are themed per-widget; the app-global input CSS stays
|
||||||
|
* off so entries render natively under the active GTK3 theme. */
|
||||||
|
g_assert_false (css_enabled);
|
||||||
g_assert_nonnull (css_font_desc);
|
g_assert_nonnull (css_font_desc);
|
||||||
|
|
||||||
if (style->font_desc)
|
if (style->font_desc)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
#include "../theme-gtk3.h"
|
#include "../theme-gtk3.h"
|
||||||
#include "../../../common/gtk3-theme-service.h"
|
#include "../../../common/gtk3-theme-service.h"
|
||||||
@@ -35,14 +36,18 @@ struct zoitechatprefs prefs;
|
|||||||
|
|
||||||
static gboolean gtk_available;
|
static gboolean gtk_available;
|
||||||
static char *temp_root;
|
static char *temp_root;
|
||||||
|
static char *xdg_data_home;
|
||||||
static char *theme_parent_root;
|
static char *theme_parent_root;
|
||||||
static char *theme_child_root;
|
static char *theme_child_root;
|
||||||
static char *theme_switch_root;
|
static char *theme_switch_root;
|
||||||
|
static char *theme_chrome_root;
|
||||||
|
|
||||||
|
static gboolean stub_system_prefers_dark;
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_policy_system_prefers_dark (void)
|
theme_policy_system_prefers_dark (void)
|
||||||
{
|
{
|
||||||
return FALSE;
|
return stub_system_prefers_dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -130,6 +135,8 @@ zoitechat_gtk3_theme_find_by_id (const char *theme_id)
|
|||||||
return make_theme (theme_id, theme_child_root);
|
return make_theme (theme_id, theme_child_root);
|
||||||
if (g_strcmp0 (theme_id, "switch") == 0)
|
if (g_strcmp0 (theme_id, "switch") == 0)
|
||||||
return make_theme (theme_id, theme_switch_root);
|
return make_theme (theme_id, theme_switch_root);
|
||||||
|
if (g_strcmp0 (theme_id, "chrome") == 0)
|
||||||
|
return make_theme (theme_id, theme_chrome_root);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +183,11 @@ zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root)
|
|||||||
g_ptr_array_add (chain, g_strdup (theme_switch_root));
|
g_ptr_array_add (chain, g_strdup (theme_switch_root));
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
if (g_strcmp0 (theme_root, theme_chrome_root) == 0)
|
||||||
|
{
|
||||||
|
g_ptr_array_add (chain, g_strdup (theme_chrome_root));
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
g_ptr_array_unref (chain);
|
g_ptr_array_unref (chain);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -198,6 +210,14 @@ get_int_setting (const char *name)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
has_default_seat (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_themes (void)
|
setup_themes (void)
|
||||||
{
|
{
|
||||||
@@ -205,6 +225,10 @@ setup_themes (void)
|
|||||||
|
|
||||||
temp_root = g_dir_make_tmp ("zoitechat-theme-gtk3-settings-XXXXXX", NULL);
|
temp_root = g_dir_make_tmp ("zoitechat-theme-gtk3-settings-XXXXXX", NULL);
|
||||||
g_assert_nonnull (temp_root);
|
g_assert_nonnull (temp_root);
|
||||||
|
xdg_data_home = g_build_filename (temp_root, "data", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (xdg_data_home, 0700), ==, 0);
|
||||||
|
g_setenv ("XDG_DATA_HOME", xdg_data_home, TRUE);
|
||||||
|
|
||||||
theme_parent_root = g_build_filename (temp_root, "parent", NULL);
|
theme_parent_root = g_build_filename (temp_root, "parent", NULL);
|
||||||
theme_child_root = g_build_filename (temp_root, "child", NULL);
|
theme_child_root = g_build_filename (temp_root, "child", NULL);
|
||||||
theme_switch_root = g_build_filename (temp_root, "switch", NULL);
|
theme_switch_root = g_build_filename (temp_root, "switch", NULL);
|
||||||
@@ -234,6 +258,22 @@ setup_themes (void)
|
|||||||
"gtk-enable-animations=false\n"
|
"gtk-enable-animations=false\n"
|
||||||
"gtk-cursor-blink-time=444\n");
|
"gtk-cursor-blink-time=444\n");
|
||||||
|
|
||||||
|
theme_chrome_root = g_build_filename (temp_root, "chrome", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (theme_chrome_root, 0700), ==, 0);
|
||||||
|
ensure_css_dir (theme_chrome_root, "gtk-3.24");
|
||||||
|
/* Defines named colors but no window/menubar/menu rules of its own,
|
||||||
|
* so all chrome styling must come from the backfill provider. */
|
||||||
|
{
|
||||||
|
char *css = g_build_filename (theme_chrome_root, "gtk-3.24", "gtk.css", NULL);
|
||||||
|
write_file (css,
|
||||||
|
"@define-color theme_bg_color #e8f4e8;\n"
|
||||||
|
"@define-color theme_fg_color #102010;\n"
|
||||||
|
"@define-color theme_selected_bg_color #3465a4;\n"
|
||||||
|
"@define-color theme_selected_fg_color #ffffff;\n"
|
||||||
|
"label { color: #102010; }\n");
|
||||||
|
g_free (css);
|
||||||
|
}
|
||||||
|
|
||||||
path = g_build_filename (theme_parent_root, "index.theme", NULL);
|
path = g_build_filename (theme_parent_root, "index.theme", NULL);
|
||||||
write_file (path, "[Desktop Entry]\nName=parent\n");
|
write_file (path, "[Desktop Entry]\nName=parent\n");
|
||||||
g_free (path);
|
g_free (path);
|
||||||
@@ -253,10 +293,14 @@ teardown_themes (void)
|
|||||||
g_free (theme_parent_root);
|
g_free (theme_parent_root);
|
||||||
g_free (theme_child_root);
|
g_free (theme_child_root);
|
||||||
g_free (theme_switch_root);
|
g_free (theme_switch_root);
|
||||||
|
g_free (theme_chrome_root);
|
||||||
|
g_free (xdg_data_home);
|
||||||
g_free (temp_root);
|
g_free (temp_root);
|
||||||
theme_parent_root = NULL;
|
theme_parent_root = NULL;
|
||||||
theme_child_root = NULL;
|
theme_child_root = NULL;
|
||||||
theme_switch_root = NULL;
|
theme_switch_root = NULL;
|
||||||
|
theme_chrome_root = NULL;
|
||||||
|
xdg_data_home = NULL;
|
||||||
temp_root = NULL;
|
temp_root = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,10 +346,13 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
g_assert_no_error (error);
|
g_assert_no_error (error);
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
||||||
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
if (has_default_seat ())
|
||||||
g_assert_cmpstr (active_theme_name, ==, "child");
|
{
|
||||||
g_free (active_theme_name);
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
active_theme_name = NULL;
|
g_assert_cmpstr (active_theme_name, ==, "child");
|
||||||
|
g_free (active_theme_name);
|
||||||
|
active_theme_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
g_assert_no_error (error);
|
g_assert_no_error (error);
|
||||||
@@ -315,24 +362,234 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
theme_gtk3_disable ();
|
theme_gtk3_disable ();
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
||||||
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
||||||
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
if (has_default_seat ())
|
||||||
g_assert_cmpstr (active_theme_name, ==, default_theme_name);
|
{
|
||||||
g_free (active_theme_name);
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
|
g_assert_cmpstr (active_theme_name, ==, default_theme_name);
|
||||||
|
g_free (active_theme_name);
|
||||||
|
}
|
||||||
g_free (default_theme_name);
|
g_free (default_theme_name);
|
||||||
g_assert_false (theme_gtk3_is_active ());
|
g_assert_false (theme_gtk3_is_active ());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_follow_system_variant_resolves_to_theme_variant (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An explicitly selected in-app theme must not follow the OS
|
||||||
|
* light/dark preference: the legacy FOLLOW_SYSTEM variant resolves
|
||||||
|
* to the variant the theme itself provides, so the window chrome
|
||||||
|
* (menu bar) stays on the theme even when the system prefers dark. */
|
||||||
|
stub_system_prefers_dark = TRUE;
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_FOLLOW_SYSTEM, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
g_assert_true (theme_gtk3_is_active ());
|
||||||
|
g_assert_cmpint (theme_gtk3_active_variant (), ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
||||||
|
g_assert_false (get_bool_setting ("gtk-application-prefer-dark-theme"));
|
||||||
|
|
||||||
|
stub_system_prefers_dark = FALSE;
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_menu_bar_follows_active_theme_colors (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *menubar;
|
||||||
|
GtkStyleContext *context;
|
||||||
|
GdkRGBA bg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The chrome theme defines no menubar rules; without the backfill
|
||||||
|
* provider the menu bar would fall through to the base (OS) theme
|
||||||
|
* and follow the OS dark/light mode instead of the selected theme. */
|
||||||
|
g_assert_true (theme_gtk3_apply ("chrome", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
menubar = gtk_menu_bar_new ();
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), menubar);
|
||||||
|
gtk_widget_show_all (window);
|
||||||
|
|
||||||
|
context = gtk_widget_get_style_context (menubar);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#e8f4e8"));
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
/* The window background must follow the theme too, or the space
|
||||||
|
* around and beside the menu bar keeps the OS theme's colors. */
|
||||||
|
context = gtk_widget_get_style_context (window);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_desktop_color_sync_does_not_leak_into_chrome (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkWidget *menubar;
|
||||||
|
GtkStyleContext *context;
|
||||||
|
GtkCssProvider *desktop_css;
|
||||||
|
GdkScreen *screen;
|
||||||
|
GdkRGBA bg;
|
||||||
|
GdkRGBA expected;
|
||||||
|
GdkRGBA injected;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KDE's GTK color-scheme sync (~/.config/gtk-3.0/gtk.css) redefines
|
||||||
|
* the standard named colors with the OS palette at USER priority.
|
||||||
|
* The switch theme defines no named colors of its own, so a naive
|
||||||
|
* @theme_bg_color reference in the chrome backfill would resolve to
|
||||||
|
* the injected OS dark palette; the backfill must use its neutral
|
||||||
|
* variant fallback instead. */
|
||||||
|
screen = gdk_screen_get_default ();
|
||||||
|
desktop_css = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (desktop_css,
|
||||||
|
"@define-color theme_bg_color #1e1e1e;\n"
|
||||||
|
"@define-color theme_fg_color #eeeeee;\n"
|
||||||
|
"menubar { background-color: #1e1e1e; }\n",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (desktop_css), GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
{
|
||||||
|
GtkWidget *item;
|
||||||
|
GdkRGBA item_bg;
|
||||||
|
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
menubar = gtk_menu_bar_new ();
|
||||||
|
item = gtk_menu_item_new_with_label ("File");
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menubar), item);
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), menubar);
|
||||||
|
gtk_widget_show_all (window);
|
||||||
|
|
||||||
|
context = gtk_widget_get_style_context (menubar);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_true (gdk_rgba_parse (&expected, "#f6f5f4"));
|
||||||
|
g_assert_true (gdk_rgba_parse (&injected, "#1e1e1e"));
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - injected.red), >, 0.1);
|
||||||
|
g_assert_cmpfloat (ABS (bg.red - expected.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.green - expected.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (bg.blue - expected.blue), <, 0.01);
|
||||||
|
|
||||||
|
/* The menu bar body and the menu items must be one surface. */
|
||||||
|
context = gtk_widget_get_style_context (item);
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &item_bg);
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.red - bg.red), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.green - bg.green), <, 0.01);
|
||||||
|
g_assert_cmpfloat (ABS (item_bg.blue - bg.blue), <, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (desktop_css));
|
||||||
|
g_object_unref (desktop_css);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_stale_theme_symlink_is_replaced (void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
char *themes_dir;
|
||||||
|
char *link_path;
|
||||||
|
char *target;
|
||||||
|
char *active_theme_name = NULL;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!has_default_seat ())
|
||||||
|
{
|
||||||
|
g_test_message ("No default seat available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A dangling symlink left behind by a removed theme must not block
|
||||||
|
* pinning gtk-theme-name to the newly applied theme. */
|
||||||
|
themes_dir = g_build_filename (xdg_data_home, "themes", NULL);
|
||||||
|
g_assert_cmpint (g_mkdir_with_parents (themes_dir, 0700), ==, 0);
|
||||||
|
link_path = g_build_filename (themes_dir, "switch", NULL);
|
||||||
|
g_unlink (link_path);
|
||||||
|
{
|
||||||
|
GFile *link_file = g_file_new_for_path (link_path);
|
||||||
|
g_assert_true (g_file_make_symbolic_link (link_file, "/nonexistent/switch", NULL, NULL));
|
||||||
|
g_object_unref (link_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
|
g_assert_no_error (error);
|
||||||
|
|
||||||
|
target = g_file_read_link (link_path, NULL);
|
||||||
|
g_assert_cmpstr (target, ==, theme_switch_root);
|
||||||
|
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
|
||||||
|
g_assert_cmpstr (active_theme_name, ==, "switch");
|
||||||
|
|
||||||
|
g_free (active_theme_name);
|
||||||
|
g_free (target);
|
||||||
|
g_free (link_path);
|
||||||
|
g_free (themes_dir);
|
||||||
|
theme_gtk3_disable ();
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
gtk_available = gtk_init_check (&argc, &argv);
|
|
||||||
setup_themes ();
|
setup_themes ();
|
||||||
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
|
|
||||||
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
|
||||||
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
|
||||||
|
g_test_add_func ("/theme/gtk3/follow_system_variant_resolves_to_theme_variant",
|
||||||
|
test_follow_system_variant_resolves_to_theme_variant);
|
||||||
|
g_test_add_func ("/theme/gtk3/menu_bar_follows_active_theme_colors",
|
||||||
|
test_menu_bar_follows_active_theme_colors);
|
||||||
|
g_test_add_func ("/theme/gtk3/desktop_color_sync_does_not_leak_into_chrome",
|
||||||
|
test_desktop_color_sync_does_not_leak_into_chrome);
|
||||||
|
g_test_add_func ("/theme/gtk3/stale_theme_symlink_is_replaced",
|
||||||
|
test_stale_theme_symlink_is_replaced);
|
||||||
|
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
|
|
||||||
|
|||||||
@@ -89,3 +89,9 @@ theme_gtk3_is_active (void)
|
|||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeGtk3Variant
|
||||||
|
theme_gtk3_active_variant (void)
|
||||||
|
{
|
||||||
|
return THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
#include "../theme-gtk3.h"
|
#include "../theme-gtk3.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -84,6 +85,15 @@ gboolean theme_policy_system_prefers_dark (void)
|
|||||||
return stub_system_prefers_dark;
|
return stub_system_prefers_dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
@@ -203,6 +213,7 @@ reset_state (void)
|
|||||||
idle_add_calls = 0;
|
idle_add_calls = 0;
|
||||||
next_idle_source_id = 33;
|
next_idle_source_id = 33;
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
test_theme_gtk3_stub_reset ();
|
test_theme_gtk3_stub_reset ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,12 +246,14 @@ test_auto_refresh_dispatches_mode_palette_and_style_reasons (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_auto_refresh_ignores_non_auto_mode (void)
|
test_auto_refresh_ignores_locked_in_app_theme (void)
|
||||||
{
|
{
|
||||||
guint listener_id;
|
guint listener_id;
|
||||||
|
|
||||||
reset_state ();
|
reset_state ();
|
||||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "locked-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
stub_apply_mode_palette_changed = TRUE;
|
stub_apply_mode_palette_changed = TRUE;
|
||||||
listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL);
|
listener_id = theme_listener_register ("auto.nonauto", auto_listener, NULL);
|
||||||
theme_manager_set_idle_add_func (immediate_idle_add);
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
@@ -254,6 +267,7 @@ test_auto_refresh_ignores_non_auto_mode (void)
|
|||||||
|
|
||||||
theme_manager_set_idle_add_func (NULL);
|
theme_manager_set_idle_add_func (NULL);
|
||||||
theme_listener_unregister (listener_id);
|
theme_listener_unregister (listener_id);
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -263,6 +277,7 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
|
|||||||
|
|
||||||
reset_state ();
|
reset_state ();
|
||||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "system-follower", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
|
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
|
||||||
theme_manager_set_idle_add_func (immediate_idle_add);
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
@@ -274,6 +289,39 @@ test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
|
|||||||
g_assert_cmpint (listener_calls, ==, 0);
|
g_assert_cmpint (listener_calls, ==, 0);
|
||||||
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
|
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
|
||||||
|
|
||||||
|
theme_manager_set_idle_add_func (NULL);
|
||||||
|
theme_listener_unregister (listener_id);
|
||||||
|
prefs.hex_gui_gtk3_theme[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_auto_refresh_follows_system_theme_without_auto_mode (void)
|
||||||
|
{
|
||||||
|
guint listener_id;
|
||||||
|
|
||||||
|
reset_state ();
|
||||||
|
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||||
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
|
||||||
|
listener_id = theme_listener_register ("auto.system", auto_listener, NULL);
|
||||||
|
theme_manager_set_idle_add_func (immediate_idle_add);
|
||||||
|
|
||||||
|
theme_manager_refresh_auto_mode ();
|
||||||
|
|
||||||
|
/* No in-app theme is selected, so a system theme change must
|
||||||
|
* re-resolve the mapped palette even outside auto dark mode. */
|
||||||
|
g_assert_cmpint (idle_add_calls, ==, 1);
|
||||||
|
g_assert_cmpint (auto_state_calls, ==, 0);
|
||||||
|
g_assert_cmpint (listener_calls, ==, 1);
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_PALETTE));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_WIDGET_STYLE));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_USERLIST));
|
||||||
|
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_MODE));
|
||||||
|
|
||||||
|
/* Unchanged system state must not dispatch again. */
|
||||||
|
theme_manager_refresh_auto_mode ();
|
||||||
|
g_assert_cmpint (idle_add_calls, ==, 2);
|
||||||
|
g_assert_cmpint (listener_calls, ==, 1);
|
||||||
|
|
||||||
theme_manager_set_idle_add_func (NULL);
|
theme_manager_set_idle_add_func (NULL);
|
||||||
theme_listener_unregister (listener_id);
|
theme_listener_unregister (listener_id);
|
||||||
}
|
}
|
||||||
@@ -284,9 +332,11 @@ main (int argc, char **argv)
|
|||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons",
|
g_test_add_func ("/theme/manager/auto_refresh_dispatches_mode_palette_and_style_reasons",
|
||||||
test_auto_refresh_dispatches_mode_palette_and_style_reasons);
|
test_auto_refresh_dispatches_mode_palette_and_style_reasons);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_ignores_non_auto_mode",
|
g_test_add_func ("/theme/manager/auto_refresh_ignores_locked_in_app_theme",
|
||||||
test_auto_refresh_ignores_non_auto_mode);
|
test_auto_refresh_ignores_locked_in_app_theme);
|
||||||
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
|
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
|
||||||
test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
|
test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
|
||||||
|
g_test_add_func ("/theme/manager/auto_refresh_follows_system_theme_without_auto_mode",
|
||||||
|
test_auto_refresh_follows_system_theme_without_auto_mode);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -77,6 +78,15 @@ gboolean theme_policy_system_prefers_dark (void)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "../theme-palette.h"
|
#include "../theme-palette.h"
|
||||||
#include "../theme-manager.h"
|
#include "../theme-manager.h"
|
||||||
|
#include "../theme-policy.h"
|
||||||
#include "../../../common/zoitechat.h"
|
#include "../../../common/zoitechat.h"
|
||||||
#include "../../../common/zoitechatc.h"
|
#include "../../../common/zoitechatc.h"
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ struct session *current_tab;
|
|||||||
struct session *lastact_sess;
|
struct session *lastact_sess;
|
||||||
struct zoitechatprefs prefs;
|
struct zoitechatprefs prefs;
|
||||||
|
|
||||||
|
static gboolean gtk_available;
|
||||||
static gboolean stub_policy_dark;
|
static gboolean stub_policy_dark;
|
||||||
static unsigned int stub_policy_mode;
|
static unsigned int stub_policy_mode;
|
||||||
static gboolean stub_apply_mode_result;
|
static gboolean stub_apply_mode_result;
|
||||||
@@ -79,7 +81,18 @@ void zoitechat_set_theme_post_apply_callback (zoitechat_theme_post_apply_callbac
|
|||||||
gboolean theme_policy_is_dark_mode_active (unsigned int mode)
|
gboolean theme_policy_is_dark_mode_active (unsigned int mode)
|
||||||
{
|
{
|
||||||
stub_policy_mode = mode;
|
stub_policy_mode = mode;
|
||||||
return stub_policy_dark;
|
if (mode == ZOITECHAT_DARK_MODE_AUTO)
|
||||||
|
return stub_policy_dark;
|
||||||
|
return fe_dark_mode_is_enabled_for (mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_init (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
(void) callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
gboolean theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
@@ -109,11 +122,12 @@ void theme_runtime_reset_mode_colors (gboolean dark_mode)
|
|||||||
(void) dark_mode;
|
(void) dark_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
|
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
(void) mode;
|
(void) mode;
|
||||||
(void) dark_active;
|
|
||||||
stub_apply_mode_calls++;
|
stub_apply_mode_calls++;
|
||||||
|
if (palette_changed)
|
||||||
|
*palette_changed = TRUE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,10 +263,11 @@ test_manager_set_token_color_routes_by_mode (void)
|
|||||||
GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 };
|
GdkRGBA color = { 0.1, 0.2, 0.3, 1.0 };
|
||||||
gboolean palette_changed = FALSE;
|
gboolean palette_changed = FALSE;
|
||||||
|
|
||||||
|
/* All edits target the single user palette; the mode argument is
|
||||||
|
* accepted for compatibility but no longer selects a dark palette. */
|
||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = FALSE;
|
stub_policy_dark = FALSE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_LIGHT);
|
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_apply_mode_calls, ==, 1);
|
g_assert_cmpint (stub_apply_mode_calls, ==, 1);
|
||||||
@@ -262,16 +277,14 @@ test_manager_set_token_color_routes_by_mode (void)
|
|||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_DARK);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
|
||||||
|
|
||||||
reset_manager_stubs ();
|
reset_manager_stubs ();
|
||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_AUTO, THEME_TOKEN_MIRC_2, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_policy_mode, ==, ZOITECHAT_DARK_MODE_AUTO);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -300,9 +313,9 @@ test_manager_set_token_color_routes_setup_indexes (void)
|
|||||||
stub_policy_dark = TRUE;
|
stub_policy_dark = TRUE;
|
||||||
palette_changed = FALSE;
|
palette_changed = FALSE;
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed);
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_DARK, def->token, &color, &palette_changed);
|
||||||
g_assert_cmpint (stub_dark_set_calls, ==, 1);
|
g_assert_cmpint (stub_user_set_calls, ==, 1);
|
||||||
g_assert_cmpint (stub_last_dark_token, ==, def->token);
|
g_assert_cmpint (stub_last_user_token, ==, def->token);
|
||||||
g_assert_cmpint (stub_user_set_calls, ==, 0);
|
g_assert_cmpint (stub_dark_set_calls, ==, 0);
|
||||||
g_assert_true (palette_changed);
|
g_assert_true (palette_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +358,12 @@ test_manager_window_attach_detach_idempotence (void)
|
|||||||
gulong *second_handler_ptr;
|
gulong *second_handler_ptr;
|
||||||
gulong second_handler_id;
|
gulong second_handler_id;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
g_assert_nonnull (window);
|
g_assert_nonnull (window);
|
||||||
|
|
||||||
@@ -395,5 +414,6 @@ main (int argc, char **argv)
|
|||||||
test_manager_listener_registration_dispatch_and_unregister);
|
test_manager_listener_registration_dispatch_and_unregister);
|
||||||
g_test_add_func ("/theme/manager/window_attach_detach_idempotence",
|
g_test_add_func ("/theme/manager/window_attach_detach_idempotence",
|
||||||
test_manager_window_attach_detach_idempotence);
|
test_manager_window_attach_detach_idempotence);
|
||||||
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ InputStyle *input_style;
|
|||||||
|
|
||||||
static gboolean gtk_available;
|
static gboolean gtk_available;
|
||||||
static int apply_current_calls;
|
static int apply_current_calls;
|
||||||
|
static int set_token_color_calls;
|
||||||
static char applied_theme_id[256];
|
static char applied_theme_id[256];
|
||||||
static ThemeGtk3Variant applied_variant;
|
static ThemeGtk3Variant applied_variant;
|
||||||
static gboolean removed_selected;
|
static gboolean removed_selected;
|
||||||
@@ -70,6 +71,15 @@ load_text_events (void)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char **
|
||||||
|
text_color_event_names (int color, int *count)
|
||||||
|
{
|
||||||
|
(void)color;
|
||||||
|
if (count)
|
||||||
|
*count = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_get_color (ThemeSemanticToken token, GdkRGBA *color)
|
theme_get_color (ThemeSemanticToken token, GdkRGBA *color)
|
||||||
{
|
{
|
||||||
@@ -95,6 +105,7 @@ theme_manager_set_token_color (unsigned int dark_mode, ThemeSemanticToken token,
|
|||||||
(void)dark_mode;
|
(void)dark_mode;
|
||||||
(void)token;
|
(void)token;
|
||||||
(void)color;
|
(void)color;
|
||||||
|
set_token_color_calls++;
|
||||||
if (changed)
|
if (changed)
|
||||||
*changed = FALSE;
|
*changed = FALSE;
|
||||||
}
|
}
|
||||||
@@ -107,12 +118,23 @@ theme_manager_reset_mode_colors (unsigned int mode, gboolean *palette_changed)
|
|||||||
*palette_changed = FALSE;
|
*palette_changed = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_runtime_clear_gtk_mapped_custom_tokens (void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_manager_save_preferences (void)
|
theme_manager_save_preferences (void)
|
||||||
{
|
{
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_manager_apply_to_window (GtkWidget *window)
|
||||||
|
{
|
||||||
|
(void)window;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_manager_dispatch_changed (ThemeChangedReason reasons)
|
theme_manager_dispatch_changed (ThemeChangedReason reasons)
|
||||||
{
|
{
|
||||||
@@ -289,7 +311,7 @@ theme_gtk3_is_active (void)
|
|||||||
#include "../theme-preferences.c"
|
#include "../theme-preferences.c"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_removed_selected_theme_commits_fallback_and_applies (void)
|
test_removed_selected_theme_falls_back_to_system_default (void)
|
||||||
{
|
{
|
||||||
GtkWidget *page;
|
GtkWidget *page;
|
||||||
theme_preferences_ui *ui;
|
theme_preferences_ui *ui;
|
||||||
@@ -306,23 +328,26 @@ test_removed_selected_theme_commits_fallback_and_applies (void)
|
|||||||
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
|
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
removed_selected = FALSE;
|
removed_selected = FALSE;
|
||||||
apply_current_calls = 0;
|
|
||||||
applied_theme_id[0] = '\0';
|
|
||||||
|
|
||||||
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
||||||
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
||||||
g_assert_nonnull (ui);
|
g_assert_nonnull (ui);
|
||||||
|
|
||||||
|
apply_current_calls = 0;
|
||||||
|
applied_theme_id[0] = '\0';
|
||||||
|
|
||||||
g_assert_nonnull (ui->gtk3_remove);
|
g_assert_nonnull (ui->gtk3_remove);
|
||||||
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
|
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
|
||||||
|
|
||||||
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
/* Removing the active in-app theme returns to the system GTK3
|
||||||
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
* theme ("None") instead of forcing another theme. */
|
||||||
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "");
|
||||||
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "");
|
||||||
|
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
|
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
g_assert_cmpint (apply_current_calls, ==, 1);
|
g_assert_cmpint (apply_current_calls, ==, 1);
|
||||||
g_assert_cmpstr (applied_theme_id, ==, "fallback-theme");
|
g_assert_cmpstr (applied_theme_id, ==, "");
|
||||||
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
|
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_FOLLOW_SYSTEM);
|
||||||
|
|
||||||
gtk_widget_destroy (page);
|
gtk_widget_destroy (page);
|
||||||
}
|
}
|
||||||
@@ -396,16 +421,55 @@ test_select_none_resets_theme_and_applies (void)
|
|||||||
gtk_widget_destroy (page);
|
gtk_widget_destroy (page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_select_theme_does_not_pin_palette_tokens (void)
|
||||||
|
{
|
||||||
|
GtkWidget *page;
|
||||||
|
theme_preferences_ui *ui;
|
||||||
|
struct zoitechatprefs setup_prefs;
|
||||||
|
|
||||||
|
if (!gtk_available)
|
||||||
|
{
|
||||||
|
g_test_message ("GTK display not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (&setup_prefs, 0, sizeof (setup_prefs));
|
||||||
|
memset (&prefs, 0, sizeof (prefs));
|
||||||
|
removed_selected = FALSE;
|
||||||
|
apply_current_calls = 0;
|
||||||
|
set_token_color_calls = 0;
|
||||||
|
|
||||||
|
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
|
||||||
|
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
||||||
|
g_assert_nonnull (ui);
|
||||||
|
|
||||||
|
/* 0 = None, 1 = removed-theme, 2 = fallback-theme */
|
||||||
|
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), 2);
|
||||||
|
|
||||||
|
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
|
||||||
|
g_assert_cmpint (apply_current_calls, ==, 1);
|
||||||
|
/* Selecting a GTK3 theme must leave the mapped palette tokens
|
||||||
|
* unpinned so the user list, channel tree and input box keep
|
||||||
|
* resolving their colors from the active theme instead of being
|
||||||
|
* frozen until the next restart. */
|
||||||
|
g_assert_cmpint (set_token_color_calls, ==, 0);
|
||||||
|
|
||||||
|
gtk_widget_destroy (page);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
gtk_available = gtk_init_check (&argc, &argv);
|
gtk_available = gtk_init_check (&argc, &argv);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_removed_selection_applies_fallback",
|
g_test_add_func ("/theme/preferences/gtk3_removed_selection_falls_back_to_system",
|
||||||
test_removed_selected_theme_commits_fallback_and_applies);
|
test_removed_selected_theme_falls_back_to_system_default);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default",
|
g_test_add_func ("/theme/preferences/gtk3_unset_keeps_system_default",
|
||||||
test_unset_theme_keeps_system_default_without_apply);
|
test_unset_theme_keeps_system_default_without_apply);
|
||||||
g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme",
|
g_test_add_func ("/theme/preferences/gtk3_select_none_resets_theme",
|
||||||
test_select_none_resets_theme_and_applies);
|
test_select_none_resets_theme_and_applies);
|
||||||
|
g_test_add_func ("/theme/preferences/gtk3_select_theme_does_not_pin_palette_tokens",
|
||||||
|
test_select_theme_does_not_pin_palette_tokens);
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,12 +290,12 @@ test_ui_edits_persist_without_legacy_array_mutation (void)
|
|||||||
theme_runtime_save ();
|
theme_runtime_save ();
|
||||||
|
|
||||||
theme_runtime_load ();
|
theme_runtime_load ();
|
||||||
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL);
|
theme_runtime_apply_dark_mode (FALSE);
|
||||||
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded));
|
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &light_loaded));
|
||||||
g_assert_true (gdk_rgba_parse (&light_expected, "#224466"));
|
g_assert_true (gdk_rgba_parse (&light_expected, "#224466"));
|
||||||
g_assert_true (colors_equal (&light_loaded, &light_expected));
|
g_assert_true (colors_equal (&light_loaded, &light_expected));
|
||||||
|
|
||||||
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_DARK, NULL);
|
theme_runtime_apply_dark_mode (TRUE);
|
||||||
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded));
|
g_assert_true (theme_runtime_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &dark_loaded));
|
||||||
g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc"));
|
g_assert_true (gdk_rgba_parse (&dark_expected, "#88aacc"));
|
||||||
g_assert_true (colors_equal (&dark_loaded, &dark_expected));
|
g_assert_true (colors_equal (&dark_loaded, &dark_expected));
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
|
|
||||||
#include "theme-access.h"
|
#include "theme-access.h"
|
||||||
|
|
||||||
#include "theme-runtime.h"
|
#include "theme-css.h"
|
||||||
#include "theme-gtk3.h"
|
#include "theme-gtk3.h"
|
||||||
|
#include "theme-runtime.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +33,8 @@ enum
|
|||||||
THEME_XTEXT_MARK_BG_INDEX = 100,
|
THEME_XTEXT_MARK_BG_INDEX = 100,
|
||||||
THEME_XTEXT_FG_INDEX = 101,
|
THEME_XTEXT_FG_INDEX = 101,
|
||||||
THEME_XTEXT_BG_INDEX = 102,
|
THEME_XTEXT_BG_INDEX = 102,
|
||||||
THEME_XTEXT_MARKER_INDEX = 103
|
THEME_XTEXT_MARKER_INDEX = 103,
|
||||||
|
THEME_XTEXT_MARKER_LEGACY_INDEX = 36
|
||||||
};
|
};
|
||||||
|
|
||||||
static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
||||||
@@ -52,14 +54,13 @@ static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len, gboolean apply_base)
|
theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
size_t start = apply_base ? 0 : 32;
|
|
||||||
|
|
||||||
if (palette_len == 0)
|
if (palette_len == 0)
|
||||||
return;
|
return;
|
||||||
for (i = start; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++)
|
for (i = 32; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++)
|
||||||
{
|
{
|
||||||
palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0;
|
palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0;
|
||||||
palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0;
|
palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0;
|
||||||
@@ -112,28 +113,95 @@ theme_access_context_get_background_color (GtkStyleContext *context, GtkStateFla
|
|||||||
gtk_style_context_restore (context);
|
gtk_style_context_restore (context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
theme_access_lookup_named_color (GtkStyleContext *context, const char *const *names, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
/* With an in-app GTK3 theme selected, prefer the colors resolved from
|
||||||
|
* the theme itself: desktop integration CSS (e.g. KDE's color-scheme
|
||||||
|
* sync) redefines the same named colors with the OS palette in the
|
||||||
|
* widget cascade, which would drag OS dark/light colors into the
|
||||||
|
* mapped palette. */
|
||||||
|
for (i = 0; names[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
if (theme_gtk3_lookup_theme_color (names[i], out_color))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; names[i] != NULL; i++)
|
||||||
|
{
|
||||||
|
if (gtk_style_context_lookup_color (context, names[i], out_color))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_access_resolve_map_background (GtkStyleContext *context, GtkStateFlags state,
|
||||||
|
const char *const *named_fallbacks, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
GdkRGBA named;
|
||||||
|
|
||||||
|
theme_access_context_get_background_color (context, state, out_color);
|
||||||
|
if (out_color->alpha > 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Unstyled CSS nodes (e.g. the custom chat text widget) report a fully
|
||||||
|
* transparent background; fall back to the theme's named colors so the
|
||||||
|
* mapped palette still follows the active GTK3 theme. */
|
||||||
|
if (theme_access_lookup_named_color (context, named_fallbacks, &named))
|
||||||
|
*out_color = named;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map)
|
theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map)
|
||||||
{
|
{
|
||||||
|
static const char *const base_fallbacks[] = { "theme_base_color", "theme_bg_color", NULL };
|
||||||
|
static const char *const selected_bg_fallbacks[] = { "theme_selected_bg_color", NULL };
|
||||||
|
static const char *const selected_fg_fallbacks[] = { "theme_selected_fg_color", NULL };
|
||||||
GtkStyleContext *context;
|
GtkStyleContext *context;
|
||||||
|
const char *palette_class;
|
||||||
|
gboolean had_palette_class;
|
||||||
|
GdkRGBA named;
|
||||||
GdkRGBA accent;
|
GdkRGBA accent;
|
||||||
|
|
||||||
g_return_val_if_fail (out_map != NULL, FALSE);
|
g_return_val_if_fail (out_map != NULL, FALSE);
|
||||||
if (!theme_gtk3_is_active () || widget == NULL || !GTK_IS_WIDGET (widget))
|
if (widget == NULL || !GTK_IS_WIDGET (widget))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
context = gtk_widget_get_style_context (widget);
|
context = gtk_widget_get_style_context (widget);
|
||||||
if (context == NULL)
|
if (context == NULL)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
|
/* The application paints these widgets with fixed colors through the
|
||||||
|
* zoitechat-palette provider. Sampling must see the underlying GTK3
|
||||||
|
* theme, not the colors we applied earlier: otherwise every theme
|
||||||
|
* change reads back the previous palette and the user list, channel
|
||||||
|
* tree and input box keep their stale colors until a restart. */
|
||||||
|
palette_class = theme_css_palette_class_name ();
|
||||||
|
had_palette_class = gtk_style_context_has_class (context, palette_class);
|
||||||
|
if (had_palette_class)
|
||||||
|
gtk_style_context_remove_class (context, palette_class);
|
||||||
|
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_foreground);
|
||||||
theme_access_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &out_map->text_background);
|
theme_access_resolve_map_background (context, GTK_STATE_FLAG_NORMAL, base_fallbacks,
|
||||||
|
&out_map->text_background);
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_foreground);
|
||||||
theme_access_context_get_background_color (context, GTK_STATE_FLAG_SELECTED, &out_map->selection_background);
|
theme_access_resolve_map_background (context, GTK_STATE_FLAG_SELECTED, selected_bg_fallbacks,
|
||||||
|
&out_map->selection_background);
|
||||||
|
if (gdk_rgba_equal (&out_map->selection_foreground, &out_map->text_foreground) &&
|
||||||
|
theme_access_lookup_named_color (context, selected_fg_fallbacks, &named))
|
||||||
|
out_map->selection_foreground = named;
|
||||||
theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent);
|
theme_access_context_get_color (context, GTK_STATE_FLAG_LINK, &accent);
|
||||||
if (accent.alpha <= 0.0)
|
if (accent.alpha <= 0.0)
|
||||||
accent = out_map->selection_background;
|
accent = out_map->selection_background;
|
||||||
out_map->accent = accent;
|
out_map->accent = accent;
|
||||||
|
|
||||||
|
if (had_palette_class)
|
||||||
|
gtk_style_context_add_class (context, palette_class);
|
||||||
|
|
||||||
out_map->enabled = TRUE;
|
out_map->enabled = TRUE;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -148,11 +216,10 @@ gboolean
|
|||||||
theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba)
|
theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba)
|
||||||
{
|
{
|
||||||
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
|
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
|
||||||
gboolean has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
|
|
||||||
|
|
||||||
if (mirc_index >= THEME_XTEXT_MIRC_COLS)
|
if (mirc_index >= THEME_XTEXT_MIRC_COLS)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (!has_user_colors || mirc_index >= 32)
|
if (mirc_index >= 32)
|
||||||
{
|
{
|
||||||
out_rgba->red = theme_default_99_mirc_colors[mirc_index][0] / 255.0;
|
out_rgba->red = theme_default_99_mirc_colors[mirc_index][0] / 255.0;
|
||||||
out_rgba->green = theme_default_99_mirc_colors[mirc_index][1] / 255.0;
|
out_rgba->green = theme_default_99_mirc_colors[mirc_index][1] / 255.0;
|
||||||
@@ -173,11 +240,10 @@ gboolean
|
|||||||
theme_get_mirc_color_rgb16 (unsigned int mirc_index, guint16 *red, guint16 *green, guint16 *blue)
|
theme_get_mirc_color_rgb16 (unsigned int mirc_index, guint16 *red, guint16 *green, guint16 *blue)
|
||||||
{
|
{
|
||||||
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
|
ThemeSemanticToken token = (ThemeSemanticToken) (THEME_TOKEN_MIRC_0 + (int) mirc_index);
|
||||||
gboolean has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
|
|
||||||
|
|
||||||
if (mirc_index >= THEME_XTEXT_MIRC_COLS)
|
if (mirc_index >= THEME_XTEXT_MIRC_COLS)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
if (!has_user_colors || mirc_index >= 32)
|
if (mirc_index >= 32)
|
||||||
{
|
{
|
||||||
*red = (guint16) (theme_default_99_mirc_colors[mirc_index][0] * 257);
|
*red = (guint16) (theme_default_99_mirc_colors[mirc_index][0] * 257);
|
||||||
*green = (guint16) (theme_default_99_mirc_colors[mirc_index][1] * 257);
|
*green = (guint16) (theme_default_99_mirc_colors[mirc_index][1] * 257);
|
||||||
@@ -226,18 +292,37 @@ theme_get_xtext_colors (XTextColor *palette, size_t palette_len)
|
|||||||
void
|
void
|
||||||
theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len)
|
theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_t palette_len)
|
||||||
{
|
{
|
||||||
|
ThemeGtkPaletteMap gtk_map = { 0 };
|
||||||
ThemeWidgetStyleValues style_values;
|
ThemeWidgetStyleValues style_values;
|
||||||
gboolean has_user_colors;
|
|
||||||
GdkRGBA marker_color;
|
GdkRGBA marker_color;
|
||||||
|
gboolean have_marker = FALSE;
|
||||||
|
|
||||||
if (!palette)
|
if (!palette)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_get_widget_style_values_for_widget (widget, &style_values);
|
if (theme_access_get_gtk_palette_map (widget, >k_map))
|
||||||
theme_runtime_get_xtext_colors (palette, palette_len);
|
{
|
||||||
has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
|
theme_runtime_get_widget_style_values_mapped (>k_map, &style_values);
|
||||||
|
theme_runtime_get_xtext_colors_mapped (>k_map, palette, palette_len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
theme_runtime_get_widget_style_values (&style_values);
|
||||||
|
theme_runtime_get_xtext_colors (palette, palette_len);
|
||||||
|
}
|
||||||
|
if (palette_len > THEME_XTEXT_MARKER_LEGACY_INDEX)
|
||||||
|
{
|
||||||
|
/* The marker token lives at its legacy slot before the extended
|
||||||
|
* mIRC palette overwrites it; keep the (possibly theme-mapped)
|
||||||
|
* value for the dedicated marker index below. */
|
||||||
|
marker_color.red = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].red;
|
||||||
|
marker_color.green = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].green;
|
||||||
|
marker_color.blue = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].blue;
|
||||||
|
marker_color.alpha = palette[THEME_XTEXT_MARKER_LEGACY_INDEX].alpha;
|
||||||
|
have_marker = TRUE;
|
||||||
|
}
|
||||||
if (palette_len >= THEME_XTEXT_MIRC_COLS)
|
if (palette_len >= THEME_XTEXT_MIRC_COLS)
|
||||||
theme_access_apply_default_99_palette (palette, palette_len, !has_user_colors);
|
theme_access_apply_default_99_palette (palette, palette_len);
|
||||||
if (palette_len > THEME_XTEXT_MARK_FG_INDEX)
|
if (palette_len > THEME_XTEXT_MARK_FG_INDEX)
|
||||||
{
|
{
|
||||||
palette[THEME_XTEXT_MARK_FG_INDEX].red = style_values.selection_foreground.red;
|
palette[THEME_XTEXT_MARK_FG_INDEX].red = style_values.selection_foreground.red;
|
||||||
@@ -254,7 +339,7 @@ theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_
|
|||||||
}
|
}
|
||||||
if (palette_len > THEME_XTEXT_MARKER_INDEX)
|
if (palette_len > THEME_XTEXT_MARKER_INDEX)
|
||||||
{
|
{
|
||||||
if (!theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color))
|
if (!have_marker && !theme_runtime_get_color (THEME_TOKEN_MARKER, &marker_color))
|
||||||
marker_color = style_values.selection_background;
|
marker_color = style_values.selection_background;
|
||||||
palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red;
|
palette[THEME_XTEXT_MARKER_INDEX].red = marker_color.red;
|
||||||
palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green;
|
palette[THEME_XTEXT_MARKER_INDEX].green = marker_color.green;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "theme-css.h"
|
#include "theme-css.h"
|
||||||
#include "theme-runtime.h"
|
#include "theme-runtime.h"
|
||||||
#include "theme-gtk3.h"
|
#include "theme-gtk3.h"
|
||||||
|
#include "theme-policy.h"
|
||||||
#include "../maingui.h"
|
#include "../maingui.h"
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
@@ -55,10 +56,19 @@ theme_application_apply_toplevel_theme (gboolean dark)
|
|||||||
|
|
||||||
if (theme_gtk3_is_active ())
|
if (theme_gtk3_is_active ())
|
||||||
{
|
{
|
||||||
if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_DARK)
|
/* An explicitly selected in-app theme controls its own
|
||||||
prefer_dark = TRUE;
|
* variant; the OS light/dark preference must not leak into
|
||||||
else if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_LIGHT)
|
* the window chrome (menu bar, dialogs). The active variant
|
||||||
prefer_dark = FALSE;
|
* is always resolved to light or dark. */
|
||||||
|
prefer_dark = theme_gtk3_active_variant () == THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Following the system GTK3 theme: only request the dark
|
||||||
|
* variant when the dark-mode preference (or the system, in
|
||||||
|
* auto mode) asks for it, instead of clobbering the value
|
||||||
|
* that came from the user's GTK configuration. */
|
||||||
|
prefer_dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
|
if (settings && g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
|
||||||
@@ -88,9 +98,15 @@ theme_application_apply_toplevel_theme (gboolean dark)
|
|||||||
gboolean
|
gboolean
|
||||||
theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
theme_application_apply_mode (unsigned int mode, gboolean *palette_changed)
|
||||||
{
|
{
|
||||||
|
static gboolean runtime_loaded = FALSE;
|
||||||
gboolean dark;
|
gboolean dark;
|
||||||
|
|
||||||
theme_runtime_load ();
|
if (!runtime_loaded)
|
||||||
|
{
|
||||||
|
theme_runtime_load ();
|
||||||
|
runtime_loaded = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
dark = theme_runtime_apply_mode (mode, palette_changed);
|
dark = theme_runtime_apply_mode (mode, palette_changed);
|
||||||
theme_application_apply_toplevel_theme (dark);
|
theme_application_apply_toplevel_theme (dark);
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
static const char *theme_css_selector_input = "#zoitechat-inputbox";
|
static const char *theme_css_selector_input = "#zoitechat-inputbox";
|
||||||
static const char *theme_css_selector_input_text = "#zoitechat-inputbox text";
|
static const char *theme_css_selector_input_text = "#zoitechat-inputbox text";
|
||||||
static const char *theme_css_selector_palette_class = "zoitechat-palette";
|
static const char *theme_css_selector_palette_class = "zoitechat-palette";
|
||||||
static const char *theme_css_selector_dark_class = "zoitechat-dark";
|
|
||||||
static const char *theme_css_selector_light_class = "zoitechat-light";
|
|
||||||
static const char *theme_css_palette_provider_key = "zoitechat-palette-provider";
|
static const char *theme_css_palette_provider_key = "zoitechat-palette-provider";
|
||||||
static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER;
|
static const guint theme_css_provider_priority = GTK_STYLE_PROVIDER_PRIORITY_USER;
|
||||||
static const char *theme_css_palette_scope_selectors =
|
static const char *theme_css_palette_scope_selectors =
|
||||||
@@ -62,6 +60,12 @@ typedef struct
|
|||||||
static GtkCssProvider *theme_css_input_provider;
|
static GtkCssProvider *theme_css_input_provider;
|
||||||
static ThemeCssInputFingerprint theme_css_input_fp;
|
static ThemeCssInputFingerprint theme_css_input_fp;
|
||||||
|
|
||||||
|
const char *
|
||||||
|
theme_css_palette_class_name (void)
|
||||||
|
{
|
||||||
|
return theme_css_selector_palette_class;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_css_apply_app_provider (GtkStyleProvider *provider)
|
theme_css_apply_app_provider (GtkStyleProvider *provider)
|
||||||
{
|
{
|
||||||
@@ -362,61 +366,9 @@ theme_css_apply_palette_widget (GtkWidget *widget, const GdkRGBA *bg, const GdkR
|
|||||||
char *
|
char *
|
||||||
theme_css_build_toplevel_classes (void)
|
theme_css_build_toplevel_classes (void)
|
||||||
{
|
{
|
||||||
return g_strdup_printf (
|
/* Windows, dialogs and menus must render with the colors of the active
|
||||||
"window.%s, window.%s:backdrop, .%s {"
|
* GTK3 theme (system or in-app selected). zoitechat-dark and
|
||||||
"background-color: #202020;"
|
* zoitechat-light are marker classes only; painting fixed colors here
|
||||||
"color: #f0f0f0;"
|
* overrides every theme (issue #334). */
|
||||||
"border-color: #202020;"
|
return g_strdup ("");
|
||||||
"}"
|
|
||||||
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
|
|
||||||
"background-color: @theme_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_fg_color;"
|
|
||||||
"border-color: @theme_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s menuitem:hover, window.%s menuitem:selected {"
|
|
||||||
"background-color: @theme_selected_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_selected_fg_color;"
|
|
||||||
"border-color: @theme_selected_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s, window.%s:backdrop, .%s {"
|
|
||||||
"background-color: #f6f6f6;"
|
|
||||||
"color: #101010;"
|
|
||||||
"border-color: #f6f6f6;"
|
|
||||||
"}"
|
|
||||||
"window.%s menubar, window.%s menubar:backdrop, window.%s menubar box, window.%s menubar box:backdrop, window.%s menuitem, window.%s menuitem:backdrop {"
|
|
||||||
"background-color: @theme_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_fg_color;"
|
|
||||||
"border-color: @theme_bg_color;"
|
|
||||||
"}"
|
|
||||||
"window.%s menuitem:hover, window.%s menuitem:selected {"
|
|
||||||
"background-color: @theme_selected_bg_color;"
|
|
||||||
"background-image: none;"
|
|
||||||
"color: @theme_selected_fg_color;"
|
|
||||||
"border-color: @theme_selected_bg_color;"
|
|
||||||
"}",
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_dark_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class,
|
|
||||||
theme_css_selector_light_class);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
* theme_css_apply_widget_provider:
|
* theme_css_apply_widget_provider:
|
||||||
* Use for widget-local CSS providers attached to a specific widget context.
|
* Use for widget-local CSS providers attached to a specific widget context.
|
||||||
*/
|
*/
|
||||||
|
const char *theme_css_palette_class_name (void);
|
||||||
void theme_css_apply_app_provider (GtkStyleProvider *provider);
|
void theme_css_apply_app_provider (GtkStyleProvider *provider);
|
||||||
void theme_css_remove_app_provider (GtkStyleProvider *provider);
|
void theme_css_remove_app_provider (GtkStyleProvider *provider);
|
||||||
void theme_css_apply_widget_provider (GtkWidget *widget, GtkStyleProvider *provider);
|
void theme_css_apply_widget_provider (GtkWidget *widget, GtkStyleProvider *provider);
|
||||||
|
|||||||
@@ -33,11 +33,60 @@
|
|||||||
|
|
||||||
static GPtrArray *theme_gtk3_providers_base;
|
static GPtrArray *theme_gtk3_providers_base;
|
||||||
static GPtrArray *theme_gtk3_providers_variant;
|
static GPtrArray *theme_gtk3_providers_variant;
|
||||||
|
static GtkCssProvider *theme_gtk3_chrome_provider;
|
||||||
|
static GHashTable *theme_gtk3_theme_colors;
|
||||||
static GHashTable *theme_gtk3_provider_cache;
|
static GHashTable *theme_gtk3_provider_cache;
|
||||||
static gboolean theme_gtk3_active;
|
static gboolean theme_gtk3_active;
|
||||||
static char *theme_gtk3_current_id;
|
static char *theme_gtk3_current_id;
|
||||||
static ThemeGtk3Variant theme_gtk3_current_variant;
|
static ThemeGtk3Variant theme_gtk3_current_variant;
|
||||||
|
|
||||||
|
/* Menu bar and menus fall back to the base gtk-theme-name theme (the OS
|
||||||
|
* theme) for any property the selected in-app theme's CSS does not define,
|
||||||
|
* which mixes OS dark/light colors into the chrome (issue: dark menu bar on
|
||||||
|
* a light in-app theme when the OS is in dark mode). While an in-app theme
|
||||||
|
* is active, backfill those widgets with the theme's own named colors. The
|
||||||
|
* provider sits just below the theme providers, so a theme that styles its
|
||||||
|
* menus keeps full control. */
|
||||||
|
#define THEME_GTK3_CHROME_PRIORITY (GTK_STYLE_PROVIDER_PRIORITY_USER - 1)
|
||||||
|
/* Concrete color values are formatted in: desktop integration CSS (e.g.
|
||||||
|
* KDE's color-scheme sync in ~/.config/gtk-3.0/gtk.css) redefines the
|
||||||
|
* standard named colors with the OS palette at USER priority, so a
|
||||||
|
* "@theme_bg_color" reference here would resolve to the OS dark/light
|
||||||
|
* colors instead of the selected theme's. */
|
||||||
|
static const char theme_gtk3_chrome_css_template[] =
|
||||||
|
".background, .background:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"headerbar, headerbar:backdrop, .titlebar, .titlebar:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}";
|
||||||
|
|
||||||
|
/* The menu bar body and the menu items are separate CSS surfaces; desktop
|
||||||
|
* integration CSS or a theme can style one but not the other, splitting
|
||||||
|
* the menu bar row into two colors. These rules sit above the theme and
|
||||||
|
* the desktop CSS so body and items always render as one surface. */
|
||||||
|
#define THEME_GTK3_MENU_UNIFORM_PRIORITY (GTK_STYLE_PROVIDER_PRIORITY_USER + 40)
|
||||||
|
static const char theme_gtk3_menu_uniform_template[] =
|
||||||
|
"menubar, menubar:backdrop, menubar > menuitem, menubar > menuitem:backdrop,"
|
||||||
|
"menu, menu:backdrop, .menu, .menu:backdrop, menu > menuitem, menu > menuitem:backdrop {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"menubar > menuitem:hover, menubar > menuitem:selected,"
|
||||||
|
"menu > menuitem:hover, menu > menuitem:selected,"
|
||||||
|
".menu menuitem:hover, .menu menuitem:selected {"
|
||||||
|
" background-color: %s;"
|
||||||
|
" background-image: none;"
|
||||||
|
" color: %s;"
|
||||||
|
"}"
|
||||||
|
"menubar > menuitem:disabled, menu > menuitem:disabled {"
|
||||||
|
" color: alpha(%s, 0.5);"
|
||||||
|
"}";
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
GHashTable *defaults;
|
GHashTable *defaults;
|
||||||
@@ -49,6 +98,7 @@ typedef struct
|
|||||||
static ThemeGtk3SettingsState theme_gtk3_settings_state;
|
static ThemeGtk3SettingsState theme_gtk3_settings_state;
|
||||||
|
|
||||||
static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value);
|
static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value);
|
||||||
|
static void theme_gtk3_menu_uniform_apply (const char *bg, const char *fg, const char *sel_bg, const char *sel_fg);
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
theme_gtk3_theme_name_is_dark (const char *name)
|
theme_gtk3_theme_name_is_dark (const char *name)
|
||||||
@@ -135,8 +185,12 @@ settings_defaults_table (void)
|
|||||||
static void
|
static void
|
||||||
settings_rescan_icon_theme (void)
|
settings_rescan_icon_theme (void)
|
||||||
{
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
GtkIconTheme *icon_theme;
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
icon_theme = gtk_icon_theme_get_default ();
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme)
|
if (!icon_theme)
|
||||||
return;
|
return;
|
||||||
@@ -156,8 +210,13 @@ theme_gtk3_reset_widgets (void)
|
|||||||
static void
|
static void
|
||||||
settings_capture_icon_search_path (void)
|
settings_capture_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -168,8 +227,13 @@ settings_capture_icon_search_path (void)
|
|||||||
static void
|
static void
|
||||||
settings_append_icon_search_path (const char *path)
|
settings_append_icon_search_path (const char *path)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -199,8 +263,13 @@ settings_apply_icon_paths (const char *theme_root)
|
|||||||
static void
|
static void
|
||||||
settings_restore_icon_search_path (void)
|
settings_restore_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -380,6 +449,21 @@ settings_theme_link_search_path (const char *theme_root, const char *theme_name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
link_path = g_build_filename (themes_root, theme_name, NULL);
|
link_path = g_build_filename (themes_root, theme_name, NULL);
|
||||||
|
|
||||||
|
/* A leftover symlink from a removed or relocated theme would satisfy
|
||||||
|
* the EXISTS check while pointing at the wrong (or no) theme, leaving
|
||||||
|
* the base gtk-theme-name on the OS theme. Replace it. */
|
||||||
|
{
|
||||||
|
char *link_target = g_file_read_link (link_path, NULL);
|
||||||
|
if (link_target)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (link_target, theme_root) != 0 ||
|
||||||
|
!g_file_test (link_path, G_FILE_TEST_IS_DIR))
|
||||||
|
g_unlink (link_path);
|
||||||
|
g_free (link_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!g_file_test (link_path, G_FILE_TEST_EXISTS))
|
if (!g_file_test (link_path, G_FILE_TEST_EXISTS))
|
||||||
{
|
{
|
||||||
GFile *link_file = g_file_new_for_path (link_path);
|
GFile *link_file = g_file_new_for_path (link_path);
|
||||||
@@ -394,6 +478,14 @@ settings_theme_link_search_path (const char *theme_root, const char *theme_name)
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
settings_default_seat_available (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
settings_apply_theme_name (const char *theme_root)
|
settings_apply_theme_name (const char *theme_root)
|
||||||
{
|
{
|
||||||
@@ -404,7 +496,7 @@ settings_apply_theme_name (const char *theme_root)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
settings = gtk_settings_get_default ();
|
settings = gtk_settings_get_default ();
|
||||||
if (!settings)
|
if (!settings || !settings_default_seat_available ())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_name = g_path_get_basename (theme_root);
|
theme_name = g_path_get_basename (theme_root);
|
||||||
@@ -730,12 +822,176 @@ settings_apply_from_file (const char *theme_root, const char *css_dir)
|
|||||||
g_ptr_array_unref (settings_paths);
|
g_ptr_array_unref (settings_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
theme_gtk3_providers_define_color (const char *name)
|
||||||
|
{
|
||||||
|
char *needle = g_strdup_printf ("@define-color %s ", name);
|
||||||
|
gboolean found = FALSE;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
for (i = 0; !found && theme_gtk3_providers_base && i < theme_gtk3_providers_base->len; i++)
|
||||||
|
{
|
||||||
|
char *dump = gtk_css_provider_to_string (g_ptr_array_index (theme_gtk3_providers_base, i));
|
||||||
|
found = dump && strstr (dump, needle) != NULL;
|
||||||
|
g_free (dump);
|
||||||
|
}
|
||||||
|
for (i = 0; !found && theme_gtk3_providers_variant && i < theme_gtk3_providers_variant->len; i++)
|
||||||
|
{
|
||||||
|
char *dump = gtk_css_provider_to_string (g_ptr_array_index (theme_gtk3_providers_variant, i));
|
||||||
|
found = dump && strstr (dump, needle) != NULL;
|
||||||
|
g_free (dump);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (needle);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolve a named color from the selected theme itself. The lookup
|
||||||
|
* context layers the theme's providers over the screen cascade, so the
|
||||||
|
* theme's own @define-color wins; when the theme does not define the
|
||||||
|
* name at all, fall back to a neutral palette matching the theme's
|
||||||
|
* variant instead of whatever the desktop injected into the cascade. */
|
||||||
|
static char *
|
||||||
|
theme_gtk3_chrome_resolve_color (GtkStyleContext *lookup, const char *name, const char *fallback)
|
||||||
|
{
|
||||||
|
GdkRGBA color;
|
||||||
|
|
||||||
|
if (!theme_gtk3_providers_define_color (name) ||
|
||||||
|
!gtk_style_context_lookup_color (lookup, name, &color))
|
||||||
|
g_assert (gdk_rgba_parse (&color, fallback));
|
||||||
|
|
||||||
|
if (!theme_gtk3_theme_colors)
|
||||||
|
theme_gtk3_theme_colors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||||
|
{
|
||||||
|
GdkRGBA *stored = g_new (GdkRGBA, 1);
|
||||||
|
*stored = color;
|
||||||
|
g_hash_table_insert (theme_gtk3_theme_colors, g_strdup (name), stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gdk_rgba_to_string (&color);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color)
|
||||||
|
{
|
||||||
|
GdkRGBA *stored;
|
||||||
|
|
||||||
|
if (!theme_gtk3_active || !theme_gtk3_theme_colors || !name || !out_color)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
stored = g_hash_table_lookup (theme_gtk3_theme_colors, name);
|
||||||
|
if (!stored)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
*out_color = *stored;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_chrome_provider_apply (gboolean prefer_dark)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkStyleContext *lookup;
|
||||||
|
char *bg;
|
||||||
|
char *fg;
|
||||||
|
char *sel_bg;
|
||||||
|
char *sel_fg;
|
||||||
|
char *css;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lookup = gtk_style_context_new ();
|
||||||
|
for (i = 0; theme_gtk3_providers_base && i < theme_gtk3_providers_base->len; i++)
|
||||||
|
gtk_style_context_add_provider (lookup,
|
||||||
|
GTK_STYLE_PROVIDER (g_ptr_array_index (theme_gtk3_providers_base, i)),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
for (i = 0; theme_gtk3_providers_variant && i < theme_gtk3_providers_variant->len; i++)
|
||||||
|
gtk_style_context_add_provider (lookup,
|
||||||
|
GTK_STYLE_PROVIDER (g_ptr_array_index (theme_gtk3_providers_variant, i)),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||||
|
|
||||||
|
if (theme_gtk3_theme_colors)
|
||||||
|
g_hash_table_remove_all (theme_gtk3_theme_colors);
|
||||||
|
|
||||||
|
bg = theme_gtk3_chrome_resolve_color (lookup, "theme_bg_color",
|
||||||
|
prefer_dark ? "#242424" : "#f6f5f4");
|
||||||
|
fg = theme_gtk3_chrome_resolve_color (lookup, "theme_fg_color",
|
||||||
|
prefer_dark ? "#eeeeee" : "#2e3436");
|
||||||
|
sel_bg = theme_gtk3_chrome_resolve_color (lookup, "theme_selected_bg_color", "#3584e4");
|
||||||
|
sel_fg = theme_gtk3_chrome_resolve_color (lookup, "theme_selected_fg_color", "#ffffff");
|
||||||
|
/* Not used by the chrome CSS itself, but resolved and stored for the
|
||||||
|
* mapped-palette sampling fallback (chat area and friends). */
|
||||||
|
g_free (theme_gtk3_chrome_resolve_color (lookup, "theme_base_color",
|
||||||
|
prefer_dark ? "#1e1e1e" : "#ffffff"));
|
||||||
|
g_object_unref (lookup);
|
||||||
|
|
||||||
|
css = g_strdup_printf (theme_gtk3_chrome_css_template,
|
||||||
|
bg, fg,
|
||||||
|
bg, fg);
|
||||||
|
|
||||||
|
if (!theme_gtk3_chrome_provider)
|
||||||
|
theme_gtk3_chrome_provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme_gtk3_chrome_provider, css, -1, NULL);
|
||||||
|
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_chrome_provider),
|
||||||
|
THEME_GTK3_CHROME_PRIORITY);
|
||||||
|
|
||||||
|
theme_gtk3_menu_uniform_apply (bg, fg, sel_bg, sel_fg);
|
||||||
|
|
||||||
|
g_free (css);
|
||||||
|
g_free (sel_fg);
|
||||||
|
g_free (sel_bg);
|
||||||
|
g_free (fg);
|
||||||
|
g_free (bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkCssProvider *theme_gtk3_menu_uniform_provider;
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_menu_uniform_apply (const char *bg, const char *fg, const char *sel_bg, const char *sel_fg)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
char *css;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
css = g_strdup_printf (theme_gtk3_menu_uniform_template, bg, fg, sel_bg, sel_fg, fg);
|
||||||
|
if (!theme_gtk3_menu_uniform_provider)
|
||||||
|
theme_gtk3_menu_uniform_provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (theme_gtk3_menu_uniform_provider, css, -1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_menu_uniform_provider),
|
||||||
|
THEME_GTK3_MENU_UNIFORM_PRIORITY);
|
||||||
|
g_free (css);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_gtk3_chrome_provider_remove (void)
|
||||||
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (theme_gtk3_chrome_provider)
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_chrome_provider));
|
||||||
|
if (theme_gtk3_menu_uniform_provider)
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (theme_gtk3_menu_uniform_provider));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_gtk3_remove_provider (void)
|
theme_gtk3_remove_provider (void)
|
||||||
{
|
{
|
||||||
GdkScreen *screen = gdk_screen_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
guint i;
|
guint i;
|
||||||
|
|
||||||
|
theme_gtk3_chrome_provider_remove ();
|
||||||
if (screen && theme_gtk3_providers_variant)
|
if (screen && theme_gtk3_providers_variant)
|
||||||
{
|
{
|
||||||
for (i = 0; i < theme_gtk3_providers_variant->len; i++)
|
for (i = 0; i < theme_gtk3_providers_variant->len; i++)
|
||||||
@@ -839,6 +1095,7 @@ load_css_with_variant (ZoitechatGtk3Theme *theme, ThemeGtk3Variant variant, GErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_ptr_array_unref (chain);
|
g_ptr_array_unref (chain);
|
||||||
|
theme_gtk3_chrome_provider_apply (prefer_dark);
|
||||||
settings_apply_for_variant (variant);
|
settings_apply_for_variant (variant);
|
||||||
theme_gtk3_reset_widgets ();
|
theme_gtk3_reset_widgets ();
|
||||||
theme_gtk3_active = TRUE;
|
theme_gtk3_active = TRUE;
|
||||||
@@ -849,24 +1106,35 @@ static gboolean
|
|||||||
theme_gtk3_apply_internal (const char *theme_id, ThemeGtk3Variant variant, gboolean force_reload, GError **error)
|
theme_gtk3_apply_internal (const char *theme_id, ThemeGtk3Variant variant, gboolean force_reload, GError **error)
|
||||||
{
|
{
|
||||||
ZoitechatGtk3Theme *theme;
|
ZoitechatGtk3Theme *theme;
|
||||||
char *previous_id = g_strdup (theme_gtk3_current_id);
|
char *previous_id;
|
||||||
ThemeGtk3Variant previous_variant = theme_gtk3_current_variant;
|
ThemeGtk3Variant previous_variant;
|
||||||
gboolean had_previous = theme_gtk3_active && previous_id && previous_id[0];
|
gboolean had_previous;
|
||||||
gboolean ok;
|
gboolean ok;
|
||||||
|
|
||||||
|
theme = zoitechat_gtk3_theme_find_by_id (theme_id);
|
||||||
|
if (!theme)
|
||||||
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme not found."), FALSE;
|
||||||
|
|
||||||
|
/* An explicitly selected in-app theme pins the rendering to that
|
||||||
|
* theme: it must not flip with the OS light/dark preference. Legacy
|
||||||
|
* configs still carry FOLLOW_SYSTEM (the pre-inference default), so
|
||||||
|
* resolve it to the variant the theme itself provides. */
|
||||||
|
if (variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM)
|
||||||
|
variant = theme_gtk3_infer_variant (theme);
|
||||||
|
|
||||||
if (!force_reload &&
|
if (!force_reload &&
|
||||||
theme_gtk3_active &&
|
theme_gtk3_active &&
|
||||||
g_strcmp0 (theme_gtk3_current_id, theme_id) == 0 &&
|
g_strcmp0 (theme_gtk3_current_id, theme_id) == 0 &&
|
||||||
theme_gtk3_current_variant == variant)
|
theme_gtk3_current_variant == variant)
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
theme = zoitechat_gtk3_theme_find_by_id (theme_id);
|
|
||||||
if (!theme)
|
|
||||||
{
|
{
|
||||||
g_free (previous_id);
|
zoitechat_gtk3_theme_free (theme);
|
||||||
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme not found."), FALSE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previous_id = g_strdup (theme_gtk3_current_id);
|
||||||
|
previous_variant = theme_gtk3_current_variant;
|
||||||
|
had_previous = theme_gtk3_active && previous_id && previous_id[0];
|
||||||
|
|
||||||
theme_gtk3_remove_provider ();
|
theme_gtk3_remove_provider ();
|
||||||
if (force_reload)
|
if (force_reload)
|
||||||
theme_gtk3_invalidate_provider_cache ();
|
theme_gtk3_invalidate_provider_cache ();
|
||||||
@@ -934,6 +1202,12 @@ void
|
|||||||
theme_gtk3_disable (void)
|
theme_gtk3_disable (void)
|
||||||
{
|
{
|
||||||
theme_gtk3_remove_provider ();
|
theme_gtk3_remove_provider ();
|
||||||
|
g_clear_object (&theme_gtk3_chrome_provider);
|
||||||
|
g_clear_pointer (&theme_gtk3_theme_colors, g_hash_table_destroy);
|
||||||
|
/* Following the system theme: keep the menu bar body and the menu
|
||||||
|
* items on one surface too, using the system's own colors. */
|
||||||
|
theme_gtk3_menu_uniform_apply ("@theme_bg_color", "@theme_fg_color",
|
||||||
|
"@theme_selected_bg_color", "@theme_selected_fg_color");
|
||||||
g_clear_pointer (&theme_gtk3_current_id, g_free);
|
g_clear_pointer (&theme_gtk3_current_id, g_free);
|
||||||
theme_gtk3_invalidate_provider_cache ();
|
theme_gtk3_invalidate_provider_cache ();
|
||||||
g_clear_pointer (&theme_gtk3_provider_cache, g_hash_table_destroy);
|
g_clear_pointer (&theme_gtk3_provider_cache, g_hash_table_destroy);
|
||||||
@@ -963,3 +1237,13 @@ theme_gtk3_is_active (void)
|
|||||||
{
|
{
|
||||||
return theme_gtk3_active;
|
return theme_gtk3_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThemeGtk3Variant
|
||||||
|
theme_gtk3_active_variant (void)
|
||||||
|
{
|
||||||
|
/* Always resolved to PREFER_LIGHT or PREFER_DARK while a theme is
|
||||||
|
* active; FOLLOW_SYSTEM only when no in-app theme is applied. */
|
||||||
|
if (!theme_gtk3_active)
|
||||||
|
return THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
|
||||||
|
return theme_gtk3_current_variant;
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#define ZOITECHAT_THEME_GTK3_H
|
#define ZOITECHAT_THEME_GTK3_H
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <gdk/gdk.h>
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@@ -38,5 +39,7 @@ ThemeGtk3Variant theme_gtk3_variant_for_theme (const char *theme_id);
|
|||||||
void theme_gtk3_invalidate_provider_cache (void);
|
void theme_gtk3_invalidate_provider_cache (void);
|
||||||
void theme_gtk3_disable (void);
|
void theme_gtk3_disable (void);
|
||||||
gboolean theme_gtk3_is_active (void);
|
gboolean theme_gtk3_is_active (void);
|
||||||
|
ThemeGtk3Variant theme_gtk3_active_variant (void);
|
||||||
|
gboolean theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -58,10 +58,52 @@ typedef struct
|
|||||||
gboolean resolved_dark_preference;
|
gboolean resolved_dark_preference;
|
||||||
char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme];
|
char gtk3_theme_id[sizeof prefs.hex_gui_gtk3_theme];
|
||||||
int gtk3_variant;
|
int gtk3_variant;
|
||||||
|
char *gtk_theme_name;
|
||||||
} ThemeManagerAutoRefreshCache;
|
} ThemeManagerAutoRefreshCache;
|
||||||
|
|
||||||
static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache;
|
static ThemeManagerAutoRefreshCache theme_manager_auto_refresh_cache;
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_reset (void)
|
||||||
|
{
|
||||||
|
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
|
||||||
|
memset (&theme_manager_auto_refresh_cache, 0, sizeof (theme_manager_auto_refresh_cache));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_store (gboolean resolved_dark_preference, char *gtk_theme_name)
|
||||||
|
{
|
||||||
|
theme_manager_auto_refresh_cache.initialized = TRUE;
|
||||||
|
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
|
||||||
|
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
|
||||||
|
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
|
||||||
|
prefs.hex_gui_gtk3_theme,
|
||||||
|
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
|
||||||
|
g_free (theme_manager_auto_refresh_cache.gtk_theme_name);
|
||||||
|
theme_manager_auto_refresh_cache.gtk_theme_name = gtk_theme_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
theme_manager_dup_gtk_theme_name (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings = gtk_settings_get_default ();
|
||||||
|
char *gtk_theme_name = NULL;
|
||||||
|
|
||||||
|
if (settings)
|
||||||
|
g_object_get (settings, "gtk-theme-name", >k_theme_name, NULL);
|
||||||
|
return gtk_theme_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record the state the application started up with so that the first queued
|
||||||
|
* auto refresh only reacts to real changes; startup already applied the
|
||||||
|
* current theme, and consumers such as the tray are not ready yet. */
|
||||||
|
static void
|
||||||
|
theme_manager_auto_refresh_cache_prime (void)
|
||||||
|
{
|
||||||
|
theme_manager_auto_refresh_cache_store (theme_policy_system_prefers_dark (),
|
||||||
|
theme_manager_dup_gtk_theme_name ());
|
||||||
|
}
|
||||||
|
|
||||||
static void theme_manager_apply_platform_window_theme (GtkWidget *window);
|
static void theme_manager_apply_platform_window_theme (GtkWidget *window);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -145,7 +187,9 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
|
|||||||
gboolean color_change = FALSE;
|
gboolean color_change = FALSE;
|
||||||
gboolean should_refresh_gtk3;
|
gboolean should_refresh_gtk3;
|
||||||
gboolean gtk3_refresh;
|
gboolean gtk3_refresh;
|
||||||
|
gboolean follows_system_theme;
|
||||||
gboolean resolved_dark_preference;
|
gboolean resolved_dark_preference;
|
||||||
|
char *gtk_theme_name = NULL;
|
||||||
static gboolean in_handler = FALSE;
|
static gboolean in_handler = FALSE;
|
||||||
|
|
||||||
(void) settings;
|
(void) settings;
|
||||||
@@ -154,39 +198,47 @@ theme_manager_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec,
|
|||||||
|
|
||||||
resolved_dark_preference = theme_policy_system_prefers_dark ();
|
resolved_dark_preference = theme_policy_system_prefers_dark ();
|
||||||
gtk3_refresh = theme_manager_should_refresh_gtk3 ();
|
gtk3_refresh = theme_manager_should_refresh_gtk3 ();
|
||||||
|
follows_system_theme = prefs.hex_gui_gtk3_theme[0] == '\0';
|
||||||
should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO;
|
should_refresh_gtk3 = gtk3_refresh || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO;
|
||||||
|
|
||||||
|
gtk_theme_name = theme_manager_dup_gtk_theme_name ();
|
||||||
|
|
||||||
if (theme_manager_auto_refresh_cache.initialized &&
|
if (theme_manager_auto_refresh_cache.initialized &&
|
||||||
theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference &&
|
theme_manager_auto_refresh_cache.resolved_dark_preference == resolved_dark_preference &&
|
||||||
theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant &&
|
theme_manager_auto_refresh_cache.gtk3_variant == prefs.hex_gui_gtk3_variant &&
|
||||||
g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0)
|
g_strcmp0 (theme_manager_auto_refresh_cache.gtk3_theme_id, prefs.hex_gui_gtk3_theme) == 0 &&
|
||||||
|
g_strcmp0 (theme_manager_auto_refresh_cache.gtk_theme_name, gtk_theme_name) == 0)
|
||||||
|
{
|
||||||
|
g_free (gtk_theme_name);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
theme_manager_auto_refresh_cache.initialized = TRUE;
|
theme_manager_auto_refresh_cache_store (resolved_dark_preference, gtk_theme_name);
|
||||||
theme_manager_auto_refresh_cache.resolved_dark_preference = resolved_dark_preference;
|
|
||||||
theme_manager_auto_refresh_cache.gtk3_variant = prefs.hex_gui_gtk3_variant;
|
|
||||||
g_strlcpy (theme_manager_auto_refresh_cache.gtk3_theme_id,
|
|
||||||
prefs.hex_gui_gtk3_theme,
|
|
||||||
sizeof (theme_manager_auto_refresh_cache.gtk3_theme_id));
|
|
||||||
|
|
||||||
if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh)
|
if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO && !gtk3_refresh && !follows_system_theme)
|
||||||
return;
|
return;
|
||||||
if (in_handler)
|
if (in_handler)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
in_handler = TRUE;
|
in_handler = TRUE;
|
||||||
|
|
||||||
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO)
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
|
||||||
{
|
{
|
||||||
fe_set_auto_dark_mode_state (resolved_dark_preference);
|
|
||||||
theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change);
|
theme_manager_commit_preferences (prefs.hex_gui_dark_mode, &color_change);
|
||||||
if (color_change)
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO)
|
||||||
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE);
|
fe_set_auto_dark_mode_state (resolved_dark_preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_refresh_gtk3)
|
if (should_refresh_gtk3)
|
||||||
theme_gtk3_apply_current (NULL);
|
theme_gtk3_apply_current (NULL);
|
||||||
|
|
||||||
|
if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO || follows_system_theme)
|
||||||
|
{
|
||||||
|
/* The system theme feeds the mapped palette (chat area, user
|
||||||
|
* list, channel tree), so consumers must re-resolve colors. */
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_PALETTE | THEME_CHANGED_REASON_WIDGET_STYLE | THEME_CHANGED_REASON_USERLIST | THEME_CHANGED_REASON_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
in_handler = FALSE;
|
in_handler = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,18 +256,28 @@ theme_manager_run_auto_refresh (gpointer data)
|
|||||||
static void
|
static void
|
||||||
theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data)
|
theme_manager_queue_auto_refresh (GtkSettings *settings, GParamSpec *pspec, gpointer data)
|
||||||
{
|
{
|
||||||
|
guint source_id;
|
||||||
|
|
||||||
(void) settings;
|
(void) settings;
|
||||||
(void) pspec;
|
(void) pspec;
|
||||||
|
|
||||||
if (theme_manager_auto_refresh_source != 0)
|
if (theme_manager_auto_refresh_source != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_manager_auto_refresh_source = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data);
|
theme_manager_auto_refresh_source = 1;
|
||||||
|
source_id = theme_manager_idle_add_func (theme_manager_run_auto_refresh, data);
|
||||||
|
/* An injected idle-add may run the refresh synchronously and already
|
||||||
|
* have cleared the pending marker; only keep the id while pending. */
|
||||||
|
if (theme_manager_auto_refresh_source != 0)
|
||||||
|
theme_manager_auto_refresh_source = source_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_manager_init (void)
|
theme_manager_init (void)
|
||||||
{
|
{
|
||||||
|
static gboolean settings_signals_connected = FALSE;
|
||||||
|
GtkSettings *settings;
|
||||||
|
|
||||||
if (!theme_manager_listeners)
|
if (!theme_manager_listeners)
|
||||||
theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
theme_manager_listeners = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
||||||
theme_listener_free);
|
theme_listener_free);
|
||||||
@@ -223,10 +285,25 @@ theme_manager_init (void)
|
|||||||
if (!theme_manager_setup_listener_id)
|
if (!theme_manager_setup_listener_id)
|
||||||
theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL);
|
theme_manager_setup_listener_id = theme_listener_register ("setup.apply", theme_manager_setup_apply_listener, NULL);
|
||||||
|
|
||||||
fe_set_auto_dark_mode_state (FALSE);
|
theme_policy_init ();
|
||||||
|
theme_policy_set_appearance_changed_callback (theme_manager_refresh_auto_mode);
|
||||||
|
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (settings && !settings_signals_connected)
|
||||||
|
{
|
||||||
|
g_signal_connect (settings, "notify::gtk-theme-name",
|
||||||
|
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
|
||||||
|
g_signal_connect (settings, "notify::gtk-application-prefer-dark-theme",
|
||||||
|
G_CALLBACK (theme_manager_queue_auto_refresh), NULL);
|
||||||
|
settings_signals_connected = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_set_auto_dark_mode_state (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO ?
|
||||||
|
theme_policy_system_prefers_dark () : FALSE);
|
||||||
theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL);
|
theme_application_apply_mode (prefs.hex_gui_dark_mode, NULL);
|
||||||
theme_gtk3_init ();
|
theme_gtk3_init ();
|
||||||
zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied);
|
zoitechat_set_theme_post_apply_callback (theme_manager_handle_theme_applied);
|
||||||
|
theme_manager_auto_refresh_cache_prime ();
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
@@ -513,13 +590,9 @@ theme_manager_apply_platform_window_theme (GtkWidget *window)
|
|||||||
|
|
||||||
context = gtk_widget_get_style_context (window);
|
context = gtk_widget_get_style_context (window);
|
||||||
if (theme_gtk3_is_active ())
|
if (theme_gtk3_is_active ())
|
||||||
{
|
dark = theme_gtk3_active_variant () == THEME_GTK3_VARIANT_PREFER_DARK;
|
||||||
dark = prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_PREFER_DARK;
|
|
||||||
if (prefs.hex_gui_gtk3_variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM)
|
|
||||||
dark = theme_policy_system_prefers_dark ();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
dark = theme_runtime_is_dark_active ();
|
dark = theme_policy_is_dark_mode_active (prefs.hex_gui_dark_mode);
|
||||||
if (context)
|
if (context)
|
||||||
{
|
{
|
||||||
gtk_style_context_remove_class (context, "zoitechat-dark");
|
gtk_style_context_remove_class (context, "zoitechat-dark");
|
||||||
@@ -712,4 +785,5 @@ theme_manager_set_idle_add_func (ThemeManagerIdleAddFunc idle_add_func)
|
|||||||
{
|
{
|
||||||
theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add;
|
theme_manager_idle_add_func = idle_add_func ? idle_add_func : g_idle_add;
|
||||||
theme_manager_auto_refresh_source = 0;
|
theme_manager_auto_refresh_source = 0;
|
||||||
|
theme_manager_auto_refresh_cache_reset ();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,379 @@
|
|||||||
|
|
||||||
#include "theme-policy.h"
|
#include "theme-policy.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "theme-gtk3.h"
|
||||||
#include "../fe-gtk.h"
|
#include "../fe-gtk.h"
|
||||||
#include "../../common/zoitechat.h"
|
#include "../../common/zoitechat.h"
|
||||||
#include "../../common/zoitechatc.h"
|
#include "../../common/zoitechatc.h"
|
||||||
|
|
||||||
|
/* org.freedesktop.appearance color-scheme values (XDG desktop portal) */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE = 0,
|
||||||
|
THEME_POLICY_COLOR_SCHEME_PREFER_DARK = 1,
|
||||||
|
THEME_POLICY_COLOR_SCHEME_PREFER_LIGHT = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
static ThemePolicyAppearanceChangedFunc policy_appearance_changed_cb;
|
||||||
|
|
||||||
|
/* GtkSettings state captured before ZoiteChat writes to it, so that values
|
||||||
|
* coming from the user's own GTK configuration are not mistaken for values
|
||||||
|
* this application set. */
|
||||||
|
static gboolean policy_initial_gtk_state_captured;
|
||||||
|
static gboolean policy_initial_prefer_dark;
|
||||||
|
static char *policy_initial_theme_name;
|
||||||
|
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
static gboolean policy_portal_watch_started;
|
||||||
|
static gboolean policy_portal_value_valid;
|
||||||
|
static guint32 policy_portal_color_scheme;
|
||||||
|
|
||||||
|
static gboolean policy_gsettings_checked;
|
||||||
|
static GSettings *policy_gsettings_interface;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_capture_initial_gtk_state (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings;
|
||||||
|
|
||||||
|
if (policy_initial_gtk_state_captured)
|
||||||
|
return;
|
||||||
|
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (!settings)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_object_get (settings,
|
||||||
|
"gtk-application-prefer-dark-theme", &policy_initial_prefer_dark,
|
||||||
|
"gtk-theme-name", &policy_initial_theme_name,
|
||||||
|
NULL);
|
||||||
|
policy_initial_gtk_state_captured = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_theme_name_is_dark (const char *name)
|
||||||
|
{
|
||||||
|
char *lower;
|
||||||
|
gboolean dark;
|
||||||
|
|
||||||
|
if (!name || !name[0])
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
lower = g_ascii_strdown (name, -1);
|
||||||
|
dark = strstr (lower, "dark") != NULL || strstr (lower, "noir") != NULL ||
|
||||||
|
strstr (lower, "black") != NULL;
|
||||||
|
g_free (lower);
|
||||||
|
return dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_notify_appearance_changed (void)
|
||||||
|
{
|
||||||
|
if (policy_appearance_changed_cb)
|
||||||
|
policy_appearance_changed_cb ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_color_scheme_from_variant (GVariant *value, guint32 *color_scheme)
|
||||||
|
{
|
||||||
|
GVariant *inner;
|
||||||
|
gboolean ok = FALSE;
|
||||||
|
|
||||||
|
if (!value || !color_scheme)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
inner = g_variant_ref (value);
|
||||||
|
while (g_variant_is_of_type (inner, G_VARIANT_TYPE_VARIANT))
|
||||||
|
{
|
||||||
|
GVariant *unwrapped = g_variant_get_variant (inner);
|
||||||
|
|
||||||
|
g_variant_unref (inner);
|
||||||
|
inner = unwrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_variant_is_of_type (inner, G_VARIANT_TYPE_UINT32))
|
||||||
|
{
|
||||||
|
*color_scheme = g_variant_get_uint32 (inner);
|
||||||
|
ok = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (inner);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_store_color_scheme (guint32 color_scheme)
|
||||||
|
{
|
||||||
|
gboolean changed;
|
||||||
|
|
||||||
|
changed = !policy_portal_value_valid || policy_portal_color_scheme != color_scheme;
|
||||||
|
policy_portal_value_valid = TRUE;
|
||||||
|
policy_portal_color_scheme = color_scheme;
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
policy_notify_appearance_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_setting_changed_cb (GDBusConnection *connection, const char *sender_name,
|
||||||
|
const char *object_path, const char *interface_name,
|
||||||
|
const char *signal_name, GVariant *parameters,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
const char *setting_namespace = NULL;
|
||||||
|
const char *setting_key = NULL;
|
||||||
|
GVariant *value = NULL;
|
||||||
|
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||||
|
|
||||||
|
(void) connection;
|
||||||
|
(void) sender_name;
|
||||||
|
(void) object_path;
|
||||||
|
(void) interface_name;
|
||||||
|
(void) signal_name;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
if (!parameters || !g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssv)")))
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_variant_get (parameters, "(&s&sv)", &setting_namespace, &setting_key, &value);
|
||||||
|
if (g_strcmp0 (setting_namespace, "org.freedesktop.appearance") == 0 &&
|
||||||
|
g_strcmp0 (setting_key, "color-scheme") == 0 &&
|
||||||
|
policy_color_scheme_from_variant (value, &color_scheme))
|
||||||
|
policy_portal_store_color_scheme (color_scheme);
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
g_variant_unref (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_read_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||||
|
{
|
||||||
|
GVariant *reply;
|
||||||
|
GVariant *value = NULL;
|
||||||
|
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||||
|
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, NULL);
|
||||||
|
if (!reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(v)")))
|
||||||
|
{
|
||||||
|
g_variant_get (reply, "(v)", &value);
|
||||||
|
if (policy_color_scheme_from_variant (value, &color_scheme))
|
||||||
|
policy_portal_store_color_scheme (color_scheme);
|
||||||
|
if (value)
|
||||||
|
g_variant_unref (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_bus_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||||
|
{
|
||||||
|
GDBusConnection *connection;
|
||||||
|
|
||||||
|
(void) source;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
connection = g_bus_get_finish (result, NULL);
|
||||||
|
if (!connection)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_dbus_connection_signal_subscribe (connection,
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
"org.freedesktop.portal.Settings",
|
||||||
|
"SettingChanged",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
NULL,
|
||||||
|
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||||
|
policy_portal_setting_changed_cb,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
g_dbus_connection_call (connection,
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
"org.freedesktop.portal.Settings",
|
||||||
|
"Read",
|
||||||
|
g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
|
||||||
|
G_VARIANT_TYPE ("(v)"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
3000,
|
||||||
|
NULL,
|
||||||
|
policy_portal_read_ready_cb,
|
||||||
|
NULL);
|
||||||
|
g_object_unref (connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_portal_watch_start (void)
|
||||||
|
{
|
||||||
|
if (policy_portal_watch_started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
policy_portal_watch_started = TRUE;
|
||||||
|
g_bus_get (G_BUS_TYPE_SESSION, NULL, policy_portal_bus_ready_cb, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
policy_gsettings_changed_cb (GSettings *settings, const char *key, gpointer user_data)
|
||||||
|
{
|
||||||
|
(void) settings;
|
||||||
|
(void) key;
|
||||||
|
(void) user_data;
|
||||||
|
|
||||||
|
policy_notify_appearance_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static GSettings *
|
||||||
|
policy_get_interface_gsettings (void)
|
||||||
|
{
|
||||||
|
GSettingsSchemaSource *source;
|
||||||
|
GSettingsSchema *schema;
|
||||||
|
gboolean has_key;
|
||||||
|
|
||||||
|
if (policy_gsettings_checked)
|
||||||
|
return policy_gsettings_interface;
|
||||||
|
|
||||||
|
policy_gsettings_checked = TRUE;
|
||||||
|
|
||||||
|
source = g_settings_schema_source_get_default ();
|
||||||
|
if (!source)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
schema = g_settings_schema_source_lookup (source, "org.gnome.desktop.interface", TRUE);
|
||||||
|
if (!schema)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Requires GLib 2.40, always satisfied by the GTK 3.22 dependency. */
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
has_key = g_settings_schema_has_key (schema, "color-scheme");
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
g_settings_schema_unref (schema);
|
||||||
|
if (!has_key)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
policy_gsettings_interface = g_settings_new ("org.gnome.desktop.interface");
|
||||||
|
g_signal_connect (policy_gsettings_interface, "changed::color-scheme",
|
||||||
|
G_CALLBACK (policy_gsettings_changed_cb), NULL);
|
||||||
|
return policy_gsettings_interface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_gsettings_color_scheme (gboolean *prefers_dark)
|
||||||
|
{
|
||||||
|
GSettings *settings;
|
||||||
|
char *scheme;
|
||||||
|
gboolean explicit_preference = FALSE;
|
||||||
|
|
||||||
|
settings = policy_get_interface_gsettings ();
|
||||||
|
if (!settings)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
scheme = g_settings_get_string (settings, "color-scheme");
|
||||||
|
if (g_strcmp0 (scheme, "prefer-dark") == 0)
|
||||||
|
{
|
||||||
|
*prefers_dark = TRUE;
|
||||||
|
explicit_preference = TRUE;
|
||||||
|
}
|
||||||
|
else if (g_strcmp0 (scheme, "prefer-light") == 0)
|
||||||
|
{
|
||||||
|
*prefers_dark = FALSE;
|
||||||
|
explicit_preference = TRUE;
|
||||||
|
}
|
||||||
|
g_free (scheme);
|
||||||
|
return explicit_preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
policy_gtk_heuristic_prefers_dark (void)
|
||||||
|
{
|
||||||
|
GtkSettings *settings;
|
||||||
|
char *theme_name = NULL;
|
||||||
|
gboolean dark;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
|
||||||
|
/* gtk-application-prefer-dark-theme is overwritten by ZoiteChat at
|
||||||
|
* runtime, so only its captured startup value (settings.ini, XSETTINGS)
|
||||||
|
* reflects the user's own configuration. */
|
||||||
|
if (policy_initial_gtk_state_captured && policy_initial_prefer_dark)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
if (theme_gtk3_is_active ())
|
||||||
|
{
|
||||||
|
/* gtk-theme-name currently holds the in-app theme override;
|
||||||
|
* fall back to the captured system theme name. */
|
||||||
|
theme_name = g_strdup (policy_initial_theme_name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settings = gtk_settings_get_default ();
|
||||||
|
if (settings)
|
||||||
|
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
dark = policy_theme_name_is_dark (theme_name);
|
||||||
|
g_free (theme_name);
|
||||||
|
return dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !G_OS_WIN32 */
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_policy_init (void)
|
||||||
|
{
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
policy_portal_watch_start ();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||||
|
{
|
||||||
|
policy_appearance_changed_cb = callback;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
theme_policy_system_prefers_dark (void)
|
theme_policy_system_prefers_dark (void)
|
||||||
{
|
{
|
||||||
return FALSE;
|
#ifdef G_OS_WIN32
|
||||||
|
gboolean prefer_dark = FALSE;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
|
||||||
|
if (fe_win32_high_contrast_is_enabled ())
|
||||||
|
return FALSE;
|
||||||
|
if (fe_win32_try_get_system_dark (&prefer_dark))
|
||||||
|
return prefer_dark;
|
||||||
|
return policy_theme_name_is_dark (policy_initial_theme_name);
|
||||||
|
#else
|
||||||
|
gboolean prefers_dark = FALSE;
|
||||||
|
|
||||||
|
policy_capture_initial_gtk_state ();
|
||||||
|
policy_portal_watch_start ();
|
||||||
|
|
||||||
|
if (policy_portal_value_valid &&
|
||||||
|
policy_portal_color_scheme != THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE)
|
||||||
|
return policy_portal_color_scheme == THEME_POLICY_COLOR_SCHEME_PREFER_DARK;
|
||||||
|
|
||||||
|
if (policy_gsettings_color_scheme (&prefers_dark))
|
||||||
|
return prefers_dark;
|
||||||
|
|
||||||
|
return policy_gtk_heuristic_prefers_dark ();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
|
|
||||||
#include "../fe-gtk.h"
|
#include "../fe-gtk.h"
|
||||||
|
|
||||||
|
typedef void (*ThemePolicyAppearanceChangedFunc) (void);
|
||||||
|
|
||||||
|
void theme_policy_init (void);
|
||||||
|
void theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback);
|
||||||
gboolean theme_policy_system_prefers_dark (void);
|
gboolean theme_policy_system_prefers_dark (void);
|
||||||
gboolean theme_policy_is_dark_mode_active (unsigned int mode);
|
gboolean theme_policy_is_dark_mode_active (unsigned int mode);
|
||||||
gboolean theme_policy_is_app_dark_mode_active (void);
|
gboolean theme_policy_is_app_dark_mode_active (void);
|
||||||
|
|||||||
@@ -35,8 +35,10 @@
|
|||||||
#include "theme-gtk3.h"
|
#include "theme-gtk3.h"
|
||||||
#include "theme-manager.h"
|
#include "theme-manager.h"
|
||||||
#include "theme-preferences.h"
|
#include "theme-preferences.h"
|
||||||
|
#include "theme-runtime.h"
|
||||||
|
|
||||||
extern void load_text_events (void);
|
extern void load_text_events (void);
|
||||||
|
extern char **text_color_event_names (int color, int *count);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@@ -99,8 +101,16 @@ typedef struct
|
|||||||
gboolean changed;
|
gboolean changed;
|
||||||
gboolean snapshot_valid[THEME_TOKEN_COUNT];
|
gboolean snapshot_valid[THEME_TOKEN_COUNT];
|
||||||
gboolean staged_valid[THEME_TOKEN_COUNT];
|
gboolean staged_valid[THEME_TOKEN_COUNT];
|
||||||
|
/* Only explicitly edited tokens are written back to the runtime
|
||||||
|
* palette; untouched tokens keep following the GTK3 theme. */
|
||||||
|
gboolean staged_dirty[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA staged[THEME_TOKEN_COUNT];
|
GdkRGBA staged[THEME_TOKEN_COUNT];
|
||||||
|
/* the GTK3 theme selection is previewed live from the combo, so
|
||||||
|
* remember what was active when the dialog opened in order to
|
||||||
|
* restore it when the dialog is cancelled */
|
||||||
|
char gtk3_theme_snapshot[sizeof ((struct zoitechatprefs *) 0)->hex_gui_gtk3_theme];
|
||||||
|
int gtk3_variant_snapshot;
|
||||||
} theme_preferences_stage_state;
|
} theme_preferences_stage_state;
|
||||||
|
|
||||||
static theme_preferences_stage_state theme_preferences_stage;
|
static theme_preferences_stage_state theme_preferences_stage;
|
||||||
@@ -145,7 +155,8 @@ theme_preferences_stage_sync_runtime_to_snapshot (void)
|
|||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
if (theme_preferences_stage.snapshot_valid[token])
|
if (theme_preferences_stage.snapshot_valid[token] &&
|
||||||
|
theme_preferences_stage.staged_dirty[token])
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
||||||
&theme_preferences_stage.snapshot[token], NULL);
|
&theme_preferences_stage.snapshot[token], NULL);
|
||||||
}
|
}
|
||||||
@@ -158,7 +169,8 @@ theme_preferences_stage_sync_runtime_to_staged (void)
|
|||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
if (theme_preferences_stage.staged_valid[token])
|
if (theme_preferences_stage.staged_valid[token] &&
|
||||||
|
theme_preferences_stage.staged_dirty[token])
|
||||||
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token,
|
||||||
&theme_preferences_stage.staged[token], NULL);
|
&theme_preferences_stage.staged[token], NULL);
|
||||||
}
|
}
|
||||||
@@ -177,6 +189,7 @@ theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgb
|
|||||||
{
|
{
|
||||||
theme_preferences_stage.staged[token] = *rgba;
|
theme_preferences_stage.staged[token] = *rgba;
|
||||||
theme_preferences_stage.staged_valid[token] = TRUE;
|
theme_preferences_stage.staged_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_dirty[token] = TRUE;
|
||||||
theme_preferences_stage_recompute_changed ();
|
theme_preferences_stage_recompute_changed ();
|
||||||
if (color_change_flag)
|
if (color_change_flag)
|
||||||
*color_change_flag = theme_preferences_stage.changed;
|
*color_change_flag = theme_preferences_stage.changed;
|
||||||
@@ -196,6 +209,11 @@ theme_preferences_stage_begin (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
theme_preferences_stage.active = TRUE;
|
theme_preferences_stage.active = TRUE;
|
||||||
|
|
||||||
|
g_strlcpy (theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
prefs.hex_gui_gtk3_theme,
|
||||||
|
sizeof (theme_preferences_stage.gtk3_theme_snapshot));
|
||||||
|
theme_preferences_stage.gtk3_variant_snapshot = prefs.hex_gui_gtk3_variant;
|
||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
GdkRGBA rgba;
|
GdkRGBA rgba;
|
||||||
@@ -229,12 +247,36 @@ theme_preferences_stage_commit (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* undo a live GTK3 theme preview: put the selection that was active when
|
||||||
|
* the dialog opened back and re-apply it */
|
||||||
|
static void
|
||||||
|
theme_preferences_stage_restore_gtk3 (void)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (prefs.hex_gui_gtk3_theme, theme_preferences_stage.gtk3_theme_snapshot) == 0 &&
|
||||||
|
prefs.hex_gui_gtk3_variant == theme_preferences_stage.gtk3_variant_snapshot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme,
|
||||||
|
theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
|
prefs.hex_gui_gtk3_variant = theme_preferences_stage.gtk3_variant_snapshot;
|
||||||
|
|
||||||
|
theme_gtk3_apply_current (NULL);
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK |
|
||||||
|
THEME_CHANGED_REASON_PALETTE |
|
||||||
|
THEME_CHANGED_REASON_WIDGET_STYLE |
|
||||||
|
THEME_CHANGED_REASON_USERLIST |
|
||||||
|
THEME_CHANGED_REASON_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_preferences_stage_discard (void)
|
theme_preferences_stage_discard (void)
|
||||||
{
|
{
|
||||||
if (!theme_preferences_stage.active)
|
if (!theme_preferences_stage.active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* restore the GTK3 theme first so the color snapshot below wins */
|
||||||
|
theme_preferences_stage_restore_gtk3 ();
|
||||||
theme_preferences_stage_sync_runtime_to_snapshot ();
|
theme_preferences_stage_sync_runtime_to_snapshot ();
|
||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
@@ -242,6 +284,9 @@ theme_preferences_stage_discard (void)
|
|||||||
static void
|
static void
|
||||||
theme_preferences_show_import_error (GtkWidget *button, const char *message);
|
theme_preferences_show_import_error (GtkWidget *button, const char *message);
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_preferences_color_set_tooltip (GtkWidget *widget, ThemeSemanticToken token);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_preferences_manager_row_free (gpointer data)
|
theme_preferences_manager_row_free (gpointer data)
|
||||||
{
|
{
|
||||||
@@ -480,6 +525,7 @@ theme_preferences_color_response_cb (GtkDialog *dialog, gint response_id, gpoint
|
|||||||
data->color_change_flag,
|
data->color_change_flag,
|
||||||
TRUE);
|
TRUE);
|
||||||
theme_preferences_color_button_apply (data->button, &rgba);
|
theme_preferences_color_button_apply (data->button, &rgba);
|
||||||
|
theme_preferences_color_set_tooltip (data->button, data->token);
|
||||||
theme_preferences_manager_update_preview ((theme_color_manager_ui *) data->manager_ui);
|
theme_preferences_manager_update_preview ((theme_color_manager_ui *) data->manager_ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,6 +604,113 @@ theme_preferences_token_display_name (ThemeSemanticToken token)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
theme_preferences_color_tooltip_markup (ThemeSemanticToken token)
|
||||||
|
{
|
||||||
|
GString *tip;
|
||||||
|
GdkRGBA rgba;
|
||||||
|
char *display;
|
||||||
|
char *hex;
|
||||||
|
char *escaped;
|
||||||
|
char **names;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
if (token < THEME_TOKEN_MIRC_0 || token > THEME_TOKEN_MIRC_31)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!theme_preferences_staged_get_color (token, &rgba))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
tip = g_string_new (NULL);
|
||||||
|
|
||||||
|
display = theme_preferences_token_display_name (token);
|
||||||
|
hex = theme_preferences_format_hex (&rgba);
|
||||||
|
escaped = g_markup_escape_text (display, -1);
|
||||||
|
g_string_append_printf (tip,
|
||||||
|
"<span background='%s'>\xe2\x80\x83\xe2\x80\x83</span> <b>%s</b> <tt><small>%s</small></tt>",
|
||||||
|
hex, escaped, hex);
|
||||||
|
g_free (escaped);
|
||||||
|
g_free (display);
|
||||||
|
|
||||||
|
names = text_color_event_names (token - THEME_TOKEN_MIRC_0, &count);
|
||||||
|
if (names)
|
||||||
|
{
|
||||||
|
int line_target;
|
||||||
|
int line_len = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
display = g_strdup_printf (ngettext ("Used by %d text event",
|
||||||
|
"Used by %d text events", count), count);
|
||||||
|
escaped = g_markup_escape_text (display, -1);
|
||||||
|
g_string_append_printf (tip,
|
||||||
|
"\n<small><span alpha='60%%'>%s</span></small>\n\n<small>",
|
||||||
|
escaped);
|
||||||
|
g_free (escaped);
|
||||||
|
g_free (display);
|
||||||
|
|
||||||
|
/* Explicit line breaks keep the tooltip geometry stable;
|
||||||
|
* heavily-used colors get wider lines so the full list
|
||||||
|
* stays on screen. */
|
||||||
|
if (count <= 12)
|
||||||
|
line_target = 44;
|
||||||
|
else if (count <= 40)
|
||||||
|
line_target = 60;
|
||||||
|
else
|
||||||
|
line_target = 76;
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int name_len = (int) g_utf8_strlen (names[i], -1);
|
||||||
|
|
||||||
|
if (i)
|
||||||
|
{
|
||||||
|
if (line_len + name_len + 5 > line_target)
|
||||||
|
{
|
||||||
|
g_string_append_c (tip, '\n');
|
||||||
|
line_len = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_string_append (tip, " \xc2\xb7 ");
|
||||||
|
line_len += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped = g_markup_escape_text (names[i], -1);
|
||||||
|
g_string_append (tip, escaped);
|
||||||
|
line_len += name_len;
|
||||||
|
g_free (escaped);
|
||||||
|
}
|
||||||
|
g_string_append (tip, "</small>");
|
||||||
|
g_strfreev (names);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
escaped = g_markup_escape_text (_("Not used by any text events"), -1);
|
||||||
|
g_string_append_printf (tip,
|
||||||
|
"\n<small><span alpha='60%%'><i>%s</i></span></small>",
|
||||||
|
escaped);
|
||||||
|
g_free (escaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (hex);
|
||||||
|
return g_string_free (tip, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static markup tooltips only: building tooltip widgets from a
|
||||||
|
* query-tooltip handler makes GtkTooltip flicker on some setups, so the
|
||||||
|
* markup is set once and refreshed whenever the color changes. */
|
||||||
|
static void
|
||||||
|
theme_preferences_color_set_tooltip (GtkWidget *widget, ThemeSemanticToken token)
|
||||||
|
{
|
||||||
|
char *markup = theme_preferences_color_tooltip_markup (token);
|
||||||
|
|
||||||
|
if (!markup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gtk_widget_set_tooltip_markup (widget, markup);
|
||||||
|
g_free (markup);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_preferences_manager_row_apply (theme_color_manager_row *row, const GdkRGBA *rgba)
|
theme_preferences_manager_row_apply (theme_color_manager_row *row, const GdkRGBA *rgba)
|
||||||
{
|
{
|
||||||
@@ -568,6 +721,7 @@ theme_preferences_manager_row_apply (theme_color_manager_row *row, const GdkRGBA
|
|||||||
hex = theme_preferences_format_hex (rgba);
|
hex = theme_preferences_format_hex (rgba);
|
||||||
gtk_entry_set_text (GTK_ENTRY (row->entry), hex);
|
gtk_entry_set_text (GTK_ENTRY (row->entry), hex);
|
||||||
g_free (hex);
|
g_free (hex);
|
||||||
|
theme_preferences_color_set_tooltip (row->row, row->token);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -727,6 +881,10 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
|
|||||||
ThemeSemanticToken token;
|
ThemeSemanticToken token;
|
||||||
ThemeWidgetStyleValues style_values;
|
ThemeWidgetStyleValues style_values;
|
||||||
|
|
||||||
|
/* The runtime palette was just reset; drop any
|
||||||
|
* pending edits so the reset is not re-pinned. */
|
||||||
|
memset (theme_preferences_stage.staged_dirty, 0,
|
||||||
|
sizeof (theme_preferences_stage.staged_dirty));
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
GdkRGBA rgba;
|
GdkRGBA rgba;
|
||||||
@@ -741,7 +899,6 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i
|
|||||||
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE;
|
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_FOREGROUND] = TRUE;
|
||||||
theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background;
|
theme_preferences_stage.staged[THEME_TOKEN_TEXT_BACKGROUND] = style_values.background;
|
||||||
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE;
|
theme_preferences_stage.staged_valid[THEME_TOKEN_TEXT_BACKGROUND] = TRUE;
|
||||||
theme_preferences_stage_sync_runtime_to_staged ();
|
|
||||||
theme_preferences_stage_recompute_changed ();
|
theme_preferences_stage_recompute_changed ();
|
||||||
if (ui->color_change_flag)
|
if (ui->color_change_flag)
|
||||||
*ui->color_change_flag = theme_preferences_stage.changed;
|
*ui->color_change_flag = theme_preferences_stage.changed;
|
||||||
@@ -883,6 +1040,8 @@ theme_preferences_create_color_manager_dialog (GtkWindow *parent, gboolean *colo
|
|||||||
if (theme_preferences_staged_get_color (token, &rgba))
|
if (theme_preferences_staged_get_color (token, &rgba))
|
||||||
theme_preferences_manager_row_apply (row, &rgba);
|
theme_preferences_manager_row_apply (row, &rgba);
|
||||||
|
|
||||||
|
theme_preferences_color_set_tooltip (list_row, token);
|
||||||
|
|
||||||
g_signal_connect (G_OBJECT (button), "clicked",
|
g_signal_connect (G_OBJECT (button), "clicked",
|
||||||
G_CALLBACK (theme_preferences_manager_pick_cb), row);
|
G_CALLBACK (theme_preferences_manager_pick_cb), row);
|
||||||
g_object_set_data (G_OBJECT (button), "zoitechat-theme-color-manager-ui", ui);
|
g_object_set_data (G_OBJECT (button), "zoitechat-theme-color-manager-ui", ui);
|
||||||
@@ -1197,6 +1356,7 @@ theme_preferences_create_color_button (GtkWidget *table,
|
|||||||
g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box);
|
g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box);
|
||||||
g_object_set_data (G_OBJECT (but), "zoitechat-theme-token", GINT_TO_POINTER (token));
|
g_object_set_data (G_OBJECT (but), "zoitechat-theme-token", GINT_TO_POINTER (token));
|
||||||
g_object_set_data (G_OBJECT (but), "zoitechat-theme-color-change", color_change_flag);
|
g_object_set_data (G_OBJECT (but), "zoitechat-theme-color-change", color_change_flag);
|
||||||
|
theme_preferences_color_set_tooltip (but, token);
|
||||||
gtk_grid_attach (GTK_GRID (table), but, col, row, 1, 1);
|
gtk_grid_attach (GTK_GRID (table), but, col, row, 1, 1);
|
||||||
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (theme_preferences_color_cb), parent);
|
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (theme_preferences_color_cb), parent);
|
||||||
if (theme_preferences_staged_get_color (token, &color))
|
if (theme_preferences_staged_get_color (token, &color))
|
||||||
@@ -1417,17 +1577,69 @@ theme_preferences_gtk3_sync_remove_state (theme_preferences_ui *ui)
|
|||||||
gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
gtk_widget_set_sensitive (ui->gtk3_remove, source == ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static void
|
||||||
theme_preferences_gtk3_apply_and_refresh (GError **error)
|
theme_preferences_stage_rebase_color (ThemeSemanticToken token, const GdkRGBA *rgba)
|
||||||
{
|
{
|
||||||
if (!theme_gtk3_apply_current (error))
|
if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba)
|
||||||
return FALSE;
|
return;
|
||||||
theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK |
|
if (!theme_preferences_stage.active)
|
||||||
THEME_CHANGED_REASON_PALETTE |
|
return;
|
||||||
THEME_CHANGED_REASON_WIDGET_STYLE |
|
|
||||||
THEME_CHANGED_REASON_USERLIST |
|
/* The theme changed underneath the dialog: shift the baseline so the
|
||||||
THEME_CHANGED_REASON_MODE);
|
* new theme-provided value is neither treated as a user edit nor
|
||||||
return TRUE;
|
* pinned into the runtime palette. */
|
||||||
|
theme_preferences_stage.snapshot[token] = *rgba;
|
||||||
|
theme_preferences_stage.staged[token] = *rgba;
|
||||||
|
theme_preferences_stage.snapshot_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_valid[token] = TRUE;
|
||||||
|
theme_preferences_stage.staged_dirty[token] = FALSE;
|
||||||
|
theme_preferences_stage_recompute_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
theme_preferences_gtk3_sync_runtime_palette (theme_preferences_ui *ui)
|
||||||
|
{
|
||||||
|
ThemeWidgetStyleValues style_values;
|
||||||
|
GtkWidget *style_source = NULL;
|
||||||
|
|
||||||
|
if (ui && ui->parent)
|
||||||
|
style_source = GTK_WIDGET (ui->parent);
|
||||||
|
else if (ui && ui->gtk3_combo)
|
||||||
|
style_source = ui->gtk3_combo;
|
||||||
|
|
||||||
|
theme_runtime_clear_gtk_mapped_custom_tokens ();
|
||||||
|
|
||||||
|
theme_get_widget_style_values_for_widget (style_source, &style_values);
|
||||||
|
|
||||||
|
/* Leave the mapped tokens unpinned so the user list, channel tree and
|
||||||
|
* input box keep resolving their colors from whatever GTK3 theme is
|
||||||
|
* active (system or in-app selected), and only refresh what the
|
||||||
|
* dialog shows. Pinning theme-derived values here freezes them into
|
||||||
|
* the runtime palette (and colors.conf), which left those widgets
|
||||||
|
* with stale colors until the next restart. */
|
||||||
|
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_FOREGROUND, &style_values.foreground);
|
||||||
|
theme_preferences_stage_rebase_color (THEME_TOKEN_TEXT_BACKGROUND, &style_values.background);
|
||||||
|
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_FOREGROUND, &style_values.selection_foreground);
|
||||||
|
theme_preferences_stage_rebase_color (THEME_TOKEN_SELECTION_BACKGROUND, &style_values.selection_background);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
theme_preferences_gtk3_apply_and_refresh (theme_preferences_ui *ui, GError **error)
|
||||||
|
{
|
||||||
|
if (!theme_gtk3_apply_current (error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (ui && ui->parent)
|
||||||
|
theme_manager_apply_to_window (GTK_WIDGET (ui->parent));
|
||||||
|
|
||||||
|
theme_preferences_gtk3_sync_runtime_palette (ui);
|
||||||
|
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK |
|
||||||
|
THEME_CHANGED_REASON_PALETTE |
|
||||||
|
THEME_CHANGED_REASON_WIDGET_STYLE |
|
||||||
|
THEME_CHANGED_REASON_USERLIST |
|
||||||
|
THEME_CHANGED_REASON_MODE);
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1463,7 +1675,7 @@ theme_preferences_gtk3_changed_cb (GtkComboBox *combo, gpointer user_data)
|
|||||||
ui->setup_prefs->hex_gui_gtk3_variant = prefs.hex_gui_gtk3_variant;
|
ui->setup_prefs->hex_gui_gtk3_variant = prefs.hex_gui_gtk3_variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection_changed && !theme_preferences_gtk3_apply_and_refresh (&error))
|
if (selection_changed && !theme_preferences_gtk3_apply_and_refresh (ui, &error))
|
||||||
{
|
{
|
||||||
theme_preferences_show_message (ui, GTK_MESSAGE_ERROR,
|
theme_preferences_show_message (ui, GTK_MESSAGE_ERROR,
|
||||||
error ? error->message : _("Failed to apply GTK3 theme."));
|
error ? error->message : _("Failed to apply GTK3 theme."));
|
||||||
@@ -1552,7 +1764,7 @@ theme_preferences_populate_gtk3 (theme_preferences_ui *ui)
|
|||||||
g_free (final_id);
|
g_free (final_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_apply && !theme_preferences_gtk3_apply_and_refresh (&error))
|
if (should_apply && !theme_preferences_gtk3_apply_and_refresh (ui, &error))
|
||||||
{
|
{
|
||||||
theme_preferences_show_message (ui, GTK_MESSAGE_ERROR,
|
theme_preferences_show_message (ui, GTK_MESSAGE_ERROR,
|
||||||
error ? error->message : _("Failed to apply GTK3 theme."));
|
error ? error->message : _("Failed to apply GTK3 theme."));
|
||||||
|
|||||||
@@ -410,6 +410,20 @@ theme_runtime_reset_mode_colors (gboolean dark_mode)
|
|||||||
dark_mode_active = FALSE;
|
dark_mode_active = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
theme_runtime_clear_gtk_mapped_custom_tokens (void)
|
||||||
|
{
|
||||||
|
light_custom_tokens[THEME_TOKEN_TEXT_FOREGROUND] = FALSE;
|
||||||
|
light_custom_tokens[THEME_TOKEN_TEXT_BACKGROUND] = FALSE;
|
||||||
|
light_custom_tokens[THEME_TOKEN_SELECTION_FOREGROUND] = FALSE;
|
||||||
|
light_custom_tokens[THEME_TOKEN_SELECTION_BACKGROUND] = FALSE;
|
||||||
|
|
||||||
|
dark_custom_tokens[THEME_TOKEN_TEXT_FOREGROUND] = FALSE;
|
||||||
|
dark_custom_tokens[THEME_TOKEN_TEXT_BACKGROUND] = FALSE;
|
||||||
|
dark_custom_tokens[THEME_TOKEN_SELECTION_FOREGROUND] = FALSE;
|
||||||
|
dark_custom_tokens[THEME_TOKEN_SELECTION_BACKGROUND] = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_runtime_load (void)
|
theme_runtime_load (void)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user