mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-18 17:29:27 +00:00
Compare commits
50 Commits
cacc8fcafe
...
enable-off
| Author | SHA1 | Date | |
|---|---|---|---|
| 44dbbdc56b | |||
|
|
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 |
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.
|
||||||
|
```
|
||||||
5
.github/workflows/appimage-build.yml
vendored
5
.github/workflows/appimage-build.yml
vendored
@@ -37,6 +37,7 @@ jobs:
|
|||||||
liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
|
liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
|
||||||
perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
|
perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
|
||||||
fonts-noto-color-emoji breeze-gtk-theme \
|
fonts-noto-color-emoji breeze-gtk-theme \
|
||||||
|
python3-venv \
|
||||||
patchelf file curl
|
patchelf file curl
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
@@ -206,6 +207,10 @@ jobs:
|
|||||||
export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/zoitechat/plugins"
|
export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/zoitechat/plugins"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$APPDIR/usr/share/doc/zoitechat/html/index.html" ]; then
|
||||||
|
export ZOITECHAT_DOCDIR="$APPDIR/usr/share/doc/zoitechat/html"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d "$APPDIR/usr/share/glib-2.0/schemas" ]; then
|
if [ -d "$APPDIR/usr/share/glib-2.0/schemas" ]; then
|
||||||
export GSETTINGS_SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
|
export GSETTINGS_SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
48
.github/workflows/windows-build.yml
vendored
48
.github/workflows/windows-build.yml
vendored
@@ -113,6 +113,33 @@ jobs:
|
|||||||
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Runs while Python 3.14 is still the active interpreter; the installer
|
||||||
|
# packages rel\offline-docs as the "Offline Documentation" component and
|
||||||
|
# skips it if this step could not build the docs.
|
||||||
|
- name: Build offline documentation
|
||||||
|
run: |
|
||||||
|
python data\misc\build_offline_docs.py --output ..\zoitechat-build\${{ matrix.platform }}\rel\offline-docs
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.8.10'
|
||||||
|
architecture: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Prepare Python 3.8
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install cffi
|
||||||
|
|
||||||
|
$pyRoot = $env:pythonLocation
|
||||||
|
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
|
||||||
|
|
||||||
|
foreach ($pyDir in @("C:\gtk-build\python-3.8.10", "C:\gtk-build\python-3.8")) {
|
||||||
|
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
|
||||||
|
$target = Join-Path $pyDir "${{ matrix.platform }}"
|
||||||
|
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
|
||||||
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||||
@@ -124,12 +151,33 @@ jobs:
|
|||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set "PYTHON38_DIR=C:\gtk-build\python-3.8\${{ matrix.platform }}"
|
||||||
|
if not exist "%PYTHON38_DIR%\libs\python38.lib" (
|
||||||
|
echo Missing %PYTHON38_DIR%\libs\python38.lib
|
||||||
|
dir "%PYTHON38_DIR%\libs"
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not exist "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" (
|
||||||
|
echo Missing %PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
set "LIB=%PYTHON_DIR%\libs;%LIB%"
|
set "LIB=%PYTHON_DIR%\libs;%LIB%"
|
||||||
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
|
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
|
||||||
|
|
||||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }"
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }"
|
||||||
|
|
||||||
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
|
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\plugins\hcpython38.dll" (
|
||||||
|
echo hcpython38.dll was not built
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\_cffi_backend.cp38*.pyd" (
|
||||||
|
echo _cffi_backend for Python 3.8 was not staged for the installer
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Preparing Artifacts
|
- name: Preparing Artifacts
|
||||||
|
|||||||
46
COPYING
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,6 +1,35 @@
|
|||||||
ZoiteChat ChangeLog
|
ZoiteChat ChangeLog
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
2.18.3 (2026-06-29)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Added IRCv3 support for message tags, echo-message, typing notifications, and replies.
|
||||||
|
- Restored the accept-invalid-cert TLS bypass behavior.
|
||||||
|
- Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.
|
||||||
|
- Snapshot Python hooks during plugin unload for safer plugin shutdown.
|
||||||
|
- Fixed Windows GtkStatusIcon tray menu popup behavior.
|
||||||
|
- Fixed self-echoed private messages routing to the target tab.
|
||||||
|
- Fixed the lag ping timeout window.
|
||||||
|
|
||||||
|
2.18.2 (2026-06-20)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Improved security and connection handling with stronger TLS defaults,
|
||||||
|
safer certificate/hostname validation, FiSHLiM OpenSSL fixes, keyring support,
|
||||||
|
and getaddrinfo-based DCC lookups.
|
||||||
|
- Expanded FiSHLiM usability with a GTK key manager, context-menu access, and clearer
|
||||||
|
channel/private-message key handling.
|
||||||
|
- Improved keyboard and navigation behavior with customizable keybinds, reset support,
|
||||||
|
channel-name switching, /server -noproxy, and configurable stale-link ping checks.
|
||||||
|
- Refined the GTK interface with native file chooser dialogs, topic layout preferences,
|
||||||
|
timestamp/date hover tooltips, better restore-down relayout, text scroll speed controls,
|
||||||
|
and aligned network meters.
|
||||||
|
- Added privacy and display options, including hiding join/part hostmasks and improved
|
||||||
|
topic/multiline mode layout controls.
|
||||||
|
- Fixed several platform/UI edge cases, including tray restore from iconified state, DBus unload
|
||||||
|
handling, GTK3 theme refresh/saving, and tab switcher compatibility.
|
||||||
|
|
||||||
2.18.1 (2026-05-21)
|
2.18.1 (2026-05-21)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,29 @@ if get_option('gtk-frontend')
|
|||||||
subdir('icons')
|
subdir('icons')
|
||||||
subdir('misc')
|
subdir('misc')
|
||||||
subdir('man')
|
subdir('man')
|
||||||
|
|
||||||
|
# Fetch and build the offline manual so Help->Contents works without
|
||||||
|
# connectivity. 'auto' skips with a warning when the docs cannot be
|
||||||
|
# fetched (e.g. network-less distro builds, which ship docs themselves);
|
||||||
|
# 'enabled' makes that a hard error.
|
||||||
|
offline_docs_opt = get_option('offline-docs')
|
||||||
|
if not offline_docs_opt.disabled()
|
||||||
|
offline_docs_args = [
|
||||||
|
'--repo', get_option('offline-docs-repo'),
|
||||||
|
'--output', join_paths(meson.current_build_dir(), 'offline-docs'),
|
||||||
|
'--stamp', '@OUTPUT@',
|
||||||
|
]
|
||||||
|
if offline_docs_opt.enabled()
|
||||||
|
offline_docs_args += ['--strict']
|
||||||
|
endif
|
||||||
|
custom_target('offline-docs',
|
||||||
|
output: 'offline-docs.stamp',
|
||||||
|
command: [find_program('python3'), files('misc/build_offline_docs.py')] + offline_docs_args,
|
||||||
|
build_by_default: true,
|
||||||
|
)
|
||||||
|
meson.add_install_script(files('misc/install_offline_docs.py'),
|
||||||
|
join_paths(meson.current_build_dir(), 'offline-docs'),
|
||||||
|
offline_docs_dir,
|
||||||
|
)
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|||||||
133
data/misc/build_offline_docs.py
Normal file
133
data/misc/build_offline_docs.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build ZoiteChat's offline HTML documentation for bundling with releases.
|
||||||
|
|
||||||
|
Clones the ZoiteChat/documentation repository, preferring a tag that
|
||||||
|
matches this source tree's version so the bundled manual matches the
|
||||||
|
application, and builds it with Sphinx into --output. Used by the
|
||||||
|
meson build (offline-docs option) and by the Windows CI workflow.
|
||||||
|
|
||||||
|
Sphinx is installed into a throwaway virtualenv so the invoking Python
|
||||||
|
(which the Windows build also bundles pieces of) is left untouched.
|
||||||
|
|
||||||
|
Unless --strict is given, any failure prints a warning (GitHub Actions
|
||||||
|
annotation syntax) and exits 0: builds then simply fall back to opening
|
||||||
|
the online manual instead of failing outright.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
DEFAULT_REPO = "https://github.com/ZoiteChat/documentation.git"
|
||||||
|
|
||||||
|
|
||||||
|
def project_version() -> str:
|
||||||
|
meson_build = pathlib.Path(__file__).resolve().parents[2] / "meson.build"
|
||||||
|
match = re.search(r"version\s*:\s*'([^']+)'", meson_build.read_text(encoding="utf-8"))
|
||||||
|
if not match:
|
||||||
|
raise RuntimeError(f"could not parse project version from {meson_build}")
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def run(argv, **kwargs) -> None:
|
||||||
|
print("+", " ".join(str(arg) for arg in argv), flush=True)
|
||||||
|
subprocess.run(argv, check=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def clone_docs(repo: str, version: str, target: pathlib.Path) -> str:
|
||||||
|
# A missing tag falls through to the default branch so the docs may be
|
||||||
|
# newer than the app, which beats shipping none at all.
|
||||||
|
for ref in (f"v{version}", version, None):
|
||||||
|
cmd = ["git", "clone", "--depth", "1"]
|
||||||
|
if ref:
|
||||||
|
cmd += ["--branch", ref]
|
||||||
|
cmd += [repo, str(target)]
|
||||||
|
try:
|
||||||
|
run(cmd)
|
||||||
|
return ref or "default branch"
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
shutil.rmtree(target, ignore_errors=True)
|
||||||
|
raise RuntimeError(f"could not clone {repo}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_source_dir(checkout: pathlib.Path) -> pathlib.Path:
|
||||||
|
for candidate in (checkout, checkout / "docs", checkout / "doc"):
|
||||||
|
if (candidate / "conf.py").is_file():
|
||||||
|
return candidate
|
||||||
|
raise RuntimeError(f"no Sphinx conf.py found in {checkout}")
|
||||||
|
|
||||||
|
|
||||||
|
def venv_python(venv_dir: pathlib.Path) -> pathlib.Path:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return venv_dir / "Scripts" / "python.exe"
|
||||||
|
return venv_dir / "bin" / "python"
|
||||||
|
|
||||||
|
|
||||||
|
def build(output: pathlib.Path, repo: str, version: str) -> None:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="zoitechat-docs-") as tmp:
|
||||||
|
tmp_dir = pathlib.Path(tmp)
|
||||||
|
checkout = tmp_dir / "documentation"
|
||||||
|
html_dir = tmp_dir / "html"
|
||||||
|
venv_dir = tmp_dir / "venv"
|
||||||
|
|
||||||
|
ref = clone_docs(repo, version, checkout)
|
||||||
|
print(f"building documentation from {ref}", flush=True)
|
||||||
|
source_dir = find_source_dir(checkout)
|
||||||
|
|
||||||
|
run([sys.executable, "-m", "venv", str(venv_dir)])
|
||||||
|
python = venv_python(venv_dir)
|
||||||
|
requirements = source_dir / "requirements.txt"
|
||||||
|
if not requirements.is_file():
|
||||||
|
requirements = checkout / "requirements.txt"
|
||||||
|
if requirements.is_file():
|
||||||
|
run([python, "-m", "pip", "install", "--quiet", "-r", str(requirements)])
|
||||||
|
run([python, "-m", "pip", "install", "--quiet", "sphinx"])
|
||||||
|
else:
|
||||||
|
run([python, "-m", "pip", "install", "--quiet", "sphinx", "sphinx_rtd_theme"])
|
||||||
|
|
||||||
|
run([python, "-m", "sphinx", "-b", "html", str(source_dir), str(html_dir)])
|
||||||
|
if not (html_dir / "index.html").is_file():
|
||||||
|
raise RuntimeError("Sphinx build produced no index.html")
|
||||||
|
|
||||||
|
if output.exists():
|
||||||
|
shutil.rmtree(output)
|
||||||
|
shutil.copytree(html_dir, output,
|
||||||
|
ignore=shutil.ignore_patterns(".doctrees", ".buildinfo"))
|
||||||
|
|
||||||
|
file_count = sum(1 for path in output.rglob("*") if path.is_file())
|
||||||
|
print(f"offline documentation staged at {output} ({file_count} files)", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--output", required=True, type=pathlib.Path,
|
||||||
|
help="directory to stage the built HTML tree into")
|
||||||
|
parser.add_argument("--repo", default=DEFAULT_REPO,
|
||||||
|
help="documentation git repository to build from")
|
||||||
|
parser.add_argument("--version", default=None,
|
||||||
|
help="app version used to pick a matching docs tag "
|
||||||
|
"(default: parsed from meson.build)")
|
||||||
|
parser.add_argument("--strict", action="store_true",
|
||||||
|
help="fail on error instead of emitting a warning")
|
||||||
|
parser.add_argument("--stamp", type=pathlib.Path, default=None,
|
||||||
|
help="file to touch on completion (meson target output)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
build(args.output, args.repo, args.version or project_version())
|
||||||
|
except Exception as error: # noqa: BLE001 - degrade to website-only help
|
||||||
|
if args.strict:
|
||||||
|
raise
|
||||||
|
print(f"::warning::offline documentation not bundled: {error}", flush=True)
|
||||||
|
|
||||||
|
if args.stamp:
|
||||||
|
args.stamp.write_text("ok\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
31
data/misc/install_offline_docs.py
Normal file
31
data/misc/install_offline_docs.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Meson install helper: copy the built offline docs into the install tree.
|
||||||
|
|
||||||
|
Silently skips when the docs were not built (offline-docs=auto without
|
||||||
|
network); Help->Contents then falls back to the online manual.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
src = pathlib.Path(sys.argv[1])
|
||||||
|
dest_arg = pathlib.PurePath(sys.argv[2])
|
||||||
|
|
||||||
|
if not (src / "index.html").is_file():
|
||||||
|
print("offline documentation was not built; skipping install")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if dest_arg.is_absolute():
|
||||||
|
# An absolute offline-docs-dir lands outside the prefix; honour DESTDIR
|
||||||
|
destdir = os.environ.get("DESTDIR", "")
|
||||||
|
dest = pathlib.Path(destdir + str(dest_arg)) if destdir else pathlib.Path(dest_arg)
|
||||||
|
else:
|
||||||
|
dest = pathlib.Path(os.environ["MESON_INSTALL_DESTDIR_PREFIX"]) / dest_arg
|
||||||
|
|
||||||
|
if dest.exists():
|
||||||
|
shutil.rmtree(dest)
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copytree(src, dest)
|
||||||
|
print(f"Installed offline documentation to {dest}")
|
||||||
@@ -29,6 +29,42 @@
|
|||||||
<id>zoitechat.desktop</id>
|
<id>zoitechat.desktop</id>
|
||||||
</provides>
|
</provides>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2026-06-29" version="2.18.3">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Added IRCv3 support for message tags, echo-message, typing notifications, and replies.</li>
|
||||||
|
<li>Restored the accept-invalid-cert TLS bypass behavior.</li>
|
||||||
|
<li>Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.</li>
|
||||||
|
<li>Snapshot Python hooks during plugin unload for safer plugin shutdown.</li>
|
||||||
|
<li>Fixed Windows GtkStatusIcon tray menu popup behavior.</li>
|
||||||
|
<li>Fixed self-echoed private messages routing to the target tab.</li>
|
||||||
|
<li>Fixed the lag ping timeout window.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release date="2026-06-20" version="2.18.2">
|
||||||
|
<description>
|
||||||
|
<p>Security and connections:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved TLS, certificate and hostname validation, FiSHLiM OpenSSL handling, keyring support, and DCC host lookups.</li>
|
||||||
|
</ul>
|
||||||
|
<p>FiSHLiM:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added a GTK key manager with context-menu access and clearer channel/private-message key handling.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Interface and preferences:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved keybind customization, reset support, channel-name switching, <code>/server -noproxy</code>, and stale-link ping checks.</li>
|
||||||
|
<li>Refined GTK behavior with native file choosers, topic layout preferences, timestamp/date tooltips, text scroll controls, and aligned network meters.</li>
|
||||||
|
<li>Added privacy and display options for hiding join/part hostmasks and controlling topic/multiline layout.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed tray restore, DBus unload handling, GTK3 theme refresh/saving, topic relayout, and tab switcher compatibility.</li>
|
||||||
|
<li>Updated project documentation.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release date="2026-05-21" version="2.18.1">
|
<release date="2026-05-21" version="2.18.1">
|
||||||
<description>
|
<description>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
12
meson.build
12
meson.build
@@ -1,5 +1,5 @@
|
|||||||
project('zoitechat', 'c',
|
project('zoitechat', 'c',
|
||||||
version: '2.18.1',
|
version: '2.18.3',
|
||||||
meson_version: '>= 0.55.0',
|
meson_version: '>= 0.55.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c17',
|
'c_std=c17',
|
||||||
@@ -35,6 +35,16 @@ config_h.set_quoted('PACKAGE_NAME', meson.project_name())
|
|||||||
config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat')
|
config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat')
|
||||||
config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'),
|
config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'),
|
||||||
get_option('datadir'), 'locale'))
|
get_option('datadir'), 'locale'))
|
||||||
|
|
||||||
|
# Where Help->Contents looks for locally installed documentation before
|
||||||
|
# falling back to the online manual. Packagers that ship the HTML docs
|
||||||
|
# elsewhere can point this at their doc directory.
|
||||||
|
offline_docs_dir = get_option('offline-docs-dir')
|
||||||
|
if offline_docs_dir == ''
|
||||||
|
offline_docs_dir = join_paths(get_option('datadir'), 'doc', 'zoitechat', 'html')
|
||||||
|
endif
|
||||||
|
config_h.set_quoted('ZOITECHATDOCDIR', join_paths(get_option('prefix'), offline_docs_dir))
|
||||||
|
|
||||||
config_h.set10('ENABLE_NLS', true)
|
config_h.set10('ENABLE_NLS', true)
|
||||||
|
|
||||||
# Optional features
|
# Optional features
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ option('install-appdata', type: 'boolean',
|
|||||||
option('install-plugin-metainfo', type: 'boolean', value: false,
|
option('install-plugin-metainfo', type: 'boolean', value: false,
|
||||||
description: 'Installs metainfo files for enabled plugins, useful when distros create split packages'
|
description: 'Installs metainfo files for enabled plugins, useful when distros create split packages'
|
||||||
)
|
)
|
||||||
|
option('offline-docs', type: 'feature', value: 'auto',
|
||||||
|
description: 'Fetch, build and install the offline HTML documentation (needs network at build time); auto skips with a warning when unavailable'
|
||||||
|
)
|
||||||
|
option('offline-docs-dir', type: 'string', value: '',
|
||||||
|
description: 'Directory searched for offline HTML documentation (index.html), absolute or relative to prefix. Defaults to $datadir/doc/zoitechat/html'
|
||||||
|
)
|
||||||
|
option('offline-docs-repo', type: 'string', value: 'https://github.com/ZoiteChat/documentation.git',
|
||||||
|
description: 'Git repository the offline documentation is built from'
|
||||||
|
)
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
option('with-checksum', type: 'boolean',
|
option('with-checksum', type: 'boolean',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: zoitechat
|
Name: zoitechat
|
||||||
Version: 2.18.1
|
Version: 2.18.3
|
||||||
Release: %autorelease
|
Release: %autorelease
|
||||||
Summary: HexChat-based IRC client
|
Summary: HexChat-based IRC client
|
||||||
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
|
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
|
||||||
@@ -74,7 +74,6 @@ appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/net.zoite
|
|||||||
xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs \
|
xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs \
|
||||||
"Theme Manager Dispatch Routing Tests" \
|
"Theme Manager Dispatch Routing Tests" \
|
||||||
"Validate net.zoite.Zoitechat.desktop" \
|
"Validate net.zoite.Zoitechat.desktop" \
|
||||||
"Theme GTK3 Settings Tests" \
|
|
||||||
"Validate translations" \
|
"Validate translations" \
|
||||||
"Fishlim Tests"
|
"Fishlim Tests"
|
||||||
|
|
||||||
|
|||||||
@@ -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.1'
|
VERSION = b'2.18.3'
|
||||||
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
||||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
||||||
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
||||||
@@ -173,7 +173,7 @@ class Plugin:
|
|||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
log('unloading', self.filename)
|
log('unloading', self.filename)
|
||||||
for hook in self.hooks:
|
for hook in list(self.hooks):
|
||||||
if hook.is_unload is True:
|
if hook.is_unload is True:
|
||||||
try:
|
try:
|
||||||
hook.callback(hook.userdata)
|
hook.callback(hook.userdata)
|
||||||
|
|||||||
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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -428,6 +428,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_lagometer", P_OFFINT (hex_gui_lagometer), TYPE_INT},
|
{"gui_lagometer", P_OFFINT (hex_gui_lagometer), TYPE_INT},
|
||||||
{"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT},
|
{"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT},
|
||||||
{"gui_mode_buttons", P_OFFINT (hex_gui_mode_buttons), TYPE_BOOL},
|
{"gui_mode_buttons", P_OFFINT (hex_gui_mode_buttons), TYPE_BOOL},
|
||||||
|
{"gui_mode_buttons_inline", P_OFFINT (hex_gui_mode_buttons_inline), TYPE_BOOL},
|
||||||
{"gui_pane_divider_position", P_OFFINT (hex_gui_pane_divider_position), TYPE_INT},
|
{"gui_pane_divider_position", P_OFFINT (hex_gui_pane_divider_position), TYPE_INT},
|
||||||
{"gui_pane_left_size", P_OFFINT (hex_gui_pane_left_size), TYPE_INT},
|
{"gui_pane_left_size", P_OFFINT (hex_gui_pane_left_size), TYPE_INT},
|
||||||
{"gui_pane_right_size", P_OFFINT (hex_gui_pane_right_size), TYPE_INT},
|
{"gui_pane_right_size", P_OFFINT (hex_gui_pane_right_size), TYPE_INT},
|
||||||
@@ -458,6 +459,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_tab_utils", P_OFFINT (hex_gui_tab_utils), TYPE_BOOL},
|
{"gui_tab_utils", P_OFFINT (hex_gui_tab_utils), TYPE_BOOL},
|
||||||
{"gui_throttlemeter", P_OFFINT (hex_gui_throttlemeter), TYPE_INT},
|
{"gui_throttlemeter", P_OFFINT (hex_gui_throttlemeter), TYPE_INT},
|
||||||
{"gui_topicbar", P_OFFINT (hex_gui_topicbar), TYPE_BOOL},
|
{"gui_topicbar", P_OFFINT (hex_gui_topicbar), TYPE_BOOL},
|
||||||
|
{"gui_topicbar_multiline", P_OFFINT (hex_gui_topicbar_multiline), TYPE_BOOL},
|
||||||
{"gui_transparency", P_OFFINT (hex_gui_transparency), TYPE_INT},
|
{"gui_transparency", P_OFFINT (hex_gui_transparency), TYPE_INT},
|
||||||
{"gui_tray", P_OFFINT (hex_gui_tray), TYPE_BOOL},
|
{"gui_tray", P_OFFINT (hex_gui_tray), TYPE_BOOL},
|
||||||
{"gui_tray_away", P_OFFINT (hex_gui_tray_away), TYPE_BOOL},
|
{"gui_tray_away", P_OFFINT (hex_gui_tray_away), TYPE_BOOL},
|
||||||
@@ -791,6 +793,7 @@ load_default_config(void)
|
|||||||
prefs.hex_gui_tab_scrollchans = 1;
|
prefs.hex_gui_tab_scrollchans = 1;
|
||||||
prefs.hex_gui_mouse_scroll_speed = 10;
|
prefs.hex_gui_mouse_scroll_speed = 10;
|
||||||
prefs.hex_gui_topicbar = 1;
|
prefs.hex_gui_topicbar = 1;
|
||||||
|
prefs.hex_gui_topicbar_multiline = 1;
|
||||||
prefs.hex_gui_transparency = 255;
|
prefs.hex_gui_transparency = 255;
|
||||||
prefs.hex_gui_tray = 1;
|
prefs.hex_gui_tray = 1;
|
||||||
prefs.hex_gui_tray_blink = 1;
|
prefs.hex_gui_tray_blink = 1;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ int fe_input_add (int sok, int flags, void *func, void *data);
|
|||||||
void fe_input_remove (int tag);
|
void fe_input_remove (int tag);
|
||||||
void fe_idle_add (void *func, void *data);
|
void fe_idle_add (void *func, void *data);
|
||||||
void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
|
void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
|
||||||
|
void fe_set_typing (struct session *sess, const char *nick, const char *state);
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
FE_COLOR_NONE = 0,
|
FE_COLOR_NONE = 0,
|
||||||
|
|||||||
@@ -46,6 +46,155 @@
|
|||||||
#include "servlist.h"
|
#include "servlist.h"
|
||||||
#include "sts.h"
|
#include "sts.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
|
||||||
|
void reply_state_clear (session *sess);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
reply_msgid_valid (const char *msgid)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (!msgid || !*msgid || *msgid == ':')
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
for (p = msgid; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p == ' ' || *p == '\r' || *p == '\n')
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_item_free (reply_item *item)
|
||||||
|
{
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
g_free (item->msgid);
|
||||||
|
g_free (item->nick);
|
||||||
|
g_free (item->text);
|
||||||
|
g_free (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_cache_free (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
g_slist_free_full (sess->reply_items, (GDestroyNotify) reply_item_free);
|
||||||
|
sess->reply_items = NULL;
|
||||||
|
reply_state_clear (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_item *
|
||||||
|
reply_cache_find (session *sess, const char *msgid)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!sess || !reply_msgid_valid (msgid))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (list = sess->reply_items; list; list = list->next)
|
||||||
|
{
|
||||||
|
reply_item *item = list->data;
|
||||||
|
if (!strcmp (item->msgid, msgid))
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_item *
|
||||||
|
reply_cache_latest_from (session *sess, const char *nick)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
|
||||||
|
if (!sess || !nick || !*nick)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (list = sess->reply_items; list; list = list->next)
|
||||||
|
{
|
||||||
|
reply_item *item = list->data;
|
||||||
|
if (!sess->server->p_cmp (item->nick, nick))
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_state_clear (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
g_clear_pointer (&sess->reply_msgid, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_target, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_nick, g_free);
|
||||||
|
g_clear_pointer (&sess->reply_text, g_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
reply_state_set (session *sess, const char *msgid, const char *target, const char *nick, const char *text)
|
||||||
|
{
|
||||||
|
if (!sess || !reply_msgid_valid (msgid))
|
||||||
|
return;
|
||||||
|
reply_state_clear (sess);
|
||||||
|
sess->reply_msgid = g_strdup (msgid);
|
||||||
|
sess->reply_target = g_strdup (target && *target ? target : sess->channel);
|
||||||
|
sess->reply_nick = g_strdup (nick && *nick ? nick : _("message"));
|
||||||
|
sess->reply_text = g_strdup (text && *text ? text : _("Original message unavailable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_cache_add (session *sess, const char *msgid, const char *nick, const char *text, time_t timestamp)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
if (!sess || !reply_msgid_valid (msgid) || !nick || !text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item = reply_cache_find (sess, msgid);
|
||||||
|
if (item)
|
||||||
|
{
|
||||||
|
g_free (item->nick);
|
||||||
|
g_free (item->text);
|
||||||
|
item->nick = g_strdup (nick);
|
||||||
|
item->text = g_strdup (text);
|
||||||
|
item->timestamp = timestamp;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = g_new0 (reply_item, 1);
|
||||||
|
item->msgid = g_strdup (msgid);
|
||||||
|
item->nick = g_strdup (nick);
|
||||||
|
item->text = g_strdup (text);
|
||||||
|
item->timestamp = timestamp;
|
||||||
|
sess->reply_items = g_slist_prepend (sess->reply_items, item);
|
||||||
|
|
||||||
|
if (g_slist_length (sess->reply_items) > 200)
|
||||||
|
{
|
||||||
|
GSList *last = g_slist_last (sess->reply_items);
|
||||||
|
item = last->data;
|
||||||
|
sess->reply_items = g_slist_delete_link (sess->reply_items, last);
|
||||||
|
reply_item_free (item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reply_context_print (session *sess, const message_tags_data *tags_data)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
if (!sess || !tags_data || !tags_data->reply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item = reply_cache_find (sess, tags_data->reply);
|
||||||
|
if (item)
|
||||||
|
PrintTextTimeStampf (sess, tags_data->timestamp, "\00314│ ↪ %s: %.180s\017\n", item->nick, item->text);
|
||||||
|
else
|
||||||
|
PrintTextTimeStamp (sess, "\00314│ ↪ Original message unavailable\017\n", tags_data->timestamp);
|
||||||
|
}
|
||||||
#include "ctcp.h"
|
#include "ctcp.h"
|
||||||
#include "zoitechatc.h"
|
#include "zoitechatc.h"
|
||||||
#include "chanopt.h"
|
#include "chanopt.h"
|
||||||
@@ -450,6 +599,9 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text,
|
|||||||
fromme = TRUE;
|
fromme = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fromme)
|
||||||
|
fe_set_typing (sess, from, "done");
|
||||||
|
|
||||||
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
|
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
|
||||||
|
|
||||||
if (!fromme && !privaction)
|
if (!fromme && !privaction)
|
||||||
@@ -518,12 +670,17 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
fromme = TRUE;
|
fromme = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fromme)
|
||||||
|
fe_set_typing (sess, from, "done");
|
||||||
|
|
||||||
if (fromme)
|
if (fromme)
|
||||||
{
|
{
|
||||||
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
|
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
|
||||||
sess->server->p_set_back (sess->server);
|
sess->server->p_set_back (sess->server);
|
||||||
|
reply_context_print (sess, tags_data);
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
|
||||||
0, tags_data->timestamp);
|
0, tags_data->timestamp);
|
||||||
|
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +689,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
if (is_hilight (from, text, sess, serv))
|
if (is_hilight (from, text, sess, serv))
|
||||||
hilight = TRUE;
|
hilight = TRUE;
|
||||||
|
|
||||||
|
reply_context_print (sess, tags_data);
|
||||||
|
|
||||||
if (sess->type == SESS_DIALOG)
|
if (sess->type == SESS_DIALOG)
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
|
||||||
tags_data->timestamp);
|
tags_data->timestamp);
|
||||||
@@ -541,6 +700,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
|||||||
else
|
else
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
|
||||||
0, tags_data->timestamp);
|
0, tags_data->timestamp);
|
||||||
|
|
||||||
|
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1483,7 +1644,7 @@ inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data)
|
|||||||
{
|
{
|
||||||
sess->server->dcc_ip = addr.s_addr;
|
sess->server->dcc_ip = addr.s_addr;
|
||||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
|
||||||
inet_ntoa (addr),
|
net_ip (ntohl (addr.s_addr)),
|
||||||
NULL, NULL, NULL, 0, tags_data->timestamp);
|
NULL, NULL, NULL, 0, tags_data->timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1654,14 +1815,14 @@ inbound_login_end (session *sess, char *text, const message_tags_data *tags_data
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* send nickserv password */
|
/* send nickserv password */
|
||||||
if (net && net->pass && inbound_nickserv_login (serv))
|
if (net && serv->password[0] && inbound_nickserv_login (serv))
|
||||||
{
|
{
|
||||||
serv->p_ns_identify (serv, net->pass);
|
serv->p_ns_identify (serv, serv->password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait for join if command or nickserv set */
|
/* wait for join if command or nickserv set */
|
||||||
if (net && prefs.hex_irc_join_delay
|
if (net && prefs.hex_irc_join_delay
|
||||||
&& ((net->pass && inbound_nickserv_login (serv))
|
&& ((serv->password[0] && inbound_nickserv_login (serv))
|
||||||
|| net->commandlist))
|
|| net->commandlist))
|
||||||
{
|
{
|
||||||
serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv);
|
serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv);
|
||||||
@@ -1742,6 +1903,10 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
|
|||||||
serv->have_awaynotify = enable;
|
serv->have_awaynotify = enable;
|
||||||
else if (!strcmp (extension, "account-tag"))
|
else if (!strcmp (extension, "account-tag"))
|
||||||
serv->have_account_tag = enable;
|
serv->have_account_tag = enable;
|
||||||
|
else if (!strcmp (extension, "message-tags"))
|
||||||
|
serv->have_message_tags = enable;
|
||||||
|
else if (!strcmp (extension, "echo-message"))
|
||||||
|
serv->have_echo_message = enable;
|
||||||
else if (!strcmp (extension, "sasl"))
|
else if (!strcmp (extension, "sasl"))
|
||||||
{
|
{
|
||||||
serv->have_sasl = enable;
|
serv->have_sasl = enable;
|
||||||
@@ -1873,6 +2038,8 @@ static const char * const supported_caps[] = {
|
|||||||
"account-tag",
|
"account-tag",
|
||||||
"extended-monitor",
|
"extended-monitor",
|
||||||
"standard-replies",
|
"standard-replies",
|
||||||
|
"message-tags",
|
||||||
|
"echo-message",
|
||||||
|
|
||||||
/* ZNC */
|
/* ZNC */
|
||||||
"znc.in/server-time-iso",
|
"znc.in/server-time-iso",
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ void inbound_login_end (session *sess, char *text,
|
|||||||
void inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
void inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
||||||
char *text, char fromme, int id,
|
char *text, char fromme, int id,
|
||||||
const message_tags_data *tags_data);
|
const message_tags_data *tags_data);
|
||||||
|
gboolean reply_msgid_valid (const char *msgid);
|
||||||
|
void reply_cache_free (session *sess);
|
||||||
|
reply_item *reply_cache_find (session *sess, const char *msgid);
|
||||||
|
reply_item *reply_cache_latest_from (session *sess, const char *nick);
|
||||||
|
void reply_state_set (session *sess, const char *msgid, const char *target,
|
||||||
|
const char *nick, const char *text);
|
||||||
|
void reply_state_clear (session *sess);
|
||||||
void clear_channel (session *sess);
|
void clear_channel (session *sess);
|
||||||
void set_topic (session *sess, char *topic, char *stripped_topic);
|
void set_topic (session *sess, char *topic, char *stripped_topic);
|
||||||
void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
|
void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
|
||||||
|
|||||||
@@ -907,6 +907,10 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
|
|||||||
{
|
{
|
||||||
if (g_strcmp0 (tokvalue, "ascii") == 0)
|
if (g_strcmp0 (tokvalue, "ascii") == 0)
|
||||||
serv->p_cmp = (void *)g_ascii_strcasecmp;
|
serv->p_cmp = (void *)g_ascii_strcasecmp;
|
||||||
|
} else if (g_strcmp0 (tokname, "CLIENTTAGDENY") == 0)
|
||||||
|
{
|
||||||
|
g_free (serv->clienttagdeny);
|
||||||
|
serv->clienttagdeny = tokadding ? g_strdup (tokvalue) : NULL;
|
||||||
} else if (g_strcmp0 (tokname, "CHARSET") == 0)
|
} else if (g_strcmp0 (tokname, "CHARSET") == 0)
|
||||||
{
|
{
|
||||||
if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
|
if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
|
||||||
|
|||||||
@@ -90,10 +90,26 @@ net_set_socket_options (int sok)
|
|||||||
char *
|
char *
|
||||||
net_ip (uint32_t addr)
|
net_ip (uint32_t addr)
|
||||||
{
|
{
|
||||||
|
static char buf[INET_ADDRSTRLEN];
|
||||||
struct in_addr ia;
|
struct in_addr ia;
|
||||||
|
|
||||||
ia.s_addr = htonl (addr);
|
ia.s_addr = htonl (addr);
|
||||||
return inet_ntoa (ia);
|
if (!inet_ntop (AF_INET, &ia, buf, sizeof (buf)))
|
||||||
|
buf[0] = 0;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
net_parse_ipv4 (const char *hostname, uint32_t *addr)
|
||||||
|
{
|
||||||
|
struct in_addr ia;
|
||||||
|
|
||||||
|
if (inet_pton (AF_INET, hostname, &ia) != 1)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
*addr = ia.s_addr;
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ int net_connect (netstore *ns, int sok4, int sok6, int *sok_return);
|
|||||||
char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
|
char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
|
||||||
void net_bind (netstore *tobindto, int sok4, int sok6);
|
void net_bind (netstore *tobindto, int sok4, int sok6);
|
||||||
char *net_ip (uint32_t addr);
|
char *net_ip (uint32_t addr);
|
||||||
|
int net_parse_ipv4 (const char *hostname, uint32_t *addr);
|
||||||
int net_lookup_ipv4 (const char *hostname, uint32_t *addr);
|
int net_lookup_ipv4 (const char *hostname, uint32_t *addr);
|
||||||
void net_sockets (int *sok4, int *sok6);
|
void net_sockets (int *sok4, int *sok6);
|
||||||
|
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
|
|||||||
type = prefs.hex_irc_ban_type;
|
type = prefs.hex_irc_ban_type;
|
||||||
|
|
||||||
buf[0] = 0;
|
buf[0] = 0;
|
||||||
if (inet_addr (fullhost) != (guint32) -1) /* "fullhost" is really a IP number */
|
if (net_parse_ipv4 (fullhost, &(guint32){0})) /* "fullhost" is really a IP number */
|
||||||
{
|
{
|
||||||
lastdot = strrchr (fullhost, '.');
|
lastdot = strrchr (fullhost, '.');
|
||||||
if (!lastdot)
|
if (!lastdot)
|
||||||
@@ -2744,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;
|
||||||
|
|
||||||
@@ -4139,9 +4270,11 @@ const struct commands xc_cmds[] = {
|
|||||||
#endif
|
#endif
|
||||||
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
|
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
|
||||||
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
|
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
|
||||||
|
{"REPLY", cmd_reply, 0, 0, 1, N_("REPLY <msgid> <message>, sends a reply-tagged message to the current channel or dialog")},
|
||||||
{"SAY", cmd_say, 0, 0, 1,
|
{"SAY", cmd_say, 0, 0, 1,
|
||||||
N_("SAY <text>, sends the text to the object in the current window")},
|
N_("SAY <text>, sends the text to the object in the current window")},
|
||||||
{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
|
{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
|
||||||
|
{"TYPING", cmd_typing, 0, 0, 1, N_("TYPING [active|paused|done] [target], sends a typing notification")},
|
||||||
#ifdef USE_OPENSSL
|
#ifdef USE_OPENSSL
|
||||||
{"SERVCHAN", cmd_servchan, 0, 0, 1,
|
{"SERVCHAN", cmd_servchan, 0, 0, 1,
|
||||||
N_("SERVCHAN [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
|
N_("SERVCHAN [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
|
||||||
@@ -4674,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)
|
||||||
@@ -4684,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
|
||||||
@@ -4733,9 +4868,9 @@ command_insert_vars (session *sess, char *cmd)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (mynet->pass)
|
if (sess->server->password[0])
|
||||||
{
|
{
|
||||||
g_string_append (expanded, mynet->pass);
|
g_string_append (expanded, sess->server->password);
|
||||||
}
|
}
|
||||||
cmd++;
|
cmd++;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -362,6 +362,18 @@ irc_message (server *serv, char *channel, char *text)
|
|||||||
tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
|
tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_message_tagged (server *serv, char *tags, char *channel, char *text)
|
||||||
|
{
|
||||||
|
tcp_sendf (serv, "@%s PRIVMSG %s :%s\r\n", tags, channel, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
irc_tagmsg (server *serv, char *tags, char *target)
|
||||||
|
{
|
||||||
|
tcp_sendf (serv, "@%s TAGMSG %s\r\n", tags, target);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
irc_action (server *serv, char *channel, char *act)
|
irc_action (server *serv, char *channel, char *act)
|
||||||
{
|
{
|
||||||
@@ -425,7 +437,7 @@ static int
|
|||||||
irc_raw (server *serv, char *raw)
|
irc_raw (server *serv, char *raw)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
char tbuf[4096];
|
char tbuf[8704];
|
||||||
if (*raw)
|
if (*raw)
|
||||||
{
|
{
|
||||||
len = strlen (raw);
|
len = strlen (raw);
|
||||||
@@ -1009,6 +1021,40 @@ process_numeric (session * sess, int n,
|
|||||||
|
|
||||||
/* handle named messages that starts with a ':' */
|
/* handle named messages that starts with a ':' */
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
emit_standard_reply (session *sess, const char *type, char *command, char *code, char *text, const message_tags_data *tags_data)
|
||||||
|
{
|
||||||
|
int is_global = g_strcmp0 (command, "*") == 0;
|
||||||
|
|
||||||
|
if (!text)
|
||||||
|
text = "";
|
||||||
|
if (*text == ':')
|
||||||
|
text++;
|
||||||
|
|
||||||
|
if (!strcmp (type, "FAIL"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
else if (!strcmp (type, "WARN"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
else if (!strcmp (type, "NOTE"))
|
||||||
|
{
|
||||||
|
if (is_global)
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||||
|
else
|
||||||
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
||||||
const message_tags_data *tags_data)
|
const message_tags_data *tags_data)
|
||||||
@@ -1322,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);
|
||||||
|
|||||||
@@ -540,6 +540,9 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
|
|||||||
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
||||||
X509_verify_cert_error_string (err), err);
|
X509_verify_cert_error_string (err), err);
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
||||||
|
|
||||||
|
if (g_sess && g_sess->server->accept_invalid_cert)
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
@@ -663,11 +666,15 @@ ssl_do_connect (server * serv)
|
|||||||
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
||||||
hostname_err);
|
hostname_err);
|
||||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
|
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
|
||||||
goto conn_fail;
|
if (!serv->accept_invalid_cert)
|
||||||
|
goto conn_fail;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
if (serv->accept_invalid_cert)
|
||||||
|
break;
|
||||||
|
|
||||||
g_snprintf (buf, sizeof (buf), "%s.? (%d)",
|
g_snprintf (buf, sizeof (buf), "%s.? (%d)",
|
||||||
X509_verify_cert_error_string (verify_error),
|
X509_verify_cert_error_string (verify_error),
|
||||||
verify_error);
|
verify_error);
|
||||||
@@ -925,7 +932,7 @@ server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
|
|||||||
break;
|
break;
|
||||||
case '5': /* prefs ip discovered */
|
case '5': /* prefs ip discovered */
|
||||||
waitline2 (source, tbuf, sizeof tbuf);
|
waitline2 (source, tbuf, sizeof tbuf);
|
||||||
prefs.local_ip = inet_addr (tbuf);
|
net_parse_ipv4 (tbuf, &prefs.local_ip);
|
||||||
break;
|
break;
|
||||||
case '7': /* prefs.hex_net_bind_host resolve failed */
|
case '7': /* prefs.hex_net_bind_host resolve failed */
|
||||||
sprintf (outbuf,
|
sprintf (outbuf,
|
||||||
@@ -1099,7 +1106,7 @@ traverse_socks (int print_fd, int sok, char *serverAddr, int port)
|
|||||||
sc.version = 4;
|
sc.version = 4;
|
||||||
sc.type = 1;
|
sc.type = 1;
|
||||||
sc.port = htons (port);
|
sc.port = htons (port);
|
||||||
sc.address = inet_addr (serverAddr);
|
net_parse_ipv4 (serverAddr, &sc.address);
|
||||||
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
|
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
|
||||||
|
|
||||||
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
|
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
|
||||||
@@ -1814,6 +1821,7 @@ void
|
|||||||
server_set_defaults (server *serv)
|
server_set_defaults (server *serv)
|
||||||
{
|
{
|
||||||
g_free (serv->chantypes);
|
g_free (serv->chantypes);
|
||||||
|
g_clear_pointer (&serv->clienttagdeny, g_free);
|
||||||
g_free (serv->chanmodes);
|
g_free (serv->chanmodes);
|
||||||
g_free (serv->nick_prefixes);
|
g_free (serv->nick_prefixes);
|
||||||
g_free (serv->nick_modes);
|
g_free (serv->nick_modes);
|
||||||
@@ -1848,6 +1856,8 @@ server_set_defaults (server *serv)
|
|||||||
serv->have_extjoin = FALSE;
|
serv->have_extjoin = FALSE;
|
||||||
serv->have_account_tag = FALSE;
|
serv->have_account_tag = FALSE;
|
||||||
serv->have_server_time = FALSE;
|
serv->have_server_time = FALSE;
|
||||||
|
serv->have_message_tags = FALSE;
|
||||||
|
serv->have_echo_message = FALSE;
|
||||||
serv->have_sasl = FALSE;
|
serv->have_sasl = FALSE;
|
||||||
serv->have_except = FALSE;
|
serv->have_except = FALSE;
|
||||||
serv->have_invite = FALSE;
|
serv->have_invite = FALSE;
|
||||||
@@ -1978,6 +1988,7 @@ server_free (server *serv)
|
|||||||
g_free (serv->nick_prefixes);
|
g_free (serv->nick_prefixes);
|
||||||
g_free (serv->chanmodes);
|
g_free (serv->chanmodes);
|
||||||
g_free (serv->chantypes);
|
g_free (serv->chantypes);
|
||||||
|
g_free (serv->clienttagdeny);
|
||||||
g_free (serv->bad_nick_prefixes);
|
g_free (serv->bad_nick_prefixes);
|
||||||
g_free (serv->last_away_reason);
|
g_free (serv->last_away_reason);
|
||||||
g_free (serv->encoding);
|
g_free (serv->encoding);
|
||||||
|
|||||||
@@ -154,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];
|
||||||
@@ -350,14 +350,13 @@ _SSL_close (SSL * ssl)
|
|||||||
{
|
{
|
||||||
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
||||||
SSL_free (ssl);
|
SSL_free (ssl);
|
||||||
|
#if !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. */
|
||||||
@@ -508,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;
|
||||||
|
|
||||||
@@ -518,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;
|
||||||
@@ -540,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
@@ -31,6 +31,7 @@ struct User
|
|||||||
char *servername;
|
char *servername;
|
||||||
char *account;
|
char *account;
|
||||||
time_t lasttalk;
|
time_t lasttalk;
|
||||||
|
time_t typing_time;
|
||||||
unsigned int access; /* axs bit field */
|
unsigned int access; /* axs bit field */
|
||||||
char prefix[2]; /* @ + % */
|
char prefix[2]; /* @ + % */
|
||||||
unsigned int op:1;
|
unsigned int op:1;
|
||||||
@@ -39,6 +40,7 @@ struct User
|
|||||||
unsigned int me:1;
|
unsigned int me:1;
|
||||||
unsigned int away:1;
|
unsigned int away:1;
|
||||||
unsigned int selected:1;
|
unsigned int selected:1;
|
||||||
|
unsigned int typing:2;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define USERACCESS_SIZE 12
|
#define USERACCESS_SIZE 12
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ lag_check (void)
|
|||||||
time_t now = time (0);
|
time_t now = time (0);
|
||||||
time_t lag;
|
time_t lag;
|
||||||
time_t ping_age;
|
time_t ping_age;
|
||||||
|
unsigned long ping_timeout;
|
||||||
|
|
||||||
tim = make_ping_time ();
|
tim = make_ping_time ();
|
||||||
|
|
||||||
@@ -389,8 +390,11 @@ lag_check (void)
|
|||||||
if (serv->connected && serv->end_of_motd)
|
if (serv->connected && serv->end_of_motd)
|
||||||
{
|
{
|
||||||
lag = now - serv->ping_recv;
|
lag = now - serv->ping_recv;
|
||||||
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0 && lag > prefs.hex_net_ping_timeout && lag > 0)
|
ping_timeout = (unsigned long) prefs.hex_net_ping_timeout * 1000;
|
||||||
|
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0
|
||||||
|
&& tim - serv->lag_sent > ping_timeout)
|
||||||
{
|
{
|
||||||
|
lag = (tim - serv->lag_sent) / 1000;
|
||||||
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
|
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
|
||||||
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
|
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
|
||||||
NULL, NULL, 0);
|
NULL, NULL, 0);
|
||||||
@@ -793,7 +797,13 @@ session_free (session *killsess)
|
|||||||
|
|
||||||
send_quit_or_part (killsess);
|
send_quit_or_part (killsess);
|
||||||
|
|
||||||
|
if (killsess->typing_timeout_tag)
|
||||||
|
fe_timeout_remove (killsess->typing_timeout_tag);
|
||||||
|
if (killsess->typing_animation_tag)
|
||||||
|
fe_timeout_remove (killsess->typing_animation_tag);
|
||||||
|
|
||||||
history_free (&killsess->history);
|
history_free (&killsess->history);
|
||||||
|
reply_cache_free (killsess);
|
||||||
g_free (killsess->topic);
|
g_free (killsess->topic);
|
||||||
g_free (killsess->current_modes);
|
g_free (killsess->current_modes);
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_gui_input_style;
|
unsigned int hex_gui_input_style;
|
||||||
unsigned int hex_gui_join_dialog;
|
unsigned int hex_gui_join_dialog;
|
||||||
unsigned int hex_gui_mode_buttons;
|
unsigned int hex_gui_mode_buttons;
|
||||||
|
unsigned int hex_gui_mode_buttons_inline;
|
||||||
unsigned int hex_gui_quit_dialog;
|
unsigned int hex_gui_quit_dialog;
|
||||||
/* unsigned int hex_gui_single; */
|
/* unsigned int hex_gui_single; */
|
||||||
unsigned int hex_gui_slist_fav;
|
unsigned int hex_gui_slist_fav;
|
||||||
@@ -148,6 +149,7 @@ struct zoitechatprefs
|
|||||||
unsigned int hex_gui_tab_sort;
|
unsigned int hex_gui_tab_sort;
|
||||||
unsigned int hex_gui_tab_utils;
|
unsigned int hex_gui_tab_utils;
|
||||||
unsigned int hex_gui_topicbar;
|
unsigned int hex_gui_topicbar;
|
||||||
|
unsigned int hex_gui_topicbar_multiline;
|
||||||
unsigned int hex_gui_tray;
|
unsigned int hex_gui_tray;
|
||||||
unsigned int hex_gui_tray_away;
|
unsigned int hex_gui_tray_away;
|
||||||
unsigned int hex_gui_tray_blink;
|
unsigned int hex_gui_tray_blink;
|
||||||
@@ -386,6 +388,14 @@ typedef enum {
|
|||||||
TAB_STATE_NEW_HILIGHT = (1 << 2),
|
TAB_STATE_NEW_HILIGHT = (1 << 2),
|
||||||
} tab_state_flags;
|
} tab_state_flags;
|
||||||
|
|
||||||
|
typedef struct reply_item
|
||||||
|
{
|
||||||
|
char *msgid;
|
||||||
|
char *nick;
|
||||||
|
char *text;
|
||||||
|
time_t timestamp;
|
||||||
|
} reply_item;
|
||||||
|
|
||||||
typedef struct session
|
typedef struct session
|
||||||
{
|
{
|
||||||
/* Per-Channel Alerts */
|
/* Per-Channel Alerts */
|
||||||
@@ -427,8 +437,17 @@ typedef struct session
|
|||||||
char *quitreason;
|
char *quitreason;
|
||||||
char *topic;
|
char *topic;
|
||||||
char *current_modes; /* free() me */
|
char *current_modes; /* free() me */
|
||||||
|
GSList *reply_items;
|
||||||
|
char *reply_msgid;
|
||||||
|
char *reply_target;
|
||||||
|
char *reply_nick;
|
||||||
|
char *reply_text;
|
||||||
|
|
||||||
int mode_timeout_tag;
|
int mode_timeout_tag;
|
||||||
|
int typing_timeout_tag;
|
||||||
|
int typing_status;
|
||||||
|
int typing_animation_tag;
|
||||||
|
int typing_animation_frame;
|
||||||
|
|
||||||
struct session *lastlog_sess;
|
struct session *lastlog_sess;
|
||||||
struct nbexec *running_exec;
|
struct nbexec *running_exec;
|
||||||
@@ -492,6 +511,8 @@ typedef struct server
|
|||||||
void (*p_set_back)(struct server *);
|
void (*p_set_back)(struct server *);
|
||||||
void (*p_set_away)(struct server *, char *reason);
|
void (*p_set_away)(struct server *, char *reason);
|
||||||
void (*p_message)(struct server *, char *channel, char *text);
|
void (*p_message)(struct server *, char *channel, char *text);
|
||||||
|
void (*p_message_tagged)(struct server *, char *tags, char *channel, char *text);
|
||||||
|
void (*p_tagmsg)(struct server *, char *tags, char *target);
|
||||||
void (*p_action)(struct server *, char *channel, char *act);
|
void (*p_action)(struct server *, char *channel, char *act);
|
||||||
void (*p_notice)(struct server *, char *channel, char *text);
|
void (*p_notice)(struct server *, char *channel, char *text);
|
||||||
void (*p_topic)(struct server *, char *channel, char *topic);
|
void (*p_topic)(struct server *, char *channel, char *topic);
|
||||||
@@ -541,6 +562,7 @@ typedef struct server
|
|||||||
int loginmethod; /* see login_types[] */
|
int loginmethod; /* see login_types[] */
|
||||||
|
|
||||||
char *chantypes; /* for 005 numeric - free me */
|
char *chantypes; /* for 005 numeric - free me */
|
||||||
|
char *clienttagdeny;
|
||||||
char *chanmodes; /* for 005 numeric - free me */
|
char *chanmodes; /* for 005 numeric - free me */
|
||||||
char *nick_prefixes; /* e.g. "*@%+" */
|
char *nick_prefixes; /* e.g. "*@%+" */
|
||||||
char *nick_modes; /* e.g. "aohv" */
|
char *nick_modes; /* e.g. "aohv" */
|
||||||
@@ -603,6 +625,8 @@ typedef struct server
|
|||||||
unsigned int have_extjoin:1; /* cap extended-join */
|
unsigned int have_extjoin:1; /* cap extended-join */
|
||||||
unsigned int have_account_tag:1; /* cap account-tag */
|
unsigned int have_account_tag:1; /* cap account-tag */
|
||||||
unsigned int have_server_time:1; /* cap server-time */
|
unsigned int have_server_time:1; /* cap server-time */
|
||||||
|
unsigned int have_message_tags:1;
|
||||||
|
unsigned int have_echo_message:1;
|
||||||
unsigned int have_sasl:1; /* SASL capability */
|
unsigned int have_sasl:1; /* SASL capability */
|
||||||
unsigned int have_except:1; /* ban exemptions +e */
|
unsigned int have_except:1; /* ban exemptions +e */
|
||||||
unsigned int have_invite:1; /* invite exemptions +I */
|
unsigned int have_invite:1; /* invite exemptions +I */
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ struct _chanview
|
|||||||
guint theme_listener_id;
|
guint theme_listener_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc);
|
||||||
|
|
||||||
struct _chan
|
struct _chan
|
||||||
{
|
{
|
||||||
chanview *cv; /* our owner */
|
chanview *cv; /* our owner */
|
||||||
@@ -169,6 +171,7 @@ chanview_apply_theme (chanview *cv)
|
|||||||
w = GTK_WIDGET (tv->tree);
|
w = GTK_WIDGET (tv->tree);
|
||||||
if (input_style)
|
if (input_style)
|
||||||
font = input_style->font_desc;
|
font = input_style->font_desc;
|
||||||
|
chanview_set_font_desc (cv, font);
|
||||||
|
|
||||||
theme_manager_apply_channel_tree_style (w,
|
theme_manager_apply_channel_tree_style (w,
|
||||||
theme_manager_get_channel_tree_palette_behavior (font));
|
theme_manager_get_channel_tree_palette_behavior (font));
|
||||||
@@ -313,6 +316,14 @@ chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
|||||||
g_free (ch);
|
g_free (ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
chanview_set_font_desc (chanview *cv, const PangoFontDescription *font_desc)
|
||||||
|
{
|
||||||
|
if (cv->font_desc)
|
||||||
|
pango_font_description_free (cv->font_desc);
|
||||||
|
cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */
|
chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */
|
||||||
{
|
{
|
||||||
@@ -335,6 +346,9 @@ chanview_destroy (chanview *cv)
|
|||||||
if (cv->box)
|
if (cv->box)
|
||||||
gtk_widget_destroy (cv->box);
|
gtk_widget_destroy (cv->box);
|
||||||
|
|
||||||
|
if (cv->font_desc)
|
||||||
|
pango_font_description_free (cv->font_desc);
|
||||||
|
|
||||||
chanview_destroy_store (cv);
|
chanview_destroy_store (cv);
|
||||||
g_free (cv);
|
g_free (cv);
|
||||||
}
|
}
|
||||||
@@ -356,7 +370,7 @@ chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons,
|
|||||||
cv = g_new0 (chanview, 1);
|
cv = g_new0 (chanview, 1);
|
||||||
cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER,
|
cv->store = gtk_tree_store_new (5, G_TYPE_STRING, G_TYPE_POINTER,
|
||||||
PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT);
|
PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF, G_TYPE_INT);
|
||||||
cv->font_desc = font_desc;
|
cv->font_desc = font_desc ? pango_font_description_copy (font_desc) : NULL;
|
||||||
cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
cv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||||
cv->trunc_len = trunc_len;
|
cv->trunc_len = trunc_len;
|
||||||
cv->sorted = sort;
|
cv->sorted = sort;
|
||||||
|
|||||||
@@ -812,18 +812,19 @@ fe_set_topic (session *sess, char *topic, char *stripped_topic)
|
|||||||
{
|
{
|
||||||
if (!sess->gui->is_tab || sess == current_tab)
|
if (!sess->gui->is_tab || sess == current_tab)
|
||||||
{
|
{
|
||||||
|
GtkTextBuffer *topic_buffer;
|
||||||
|
GtkTextIter start;
|
||||||
|
|
||||||
|
topic_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry));
|
||||||
if (prefs.hex_text_stripcolor_topic)
|
if (prefs.hex_text_stripcolor_topic)
|
||||||
{
|
gtk_text_buffer_set_text (topic_buffer, stripped_topic, -1);
|
||||||
gtk_text_buffer_set_text (
|
|
||||||
gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry)),
|
|
||||||
stripped_topic, -1);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
gtk_text_buffer_set_text (topic_buffer, topic, -1);
|
||||||
gtk_text_buffer_set_text (
|
|
||||||
gtk_text_view_get_buffer (GTK_TEXT_VIEW (sess->gui->topic_entry)),
|
gtk_text_buffer_get_start_iter (topic_buffer, &start);
|
||||||
topic, -1);
|
gtk_text_buffer_place_cursor (topic_buffer, &start);
|
||||||
}
|
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (sess->gui->topic_entry),
|
||||||
|
&start, 0.0, FALSE, 0.0, 0.0);
|
||||||
mg_set_topic_tip (sess);
|
mg_set_topic_tip (sess);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1356,17 +1357,19 @@ fe_open_url_inner (const char *url)
|
|||||||
gboolean opened = FALSE;
|
gboolean opened = FALSE;
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
|
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (url_utf16 != NULL)
|
||||||
|
{
|
||||||
|
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
||||||
|
g_free (url_utf16);
|
||||||
|
}
|
||||||
|
|
||||||
if (!opened)
|
if (!opened)
|
||||||
{
|
{
|
||||||
g_clear_error (&error);
|
opened = g_app_info_launch_default_for_uri (escaped_url, NULL, &error);
|
||||||
gunichar2 *url_utf16 = g_utf8_to_utf16 (escaped_url, -1, NULL, NULL, NULL);
|
if (!opened)
|
||||||
|
g_clear_error (&error);
|
||||||
if (url_utf16 != NULL)
|
|
||||||
{
|
|
||||||
opened = ((INT_PTR) ShellExecuteW (0, L"open", url_utf16, NULL, NULL, SW_SHOWNORMAL)) > 32;
|
|
||||||
g_free (url_utf16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -67,6 +67,34 @@
|
|||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <gdk/gdkwin32.h>
|
#include <gdk/gdkwin32.h>
|
||||||
|
|
||||||
|
/* The shell only minimizes a window from its taskbar button when the
|
||||||
|
* window style carries WS_MINIMIZEBOX; without it the click merely
|
||||||
|
* deactivates the window (issue #65). GTK3 older than 3.24.50 can drop
|
||||||
|
* the bit from top-level windows (https://gitlab.gnome.org/GNOME/gtk/-/issues/7494),
|
||||||
|
* so guarantee it ourselves for session windows. */
|
||||||
|
static void
|
||||||
|
mg_win32_ensure_minimizable (GdkWindow *gdk_window)
|
||||||
|
{
|
||||||
|
HWND hwnd;
|
||||||
|
LONG_PTR window_style;
|
||||||
|
|
||||||
|
if (!gdk_window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hwnd = gdk_win32_window_get_handle (gdk_window);
|
||||||
|
if (!hwnd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window_style = GetWindowLongPtr (hwnd, GWL_STYLE);
|
||||||
|
if (!window_style || (window_style & WS_MINIMIZEBOX))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetWindowLongPtr (hwnd, GWL_STYLE, window_style | WS_MINIMIZEBOX);
|
||||||
|
SetWindowPos (hwnd, NULL, 0, 0, 0, 0,
|
||||||
|
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE |
|
||||||
|
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mg_win32_allow_autohide_taskbar (GtkWindow *window, GdkEventWindowState *event)
|
mg_win32_allow_autohide_taskbar (GtkWindow *window, GdkEventWindowState *event)
|
||||||
{
|
{
|
||||||
@@ -173,6 +201,7 @@ enum
|
|||||||
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
||||||
|
|
||||||
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
||||||
|
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
||||||
|
|
||||||
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
||||||
|
|
||||||
@@ -731,6 +760,164 @@ mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
mg_client_tag_allowed (server *serv, const char *tag)
|
||||||
|
{
|
||||||
|
char **deny;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!serv->have_message_tags)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!serv->clienttagdeny || !*serv->clienttagdeny)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
deny = g_strsplit (serv->clienttagdeny, ",", 0);
|
||||||
|
for (i = 0; deny[i]; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
|
||||||
|
{
|
||||||
|
g_strfreev (deny);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev (deny);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_send_typing (session *sess, const char *state)
|
||||||
|
{
|
||||||
|
char tags[32];
|
||||||
|
|
||||||
|
if (!sess || !sess->server->connected || !mg_client_tag_allowed (sess->server, "typing") || !sess->channel[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->type != SESS_CHANNEL && sess->type != SESS_DIALOG)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
|
||||||
|
sess->server->p_tagmsg (sess->server, tags, sess->channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mg_typing_pause_cb (session *sess)
|
||||||
|
{
|
||||||
|
sess->typing_timeout_tag = 0;
|
||||||
|
if (sess->typing_status == 1)
|
||||||
|
{
|
||||||
|
mg_send_typing (sess, "paused");
|
||||||
|
sess->typing_status = 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_typing_update (session *sess, const char *text)
|
||||||
|
{
|
||||||
|
if (!sess)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->typing_timeout_tag)
|
||||||
|
{
|
||||||
|
fe_timeout_remove (sess->typing_timeout_tag);
|
||||||
|
sess->typing_timeout_tag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text || !*text || text[0] == prefs.hex_input_command_char[0])
|
||||||
|
{
|
||||||
|
if (sess->typing_status)
|
||||||
|
mg_send_typing (sess, "done");
|
||||||
|
sess->typing_status = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sess->typing_status != 1)
|
||||||
|
{
|
||||||
|
mg_send_typing (sess, "active");
|
||||||
|
sess->typing_status = 1;
|
||||||
|
}
|
||||||
|
sess->typing_timeout_tag = fe_timeout_add_seconds (6, mg_typing_pause_cb, sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_reply_show_child (GtkWidget *widget, gpointer data)
|
||||||
|
{
|
||||||
|
gtk_widget_show (widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mg_reply_update (session *sess)
|
||||||
|
{
|
||||||
|
char *nick;
|
||||||
|
char *text;
|
||||||
|
char *markup;
|
||||||
|
|
||||||
|
if (!sess || !sess->gui || !sess->gui->reply_box || !sess->gui->reply_label)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!sess->reply_msgid)
|
||||||
|
{
|
||||||
|
gtk_widget_hide (sess->gui->reply_box);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nick = g_markup_escape_text (sess->reply_nick ? sess->reply_nick : _("message"), -1);
|
||||||
|
text = g_markup_escape_text (sess->reply_text ? sess->reply_text : _("Original message unavailable"), -1);
|
||||||
|
markup = g_strdup_printf ("<span foreground='#7d8790'>↪ Replying to <b>%s</b> · %.160s</span>", nick, text);
|
||||||
|
gtk_label_set_markup (GTK_LABEL (sess->gui->reply_label), markup);
|
||||||
|
gtk_container_foreach (GTK_CONTAINER (sess->gui->reply_box), mg_reply_show_child, NULL);
|
||||||
|
gtk_widget_show (sess->gui->reply_box);
|
||||||
|
g_free (markup);
|
||||||
|
g_free (text);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_reply_cancel_cb (GtkWidget *wid, session *sess)
|
||||||
|
{
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_send_reply_or_text (session *sess, char *cmd)
|
||||||
|
{
|
||||||
|
char *reply_cmd;
|
||||||
|
|
||||||
|
if (!sess->reply_msgid || cmd[0] == prefs.hex_input_command_char[0])
|
||||||
|
{
|
||||||
|
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sess->server->connected || !mg_client_tag_allowed (sess->server, "reply"))
|
||||||
|
{
|
||||||
|
PrintText (sess, _("Replies are not supported on this server. Sending normally.\n"));
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_cmd = g_strdup_printf ("%cREPLY %s %s", prefs.hex_input_command_char[0], sess->reply_msgid, cmd);
|
||||||
|
handle_multiline (sess, reply_cmd, TRUE, FALSE);
|
||||||
|
g_free (reply_cmd);
|
||||||
|
reply_state_clear (sess);
|
||||||
|
mg_reply_update (sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_inputbox_changed (GtkEditable *editable, session_gui *gui)
|
||||||
|
{
|
||||||
|
key_check_replace_on_change (editable, NULL);
|
||||||
|
if (current_sess && current_sess->gui == gui)
|
||||||
|
mg_typing_update (current_sess, gtk_entry_get_text (GTK_ENTRY (editable)));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
||||||
{
|
{
|
||||||
@@ -772,7 +959,7 @@ mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sess)
|
if (sess)
|
||||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
mg_send_reply_or_text (sess, cmd);
|
||||||
|
|
||||||
g_free (cmd);
|
g_free (cmd);
|
||||||
}
|
}
|
||||||
@@ -3042,7 +3229,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gui->key_entry = gtk_entry_new ();
|
gui->key_entry = gtk_entry_new ();
|
||||||
gtk_widget_set_name (gui->key_entry, "zoitechat-inputbox");
|
gtk_widget_set_name (gui->key_entry, "zoitechat-inputbox");
|
||||||
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
||||||
gtk_widget_set_size_request (gui->key_entry, 115, 11);
|
gtk_widget_set_size_request (gui->key_entry, 58, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->key_entry);
|
mg_apply_emoji_fallback_widget (gui->key_entry);
|
||||||
mg_apply_compact_mode_css (gui->key_entry);
|
mg_apply_compact_mode_css (gui->key_entry);
|
||||||
@@ -3059,7 +3246,8 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gui->limit_entry = gtk_entry_new ();
|
gui->limit_entry = gtk_entry_new ();
|
||||||
gtk_widget_set_name (gui->limit_entry, "zoitechat-inputbox");
|
gtk_widget_set_name (gui->limit_entry, "zoitechat-inputbox");
|
||||||
gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
|
gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10);
|
||||||
gtk_widget_set_size_request (gui->limit_entry, 30, 11);
|
gtk_entry_set_width_chars (GTK_ENTRY (gui->limit_entry), 5);
|
||||||
|
gtk_widget_set_size_request (gui->limit_entry, 45, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->limit_entry);
|
mg_apply_emoji_fallback_widget (gui->limit_entry);
|
||||||
mg_apply_compact_mode_css (gui->limit_entry);
|
mg_apply_compact_mode_css (gui->limit_entry);
|
||||||
@@ -3193,8 +3381,11 @@ mg_topicbar_update_height (GtkWidget *topic)
|
|||||||
width -= margin_left + margin_right;
|
width -= margin_left + margin_right;
|
||||||
if (width < 1)
|
if (width < 1)
|
||||||
width = 1;
|
width = 1;
|
||||||
pango_layout_set_width (layout, width * PANGO_SCALE);
|
if (prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline)
|
||||||
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
|
{
|
||||||
|
pango_layout_set_width (layout, width * PANGO_SCALE);
|
||||||
|
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
context = gtk_widget_get_pango_context (topic);
|
context = gtk_widget_get_pango_context (topic);
|
||||||
metrics = pango_context_get_metrics (context,
|
metrics = pango_context_get_metrics (context,
|
||||||
@@ -3206,7 +3397,8 @@ mg_topicbar_update_height (GtkWidget *topic)
|
|||||||
if (line_height <= 0)
|
if (line_height <= 0)
|
||||||
line_height = 16;
|
line_height = 16;
|
||||||
|
|
||||||
line_count = pango_layout_get_line_count (layout);
|
line_count = prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline ?
|
||||||
|
pango_layout_get_line_count (layout) : 1;
|
||||||
if (line_count <= 0)
|
if (line_count <= 0)
|
||||||
line_count = 1;
|
line_count = 1;
|
||||||
|
|
||||||
@@ -3224,10 +3416,19 @@ mg_topicbar_update_height (GtkWidget *topic)
|
|||||||
|
|
||||||
if (parent && GTK_IS_SCROLLED_WINDOW (parent))
|
if (parent && GTK_IS_SCROLLED_WINDOW (parent))
|
||||||
{
|
{
|
||||||
gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (parent), -1);
|
GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW (parent);
|
||||||
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (parent), -1);
|
int max_height = gtk_scrolled_window_get_max_content_height (scrolled);
|
||||||
gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (parent), target_height);
|
|
||||||
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (parent), target_height);
|
if (max_height != -1 && target_height > max_height)
|
||||||
|
{
|
||||||
|
gtk_scrolled_window_set_max_content_height (scrolled, target_height);
|
||||||
|
gtk_scrolled_window_set_min_content_height (scrolled, target_height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_scrolled_window_set_min_content_height (scrolled, target_height);
|
||||||
|
gtk_scrolled_window_set_max_content_height (scrolled, target_height);
|
||||||
|
}
|
||||||
gtk_widget_set_size_request (parent, -1, target_height);
|
gtk_widget_set_size_request (parent, -1, target_height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3328,7 +3529,7 @@ mg_apply_session_font_prefs (session_gui *gui)
|
|||||||
static void
|
static void
|
||||||
mg_create_topicbar (session *sess, GtkWidget *box)
|
mg_create_topicbar (session *sess, GtkWidget *box)
|
||||||
{
|
{
|
||||||
GtkWidget *vbox, *hbox, *mode_hbox, *topic, *bbox;
|
GtkWidget *vbox, *hbox, *mode_hbox, *topic, *topic_scroll, *bbox;
|
||||||
session_gui *gui = sess->gui;
|
session_gui *gui = sess->gui;
|
||||||
|
|
||||||
gui->topic_bar = vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
gui->topic_bar = vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
||||||
@@ -3342,7 +3543,9 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
|
|
||||||
gui->topic_entry = topic = gtk_text_view_new ();
|
gui->topic_entry = topic = gtk_text_view_new ();
|
||||||
gtk_widget_set_name (topic, "zoitechat-topicbox");
|
gtk_widget_set_name (topic, "zoitechat-topicbox");
|
||||||
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic), GTK_WRAP_WORD_CHAR);
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic),
|
||||||
|
prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline ?
|
||||||
|
GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
|
||||||
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (topic), 4);
|
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (topic), 4);
|
||||||
@@ -3355,8 +3558,17 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
G_CALLBACK (mg_topicbar_buffer_changed_cb), topic);
|
G_CALLBACK (mg_topicbar_buffer_changed_cb), topic);
|
||||||
g_signal_connect (G_OBJECT (topic), "size-allocate",
|
g_signal_connect (G_OBJECT (topic), "size-allocate",
|
||||||
G_CALLBACK (mg_topicbar_size_allocate_cb), NULL);
|
G_CALLBACK (mg_topicbar_size_allocate_cb), NULL);
|
||||||
|
topic_scroll = gtk_scrolled_window_new (NULL, NULL);
|
||||||
|
gtk_widget_set_hexpand (topic_scroll, TRUE);
|
||||||
|
gtk_widget_set_size_request (topic_scroll, 1, -1);
|
||||||
|
gtk_widget_set_size_request (topic, 1, -1);
|
||||||
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (topic_scroll),
|
||||||
|
GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
|
||||||
|
gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (topic_scroll), FALSE);
|
||||||
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (topic_scroll), GTK_SHADOW_NONE);
|
||||||
|
gtk_container_add (GTK_CONTAINER (topic_scroll), topic);
|
||||||
mg_topicbar_update_height (topic);
|
mg_topicbar_update_height (topic);
|
||||||
gtk_box_pack_start (GTK_BOX (hbox), topic, TRUE, TRUE, 0);
|
gtk_box_pack_start (GTK_BOX (hbox), topic_scroll, TRUE, TRUE, 0);
|
||||||
gtk_widget_add_events (topic, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
gtk_widget_add_events (topic, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
||||||
GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
|
GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
|
||||||
g_signal_connect (G_OBJECT (topic), "key-press-event",
|
g_signal_connect (G_OBJECT (topic), "key-press-event",
|
||||||
@@ -3373,9 +3585,13 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
mg_create_dialogbuttons (bbox);
|
mg_create_dialogbuttons (bbox);
|
||||||
|
|
||||||
mode_hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
mode_hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), mode_hbox, 0, 0, 0);
|
if (prefs.hex_gui_mode_buttons_inline)
|
||||||
|
gtk_box_pack_start (GTK_BOX (hbox), mode_hbox, 0, 0, 0);
|
||||||
|
else
|
||||||
|
gtk_box_pack_start (GTK_BOX (vbox), mode_hbox, 0, 0, 0);
|
||||||
|
|
||||||
gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
|
gtk_widget_set_valign (bbox, GTK_ALIGN_CENTER);
|
||||||
gtk_box_pack_end (GTK_BOX (mode_hbox), bbox, 0, 0, 0);
|
gtk_box_pack_end (GTK_BOX (mode_hbox), bbox, 0, 0, 0);
|
||||||
mg_create_chanmodebuttons (gui, bbox);
|
mg_create_chanmodebuttons (gui, bbox);
|
||||||
}
|
}
|
||||||
@@ -4521,6 +4737,20 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
};
|
};
|
||||||
const char *emoji_fallback_icon_name;
|
const char *emoji_fallback_icon_name;
|
||||||
|
|
||||||
|
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||||
|
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
|
||||||
|
gtk_widget_set_no_show_all (gui->reply_box, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (box), gui->reply_box, 0, 0, 0);
|
||||||
|
gui->reply_label = gtk_label_new ("");
|
||||||
|
gtk_label_set_ellipsize (GTK_LABEL (gui->reply_label), PANGO_ELLIPSIZE_END);
|
||||||
|
gtk_box_pack_start (GTK_BOX (gui->reply_box), gui->reply_label, TRUE, TRUE, 8);
|
||||||
|
but = gtk_button_new_with_label ("×");
|
||||||
|
gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
|
||||||
|
gtk_widget_set_can_focus (but, FALSE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (gui->reply_box), but, FALSE, FALSE, 0);
|
||||||
|
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (mg_reply_cancel_cb), sess);
|
||||||
|
gtk_widget_hide (gui->reply_box);
|
||||||
|
|
||||||
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||||
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
||||||
|
|
||||||
@@ -4542,7 +4772,7 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
g_signal_connect (G_OBJECT (entry), "activate",
|
g_signal_connect (G_OBJECT (entry), "activate",
|
||||||
G_CALLBACK (mg_inputbox_cb), gui);
|
G_CALLBACK (mg_inputbox_cb), gui);
|
||||||
g_signal_connect (G_OBJECT (entry), "changed",
|
g_signal_connect (G_OBJECT (entry), "changed",
|
||||||
G_CALLBACK (key_check_replace_on_change), NULL);
|
G_CALLBACK (mg_inputbox_changed), gui);
|
||||||
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
|
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
|
||||||
|
|
||||||
gtk_widget_set_name (entry, "zoitechat-inputbox");
|
gtk_widget_set_name (entry, "zoitechat-inputbox");
|
||||||
@@ -4783,6 +5013,7 @@ mg_create_topwindow (session *sess)
|
|||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
parent_win = gtk_widget_get_window (win);
|
parent_win = gtk_widget_get_window (win);
|
||||||
gdk_window_add_filter (parent_win, mg_win32_filter, NULL);
|
gdk_window_add_filter (parent_win, mg_win32_filter, NULL);
|
||||||
|
mg_win32_ensure_minimizable (parent_win);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4819,6 +5050,18 @@ mg_win32_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data)
|
|||||||
if (!msg)
|
if (!msg)
|
||||||
return GDK_FILTER_CONTINUE;
|
return GDK_FILTER_CONTINUE;
|
||||||
|
|
||||||
|
if (msg->message == WM_STYLECHANGING && msg->wParam == (WPARAM)GWL_STYLE)
|
||||||
|
{
|
||||||
|
STYLESTRUCT *style = (STYLESTRUCT *)msg->lParam;
|
||||||
|
|
||||||
|
/* Keep the window minimizable from its taskbar button; see
|
||||||
|
* mg_win32_ensure_minimizable (). */
|
||||||
|
if (style)
|
||||||
|
style->styleNew |= WS_MINIMIZEBOX;
|
||||||
|
|
||||||
|
return GDK_FILTER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg->message == WM_TIMECHANGE)
|
if (msg->message == WM_TIMECHANGE)
|
||||||
{
|
{
|
||||||
_tzset();
|
_tzset();
|
||||||
@@ -4968,6 +5211,7 @@ mg_create_tabwindow (session *sess)
|
|||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
parent_win = gtk_widget_get_window (win);
|
parent_win = gtk_widget_get_window (win);
|
||||||
gdk_window_add_filter (parent_win, mg_win32_filter, NULL);
|
gdk_window_add_filter (parent_win, mg_win32_filter, NULL);
|
||||||
|
mg_win32_ensure_minimizable (parent_win);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5174,6 +5418,12 @@ fe_set_channel (session *sess)
|
|||||||
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_set_typing (session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
fe_userlist_set_typing (sess, nick, state);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
|
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ void mg_dnd_drop_file (session *sess, char *target, char *uri);
|
|||||||
void mg_change_layout (int type);
|
void mg_change_layout (int type);
|
||||||
void mg_update_meters (session_gui *);
|
void mg_update_meters (session_gui *);
|
||||||
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
||||||
|
void mg_reply_update (session *sess);
|
||||||
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
||||||
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
||||||
/* DND */
|
/* DND */
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "../common/zoitechatc.h"
|
#include "../common/zoitechatc.h"
|
||||||
#include "../common/cfgfiles.h"
|
#include "../common/cfgfiles.h"
|
||||||
#include "../common/outbound.h"
|
#include "../common/outbound.h"
|
||||||
|
#include "../common/inbound.h"
|
||||||
#include "../common/ignore.h"
|
#include "../common/ignore.h"
|
||||||
#include "../common/fe.h"
|
#include "../common/fe.h"
|
||||||
#include "../common/server.h"
|
#include "../common/server.h"
|
||||||
@@ -137,7 +138,6 @@ struct mymenu
|
|||||||
#define XCMENU_MARKUP 2
|
#define XCMENU_MARKUP 2
|
||||||
#define XCMENU_MNEMONIC 4
|
#define XCMENU_MNEMONIC 4
|
||||||
|
|
||||||
/* execute a userlistbutton/popupmenu command */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
nick_command (session * sess, char *cmd)
|
nick_command (session * sess, char *cmd)
|
||||||
@@ -148,7 +148,6 @@ nick_command (session * sess, char *cmd)
|
|||||||
handle_command (sess, cmd, TRUE);
|
handle_command (sess, cmd, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fill in the %a %s %n etc and execute the command */
|
|
||||||
|
|
||||||
void
|
void
|
||||||
nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
|
nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
|
||||||
@@ -177,7 +176,6 @@ nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this can't overflow, since popup->cmd is only 256 */
|
|
||||||
len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512;
|
len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512;
|
||||||
buf = g_malloc (len);
|
buf = g_malloc (len);
|
||||||
|
|
||||||
@@ -190,7 +188,6 @@ nick_command_parse (session *sess, char *cmd, char *nick, char *allnick)
|
|||||||
g_free (buf);
|
g_free (buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* userlist button has been clicked */
|
|
||||||
|
|
||||||
void
|
void
|
||||||
userlist_button_cb (GtkWidget * button, char *cmd)
|
userlist_button_cb (GtkWidget * button, char *cmd)
|
||||||
@@ -207,7 +204,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
|
|||||||
|
|
||||||
if (sess->type == SESS_DIALOG)
|
if (sess->type == SESS_DIALOG)
|
||||||
{
|
{
|
||||||
/* fake a selection */
|
|
||||||
nicks = g_new (char *, 2);
|
nicks = g_new (char *, 2);
|
||||||
nicks[0] = g_strdup (sess->channel);
|
nicks[0] = g_strdup (sess->channel);
|
||||||
nicks[1] = NULL;
|
nicks[1] = NULL;
|
||||||
@@ -215,7 +211,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* find number of selected rows */
|
|
||||||
nicks = userlist_selection_list (sess->gui->user_tree, &num_sel);
|
nicks = userlist_selection_list (sess->gui->user_tree, &num_sel);
|
||||||
if (num_sel < 1)
|
if (num_sel < 1)
|
||||||
{
|
{
|
||||||
@@ -226,7 +221,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* create "allnicks" string */
|
|
||||||
allnicks = g_malloc (((NICKLEN + 1) * num_sel) + 1);
|
allnicks = g_malloc (((NICKLEN + 1) * num_sel) + 1);
|
||||||
*allnicks = 0;
|
*allnicks = 0;
|
||||||
|
|
||||||
@@ -240,7 +234,6 @@ userlist_button_cb (GtkWidget * button, char *cmd)
|
|||||||
if (!nick)
|
if (!nick)
|
||||||
nick = nicks[0];
|
nick = nicks[0];
|
||||||
|
|
||||||
/* if not using "%a", execute the command once for each nickname */
|
|
||||||
if (!using_allnicks)
|
if (!using_allnicks)
|
||||||
nick_command_parse (sess, cmd, nicks[i], "");
|
nick_command_parse (sess, cmd, nicks[i], "");
|
||||||
|
|
||||||
@@ -264,19 +257,16 @@ userlist_button_cb (GtkWidget * button, char *cmd)
|
|||||||
g_free (allnicks);
|
g_free (allnicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* a popup-menu-item has been selected */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
popup_menu_cb (GtkWidget * item, char *cmd)
|
popup_menu_cb (GtkWidget * item, char *cmd)
|
||||||
{
|
{
|
||||||
char *nick;
|
char *nick;
|
||||||
|
|
||||||
/* the userdata is set in menu_quick_item() */
|
|
||||||
nick = g_object_get_data (G_OBJECT (item), "u");
|
nick = g_object_get_data (G_OBJECT (item), "u");
|
||||||
|
|
||||||
if (!nick) /* userlist popup menu */
|
if (!nick) /* userlist popup menu */
|
||||||
{
|
{
|
||||||
/* treat it just like a userlist button */
|
|
||||||
userlist_button_cb (NULL, cmd);
|
userlist_button_cb (NULL, cmd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -388,7 +378,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
|
|||||||
if (!name)
|
if (!name)
|
||||||
return menu;
|
return menu;
|
||||||
|
|
||||||
/* Code to add a submenu */
|
|
||||||
sub_menu = menu_new ();
|
sub_menu = menu_new ();
|
||||||
if (flags & XCMENU_MARKUP)
|
if (flags & XCMENU_MARKUP)
|
||||||
{
|
{
|
||||||
@@ -410,7 +399,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
|
|||||||
*sub_item_ret = sub_item;
|
*sub_item_ret = sub_item;
|
||||||
|
|
||||||
if (flags & XCMENU_DOLIST)
|
if (flags & XCMENU_DOLIST)
|
||||||
/* We create a new element in the list */
|
|
||||||
submenu_list = g_slist_prepend (submenu_list, sub_menu);
|
submenu_list = g_slist_prepend (submenu_list, sub_menu);
|
||||||
return sub_menu;
|
return sub_menu;
|
||||||
}
|
}
|
||||||
@@ -418,7 +406,6 @@ menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags
|
|||||||
static GtkWidget *
|
static GtkWidget *
|
||||||
menu_quick_endsub (void)
|
menu_quick_endsub (void)
|
||||||
{
|
{
|
||||||
/* Just delete the first element in the linked list pointed to by first */
|
|
||||||
if (submenu_list)
|
if (submenu_list)
|
||||||
submenu_list = g_slist_remove (submenu_list, submenu_list->data);
|
submenu_list = g_slist_remove (submenu_list, submenu_list->data);
|
||||||
|
|
||||||
@@ -449,10 +436,7 @@ is_in_path (char *cmd)
|
|||||||
char **argv;
|
char **argv;
|
||||||
int argc;
|
int argc;
|
||||||
|
|
||||||
/* special-case these default entries. */
|
|
||||||
/* 123456789012345678 */
|
|
||||||
if (strncmp (prog, "gnome-terminal -x ", 18) == 0)
|
if (strncmp (prog, "gnome-terminal -x ", 18) == 0)
|
||||||
/* don't check for gnome-terminal, but the thing it's executing! */
|
|
||||||
prog += 18;
|
prog += 18;
|
||||||
|
|
||||||
if (g_shell_parse_argv (prog, &argc, &argv, NULL))
|
if (g_shell_parse_argv (prog, &argc, &argv, NULL))
|
||||||
@@ -471,7 +455,6 @@ is_in_path (char *cmd)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_extract_icon (char *name, char **label, char **icon)
|
menu_extract_icon (char *name, char **label, char **icon)
|
||||||
@@ -484,7 +467,6 @@ menu_extract_icon (char *name, char **label, char **icon)
|
|||||||
{
|
{
|
||||||
if (*p == '~')
|
if (*p == '~')
|
||||||
{
|
{
|
||||||
/* escape \~ */
|
|
||||||
if (p == name || p[-1] != '\\')
|
if (p == name || p[-1] != '\\')
|
||||||
{
|
{
|
||||||
if (!start)
|
if (!start)
|
||||||
@@ -511,7 +493,6 @@ menu_extract_icon (char *name, char **label, char **icon)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* append items to "menu" using the (struct popup*) list provided */
|
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
||||||
@@ -538,14 +519,12 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
|||||||
|
|
||||||
} else if (!g_ascii_strncasecmp (pop->name, "ENDSUB", 6))
|
} else if (!g_ascii_strncasecmp (pop->name, "ENDSUB", 6))
|
||||||
{
|
{
|
||||||
/* empty sub menu due to no programs in PATH? */
|
|
||||||
if (check_path && childcount < 1)
|
if (check_path && childcount < 1)
|
||||||
gtk_widget_destroy (subitem);
|
gtk_widget_destroy (subitem);
|
||||||
subitem = NULL;
|
subitem = NULL;
|
||||||
|
|
||||||
if (tempmenu != menu)
|
if (tempmenu != menu)
|
||||||
tempmenu = menu_quick_endsub ();
|
tempmenu = menu_quick_endsub ();
|
||||||
/* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */
|
|
||||||
|
|
||||||
} else if (!g_ascii_strncasecmp (pop->name, "SEP", 3))
|
} else if (!g_ascii_strncasecmp (pop->name, "SEP", 3))
|
||||||
{
|
{
|
||||||
@@ -555,10 +534,8 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
|||||||
{
|
{
|
||||||
char *icon, *label;
|
char *icon, *label;
|
||||||
|
|
||||||
/* default command in zoitechat.c */
|
|
||||||
if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s"))
|
if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s"))
|
||||||
{
|
{
|
||||||
/* don't create this item if already in notify list */
|
|
||||||
if (!target || notify_is_in_list (current_sess->server, target))
|
if (!target || notify_is_in_list (current_sess->server, target))
|
||||||
{
|
{
|
||||||
list = list->next;
|
list = list->next;
|
||||||
@@ -571,7 +548,6 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
|||||||
if (!check_path || pop->cmd[0] != '!')
|
if (!check_path || pop->cmd[0] != '!')
|
||||||
{
|
{
|
||||||
menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
|
menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon);
|
||||||
/* check if the program is in path, if not, leave it out! */
|
|
||||||
} else if (is_in_path (pop->cmd))
|
} else if (is_in_path (pop->cmd))
|
||||||
{
|
{
|
||||||
childcount++;
|
childcount++;
|
||||||
@@ -585,7 +561,6 @@ menu_create (GtkWidget *menu, GSList *list, char *target, int check_path)
|
|||||||
list = list->next;
|
list = list->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Let's clean up the linked list from mem */
|
|
||||||
while (submenu_list)
|
while (submenu_list)
|
||||||
submenu_list = g_slist_remove (submenu_list, submenu_list->data);
|
submenu_list = g_slist_remove (submenu_list, submenu_list->data);
|
||||||
}
|
}
|
||||||
@@ -639,10 +614,8 @@ menu_nickinfo_cb (GtkWidget *menu, session *sess)
|
|||||||
if (!is_session (sess))
|
if (!is_session (sess))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* issue a /WHOIS */
|
|
||||||
g_snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy);
|
g_snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy);
|
||||||
handle_command (sess, buf, FALSE);
|
handle_command (sess, buf, FALSE);
|
||||||
/* and hide the output */
|
|
||||||
sess->server->skip_next_whois = 1;
|
sess->server->skip_next_whois = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,7 +625,6 @@ copy_to_clipboard_cb (GtkWidget *item, char *url)
|
|||||||
gtkutil_copy_to_clipboard (item, NULL, url);
|
gtkutil_copy_to_clipboard (item, NULL, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns boolean: Some data is missing */
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
|
menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
|
||||||
@@ -664,7 +636,6 @@ menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu)
|
|||||||
gboolean missing = FALSE;
|
gboolean missing = FALSE;
|
||||||
GtkWidget *item;
|
GtkWidget *item;
|
||||||
|
|
||||||
/* let the translators tweak this if need be */
|
|
||||||
fmt = _("<tt><b>%-11s</b></tt> %s");
|
fmt = _("<tt><b>%-11s</b></tt> %s");
|
||||||
g_snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown"));
|
g_snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown"));
|
||||||
|
|
||||||
@@ -755,14 +726,11 @@ fe_userlist_update (session *sess, struct User *user)
|
|||||||
if (!nick_submenu || !str_copy)
|
if (!nick_submenu || !str_copy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* not the same nick as the menu? */
|
|
||||||
if (sess->server->p_cmp (user->nick, str_copy))
|
if (sess->server->p_cmp (user->nick, str_copy))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* get rid of the "show" signal */
|
|
||||||
g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess);
|
g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess);
|
||||||
|
|
||||||
/* destroy all the old items */
|
|
||||||
items = gtk_container_get_children (GTK_CONTAINER (nick_submenu));
|
items = gtk_container_get_children (GTK_CONTAINER (nick_submenu));
|
||||||
iter = items;
|
iter = items;
|
||||||
while (iter)
|
while (iter)
|
||||||
@@ -773,7 +741,6 @@ fe_userlist_update (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
g_list_free (items);
|
g_list_free (items);
|
||||||
|
|
||||||
/* and re-create them with new info */
|
|
||||||
needs_refresh = menu_create_nickinfo_menu (user, nick_submenu) ||
|
needs_refresh = menu_create_nickinfo_menu (user, nick_submenu) ||
|
||||||
!user->hostname || !user->realname || !user->servername;
|
!user->hostname || !user->realname || !user->servername;
|
||||||
|
|
||||||
@@ -784,6 +751,25 @@ fe_userlist_update (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
menu_reply_to_latest_cb (GtkWidget *wid, gpointer data)
|
||||||
|
{
|
||||||
|
reply_item *item;
|
||||||
|
|
||||||
|
item = reply_cache_latest_from (current_sess, str_copy);
|
||||||
|
if (!item)
|
||||||
|
{
|
||||||
|
PrintText (current_sess, _("No recent message to reply to.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text);
|
||||||
|
mg_reply_update (current_sess);
|
||||||
|
if (current_sess->gui && current_sess->gui->input_box)
|
||||||
|
gtk_widget_grab_focus (current_sess->gui->input_box);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||||
{
|
{
|
||||||
@@ -796,7 +782,6 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
|||||||
|
|
||||||
submenu_list = 0; /* first time through, might not be 0 */
|
submenu_list = 0; /* first time through, might not be 0 */
|
||||||
|
|
||||||
/* more than 1 nick selected? */
|
|
||||||
if (num_sel > 1)
|
if (num_sel > 1)
|
||||||
{
|
{
|
||||||
g_snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel);
|
g_snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel);
|
||||||
@@ -827,6 +812,12 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
|||||||
else
|
else
|
||||||
menu_create (menu, popup_list, str_copy, FALSE);
|
menu_create (menu, popup_list, str_copy, FALSE);
|
||||||
|
|
||||||
|
if (num_sel <= 1)
|
||||||
|
{
|
||||||
|
menu_quick_item_with_callback (menu_reply_to_latest_cb, _("Reply"), menu, 0);
|
||||||
|
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (num_sel == 0) /* xtext click */
|
if (num_sel == 0) /* xtext click */
|
||||||
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
||||||
else /* userlist treeview click */
|
else /* userlist treeview click */
|
||||||
@@ -835,7 +826,6 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
|||||||
menu_popup (menu, event, NULL);
|
menu_popup (menu, event, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stuff for the View menu */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_showhide_cb (session *sess)
|
menu_showhide_cb (session *sess)
|
||||||
@@ -999,7 +989,6 @@ menu_fullscreen_toggle (GtkWidget *wid, gpointer ud)
|
|||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (!prefs.hex_gui_win_state) /* not maximized */
|
if (!prefs.hex_gui_win_state) /* not maximized */
|
||||||
{
|
{
|
||||||
/* other window managers seem to handle this */
|
|
||||||
gtk_window_resize (GTK_WINDOW (parent_window),
|
gtk_window_resize (GTK_WINDOW (parent_window),
|
||||||
prefs.hex_gui_win_width, prefs.hex_gui_win_height);
|
prefs.hex_gui_win_width, prefs.hex_gui_win_height);
|
||||||
gtk_window_move (GTK_WINDOW (parent_window),
|
gtk_window_move (GTK_WINDOW (parent_window),
|
||||||
@@ -1025,7 +1014,6 @@ open_url_cb (GtkWidget *item, char *url)
|
|||||||
{
|
{
|
||||||
char buf[512];
|
char buf[512];
|
||||||
|
|
||||||
/* pass this to /URL so it can handle irc:// */
|
|
||||||
g_snprintf (buf, sizeof (buf), "URL %s", url);
|
g_snprintf (buf, sizeof (buf), "URL %s", url);
|
||||||
handle_command (current_sess, buf, FALSE);
|
handle_command (current_sess, buf, FALSE);
|
||||||
}
|
}
|
||||||
@@ -1040,7 +1028,6 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
str_copy = g_strdup (url);
|
str_copy = g_strdup (url);
|
||||||
|
|
||||||
menu = menu_new ();
|
menu = menu_new ();
|
||||||
/* more than 51 chars? Chop it */
|
|
||||||
if (g_utf8_strlen (str_copy, -1) >= 52)
|
if (g_utf8_strlen (str_copy, -1) >= 52)
|
||||||
{
|
{
|
||||||
tmp = g_strdup (str_copy);
|
tmp = g_strdup (str_copy);
|
||||||
@@ -1055,14 +1042,12 @@ menu_urlmenu (GdkEventButton *event, char *url)
|
|||||||
}
|
}
|
||||||
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
||||||
|
|
||||||
/* Two hardcoded entries */
|
|
||||||
if (strncmp (str_copy, "irc://", 6) == 0 ||
|
if (strncmp (str_copy, "irc://", 6) == 0 ||
|
||||||
strncmp (str_copy, "ircs://",7) == 0)
|
strncmp (str_copy, "ircs://",7) == 0)
|
||||||
menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
|
menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy);
|
||||||
else
|
else
|
||||||
menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
|
menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy);
|
||||||
menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
|
menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy);
|
||||||
/* custom ones from urlhandlers.conf */
|
|
||||||
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
menu_create (menu, urlhandler_list, str_copy, TRUE);
|
||||||
menu_add_plugin_items (menu, "\x4$URL", str_copy);
|
menu_add_plugin_items (menu, "\x4$URL", str_copy);
|
||||||
menu_popup (menu, event, NULL);
|
menu_popup (menu, event, NULL);
|
||||||
@@ -1330,7 +1315,6 @@ menu_newserver_tab (GtkWidget * wid, gpointer none)
|
|||||||
int oldf = prefs.hex_gui_tab_newtofront;
|
int oldf = prefs.hex_gui_tab_newtofront;
|
||||||
|
|
||||||
prefs.hex_gui_tab_chans = 1;
|
prefs.hex_gui_tab_chans = 1;
|
||||||
/* force focus if setting is "only requested tabs" */
|
|
||||||
if (prefs.hex_gui_tab_newtofront == 2)
|
if (prefs.hex_gui_tab_newtofront == 2)
|
||||||
prefs.hex_gui_tab_newtofront = 1;
|
prefs.hex_gui_tab_newtofront = 1;
|
||||||
new_ircwindow (NULL, NULL, SESS_SERVER, 0);
|
new_ircwindow (NULL, NULL, SESS_SERVER, 0);
|
||||||
@@ -1714,10 +1698,67 @@ menu_ctcpguiopen (void)
|
|||||||
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
|
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns the index.html of a locally installed copy of the documentation,
|
||||||
|
* or NULL if none is available. Callers own the returned string. */
|
||||||
|
static char *
|
||||||
|
menu_docs_local_index (void)
|
||||||
|
{
|
||||||
|
const char *env_docdir;
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
env_docdir = g_getenv ("ZOITECHAT_DOCDIR");
|
||||||
|
if (env_docdir && env_docdir[0])
|
||||||
|
{
|
||||||
|
path = g_build_filename (env_docdir, "index.html", NULL);
|
||||||
|
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
|
||||||
|
return path;
|
||||||
|
g_free (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Docs the user placed in the config dir; works on every platform,
|
||||||
|
* including portable installs and Flatpak (host-readable, unlike /app) */
|
||||||
|
path = g_build_filename (get_xdir (), "offline-docs", "index.html", NULL);
|
||||||
|
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
|
||||||
|
return path;
|
||||||
|
g_free (path);
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
/* The installer's "Offline Documentation" component; ZOITECHATDOCDIR is
|
||||||
|
* relative to the install dir on Windows and must not be resolved
|
||||||
|
* against the CWD, which depends on how the app was launched */
|
||||||
|
if (!g_path_is_absolute (ZOITECHATDOCDIR))
|
||||||
|
{
|
||||||
|
char *install_dir = g_win32_get_package_installation_directory_of_module (NULL);
|
||||||
|
|
||||||
|
if (!install_dir)
|
||||||
|
return NULL;
|
||||||
|
path = g_build_filename (install_dir, ZOITECHATDOCDIR, "index.html", NULL);
|
||||||
|
g_free (install_dir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
path = g_build_filename (ZOITECHATDOCDIR, "index.html", NULL);
|
||||||
|
#else
|
||||||
|
path = g_build_filename (ZOITECHATDOCDIR, "index.html", NULL);
|
||||||
|
#endif
|
||||||
|
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
|
||||||
|
return path;
|
||||||
|
g_free (path);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_docs (GtkWidget *wid, gpointer none)
|
menu_docs (GtkWidget *wid, gpointer none)
|
||||||
{
|
{
|
||||||
fe_open_url ("https://docs.zoitechat.org/en/latest/");
|
char *local_index = menu_docs_local_index ();
|
||||||
|
|
||||||
|
if (local_index)
|
||||||
|
{
|
||||||
|
fe_open_url (local_index);
|
||||||
|
g_free (local_index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fe_open_url ("https://docs.zoitechat.org/en/latest/");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static void
|
/*static void
|
||||||
@@ -2226,7 +2267,6 @@ menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data)
|
|||||||
return gtk_widget_is_sensitive (widget);
|
return gtk_widget_is_sensitive (widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === STUFF FOR /MENU === */
|
|
||||||
|
|
||||||
static GtkMenuItem *
|
static GtkMenuItem *
|
||||||
menu_find_item (GtkWidget *menu, char *name)
|
menu_find_item (GtkWidget *menu, char *name)
|
||||||
@@ -2292,7 +2332,6 @@ menu_find_path (GtkWidget *menu, char *path)
|
|||||||
char name[128];
|
char name[128];
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
/* grab the next part of the path */
|
|
||||||
s = strchr (path, '/');
|
s = strchr (path, '/');
|
||||||
len = s - path;
|
len = s - path;
|
||||||
if (!s)
|
if (!s)
|
||||||
@@ -2346,7 +2385,6 @@ menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, c
|
|||||||
list = list->next;
|
list = list->next;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* do it only once for tab sessions, since they share a GUI */
|
|
||||||
if (!sess->gui->is_tab || !tabdone)
|
if (!sess->gui->is_tab || !tabdone)
|
||||||
{
|
{
|
||||||
callback (sess->gui->menu, me, NULL);
|
callback (sess->gui->menu, me, NULL);
|
||||||
@@ -2366,13 +2404,11 @@ menu_update_cb (GtkWidget *menu, menu_entry *me, char *target)
|
|||||||
if (item)
|
if (item)
|
||||||
{
|
{
|
||||||
gtk_widget_set_sensitive (item, me->enable);
|
gtk_widget_set_sensitive (item, me->enable);
|
||||||
/* must do it without triggering the callback */
|
|
||||||
if (GTK_IS_CHECK_MENU_ITEM (item))
|
if (GTK_IS_CHECK_MENU_ITEM (item))
|
||||||
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), me->state);
|
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), me->state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* radio state changed via mouse click */
|
|
||||||
static void
|
static void
|
||||||
menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
|
menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
|
||||||
{
|
{
|
||||||
@@ -2380,15 +2416,12 @@ menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me)
|
|||||||
if (gtk_check_menu_item_get_active (item))
|
if (gtk_check_menu_item_get_active (item))
|
||||||
me->state = 1;
|
me->state = 1;
|
||||||
|
|
||||||
/* update the state, incase this was changed via right-click. */
|
|
||||||
/* This will update all other windows and menu bars */
|
|
||||||
menu_foreach_gui (me, menu_update_cb);
|
menu_foreach_gui (me, menu_update_cb);
|
||||||
|
|
||||||
if (me->state && me->cmd)
|
if (me->state && me->cmd)
|
||||||
handle_command (current_sess, me->cmd, FALSE);
|
handle_command (current_sess, me->cmd, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* toggle state changed via mouse click */
|
|
||||||
static void
|
static void
|
||||||
menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
|
menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
|
||||||
{
|
{
|
||||||
@@ -2396,8 +2429,6 @@ menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me)
|
|||||||
if (gtk_check_menu_item_get_active (item))
|
if (gtk_check_menu_item_get_active (item))
|
||||||
me->state = 1;
|
me->state = 1;
|
||||||
|
|
||||||
/* update the state, incase this was changed via right-click. */
|
|
||||||
/* This will update all other windows and menu bars */
|
|
||||||
menu_foreach_gui (me, menu_update_cb);
|
menu_foreach_gui (me, menu_update_cb);
|
||||||
|
|
||||||
if (me->state)
|
if (me->state)
|
||||||
@@ -2568,7 +2599,6 @@ fe_menu_add (menu_entry *me)
|
|||||||
if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL))
|
if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* return the label with markup stripped */
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2584,7 +2614,6 @@ fe_menu_update (menu_entry *me)
|
|||||||
menu_foreach_gui (me, menu_update_cb);
|
menu_foreach_gui (me, menu_update_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* used to add custom menus to the right-click menu */
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_add_plugin_mainmenu_items (GtkWidget *menu)
|
menu_add_plugin_mainmenu_items (GtkWidget *menu)
|
||||||
@@ -2618,7 +2647,6 @@ menu_add_plugin_items (GtkWidget *menu, char *root, char *target)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === END STUFF FOR /MENU === */
|
|
||||||
|
|
||||||
GtkWidget *
|
GtkWidget *
|
||||||
menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
||||||
@@ -2651,13 +2679,11 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
else
|
else
|
||||||
menu_bar = menu_new ();
|
menu_bar = menu_new ();
|
||||||
|
|
||||||
/* /MENU needs to know this later */
|
|
||||||
g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group);
|
g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group);
|
||||||
|
|
||||||
g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel",
|
g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel",
|
||||||
G_CALLBACK (menu_canacaccel), 0);
|
G_CALLBACK (menu_canacaccel), 0);
|
||||||
|
|
||||||
/* set the initial state of toggles */
|
|
||||||
mymenu[MENUBAR_OFFSET].state = !prefs.hex_gui_hide_menu;
|
mymenu[MENUBAR_OFFSET].state = !prefs.hex_gui_hide_menu;
|
||||||
mymenu[MENUBAR_OFFSET+1].state = prefs.hex_gui_topicbar;
|
mymenu[MENUBAR_OFFSET+1].state = prefs.hex_gui_topicbar;
|
||||||
mymenu[MENUBAR_OFFSET+2].state = !prefs.hex_gui_ulist_hide;
|
mymenu[MENUBAR_OFFSET+2].state = !prefs.hex_gui_ulist_hide;
|
||||||
@@ -2696,7 +2722,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
mymenu[METRE_OFFSET+3].state = 1;
|
mymenu[METRE_OFFSET+3].state = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* change Close binding to ctrl-shift-w when using emacs keys */
|
|
||||||
settings = gtk_widget_get_settings (menu_bar);
|
settings = gtk_widget_get_settings (menu_bar);
|
||||||
if (settings)
|
if (settings)
|
||||||
{
|
{
|
||||||
@@ -2712,7 +2737,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */
|
|
||||||
{
|
{
|
||||||
char *help = _("_Help");
|
char *help = _("_Help");
|
||||||
char *under = strchr (help, '_');
|
char *under = strchr (help, '_');
|
||||||
@@ -2749,7 +2773,6 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
if (mymenu[i].id == MENU_ID_USERMENU)
|
if (mymenu[i].id == MENU_ID_USERMENU)
|
||||||
usermenu = menu;
|
usermenu = menu;
|
||||||
menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
/* record the English name for /menu */
|
|
||||||
g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text);
|
g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text);
|
||||||
#ifdef HAVE_GTK_MAC /* Added to app menu, see below */
|
#ifdef HAVE_GTK_MAC /* Added to app menu, see below */
|
||||||
if (!bar || mymenu[i].id != MENU_ID_ZOITECHAT)
|
if (!bar || mymenu[i].id != MENU_ID_ZOITECHAT)
|
||||||
@@ -2801,7 +2824,6 @@ normalitem:
|
|||||||
togitem:
|
togitem:
|
||||||
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
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));
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
/* 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),
|
||||||
mymenu[i].state);*/
|
mymenu[i].state);*/
|
||||||
@@ -2841,7 +2863,6 @@ togitem:
|
|||||||
group = NULL;
|
group = NULL;
|
||||||
submenu = menu_new ();
|
submenu = menu_new ();
|
||||||
item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
|
item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE);
|
||||||
/* record the English name for /menu */
|
|
||||||
g_object_set_data (G_OBJECT (item), "name", mymenu[i].text);
|
g_object_set_data (G_OBJECT (item), "name", mymenu[i].text);
|
||||||
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
|
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
|
||||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
|
||||||
@@ -2863,13 +2884,10 @@ togitem:
|
|||||||
submenu = NULL;
|
submenu = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* record this GtkWidget * so it's state might be changed later */
|
|
||||||
if (mymenu[i].id != 0 && menu_widgets)
|
if (mymenu[i].id != 0 && menu_widgets)
|
||||||
/* this ends up in sess->gui->menu_item[MENU_ID_XXX] */
|
|
||||||
menu_widgets[mymenu[i].id] = item;
|
menu_widgets[mymenu[i].id] = item;
|
||||||
|
|
||||||
#ifdef HAVE_GTK_MAC
|
#ifdef HAVE_GTK_MAC
|
||||||
/* We want ZoiteChat to be the app menu, not including Quit or ZoiteChat itself */
|
|
||||||
if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT)
|
if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT)
|
||||||
{
|
{
|
||||||
if (!submenu || mymenu[i].type == M_MENUSUB)
|
if (!submenu || mymenu[i].type == M_MENUSUB)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ zoitechat_gtk_deps = [
|
|||||||
|
|
||||||
gtk_dep = dependency('gtk+-3.0', version: '>= 3.22')
|
gtk_dep = dependency('gtk+-3.0', version: '>= 3.22')
|
||||||
|
|
||||||
zoitechat_theme_deps = [gtk_dep]
|
zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep]
|
||||||
|
|
||||||
if host_machine.system() != 'windows'
|
if host_machine.system() != 'windows'
|
||||||
appindicator_opt = get_option('appindicator')
|
appindicator_opt = get_option('appindicator')
|
||||||
@@ -227,21 +227,6 @@ test('Theme Access Routing Tests', theme_access_tests,
|
|||||||
timeout: 120,
|
timeout: 120,
|
||||||
)
|
)
|
||||||
|
|
||||||
theme_gtk3_settings_tests = executable('theme_gtk3_settings_tests',
|
|
||||||
[
|
|
||||||
'theme/tests/test-theme-gtk3-settings.c',
|
|
||||||
'theme/theme-gtk3.c',
|
|
||||||
],
|
|
||||||
include_directories: [config_h_include],
|
|
||||||
dependencies: [gtk_dep],
|
|
||||||
)
|
|
||||||
|
|
||||||
test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
|
|
||||||
protocol: 'tap',
|
|
||||||
timeout: 120,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
||||||
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
||||||
include_directories: [config_h_include],
|
include_directories: [config_h_include],
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -1092,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)
|
||||||
{
|
{
|
||||||
@@ -1219,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;
|
||||||
@@ -1258,6 +1383,7 @@ tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
|||||||
if (event)
|
if (event)
|
||||||
gdk_event_free (event);
|
gdk_event_free (event);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ static gboolean color_change;
|
|||||||
static struct zoitechatprefs setup_prefs;
|
static struct zoitechatprefs setup_prefs;
|
||||||
static GtkWidget *cancel_button;
|
static GtkWidget *cancel_button;
|
||||||
static GtkWidget *font_dialog = NULL;
|
static GtkWidget *font_dialog = NULL;
|
||||||
|
static GtkWidget *setup_topicbar_multiline_toggle = NULL;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@@ -185,6 +186,10 @@ static const setting appearance_settings[] =
|
|||||||
{ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0},
|
{ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0},
|
||||||
{ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0},
|
{ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0},
|
||||||
|
|
||||||
|
{ST_HEADER, N_("Topic Bar"),0,0,0},
|
||||||
|
{ST_TOGGLE, N_("Place mode buttons beside the topic"), P_OFFINTNL(hex_gui_mode_buttons_inline), 0, 0, 0},
|
||||||
|
{ST_TOGGLE, N_("Allow multi-line topics"), P_OFFINTNL(hex_gui_topicbar_multiline), 0, 0, 0},
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -833,6 +838,16 @@ setup_toggle_sensitive_cb (GtkToggleButton *but, GtkWidget *wid)
|
|||||||
gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (but));
|
gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (but));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setup_topicbar_inline_toggled_cb (GtkToggleButton *but, gpointer userdata)
|
||||||
|
{
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
if (setup_topicbar_multiline_toggle)
|
||||||
|
gtk_widget_set_sensitive (setup_topicbar_multiline_toggle,
|
||||||
|
!gtk_toggle_button_get_active (but));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
|
||||||
{
|
{
|
||||||
@@ -859,6 +874,14 @@ setup_create_toggleL (GtkWidget *tab, int row, const setting *set)
|
|||||||
setup_get_int (&setup_prefs, set));
|
setup_get_int (&setup_prefs, set));
|
||||||
g_signal_connect (G_OBJECT (wid), "toggled",
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
G_CALLBACK (setup_toggle_cb), (gpointer)set);
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_mode_buttons_inline))
|
||||||
|
g_signal_connect (G_OBJECT (wid), "toggled",
|
||||||
|
G_CALLBACK (setup_topicbar_inline_toggled_cb), NULL);
|
||||||
|
if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_topicbar_multiline))
|
||||||
|
{
|
||||||
|
setup_topicbar_multiline_toggle = wid;
|
||||||
|
gtk_widget_set_sensitive (wid, !setup_prefs.hex_gui_mode_buttons_inline);
|
||||||
|
}
|
||||||
if (set->tooltip)
|
if (set->tooltip)
|
||||||
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
gtk_widget_set_tooltip_text (wid, _(set->tooltip));
|
||||||
setup_table_attach (tab, wid, 2, row==6 ? 6 : 4, row, row + 1, FALSE, FALSE,
|
setup_table_attach (tab, wid, 2, row==6 ? 6 : 4, row, row + 1, FALSE, FALSE,
|
||||||
@@ -2286,6 +2309,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_lagometer))
|
if (DIFF (hex_gui_lagometer))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
|
if (DIFF (hex_gui_mode_buttons_inline))
|
||||||
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_icons))
|
if (DIFF (hex_gui_tab_icons))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_closebuttons))
|
if (DIFF (hex_gui_tab_closebuttons))
|
||||||
@@ -2300,6 +2325,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_throttlemeter))
|
if (DIFF (hex_gui_throttlemeter))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
|
if (DIFF (hex_gui_topicbar_multiline))
|
||||||
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_count))
|
if (DIFF (hex_gui_ulist_count))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_icons))
|
if (DIFF (hex_gui_ulist_icons))
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ get_int_setting (const char *name)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
has_default_seat (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_themes (void)
|
setup_themes (void)
|
||||||
{
|
{
|
||||||
@@ -309,10 +317,13 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
|
||||||
g_assert_no_error (error);
|
g_assert_no_error (error);
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
|
||||||
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);
|
||||||
@@ -322,9 +333,12 @@ test_settings_restored_on_disable_and_switch (void)
|
|||||||
theme_gtk3_disable ();
|
theme_gtk3_disable ();
|
||||||
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
|
||||||
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
|
||||||
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 ());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,14 +52,13 @@ static const guint8 theme_default_99_mirc_colors[THEME_XTEXT_MIRC_COLS][3] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len, gboolean apply_base)
|
theme_access_apply_default_99_palette (XTextColor *palette, size_t palette_len)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
size_t start = apply_base ? 0 : 32;
|
|
||||||
|
|
||||||
if (palette_len == 0)
|
if (palette_len == 0)
|
||||||
return;
|
return;
|
||||||
for (i = start; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++)
|
for (i = 32; i < THEME_XTEXT_MIRC_COLS && i < palette_len; i++)
|
||||||
{
|
{
|
||||||
palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0;
|
palette[i].red = theme_default_99_mirc_colors[i][0] / 255.0;
|
||||||
palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0;
|
palette[i].green = theme_default_99_mirc_colors[i][1] / 255.0;
|
||||||
@@ -148,11 +147,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 +171,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);
|
||||||
@@ -227,7 +224,6 @@ 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)
|
||||||
{
|
{
|
||||||
ThemeWidgetStyleValues style_values;
|
ThemeWidgetStyleValues style_values;
|
||||||
gboolean has_user_colors;
|
|
||||||
GdkRGBA marker_color;
|
GdkRGBA marker_color;
|
||||||
|
|
||||||
if (!palette)
|
if (!palette)
|
||||||
@@ -235,9 +231,8 @@ theme_get_xtext_colors_for_widget (GtkWidget *widget, XTextColor *palette, size_
|
|||||||
|
|
||||||
theme_get_widget_style_values_for_widget (widget, &style_values);
|
theme_get_widget_style_values_for_widget (widget, &style_values);
|
||||||
theme_runtime_get_xtext_colors (palette, palette_len);
|
theme_runtime_get_xtext_colors (palette, palette_len);
|
||||||
has_user_colors = theme_runtime_mode_has_user_colors (theme_runtime_is_dark_active ());
|
|
||||||
if (palette_len >= THEME_XTEXT_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;
|
||||||
|
|||||||
@@ -135,8 +135,12 @@ settings_defaults_table (void)
|
|||||||
static void
|
static void
|
||||||
settings_rescan_icon_theme (void)
|
settings_rescan_icon_theme (void)
|
||||||
{
|
{
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
GtkIconTheme *icon_theme;
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
icon_theme = gtk_icon_theme_get_default ();
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme)
|
if (!icon_theme)
|
||||||
return;
|
return;
|
||||||
@@ -156,8 +160,13 @@ theme_gtk3_reset_widgets (void)
|
|||||||
static void
|
static void
|
||||||
settings_capture_icon_search_path (void)
|
settings_capture_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -168,8 +177,13 @@ settings_capture_icon_search_path (void)
|
|||||||
static void
|
static void
|
||||||
settings_append_icon_search_path (const char *path)
|
settings_append_icon_search_path (const char *path)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -199,8 +213,13 @@ settings_apply_icon_paths (const char *theme_root)
|
|||||||
static void
|
static void
|
||||||
settings_restore_icon_search_path (void)
|
settings_restore_icon_search_path (void)
|
||||||
{
|
{
|
||||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkIconTheme *icon_theme;
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
icon_theme = gtk_icon_theme_get_default ();
|
||||||
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -394,6 +413,14 @@ settings_theme_link_search_path (const char *theme_root, const char *theme_name)
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
settings_default_seat_available (void)
|
||||||
|
{
|
||||||
|
GdkDisplay *display = gdk_display_get_default ();
|
||||||
|
|
||||||
|
return display && GDK_IS_SEAT (gdk_display_get_default_seat (display));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
settings_apply_theme_name (const char *theme_root)
|
settings_apply_theme_name (const char *theme_root)
|
||||||
{
|
{
|
||||||
@@ -404,7 +431,7 @@ settings_apply_theme_name (const char *theme_root)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
settings = gtk_settings_get_default ();
|
settings = gtk_settings_get_default ();
|
||||||
if (!settings)
|
if (!settings || !settings_default_seat_available ())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
theme_name = g_path_get_basename (theme_root);
|
theme_name = g_path_get_basename (theme_root);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "theme-runtime.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
|
||||||
{
|
{
|
||||||
@@ -243,6 +244,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)
|
||||||
{
|
{
|
||||||
@@ -481,6 +485,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +564,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)
|
||||||
{
|
{
|
||||||
@@ -569,6 +681,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
|
||||||
@@ -884,6 +997,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);
|
||||||
@@ -1198,6 +1313,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))
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#include "fe-gtk.h"
|
#include "fe-gtk.h"
|
||||||
|
|
||||||
@@ -53,6 +54,36 @@ enum
|
|||||||
|
|
||||||
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
|
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
userlist_typing_suffix (session *sess, struct User *user)
|
||||||
|
{
|
||||||
|
static const char *active[] = { " [✎]", " [✎.]", " [✎..]" };
|
||||||
|
|
||||||
|
if (!user || !user->typing)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (user->typing == 2)
|
||||||
|
return " [✎…]";
|
||||||
|
|
||||||
|
return active[sess->typing_animation_frame % G_N_ELEMENTS (active)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
userlist_nick_markup (session *sess, struct User *user)
|
||||||
|
{
|
||||||
|
char *nick = g_markup_escape_text (user->nick, -1);
|
||||||
|
const char *typing = userlist_typing_suffix (sess, user);
|
||||||
|
|
||||||
|
if (*typing)
|
||||||
|
{
|
||||||
|
char *marked = g_strdup_printf ("%s%s", nick, typing);
|
||||||
|
g_free (nick);
|
||||||
|
return marked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
userlist_prefix_color (char prefix)
|
userlist_prefix_color (char prefix)
|
||||||
{
|
{
|
||||||
@@ -463,6 +494,87 @@ fe_userlist_remove (session *sess, struct User *user)
|
|||||||
return sel;
|
return sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
userlist_typing_tick (session *sess)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
gboolean valid;
|
||||||
|
gboolean keep = FALSE;
|
||||||
|
time_t now = time (NULL);
|
||||||
|
|
||||||
|
if (!sess || !sess->res || !sess->res->user_model)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
sess->typing_animation_frame++;
|
||||||
|
model = GTK_TREE_MODEL (sess->res->user_model);
|
||||||
|
valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||||
|
|
||||||
|
while (valid)
|
||||||
|
{
|
||||||
|
struct User *user = NULL;
|
||||||
|
|
||||||
|
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
|
||||||
|
if (user && user->typing)
|
||||||
|
{
|
||||||
|
char *nick;
|
||||||
|
|
||||||
|
if ((user->typing == 1 && now - user->typing_time >= 6) || (user->typing == 2 && now - user->typing_time >= 30))
|
||||||
|
user->typing = 0;
|
||||||
|
|
||||||
|
nick = userlist_nick_markup (sess, user);
|
||||||
|
gtk_list_store_set (sess->res->user_model, &iter, COL_NICK, nick, -1);
|
||||||
|
g_free (nick);
|
||||||
|
if (user->typing)
|
||||||
|
keep = TRUE;
|
||||||
|
}
|
||||||
|
valid = gtk_tree_model_iter_next (model, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep)
|
||||||
|
{
|
||||||
|
sess->typing_animation_tag = 0;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_userlist_set_typing (session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
struct User *user;
|
||||||
|
GtkTreeIter *iter;
|
||||||
|
int sel;
|
||||||
|
|
||||||
|
if (!sess || !nick || !sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
user = userlist_find (sess, nick);
|
||||||
|
if (!user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!strcmp (state, "active"))
|
||||||
|
user->typing = 1;
|
||||||
|
else if (!strcmp (state, "paused"))
|
||||||
|
user->typing = 2;
|
||||||
|
else
|
||||||
|
user->typing = 0;
|
||||||
|
user->typing_time = time (NULL);
|
||||||
|
|
||||||
|
iter = find_row (sess, GTK_TREE_VIEW (sess->gui->user_tree), GTK_TREE_MODEL (sess->res->user_model), user, &sel);
|
||||||
|
if (iter)
|
||||||
|
{
|
||||||
|
char *nick = userlist_nick_markup (sess, user);
|
||||||
|
gtk_list_store_set (sess->res->user_model, iter, COL_NICK, nick, -1);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user->typing && !sess->typing_animation_tag)
|
||||||
|
sess->typing_animation_tag = fe_timeout_add (350, userlist_typing_tick, sess);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_userlist_rehash (session *sess, struct User *user)
|
fe_userlist_rehash (session *sess, struct User *user)
|
||||||
{
|
{
|
||||||
@@ -493,9 +605,14 @@ fe_userlist_rehash (session *sess, struct User *user)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
{
|
||||||
|
char *nick = userlist_nick_markup (sess, user);
|
||||||
|
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
|
||||||
|
COL_NICK, nick,
|
||||||
COL_HOST, user->hostname,
|
COL_HOST, user->hostname,
|
||||||
-1);
|
-1);
|
||||||
|
g_free (nick);
|
||||||
|
}
|
||||||
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
|
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,7 +623,6 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
|
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
|
||||||
GtkTreeIter iter;
|
GtkTreeIter iter;
|
||||||
char *nick;
|
char *nick;
|
||||||
char *nick_escaped;
|
|
||||||
char *prefix = NULL;
|
char *prefix = NULL;
|
||||||
char *prefix_escaped;
|
char *prefix_escaped;
|
||||||
char prefix_text[2];
|
char prefix_text[2];
|
||||||
@@ -530,8 +646,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nick_escaped = g_markup_escape_text (newuser->nick, -1);
|
nick = userlist_nick_markup (sess, newuser);
|
||||||
nick = nick_escaped;
|
|
||||||
if (!prefs.hex_gui_ulist_icons)
|
if (!prefs.hex_gui_ulist_icons)
|
||||||
{
|
{
|
||||||
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
|
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
|
||||||
@@ -559,7 +674,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
|
|||||||
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
|
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
|
||||||
|
|
||||||
g_free (prefix);
|
g_free (prefix);
|
||||||
g_free (nick_escaped);
|
g_free (nick);
|
||||||
|
|
||||||
userlist_row_map_set (sess, model, newuser, &iter);
|
userlist_row_map_set (sess, model, newuser, &iter);
|
||||||
|
|
||||||
@@ -757,6 +872,7 @@ userlist_add_columns (GtkTreeView * treeview)
|
|||||||
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
||||||
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
|
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
|
||||||
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
|
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
|
||||||
|
|
||||||
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
|
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
|
||||||
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
|
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
|
||||||
gtk_tree_view_column_set_expand (column, TRUE);
|
gtk_tree_view_column_set_expand (column, TRUE);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ GtkWidget *userlist_create (GtkWidget *box);
|
|||||||
GtkListStore *userlist_create_model (session *sess);
|
GtkListStore *userlist_create_model (session *sess);
|
||||||
void userlist_show (session *sess);
|
void userlist_show (session *sess);
|
||||||
void userlist_select (session *sess, char *name);
|
void userlist_select (session *sess, char *name);
|
||||||
|
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
||||||
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
|
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
|
||||||
GdkPixbuf *get_user_icon (server *serv, struct User *user);
|
GdkPixbuf *get_user_icon (server *serv, struct User *user);
|
||||||
|
|
||||||
|
|||||||
@@ -286,10 +286,12 @@ xtext_adj_get_page_increment (GtkAdjustment *adj)
|
|||||||
static void
|
static void
|
||||||
xtext_adjustment_apply (GtkAdjustment *adj, gdouble lower, gdouble upper, gdouble value, gdouble page_size)
|
xtext_adjustment_apply (GtkAdjustment *adj, gdouble lower, gdouble upper, gdouble value, gdouble page_size)
|
||||||
{
|
{
|
||||||
gtk_adjustment_set_lower (adj, lower);
|
gdouble step_increment = page_size * prefs.hex_gui_mouse_scroll_speed / 100.0;
|
||||||
gtk_adjustment_set_upper (adj, upper);
|
|
||||||
gtk_adjustment_set_page_size (adj, page_size);
|
if (step_increment < 1)
|
||||||
gtk_adjustment_set_value (adj, value);
|
step_increment = 1;
|
||||||
|
|
||||||
|
gtk_adjustment_configure (adj, value, lower, upper, step_increment, page_size, page_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
|
|||||||
@@ -647,6 +647,11 @@ void
|
|||||||
fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
|
fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
fe_set_typing (struct session *sess, const char *nick, const char *state)
|
||||||
|
{
|
||||||
|
}
|
||||||
void
|
void
|
||||||
fe_cleanup (void)
|
fe_cleanup (void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#define PACKAGE_VERSION "<#= [string]::Join('.', $versionParts) #>"
|
#define PACKAGE_VERSION "<#= [string]::Join('.', $versionParts) #>"
|
||||||
#define ZOITECHATLIBDIR ".\\plugins"
|
#define ZOITECHATLIBDIR ".\\plugins"
|
||||||
#define ZOITECHATSHAREDIR "."
|
#define ZOITECHATSHAREDIR "."
|
||||||
|
#define ZOITECHATDOCDIR "offline-docs"
|
||||||
#define OLD_PERL
|
#define OLD_PERL
|
||||||
#define GETTEXT_PACKAGE "zoitechat"
|
#define GETTEXT_PACKAGE "zoitechat"
|
||||||
#define PACKAGE_TARNAME "zoitechat-<#= [string]::Join('.', $versionParts) #>"
|
#define PACKAGE_TARNAME "zoitechat-<#= [string]::Join('.', $versionParts) #>"
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
<None Include="$(DepsRoot)\bin\*zlib*.dll" />
|
<None Include="$(DepsRoot)\bin\*zlib*.dll" />
|
||||||
<None Include="$(DepsRoot)\bin\*girepository*.dll" Condition="'$(LuaEnabled)'=='true' and '$(LuaLibDir)'!=''" />
|
<None Include="$(DepsRoot)\bin\*girepository*.dll" Condition="'$(LuaEnabled)'=='true' and '$(LuaLibDir)'!=''" />
|
||||||
<None Include="$(Python3Path)\Lib\site-packages\_cffi_backend.*.pyd" Condition="'$(Python3Enabled)'=='true'" />
|
<None Include="$(Python3Path)\Lib\site-packages\_cffi_backend.*.pyd" Condition="'$(Python3Enabled)'=='true'" />
|
||||||
|
<None Include="$(Python38Path)\Lib\site-packages\_cffi_backend.*.pyd" Condition="'$(Python38Enabled)'=='true'" />
|
||||||
|
|
||||||
<LuaLib Include="$(LuaLibDir)\**\*.dll" Condition="'$(LuaEnabled)'=='true' and '$(LuaLibDir)'!=''" />
|
<LuaLib Include="$(LuaLibDir)\**\*.dll" Condition="'$(LuaEnabled)'=='true' and '$(LuaLibDir)'!=''" />
|
||||||
<LuaShare Include="$(LuaShareDir)\*.lua" Condition="'$(LuaEnabled)'=='true' and '$(LuaShareDir)'!=''" />
|
<LuaShare Include="$(LuaShareDir)\*.lua" Condition="'$(LuaEnabled)'=='true' and '$(LuaShareDir)'!=''" />
|
||||||
@@ -105,10 +106,10 @@
|
|||||||
<Copy SourceFiles="@(LuaShare)" DestinationFiles="@(LuaShare->'$(ZoiteChatRel)\share\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(LuaShare)" DestinationFiles="@(LuaShare->'$(ZoiteChatRel)\share\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(LuaLib)" DestinationFiles="@(LuaLib->'$(ZoiteChatRel)\lib\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(LuaLib)" DestinationFiles="@(LuaLib->'$(ZoiteChatRel)\lib\lua\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(Typelib)" DestinationFiles="@(Typelib->'$(ZoiteChatRel)\lib\girepository-1.0\%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(Typelib)" DestinationFiles="@(Typelib->'$(ZoiteChatRel)\lib\girepository-1.0\%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="..\..\plugins\python\xchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true'" />
|
<Copy SourceFiles="..\..\plugins\python\xchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||||
<Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true'" />
|
<Copy SourceFiles="..\..\plugins\python\hexchat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||||
<Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true'" />
|
<Copy SourceFiles="..\..\plugins\python\zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||||
<Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true'" />
|
<Copy SourceFiles="..\..\plugins\python\_zoitechat.py" DestinationFolder="$(ZoiteChatRel)\python" Condition="'$(Python3Enabled)'=='true' or '$(Python38Enabled)'=='true'" />
|
||||||
|
|
||||||
<WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" />
|
<WriteLinesToFile File="$(ZoiteChatRel)portable-mode" Lines="2" Overwrite="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ AppUpdatesURL=http://zoitechat.org/downloads.html
|
|||||||
LicenseFile=share\doc\zoitechat\COPYING
|
LicenseFile=share\doc\zoitechat\COPYING
|
||||||
UninstallDisplayIcon={app}\zoitechat.exe
|
UninstallDisplayIcon={app}\zoitechat.exe
|
||||||
UninstallDisplayName=ZoiteChat
|
UninstallDisplayName=ZoiteChat
|
||||||
DefaultDirName={pf64}\ZoiteChat
|
DefaultDirName={commonpf64}\ZoiteChat
|
||||||
DisableDirPage=no
|
DisableDirPage=no
|
||||||
DefaultGroupName=ZoiteChat
|
DefaultGroupName=ZoiteChat
|
||||||
AllowNoIcons=yes
|
AllowNoIcons=yes
|
||||||
@@ -28,11 +28,11 @@ OutputBaseFilename={#APPNAM}-{#APPVER}_x64
|
|||||||
FlatComponentsList=no
|
FlatComponentsList=no
|
||||||
PrivilegesRequired=none
|
PrivilegesRequired=none
|
||||||
ShowComponentSizes=no
|
ShowComponentSizes=no
|
||||||
CreateUninstallRegKey=not IsTaskSelected('portable')
|
CreateUninstallRegKey=not WizardIsTaskSelected('portable')
|
||||||
Uninstallable=not IsTaskSelected('portable')
|
Uninstallable=not WizardIsTaskSelected('portable')
|
||||||
ArchitecturesAllowed=x64compatible
|
ArchitecturesAllowed=x64compatible
|
||||||
ArchitecturesInstallIn64BitMode=x64compatible
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
MinVersion=6.1
|
MinVersion=6.1sp1
|
||||||
WizardImageFile={#PROJECTDIR}wizardimage.bmp
|
WizardImageFile={#PROJECTDIR}wizardimage.bmp
|
||||||
WizardSmallImageFile={#PROJECTDIR}wizardsmallimage.bmp
|
WizardSmallImageFile={#PROJECTDIR}wizardsmallimage.bmp
|
||||||
SetupIconFile={#PROJECTDIR}..\..\data\icons\zoitechat.ico
|
SetupIconFile={#PROJECTDIR}..\..\data\icons\zoitechat.ico
|
||||||
@@ -49,6 +49,7 @@ Name: "icons"; Description: "Create Shortcuts"; Types: custom; Flags: disablenou
|
|||||||
Name: "icons\desktopicon"; Description: "Create Desktop Shortcut"; Types: custom; Flags: disablenouninstallwarning
|
Name: "icons\desktopicon"; Description: "Create Desktop Shortcut"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "icons\quicklaunchicon"; Description: "Create Quick Launch Shortcut"; Types: custom; Flags: disablenouninstallwarning
|
Name: "icons\quicklaunchicon"; Description: "Create Quick Launch Shortcut"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "translations"; Description: "Translations"; Types: normal custom; Flags: disablenouninstallwarning
|
Name: "translations"; Description: "Translations"; Types: normal custom; Flags: disablenouninstallwarning
|
||||||
|
Name: "docs"; Description: "Offline Documentation"; Types: normal custom; Flags: disablenouninstallwarning
|
||||||
Name: "spell"; Description: "Spelling Dictionaries"; Types: custom; Flags: disablenouninstallwarning
|
Name: "spell"; Description: "Spelling Dictionaries"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "plugins"; Description: "Plugins"; Types: custom; Flags: disablenouninstallwarning
|
Name: "plugins"; Description: "Plugins"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "plugins\checksum"; Description: "Checksum"; Types: custom; Flags: disablenouninstallwarning
|
Name: "plugins\checksum"; Description: "Checksum"; Types: custom; Flags: disablenouninstallwarning
|
||||||
@@ -59,7 +60,7 @@ Name: "plugins\upd"; Description: "Update Checker"; Types: normal custom; Flags:
|
|||||||
Name: "langs"; Description: "Language Interfaces"; Types: custom; Flags: disablenouninstallwarning
|
Name: "langs"; Description: "Language Interfaces"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "langs\lua"; Description: "Lua (LuaJIT 2.1)"; Types: normal custom; Flags: disablenouninstallwarning
|
Name: "langs\lua"; Description: "Lua (LuaJIT 2.1)"; Types: normal custom; Flags: disablenouninstallwarning
|
||||||
Name: "langs\perl"; Description: "Perl (Strawberry Perl 5.42.0.1)"; Types: custom; Flags: disablenouninstallwarning
|
Name: "langs\perl"; Description: "Perl (Strawberry Perl 5.42.0.1)"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "langs\python"; Description: "Python (Python 3.14.3)"; Types: custom; Flags: disablenouninstallwarning
|
Name: "langs\python"; Description: "Python (Python 3.14.3, or 3.8.10 before Windows 10)"; Types: custom; Flags: disablenouninstallwarning
|
||||||
Name: "themes"; Description: "GTK3 Themes"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
Name: "themes"; Description: "GTK3 Themes"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
||||||
Name: "themes\windows10"; Description: "Windows 10"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
Name: "themes\windows10"; Description: "Windows 10"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
||||||
Name: "themes\windows10dark"; Description: "Windows 10 Dark"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
Name: "themes\windows10dark"; Description: "Windows 10 Dark"; Types: normal minimal custom; Flags: disablenouninstallwarning
|
||||||
@@ -109,6 +110,9 @@ Source: "share\themes\MS-Windows\*"; DestDir: "{app}\share\themes\MS-Windows"; F
|
|||||||
Source: "share\glib-2.0\schemas\*"; DestDir: "{app}\share\glib-2.0\schemas"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
Source: "share\glib-2.0\schemas\*"; DestDir: "{app}\share\glib-2.0\schemas"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
||||||
Source: "share\icons\hicolor\*"; DestDir: "{app}\share\icons\hicolor"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
Source: "share\icons\hicolor\*"; DestDir: "{app}\share\icons\hicolor"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
||||||
Source: "share\locale\*"; DestDir: "{app}\share\locale"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: translations
|
Source: "share\locale\*"; DestDir: "{app}\share\locale"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: translations
|
||||||
|
; Staged at build time by data\misc\build_offline_docs.py; absent when the
|
||||||
|
; docs could not be built, in which case Help->Contents opens the website
|
||||||
|
Source: "offline-docs\*"; DestDir: "{app}\offline-docs"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: docs
|
||||||
Source: "etc\fonts\*"; DestDir: "{app}\etc\fonts"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
Source: "etc\fonts\*"; DestDir: "{app}\etc\fonts"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
||||||
|
|
||||||
Source: "atk-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
Source: "atk-1.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
||||||
@@ -181,7 +185,8 @@ Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Co
|
|||||||
|
|
||||||
Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python
|
Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python
|
||||||
|
|
||||||
Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python
|
Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python; Check: not NeedsLegacyPython
|
||||||
|
Source: "plugins\hcpython38.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\python; Check: NeedsLegacyPython
|
||||||
Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python
|
Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python
|
||||||
|
|
||||||
Source: "zoitechat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
Source: "zoitechat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
||||||
@@ -328,6 +333,32 @@ begin
|
|||||||
Result := DirExists(ExpandConstant('{localappdata}') + '\enchant\myspell');
|
Result := DirExists(ExpandConstant('{localappdata}') + '\enchant\myspell');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Python >= 3.9 dropped support for Windows versions before 10;
|
||||||
|
// those systems get Python 3.8 and the matching plugin instead.
|
||||||
|
function NeedsLegacyPython(): Boolean;
|
||||||
|
var
|
||||||
|
Version: TWindowsVersion;
|
||||||
|
begin
|
||||||
|
GetWindowsVersionEx(Version);
|
||||||
|
Result := Version.NTPlatform and (Version.Major < 10);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function PythonURL(): String;
|
||||||
|
begin
|
||||||
|
if NeedsLegacyPython() then
|
||||||
|
Result := 'https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe'
|
||||||
|
else
|
||||||
|
Result := 'https://www.python.org/ftp/python/3.14.3/python-3.14.3-amd64.exe';
|
||||||
|
end;
|
||||||
|
|
||||||
|
function PythonDLL(): String;
|
||||||
|
begin
|
||||||
|
if NeedsLegacyPython() then
|
||||||
|
Result := 'python38.dll'
|
||||||
|
else
|
||||||
|
Result := 'python314.dll';
|
||||||
|
end;
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// Sets up the automatic downloads
|
// Sets up the automatic downloads
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -345,34 +376,34 @@ begin
|
|||||||
begin
|
begin
|
||||||
idpClearFiles;
|
idpClearFiles;
|
||||||
|
|
||||||
if IsComponentSelected('themes\windows10') then
|
if WizardIsComponentSelected('themes\windows10') then
|
||||||
idpAddFile('https://dl.zoitechat.zoite.net/themes/GTK3Themes/Windows-10-3.2.1.zip', ExpandConstant('{tmp}\Windows-10-3.2.1.zip'));
|
idpAddFile('https://dl.zoitechat.zoite.net/themes/GTK3Themes/Windows-10-3.2.1.zip', ExpandConstant('{tmp}\Windows-10-3.2.1.zip'));
|
||||||
|
|
||||||
if IsComponentSelected('themes\windows10dark') then
|
if WizardIsComponentSelected('themes\windows10dark') then
|
||||||
idpAddFile('https://dl.zoitechat.zoite.net/themes/GTK3Themes/Windows-10-Dark-3.2.1-dark.zip', ExpandConstant('{tmp}\Windows-10-Dark-3.2.1-dark.zip'));
|
idpAddFile('https://dl.zoitechat.zoite.net/themes/GTK3Themes/Windows-10-Dark-3.2.1-dark.zip', ExpandConstant('{tmp}\Windows-10-Dark-3.2.1-dark.zip'));
|
||||||
|
|
||||||
if not IsTaskSelected('portable') then
|
if not WizardIsTaskSelected('portable') then
|
||||||
begin
|
begin
|
||||||
|
|
||||||
REDIST := 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
|
REDIST := 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
|
||||||
PERL := 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_54201_64bit/strawberry-perl-5.42.0.1-64bit.msi';
|
PERL := 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_54201_64bit/strawberry-perl-5.42.0.1-64bit.msi';
|
||||||
PY3 := 'https://www.python.org/ftp/python/3.14.3/python-3.14.3-amd64.exe';
|
PY3 := PythonURL();
|
||||||
SPELL := 'https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.16.2/ZoiteChat.Spelling.Dictionaries.r2.exe';
|
SPELL := 'https://github.com/hexchat/gvsbuild/releases/download/hexchat-2.16.2/HexChat.Spelling.Dictionaries.r2.exe';
|
||||||
|
|
||||||
if IsComponentSelected('deps\vcredist2015') and not CheckVCInstall() then
|
if WizardIsComponentSelected('deps\vcredist2015') and not CheckVCInstall() then
|
||||||
idpAddFile(REDIST, ExpandConstant('{tmp}\vcredist.exe'));
|
idpAddFile(REDIST, ExpandConstant('{tmp}\vcredist.exe'));
|
||||||
|
|
||||||
if IsComponentSelected('spell') and not CheckSpellInstall() then
|
if WizardIsComponentSelected('spell') and not CheckSpellInstall() then
|
||||||
idpAddFile(SPELL, ExpandConstant('{tmp}\spelling-dicts.exe'));
|
idpAddFile(SPELL, ExpandConstant('{tmp}\spelling-dicts.exe'));
|
||||||
|
|
||||||
if not WizardSilent() then
|
if not WizardSilent() then
|
||||||
begin
|
begin
|
||||||
if IsComponentSelected('langs\perl') and not CheckDLL('perl542.dll') then
|
if WizardIsComponentSelected('langs\perl') and not CheckDLL('perl542.dll') then
|
||||||
begin
|
begin
|
||||||
idpAddFile(PERL, ExpandConstant('{tmp}\perl.msi'))
|
idpAddFile(PERL, ExpandConstant('{tmp}\perl.msi'))
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if IsComponentSelected('langs\python') and not CheckDLL('python314.dll') then
|
if WizardIsComponentSelected('langs\python') and not CheckDLL(PythonDLL()) then
|
||||||
idpAddFile(PY3, ExpandConstant('{tmp}\python.exe'));
|
idpAddFile(PY3, ExpandConstant('{tmp}\python.exe'));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -397,7 +428,7 @@ begin
|
|||||||
|
|
||||||
if (CurPageID = wpSelectTasks) then
|
if (CurPageID = wpSelectTasks) then
|
||||||
if (WizardForm.TasksList.Checked[1] = True) then
|
if (WizardForm.TasksList.Checked[1] = True) then
|
||||||
if (WizardDirValue() = ExpandConstant('{pf64}\ZoiteChat')) then
|
if (WizardDirValue() = ExpandConstant('{commonpf64}\ZoiteChat')) then
|
||||||
begin
|
begin
|
||||||
WizardForm.TasksList.Checked[1] := False
|
WizardForm.TasksList.Checked[1] := False
|
||||||
MsgBox('Portable mode is only intended for use on portable drives and has been disabled.', mbInformation, MB_OK)
|
MsgBox('Portable mode is only intended for use on portable drives and has been disabled.', mbInformation, MB_OK)
|
||||||
@@ -405,21 +436,21 @@ begin
|
|||||||
|
|
||||||
if CurPageID = wpReady then
|
if CurPageID = wpReady then
|
||||||
begin
|
begin
|
||||||
if IsComponentSelected('themes\windows10') and not FileExists(ExpandConstant('{tmp}\Windows-10-3.2.1.zip')) then
|
if WizardIsComponentSelected('themes\windows10') and not FileExists(ExpandConstant('{tmp}\Windows-10-3.2.1.zip')) then
|
||||||
begin
|
begin
|
||||||
MsgBox('Windows 10 GTK3 theme could not be downloaded. Please retry setup and rerun setup.', mbError, MB_OK);
|
MsgBox('Windows 10 GTK3 theme could not be downloaded. Please retry setup and rerun setup.', mbError, MB_OK);
|
||||||
Result := False;
|
Result := False;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if IsComponentSelected('themes\windows10dark') and not FileExists(ExpandConstant('{tmp}\Windows-10-Dark-3.2.1-dark.zip')) then
|
if WizardIsComponentSelected('themes\windows10dark') and not FileExists(ExpandConstant('{tmp}\Windows-10-Dark-3.2.1-dark.zip')) then
|
||||||
begin
|
begin
|
||||||
MsgBox('Windows 10 Dark GTK3 theme could not be downloaded. Please retry setup and rerun setup.', mbError, MB_OK);
|
MsgBox('Windows 10 Dark GTK3 theme could not be downloaded. Please retry setup and rerun setup.', mbError, MB_OK);
|
||||||
Result := False;
|
Result := False;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if IsComponentSelected('deps\vcredist2015') and not CheckVCInstall() and not FileExists(ExpandConstant('{tmp}\vcredist.exe')) then
|
if WizardIsComponentSelected('deps\vcredist2015') and not CheckVCInstall() and not FileExists(ExpandConstant('{tmp}\vcredist.exe')) then
|
||||||
begin
|
begin
|
||||||
MsgBox('Visual C++ Redistributable could not be downloaded. Please retry setup or install it manually and rerun setup.', mbError, MB_OK);
|
MsgBox('Visual C++ Redistributable could not be downloaded. Please retry setup or install it manually and rerun setup.', mbError, MB_OK);
|
||||||
Result := False;
|
Result := False;
|
||||||
@@ -486,7 +517,7 @@ end;
|
|||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
begin
|
begin
|
||||||
if not (IsTaskSelected('portable')) then
|
if not (WizardIsTaskSelected('portable')) then
|
||||||
begin
|
begin
|
||||||
if (CurStep=ssInstall) then
|
if (CurStep=ssInstall) then
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.18.1
|
2.18.3
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<YourGendefPath>c:\gtk-build\gendef</YourGendefPath>
|
<YourGendefPath>c:\gtk-build\gendef</YourGendefPath>
|
||||||
<YourPerlPath>c:\gtk-build\perl-5.42.0.1</YourPerlPath>
|
<YourPerlPath>c:\gtk-build\perl-5.42.0.1</YourPerlPath>
|
||||||
<YourPython3Path>c:\gtk-build\python-3.14</YourPython3Path>
|
<YourPython3Path>c:\gtk-build\python-3.14</YourPython3Path>
|
||||||
|
<YourPython38Path>c:\gtk-build\python-3.8</YourPython38Path>
|
||||||
<YourWinSparklePath>c:\gtk-build\WinSparkle</YourWinSparklePath>
|
<YourWinSparklePath>c:\gtk-build\WinSparkle</YourWinSparklePath>
|
||||||
|
|
||||||
<!-- YOU SHOULDN'T TOUCH ANYTHING BELOW -->
|
<!-- YOU SHOULDN'T TOUCH ANYTHING BELOW -->
|
||||||
@@ -32,6 +33,12 @@
|
|||||||
<Python3Enabled Condition="Exists('$(Python3Path)\\python.exe') and Exists('$(Python3Path)\\libs\\$(Python3Lib).lib') and Exists('$(Python3Path)\\Lib\\site-packages\\cffi')">true</Python3Enabled>
|
<Python3Enabled Condition="Exists('$(Python3Path)\\python.exe') and Exists('$(Python3Path)\\libs\\$(Python3Lib).lib') and Exists('$(Python3Path)\\Lib\\site-packages\\cffi')">true</Python3Enabled>
|
||||||
<Python3Enabled Condition="'$(Python3Enabled)'==''">false</Python3Enabled>
|
<Python3Enabled Condition="'$(Python3Enabled)'==''">false</Python3Enabled>
|
||||||
|
|
||||||
|
<Python38Path>$(YourPython38Path)\$(ZoiteChatPlatform)</Python38Path>
|
||||||
|
<Python38Lib>python38</Python38Lib>
|
||||||
|
<Python38Output>hcpython38</Python38Output>
|
||||||
|
<Python38Enabled Condition="Exists('$(Python38Path)\\python.exe') and Exists('$(Python38Path)\\libs\\$(Python38Lib).lib') and Exists('$(Python38Path)\\Lib\\site-packages\\cffi')">true</Python38Enabled>
|
||||||
|
<Python38Enabled Condition="'$(Python38Enabled)'==''">false</Python38Enabled>
|
||||||
|
|
||||||
<PerlEnabled Condition="Exists('$(PerlPath)\\bin\\perl.exe') and Exists('$(PerlPath)\\bin\\$(PerlLib).dll') and Exists('$(GendefPath)\\gendef.exe')">true</PerlEnabled>
|
<PerlEnabled Condition="Exists('$(PerlPath)\\bin\\perl.exe') and Exists('$(PerlPath)\\bin\\$(PerlLib).dll') and Exists('$(GendefPath)\\gendef.exe')">true</PerlEnabled>
|
||||||
<PerlEnabled Condition="'$(PerlEnabled)'==''">false</PerlEnabled>
|
<PerlEnabled Condition="'$(PerlEnabled)'==''">false</PerlEnabled>
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ EndProject
|
|||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcxproj", "{5A0F4962-E670-4DA2-9E45-52CC47F26E2F}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcxproj", "{5A0F4962-E670-4DA2-9E45-52CC47F26E2F}"
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF} = {C2321A03-0BA7-45B3-8740-ABD82B36B0BF}
|
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF} = {C2321A03-0BA7-45B3-8740-ABD82B36B0BF}
|
||||||
|
{D58924D2-1492-4AC3-B058-7A214424EEDC} = {D58924D2-1492-4AC3-B058-7A214424EEDC}
|
||||||
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} = {BF0EBC16-68AD-4CD1-864C-5B56836EBE2A}
|
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} = {BF0EBC16-68AD-4CD1-864C-5B56836EBE2A}
|
||||||
{17E4BE39-76F7-4A06-AD21-EFD0C5091F76} = {17E4BE39-76F7-4A06-AD21-EFD0C5091F76}
|
{17E4BE39-76F7-4A06-AD21-EFD0C5091F76} = {17E4BE39-76F7-4A06-AD21-EFD0C5091F76}
|
||||||
{4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} = {4C0F3940-2EEE-4646-82F7-6CE75B9A72F4}
|
{4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} = {4C0F3940-2EEE-4646-82F7-6CE75B9A72F4}
|
||||||
@@ -80,6 +81,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "perl", "..\plugins\perl\per
|
|||||||
{87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3}
|
{87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3}
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python38", "..\plugins\python\python38.vcxproj", "{D58924D2-1492-4AC3-B058-7A214424EEDC}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python3", "..\plugins\python\python3.vcxproj", "{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python3", "..\plugins\python\python3.vcxproj", "{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}"
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
{87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3}
|
{87554B59-006C-4D94-9714-897B27067BA3} = {87554B59-006C-4D94-9714-897B27067BA3}
|
||||||
@@ -127,6 +133,8 @@ Global
|
|||||||
{D90BC3E3-1341-4849-9354-5F40489D39D1}.Release|x64.Build.0 = Release|x64
|
{D90BC3E3-1341-4849-9354-5F40489D39D1}.Release|x64.Build.0 = Release|x64
|
||||||
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}.Release|x64.ActiveCfg = Release|x64
|
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}.Release|x64.ActiveCfg = Release|x64
|
||||||
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}.Release|x64.Build.0 = Release|x64
|
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF}.Release|x64.Build.0 = Release|x64
|
||||||
|
{D58924D2-1492-4AC3-B058-7A214424EEDC}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{D58924D2-1492-4AC3-B058-7A214424EEDC}.Release|x64.Build.0 = Release|x64
|
||||||
{C53145CC-D021-40C9-B97C-0249AB9A43C9}.Release|x64.ActiveCfg = Release|x64
|
{C53145CC-D021-40C9-B97C-0249AB9A43C9}.Release|x64.ActiveCfg = Release|x64
|
||||||
{C53145CC-D021-40C9-B97C-0249AB9A43C9}.Release|x64.Build.0 = Release|x64
|
{C53145CC-D021-40C9-B97C-0249AB9A43C9}.Release|x64.Build.0 = Release|x64
|
||||||
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A}.Release|x64.ActiveCfg = Release|x64
|
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A}.Release|x64.ActiveCfg = Release|x64
|
||||||
@@ -151,6 +159,7 @@ Global
|
|||||||
{5A0F4962-E670-4DA2-9E45-52CC47F26E2F} = {0FD996A7-464F-4981-8380-3DCA3A244A13}
|
{5A0F4962-E670-4DA2-9E45-52CC47F26E2F} = {0FD996A7-464F-4981-8380-3DCA3A244A13}
|
||||||
{D90BC3E3-1341-4849-9354-5F40489D39D1} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
{D90BC3E3-1341-4849-9354-5F40489D39D1} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
||||||
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
{C2321A03-0BA7-45B3-8740-ABD82B36B0BF} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
||||||
|
{D58924D2-1492-4AC3-B058-7A214424EEDC} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
||||||
{C53145CC-D021-40C9-B97C-0249AB9A43C9} = {561126F4-FA18-45FC-A2BF-8F858F161D6D}
|
{C53145CC-D021-40C9-B97C-0249AB9A43C9} = {561126F4-FA18-45FC-A2BF-8F858F161D6D}
|
||||||
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} = {021EC1D0-FF67-4700-9AB2-EAABF1159C09}
|
{BF0EBC16-68AD-4CD1-864C-5B56836EBE2A} = {021EC1D0-FF67-4700-9AB2-EAABF1159C09}
|
||||||
{4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
{4C0F3940-2EEE-4646-82F7-6CE75B9A72F4} = {D237DA6B-BD5F-46C0-8BEA-50E9A1340240}
|
||||||
|
|||||||
Reference in New Issue
Block a user