Compare commits

..

2 Commits

Author SHA1 Message Date
fbee94cb81 Remove arch from CTCP VERSION on win32
Also probably not useful information.
2017-11-20 18:40:31 -05:00
779d373e1d Remove system info from CTCP VERSION
This is a common complaint and its honestly totally useless noise.

This also removes it from user commands which was also not very useful
and it was a decent chunk of not portable code.
2017-11-18 23:12:15 -05:00
219 changed files with 45225 additions and 34033 deletions

View File

@ -1,17 +0,0 @@
name: Flatpak Build
on: [push, pull_request]
jobs:
flatpak_build:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-40
options: --privileged
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v3
with:
bundle: hexchat.flatpak
manifest-path: flatpak/io.github.Hexchat.json

View File

@ -1,41 +0,0 @@
name: MSYS2 Build
on: [push, pull_request]
jobs:
msys2_build:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v2
- uses: msys2/setup-msys2@v2
with:
install: >-
mingw-w64-x86_64-gcc
mingw-w64-x86_64-pkg-config
mingw-w64-x86_64-python3-cffi
mingw-w64-x86_64-meson
mingw-w64-x86_64-gtk2
mingw-w64-x86_64-gtk-update-icon-cache
mingw-w64-x86_64-luajit
mingw-w64-x86_64-desktop-file-utils
- name: Configure
run: >-
meson build
-Dtext-frontend=true
-Ddbus=disabled
-Dwith-upd=false
-Dwith-perl=false
- name: Build
run: ninja -C build
- name: Test
run: ninja -C build test
- name: Install
run: ninja -C build install

View File

@ -1,25 +0,0 @@
name: Ubuntu Build
on: [push, pull_request]
jobs:
ubuntu_build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y meson libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libpci-dev libperl-dev libssl-dev python3-dev python3-cffi mono-devel desktop-file-utils
- name: Configure
run: meson build -Dtext=true -Dtheme-manager=true -Dauto_features=enabled
- name: Build
run: ninja -C build
- name: Test
run: ninja -C build test
- name: Install
run: sudo ninja -C build install

View File

@ -1,72 +0,0 @@
name: Windows Build
on: [push, pull_request]
jobs:
windows_build:
runs-on: windows-2019
strategy:
matrix:
platform: [x64, win32]
arch: [x64, x86]
exclude:
- platform: x64
arch: x86
- platform: win32
arch: x64
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
New-Item -Name "deps" -ItemType "Directory"
Invoke-WebRequest http://files.jrsoftware.org/is/5/innosetup-5.5.9-unicode.exe -OutFile deps\innosetup-unicode.exe
& deps\innosetup-unicode.exe /VERYSILENT | Out-Null
Invoke-WebRequest https://dl.hexchat.net/misc/idpsetup-1.5.1.exe -OutFile deps\idpsetup.exe
& deps\idpsetup.exe /VERYSILENT
Invoke-WebRequest https://dl.hexchat.net/gtk/gtk-${{ matrix.platform }}-2018-08-29-openssl1.1.7z -OutFile deps\gtk-${{ matrix.arch }}.7z
& 7z.exe x deps\gtk-${{ matrix.arch }}.7z -oC:\gtk-build\gtk
Invoke-WebRequest https://dl.hexchat.net/gtk-win32/gendef-20111031.7z -OutFile deps\gendef.7z
& 7z.exe x deps\gendef.7z -oC:\gtk-build
Invoke-WebRequest https://dl.hexchat.net/gtk-win32/WinSparkle-20151011.7z -OutFile deps\WinSparkle.7z
& 7z.exe x deps\WinSparkle.7z -oC:\gtk-build\WinSparkle
Invoke-WebRequest https://dl.hexchat.net/misc/perl/perl-5.20.0-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z
& 7z.exe x deps\perl-${{ matrix.arch }}.7z -oC:\gtk-build\perl-5.20\${{ matrix.platform }}
New-Item -Path "c:\gtk-build" -Name "python-2.7" -ItemType "Directory"
New-Item -Path "c:\gtk-build" -Name "python-3.8" -ItemType "Directory"
New-Item -Path "c:\gtk-build\python-2.7" -Name "${{ matrix.platform }}" -ItemType "SymbolicLink" -Value "C:/hostedtoolcache/windows/Python/2.7.18/${{ matrix.arch }}"
New-Item -Path "c:\gtk-build\python-3.8" -Name "${{ matrix.platform }}" -ItemType "SymbolicLink" -Value "C:/hostedtoolcache/windows/Python/3.8.10/${{ matrix.arch }}"
C:/hostedtoolcache/windows/Python/3.8.10/${{ matrix.arch }}/python.exe -m pip install cffi
C:/hostedtoolcache/windows/Python/2.7.18/${{ matrix.arch }}/python.exe -m pip install -qq cffi
shell: powershell
- name: Build
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
msbuild win32\hexchat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
shell: cmd
- name: Preparing Artifacts
run: |
move ..\hexchat-build\${{ matrix.platform }}\HexChat*.exe .\
move ..\hexchat-build .\
shell: cmd
- uses: actions/upload-artifact@v2
with:
name: Installer ${{ matrix.arch }}
path: HexChat*.exe
- uses: actions/upload-artifact@v2
with:
name: Build Files ${{ matrix.arch }}
path: hexchat-build

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "flatpak/shared-modules"]
path = flatpak/shared-modules
url = https://github.com/flathub/shared-modules.git

View File

@ -1,5 +0,0 @@
extraction:
cpp:
prepare:
packages:
- python3-cffi

20
.travis.yml Normal file
View File

@ -0,0 +1,20 @@
sudo: required
services: docker
before_install:
- docker pull ubuntu:16.04
- docker run --privileged --cidfile=/tmp/cid ubuntu:16.04 /bin/sh -c 'apt-get update && apt-get install -y meson/xenial-backports libcanberra-dev libdbus-glib-1-dev libglib2.0-dev libgtk2.0-dev libluajit-5.1-dev libnotify-dev libpci-dev libperl-dev libproxy-dev libssl-dev python3-dev mono-devel desktop-file-utils'
- docker commit `cat /tmp/cid` hexchat/ubuntu-ci
- rm -f /tmp/cid
install:
- docker run -d --privileged --cidfile=/tmp/cid --volume=${PWD}:/opt/hexchat hexchat/ubuntu-ci /bin/systemd --system
script:
- docker exec `cat /tmp/cid` /bin/sh -c 'meson /opt/hexchat /opt/hexchat-build -Dwith-text=true -Dwith-theme-manager=true && ninja -C /opt/hexchat-build install'
after_script:
- docker kill `cat /tmp/cid`
notifications:
irc:
channels: "chat.freenode.net#hexchat-devel"
template: "Build %{build_url} (%{commit} in %{branch}) by %{author}: %{message}"
on_success: change
matrix:
fast_finish: true

View File

@ -1,11 +1,9 @@
icondir = join_paths(get_option('datadir'), 'icons/hicolor')
install_data(
'hexchat.png',
rename: 'io.github.Hexchat.png',
install_dir: join_paths(icondir, '48x48/apps')
)
install_data(
'hexchat.svg',
rename: 'io.github.Hexchat.svg',
install_dir: join_paths(icondir, 'scalable/apps')
)

View File

@ -1,11 +1,11 @@
if get_option('plugin')
if get_option('with-plugin')
subdir('pkgconfig')
endif
if get_option('gtk-frontend')
if get_option('with-gtk')
subdir('icons')
subdir('misc')
subdir('man')
elif get_option('theme-manager')
elif get_option('with-theme-manager')
subdir('misc')
endif

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>hexchat.desktop</id>
<name>HexChat</name>
<developer_name>HexChat</developer_name>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0+</project_license>
<translation type="gettext">hexchat</translation>
<summary>IRC Client</summary>
<description>
<p>HexChat is an easy to use yet extensible IRC Client. It allows you to securely join multiple networks and talk to users privately or in channels using a customizable interface. You can even transfer files.</p>
<p>HexChat supports features such as: DCC, SASL, proxies, spellcheck, alerts, logging, custom themes, and Python/Perl scripts.</p>
</description>
<url type="homepage">http://hexchat.github.io</url>
<url type="bugtracker">https://github.com/hexchat/hexchat</url>
<url type="translate">https://www.transifex.com/hexchat/hexchat</url>
<url type="donation">https://goo.gl/jESZvU</url>
<url type="help">https://hexchat.readthedocs.io/en/latest/</url>
<screenshots>
<screenshot type="default">
<image>http://i.imgur.com/tLMguQz.png</image>
<caption>Main Chat Window</caption>
</screenshot>
</screenshots>
<releases>
<release date="2016-12-10" version="2.12.4">
<description>
<p>This is another bug fix release:</p>
<ul>
<li>Fix issue with timers causing ping timeouts</li>
<li>Fix building against OpenSSL 1.1</li>
<li>Fix /exec output printing invalid utf8</li>
<li>Replace doat plugin with an internal command</li>
<li>Change how tab colors interact with plugins</li>
<li>Enable filtering the beep character by default</li>
</ul>
</description>
</release>
<release date="2016-10-22" version="2.12.3">
<description>
<p>This is a minor bug fix release just cleaning up a few issues:</p>
<ul>
<li>Fix crash with bad translations</li>
<li>Add new mhop command</li>
<li>Change ping timeout to 60 by default</li>
</ul>
</description>
</release>
</releases>
<kudos>
<kudo>UserDocs</kudo>
<kudo>HiDpiIcon</kudo>
<kudo>Notifications</kudo>
</kudos>
<update_contact>tingping_at_fedoraproject.org</update_contact>
</component>

View File

@ -4,12 +4,11 @@ GenericName=IRC Client
Comment=Chat with other people online
Keywords=IM;Chat;
Exec=@exec_command@
Icon=io.github.Hexchat
Icon=hexchat
Terminal=false
Type=Application
Categories=GTK;Network;IRCClient;
StartupNotify=true
StartupWMClass=Hexchat
X-GNOME-UsesNotifications=true
MimeType=x-scheme-handler/irc;x-scheme-handler/ircs;
Actions=SafeMode;

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="addon">
<id>io.github.Hexchat.Plugin.@NAME@</id>
<extends>io.github.Hexchat</extends>
<name>@NAME@ Plugin</name>
<summary>@SUMMARY@</summary>
<url type="homepage">https://hexchat.github.io/</url>
<project_license>@LICENSE@</project_license>
<metadata_license>CC0-1.0</metadata_license>
<update_contact>tingping_AT_fedoraproject.org</update_contact>
</component>

View File

@ -1,151 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.Hexchat</id>
<name>HexChat</name>
<launchable type="desktop-id">io.github.Hexchat.desktop</launchable>
<developer_name>HexChat</developer_name>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0+</project_license>
<translation type="gettext">hexchat</translation>
<summary>IRC Client</summary>
<description>
<p>HexChat is an easy to use yet extensible IRC Client. It allows you to securely join multiple networks and talk to users privately or in channels using a customizable interface. You can even transfer files.</p>
<p>HexChat supports features such as: DCC, SASL, proxies, spellcheck, alerts, logging, custom themes, and Python/Perl scripts.</p>
</description>
<url type="homepage">http://hexchat.github.io</url>
<url type="bugtracker">https://github.com/hexchat/hexchat</url>
<url type="donation">https://goo.gl/jESZvU</url>
<url type="help">https://hexchat.readthedocs.io/en/latest/</url>
<screenshots>
<screenshot type="default">
<image>http://i.imgur.com/tLMguQz.png</image>
<caption>Main Chat Window</caption>
</screenshot>
</screenshots>
<provides>
<!-- Renamed from this -->
<id>hexchat.desktop</id>
</provides>
<releases>
<release date="2022-02-12" version="2.16.1">
<description>
<p>This is a minor release with mostly bug-fixes:</p>
<ul>
<li>Add `-NOOVERRIDE` flag to the `GUI COLOR` command</li>
<li>Add `-q` (quiet) flag to the `EXECWRITE` command</li>
<li>Rename installed icon to match app-id (Fixes notification icon)</li>
<li>Fix escaping already escaped URLs when opening them</li>
<li>Fix Python scripts not being opened as UTF-8</li>
<li>Fix `TIMER` command supporting decimals regardless of locale</li>
</ul>
</description>
</release>
<release date="2021-10-01" version="2.16.0">
<description>
<p>This is a feature release:</p>
<ul>
<li>Add support for IRCv3 SETNAME, invite-notify, account-tag, standard replies, and UTF8ONLY</li>
<li>Add support for strikethrough formatting</li>
<li>Fix text clipping issues by respecting font line height</li>
<li>Fix URLs not being escaped when opened</li>
<li>Fix possible hang when showing notifications</li>
<li>Print ChanServ notices in the front tab by default</li>
<li>Update network list</li>
<li>python: Rewrite plugin improving memory usage and compatibility</li>
<li>fishlim: Add support for CBC and other improvements</li>
</ul>
</description>
</release>
<release date="2019-12-20" version="2.14.3">
<description>
<p>This is a bug-fix release:</p>
<ul>
<li>Fix various incorrect parsing of IRC messages relating to trailing parameters</li>
<li>Fix SASL negotiation combined with multi-line cap</li>
<li>Fix input box theming with Yaru theme</li>
<li>python: Work around Python 3.7 regression causing crash on unload</li>
<li>sysinfo: Add support for /etc/os-release</li>
<li>sysinfo: Ignore irrelevant mounts when calculating storage size</li>
</ul>
</description>
</release>
<release date="2018-08-29" version="2.14.2">
<description>
<p>This is a minor release:</p>
<ul>
<li>Remove shift+click to close tab binding</li>
<li>Always unminimize when opening from tray</li>
<li>Fix some translations containing invalid text events</li>
<li>Fix sending server passwords starting with ":"</li>
</ul>
</description>
</release>
<release date="2018-03-13" version="2.14.1">
<description>
<p>This is a very minor bug-fix release:</p>
<ul>
<li>Fix performance regression</li>
</ul>
</description>
</release>
<release date="2018-03-10" version="2.14.0">
<description>
<p>This is largely a bug fix release though it has some large behind the scenes changes:</p>
<ul>
<li>Rename data files to use *io.github.Hexchat* name</li>
<li>Add option (irc_reconnect_rejoin) to disable auto-rejoin on reconnect</li>
<li>Add ability to set custom tray icon separate of app icon</li>
<li>Fix Enchant 2.0+ support</li>
<li>Fix input box theming with Adwaita-dark</li>
<li>Fix custom sounds not respecting omit if away option</li>
<li>Fix detecting if a tray doesn't exist on x11</li>
<li>Fix cutting off ctcp text after ending \01</li>
<li>Fix /ignore not accepting full hosts</li>
<li>Fix characters getting cut off when their width changes</li>
<li>Fix various possible crashes</li>
<li>Change preference window to be scroll-able</li>
<li>Remove ctrl+w binding by default</li>
<li>doat: Fix channels with / in them</li>
<li>fishlim: Fix key exchange</li>
<li>fishlim: Fix building against LibreSSL</li>
<li>sysinfo: Fix pci.ids file not being found on some distros</li>
<li>sysinfo: Make libpci optional</li>
<li>lua: Avoid loading the same script multiple times</li>
<li>Update translations</li>
</ul>
</description>
</release>
<release date="2016-12-10" version="2.12.4">
<description>
<p>This is another bug fix release:</p>
<ul>
<li>Fix issue with timers causing ping timeouts</li>
<li>Fix building against OpenSSL 1.1</li>
<li>Fix /exec output printing invalid utf8</li>
<li>Replace doat plugin with an internal command</li>
<li>Change how tab colors interact with plugins</li>
<li>Enable filtering the beep character by default</li>
</ul>
</description>
</release>
<release date="2016-10-22" version="2.12.3">
<description>
<p>This is a minor bug fix release just cleaning up a few issues:</p>
<ul>
<li>Fix crash with bad translations</li>
<li>Add new mhop command</li>
<li>Change ping timeout to 60 by default</li>
</ul>
</description>
</release>
</releases>
<kudos>
<kudo>UserDocs</kudo>
<kudo>HiDpiIcon</kudo>
<kudo>Notifications</kudo>
</kudos>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<update_contact>tingping_at_fedoraproject.org</update_contact>
</component>

View File

@ -1,41 +1,38 @@
appdir = join_paths(get_option('datadir'), 'applications')
metainfodir = join_paths(get_option('datadir'), 'metainfo')
desktop_utils = find_program('desktop-file-validate', required: false)
if get_option('gtk-frontend')
if get_option('install-appdata')
hexchat_appdata = i18n.merge_file(
input: 'io.github.Hexchat.appdata.xml.in',
output: 'io.github.Hexchat.appdata.xml',
po_dir: '../../po',
install: true,
install_dir: metainfodir
)
if get_option('with-gtk')
hexchat_appdata = i18n.merge_file(
input: 'hexchat.appdata.xml.in',
output: 'hexchat.appdata.xml',
po_dir: '../../po',
install: true,
install_dir: join_paths(get_option('datadir'), 'appdata')
)
appstream_util = find_program('appstream-util', required: false)
if appstream_util.found()
test('Validate io.github.Hexchat.appdata.xml', appstream_util,
args: ['validate-relax', hexchat_appdata]
)
endif
appstream_util = find_program('appstream-util', required: false)
if appstream_util.found()
test('Validate hexchat.appdata.xml', appstream_util,
args: ['validate', hexchat_appdata]
)
endif
desktop_conf = configuration_data()
if dbus_glib_dep.found()
if get_option('with-dbus')
desktop_conf.set('exec_command', 'hexchat --existing %U')
else
desktop_conf.set('exec_command', 'hexchat %U')
endif
desktop_file = configure_file(
input: 'io.github.Hexchat.desktop.in.in',
output: 'io.github.Hexchat.desktop.in',
input: 'hexchat.desktop.in.in',
output: 'hexchat.desktop.in',
configuration: desktop_conf
)
hexchat_desktop = i18n.merge_file(
input: desktop_file,
output: 'io.github.Hexchat.desktop',
output: 'hexchat.desktop',
po_dir: '../../po',
type: 'desktop',
install: true,
@ -43,16 +40,16 @@ if get_option('gtk-frontend')
)
if desktop_utils.found()
test('Validate io.github.Hexchat.desktop', desktop_utils,
test('Validate hexchat.desktop', desktop_utils,
args: [hexchat_desktop]
)
endif
endif
if get_option('theme-manager')
if get_option('with-theme-manager')
htm_desktop = i18n.merge_file(
input: 'io.github.Hexchat.ThemeManager.desktop.in',
output: 'io.github.Hexchat.ThemeManager.desktop',
input: 'htm.desktop.in',
output: 'htm.desktop',
po_dir: '../../po',
type: 'desktop',
install: true,
@ -60,68 +57,12 @@ if get_option('theme-manager')
)
if desktop_utils.found()
test('Validate io.github.Hexchat.ThemeManager.desktop', desktop_utils,
test('Validate htm.desktop', desktop_utils,
args: [htm_desktop]
)
endif
install_data('io.github.Hexchat.ThemeManager.xml',
install_data('htm-mime.xml',
install_dir: join_paths(get_option('datadir'), 'mime/packages')
)
endif
if get_option('plugin')
plugin_metainfo = []
# FIXME: These should all get translated somewhere
if get_option('with-checksum')
plugin_metainfo += [
['Checksum', 'Calculates a checksum for all sent and recieved DCC files', 'MIT']
]
endif
if get_option('with-fishlim')
plugin_metainfo += [
['Fishlim', 'Allows setting a key for encrypted conversations', 'MIT AND GPL-2.0+']
]
endif
if get_option('with-lua') != 'false'
plugin_metainfo += [
['Lua', 'Provides a scripting interface in Lua', 'MIT']
]
endif
if get_option('with-perl') != 'false'
plugin_metainfo += [
['Perl', 'Provides a scripting interface in Perl', 'GPL-2.0+']
]
endif
if get_option('with-python') != 'false'
plugin_metainfo += [
['Python', 'Provides a scripting interface in Python', 'GPL-2.0+']
]
endif
if get_option('with-sysinfo')
plugin_metainfo += [
['Sysinfo', 'Adds command to display system information', 'GPL-2.0+']
]
endif
foreach metainfo : plugin_metainfo
name = metainfo[0]
conf = configuration_data()
conf.set('NAME', name)
conf.set('SUMMARY', metainfo[1])
conf.set('LICENSE', metainfo[2])
configure_file(
input: 'io.github.Hexchat.Plugin.metainfo.xml.in',
output: 'io.github.Hexchat.Plugin.@0@.metainfo.xml'.format(name),
configuration: conf,
install_dir: get_option('install-plugin-metainfo') ? metainfodir : '',
)
endforeach
endif

View File

@ -2,8 +2,8 @@ pkg_conf = configuration_data()
prefix = get_option('prefix')
pkg_conf.set('prefix', prefix)
pkg_conf.set('VERSION', meson.project_version())
pkg_conf.set('hexchatlibdir', join_paths('${prefix}', plugindir))
pkg_conf.set('includedir', join_paths('${prefix}', get_option('includedir')))
pkg_conf.set('hexchatlibdir', join_paths(prefix, plugindir))
pkg_conf.set('includedir', join_paths(prefix, get_option('includedir')))
configure_file(
input: 'hexchat-plugin.pc.in',

View File

@ -7,6 +7,9 @@ ar = '/usr/bin/x86_64-w64-mingw32-gcc-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
[properties]
c_args = ['-DNTDDI_VERSION=NTDDI_WIN7', '-D_WIN32_WINNT=_WIN32_WINNT_WIN7']
[host_machine]
system = 'windows'
cpu_family = 'x86_64'

View File

@ -1,25 +0,0 @@
From 918503d57c6740d20be68a6717158673a2a8b25f Mon Sep 17 00:00:00 2001
From: Patrick Griffis <tingping@tingping.se>
Date: Sat, 17 Mar 2018 05:57:49 -0400
Subject: [PATCH] Support loading Flatpak extensions
---
src/common/plugin.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/common/plugin.c b/src/common/plugin.c
index 3ad3c558..6addf962 100644
--- a/src/common/plugin.c
+++ b/src/common/plugin.c
@@ -450,6 +450,8 @@ plugin_auto_load (session *sess)
lib_dir = plugin_get_libdir ();
sub_dir = g_build_filename (get_xdir (), "addons", NULL);
+ for_files ("/app/extensions/lib/hexchat/plugins", "*.so", plugin_auto_load_cb);
+
#ifdef WIN32
/* a long list of bundled plugins that should be loaded automatically,
* user plugins should go to <config>, leave Program Files alone! */
--
2.14.3

View File

@ -1,78 +0,0 @@
{
"app-id": "io.github.Hexchat",
"branch": "stable",
"runtime": "org.gnome.Platform",
"runtime-version": "40",
"sdk": "org.gnome.Sdk",
"command": "hexchat",
"finish-args": [
"--share=ipc",
"--socket=x11",
"--share=network",
"--socket=pulseaudio",
"--filesystem=xdg-download",
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.mpris.MediaPlayer2.*"
],
"add-extensions": {
"io.github.Hexchat.Plugin": {
"version": "20.08",
"directory": "extensions",
"add-ld-path": "lib",
"merge-dirs": "lib/hexchat/plugins",
"subdirectories": true,
"no-autodownload": true,
"autodelete": true
}
},
"modules": [
"shared-modules/gtk2/gtk2.json",
"shared-modules/gtk2/gtk2-common-themes.json",
"shared-modules/dbus-glib/dbus-glib.json",
"shared-modules/lua5.3/lua-5.3.5.json",
"shared-modules/libcanberra/libcanberra.json",
"python3-cffi.json",
{
"name": "lgi",
"buildsystem": "meson",
"sources": [
{
"type": "git",
"url": "https://github.com/pavouk/lgi.git",
"commit": "95418635aa8151a516d43166227ea2b9d4c4403f"
}
]
},
{
"name": "hexchat",
"buildsystem": "meson",
"config-opts": [
"--buildtype=release",
"-Ddbus-service-use-appid=true",
"-Dwith-perl=false",
"-Dwith-lua=lua"
],
"build-options": {
"cflags": "-Wno-error=missing-include-dirs"
},
"cleanup": [
"/share/man"
],
"post-install": [
"install -d /app/extensions"
],
"sources": [
{
"type": "dir",
"path": ".."
},
{
"type": "patch",
"path": "Load-plugins-from-Flatpak-extensions.patch"
}
]
}
]
}

View File

@ -1,19 +0,0 @@
{
"name": "python3-cffi",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"cffi\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/0f/86/e19659527668d70be91d0369aeaa055b4eb396b0f387a4f92293a20035bd/pycparser-2.20.tar.gz",
"sha256": "2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/a8/20/025f59f929bbcaa579704f443a438135918484fffaacfaddba776b374563/cffi-1.14.5.tar.gz",
"sha256": "fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
}
]
}

View File

@ -1,6 +1,6 @@
project('hexchat', 'c',
version: '2.16.1',
meson_version: '>= 0.47.0',
version: '2.12.4',
meson_version: '>= 0.38.0',
default_options: [
'c_std=gnu89',
'buildtype=debugoptimized',
@ -15,17 +15,11 @@ cc = meson.get_compiler('c')
libgio_dep = dependency('gio-2.0', version: '>= 2.34.0')
libgmodule_dep = dependency('gmodule-2.0')
libcanberra_dep = dependency('libcanberra', version: '>= 0.22',
required: get_option('libcanberra'))
dbus_glib_dep = dependency('dbus-glib-1', required: get_option('dbus'))
global_deps = []
if cc.get_id() == 'msvc'
libssl_dep = cc.find_library('libssl')
libssl_dep = cc.find_library('libeay32')
else
libssl_dep = dependency('openssl', version: '>= 0.9.8',
required: get_option('tls'))
required: get_option('with-ssl'))
endif
config_h = configuration_data()
@ -34,14 +28,14 @@ config_h.set_quoted('PACKAGE_NAME', meson.project_name())
config_h.set_quoted('GETTEXT_PACKAGE', 'hexchat')
config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'),
get_option('datadir'), 'locale'))
config_h.set_quoted('G_LOG_DOMAIN', 'hexchat')
config_h.set10('ENABLE_NLS', true)
# Optional features
config_h.set('USE_OPENSSL', libssl_dep.found())
config_h.set('USE_LIBCANBERRA', libcanberra_dep.found())
config_h.set('USE_DBUS', dbus_glib_dep.found())
config_h.set('USE_PLUGIN', get_option('plugin'))
config_h.set('USE_OPENSSL', get_option('with-ssl'))
config_h.set('USE_LIBPROXY', get_option('with-libproxy'))
config_h.set('USE_LIBCANBERRA', get_option('with-libcanberra'))
config_h.set('USE_DBUS', get_option('with-dbus'))
config_h.set('USE_PLUGIN', get_option('with-plugin'))
config_h.set('G_DISABLE_SINGLE_INCLUDES', true)
config_h.set('GTK_DISABLE_DEPRECATED', true)
@ -54,10 +48,6 @@ config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_34')
config_h.set('HAVE_MEMRCHR', cc.has_function('memrchr'))
config_h.set('HAVE_STRINGS_H', cc.has_header('strings.h'))
config_h.set_quoted('HEXCHATLIBDIR',
join_paths(get_option('prefix'), get_option('libdir'), 'hexchat/plugins')
)
if libssl_dep.found()
config_h.set('HAVE_X509_GET_SIGNATURE_NID',
cc.has_function('X509_get_signature_nid', dependencies: libssl_dep)
@ -86,16 +76,13 @@ configure_file(output: 'config.h', configuration: config_h)
config_h_include = include_directories('.')
if host_machine.system() == 'windows'
add_project_arguments(
'-DWIN32',
'-DNTDDI_VERSION=NTDDI_WIN7',
'-D_WIN32_WINNT=_WIN32_WINNT_WIN7',
language: 'c')
add_project_arguments('-DWIN32', language: 'c')
endif
global_cflags = []
test_cflags = [
'-pipe',
'-funsigned-char',
'-Wno-conversion',
'-Wno-pointer-sign',
@ -125,10 +112,6 @@ if get_option('buildtype') != 'plain'
}
''', args: '-fstack-protector-all')
global_cflags += '-fstack-protector-strong'
if host_machine.system() == 'windows'
global_deps += cc.find_library('ssp')
endif
endif
endif
add_project_arguments(global_cflags, language: 'c')
@ -138,27 +121,16 @@ global_ldflags = []
test_ldflags = [
'-Wl,-z,relro',
'-Wl,-z,now',
# mingw
'-Wl,--nxcompat',
]
if not (host_machine.system() == 'windows' and get_option('debug'))
test_ldflags += '-Wl,--dynamicbase'
endif
foreach ldflag : test_ldflags
if meson.version().version_compare('>= 0.46.0')
has_arg = cc.has_link_argument(ldflag)
else
has_arg = cc.has_argument(ldflag)
endif
if has_arg and cc.links('int main (void) { return 0; }', args: ldflag)
if cc.has_argument(ldflag) and cc.links('int main (void) { return 0; }', args: ldflag)
global_ldflags += ldflag
endif
endforeach
add_project_link_arguments(global_ldflags, language: 'c')
subdir('src')
if get_option('plugin')
if get_option('with-plugin')
subdir('plugins')
endif
if cc.get_id() != 'msvc'
@ -166,32 +138,6 @@ if cc.get_id() != 'msvc'
subdir('po') # FIXME: build xgettext
meson.add_install_script('meson_post_install.py',
'@0@'.format(get_option('theme-manager'))
'@0@'.format(get_option('with-theme-manager'))
)
endif
if meson.version().version_compare('>= 0.53.0')
summary({
'prefix': get_option('prefix'),
'bindir': get_option('bindir'),
'libdir': get_option('libdir'),
'datadir': get_option('datadir'),
}, section: 'Directories')
summary({
'TLS (openssl)': libssl_dep.found(),
'Plugin Support': get_option('plugin'),
'DBus Support': dbus_glib_dep.found(),
'libcanberra': libcanberra_dep.found(),
}, section: 'Features')
summary({
'Lua': get_option('with-lua'),
'Python': get_option('with-python'),
'Perl': get_option('with-perl'),
'Perl Legacy API': get_option('with-perl-legacy-api'),
'FiSH': get_option('with-fishlim'),
'Sysinfo': get_option('with-sysinfo'),
'DCC Checksum': get_option('with-checksum'),
}, section: 'Plugins')
endif

View File

@ -1,37 +1,29 @@
# Applications
option('gtk-frontend', type: 'boolean',
option('with-gtk', type: 'boolean',
description: 'Main graphical interface'
)
option('text-frontend', type: 'boolean', value: false,
option('with-text', type: 'boolean', value: false,
description: 'Text interface (not generally useful)'
)
option('theme-manager', type: 'boolean', value: false,
description: 'Utility to help manage themes, requires mono/.net'
)
# Features
option('tls', type: 'feature', value: 'enabled',
option('with-ssl', type: 'boolean',
description: 'Support for TLS connections, requires openssl'
)
option('plugin', type: 'boolean',
option('with-plugin', type: 'boolean',
description: 'Support for loadable plugins'
)
option('dbus', type: 'feature', value: 'auto',
option('with-dbus', type: 'boolean',
description: 'Support used for single-instance and scripting interface, Unix only'
)
option('libcanberra', type: 'feature', value: 'auto',
option('with-libproxy', type: 'boolean',
description: 'Support for getting proxy information, Unix only'
)
option('with-libnotify', type: 'boolean',
description: 'Support for freedesktop notifications, Unix only'
)
option('with-libcanberra', type: 'boolean',
description: 'Support for sound alerts, Unix only'
)
# Install options
option('dbus-service-use-appid', type: 'boolean', value: false,
description: 'Rename dbus service to match app-id, required for Flatpak'
)
option('install-appdata', type: 'boolean',
description: 'Install appdata files for app stores'
)
option('install-plugin-metainfo', type: 'boolean', value: false,
description: 'Installs metainfo files for enabled plugins, useful when distros create split packages'
option('with-theme-manager', type: 'boolean', value: false,
description: 'Utility to help manage themes, requires mono/.net'
)
# Plugins
@ -47,8 +39,8 @@ option('with-fishlim', type: 'boolean',
option('with-lua', type: 'string', value: 'luajit',
description: 'Lua scripting plugin, value is pkg-config name to use or "false"'
)
option('with-perl', type: 'string', value: 'perl',
description: 'Perl scripting plugin, value is path to perl executable or "false"'
option('with-perl', type: 'boolean',
description: 'Perl scripting plugin'
)
option('with-python', type: 'string', value: 'python3',
description: 'Python scripting plugin. value is pkg-config name to use or "false"'
@ -62,6 +54,3 @@ option('with-upd', type: 'boolean',
option('with-winamp', type: 'boolean',
description: 'Winamp plugin, Windows only'
)
option('with-perl-legacy-api', type: 'boolean', value: false,
description: 'Enables the legacy IRC perl module for compatibility with old scripts'
)

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -3,5 +3,4 @@ shared_module('checksum', 'checksum.c',
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'checksum.def',
)

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -1,6 +1,5 @@
shared_module('exec', 'exec.c',
dependencies: hexchat_plugin_dep,
install: true,
install_dir: plugindir,
vs_module_defs: 'exec.def',
install_dir: plugindir
)

View File

@ -189,12 +189,10 @@ dh1080_compute_key (const char *priv_key, const char *pub_key, char **secret_key
char *pub_key_data;
gsize pub_key_len;
BIGNUM *pk;
DH *dh;
#ifdef HAVE_DH_SET0_KEY
BIGNUM *temp_pub_key = BN_new();
#endif
DH *dh;
g_assert (secret_key != NULL);
g_assert (secret_key != NULL);
/* Verify base64 strings */
if (strspn (priv_key, B64ABC) != strlen (priv_key)
@ -215,7 +213,7 @@ dh1080_compute_key (const char *priv_key, const char *pub_key, char **secret_key
int shared_len;
BIGNUM *priv_key_num;
priv_key_data = dh1080_decode_b64 (priv_key, &priv_key_len);
priv_key_data = dh1080_decode_b64 (priv_key, &priv_key_len);
priv_key_num = BN_bin2bn(priv_key_data, priv_key_len, NULL);
#ifndef HAVE_DH_SET0_KEY
dh->priv_key = priv_key_num;
@ -228,7 +226,7 @@ dh1080_compute_key (const char *priv_key, const char *pub_key, char **secret_key
*secret_key = dh1080_encode_b64 (sha256, sizeof(sha256));
OPENSSL_cleanse (priv_key_data, priv_key_len);
g_free (priv_key_data);
g_free (priv_key_data);
}
BN_free (pk);

View File

@ -1,7 +1,6 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2019-2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -28,22 +27,15 @@
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/blowfish.h>
#include <openssl/rand.h>
#include "keystore.h"
#include "fish.h"
#include "utils.h"
#define IB 64
/* rfc 2812; 512 - CR-LF = 510 */
static const int MAX_COMMAND_LENGTH = 510;
static const char fish_base64[] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const signed char fish_unbase64[256] = {
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
@ -61,12 +53,6 @@ static const signed char fish_unbase64[256] = {
27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB,
};
/**
* Convert Int to 4 Bytes (Big-endian)
*
* @param int source
* @param char* dest
*/
#define GET_BYTES(dest, source) do { \
*((dest)++) = ((source) >> 24) & 0xFF; \
*((dest)++) = ((source) >> 16) & 0xFF; \
@ -74,488 +60,135 @@ static const signed char fish_unbase64[256] = {
*((dest)++) = (source) & 0xFF; \
} while (0);
/**
* Convert 4 Bytes to Int (Big-endian)
*
* @param char* source
* @param int dest
*/
#define GET_LONG(dest, source) do { \
dest = ((uint8_t)*((source)++) << 24); \
dest |= ((uint8_t)*((source)++) << 16); \
dest |= ((uint8_t)*((source)++) << 8); \
dest |= (uint8_t)*((source)++); \
} while (0);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/provider.h>
static OSSL_PROVIDER *legacy_provider;
static OSSL_PROVIDER *default_provider;
static OSSL_LIB_CTX *ossl_ctx;
#endif
int fish_init(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
ossl_ctx = OSSL_LIB_CTX_new();
if (!ossl_ctx)
return 0;
legacy_provider = OSSL_PROVIDER_load(ossl_ctx, "legacy");
if (!legacy_provider) {
fish_deinit();
return 0;
}
default_provider = OSSL_PROVIDER_load(ossl_ctx, "default");
if (!default_provider) {
fish_deinit();
return 0;
}
#endif
return 1;
}
void fish_deinit(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
if (legacy_provider) {
OSSL_PROVIDER_unload(legacy_provider);
legacy_provider = NULL;
}
if (default_provider) {
OSSL_PROVIDER_unload(default_provider);
default_provider = NULL;
}
if (ossl_ctx) {
OSSL_LIB_CTX_free(ossl_ctx);
ossl_ctx = NULL;
}
#endif
}
/**
* Encode ECB FiSH Base64
*
* @param [in] message Bytes to encode
* @param [in] message_len Size of bytes to encode
* @return Array of char with encoded string
*/
char *fish_base64_encode(const char *message, size_t message_len) {
BF_LONG left = 0, right = 0;
int i, j;
char *encoded = NULL;
char *end = NULL;
char *msg = NULL;
if (message_len == 0)
return NULL;
/* Each 8-byte block becomes 12 bytes (fish base64 format) and add 1 byte for \0 */
encoded = g_malloc(((message_len - 1) / 8) * 12 + 12 + 1);
end = encoded;
/* Iterate over each 8-byte block (Blowfish block size) */
for (j = 0; j < message_len; j += 8) {
msg = (char *) message;
/* Set left and right longs */
GET_LONG(left, msg);
GET_LONG(right, msg);
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[right & 0x3fu];
right = (right >> 6u);
char *fish_encrypt(const char *key, size_t keylen, const char *message) {
BF_KEY bfkey;
size_t messagelen;
size_t i;
int j;
char *encrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
messagelen = strlen(message);
if (messagelen == 0) return NULL;
encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */
end = encrypted;
while (*message) {
/* Read 8 bytes (a Blowfish block) */
BF_LONG binary[2] = { 0, 0 };
unsigned char c;
for (i = 0; i < 8; i++) {
c = message[i];
binary[i >> 2] |= c << 8*(3 - (i&3));
if (c == '\0') break;
}
for (i = 0; i < 6; ++i) {
*end++ = fish_base64[left & 0x3fu];
left = (left >> 6u);
}
/* The previous for'd ensure fill all bytes of encoded, we don't need will with zeros */
message += 8;
}
*end = '\0';
return encoded;
}
/**
* Decode ECB FiSH Base64
*
* @param [in] message Base64 encoded string
* @param [out] final_len Real length of message
* @return Array of char with decoded message
*/
char *fish_base64_decode(const char *message, size_t *final_len) {
BF_LONG left, right;
int i;
char *bytes = NULL;
char *msg = NULL;
char *byt = NULL;
size_t message_len;
message_len = strlen(message);
/* Ensure blocks of 12 bytes each one and valid characters */
if (message_len == 0 || message_len % 12 != 0 || strspn(message, fish_base64) != message_len)
return NULL;
/* Each 12 bytes becomes 8-byte block and add 1 byte for \0 */
*final_len = ((message_len - 1) / 12) * 8 + 8 + 1;
(*final_len)--; /* We support binary data */
bytes = (char *) g_malloc0(*final_len);
byt = bytes;
msg = (char *) message;
while (*msg) {
right = 0;
left = 0;
for (i = 0; i < 6; i++) right |= (uint8_t) fish_unbase64[(int)*msg++] << (i * 6u);
for (i = 0; i < 6; i++) left |= (uint8_t) fish_unbase64[(int)*msg++] << (i * 6u);
GET_BYTES(byt, left);
GET_BYTES(byt, right);
}
return bytes;
}
/**
* Encrypt or decrypt data with Blowfish cipher, support binary data.
*
* Good documentation for EVP:
*
* - https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
*
* - https://stackoverflow.com/questions/5727646/what-is-the-length-parameter-of-aes-evp-decrypt
*
* - https://stackoverflow.com/questions/26345175/correct-way-to-free-allocate-the-context-in-the-openssl
*
* - https://stackoverflow.com/questions/29874150/working-with-evp-and-openssl-coding-in-c
*
* @param [in] plaintext Bytes to encrypt or decrypt
* @param [in] plaintext_len Size of plaintext
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] encode 1 or encrypt 0 for decrypt
* @param [in] mode EVP_CIPH_ECB_MODE or EVP_CIPH_CBC_MODE
* @param [out] ciphertext_len The bytes written
* @return Array of char with data encrypted or decrypted
*/
char *fish_cipher(const char *plaintext, size_t plaintext_len, const char *key, size_t keylen, int encode, int mode, size_t *ciphertext_len) {
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int bytes_written = 0;
unsigned char *ciphertext = NULL;
unsigned char *iv_ciphertext = NULL;
unsigned char *iv = NULL;
size_t block_size = 0;
*ciphertext_len = 0;
if (plaintext_len == 0 || keylen == 0 || encode < 0 || encode > 1)
return NULL;
block_size = plaintext_len;
if (mode == EVP_CIPH_CBC_MODE) {
if (encode == 1) {
iv = (unsigned char *) g_malloc0(8);
RAND_bytes(iv, 8);
} else {
if (plaintext_len <= 8) /* IV + DATA */
return NULL;
iv = (unsigned char *) plaintext;
block_size -= 8;
plaintext += 8;
plaintext_len -= 8;
/* Encrypt block */
BF_encrypt(binary, &bfkey);
/* Emit FiSH-BASE64 */
bit = 0;
word = 1;
for (j = 0; j < 12; j++) {
d = fish_base64[(binary[word] >> bit) & 63];
*(end++) = d;
bit += 6;
if (j == 5) {
bit = 0;
word = 0;
}
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-CBC", NULL);
#else
cipher = (EVP_CIPHER *) EVP_bf_cbc();
#endif
} else if (mode == EVP_CIPH_ECB_MODE) {
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
cipher = EVP_CIPHER_fetch(ossl_ctx, "BF-ECB", NULL);
#else
cipher = (EVP_CIPHER *) EVP_bf_ecb();
#endif
/* Stop if a null terminator was found */
if (c == '\0') break;
}
*end = '\0';
return encrypted;
}
/* Zero Padding */
if (block_size % 8 != 0) {
block_size = block_size + 8 - (block_size % 8);
}
ciphertext = (unsigned char *) g_malloc0(block_size);
memcpy(ciphertext, plaintext, plaintext_len);
/* Create and initialise the context */
if (!(ctx = EVP_CIPHER_CTX_new()))
return NULL;
/* Initialise the cipher operation only with mode */
if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encode))
return NULL;
/* Set custom key length */
if (!EVP_CIPHER_CTX_set_key_length(ctx, keylen))
return NULL;
/* Finish the initiation the cipher operation */
if (1 != EVP_CipherInit_ex(ctx, NULL, NULL, (const unsigned char *) key, iv, encode))
return NULL;
/* We will manage this */
EVP_CIPHER_CTX_set_padding(ctx, 0);
/* Do cipher operation */
if (1 != EVP_CipherUpdate(ctx, ciphertext, &bytes_written, ciphertext, block_size))
return NULL;
*ciphertext_len = bytes_written;
/* Finalise the cipher. Further ciphertext bytes may be written at this stage */
if (1 != EVP_CipherFinal_ex(ctx, ciphertext + bytes_written, &bytes_written))
return NULL;
*ciphertext_len += bytes_written;
/* Clean up */
EVP_CIPHER_CTX_free(ctx);
if (mode == EVP_CIPH_CBC_MODE && encode == 1) {
/* Join IV + DATA */
iv_ciphertext = g_malloc0(8 + *ciphertext_len);
memcpy(iv_ciphertext, iv, 8);
memcpy(&iv_ciphertext[8], ciphertext, *ciphertext_len);
*ciphertext_len += 8;
g_free(ciphertext);
g_free(iv);
return (char *) iv_ciphertext;
} else {
return (char *) ciphertext;
char *fish_decrypt(const char *key, size_t keylen, const char *data) {
BF_KEY bfkey;
size_t i;
char *decrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
decrypted = g_malloc(strlen(data) + 1);
end = decrypted;
while (*data) {
/* Convert from FiSH-BASE64 */
BF_LONG binary[2] = { 0, 0 };
bit = 0;
word = 1;
for (i = 0; i < 12; i++) {
d = fish_unbase64[(const unsigned char)*(data++)];
if (d == IB) goto decrypt_end;
binary[word] |= (unsigned long)d << bit;
bit += 6;
if (i == 5) {
bit = 0;
word = 0;
}
}
/* Decrypt block */
BF_decrypt(binary, &bfkey);
/* Copy to buffer */
GET_BYTES(end, binary[0]);
GET_BYTES(end, binary[1]);
}
decrypt_end:
*end = '\0';
return decrypted;
}
/**
* Return a fish or standard Base64 encoded string with data encrypted
* is binary safe
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] message Bytes to encrypt
* @param [in] message_len Size of message
* @param [in] mode Chiper mode
* @return Array of char with data encrypted
* Encrypts a message (see fish_decrypt). The key is searched for in the
* key store.
*/
char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode) {
size_t ciphertext_len = 0;
char *ciphertext = NULL;
char *b64 = NULL;
if (keylen == 0 || message_len == 0)
return NULL;
ciphertext = fish_cipher(message, message_len, key, keylen, 1, mode, &ciphertext_len);
if (ciphertext == NULL || ciphertext_len == 0)
return NULL;
switch (mode) {
case FISH_CBC_MODE:
b64 = g_base64_encode((const unsigned char *) ciphertext, ciphertext_len);
break;
case FISH_ECB_MODE:
b64 = fish_base64_encode((const char *) ciphertext, ciphertext_len);
}
g_free(ciphertext);
if (b64 == NULL)
return NULL;
return b64;
}
/**
* Return an array of bytes with data decrypted
* is binary safe
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] data Fish or standard Base64 encoded string
* @param [in] mode Chiper mode
* @param [out] final_len Length of returned array
* @return Array of char with data decrypted
*/
char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len) {
size_t ciphertext_len = 0;
char *ciphertext = NULL;
char *plaintext = NULL;
*final_len = 0;
if (keylen == 0 || strlen(data) == 0)
return NULL;
switch (mode) {
case FISH_CBC_MODE:
if (strspn(data, base64_chars) != strlen(data))
return NULL;
ciphertext = (char *) g_base64_decode(data, &ciphertext_len);
break;
case FISH_ECB_MODE:
ciphertext = fish_base64_decode(data, &ciphertext_len);
}
if (ciphertext == NULL || ciphertext_len == 0)
return NULL;
plaintext = fish_cipher(ciphertext, ciphertext_len, key, keylen, 0, mode, final_len);
g_free(ciphertext);
if (*final_len == 0)
return NULL;
return plaintext;
}
/**
* Similar to fish_decrypt, but pad with zeros any after the first zero in the decrypted data
*
* @param [in] key Bytes of key
* @param [in] keylen Size of key
* @param [in] data Fish or standard Base64 encoded string
* @param [in] mode Chiper mode
* @return Array of char with data decrypted
*/
char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode) {
char *decrypted = NULL;
char *plaintext_str = NULL;
size_t decrypted_len = 0;
decrypted = fish_decrypt(key, strlen(key), data, mode, &decrypted_len);
if (decrypted == NULL || decrypted_len == 0)
return NULL;
plaintext_str = g_strndup(decrypted, decrypted_len);
g_free(decrypted);
return plaintext_str;
}
/**
* Determine if a nick have a key
*
* @param [in] nick Nickname
* @return TRUE if have a key or FALSE if not
*/
gboolean fish_nick_has_key(const char *nick) {
gboolean has_key = FALSE;
char *fish_encrypt_for_nick(const char *nick, const char *data) {
char *key;
enum fish_mode mode;
key = keystore_get_key(nick, &mode);
if (key) {
has_key = TRUE;
g_free(key);
};
return has_key;
}
/**
* Encrypts a message (see fish_encrypt). The key is searched for in the key store
*
* @param [in] nick Nickname
* @param [in] data Plaintext to encrypt
* @param [out] omode Mode of encryption
* @param [in] command_len Length of command to use without the message part
* @return A list of encoded strings with the message encrypted or NULL if any error occurred
*/
GSList *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode, size_t command_len) {
char *key;
GSList *encrypted_list = NULL;
char *encrypted = NULL;
enum fish_mode mode;
int max_len, max_chunks_len, chunks_len;
char *encrypted;
/* Look for key */
key = keystore_get_key(nick, &mode);
key = keystore_get_key(nick);
if (!key) return NULL;
*omode = mode;
/* Calculate max length of each line */
max_len = MAX_COMMAND_LENGTH - command_len;
/* Add '*' */
if (mode == FISH_CBC_MODE) max_len--;
max_chunks_len = max_text_command_len(max_len, mode);
const char *data_chunk = data;
while(foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) {
encrypted = fish_encrypt(key, strlen(key), data_chunk, chunks_len, mode);
if (mode == FISH_CBC_MODE) {
/* Add '*' for CBC */
encrypted_list = g_slist_append(encrypted_list, g_strdup_printf("*%s", encrypted));
g_free(encrypted);
} else {
encrypted_list = g_slist_append(encrypted_list, encrypted);
}
/* Next chunk */
data_chunk += chunks_len;
}
return encrypted_list;
/* Encrypt */
encrypted = fish_encrypt(key, strlen(key), data);
g_free(key);
return encrypted;
}
/**
* Decrypts a message (see fish_decrypt). The key is searched for in the key store
*
* @param [in] nick Nickname
* @param [in] data Plaintext to encrypt
* @param [out] omode Mode of encryption
* @return Plaintext message or NULL if any error occurred
* Decrypts a message (see fish_decrypt). The key is searched for in the
* key store.
*/
char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode) {
char *fish_decrypt_from_nick(const char *nick, const char *data) {
char *key;
char *decrypted;
enum fish_mode mode;
/* Look for key */
key = keystore_get_key(nick, &mode);
key = keystore_get_key(nick);
if (!key) return NULL;
*omode = mode;
if (mode == FISH_CBC_MODE)
++data;
/* Decrypt */
decrypted = fish_decrypt_str(key, strlen(key), data, mode);
decrypted = fish_decrypt(key, strlen(key), data);
g_free(key);
return decrypted;
}

View File

@ -1,7 +1,6 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2019-2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -30,21 +29,10 @@
#include <glib.h>
enum fish_mode {
FISH_ECB_MODE = 0x1,
FISH_CBC_MODE = 0x2
};
int fish_init(void);
void fish_deinit(void);
char *fish_base64_encode(const char *message, size_t message_len);
char *fish_base64_decode(const char *message, size_t *final_len);
char *fish_encrypt(const char *key, size_t keylen, const char *message, size_t message_len, enum fish_mode mode);
char *fish_decrypt(const char *key, size_t keylen, const char *data, enum fish_mode mode, size_t *final_len);
char *fish_decrypt_str(const char *key, size_t keylen, const char *data, enum fish_mode mode);
gboolean fish_nick_has_key(const char *nick);
GSList *fish_encrypt_for_nick(const char *nick, const char *data, enum fish_mode *omode, size_t command_len);
char *fish_decrypt_from_nick(const char *nick, const char *data, enum fish_mode *omode);
char *fish_encrypt(const char *key, size_t keylen, const char *message);
char *fish_decrypt(const char *key, size_t keylen, const char *data);
char *fish_encrypt_for_nick(const char *nick, const char *data);
char *fish_decrypt_from_nick(const char *nick, const char *data);
#endif

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
@ -29,7 +29,7 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(Glib);..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
@ -40,7 +40,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;HAVE_DH_SET0_PQG;HAVE_DH_GET0_KEY;HAVE_DH_SET0_KEY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;FISHLIM_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(DepsRoot)\include;$(Glib);..\..\src\common;$(HexChatLib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
@ -55,7 +55,6 @@
<ItemGroup>
<ClInclude Include="dh1080.h" />
<ClInclude Include="fish.h" />
<ClInclude Include="utils.h" />
<ClInclude Include="irc.h" />
<ClInclude Include="keystore.h" />
<ClInclude Include="plugin_hexchat.h" />
@ -63,7 +62,6 @@
<ItemGroup>
<ClCompile Include="dh1080.c" />
<ClCompile Include="fish.c" />
<ClCompile Include="utils.c" />
<ClCompile Include="irc.c" />
<ClCompile Include="keystore.c" />
<ClCompile Include="plugin_hexchat.c" />

View File

@ -23,15 +23,9 @@
<ClInclude Include="bool.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dh1080.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="fish.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="utils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="irc.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -43,15 +37,9 @@
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dh1080.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="fish.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="utils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="irc.c">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -103,55 +103,27 @@ static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *it
/**
* Extracts a key from the key store file.
*/
char *keystore_get_key(const char *nick, enum fish_mode *mode) {
GKeyFile *keyfile;
char *escaped_nick;
gchar *value, *key_mode;
int encrypted_mode;
char *password;
char *encrypted;
char *decrypted;
char *keystore_get_key(const char *nick) {
/* Get the key */
keyfile = getConfigFile();
escaped_nick = escape_nickname(nick);
value = get_nick_value(keyfile, escaped_nick, "key");
key_mode = get_nick_value(keyfile, escaped_nick, "mode");
GKeyFile *keyfile = getConfigFile();
char *escaped_nick = escape_nickname(nick);
gchar *value = get_nick_value(keyfile, escaped_nick, "key");
g_key_file_free(keyfile);
g_free(escaped_nick);
/* Determine cipher mode */
*mode = FISH_ECB_MODE;
if (key_mode) {
if (*key_mode == '1')
*mode = FISH_ECB_MODE;
else if (*key_mode == '2')
*mode = FISH_CBC_MODE;
g_free(key_mode);
}
if (!value)
return NULL;
if (strncmp(value, "+OK ", 4) == 0) {
/* Key is encrypted */
encrypted = (char *) value;
encrypted += 4;
encrypted_mode = FISH_ECB_MODE;
if (*encrypted == '*') {
++encrypted;
encrypted_mode = FISH_CBC_MODE;
}
password = (char *) get_keystore_password();
decrypted = fish_decrypt_str((const char *) password, strlen(password), (const char *) encrypted, encrypted_mode);
g_free(value);
return decrypted;
} else {
if (strncmp(value, "+OK ", 4) != 0) {
/* Key is stored in plaintext */
return value;
} else {
/* Key is encrypted */
const char *encrypted = value+4;
const char *password = get_keystore_password();
char *decrypted = fish_decrypt(password, strlen(password), encrypted);
g_free(value);
return decrypted;
}
}
@ -217,7 +189,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
/**
* Sets a key in the key store file.
*/
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
gboolean keystore_store_key(const char *nick, const char *key) {
const char *password;
char *encrypted;
char *wrapped;
@ -232,11 +204,11 @@ gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mo
password = get_keystore_password();
if (password) {
/* Encrypt the password */
encrypted = fish_encrypt(password, strlen(password), key, strlen(key), FISH_CBC_MODE);
encrypted = fish_encrypt(password, strlen(password), key);
if (!encrypted) goto end;
/* Prepend "+OK " */
wrapped = g_strconcat("+OK *", encrypted, NULL);
wrapped = g_strconcat("+OK ", encrypted, NULL);
g_free(encrypted);
/* Store encrypted in file */
@ -246,9 +218,6 @@ gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mo
/* Store unencrypted in file */
g_key_file_set_string(keyfile, escaped_nick, "key", key);
}
/* Store cipher mode */
g_key_file_set_integer(keyfile, escaped_nick, "mode", mode);
/* Save key store file */
ok = save_keystore(keyfile);

View File

@ -28,10 +28,9 @@
#include <stddef.h>
#include <glib.h>
#include "fish.h"
char *keystore_get_key(const char *nick, enum fish_mode *mode);
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode);
char *keystore_get_key(const char *nick);
gboolean keystore_store_key(const char *nick, const char *key);
gboolean keystore_delete_nick(const char *nick);
#endif

View File

@ -2,13 +2,9 @@ if not libssl_dep.found()
error('fish plugin requires openssl')
endif
# Run tests
subdir('tests')
fishlim_sources = [
'dh1080.c',
'fish.c',
'utils.c',
'irc.c',
'keystore.c',
'plugin_hexchat.c'
@ -19,5 +15,4 @@ shared_module('fishlim', fishlim_sources,
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'fishlim.def',
)

View File

@ -2,7 +2,6 @@
Copyright (c) 2010-2011 Samuel Lidén Borell <samuel@kodafritt.se>
Copyright (c) 2015 <the.cypher@gmail.com>
Copyright (c) 2019-2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -29,21 +28,21 @@
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include "hexchat-plugin.h"
#define HEXCHAT_MAX_WORDS 32
#include "fish.h"
#include "dh1080.h"
#include "keystore.h"
#include "irc.h"
static const char *fish_modes[] = {"", "ECB", "CBC", NULL};
static const char plugin_name[] = "FiSHLiM";
static const char plugin_desc[] = "Encryption plugin for the FiSH protocol. Less is More!";
static const char plugin_version[] = "1.0.0";
static const char plugin_version[] = "0.1.0";
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] [<mode>:]<password>, sets the key for a channel or nick. Modes: ECB, CBC";
static const char usage_delkey[] = "Usage: DELKEY [<nick or #channel>], deletes the key for a channel or nick";
static const char usage_setkey[] = "Usage: SETKEY [<nick or #channel>] <password>, sets the key for a channel or nick";
static const char usage_delkey[] = "Usage: DELKEY <nick or #channel>, deletes the key for a channel or nick";
static const char usage_keyx[] = "Usage: KEYX [<nick>], performs DH1080 key-exchange with <nick>";
static const char usage_topic[] = "Usage: TOPIC+ <topic>, sets a new encrypted topic for the current channel";
static const char usage_notice[] = "Usage: NOTICE+ <nick or #channel> <notice>";
@ -54,13 +53,6 @@ static hexchat_plugin *ph;
static GHashTable *pending_exchanges;
/**
* Compare two nicks using the current plugin
*/
int irc_nick_cmp(const char *a, const char *b) {
return hexchat_nickcmp (ph, a, b);
}
/**
* Returns the path to the key store file.
*/
@ -96,7 +88,7 @@ static hexchat_context *find_context_on_network (const char *name) {
int chan_id = hexchat_list_int(ph, channels, "id");
const char *chan_name = hexchat_list_str(ph, channels, "channel");
if (chan_id == id && chan_name && irc_nick_cmp (chan_name, name) == 0) {
if (chan_id == id && chan_name && hexchat_nickcmp (ph, chan_name, name) == 0) {
ret = (hexchat_context*)hexchat_list_str(ph, channels, "context");
break;
}
@ -106,175 +98,8 @@ static hexchat_context *find_context_on_network (const char *name) {
return ret;
}
/**
* Retrive the field for own user in current context
* @return the field value
*/
char *get_my_info(const char *field, gboolean find_in_other_context) {
char *result = NULL;
const char *own_nick;
hexchat_list *list;
hexchat_context *ctx_current, *ctx_channel;
/* Display message */
own_nick = hexchat_get_info(ph, "nick");
if (!own_nick)
return NULL;
/* Get field for own nick if any */
list = hexchat_list_get(ph, "users");
if (list) {
while (hexchat_list_next(ph, list)) {
if (irc_nick_cmp(own_nick, hexchat_list_str(ph, list, "nick")) == 0)
result = g_strdup(hexchat_list_str(ph, list, field));
}
hexchat_list_free(ph, list);
}
if (result) {
return result;
}
/* Try to get from a channel (we are outside a channel) */
if (!find_in_other_context) {
return NULL;
}
list = hexchat_list_get(ph, "channels");
if (list) {
ctx_current = hexchat_get_context(ph);
while (hexchat_list_next(ph, list)) {
ctx_channel = (hexchat_context *) hexchat_list_str(ph, list, "context");
hexchat_set_context(ph, ctx_channel);
result = get_my_info(field, FALSE);
hexchat_set_context(ph, ctx_current);
if (result) {
break;
}
}
hexchat_list_free(ph, list);
}
return result;
}
/**
* Retrive the prefix character for own nick in current context
* @return @ or + or NULL
*/
char *get_my_own_prefix(void) {
return get_my_info("prefix", FALSE);
}
/**
* Retrive the mask for own nick in current context
* @return Host name in the form: user@host (or NULL if not known)
*/
char *get_my_own_host(void) {
return get_my_info("host", TRUE);
}
/**
* Calculate the length of prefix for current user in current context
*
* @return Length of prefix
*/
int get_prefix_length(void) {
char *own_host;
int prefix_len = 0;
/* ':! ' + 'nick' + 'ident@host', e.g. ':user!~name@mynet.com ' */
prefix_len = 3 + strlen(hexchat_get_info(ph, "nick"));
own_host = get_my_own_host();
if (own_host) {
prefix_len += strlen(own_host);
} else {
/* https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix */
prefix_len += 64;
}
g_free(own_host);
return prefix_len;
}
/**
* Try to decrypt the first occurrence of fish message
*
* @param message Message to decrypt
* @param key Key of message
* @return Array of char with decrypted message or NULL. The returned string
* should be freed with g_free() when no longer needed.
*/
char *decrypt_raw_message(const char *message, const char *key) {
const char *prefixes[] = {"+OK ", "mcps ", NULL};
char *start = NULL, *end = NULL;
char *left = NULL, *right = NULL;
char *encrypted = NULL, *decrypted = NULL;
int length = 0;
int index_prefix;
enum fish_mode mode;
GString *message_decrypted;
char *result = NULL;
if (message == NULL || key == NULL)
return NULL;
for (index_prefix = 0; index_prefix < 2; index_prefix++) {
start = g_strstr_len(message, strlen(message), prefixes[index_prefix]);
if (start) {
/* Length ALWAYS will be less that original message
* add '[CBC] ' length */
message_decrypted = g_string_sized_new(strlen(message) + 6);
/* Left part of message */
left = g_strndup(message, start - message);
g_string_append(message_decrypted, left);
g_free(left);
/* Encrypted part */
start += strlen(prefixes[index_prefix]);
end = g_strstr_len(start, strlen(message), " ");
if (end) {
length = end - start;
right = end;
}
if (length > 0) {
encrypted = g_strndup(start, length);
} else {
encrypted = g_strdup(start);
}
decrypted = fish_decrypt_from_nick(key, encrypted, &mode);
g_free(encrypted);
if (decrypted == NULL) {
g_string_free(message_decrypted, TRUE);
return NULL;
}
/* Add encrypted flag */
g_string_append(message_decrypted, "[");
g_string_append(message_decrypted, fish_modes[mode]);
g_string_append(message_decrypted, "] ");
/* Decrypted message */
g_string_append(message_decrypted, decrypted);
g_free(decrypted);
/* Right part of message */
if (right) {
g_string_append(message_decrypted, right);
}
result = message_decrypted->str;
g_string_free(message_decrypted, FALSE);
return result;
}
}
return NULL;
int irc_nick_cmp(const char *a, const char *b) {
return hexchat_nickcmp (ph, a, b);
}
/*static int handle_debug(char *word[], char *word_eol[], void *userdata) {
@ -290,49 +115,20 @@ char *decrypt_raw_message(const char *message, const char *key) {
* Called when a message is to be sent.
*/
static int handle_outgoing(char *word[], char *word_eol[], void *userdata) {
char *prefix;
enum fish_mode mode;
char *message;
GString *command;
GSList *encrypted_list, *encrypted_item;
const char *own_nick;
/* Encrypt the message if possible */
const char *channel = hexchat_get_info(ph, "channel");
/* Check if we can encrypt */
if (!fish_nick_has_key(channel)) return HEXCHAT_EAT_NONE;
command = g_string_new("");
g_string_printf(command, "PRIVMSG %s :+OK ", channel);
encrypted_list = fish_encrypt_for_nick(channel, word_eol[1], &mode, get_prefix_length() + command->len);
if (!encrypted_list) {
g_string_free(command, TRUE);
return HEXCHAT_EAT_NONE;
}
/* Get prefix for own nick if any */
prefix = get_my_own_prefix();
/* Add encrypted flag */
message = g_strconcat("[", fish_modes[mode], "] ", word_eol[1], NULL);
char *encrypted = fish_encrypt_for_nick(channel, word_eol[1]);
if (!encrypted) return HEXCHAT_EAT_NONE;
/* Display message */
hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message, prefix, NULL);
g_free(message);
/* Send encrypted messages */
encrypted_item = encrypted_list;
while (encrypted_item)
{
hexchat_commandf(ph, "%s%s", command->str, (char *)encrypted_item->data);
encrypted_item = encrypted_item->next;
}
g_free(prefix);
g_string_free(command, TRUE);
g_slist_free_full(encrypted_list, g_free);
own_nick = hexchat_get_info(ph, "nick");
hexchat_emit_print(ph, "Your Message", own_nick, word_eol[1], NULL);
/* Send message */
hexchat_commandf(ph, "PRIVMSG %s :+OK %s", channel, encrypted);
g_free(encrypted);
return HEXCHAT_EAT_HEXCHAT;
}
@ -343,59 +139,96 @@ static int handle_incoming(char *word[], char *word_eol[], hexchat_event_attrs *
const char *prefix;
const char *command;
const char *recipient;
const char *raw_message = word_eol[1];
const char *encrypted;
const char *peice;
char *sender_nick;
char *decrypted;
size_t parameters_offset;
size_t w;
size_t ew;
size_t uw;
char prefix_char = 0;
GString *message;
if (!irc_parse_message((const char **)word, &prefix, &command, &parameters_offset))
if (!irc_parse_message((const char **)word, &prefix, &command, &w))
return HEXCHAT_EAT_NONE;
/* Topic (command 332) has an extra parameter */
if (!strcmp(command, "332"))
parameters_offset++;
/* Extract sender nick and recipient nick/channel and try to decrypt */
recipient = word[parameters_offset];
decrypted = decrypt_raw_message(raw_message, recipient);
if (decrypted == NULL) {
sender_nick = irc_prefix_get_nick(prefix);
decrypted = decrypt_raw_message(raw_message, sender_nick);
g_free(sender_nick);
if (!strcmp(command, "332")) w++;
/* Look for encrypted data */
for (ew = w+1; ew < HEXCHAT_MAX_WORDS-1; ew++) {
const char *s = (ew == w+1 ? word[ew]+1 : word[ew]);
if (*s && (s[1] == '+' || s[1] == 'm')) { prefix_char = *(s++); }
else { prefix_char = 0; }
if (strcmp(s, "+OK") == 0 || strcmp(s, "mcps") == 0) goto has_encrypted_data;
}
/* Nothing to decrypt */
if (decrypted == NULL)
return HEXCHAT_EAT_NONE;
/* Build decrypted message */
/* decrypted + 'RECV ' + '@time=YYYY-MM-DDTHH:MM:SS.fffffZ ' */
message = g_string_sized_new (strlen(decrypted) + 5 + 33);
g_string_append (message, "RECV ");
return HEXCHAT_EAT_NONE;
has_encrypted_data: ;
/* Extract sender nick and recipient nick/channel */
sender_nick = irc_prefix_get_nick(prefix);
recipient = word[w];
/* Try to decrypt with these (the keys are searched for in the key store) */
encrypted = word[ew+1];
decrypted = fish_decrypt_from_nick(recipient, encrypted);
if (!decrypted) decrypted = fish_decrypt_from_nick(sender_nick, encrypted);
/* Check for error */
if (!decrypted) goto decrypt_error;
/* Build unecrypted message */
message = g_string_sized_new (100); /* TODO: more accurate estimation of size */
g_string_append (message, "RECV");
if (attrs->server_time_utc)
{
GTimeVal tv = { (glong)attrs->server_time_utc, 0 };
char *timestamp = g_time_val_to_iso8601 (&tv);
g_string_append (message, "@time=");
g_string_append (message, " @time=");
g_string_append (message, timestamp);
g_string_append (message, " ");
g_free (timestamp);
}
g_string_append (message, decrypted);
for (uw = 1; uw < HEXCHAT_MAX_WORDS; uw++) {
if (word[uw][0] != '\0')
g_string_append_c (message, ' ');
if (uw == ew) {
/* Add the encrypted data */
peice = decrypted;
uw++; /* Skip "OK+" */
if (ew == w+1) {
/* Prefix with colon, which gets stripped out otherwise */
g_string_append_c (message, ':');
}
if (prefix_char) {
g_string_append_c (message, prefix_char);
}
} else {
/* Add unencrypted data (for example, a prefix from a bouncer or bot) */
peice = word[uw];
}
g_string_append (message, peice);
}
g_free(decrypted);
/* Fake server message
* RECV command will throw this function again, if message have multiple
* encrypted data, we will decrypt all */
/* Simulate unencrypted message */
/* hexchat_printf(ph, "simulating: %s\n", message->str); */
hexchat_command(ph, message->str);
g_string_free (message, TRUE);
g_string_free (message, TRUE);
g_free(sender_nick);
return HEXCHAT_EAT_HEXCHAT;
decrypt_error:
g_free(decrypted);
g_free(sender_nick);
return HEXCHAT_EAT_NONE;
}
static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
@ -403,8 +236,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
const char *dh_pubkey = word[5];
hexchat_context *query_ctx;
const char *prefix;
gboolean cbc;
char *sender, *secret_key, *priv_key = NULL;
enum fish_mode mode = FISH_ECB_MODE;
if (!*dh_message || !*dh_pubkey || strlen(dh_pubkey) != 181)
return HEXCHAT_EAT_NONE;
@ -415,19 +248,25 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
sender = irc_prefix_get_nick(prefix);
query_ctx = find_context_on_network(sender);
if (query_ctx)
g_assert(hexchat_set_context(ph, query_ctx) == 1);
hexchat_set_context(ph, query_ctx);
dh_message++; /* : prefix */
if (*dh_message == '+' || *dh_message == '-')
dh_message++; /* identify-msg */
if (g_strcmp0 (word[6], "CBC") == 0)
mode = FISH_CBC_MODE;
cbc = g_strcmp0 (word[6], "CBC") == 0;
if (!strcmp(dh_message, "DH1080_INIT")) {
char *pub_key;
hexchat_printf(ph, "Received public key from %s (%s), sending mine...", sender, fish_modes[mode]);
if (cbc) {
hexchat_print(ph, "Received key exchange for CBC mode which is not supported.");
goto cleanup;
}
hexchat_printf(ph, "Received public key from %s, sending mine...", sender);
if (dh1080_generate_key(&priv_key, &pub_key)) {
hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s%s", sender, pub_key, (mode == FISH_CBC_MODE) ? " CBC" : "");
hexchat_commandf(ph, "quote NOTICE %s :DH1080_FINISH %s", sender, pub_key);
g_free(pub_key);
} else {
hexchat_print(ph, "Failed to generate keys");
@ -440,6 +279,11 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
g_hash_table_steal(pending_exchanges, sender_lower);
g_free(sender_lower);
if (cbc) {
hexchat_print(ph, "Received key exchange for CBC mode which is not supported.");
goto cleanup;
}
if (!priv_key) {
hexchat_printf(ph, "Received a key exchange response for unknown user: %s", sender);
goto cleanup;
@ -451,8 +295,8 @@ static int handle_keyx_notice(char *word[], char *word_eol[], void *userdata) {
}
if (dh1080_compute_key(priv_key, dh_pubkey, &secret_key)) {
keystore_store_key(sender, secret_key, mode);
hexchat_printf(ph, "Stored new key for %s (%s)", sender, fish_modes[mode]);
keystore_store_key(sender, secret_key);
hexchat_printf(ph, "Stored new key for %s", sender);
g_free(secret_key);
} else {
hexchat_print(ph, "Failed to create secret key!");
@ -470,7 +314,6 @@ cleanup:
static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
const char *nick;
const char *key;
enum fish_mode mode;
/* Check syntax */
if (*word[2] == '\0') {
@ -488,17 +331,9 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
key = word_eol[3];
}
mode = FISH_ECB_MODE;
if (g_ascii_strncasecmp("cbc:", key, 4) == 0) {
key = key+4;
mode = FISH_CBC_MODE;
} else if (g_ascii_strncasecmp("ecb:", key, 4) == 0) {
key = key+4;
}
/* Set password */
if (keystore_store_key(nick, key, mode)) {
hexchat_printf(ph, "Stored key for %s (%s)\n", nick, fish_modes[mode]);
if (keystore_store_key(nick, key)) {
hexchat_printf(ph, "Stored key for %s\n", nick);
} else {
hexchat_printf(ph, "\00305Failed to store key in addon_fishlim.conf\n");
}
@ -510,30 +345,22 @@ static int handle_setkey(char *word[], char *word_eol[], void *userdata) {
* Command handler for /delkey
*/
static int handle_delkey(char *word[], char *word_eol[], void *userdata) {
char *nick = NULL;
int ctx_type = 0;
const char *nick;
/* Delete key from input */
if (*word[2] != '\0') {
nick = g_strstrip(g_strdup(word_eol[2]));
} else { /* Delete key from current context */
nick = g_strdup(hexchat_get_info(ph, "channel"));
ctx_type = hexchat_list_int(ph, NULL, "type");
/* Only allow channel or dialog */
if (ctx_type < 2 || ctx_type > 3) {
hexchat_printf(ph, "%s\n", usage_delkey);
return HEXCHAT_EAT_HEXCHAT;
}
/* Check syntax */
if (*word[2] == '\0' || *word[3] != '\0') {
hexchat_printf(ph, "%s\n", usage_delkey);
return HEXCHAT_EAT_HEXCHAT;
}
nick = g_strstrip (word_eol[2]);
/* Delete the given nick from the key store */
if (keystore_delete_nick(nick)) {
hexchat_printf(ph, "Deleted key for %s\n", nick);
} else {
hexchat_printf(ph, "\00305Failed to delete key in addon_fishlim.conf!\n");
}
g_free(nick);
return HEXCHAT_EAT_HEXCHAT;
}
@ -552,7 +379,7 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
}
if (query_ctx) {
g_assert(hexchat_set_context(ph, query_ctx) == 1);
hexchat_set_context(ph, query_ctx);
ctx_type = hexchat_list_int(ph, NULL, "type");
}
@ -564,8 +391,8 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
if (dh1080_generate_key(&priv_key, &pub_key)) {
g_hash_table_replace (pending_exchanges, g_ascii_strdown(target, -1), priv_key);
hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s CBC", target, pub_key);
hexchat_printf(ph, "Sent public key to %s (CBC), waiting for reply...", target);
hexchat_commandf(ph, "quote NOTICE %s :DH1080_INIT %s", target, pub_key);
hexchat_printf(ph, "Sent public key to %s, waiting for reply...", target);
g_free(pub_key);
} else {
@ -581,9 +408,7 @@ static int handle_keyx(char *word[], char *word_eol[], void *userdata) {
static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) {
const char *target;
const char *topic = word_eol[2];
enum fish_mode mode;
GString *command;
GSList *encrypted_list;
char *buf;
if (!*topic) {
hexchat_print(ph, usage_topic);
@ -596,77 +421,40 @@ static int handle_crypt_topic(char *word[], char *word_eol[], void *userdata) {
}
target = hexchat_get_info(ph, "channel");
/* Check if we can encrypt */
if (!fish_nick_has_key(target)) {
buf = fish_encrypt_for_nick(target, topic);
if (buf == NULL) {
hexchat_printf(ph, "/topic+ error, no key found for %s", target);
return HEXCHAT_EAT_ALL;
}
command = g_string_new("");
g_string_printf(command, "TOPIC %s +OK ", target);
encrypted_list = fish_encrypt_for_nick(target, topic, &mode, get_prefix_length() + command->len);
if (!encrypted_list) {
g_string_free(command, TRUE);
hexchat_printf(ph, "/topic+ error, can't encrypt %s", target);
return HEXCHAT_EAT_ALL;
}
hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_list->data);
g_string_free(command, TRUE);
g_slist_free_full(encrypted_list, g_free);
hexchat_commandf(ph, "TOPIC %s +OK %s", target, buf);
g_free(buf);
return HEXCHAT_EAT_ALL;
}
}
/**
* Command handler for /notice+
*/
static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) {
static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata)
{
const char *target = word[2];
const char *notice = word_eol[3];
char *notice_flag;
enum fish_mode mode;
GString *command;
GSList *encrypted_list, *encrypted_item;
char *buf;
if (!*target || !*notice) {
hexchat_print(ph, usage_notice);
return HEXCHAT_EAT_ALL;
}
/* Check if we can encrypt */
if (!fish_nick_has_key(target)) {
buf = fish_encrypt_for_nick(target, notice);
if (buf == NULL) {
hexchat_printf(ph, "/notice+ error, no key found for %s.", target);
return HEXCHAT_EAT_ALL;
}
command = g_string_new("");
g_string_printf(command, "quote NOTICE %s :+OK ", target);
encrypted_list = fish_encrypt_for_nick(target, notice, &mode, get_prefix_length() + command->len);
if (!encrypted_list) {
g_string_free(command, TRUE);
hexchat_printf(ph, "/notice+ error, can't encrypt %s", target);
return HEXCHAT_EAT_ALL;
}
notice_flag = g_strconcat("[", fish_modes[mode], "] ", notice, NULL);
hexchat_emit_print(ph, "Notice Send", target, notice_flag);
/* Send encrypted messages */
encrypted_item = encrypted_list;
while (encrypted_item) {
hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_item->data);
encrypted_item = encrypted_item->next;
}
g_free(notice_flag);
g_string_free(command, TRUE);
g_slist_free_full(encrypted_list, g_free);
hexchat_commandf(ph, "quote NOTICE %s :+OK %s", target, buf);
hexchat_emit_print(ph, "Notice Sent", target, notice);
g_free(buf);
return HEXCHAT_EAT_ALL;
}
@ -674,102 +462,56 @@ static int handle_crypt_notice(char *word[], char *word_eol[], void *userdata) {
/**
* Command handler for /msg+
*/
static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata) {
static int handle_crypt_msg(char *word[], char *word_eol[], void *userdata)
{
const char *target = word[2];
const char *message = word_eol[3];
char *message_flag;
char *prefix;
hexchat_context *query_ctx;
enum fish_mode mode;
GString *command;
GSList *encrypted_list, *encrypted_item;
char *buf;
if (!*target || !*message) {
hexchat_print(ph, usage_msg);
return HEXCHAT_EAT_ALL;
}
/* Check if we can encrypt */
if (!fish_nick_has_key(target)) {
buf = fish_encrypt_for_nick(target, message);
if (buf == NULL) {
hexchat_printf(ph, "/msg+ error, no key found for %s", target);
return HEXCHAT_EAT_ALL;
}
command = g_string_new("");
g_string_printf(command, "PRIVMSG %s :+OK ", target);
encrypted_list = fish_encrypt_for_nick(target, message, &mode, get_prefix_length() + command->len);
if (!encrypted_list) {
g_string_free(command, TRUE);
hexchat_printf(ph, "/msg+ error, can't encrypt %s", target);
return HEXCHAT_EAT_ALL;
}
/* Send encrypted messages */
encrypted_item = encrypted_list;
while (encrypted_item) {
hexchat_commandf(ph, "%s%s", command->str, (char *) encrypted_item->data);
encrypted_item = encrypted_item->next;
}
g_string_free(command, TRUE);
g_slist_free_full(encrypted_list, g_free);
hexchat_commandf(ph, "PRIVMSG %s :+OK %s", target, buf);
query_ctx = find_context_on_network(target);
if (query_ctx) {
g_assert(hexchat_set_context(ph, query_ctx) == 1);
hexchat_set_context(ph, query_ctx);
prefix = get_my_own_prefix();
/* Add encrypted flag */
message_flag = g_strconcat("[", fish_modes[mode], "] ", message, NULL);
hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"), message_flag, prefix, NULL);
g_free(prefix);
g_free(message_flag);
} else {
/* FIXME: Mode char */
hexchat_emit_print(ph, "Your Message", hexchat_get_info(ph, "nick"),
message, "", "");
}
else {
hexchat_emit_print(ph, "Message Send", target, message);
}
g_free(buf);
return HEXCHAT_EAT_ALL;
}
static int handle_crypt_me(char *word[], char *word_eol[], void *userdata) {
const char *channel = hexchat_get_info(ph, "channel");
enum fish_mode mode;
GString *command;
GSList *encrypted_list, *encrypted_item;
const char *channel = hexchat_get_info(ph, "channel");
char *buf;
/* Check if we can encrypt */
if (!fish_nick_has_key(channel)) {
buf = fish_encrypt_for_nick(channel, word_eol[2]);
if (!buf)
return HEXCHAT_EAT_NONE;
}
command = g_string_new("");
g_string_printf(command, "PRIVMSG %s :\001ACTION +OK ", channel);
hexchat_commandf(ph, "PRIVMSG %s :\001ACTION +OK %s \001", channel, buf);
hexchat_emit_print(ph, "Your Action", hexchat_get_info(ph, "nick"),
word_eol[2], NULL);
/* 2 = ' \001' */
encrypted_list = fish_encrypt_for_nick(channel, word_eol[2], &mode, get_prefix_length() + command->len + 2);
if (!encrypted_list) {
g_string_free(command, TRUE);
hexchat_printf(ph, "/me error, can't encrypt %s", channel);
return HEXCHAT_EAT_ALL;
}
hexchat_emit_print(ph, "Your Action", hexchat_get_info(ph, "nick"), word_eol[2], NULL);
/* Send encrypted messages */
encrypted_item = encrypted_list;
while (encrypted_item) {
hexchat_commandf(ph, "%s%s \001", command->str, (char *) encrypted_item->data);
encrypted_item = encrypted_item->next;
}
g_string_free(command, TRUE);
g_slist_free_full(encrypted_list, g_free);
return HEXCHAT_EAT_ALL;
g_free(buf);
return HEXCHAT_EAT_ALL;
}
/**
@ -815,9 +557,6 @@ int hexchat_plugin_init(hexchat_plugin *plugin_handle,
hexchat_hook_server_attrs(ph, "TOPIC", HEXCHAT_PRI_NORM, handle_incoming, NULL);
hexchat_hook_server_attrs(ph, "332", HEXCHAT_PRI_NORM, handle_incoming, NULL);
if (!fish_init())
return 0;
if (!dh1080_init())
return 0;
@ -831,7 +570,6 @@ int hexchat_plugin_init(hexchat_plugin *plugin_handle,
int hexchat_plugin_deinit(void) {
g_clear_pointer(&pending_exchanges, g_hash_table_destroy);
dh1080_deinit();
fish_deinit();
hexchat_printf(ph, "%s plugin unloaded\n", plugin_name);
return 1;

View File

@ -1,16 +0,0 @@
fishlim_test_sources = [
'tests.c',
'mock-keystore.c',
'../fish.c',
'../utils.c',
]
fishlim_tests = executable('fishlim_tests', fishlim_test_sources,
dependencies: [libgio_dep, libssl_dep, hexchat_plugin_dep],
include_directories: include_directories('..'),
)
test('Fishlim Tests', fishlim_tests,
protocol: 'tap',
timeout: 600,
)

View File

@ -1,51 +0,0 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "fish.h"
/**
* Extracts a key from the key store file.
*/
char *
keystore_get_key(const char *nick, enum fish_mode *mode)
{
return NULL;
}
/**
* Sets a key in the key store file.
*/
gboolean
keystore_store_key(const char *nick, const char *key, enum fish_mode mode)
{
return TRUE;
}
/**
* Deletes a nick from the key store.
*/
gboolean
keystore_delete_nick(const char *nick)
{
return TRUE;
}

View File

@ -1,285 +0,0 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <string.h>
#include <glib.h>
#include "fish.h"
#include "utils.h"
/**
* Auxiliary function: Generate a random string
* @param out Preallocated string to fill
* @param len Size of bytes to fill
*/
static void
random_string(char *out, size_t len)
{
GRand *rand = NULL;
int i = 0;
rand = g_rand_new();
for (i = 0; i < len; ++i) {
out[i] = g_rand_int_range(rand, 1, 256);
}
out[len] = 0;
g_rand_free(rand);
}
/**
* Check encrypt and decrypt in ECB mode
*/
static void
test_ecb(void)
{
char *b64 = NULL;
char *de = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
/* Encrypt */
b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE);
g_assert_nonnull(b64);
/* Decrypt */
/* Linear */
de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE);
g_assert_cmpstr (de, ==, message);
g_free(de);
/* Mixed */
de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE);
g_assert_cmpstr (de, ==, message);
g_free(de);
g_free(b64);
}
}
}
/**
* Check encrypt and decrypt in CBC mode
*/
static void
test_cbc(void)
{
char *b64 = NULL;
char *de = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
/* Encrypt */
b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE);
g_assert_nonnull(b64);
/* Decrypt */
/* Linear */
de = fish_decrypt_str(key, key_len, b64, FISH_CBC_MODE);
g_assert_cmpstr (de, ==, message);
g_free(de);
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encoded string in Base64
*/
static void
test_base64_len (void)
{
char *b64 = NULL;
char message[1000];
int message_end = sizeof (message) - 1;
random_string(message, message_end);
for (; message_end >= 0; --message_end) {
message[message_end] = '\0'; /* Truncate instead of generating new strings */
b64 = g_base64_encode((const unsigned char *) message, message_end);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , base64_len(message_end));
g_free(b64);
}
}
/**
* Check the calculation of final length from an encoded string in BlowcryptBase64
*/
static void
test_base64_fish_len (void)
{
char *b64 = NULL;
int message_len = 0;
char message[1000];
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_base64_encode(message, message_len);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , base64_fish_len(message_len));
g_free(b64);
}
}
/**
* Check the calculation of final length from an encrypted string in ECB mode
*/
static void
test_base64_ecb_len(void)
{
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , ecb_len(message_len));
g_free(b64);
}
}
}
/**
* Check the calculation of final length from an encrypted string in CBC mode
*/
static void
test_base64_cbc_len(void)
{
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
/* Generate key 32448 bits (Yes, I start with 8 bits) */
for (key_len = 1; key_len < 57; ++key_len) {
random_string(key, key_len);
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_CBC_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(strlen(b64), == , cbc_len(message_len));
g_free(b64);
}
}
}
/**
* Check the calculation of length limit for a plaintext in each encryption mode
*/
static void
test_max_text_command_len(void)
{
int max_encoded_len, plaintext_len;
enum fish_mode mode;
for (max_encoded_len = 0; max_encoded_len < 10000; ++max_encoded_len) {
for (mode = FISH_ECB_MODE; mode <= FISH_CBC_MODE; ++mode) {
plaintext_len = max_text_command_len(max_encoded_len, mode);
g_assert_cmpuint(encoded_len(plaintext_len, mode), <= , max_encoded_len);
}
}
}
/**
* Check the calculation of length limit for a plaintext in each encryption mode
*/
static void
test_foreach_utf8_data_chunks(void)
{
GRand *rand = NULL;
GString *chunks = NULL;
int max_chunks_len, chunks_len;
char ascii_message[1001];
char *data_chunk = NULL;
rand = g_rand_new();
max_chunks_len = g_rand_int_range(rand, 2, 301);
random_string(ascii_message, 1000);
data_chunk = ascii_message;
chunks = g_string_new(NULL);
while (foreach_utf8_data_chunks(data_chunk, max_chunks_len, &chunks_len)) {
g_string_append(chunks, g_strndup(data_chunk, chunks_len));
/* Next chunk */
data_chunk += chunks_len;
}
/* Check data loss */
g_assert_cmpstr(chunks->str, == , ascii_message);
g_string_free(chunks, TRUE);
g_rand_free (rand);
}
int
main(int argc, char *argv[]) {
g_test_init(&argc, &argv, NULL);
g_test_add_func("/fishlim/ecb", test_ecb);
g_test_add_func("/fishlim/cbc", test_cbc);
g_test_add_func("/fishlim/base64_len", test_base64_len);
g_test_add_func("/fishlim/base64_fish_len", test_base64_fish_len);
g_test_add_func("/fishlim/base64_ecb_len", test_base64_ecb_len);
g_test_add_func("/fishlim/base64_cbc_len", test_base64_cbc_len);
g_test_add_func("/fishlim/max_text_command_len", test_max_text_command_len);
g_test_add_func("/fishlim/foreach_utf8_data_chunks", test_foreach_utf8_data_chunks);
fish_init();
int ret = g_test_run();
fish_deinit();
return ret;
}

View File

@ -1,151 +0,0 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <string.h>
#include "utils.h"
#include "fish.h"
/**
* Calculate the length of Base64-encoded string
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long base64_len(size_t plaintext_len) {
int length_unpadded = (4 * plaintext_len) / 3;
/* Add padding */
return length_unpadded % 4 != 0 ? length_unpadded + (4 - length_unpadded % 4) : length_unpadded;
}
/**
* Calculate the length of BlowcryptBase64-encoded string
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long base64_fish_len(size_t plaintext_len) {
int length_unpadded = (12 * plaintext_len) / 8;
/* Add padding */
return length_unpadded % 12 != 0 ? length_unpadded + (12 - length_unpadded % 12) : length_unpadded;
}
/**
* Calculate the length of fish-encrypted string in CBC mode
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long cbc_len(size_t plaintext_len) {
/*IV + DATA + Zero Padding */
return base64_len(8 + (plaintext_len % 8 != 0 ? plaintext_len + 8 - (plaintext_len % 8) : plaintext_len));
}
/**
* Calculate the length of fish-encrypted string in ECB mode
*
* @param plaintext_len Size of clear text to encode
* @return Size of encoded string
*/
unsigned long ecb_len(size_t plaintext_len) {
return base64_fish_len(plaintext_len);
}
/**
* Calculate the length of encrypted string in 'mode' mode
*
* @param plaintext_len Length of plaintext
* @param mode Encryption mode
* @return Size of encoded string
*/
unsigned long encoded_len(size_t plaintext_len, enum fish_mode mode) {
switch (mode) {
case FISH_CBC_MODE:
return cbc_len(plaintext_len);
break;
case FISH_ECB_MODE:
return ecb_len(plaintext_len);
}
return 0;
}
/**
* Determine the maximum length of plaintext for a 'max_len' limit taking care the overload of encryption
*
* @param max_len Limit for plaintext
* @param mode Encryption mode
* @return Maximum allowed plaintext length
*/
int max_text_command_len(size_t max_len, enum fish_mode mode) {
int len;
for (len = max_len; encoded_len(len, mode) > max_len; --len);
return len;
}
/**
* Iterate over 'data' in chunks of 'max_chunk_len' taking care the UTF-8 characters
*
* @param data Data to iterate
* @param max_chunk_len Size of biggest chunk
* @param [out] chunk_len Current chunk length
* @return Pointer to current chunk position or NULL if not have more chunks
*/
const char *foreach_utf8_data_chunks(const char *data, int max_chunk_len, int *chunk_len) {
int data_len, last_chunk_len = 0;
if (!*data) {
return NULL;
}
/* Last chunk of data */
data_len = strlen(data);
if (data_len <= max_chunk_len) {
*chunk_len = data_len;
return data;
}
*chunk_len = 0;
const char *utf8_character = data;
/* Not valid UTF-8, but maybe valid text, just split into max length */
if (!g_utf8_validate(data, -1, NULL)) {
*chunk_len = max_chunk_len;
return utf8_character;
}
while (*utf8_character && *chunk_len <= max_chunk_len) {
last_chunk_len = *chunk_len;
*chunk_len = (g_utf8_next_char(utf8_character) - data) * sizeof(*utf8_character);
utf8_character = g_utf8_next_char(utf8_character);
}
/* We need the previous length before overflow the limit */
*chunk_len = last_chunk_len;
return utf8_character;
}

View File

@ -1,39 +0,0 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef PLUGIN_HEXCHAT_FISHLIM_UTILS_H
#define PLUGIN_HEXCHAT_FISHLIM_UTILS_H
#include <stddef.h>
#include "fish.h"
unsigned long base64_len(size_t plaintext_len);
unsigned long base64_fish_len(size_t plaintext_len);
unsigned long cbc_len(size_t plaintext_len);
unsigned long ecb_len(size_t plaintext_len);
unsigned long encoded_len(size_t plaintext_len, enum fish_mode mode);
int max_text_command_len(size_t max_len, enum fish_mode mode);
const char *foreach_utf8_data_chunks(const char *data, int max_chunk_len, int *chunk_len);
#endif

View File

@ -35,8 +35,6 @@
#include <hexchat-plugin.h>
#define WORD_ARRAY_LEN 32
static char plugin_name[] = "Lua";
static char plugin_description[] = "Lua scripting interface";
static char plugin_version[16] = "1.3";
@ -277,13 +275,13 @@ static int api_command_closure(char *word[], char *word_eol[], void *udata)
base = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word[i]);
lua_rawseti(L, -2, i);
}
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word_eol[i]);
lua_rawseti(L, -2, i);
@ -464,13 +462,13 @@ static int api_server_closure(char *word[], char *word_eol[], void *udata)
base = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word[i]);
lua_rawseti(L, -2, i);
}
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word_eol[i]);
lua_rawseti(L, -2, i);
@ -523,13 +521,13 @@ static int api_server_attrs_closure(char *word[], char *word_eol[], hexchat_even
base = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word[i]);
lua_rawseti(L, -2, i);
}
lua_newtable(L);
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
for(i = 1; i < 32 && *word_eol[i]; i++)
{
lua_pushstring(L, word_eol[i]);
lua_rawseti(L, -2, i);
@ -957,21 +955,16 @@ static int api_hexchat_pluginprefs_meta_pairs(lua_State *L)
hexchat_plugin *h;
if(!script->name)
return luaL_error(L, "cannot use hexchat.pluginprefs before registering with hexchat.register");
dest = lua_newuserdata(L, 4096);
h = script->handle;
if(!hexchat_pluginpref_list(h, dest))
strcpy(dest, "");
lua_pushlightuserdata(L, dest);
lua_pushlightuserdata(L, dest);
lua_pushcclosure(L, api_hexchat_pluginprefs_meta_pairs_closure, 2);
lua_insert(L, -2); // Return the userdata (second return value from pairs),
// even though it's not used by the closure (first return
// value from pairs), so that Lua knows not to GC it.
return 2;
return 1;
}
static int api_attrs_meta_index(lua_State *L)
@ -1194,11 +1187,11 @@ static void patch_clibs(lua_State *L)
if(lua_type(L, -2) == LUA_TLIGHTUSERDATA && lua_type(L, -1) == LUA_TTABLE)
{
lua_setfield(L, LUA_REGISTRYINDEX, "_CLIBS");
lua_pop(L, 1);
break;
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
static GPtrArray *scripts;
@ -1769,3 +1762,4 @@ G_MODULE_EXPORT int hexchat_plugin_deinit(hexchat_plugin *plugin_handle)
g_clear_pointer(&expand_buffer, g_free);
return 1;
}

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -26,7 +26,7 @@ if get_option('with-lua') != 'false'
subdir('lua')
endif
if get_option('with-perl') != 'false'
if get_option('with-perl')
subdir('perl')
endif

156
plugins/mpcinfo/functions.c Normal file
View File

@ -0,0 +1,156 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <glib.h>
char *split(char *text, char separator)
{
int pos = -1;
size_t i;
for (i = 0; i < strlen(text); i++)
{
if (text[i] == separator) {
pos = i;
i = strlen(text) + 1;
}
}
if (pos == -1)
{
return text;
}
text[pos] = 0;
return &(text[pos + 1]);
}
int endsWith(char *text, char *suffix){
char *tmp=strstr(text,suffix);
if (tmp==NULL) return 0;
if (strlen(tmp)==strlen(suffix)) return 1;
return 0;
}
int inStr(char *s1, size_t sl1, char *s2)
{
size_t i;
for (i = 0; i < sl1 - strlen(s2); i++)
{
size_t j;
for (j = 0; j < strlen(s2); j++)
{
if (s1[i + j] != s2[j])
{
j = strlen(s2) + 2;
}
}
if (j == strlen(s2))
{
return i;
}
}
return -1;
}
static char *subString(char *text, int first, int length, int spcKill){
//if (DEBUG==1) putlog("creating substring");
char *ret = g_new (char, length + 1);
int i;
ret[length]=0;
for (i=0;i<length;i++){
ret[i]=text[i+first];
//if (ret[i]==0) ret[i]='0';
}
if (spcKill==1){
for (i=length-1;i>=0;i--){
if (ret[i]==32) ret[i]=0;
else i=-1;
}
}
//if (DEBUG==1) putlog("substring created");
return ret;
}
static char *substring(char *text, int first, int length){return subString(text,first,length,0);}
char *readLine(FILE *f){
//if (DEBUG==1) putlog("reading line from file");
char *buffer = g_new (char, 1024);
int pos=0;
int cc=0;
while((cc!=EOF)&&(pos<1024)&&(cc!=10)){
cc=fgetc(f);
if ((cc!=10)&&(cc!=13)){
if (cc==EOF) buffer[pos]=0;
else buffer[pos]=(char)cc;pos++;
}
}
if (buffer[pos]==EOF) hexchat_printf(ph,"EOF: %i\n",pos);
return buffer;
}
char *toUpper(char *text)
{
char *ret = (char*) calloc(strlen(text) + 1, sizeof(char));
size_t i;
for (i = 0; i < strlen(text); i++)
{
ret[i] = toupper(text[i]);
}
ret[strlen(text)] = 0;
return ret;
}
static char *str3cat(char *s1, char *s2, char *s3){
//if (DEBUG==1) putlog("cating 3 strings");
char *ret=(char*)calloc(strlen(s1)+strlen(s2)+strlen(s3)+1,sizeof(char));
strcpy(ret,s1);strcat(ret,s2);strcat(ret,s3);
ret[strlen(s1)+strlen(s2)+strlen(s3)]=0;
//if (DEBUG==1) putlog("strings cated");
return ret;
}
char *replace(char *text, char *from, char *to){
//if (DEBUG==1) putlog("replacing");
char *ret=(char*)calloc( strlen(text)+(strlen(to)-strlen(from)),sizeof(char));
char *left;
char *right;
int pos=inStr(text,strlen(text),from);
if (pos!=-1){
left=substring(text,0,pos);
right=substring(text,pos+strlen(from),strlen(text)-(pos+strlen(from)));
ret=str3cat(left,to,right);
return replace(ret,from,to);
}
//if (DEBUG==1) putlog("replaced");
return text;
}
char *intReplaceF(char *text, char *from, int to, char *form){
//if (DEBUG==1) putlog("replaceF");
char *buffer=(char*) calloc(16,sizeof(char));
sprintf(buffer,form,to);
//if (DEBUG==1) putlog("replaceF done");
return replace(text,from,buffer);
}
char *intReplace(char *text, char *from, int to){return intReplaceF(text,from,to,"%i");}

334
plugins/mpcinfo/mp3Info.c Normal file
View File

@ -0,0 +1,334 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
//#include <stdio.h>
#include <sys/stat.h>
//#include "functions.c"
struct tagInfo{
int mode;
int cbr;
int bitrate;
unsigned int freq;
char *artist;
char *title;
char *album;
char *comment;
char *genre;
//int genre;
//int track;
};
static int RATES[2][3][15]={
{//mpeg2
{-1,8,16,24,32,64,80,56,64,128,160,112,128,256,320},//layer3 (V2)
{-1,32,48,56,64,80,96,112,128,160,192,224,256,320,384},//layer2 (V2)
{-1,32,64,96,128,160,192,224,256,288,320,352,384,416,448},//layer1 (V2)
},
{//mpeg1
{-1,32,40,48,56,64,80,96,112,128,160,192,224,256,320},//layer3 (V1)
{-1,32,48,56,64,80,96,112,128,160,192,224,256,320,384},//layer2 (V1)
{-1,32,64,96,128,160,192,224,256,288,320,352,384,416,448},//layer1 (V1)
}};
static int FREQS[2][4]={{22050,24000,16000,-1},{44100,48000,32000,-1}};
//static double FRATES[]={38.5,32.5,27.8,0.0};
static char GENRES[][50]={"Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal",
"New Age","Oldies","Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial",
"Alternative","Ska","Death Metal","Pranks","Soundtrack","Euro-Techno","Ambient","Trip-Hop","Vocal","Jazz+Funk",
"Fusion","Trance","Classical","Instrumental","Acid","House","Game","Sound Clip","Gospel","Noise",
"AlternRock","Bass","Soul","Punk","Space","Meditative","Instrumental Pop","Instrumental Rock","Ethnic","Gothic",
"Darkwave","Techno-Industrial","Electronic","Pop-Folk","Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta",
"Top 40","Christian Rap","Pop/Funk","Jungle","Native American","Cabaret","New Wave","Psychadelic","Rave","Showtunes",
"Trailer","Lo-Fi","Tribal","Acid Punk","Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard Rock",
//################## END OF OFFICIAL ID3 TAGS, WINAMP TAGS BELOW ########################################
"Folk","Folk/Rock","National Folk","Swing","Fast Fusion","Bebob","Latin","Revival","Celtic","Bluegrass",
"Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock","Slow Rock","Big Band","Chorus","Easy Listening","Acoustic",
"Humour","Speech","Chanson","Opera","Chamber Music","Sonata","Symphony","Booty Bass","Primus","Porn Groove",
"Satire","Slow Jam","Club","Tango","Samba","Folklore","Ballad","Poweer Ballad","Rhytmic Soul","Freestyle",
"Duet","Punk Rock","Drum Solo","A Capela","Euro-House","Dance Hall",
//################## FOUND AT http://en.wikipedia.org/wiki/ID3 ###########################################
"Goa","Drum & Bass","Club-House","Hardcore",
"Terror","Indie","BritPop","Negerpunk","Polsk Punk","Beat","Christian Gangsta Rap","Heavy Metal","Black Metal","Crossover",
"Contemporary Christian","Christian Rock","Merengue","Salsa","Thrash Metal","Anime","JPop","Synthpop"
};
static char MODES [][13]={"Stereo","Joint-Stereo","Dual-Channel","Mono"};
int iPow(int x, int y){return (int)(pow((double)x,(double) y));}
int str2int(char *text)
{
int ret = 0;
size_t i;
for (i = 1; i <= strlen(text); i++)
{
if ((text[strlen(text) - i] > 57) || (text[strlen(text) - i] < 48))
{
hexchat_printf(ph, "invalid char in string: %i", (int) text[strlen(text) - i]);
return 255;
}
ret += ((int) text[strlen(text) - i] - 48)*iPow(10, i - 1);
}
return ret;
}
static char *tagExtract(char *tag, int tagLen, char* info){
//if (DEBUG==1) putlog("extracting tag");
int pos, len, i;
pos=inStr(tag,tagLen,info);
//hexchat_printf(ph,"pos=%i",pos);
if (pos==-1) return "";//NULL;
//printf("position of %s = %i\n",info,pos);
len=0;
//for (i=pos;i<pos+10;i++)printf("tag[%i]=%i \n",i,tag[i]);
for (i=0;i<4;i++) {
len+=tag[pos+strlen(info)+i]*iPow(255,3-i);
}
//printf("Tag-Length: %i\n",len);
if (strcmp("COMM",info)!=0) return substring(tag,pos+7+strlen(info),len-1);//11
return substring(tag,pos+7+strlen(info),len-1);//11
//char *ct=substring(tag,pos+7+strlen(info),len-1);//11
//return substring(ct,strlen(ct)+1,len-1-strlen(ct)); //<-- do not understand, what i did here :(
}
struct tagInfo readID3V1(char *file){
//if (DEBUG==1) putlog("reading ID3V1");
FILE *f;
struct tagInfo ret;
int res, i, c, val;
char *tag;
char *id;
char *tmp;
ret.artist=NULL;
f=fopen(file,"rb");
if (f==NULL){
hexchat_print(ph,"file not found while trying to read id3v1");
//if (DEBUG==1) putlog("file not found while trying to read id3v1");
return ret;
}
//int offset=getSize(file)-128;
res=fseek(f,-128,SEEK_END);
if (res!=0) {printf("seek failed\n");fclose(f);return ret;}
tag = (char*) malloc(sizeof(char)*129);
//long int pos=ftell(f);
//printf("position= %li\n",pos);
for (i=0;i<128;i++) {
c=fgetc(f);
if (c==EOF) {hexchat_printf(ph,"read ID3V1 failed\n");fclose(f);free(tag);return ret;}
tag[i]=(char)c;
}
fclose(f);
//printf("tag readed: \n");
id=substring(tag,0,3);
//printf("header: %s\n",id);
res=strcmp(id,"TAG");
free(id);
if (res!=0){hexchat_printf(ph,"no id3 v1 found\n");free(tag);return ret;}
ret.title=subString(tag,3,30,1);
ret.artist=subString(tag,33,30,1);
ret.album=subString(tag,63,30,1);
ret.comment=subString(tag,97,30,1);
tmp=substring(tag,127,1);
//ret.genre=substring(tag,127,1);
val=(int)tmp[0];
if (val<0)val+=256;
//hexchat_printf(ph, "tmp[0]=%i (%i)",val,tmp[0]);
if ((val<148)&&(val>=0))
ret.genre=GENRES[val];//#############changed
else {
ret.genre="unknown";
//hexchat_printf(ph, "tmp[0]=%i (%i)",val,tmp[0]);
}
//hexchat_printf(ph, "tmp: \"%s\" -> %i",tmp,tmp[0]);
//hexchat_printf(ph,"genre \"%s\"",ret.genre);
//if (DEBUG==1) putlog("id3v1 extracted");
free(tmp);
free(tag);
return ret;
}
char *extractID3Genre(char *tag)
{
if (tag[strlen(tag) - 1] == ')')
{
tag[strlen(tag) - 1] = 0;
tag = &tag[1];
return GENRES[str2int(tag)];
}
else
{
size_t i;
for (i = 0; i < strlen(tag); i++)
{
if (tag[i] == ')')
{
tag = &tag[i] + 1;
return tag;
}
}
}
return "[152] failed";
}
struct tagInfo readID3V2(char *file){
//if (DEBUG==1) putlog("reading id3v2");
FILE *f;
int i, c, len;
char header[10];
char *tag;
struct tagInfo ret;
f = fopen(file,"rb");
//hexchat_printf(ph,"file :%s",file);
if (f==NULL)
{
hexchat_print(ph,"file not found whilt trying to read ID3V2");
//if (DEBUG==1)putlog("file not found while trying to read ID3V2");
return ret;
}
ret.artist=NULL;
for (i=0;i<10;i++){
c=fgetc(f);
if (c==EOF){
fclose(f);
//putlog("found eof while reading id3v2");
return ret;
}
header[i]=(char)c;
}
if (strstr(header,"ID3")==header){
//hexchat_printf(ph,"found id3v2\n");
len=0;
for (i=6;i<10;i++) len+=(int)header[i]*iPow(256,9-i);
//char *tag=(char*)malloc(sizeof(char)*len);
tag=(char*) calloc(len,sizeof(char)); //malloc(sizeof(char)*len);
for (i=0;i<len;i++){c=fgetc(f);tag[i]=(char)c;}
//hexchat_printf(ph,"tag length: %i\n",len);
//hexchat_printf(ph,"tag: %s\n",tag);
fclose(f);
ret.comment=tagExtract(tag,len,"COMM");
//hexchat_printf(ph,"Comment: %s\n",ret.comment);
ret.genre=tagExtract(tag,len,"TCON");
//if (strcmp(ret.genre,"(127)")==0) ret.genre="unknown";
//hexchat_printf(ph, "ret.genre = %s",ret.genre);
if ((ret.genre!=NULL)&&(ret.genre[0]=='(')) ret.genre=extractID3Genre(ret.genre);
//hexchat_printf(ph,"genre: %s\n",ret.genre);
ret.title=tagExtract(tag,len,"TIT2");
//hexchat_printf(ph,"Title: %s\n",ret.title);
ret.album=tagExtract(tag,len,"TALB");
//hexchat_printf(ph,"Album: %s\n",ret.album);
ret.artist=tagExtract(tag,len,"TPE1");
//hexchat_printf(ph,"Artist: %s\n",ret.artist);
}
else{fclose(f);printf("no id3v2 tag found\n"); return ret;}
//printf("id2v2 done\n");
//if (DEBUG==1) putlog("id3v2 readed");
return ret;
}
struct tagInfo readHeader(char *file){
//if (DEBUG==1) putlog("reading header");
FILE *f;
//int buffer[5120];
int versionB, layerB, bitrateB, freqB, modeB;
int header[4];
int count=0;
int cc=0;
struct tagInfo info;
info.artist=NULL;
f = fopen(file,"rb");
if (f==NULL)
{
hexchat_print(ph,"file not found while trying to read mp3 header");
//if (DEBUG==1) putlog("file not found while trying to read mp3 header");
return info;
}
//struct tagInfo tagv2
info=readID3V2(file);
//struct tagInfo tagv1;//=readID3V1(file);
//if (tagv2.artist!=NULL){info=tagv2;}
//else {
if (info.artist==NULL){
//printf("searching for id3v1\n");
//tagv1=readID3V1(file);
info=readID3V1(file); //#####################
}
/*
if (tagv1.artist!=NULL){
//printf("Artist: %s\nTitle: %s\nAlbum: %s\nComment: %s\nGenre: %s\n",tagv1.artist,tagv1.title,tagv1.album,tagv1.comment,tagv1.genre);
info=tagv1;
}
*/
while ((count<5120)&&(cc!=EOF)&&(cc!=255)) {cc=fgetc(f);count++;}
if ((cc==EOF)||(count==5119)) printf("no header found\n");
else {
//printf("located header at %i\n",count);
header[0]=255;
for (count=1;count<4;count++){
header[count]=fgetc(f);
//printf("header[%i]=%i\n",count,header[count]);
}
versionB=(header[1]&8)>>3;
layerB=(header[1]&6)>>1;
bitrateB=(header[2]&240)>>4; //4
freqB=(header[2]&12)>>2;//2
modeB=(header[3]&192)>>6;//6
//printf("Mpeg: %i\nLayer: %i\nBitrate: %i\nFreq: %i\nMode: %i\n",versionB, layerB, bitrateB, freqB, modeB);
//int Bitrate=RATES[versionB][layerB-1][bitrateB];
//int Freq=FREQS[versionB][freqB];
info.bitrate=RATES[versionB][layerB-1][bitrateB];
info.freq=FREQS[versionB][freqB];
info.mode=modeB;
}
fclose(f);
//if (DEBUG==1) putlog("header readed");
return info;
}
/*
static void printMp3Info(char *file){
//printf("\nScanning Mp3-File for Informations: %s\n",file);
//printf("size:\t%10d byte\n",getSize(file));
struct tagInfo info =readHeader(file);
printf("%s | %10d",file,getSize(file));
if (info.bitrate>0){
//printf("Bitrate: %i\nFreq: %i\nMode: %s\n",info.bitrate,info.freq,MODES[info.mode]);
printf(" | %i kbps | %i kHz | %s",info.bitrate,info.freq,MODES[info.mode]);
//if (info.artist!=NULL) printf("\nArtist: %s\nTitle: %s\nAlbum: %s\nComment: %s\nGenre: %s\n",info.artist,info.title,info.album,info.comment,info.genre);
if (info.artist!=NULL) {
printf("| %s | %s | %s | %s | %s",info.artist,info.title,info.album,info.comment,info.genre);
//printf("| %s ",info.title);//,info.title,info.album,info.comment,info.genre
}
}
printf("\n");
}
*/

160
plugins/mpcinfo/mpcInfo.c Normal file
View File

@ -0,0 +1,160 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
//static int DEBUG=0;
static char *VERSION="0.0.6";
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "hexchat-plugin.h"
static hexchat_plugin *ph;
#include "functions.c"
#include "mp3Info.c"
#include "oggInfo.c"
#include "theme.c"
static int print_themes (char *word[], char *word_eol[], void *userdata){
printThemes();
return HEXCHAT_EAT_ALL;
}
static int mpc_themeReload(char *word[], char *word_eol[], void *userdata){
themeInit();
loadThemes();
return HEXCHAT_EAT_ALL;
}
static int mpc_tell(char *word[], char *word_eol[], void *userdata){
char *tTitle, *zero, *oggLine, *line;
struct tagInfo info;
HWND hwnd = FindWindow("MediaPlayerClassicW",NULL);
if (hwnd==0) {hexchat_print(ph, randomLine(notRunTheme));return HEXCHAT_EAT_ALL;}
tTitle = g_new(char, 1024);
GetWindowText(hwnd, tTitle, 1024);
zero = strstr (tTitle, " - Media Player Classic");
if (zero != NULL)
{
zero[0] = 0;
}
else
{
g_free(tTitle);
hexchat_print(ph, "pattern not found");
return HEXCHAT_EAT_ALL;
}
if ((tTitle[1]==':')&&(tTitle[2]=='\\')){
//hexchat_print(ph,"seams to be full path");
if (endsWith(tTitle,".mp3")==1){
//hexchat_print(ph,"seams to be a mp3 file");
info = readHeader(tTitle);
if ((info.artist!=NULL)&&(strcmp(info.artist,"")!=0)){
char *mode=MODES[info.mode];
//hexchat_printf(ph,"mode: %s\n",mode);
char *mp3Line=randomLine(mp3Theme);
mp3Line=replace(mp3Line,"%art",info.artist);
mp3Line=replace(mp3Line,"%tit",info.title);
mp3Line=replace(mp3Line,"%alb",info.album);
mp3Line=replace(mp3Line,"%com",info.comment);
mp3Line=replace(mp3Line,"%gen",info.genre);
//mp3Line=replace(mp3Line,"%time",pos);
//mp3Line=replace(mp3Line,"%length",len);
//mp3Line=replace(mp3Line,"%ver",waVers);
//mp3Line=intReplace(mp3Line,"%br",br);
//mp3Line=intReplace(mp3Line,"%frq",frq);
mp3Line=intReplace(mp3Line,"%br",info.bitrate);
mp3Line=intReplace(mp3Line,"%frq",info.freq);
mp3Line=replace(mp3Line,"%mode",mode);
//mp3Line=replace(mp3Line,"%size",size);
//mp3Line=intReplace(mp3Line,"%perc",perc);
//mp3Line=replace(mp3Line,"%plTitle",title);
mp3Line=replace(mp3Line,"%file",tTitle);
g_free(tTitle);
hexchat_command(ph, mp3Line);
return HEXCHAT_EAT_ALL;
}
}
if (endsWith(tTitle,".ogg")==1){
hexchat_printf(ph,"Ogg detected\n");
info = getOggHeader(tTitle);
if (info.artist!=NULL){
char *cbr;
if (info.cbr==1) cbr="CBR"; else cbr="VBR";
oggLine=randomLine(oggTheme);
//if (cue==1) oggLine=cueLine;
//hexchat_printf(ph,"ogg-line: %s\n",oggLine);
oggLine=replace(oggLine,"%art",info.artist);
oggLine=replace(oggLine,"%tit",info.title);
oggLine=replace(oggLine,"%alb",info.album);
oggLine=replace(oggLine,"%com",info.comment);
oggLine=replace(oggLine,"%gen",info.genre);
//oggLine=replace(oggLine,"%time",pos);
//oggLine=replace(oggLine,"%length",len);
//oggLine=replace(oggLine,"%ver",waVers);
oggLine=intReplace(oggLine,"%chan",info.mode);
oggLine=replace(oggLine,"%cbr",cbr);
oggLine=intReplace(oggLine,"%br",info.bitrate/1000);//br);
oggLine=intReplace(oggLine,"%frq",info.freq);
//oggLine=replace(oggLine,"%size",size);
//oggLine=intReplace(oggLine,"%perc",perc);
//oggLine=replace(oggLine,"%plTitle",title);
oggLine=replace(oggLine,"%file",tTitle);
g_free(tTitle);
hexchat_command(ph, oggLine);
return HEXCHAT_EAT_ALL;
}
}
}
line=randomLine(titleTheme);
line=replace(line,"%title", tTitle);
g_free(tTitle);
hexchat_command(ph, line);
return HEXCHAT_EAT_ALL;
}
int hexchat_plugin_init(hexchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg){
ph = plugin_handle;
*plugin_name = "mpcInfo";
*plugin_desc = "Information-Script for Media Player Classic";
*plugin_version=VERSION;
hexchat_hook_command(ph, "mpc", HEXCHAT_PRI_NORM, mpc_tell,"no help text", 0);
hexchat_hook_command(ph, "mpc_themes", HEXCHAT_PRI_NORM, print_themes,"no help text", 0);
hexchat_hook_command(ph, "mpc_reloadthemes", HEXCHAT_PRI_NORM, mpc_themeReload,"no help text", 0);
hexchat_command (ph, "MENU -ishare\\music.png ADD \"Window/Display Current Song (MPC)\" \"MPC\"");
themeInit();
loadThemes();
hexchat_printf(ph, "%s plugin loaded\n", *plugin_name);
return 1;
}
int
hexchat_plugin_deinit (void)
{
hexchat_command (ph, "MENU DEL \"Window/Display Current Song (MPC)\"");
hexchat_print (ph, "mpcInfo plugin unloaded\n");
return 1;
}

View File

@ -0,0 +1,3 @@
EXPORTS
hexchat_plugin_init
hexchat_plugin_deinit

View File

@ -0,0 +1,59 @@
<?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>v140</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>{B0E36D93-CA2A-49FE-9EB9-9C96C6016EEC}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>mpcinfo</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\hexchat.props" />
<PropertyGroup>
<TargetName>hcmpcinfo</TargetName>
<OutDir>$(HexChatRel)plugins\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;MPCINFO_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\src\common;$(Glib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>mpcinfo.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_WIN64;_AMD64_;NDEBUG;_WINDOWS;_USRDLL;MPCINFO_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\src\common;$(Glib);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>mpcinfo.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="mpcinfo.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="mpcInfo.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

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

124
plugins/mpcinfo/oggInfo.c Normal file
View File

@ -0,0 +1,124 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
static int getOggInt(char *buff, int beg, int bytes){
//if (DEBUG==1) putlog("getOggInt");
int ret=0;
int i;
for (i=0;i<bytes;i++){
if (buff[i+beg]>=0) ret+=buff[i+beg]*iPow(256,i);else ret+=(256+buff[i+beg])*iPow(256,i);
//printf("[%i]=%i\n",i,buff[i+beg]);
}
return ret;
}
static char *upperStr(char *text)
{
char *ret = (char*) malloc(sizeof(char)*(strlen(text) + 1));
size_t i;
for (i = 0; i < strlen(text); i++)
{
ret[i] = toupper(text[i]);
}
ret[strlen(text)] = 0;
return ret;
}
struct tagInfo getOggHeader(char *file){
//if (DEBUG==1) putlog("reading ogg header");
char header[4096];
int i, c;
int h1pos, h3pos, maxBr, nomBr, minBr, pos, count, tagLen;
char *sub;
char *name;
char *val;
char HEADLOC1[]="_vorbis", HEADLOC3[]="_vorbis", HEADLOC5[]="_vorbis";
FILE *f;
struct tagInfo info;
info.artist=NULL;
f = fopen(file,"rb");
if (f==NULL){
hexchat_print(ph,"file not found while trying to read ogg header");
//if (DEBUG==1) putlog("file not found while trying to read ogg header");
return info;
}
for (i=0;i<4095;i++) {c=fgetc(f);header[i]=(char)c;}
fclose(f);
HEADLOC1[0]=1;
HEADLOC3[0]=3;
HEADLOC5[0]=5;
h1pos=inStr(header,4096,HEADLOC1);
h3pos=inStr(header,4096,HEADLOC3);
//int h5pos=inStr(header,4096,HEADLOC5); //not needed
//printf("loc1: %i\n",h1pos);printf("loc3: %i\n",h3pos);printf("loc5: %i\n",h5pos);
maxBr=getOggInt(header,h1pos+7+9,4);
nomBr=getOggInt(header,h1pos+7+13,4);
minBr=getOggInt(header,h1pos+7+17,4);
info.freq=getOggInt(header,h1pos+7+5,4);
info.mode=header[h1pos+7+4];
info.bitrate=nomBr;
if (((maxBr==nomBr)&&(nomBr=minBr))||((minBr==0)&&(maxBr==0))||((minBr=-1)&&(maxBr=-1)) )info.cbr=1;else info.cbr=0;
printf("bitrates: %i|%i|%i\n",maxBr,nomBr,minBr);
printf("freq: %u\n",info.freq);
pos=h3pos+7;
pos+=getOggInt(header,pos,4)+4;
count=getOggInt(header,pos,4);
//printf("tags: %i\n",count);
pos+=4;
info.artist=NULL;info.title=NULL;info.album=NULL;info.comment=NULL;info.genre=NULL;
for (i=0;i<count;i++){
tagLen=getOggInt(header,pos,4);
//printf("taglength: %i\n",tagLen);
sub=substring(header,pos+4,tagLen);
name=upperStr(substring(sub,0,inStr(sub,tagLen,"=")));
val=substring(sub,inStr(sub,tagLen,"=")+1,tagLen-inStr(sub,tagLen,"=")-1);
//printf("Tag: %s\n",sub);
//printf("Name: %s\n",name);
//printf("value: %s\n",val);
if (strcmp(name,"ARTIST")==0) info.artist=val;
if (strcmp(name,"TITLE")==0) info.title=val;
if (strcmp(name,"ALBUM")==0) info.album=val;
if (strcmp(name,"GENRE")==0) info.genre=val;
if (strcmp(name,"COMMENT")==0) info.comment=val;
pos+=4+tagLen;
free(name);
}
if (info.artist==NULL) info.artist="";
if (info.album==NULL) info.album ="";
if (info.title==NULL) info.title="";
if (info.genre==NULL) info.genre="";
if (info.comment==NULL) info.comment="";
printf("Artist: %s\nTitle: %s\nAlbum: %s\n",info.artist,info.title, info.album);
printf("Genre: %s\nComment: %s\nMode: %i\nCBR: %i\n",info.genre,info.comment,info.mode,info.cbr);
//if (DEBUG==1) putlog("ogg header readed");
return info;
}
/*
void printOggInfo(char *file){
printf("Scanning Ogg-File for Informations: %s\n",file);
printf("size:\t%10d byte\n",getSize(file));
struct tagInfo info = getOggHeader(file);
}
*/

148
plugins/mpcinfo/theme.c Normal file
View File

@ -0,0 +1,148 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <time.h>
struct theme{
int size;
char **line;
};
static struct theme notRunTheme;
static struct theme titleTheme;
static struct theme mp3Theme;
static struct theme oggTheme;
void themeInit(){
//if (DEBUG==1) putlog("init theme");
/*mp3Theme.size=0;oggTheme.size=0;cueTheme.size=0;streamTheme.size=0;etcTheme.size=0;
stopTheme.size=0;pauseTheme.size=0;*/
notRunTheme.size=0;titleTheme.size=0;
srand((unsigned int)time((time_t *)NULL));
//if (DEBUG==1) putlog("theme init done");
}
void printTheme(struct theme data){
int i;
for (i=0;i<data.size;i++) hexchat_printf(ph,"line[%i]=%s\n",i,data.line[i]);
}
void printThemes(){
hexchat_printf(ph,"\nNotRun-Theme:\n");printTheme(notRunTheme);
hexchat_printf(ph,"\nMP3-Theme:\n");printTheme(mp3Theme);
hexchat_printf(ph,"\nOGG-Theme:\n");printTheme(oggTheme);
hexchat_printf(ph,"\nTitle-Theme:\n");printTheme(titleTheme);
}
void cbFix(char *line)
{
size_t i;
for (i = 0; i < strlen(line); i++)
{
size_t j;
if (line[i] == '%')
{
if ((line[i + 1] == 'C') || (line[i + 1] == 'B') || (line[i + 1] == 'U') || (line[i + 1] == 'O') || (line[i + 1] == 'R'))
{
if (line[i + 1] == 'C') line[i] = 3;
if (line[i + 1] == 'B') line[i] = 2;
if (line[i + 1] == 'U') line[i] = 37;
if (line[i + 1] == 'O') line[i] = 17;
if (line[i + 1] == 'R') line[i] = 26;
for (j = i + 1; j < strlen(line) - 1; j++)
{
line[j] = line[j + 1];
}
line[strlen(line) - 1] = 0;
}
}
}
}
struct theme themeAdd(struct theme data, char *info){
//if (DEBUG==1) putlog("adding theme");
struct theme ret;
char **newLine=(char **)calloc(data.size+1,sizeof(char*));
int i;
for (i=0;i<data.size;i++) newLine[i]=data.line[i];
cbFix(info);
newLine[data.size]=info;
ret.line=newLine;ret.size=data.size+1;
//if (DEBUG==1) putlog("theme added");
return ret;
}
void loadThemes(){
char *hDir, *hFile, *line, *lineCap, *val;
FILE *f;
hexchat_print(ph,"loading themes\n");
hDir=(char*)calloc(1024,sizeof(char));
strcpy(hDir,hexchat_get_info(ph,"configdir"));
hFile=str3cat(hDir,"\\","mpcInfo.theme.txt");
f = fopen(hFile,"r");
free(hDir);
free(hFile);
if(f==NULL)
{
hexchat_print(ph,"no theme in homedir, checking global theme");
f=fopen("mpcInfo.theme.txt","r");
}
//hexchat_printf(ph,"file_desc: %p\n",f);
if (f==NULL) hexchat_print(ph, "no theme found, using hardcoded\n");
else {
if (f > 0)
{
line=" ";
} else
{
line="\0";
}
while (line[0]!=0)
{
line=readLine(f);
val=split(line,'=');
printf("line: %s\n",line);
printf("val: %s\n",val);
lineCap=toUpper(line);
if (strcmp(lineCap,"OFF_LINE")==0) notRunTheme=themeAdd(notRunTheme,val);
if (strcmp(lineCap,"TITLE_LINE")==0) titleTheme=themeAdd(titleTheme,val);
if (strcmp(lineCap,"MP3_LINE")==0) mp3Theme=themeAdd(mp3Theme,val);
if (strcmp(lineCap,"OGG_LINE")==0) mp3Theme=themeAdd(oggTheme,val);
free(lineCap);
}
fclose(f);
hexchat_print(ph, "theme loaded successfull\n");
}
if (notRunTheme.size==0) notRunTheme=themeAdd(notRunTheme,"Media Player Classic not running");
if (titleTheme.size==0) titleTheme=themeAdd(titleTheme,"say Playing %title in Media Player Classic");
if (mp3Theme.size==0) mp3Theme=themeAdd(mp3Theme,"me listens to %art with %tit from %alb [%gen|%br kbps|%frq kHz|%mode] in Media Player Classic ");
if (oggTheme.size==0) oggTheme=themeAdd(oggTheme,"me listens to %art with %tit from %alb [%gen|%br kbps|%frq kHz|%chan channels] in Media Player Classic ");
//mp3Theme=themeAdd(mp3Theme,"me listens to %art with %tit from %alb [%time|%length|%perc%|%br kbps|%frq kHz|%mode] in Media Player Classic ");
}
int rnd(int max){
return rand()%max;
}
char *randomLine(struct theme data){
return data.line[rnd(data.size)];
}

View File

@ -51,7 +51,7 @@ sub get_context;
sub HexChat::Internal::context_info;
sub HexChat::Internal::print;
#keep compatibility with Xchat scripts
#keep compability with Xchat scripts
sub EAT_XCHAT ();
BEGIN {
*Xchat:: = *HexChat::;

View File

@ -13,24 +13,19 @@ hexchat_perl_module = custom_target('hexchat-perl-header',
command: [generate_perl_header, '@OUTPUT@', '@INPUT@']
)
perl_cflags = []
irc_perl_module = []
irc_perl_module = custom_target('irc-perl-header',
input: 'lib/IRC.pm',
output: 'irc.pm.h',
command: [generate_perl_header, '@OUTPUT@', '@INPUT@']
)
if get_option('with-perl-legacy-api')
irc_perl_module = custom_target('irc-perl-header',
input: 'lib/IRC.pm',
output: 'irc.pm.h',
command: [generate_perl_header, '@OUTPUT@', '@INPUT@']
)
perl_cflags += '-DOLD_PERL'
endif
perl = find_program(get_option('with-perl'))
perl = find_program('perl')
ret = run_command([perl, '-MExtUtils::Embed', '-e', 'ccopts'])
if ret.returncode() != 0
error('perl: Failed to get cflags')
endif
perl_cflags = []
foreach flag : ret.stdout().strip().split(' ')
if flag.startswith('-I') or flag.startswith('-D')
perl_cflags += flag
@ -76,17 +71,13 @@ int main(void) {
error('found perl not suitable for plugin')
endif
perl_dep = declare_dependency(
compile_args: perl_cflags,
link_args: perl_ldflags
)
shared_module('perl',
sources: ['perl.c', hexchat_perl_module, irc_perl_module],
dependencies: [libgio_dep, hexchat_plugin_dep, perl_dep],
dependencies: [libgio_dep, hexchat_plugin_dep],
c_args: perl_cflags,
link_args: perl_ldflags,
install: true,
install_dir: plugindir,
install_rpath: perl_rpath,
name_prefix: '',
vs_module_defs: 'perl.def',
)

View File

@ -283,8 +283,6 @@ list_item_to_sv ( hexchat_list *list, const char *const *fields )
return sv_2mortal (newRV_noinc ((SV *) hash));
}
#define WORD_ARRAY_LEN 32
static AV *
array2av (char *array[])
{
@ -295,7 +293,7 @@ array2av (char *array[])
for (
count = 1;
count < WORD_ARRAY_LEN && array[count] != NULL && array[count][0] != 0;
count < 32 && array[count] != NULL && array[count][0] != 0;
count++
) {
temp = newSVpv (array[count], 0);

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -1,386 +0,0 @@
import inspect
import sys
from contextlib import contextmanager
from _hexchat_embedded import ffi, lib
__all__ = [
'EAT_ALL', 'EAT_HEXCHAT', 'EAT_NONE', 'EAT_PLUGIN', 'EAT_XCHAT',
'PRI_HIGH', 'PRI_HIGHEST', 'PRI_LOW', 'PRI_LOWEST', 'PRI_NORM',
'__doc__', '__version__', 'command', 'del_pluginpref', 'emit_print',
'find_context', 'get_context', 'get_info',
'get_list', 'get_lists', 'get_pluginpref', 'get_prefs', 'hook_command',
'hook_print', 'hook_print_attrs', 'hook_server', 'hook_server_attrs',
'hook_timer', 'hook_unload', 'list_pluginpref', 'nickcmp', 'prnt',
'set_pluginpref', 'strip', 'unhook',
]
__doc__ = 'HexChat Scripting Interface'
__version__ = (2, 0)
__license__ = 'GPL-2.0+'
EAT_NONE = 0
EAT_HEXCHAT = 1
EAT_XCHAT = EAT_HEXCHAT
EAT_PLUGIN = 2
EAT_ALL = EAT_HEXCHAT | EAT_PLUGIN
PRI_LOWEST = -128
PRI_LOW = -64
PRI_NORM = 0
PRI_HIGH = 64
PRI_HIGHEST = 127
# We need each module to be able to reference their parent plugin
# which is a bit tricky since they all share the exact same module.
# Simply navigating up to what module called it seems to actually
# be a fairly reliable and simple method of doing so if ugly.
def __get_current_plugin():
frame = inspect.stack()[1][0]
while '__plugin' not in frame.f_globals:
frame = frame.f_back
assert frame is not None
return frame.f_globals['__plugin']
# Keeping API compat
if sys.version_info[0] == 2:
def __decode(string):
return string
else:
def __decode(string):
return string.decode()
# ------------ API ------------
def prnt(string):
lib.hexchat_print(lib.ph, string.encode())
def emit_print(event_name, *args, **kwargs):
time = kwargs.pop('time', 0) # For py2 compat
cargs = []
for i in range(4):
arg = args[i].encode() if len(args) > i else b''
cstring = ffi.new('char[]', arg)
cargs.append(cstring)
if time == 0:
return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs)
attrs = lib.hexchat_event_attrs_create(lib.ph)
attrs.server_time_utc = time
ret = lib.hexchat_emit_print_attrs(lib.ph, attrs, event_name.encode(), *cargs)
lib.hexchat_event_attrs_free(lib.ph, attrs)
return ret
# TODO: this shadows itself. command should be changed to cmd
def command(command):
lib.hexchat_command(lib.ph, command.encode())
def nickcmp(string1, string2):
return lib.hexchat_nickcmp(lib.ph, string1.encode(), string2.encode())
def strip(text, length=-1, flags=3):
stripped = lib.hexchat_strip(lib.ph, text.encode(), length, flags)
ret = __decode(ffi.string(stripped))
lib.hexchat_free(lib.ph, stripped)
return ret
def get_info(name):
ret = lib.hexchat_get_info(lib.ph, name.encode())
if ret == ffi.NULL:
return None
if name in ('gtkwin_ptr', 'win_ptr'):
# Surely there is a less dumb way?
ptr = repr(ret).rsplit(' ', 1)[1][:-1]
return ptr
return __decode(ffi.string(ret))
def get_prefs(name):
string_out = ffi.new('char**')
int_out = ffi.new('int*')
_type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out)
if _type == 0:
return None
if _type == 1:
return __decode(ffi.string(string_out[0]))
if _type in (2, 3): # XXX: 3 should be a bool, but keeps API
return int_out[0]
raise AssertionError('Out of bounds pref storage')
def __cstrarray_to_list(arr):
i = 0
ret = []
while arr[i] != ffi.NULL:
ret.append(ffi.string(arr[i]))
i += 1
return ret
__FIELD_CACHE = {}
def __get_fields(name):
return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name)))
__FIELD_PROPERTY_CACHE = {}
def __cached_decoded_str(string):
return __FIELD_PROPERTY_CACHE.setdefault(string, __decode(string))
def get_lists():
return [__cached_decoded_str(field) for field in __get_fields(b'lists')]
class ListItem:
def __init__(self, name):
self._listname = name
def __repr__(self):
return '<{} list item at {}>'.format(self._listname, id(self))
# done this way for speed
if sys.version_info[0] == 2:
def get_getter(name):
return ord(name[0])
else:
def get_getter(name):
return name[0]
def get_list(name):
# XXX: This function is extremely inefficient and could be interators and
# lazily loaded properties, but for API compat we stay slow
orig_name = name
name = name.encode()
if name not in __get_fields(b'lists'):
raise KeyError('list not available')
list_ = lib.hexchat_list_get(lib.ph, name)
if list_ == ffi.NULL:
return None
ret = []
fields = __get_fields(name)
def string_getter(field):
string = lib.hexchat_list_str(lib.ph, list_, field)
if string != ffi.NULL:
return __decode(ffi.string(string))
return ''
def ptr_getter(field):
if field == b'context':
ptr = lib.hexchat_list_str(lib.ph, list_, field)
ctx = ffi.cast('hexchat_context*', ptr)
return Context(ctx)
return None
getters = {
ord('s'): string_getter,
ord('i'): lambda field: lib.hexchat_list_int(lib.ph, list_, field),
ord('t'): lambda field: lib.hexchat_list_time(lib.ph, list_, field),
ord('p'): ptr_getter,
}
while lib.hexchat_list_next(lib.ph, list_) == 1:
item = ListItem(orig_name)
for _field in fields:
getter = getters.get(get_getter(_field))
if getter is not None:
field_name = _field[1:]
setattr(item, __cached_decoded_str(field_name), getter(field_name))
ret.append(item)
lib.hexchat_list_free(lib.ph, list_)
return ret
# TODO: 'command' here shadows command above, and should be renamed to cmd
def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook,
help.encode() if help is not None else ffi.NULL, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_print(name, callback, userdata=None, priority=PRI_NORM):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_server(name, callback, userdata=None, priority=PRI_NORM):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_timer(timeout, callback, userdata=None):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata)
handle = lib.hexchat_hook_timer(lib.ph, timeout, lib._on_timer_hook, hook.handle)
hook.hexchat_hook = handle
return id(hook)
def hook_unload(callback, userdata=None):
plugin = __get_current_plugin()
hook = plugin.add_hook(callback, userdata, is_unload=True)
return id(hook)
def unhook(handle):
plugin = __get_current_plugin()
return plugin.remove_hook(handle)
def set_pluginpref(name, value):
if isinstance(value, str):
return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode()))
if isinstance(value, int):
return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value))
# XXX: This should probably raise but this keeps API
return False
def get_pluginpref(name):
name = name.encode()
string_out = ffi.new('char[512]')
if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1:
return None
string = ffi.string(string_out)
# This API stores everything as a string so we have to figure out what
# its actual type was supposed to be.
if len(string) > 12: # Can't be a number
return __decode(string)
number = lib.hexchat_pluginpref_get_int(lib.ph, name)
if number == -1 and string != b'-1':
return __decode(string)
return number
def del_pluginpref(name):
return bool(lib.hexchat_pluginpref_delete(lib.ph, name.encode()))
def list_pluginpref():
prefs_str = ffi.new('char[4096]')
if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1:
return __decode(ffi.string(prefs_str)).split(',')
return []
class Context:
def __init__(self, ctx):
self._ctx = ctx
def __eq__(self, value):
if not isinstance(value, Context):
return False
return self._ctx == value._ctx
@contextmanager
def __change_context(self):
old_ctx = lib.hexchat_get_context(lib.ph)
if not self.set():
# XXX: Behavior change, previously used wrong context
lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call')
return
yield
lib.hexchat_set_context(lib.ph, old_ctx)
def set(self):
# XXX: API addition, C plugin silently ignored failure
return bool(lib.hexchat_set_context(lib.ph, self._ctx))
def prnt(self, string):
with self.__change_context():
prnt(string)
def emit_print(self, event_name, *args, **kwargs):
time = kwargs.pop('time', 0) # For py2 compat
with self.__change_context():
return emit_print(event_name, *args, time=time)
def command(self, string):
with self.__change_context():
command(string)
def get_info(self, name):
with self.__change_context():
return get_info(name)
def get_list(self, name):
with self.__change_context():
return get_list(name)
def get_context():
ctx = lib.hexchat_get_context(lib.ph)
return Context(ctx)
def find_context(server=None, channel=None):
server = server.encode() if server is not None else ffi.NULL
channel = channel.encode() if channel is not None else ffi.NULL
ctx = lib.hexchat_find_context(lib.ph, server, channel)
if ctx == ffi.NULL:
return None
return Context(ctx)

View File

@ -1,89 +0,0 @@
#!/usr/bin/env python3
import sys
import cffi
builder = cffi.FFI()
# hexchat-plugin.h
with open(sys.argv[1]) as f:
output = []
eat_until_endif = 0
# This is very specific to hexchat-plugin.h, it is not a cpp
for line in f:
if line.startswith('#define'):
continue
elif line.endswith('HEXCHAT_PLUGIN_H\n'):
continue
elif 'time.h' in line:
output.append('typedef int... time_t;')
elif line.startswith('#if'):
eat_until_endif += 1
elif line.startswith('#endif'):
eat_until_endif -= 1
elif eat_until_endif and '_hexchat_context' not in line:
continue
else:
output.append(line)
builder.cdef(''.join(output))
builder.embedding_api('''
extern "Python" int _on_py_command(char **, char **, void *);
extern "Python" int _on_load_command(char **, char **, void *);
extern "Python" int _on_unload_command(char **, char **, void *);
extern "Python" int _on_reload_command(char **, char **, void *);
extern "Python" int _on_say_command(char **, char **, void *);
extern "Python" int _on_command_hook(char **, char **, void *);
extern "Python" int _on_print_hook(char **, void *);
extern "Python" int _on_print_attrs_hook(char **, hexchat_event_attrs *, void *);
extern "Python" int _on_server_hook(char **, char **, void *);
extern "Python" int _on_server_attrs_hook(char **, char **, hexchat_event_attrs *, void *);
extern "Python" int _on_timer_hook(void *);
extern "Python" int _on_plugin_init(char **, char **, char **, char *, char *);
extern "Python" int _on_plugin_deinit(void);
static hexchat_plugin *ph;
''')
builder.set_source('_hexchat_embedded', '''
/* Python's header defines these.. */
#undef HAVE_MEMRCHR
#undef HAVE_STRINGS_H
#include "config.h"
#include "hexchat-plugin.h"
static hexchat_plugin *ph;
CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *);
CFFI_DLLEXPORT int _on_plugin_deinit(void);
int hexchat_plugin_init(hexchat_plugin *plugin_handle,
char **name_out, char **description_out,
char **version_out, char *arg)
{
if (ph != NULL)
{
puts ("Python plugin already loaded\\n");
return 0; /* Prevent loading twice */
}
ph = plugin_handle;
return _on_plugin_init(name_out, description_out, version_out, arg, HEXCHATLIBDIR);
}
int hexchat_plugin_deinit(void)
{
int ret = _on_plugin_deinit();
ph = NULL;
return ret;
}
''')
# python.py
with open(sys.argv[2]) as f:
builder.embedding_init_code(f.read())
# python.c
builder.emit_c_code(sys.argv[3])

View File

@ -1 +0,0 @@
from _hexchat import *

View File

@ -1,32 +1,13 @@
python_opt = get_option('with-python')
if python_opt.startswith('python3')
# Python 3.8 introduced a new -embed variant
if not python_opt.endswith('-embed')
python_dep = dependency(python_opt + '-embed', version: '>= 3.3', required: false)
if not python_dep.found()
python_dep = dependency(python_opt, version: '>= 3.3')
endif
else
python_dep = dependency(python_opt, version: '>= 3.3')
endif
python_dep = dependency(python_opt, version: '>= 3.3')
else
python_dep = dependency(python_opt, version: '>= 2.7')
endif
python3_source = custom_target('python-bindings',
input: ['../../src/common/hexchat-plugin.h', 'python.py'],
output: 'python.c',
command: [find_program('generate_plugin.py'), '@INPUT@', '@OUTPUT@']
)
install_data(['_hexchat.py', 'hexchat.py', 'xchat.py'],
install_dir: join_paths(get_option('libdir'), 'hexchat/python')
)
shared_module('python', python3_source,
dependencies: [hexchat_plugin_dep, python_dep],
shared_module('python', 'python.c',
dependencies: [libgio_dep, hexchat_plugin_dep, python_dep],
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'python.def'
)

2834
plugins/python/python.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
EXPORTS
hexchat_plugin_init
hexchat_plugin_deinit
hexchat_plugin_get_info

View File

@ -1,554 +0,0 @@
from __future__ import print_function
import importlib
import os
import pydoc
import signal
import sys
import traceback
import weakref
from contextlib import contextmanager
from _hexchat_embedded import ffi, lib
if sys.version_info < (3, 0):
from io import BytesIO as HelpEater
else:
from io import StringIO as HelpEater
if not hasattr(sys, 'argv'):
sys.argv = ['<hexchat>']
VERSION = b'2.0' # Sync with hexchat.__version__
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_VERSION = ffi.new('char[]', VERSION)
# TODO: Constants should be screaming snake case
hexchat = None
local_interp = None
hexchat_stdout = None
plugins = set()
@contextmanager
def redirected_stdout():
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
yield
sys.stdout = hexchat_stdout
sys.stderr = hexchat_stdout
if os.getenv('HEXCHAT_LOG_PYTHON'):
def log(*args):
with redirected_stdout():
print(*args)
else:
def log(*args):
pass
class Stdout:
def __init__(self):
self.buffer = bytearray()
def write(self, string):
string = string.encode()
idx = string.rfind(b'\n')
if idx != -1:
self.buffer += string[:idx]
lib.hexchat_print(lib.ph, bytes(self.buffer))
self.buffer = bytearray(string[idx + 1:])
else:
self.buffer += string
def isatty(self):
return False
class Attribute:
def __init__(self):
self.time = 0
def __repr__(self):
return '<Attribute object at {}>'.format(id(self))
class Hook:
def __init__(self, plugin, callback, userdata, is_unload):
self.is_unload = is_unload
self.plugin = weakref.proxy(plugin)
self.callback = callback
self.userdata = userdata
self.hexchat_hook = None
self.handle = ffi.new_handle(weakref.proxy(self))
def __del__(self):
log('Removing hook', id(self))
if self.is_unload is False:
assert self.hexchat_hook is not None
lib.hexchat_unhook(lib.ph, self.hexchat_hook)
if sys.version_info[0] == 2:
def compile_file(data, filename):
return compile(data, filename, 'exec', dont_inherit=True)
def compile_line(string):
try:
return compile(string, '<string>', 'eval', dont_inherit=True)
except SyntaxError:
# For some reason `print` is invalid for eval
# This will hide any return value though
return compile(string, '<string>', 'exec', dont_inherit=True)
else:
def compile_file(data, filename):
return compile(data, filename, 'exec', optimize=2, dont_inherit=True)
def compile_line(string):
# newline appended to solve unexpected EOF issues
return compile(string + '\n', '<string>', 'single', optimize=2, dont_inherit=True)
class Plugin:
def __init__(self):
self.ph = None
self.name = ''
self.filename = ''
self.version = ''
self.description = ''
self.hooks = set()
self.globals = {
'__plugin': weakref.proxy(self),
'__name__': '__main__',
}
def add_hook(self, callback, userdata, is_unload=False):
hook = Hook(self, callback, userdata, is_unload=is_unload)
self.hooks.add(hook)
return hook
def remove_hook(self, hook):
for h in self.hooks:
if id(h) == hook:
ud = h.userdata
self.hooks.remove(h)
return ud
log('Hook not found')
return None
def loadfile(self, filename):
try:
self.filename = filename
with open(filename, 'rb') as f:
data = f.read().decode('utf-8')
compiled = compile_file(data, filename)
exec(compiled, self.globals)
try:
self.name = self.globals['__module_name__']
except KeyError:
lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set')
return False
self.version = self.globals.get('__module_version__', '')
self.description = self.globals.get('__module_description__', '')
self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(),
self.description.encode(), self.version.encode(), ffi.NULL)
except Exception as e:
lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode())
traceback.print_exc()
return False
return True
def __del__(self):
log('unloading', self.filename)
for hook in self.hooks:
if hook.is_unload is True:
try:
hook.callback(hook.userdata)
except Exception as e:
log('Failed to run hook:', e)
traceback.print_exc()
del self.hooks
if self.ph is not None:
lib.hexchat_plugingui_remove(lib.ph, self.ph)
if sys.version_info[0] == 2:
def __decode(string):
return string
else:
def __decode(string):
return string.decode()
# There can be empty entries between non-empty ones so find the actual last value
def wordlist_len(words):
for i in range(31, 0, -1):
if ffi.string(words[i]):
return i
return 0
def create_wordlist(words):
size = wordlist_len(words)
return [__decode(ffi.string(words[i])) for i in range(1, size + 1)]
# This function only exists for compat reasons with the C plugin
# It turns the word list from print hooks into a word_eol list
# This makes no sense to do...
def create_wordeollist(words):
words = reversed(words)
accum = None
ret = []
for word in words:
if accum is None:
accum = word
elif word:
last = accum
accum = ' '.join((word, last))
ret.insert(0, accum)
return ret
def to_cb_ret(value):
if value is None:
return 0
return int(value)
@ffi.def_extern()
def _on_command_hook(word, word_eol, userdata):
hook = ffi.from_handle(userdata)
word = create_wordlist(word)
word_eol = create_wordlist(word_eol)
return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
@ffi.def_extern()
def _on_print_hook(word, userdata):
hook = ffi.from_handle(userdata)
word = create_wordlist(word)
word_eol = create_wordeollist(word)
return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
@ffi.def_extern()
def _on_print_attrs_hook(word, attrs, userdata):
hook = ffi.from_handle(userdata)
word = create_wordlist(word)
word_eol = create_wordeollist(word)
attr = Attribute()
attr.time = attrs.server_time_utc
return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr))
@ffi.def_extern()
def _on_server_hook(word, word_eol, userdata):
hook = ffi.from_handle(userdata)
word = create_wordlist(word)
word_eol = create_wordlist(word_eol)
return to_cb_ret(hook.callback(word, word_eol, hook.userdata))
@ffi.def_extern()
def _on_server_attrs_hook(word, word_eol, attrs, userdata):
hook = ffi.from_handle(userdata)
word = create_wordlist(word)
word_eol = create_wordlist(word_eol)
attr = Attribute()
attr.time = attrs.server_time_utc
return to_cb_ret(hook.callback(word, word_eol, hook.userdata, attr))
@ffi.def_extern()
def _on_timer_hook(userdata):
hook = ffi.from_handle(userdata)
if hook.callback(hook.userdata) == True:
return 1
hook.is_unload = True # Don't unhook
for h in hook.plugin.hooks:
if h == hook:
hook.plugin.hooks.remove(h)
break
return 0
@ffi.def_extern(error=3)
def _on_say_command(word, word_eol, userdata):
channel = ffi.string(lib.hexchat_get_info(lib.ph, b'channel'))
if channel == b'>>python<<':
python = ffi.string(word_eol[1])
lib.hexchat_print(lib.ph, b'>>> ' + python)
exec_in_interp(__decode(python))
return 1
return 0
def load_filename(filename):
filename = os.path.expanduser(filename)
if not os.path.isabs(filename):
configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
filename = os.path.join(configdir, 'addons', filename)
if filename and not any(plugin.filename == filename for plugin in plugins):
plugin = Plugin()
if plugin.loadfile(filename):
plugins.add(plugin)
return True
return False
def unload_name(name):
if name:
for plugin in plugins:
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
plugins.remove(plugin)
return True
return False
def reload_name(name):
if name:
for plugin in plugins:
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
filename = plugin.filename
plugins.remove(plugin)
return load_filename(filename)
return False
@contextmanager
def change_cwd(path):
old_cwd = os.getcwd()
os.chdir(path)
yield
os.chdir(old_cwd)
def autoload():
configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
addondir = os.path.join(configdir, 'addons')
try:
with change_cwd(addondir): # Maintaining old behavior
for f in os.listdir(addondir):
if f.endswith('.py'):
log('Autoloading', f)
# TODO: Set cwd
load_filename(os.path.join(addondir, f))
except FileNotFoundError as e:
log('Autoload failed', e)
def list_plugins():
if not plugins:
lib.hexchat_print(lib.ph, b'No python modules loaded')
return
tbl_headers = [b'Name', b'Version', b'Filename', b'Description']
tbl = [
tbl_headers,
[(b'-' * len(s)) for s in tbl_headers]
]
for plugin in plugins:
basename = os.path.basename(plugin.filename).encode()
name = plugin.name.encode()
version = plugin.version.encode() if plugin.version else b'<none>'
description = plugin.description.encode() if plugin.description else b'<none>'
tbl.append((name, version, basename, description))
column_sizes = [
max(len(item) for item in column)
for column in zip(*tbl)
]
for row in tbl:
lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i])
for i, item in enumerate(row)))
lib.hexchat_print(lib.ph, b'')
def exec_in_interp(python):
global local_interp
if not python:
return
if local_interp is None:
local_interp = Plugin()
local_interp.locals = {}
local_interp.globals['hexchat'] = hexchat
code = compile_line(python)
try:
ret = eval(code, local_interp.globals, local_interp.locals)
if ret is not None:
lib.hexchat_print(lib.ph, '{}'.format(ret).encode())
except Exception as e:
traceback.print_exc(file=hexchat_stdout)
@ffi.def_extern()
def _on_load_command(word, word_eol, userdata):
filename = ffi.string(word[2])
if filename.endswith(b'.py'):
load_filename(__decode(filename))
return 3
return 0
@ffi.def_extern()
def _on_unload_command(word, word_eol, userdata):
filename = ffi.string(word[2])
if filename.endswith(b'.py'):
unload_name(__decode(filename))
return 3
return 0
@ffi.def_extern()
def _on_reload_command(word, word_eol, userdata):
filename = ffi.string(word[2])
if filename.endswith(b'.py'):
reload_name(__decode(filename))
return 3
return 0
@ffi.def_extern(error=3)
def _on_py_command(word, word_eol, userdata):
subcmd = __decode(ffi.string(word[2])).lower()
if subcmd == 'exec':
python = __decode(ffi.string(word_eol[3]))
exec_in_interp(python)
elif subcmd == 'load':
filename = __decode(ffi.string(word[3]))
load_filename(filename)
elif subcmd == 'unload':
name = __decode(ffi.string(word[3]))
if not unload_name(name):
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
elif subcmd == 'reload':
name = __decode(ffi.string(word[3]))
if not reload_name(name):
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
elif subcmd == 'console':
lib.hexchat_command(lib.ph, b'QUERY >>python<<')
elif subcmd == 'list':
list_plugins()
elif subcmd == 'about':
lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION)
else:
lib.hexchat_command(lib.ph, b'HELP PY')
return 3
@ffi.def_extern()
def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir):
global hexchat
global hexchat_stdout
signal.signal(signal.SIGINT, signal.SIG_DFL)
plugin_name[0] = PLUGIN_NAME
plugin_desc[0] = PLUGIN_DESC
plugin_version[0] = PLUGIN_VERSION
try:
libdir = __decode(ffi.string(libdir))
modpath = os.path.join(libdir, '..', 'python')
sys.path.append(os.path.abspath(modpath))
hexchat = importlib.import_module('hexchat')
except (UnicodeDecodeError, ImportError) as e:
lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode())
return 0
hexchat_stdout = Stdout()
sys.stdout = hexchat_stdout
sys.stderr = hexchat_stdout
pydoc.help = pydoc.Helper(HelpEater(), HelpEater())
lib.hexchat_hook_command(lib.ph, b'', 0, lib._on_say_command, ffi.NULL, ffi.NULL)
lib.hexchat_hook_command(lib.ph, b'LOAD', 0, lib._on_load_command, ffi.NULL, ffi.NULL)
lib.hexchat_hook_command(lib.ph, b'UNLOAD', 0, lib._on_unload_command, ffi.NULL, ffi.NULL)
lib.hexchat_hook_command(lib.ph, b'RELOAD', 0, lib._on_reload_command, ffi.NULL, ffi.NULL)
lib.hexchat_hook_command(lib.ph, b'PY', 0, lib._on_py_command, b'''Usage: /PY LOAD <filename>
UNLOAD <filename|name>
RELOAD <filename|name>
LIST
EXEC <command>
CONSOLE
ABOUT''', ffi.NULL)
lib.hexchat_print(lib.ph, b'Python interface loaded')
autoload()
return 1
@ffi.def_extern()
def _on_plugin_deinit():
global local_interp
global hexchat
global hexchat_stdout
global plugins
plugins = set()
local_interp = None
hexchat = None
hexchat_stdout = None
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
pydoc.help = pydoc.Helper()
for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'):
try:
del sys.modules[mod]
except KeyError:
pass
return 1

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
@ -37,9 +37,6 @@
<AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@ -51,15 +48,12 @@
<AdditionalDependencies>"$(Python2Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python2Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="python.def" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
<ClCompile Include="python.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
@ -37,9 +37,6 @@
<AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@ -51,20 +48,12 @@
<AdditionalDependencies>"$(Python3Lib).lib";$(DepLibs);%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;$(Python3Path)\libs;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>"$(Python3Path)\python.exe" generate_plugin.py ..\..\src\common\hexchat-plugin.h python.py "$(IntDir)python.c"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="generate_plugin.py" />
<None Include="hexchat.py" />
<None Include="python.def" />
<None Include="python.py" />
<None Include="xchat.py" />
<None Include="_hexchat.py" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
<ClCompile Include="python.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>

View File

@ -9,26 +9,13 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(IntDir)python.c" />
<ClCompile Include="python.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="python.def">
<Filter>Resource Files</Filter>
</None>
<None Include="_hexchat.py">
<Filter>Source Files</Filter>
</None>
<None Include="generate_plugin.py">
<Filter>Source Files</Filter>
</None>
<None Include="hexchat.py">
<Filter>Source Files</Filter>
</None>
<None Include="python.py">
<Filter>Source Files</Filter>
</None>
<None Include="xchat.py">
<Filter>Source Files</Filter>
</None>
</ItemGroup>
</Project>

View File

@ -1,26 +0,0 @@
# HexChat Python Module Style Guide
(This is a work in progress).
## General rules
- PEP8 as general fallback recommendations
- Max line length: 120
- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x`
## Indentation style
### Multi-line functions
```python
foo(really_long_arg_1,
really_long_arg_2)
```
### Mutli-line lists/dicts
```python
foo = {
'bar': 'baz',
}
```

View File

@ -1 +0,0 @@
from _hexchat import *

View File

@ -5,23 +5,22 @@ sysinfo_sources = [
sysinfo_deps = [
libgio_dep,
hexchat_plugin_dep,
common_sysinfo_deps,
hexchat_plugin_dep
]
sysinfo_includes = []
sysinfo_cargs = []
system = host_machine.system()
if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'darwin' or system == 'freebsd'
if system == 'linux' or system == 'darwin'
sysinfo_includes += 'shared'
sysinfo_sources += [
'shared/df.c'
]
if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system == 'freebsd'
libpci = dependency('libpci', required: false, method: 'pkg-config')
if libpci.found()
if system == 'linux'
libpci = dependency('libpci', required: false)
if libpci.found() and cc.has_header('pci/pci.h')
sysinfo_deps += libpci
sysinfo_cargs += '-DHAVE_LIBPCI'
sysinfo_sources += 'unix/pci.c'
@ -42,10 +41,7 @@ if system == 'linux' or system == 'gnu' or system.startswith('gnu/') or system =
endif
elif system == 'windows'
sysinfo_sources += [
'win32/backend.c',
'../../src/common/sysinfo/win32/backend.c'
]
sysinfo_sources += 'win32/backend.c'
else
error('sysinfo: Unknown system?')
endif
@ -57,5 +53,4 @@ shared_module('sysinfo', sysinfo_sources,
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'sysinfo.def',
)

View File

@ -26,7 +26,7 @@ int xs_parse_df(gint64 *out_total, gint64 *out_free)
FILE *pipe;
char buffer[bsize];
pipe = popen("df -k -l -P --exclude-type=squashfs --exclude-type=devtmpfs --exclude-type=tmpfs", "r");
pipe = popen("df -k -l -P", "r");
if(pipe==NULL)
return 1;

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>

View File

@ -269,16 +269,6 @@ int xs_parse_meminfo(unsigned long long *mem_tot, unsigned long long *mem_free,
return 0;
}
static void strip_quotes(char *string)
{
size_t len = strlen(string);
if (string[len - 1] == '"')
string[--len] = '\0';
if (string[0] == '"')
memmove(string, string + 1, len);
}
int xs_parse_distro(char *name)
{
FILE *fp = NULL;
@ -330,20 +320,6 @@ int xs_parse_distro(char *name)
else
g_snprintf(buffer, bsize, "Gentoo Linux %s", keywords);
}
else if((fp = fopen("/etc/os-release", "r")) != NULL)
{
char name[bsize], version[bsize];
strcpy(name, "?");
strcpy(version, "?");
while(fgets(buffer, bsize, fp) != NULL)
{
find_match_char(buffer, "NAME=", name);
find_match_char(buffer, "VERSION=", version);
}
strip_quotes(name);
strip_quotes(version);
g_snprintf(buffer, bsize, "%s %s", name, version);
}
else
g_snprintf(buffer, bsize, "Unknown Distro");
if(fp != NULL)

View File

@ -142,7 +142,7 @@ void pci_find_fullname(char *fullname, char *vendor, char *device)
{
position = strstr(buffer, vendor);
position += 6;
g_strlcpy(vendorname, position, sizeof (vendorname));
strncpy(vendorname, position, bsize/2);
position = strstr(vendorname, "\n");
*(position) = '\0';
break;
@ -154,7 +154,7 @@ void pci_find_fullname(char *fullname, char *vendor, char *device)
{
position = strstr(buffer, device);
position += 6;
g_strlcpy(devicename, position, sizeof (devicename));
strncpy(devicename, position, bsize/2);
position = strstr(devicename, " (");
if (position == NULL)
position = strstr(devicename, "\n");

View File

@ -30,7 +30,14 @@
#include "../format.h"
static char *get_memory_info (void);
static int command_callback (char *word[], char *word_eol[], void *user_data);
void print_info (void);
guint64 hdd_capacity;
guint64 hdd_free_space;
char *read_hdd_info (IWbemClassObject *object);
char *get_memory_info (void);
char *
sysinfo_backend_get_sound (void)
@ -91,6 +98,11 @@ sysinfo_backend_get_os (void)
return sysinfo_get_os ();
}
static int get_cpu_arch (void)
{
return sysinfo_get_cpu_arch ();
}
static char *get_memory_info (void)
{
MEMORYSTATUSEX meminfo = { 0 };

View File

@ -5,5 +5,4 @@ shared_module('upd', 'upd.c',
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'upd.def',
)

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -3,5 +3,4 @@ shared_module('winamp', 'winamp.c',
install: true,
install_dir: plugindir,
name_prefix: '',
vs_module_defs: 'winamp.def',
)

View File

@ -1,7 +1,7 @@
<?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>
<PlatformToolset>v140</PlatformToolset>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">

View File

@ -1,6 +1,6 @@
data/misc/io.github.Hexchat.appdata.xml.in
data/misc/io.github.Hexchat.desktop.in.in
data/misc/io.github.Hexchat.ThemeManager.desktop.in
data/misc/hexchat.appdata.xml.in
data/misc/hexchat.desktop.in.in
data/misc/htm.desktop.in
src/common/cfgfiles.c
src/common/chanopt.c
src/common/dcc.c

View File

@ -1 +1 @@
data/misc/io.github.Hexchat.desktop.in.in
data/misc/hexchat.desktop.in.in

770
po/af.po

File diff suppressed because it is too large Load Diff

793
po/am.po

File diff suppressed because it is too large Load Diff

1173
po/ast.po

File diff suppressed because it is too large Load Diff

902
po/az.po

File diff suppressed because it is too large Load Diff

1179
po/be.po

File diff suppressed because it is too large Load Diff

964
po/bg.po

File diff suppressed because it is too large Load Diff

2175
po/ca.po

File diff suppressed because it is too large Load Diff

1663
po/cs.po

File diff suppressed because it is too large Load Diff

1489
po/da.po

File diff suppressed because it is too large Load Diff

1634
po/de.po

File diff suppressed because it is too large Load Diff

1223
po/el.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2362
po/es.po

File diff suppressed because it is too large Load Diff

1122
po/et.po

File diff suppressed because it is too large Load Diff

964
po/eu.po

File diff suppressed because it is too large Load Diff

1246
po/fi.po

File diff suppressed because it is too large Load Diff

1686
po/fr.po

File diff suppressed because it is too large Load Diff

1133
po/gl.po

File diff suppressed because it is too large Load Diff

1019
po/gu.po

File diff suppressed because it is too large Load Diff

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