Compare commits

..
Author SHA1 Message Date
deepend 6bae96668a add autoaway 2026-10-20 23:47:29 -06:00
42 changed files with 846 additions and 253 deletions
+3 -4
View File
@@ -29,13 +29,13 @@ jobs:
build-essential pkg-config meson ninja-build cmake \ build-essential pkg-config meson ninja-build cmake \
gettext \ gettext \
libcanberra-dev libglib2.0-dev \ libcanberra-dev libglib2.0-dev \
libarchive-dev \ libarchive-dev libxss-dev \
libgtk-3-dev libfontconfig1-dev libpango1.0-dev xvfb xauth dbus-daemon at-spi2-core \ libgtk-3-dev libfontconfig1-dev libpango1.0-dev xvfb xauth dbus-daemon at-spi2-core \
libwayland-client0 libwayland-cursor0 libwayland-egl1 \ libwayland-client0 libwayland-cursor0 libwayland-egl1 \
libxkbcommon0 \ libxkbcommon0 \
libgtk-3-bin libglib2.0-bin glib-networking shared-mime-info gsettings-desktop-schemas \ libgtk-3-bin libglib2.0-bin glib-networking shared-mime-info gsettings-desktop-schemas \
liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \ liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
perl python3 python3-minimal python3-dev python3-cffi python3-sphinx mono-devel desktop-file-utils \ perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
breeze-gtk-theme \ breeze-gtk-theme \
patchelf file curl patchelf file curl
@@ -45,7 +45,6 @@ jobs:
rm -rf build rm -rf build
meson setup build \ meson setup build \
--prefix=/usr \ --prefix=/usr \
-Dinstall-docs=true \
-Dtext-frontend=true \ -Dtext-frontend=true \
-Dwith-perl=perl \ -Dwith-perl=perl \
-Dwith-python=python3 \ -Dwith-python=python3 \
@@ -58,7 +57,7 @@ jobs:
ninja -C build ninja -C build
- name: Test emoji catalog, font coverage and picker - name: Test emoji catalog, font coverage and picker
run: xvfb-run -a dbus-run-session -- meson test -C build --print-errorlogs "Emoji Data Tests" "Emoji Font Tests" "Emoji UI Tests" run: xvfb-run -a dbus-run-session -- meson test -C build --print-errorlogs "Autoaway Tests" "Idle Backend Tests" "Emoji Data Tests" "Emoji Font Tests" "Emoji UI Tests"
- name: Install to AppDir - name: Install to AppDir
run: | run: |
+5 -1
View File
@@ -198,7 +198,7 @@ jobs:
) )
shell: cmd shell: cmd
- name: Test emoji catalog, font coverage and picker - name: Test autoaway, emoji catalog, font coverage and picker
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
@@ -213,6 +213,10 @@ jobs:
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap ..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
) )
msbuild win32\autoaway-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64
if errorlevel 1 exit /b 1
..\zoitechat-build\x64\tests\autoaway-tests.exe --tap
if errorlevel 1 exit /b 1
- name: Preparing Artifacts - name: Preparing Artifacts
run: | run: |
+5 -1
View File
@@ -209,7 +209,7 @@ jobs:
# Master added native emoji font staging and tests after the release # Master added native emoji font staging and tests after the release
# branch split. Run these before packaging so MSI/MSIX use verified files. # branch split. Run these before packaging so MSI/MSIX use verified files.
- name: Test emoji catalog, font coverage and picker - name: Test autoaway, emoji catalog, font coverage and picker
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
@@ -224,6 +224,10 @@ jobs:
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap ..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
) )
msbuild win32\autoaway-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64
if errorlevel 1 exit /b 1
..\zoitechat-build\x64\tests\autoaway-tests.exe --tap
if errorlevel 1 exit /b 1
- name: Install WiX Toolset - name: Install WiX Toolset
shell: pwsh shell: pwsh
+5 -11
View File
@@ -5,14 +5,10 @@ Unicode Emoji 17.0. It is vendored from Debian's
`fonts-noto-color-emoji` 2.051 package, built from the corresponding `fonts-noto-color-emoji` 2.051 package, built from the corresponding
Google Noto Emoji release. Google Noto Emoji release.
By default, GTK builds install this exact file, including Windows Every GTK build installs this exact file: Windows (installer and portable
(installer and portable builds), AppImage, Flatpak, and native Meson/RPM. build files), AppImage, Flatpak and native Meson/RPM builds. It is loaded
Meson packagers can set `-Dinstall-emoji-font=false` to omit the bundled into a private Pango/FreeType font map as `ZoiteChat Emoji`; an older system
font and its installed notices and use system emoji fonts instead. An Noto font cannot replace it. No font is installed globally on Windows.
explicit `ZOITECHAT_EMOJI_FONT` path still enables the private font map
in such builds. With the bundled font, it is registered privately as
`ZoiteChat Emoji`; an older system Noto font cannot replace it. No font
is installed globally on Windows.
Chat windows and the emoji picker share this map. Fontconfig still loads Chat windows and the emoji picker share this map. Fontconfig still loads
system fonts and configuration for ordinary text, and the configured text system fonts and configuration for ordinary text, and the configured text
@@ -33,9 +29,7 @@ directory; Windows resolves it relative to the executable. AppImage sets
ZOITECHAT_EMOJI_FONT="$PWD/data/fonts/NotoColorEmoji.ttf" build/src/fe-gtk/zoitechat ZOITECHAT_EMOJI_FONT="$PWD/data/fonts/NotoColorEmoji.ttf" build/src/fe-gtk/zoitechat
``` ```
An unexpectedly missing or unloadable bundled font logs a warning and retains A missing/unloadable file logs a warning and retains system-font fallback.
system-font fallback. When installation is disabled deliberately, fallback is
quiet unless an explicit `ZOITECHAT_EMOJI_FONT` override is invalid.
CI verifies the installed copy and tests catalog/font coverage; AppImage and CI verifies the installed copy and tests catalog/font coverage; AppImage and
Windows also run the interactive picker regression tests. Native themes, Windows also run the interactive picker regression tests. Native themes,
DPI and different Pango/Cairo versions can still affect pixel-level output. DPI and different Pango/Cairo versions can still affect pixel-level output.
+9 -11
View File
@@ -1,15 +1,13 @@
emoji_font_dir = join_paths(get_option('datadir'), 'fonts', 'zoitechat') emoji_font_dir = join_paths(get_option('datadir'), 'fonts', 'zoitechat')
emoji_font_doc_dir = join_paths(get_option('datadir'), 'doc', 'zoitechat', 'fonts') emoji_font_doc_dir = join_paths(get_option('datadir'), 'doc', 'zoitechat', 'fonts')
if get_option('install-emoji-font') install_data(
install_data( 'NotoColorEmoji.ttf',
'NotoColorEmoji.ttf', install_dir: emoji_font_dir,
install_dir: emoji_font_dir, )
)
install_data( install_data(
'OFL.txt', 'OFL.txt',
'README.md', 'README.md',
install_dir: emoji_font_doc_dir, install_dir: emoji_font_doc_dir,
) )
endif
-6
View File
@@ -71,12 +71,6 @@ Packagers may exclude `docs/_theme/basicstrap/` from the source package.
The default theme continues to build without that directory. No pre-minified The default theme continues to build without that directory. No pre-minified
third-party assets need to be added or rebuilt for this theme. third-party assets need to be added or rebuilt for this theme.
The Meson option `-Dinstall-docs` defaults to `false`, so platforms that
do not ship offline documentation do not need Sphinx just to build ZoiteChat.
Pass `-Dinstall-docs=true` when packaging local HTML documentation. Fedora
and AppImage enable it explicitly; the Debian documentation package can do
the same.
Build with the distribution's `python3-sphinx`. After installing the HTML Build with the distribution's `python3-sphinx`. After installing the HTML
into the package staging directory, use `dh_sphinxdoc` (or `dh --with into the package staging directory, use `dh_sphinxdoc` (or `dh --with
sphinxdoc`) to replace Sphinx's standard JavaScript files with system sphinxdoc`) to replace Sphinx's standard JavaScript files with system
+1 -1
View File
@@ -807,7 +807,7 @@ a.headerlink:focus-visible {
:root { :root {
--page: var(--bg); --page: var(--bg);
--panel: var(--chrome); --panel: var(--chrome);
--font: system-ui, -apple-system, "Segoe UI", "DejaVu Sans", sans-serif; --font: system-ui, -apple-system, "Segoe UI", sans-serif;
} }
.site-header { padding: 0; } .site-header { padding: 0; }
.appearance select { min-height: 0; border-radius: 3px; } .appearance select { min-height: 0; border-radius: 3px; }
+2 -2
View File
@@ -16,7 +16,7 @@
--success: #236741; --success: #236741;
--code: #f2f4f6; --code: #f2f4f6;
--shadow: none; --shadow: none;
--mono: Consolas,"DejaVu Sans Mono","Liberation Mono",monospace; --mono: Consolas,"Liberation Mono",monospace;
} }
html[data-theme=dark] { html[data-theme=dark] {
@@ -45,7 +45,7 @@ body {
margin: 0; margin: 0;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
font: 15px/1.6 system-ui,-apple-system,"Segoe UI","DejaVu Sans",sans-serif; font: 15px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;
} }
a { a {
-2
View File
@@ -135,9 +135,7 @@
{{ js_tag(scriptfile) }} {{ js_tag(scriptfile) }}
{%- endif -%} {%- endif -%}
{%- endfor %} {%- endfor %}
{%- if theme_theme_preview|tobool %}
<script type="text/javascript" src="{{ pathto('_static/js/jquery.cookie.min.js', 1) }}"></script> <script type="text/javascript" src="{{ pathto('_static/js/jquery.cookie.min.js', 1) }}"></script>
{%- endif %}
<script type="text/javascript" src="{{ pathto('_static/js/basicstrap.js', 1) }}"></script> <script type="text/javascript" src="{{ pathto('_static/js/basicstrap.js', 1) }}"></script>
<script type="text/javascript"> <script type="text/javascript">
DOCUMENTATION_OPTIONS.FILE_SUFFIX = DOCUMENTATION_OPTIONS.FILE_SUFFIX || '.html'; DOCUMENTATION_OPTIONS.FILE_SUFFIX = DOCUMENTATION_OPTIONS.FILE_SUFFIX || '.html';
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/env python3
import os
import shutil
import subprocess
import sys
def prune_unused_local_assets(output_dir):
index_path = os.path.join(output_dir, "index.html")
try:
with open(index_path, "r", encoding="utf-8") as index_file:
index_html = index_file.read()
except OSError:
return
if "bootswatch-" not in index_html:
for bootstrap_version in ("bootstrap2", "bootstrap3"):
css_dir = os.path.join(
output_dir, "_static", "css", bootstrap_version
)
if not os.path.isdir(css_dir):
continue
for filename in os.listdir(css_dir):
if filename.startswith("bootswatch-") and filename.endswith(".css"):
os.remove(os.path.join(css_dir, filename))
if "jquery.cookie.min.js" not in index_html:
js_dir = os.path.join(output_dir, "_static", "js")
for filename in ("jquery.cookie.min.js", "jquery.cookie-1.4.1.min.js"):
path = os.path.join(js_dir, filename)
if os.path.isfile(path):
os.remove(path)
def main():
if len(sys.argv) != 4:
raise SystemExit("usage: build-docs.py SOURCE_DIR OUTPUT_DIR STAMP_FILE")
source_dir = os.path.abspath(sys.argv[1])
output_dir = os.path.abspath(sys.argv[2])
stamp_file = os.path.abspath(sys.argv[3])
doctree_dir = output_dir + ".doctrees"
shutil.rmtree(output_dir, ignore_errors=True)
shutil.rmtree(doctree_dir, ignore_errors=True)
os.makedirs(output_dir, exist_ok=True)
subprocess.run(
[
sys.executable,
"-m",
"sphinx",
"-b",
"html",
"-d",
doctree_dir,
source_dir,
output_dir,
],
check=True,
)
prune_unused_local_assets(output_dir)
with open(stamp_file, "w", encoding="utf-8") as stamp:
stamp.write("built\n")
if __name__ == "__main__":
main()
-36
View File
@@ -1,36 +0,0 @@
#!/usr/bin/env python3
import os
import shutil
import sys
def main():
if len(sys.argv) != 4:
raise SystemExit("usage: install-docs.py STAMP_FILE SOURCE_DIR DATADIR")
_, source_dir, datadir = sys.argv[1:]
source_dir = os.path.abspath(source_dir)
if not os.path.isfile(sys.argv[1]):
raise SystemExit("documentation build did not produce its stamp file")
if not os.path.isfile(os.path.join(source_dir, "index.html")):
raise SystemExit("documentation build did not produce index.html")
if os.path.isabs(datadir):
destdir = os.environ.get("DESTDIR", "")
if destdir:
data_root = os.path.join(destdir, datadir.lstrip("/\\"))
else:
data_root = datadir
else:
data_root = os.path.join(os.environ["MESON_INSTALL_DESTDIR_PREFIX"], datadir)
destination = os.path.join(data_root, "doc", "zoitechat", "html")
shutil.rmtree(destination, ignore_errors=True)
os.makedirs(os.path.dirname(destination), exist_ok=True)
shutil.copytree(source_dir, destination)
if __name__ == "__main__":
main()
+35
View File
@@ -628,3 +628,38 @@ List of Settings
+------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| **url\_logging** | Toggle logging URLs to *<config>/url.log*. | | **url\_logging** | Toggle logging URLs to *<config>/url.log*. |
+------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Autoaway
--------
In **Preferences > Chatting > General > Away**, set **Autoaway after** to a
number of minutes. The default is 0 (disabled). The separate **Autoaway
message** defaults to "Idle". No plugin or scripting language is required.
The same options are available immediately through ``/SET``::
/SET away_auto_timeout 10
/SET away_auto_reason Idle
/SET away_auto_system ON
With desktop activity enabled, Windows uses the current login session's last
input time, macOS uses CoreGraphics input time, and native X11 uses the
XScreenSaver extension if its development library was available at build time.
Debian/Ubuntu builders can install ``libxss-dev``; Fedora builders can install
``libXScrnSaver-devel``. Meson detects this optional dependency automatically.
On native Wayland, XWayland, unsupported desktops, and builds without XScreenSaver,
autoaway falls back to keyboard, mouse, scroll and touch activity **inside
ZoiteChat**. Activity in other applications does not reset that fallback timer.
Set ``away_auto_system OFF`` to always use ZoiteChat activity on every platform.
The text frontend uses submitted user input. Network traffic, redraws and window
focus changes do not count as user activity.
Autoaway checks every five seconds, after IRC login finishes, independently on
each connected network. Returning clears only away states set by autoaway.
Manual ``/AWAY`` (including ``/AWAY`` while already automatically away) takes
ownership, so activity does not clear it. ``/BACK`` resets the inactivity timer.
The existing **Automatically unmark away** setting keeps its separate behaviour
when sending messages. Do not run another autoaway script at the same time.
Disabling autoaway clears its own away states. Reconnecting re-evaluates current
inactivity instead of restoring an outdated automatic away state; manual away
restoration is unchanged.
+1 -2
View File
@@ -55,11 +55,10 @@
"run-tests": true, "run-tests": true,
"test-rule": "", "test-rule": "",
"test-commands": [ "test-commands": [
"meson test --print-errorlogs 'Emoji Data Tests' 'Emoji Font Tests'" "meson test --print-errorlogs 'Autoaway Tests' 'Idle Backend Tests' 'Emoji Data Tests' 'Emoji Font Tests'"
], ],
"config-opts": [ "config-opts": [
"-Ddbus-service-use-appid=true", "-Ddbus-service-use-appid=true",
"-Dinstall-docs=false",
"-Dwith-perl=perl", "-Dwith-perl=perl",
"-Dwith-python=python3", "-Dwith-python=python3",
"-Dwith-lua=lua" "-Dwith-lua=lua"
-39
View File
@@ -38,15 +38,11 @@ endif
config_h = configuration_data() config_h = configuration_data()
config_h.set('HAVE_GTK_MAC', host_machine.system() == 'darwin' and gtk_mac_dep.found()) config_h.set('HAVE_GTK_MAC', host_machine.system() == 'darwin' and gtk_mac_dep.found())
config_h.set10('ZOITECHAT_INSTALL_EMOJI_FONT', get_option('install-emoji-font'))
config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
config_h.set_quoted('PACKAGE_NAME', meson.project_name()) config_h.set_quoted('PACKAGE_NAME', meson.project_name())
config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat') config_h.set_quoted('GETTEXT_PACKAGE', 'zoitechat')
config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'),
get_option('datadir'), 'locale')) get_option('datadir'), 'locale'))
config_h.set_quoted('ZOITECHAT_DOCDIR',
join_paths(get_option('prefix'), get_option('datadir'), 'doc', 'zoitechat', 'html')
)
config_h.set10('ENABLE_NLS', true) config_h.set10('ENABLE_NLS', true)
config_h.set_quoted('ZOITECHAT_DATADIR', join_paths(get_option('prefix'), get_option('datadir'))) config_h.set_quoted('ZOITECHAT_DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
@@ -183,41 +179,6 @@ if host_machine.system() == 'darwin'
endif endif
add_project_link_arguments(global_ldflags, language: 'c') add_project_link_arguments(global_ldflags, language: 'c')
if get_option('install-docs')
docs_python = find_program('python3', 'python')
docs_sphinx = run_command(docs_python, '-c', 'import sphinx', check: false)
if docs_sphinx.returncode() != 0
error('Sphinx is required to build the local HTML documentation. Install Sphinx or configure with -Dinstall-docs=false.')
endif
docs_source_dir = join_paths(meson.source_root(), 'docs')
docs_output_dir = join_paths(meson.build_root(), 'docs-html')
docs_build_script = join_paths(docs_source_dir, 'build-docs.py')
docs_install_script = join_paths(docs_source_dir, 'install-docs.py')
docs_target = custom_target(
'html-documentation',
output: 'html-documentation.stamp',
command: [
docs_python,
docs_build_script,
docs_source_dir,
docs_output_dir,
'@OUTPUT@',
],
build_by_default: true,
build_always_stale: true,
)
meson.add_install_script(
docs_python,
docs_install_script,
docs_target,
docs_output_dir,
get_option('datadir'),
)
endif
subdir('src') subdir('src')
if get_option('plugin') if get_option('plugin')
subdir('plugins') subdir('plugins')
-6
View File
@@ -37,12 +37,6 @@ option('install-appdata', type: 'boolean',
option('install-plugin-metainfo', type: 'boolean', value: false, option('install-plugin-metainfo', type: 'boolean', value: false,
description: 'Installs metainfo files for enabled plugins, useful when distros create split packages' description: 'Installs metainfo files for enabled plugins, useful when distros create split packages'
) )
option('install-emoji-font', type: 'boolean', value: true,
description: 'Install the bundled Noto Color Emoji font instead of using system emoji fonts'
)
option('install-docs', type: 'boolean', value: false,
description: 'Build and install the local HTML documentation'
)
# Plugins # Plugins
option('with-checksum', type: 'boolean', option('with-checksum', type: 'boolean',
+1 -3
View File
@@ -15,7 +15,6 @@ BuildRequires: perl
BuildRequires: perl-devel BuildRequires: perl-devel
BuildRequires: python3 BuildRequires: python3
BuildRequires: python3-cffi BuildRequires: python3-cffi
BuildRequires: python3-sphinx
BuildRequires: publicsuffix-list BuildRequires: publicsuffix-list
BuildRequires: xwayland-run BuildRequires: xwayland-run
BuildRequires: weston BuildRequires: weston
@@ -26,6 +25,7 @@ BuildRequires: pkgconfig(gmodule-2.0)
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22 BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
BuildRequires: pkgconfig(fontconfig) >= 2.13 BuildRequires: pkgconfig(fontconfig) >= 2.13
BuildRequires: pkgconfig(pangoft2) >= 1.44 BuildRequires: pkgconfig(pangoft2) >= 1.44
BuildRequires: pkgconfig(xscrnsaver)
BuildRequires: pkgconfig(iso-codes) BuildRequires: pkgconfig(iso-codes)
BuildRequires: pkgconfig(libarchive) BuildRequires: pkgconfig(libarchive)
BuildRequires: pkgconfig(libcanberra) >= 0.22 BuildRequires: pkgconfig(libcanberra) >= 0.22
@@ -53,7 +53,6 @@ systems.
%build %build
%meson \ %meson \
-Dinstall-docs=true \
-Dtext-frontend=false \ -Dtext-frontend=false \
-Dwith-checksum=true \ -Dwith-checksum=true \
-Dwith-fishlim=true \ -Dwith-fishlim=true \
@@ -96,7 +95,6 @@ xwfb-run -- /usr/bin/meson test -C %{_vpath_builddir} --num-processes %{_smp_bui
%{_datadir}/icons/hicolor/scalable/apps/net.zoite.Zoitechat.svg %{_datadir}/icons/hicolor/scalable/apps/net.zoite.Zoitechat.svg
%{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml %{_datadir}/metainfo/net.zoite.Zoitechat.appdata.xml
%{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml %{_datadir}/metainfo/net.zoite.Zoitechat*.metainfo.xml
%{_datadir}/doc/zoitechat/html/
%dir %{_libdir}/zoitechat %dir %{_libdir}/zoitechat
%dir %{_libdir}/zoitechat/plugins %dir %{_libdir}/zoitechat/plugins
%dir %{_libdir}/zoitechat/python %dir %{_libdir}/zoitechat/python
-1
View File
@@ -48,7 +48,6 @@ def main() -> None:
"--prefix=/", "--libdir=lib", "--buildtype=release", "-Dgtk-frontend=true", "--prefix=/", "--libdir=lib", "--buildtype=release", "-Dgtk-frontend=true",
"-Dtext-frontend=false", "-Dmac-integration=enabled", "-Ddbus=disabled", "-Dtext-frontend=false", "-Dmac-integration=enabled", "-Ddbus=disabled",
"-Dlibcanberra=disabled", "-Dappindicator=disabled", "-Dinstall-appdata=false", "-Dlibcanberra=disabled", "-Dappindicator=disabled", "-Dinstall-appdata=false",
"-Dinstall-docs=false",
"-Dwith-sysinfo=false", "-Dwith-python=false", "-Dwith-perl=false", "-Dwith-sysinfo=false", "-Dwith-python=false", "-Dwith-perl=false",
"-Dwith-lua=lua", "-Dwith-fishlim=true", "-Dwith-checksum=true", "-Dwith-lua=lua", "-Dwith-fishlim=true", "-Dwith-checksum=true",
"-Dplugin=" + ("false" if args.store_experimental else "true"), "-Dplugin=" + ("false" if args.store_experimental else "true"),
+135
View File
@@ -0,0 +1,135 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "zoitechat.h"
#include "zoitechatc.h"
#include "server.h"
#include "fe.h"
#include "autoaway.h"
static gint64 last_activity;
static int timer_tag;
static int previous_timeout;
static unsigned int previous_system;
void
autoaway_activity (void)
{
last_activity = g_get_monotonic_time ();
}
/* A manual /AWAY takes ownership even if the server already says away. */
void
autoaway_manual (server *serv)
{
serv->autoaway_owned = FALSE;
autoaway_activity ();
}
void
autoaway_reset (server *serv)
{
if (!serv->autoaway_owned)
return;
serv->autoaway_owned = FALSE;
serv->reconnect_away = FALSE;
g_clear_pointer (&serv->last_away_reason, g_free);
}
static void
autoaway_release (server *serv)
{
if (!serv->autoaway_owned)
return;
/* Cancel even an unacknowledged AWAY. IRC replies may arrive later. */
if (serv->connected && serv->end_of_motd)
serv->p_set_back (serv);
autoaway_reset (serv);
}
void
autoaway_check (gint64 now, gint64 desktop_idle)
{
GSList *list;
gint64 idle = MAX ((gint64)0, now - last_activity);
gint64 limit = (gint64)prefs.hex_away_auto_timeout * 60 * G_USEC_PER_SEC;
if (prefs.hex_away_auto_system && desktop_idle >= 0)
idle = MIN (idle, desktop_idle);
for (list = serv_list; list; list = list->next)
{
server *serv = list->data;
if (!serv->connected)
{
/* Re-evaluate idle after login instead of restoring stale autoaway. */
autoaway_reset (serv);
continue;
}
if (!serv->end_of_motd)
continue;
if (limit <= 0 || idle < limit)
{
autoaway_release (serv);
continue;
}
if (serv->is_away || serv->reconnect_away)
continue;
/* Retry a rejected/lost request slowly, never once per poll. */
if (serv->autoaway_owned &&
now - serv->autoaway_sent_at < 30 * G_USEC_PER_SEC)
continue;
g_free (serv->last_away_reason);
serv->last_away_reason = g_strdup (prefs.hex_away_auto_reason[0] ?
prefs.hex_away_auto_reason : _("Idle"));
serv->autoaway_owned = TRUE;
serv->autoaway_sent_at = now;
serv->p_set_away (serv, serv->last_away_reason);
}
}
static int
autoaway_timer (void)
{
autoaway_check (g_get_monotonic_time (),
prefs.hex_away_auto_system ? fe_get_idle_time () : -1);
return 1;
}
void
autoaway_reinit (void)
{
if (previous_timeout != prefs.hex_away_auto_timeout ||
previous_system != prefs.hex_away_auto_system)
{
autoaway_activity ();
previous_timeout = prefs.hex_away_auto_timeout;
previous_system = prefs.hex_away_auto_system;
}
if (prefs.hex_away_auto_timeout > 0)
{
if (!timer_tag)
{
autoaway_activity ();
timer_tag = fe_timeout_add_seconds (5, autoaway_timer, NULL);
}
}
else
{
if (timer_tag)
{
fe_timeout_remove (timer_tag);
timer_tag = 0;
}
/* Disabling autoaway must also work without another input event. */
autoaway_check (g_get_monotonic_time (), -1);
}
}
+19
View File
@@ -0,0 +1,19 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ZOITECHAT_AUTOAWAY_H
#define ZOITECHAT_AUTOAWAY_H
#include <glib.h>
struct server;
void autoaway_activity (void);
void autoaway_manual (struct server *serv);
void autoaway_reset (struct server *serv);
void autoaway_reinit (void);
/* Both samples are in microseconds; -1 means desktop idle is unavailable. */
void autoaway_check (gint64 now, gint64 desktop_idle);
#endif
+6
View File
@@ -29,6 +29,7 @@
#include "fe.h" #include "fe.h"
#include "text.h" #include "text.h"
#include "zoitechatc.h" #include "zoitechatc.h"
#include "autoaway.h"
#include "typedef.h" #include "typedef.h"
#ifdef WIN32 #ifdef WIN32
@@ -359,6 +360,9 @@ default_file (void)
const struct prefs vars[] = const struct prefs vars[] =
{ {
{"away_auto_reason", P_OFFSET (hex_away_auto_reason), TYPE_STR},
{"away_auto_system", P_OFFINT (hex_away_auto_system), TYPE_BOOL, autoaway_reinit},
{"away_auto_timeout", P_OFFINT (hex_away_auto_timeout), TYPE_INT, autoaway_reinit},
{"away_auto_unmark", P_OFFINT (hex_away_auto_unmark), TYPE_BOOL}, {"away_auto_unmark", P_OFFINT (hex_away_auto_unmark), TYPE_BOOL},
{"away_omit_alerts", P_OFFINT (hex_away_omit_alerts), TYPE_BOOL}, {"away_omit_alerts", P_OFFINT (hex_away_omit_alerts), TYPE_BOOL},
{"away_reason", P_OFFSET (hex_away_reason), TYPE_STR}, {"away_reason", P_OFFSET (hex_away_reason), TYPE_STR},
@@ -767,6 +771,7 @@ load_default_config(void)
/* put in default values, anything left out is automatically zero */ /* put in default values, anything left out is automatically zero */
/* BOOLEANS */ /* BOOLEANS */
prefs.hex_away_auto_system = 1;
prefs.hex_away_show_once = 1; prefs.hex_away_show_once = 1;
prefs.hex_away_track = 1; prefs.hex_away_track = 1;
prefs.hex_dcc_auto_resume = 1; prefs.hex_dcc_auto_resume = 1;
@@ -887,6 +892,7 @@ load_default_config(void)
prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */ prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */
/* STRINGS */ /* STRINGS */
strcpy (prefs.hex_away_auto_reason, _("Idle"));
strcpy (prefs.hex_away_reason, _("I'm busy")); strcpy (prefs.hex_away_reason, _("I'm busy"));
strcpy (prefs.hex_completion_suffix, ","); strcpy (prefs.hex_completion_suffix, ",");
#ifdef WIN32 #ifdef WIN32
+2
View File
@@ -11,6 +11,7 @@
</ProjectConfiguration> </ProjectConfiguration>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="autoaway.h" />
<ClInclude Include="cfgfiles.h" /> <ClInclude Include="cfgfiles.h" />
<ClInclude Include="chanopt.h" /> <ClInclude Include="chanopt.h" />
<ClInclude Include="ctcp.h" /> <ClInclude Include="ctcp.h" />
@@ -52,6 +53,7 @@
<ClInclude Include="gtk3-theme-service.h" /> <ClInclude Include="gtk3-theme-service.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="autoaway.c" />
<ClCompile Include="cfgfiles.c" /> <ClCompile Include="cfgfiles.c" />
<ClCompile Include="chanopt.c" /> <ClCompile Include="chanopt.c" />
<ClCompile Include="ctcp.c" /> <ClCompile Include="ctcp.c" />
+6
View File
@@ -17,6 +17,9 @@
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="autoaway.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="cfgfiles.h"> <ClInclude Include="cfgfiles.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@@ -136,6 +139,9 @@
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="autoaway.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="cfgfiles.c"> <ClCompile Include="cfgfiles.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
+1
View File
@@ -142,6 +142,7 @@ void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gtk_xtext_sea
void fe_set_lag (server *serv, long lag); void fe_set_lag (server *serv, long lag);
void fe_set_throttle (server *serv); void fe_set_throttle (server *serv);
void fe_set_away (server *serv); void fe_set_away (server *serv);
gint64 fe_get_idle_time (void);
void fe_serverlist_open (session *sess); void fe_serverlist_open (session *sess);
void fe_get_bool (char *title, char *prompt, void *callback, void *userdata); void fe_get_bool (char *title, char *prompt, void *callback, void *userdata);
void fe_get_str (char *prompt, char *def, void *callback, void *ud); void fe_get_str (char *prompt, char *def, void *callback, void *ud);
+8
View File
@@ -1,4 +1,5 @@
common_sources = [ common_sources = [
'autoaway.c',
'cfgfiles.c', 'cfgfiles.c',
'chanopt.c', 'chanopt.c',
'ctcp.c', 'ctcp.c',
@@ -224,3 +225,10 @@ test('Hex Color Tests', hexcolor_tests,
protocol: 'tap', protocol: 'tap',
timeout: 120, timeout: 120,
) )
autoaway_tests = executable('autoaway_tests',
['tests/test-autoaway.c', 'autoaway.c'],
include_directories: [config_h_include, include_directories('.')],
dependencies: zoitechat_common_header_deps,
)
test('Autoaway Tests', autoaway_tests, protocol: 'tap', timeout: 30)
+12 -1
View File
@@ -54,6 +54,7 @@
#include "server.h" #include "server.h"
#include "tree.h" #include "tree.h"
#include "outbound.h" #include "outbound.h"
#include "autoaway.h"
#include "chanopt.h" #include "chanopt.h"
#define TBUFSIZE 4096 #define TBUFSIZE 4096
@@ -360,6 +361,10 @@ cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{ {
char *reason = word_eol[2]; char *reason = word_eol[2];
/* Restoring a manual away after reconnect is not new user input. */
if (!sess->server->reconnect_away)
autoaway_manual (sess->server);
if (!(*reason)) if (!(*reason))
{ {
if (sess->server->is_away) if (sess->server->is_away)
@@ -396,7 +401,10 @@ cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
static int static int
cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[]) cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{ {
if (sess->server->is_away) gboolean was_autoaway = sess->server->autoaway_owned;
autoaway_manual (sess->server);
if (sess->server->is_away || was_autoaway)
{ {
sess->server->p_set_back (sess->server); sess->server->p_set_back (sess->server);
} }
@@ -5051,6 +5059,9 @@ handle_user_input (session *sess, char *text, int history, int nocommand)
if (*text == '\0') if (*text == '\0')
return 1; return 1;
if (history)
autoaway_activity ();
if (history) if (history)
history_add (&sess->history, text); history_add (&sess->history, text);
+7
View File
@@ -30,6 +30,7 @@
#include "zoitechat.h" #include "zoitechat.h"
#include "proto-irc.h" #include "proto-irc.h"
#include "autoaway.h"
#include "ctcp.h" #include "ctcp.h"
#include "fe.h" #include "fe.h"
#include "ignore.h" #include "ignore.h"
@@ -438,6 +439,12 @@ irc_raw (server *serv, char *raw)
{ {
int len; int len;
char tbuf[8704]; char tbuf[8704];
/* /QUOTE AWAY and scripts sending raw AWAY also take manual ownership. */
if (!g_ascii_strncasecmp (raw, "AWAY", 4) &&
(raw[4] == 0 || raw[4] == ' '))
autoaway_manual (serv);
if (*raw) if (*raw)
{ {
len = strlen (raw); len = strlen (raw);
+2
View File
@@ -54,6 +54,7 @@
#include "proto-irc.h" #include "proto-irc.h"
#include "servlist.h" #include "servlist.h"
#include "server.h" #include "server.h"
#include "autoaway.h"
#include "sts.h" #include "sts.h"
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
@@ -1873,6 +1874,7 @@ is_server (server *serv)
void void
server_set_defaults (server *serv) server_set_defaults (server *serv)
{ {
autoaway_reset (serv);
g_free (serv->chantypes); g_free (serv->chantypes);
g_clear_pointer (&serv->clienttagdeny, g_free); g_clear_pointer (&serv->clienttagdeny, g_free);
g_free (serv->chanmodes); g_free (serv->chanmodes);
+296
View File
@@ -0,0 +1,296 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <string.h>
#include "../zoitechat.h"
#include "../autoaway.h"
struct zoitechatprefs prefs;
GSList *serv_list;
static server servers[3];
static int away_count, back_count, timer_adds, timer_removes;
static gint64 base;
static char *sent_reason;
/* Protocol stubs deliberately do not acknowledge requests. */
static void
send_away (server *serv, char *reason)
{
away_count++;
g_free (sent_reason);
sent_reason = g_strdup (reason);
}
static void
send_back (server *serv)
{
back_count++;
}
int
fe_timeout_add_seconds (int interval, void *callback, void *data)
{
g_assert_cmpint (interval, ==, 5);
timer_adds++;
return 42;
}
void
fe_timeout_remove (int tag)
{
g_assert_cmpint (tag, ==, 42);
timer_removes++;
}
gint64
fe_get_idle_time (void)
{
return -1;
}
static void
setup (void)
{
int i;
prefs.hex_away_auto_timeout = 0;
autoaway_reinit ();
for (i = 0; i < 3; i++)
g_free (servers[i].last_away_reason);
g_slist_free (serv_list);
serv_list = NULL;
memset (servers, 0, sizeof servers);
memset (&prefs, 0, sizeof prefs);
away_count = back_count = timer_adds = timer_removes = 0;
g_clear_pointer (&sent_reason, g_free);
prefs.hex_away_auto_timeout = 10;
prefs.hex_away_auto_system = TRUE;
g_strlcpy (prefs.hex_away_auto_reason, "Auto idle", sizeof prefs.hex_away_auto_reason);
for (i = 0; i < 3; i++)
{
servers[i].connected = TRUE;
servers[i].end_of_motd = TRUE;
servers[i].p_set_away = send_away;
servers[i].p_set_back = send_back;
}
serv_list = g_slist_append (NULL, &servers[0]);
autoaway_activity ();
base = g_get_monotonic_time ();
}
static void
test_fallback_boundary (void)
{
setup ();
autoaway_check (base + 599 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 0);
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
g_assert_true (servers[0].autoaway_owned);
g_assert_cmpstr (sent_reason, ==, "Auto idle");
/* An ACK is not required to avoid flooding the connection. */
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
servers[0].is_away = TRUE;
autoaway_check (base + 640 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
autoaway_activity ();
autoaway_check (g_get_monotonic_time (), -1);
g_assert_cmpint (back_count, ==, 1);
g_assert_false (servers[0].autoaway_owned);
g_assert_null (servers[0].last_away_reason);
}
static void
test_desktop_activity (void)
{
setup ();
autoaway_check (base + 900 * G_USEC_PER_SEC, 5 * G_USEC_PER_SEC);
g_assert_cmpint (away_count, ==, 0);
autoaway_check (base + 900 * G_USEC_PER_SEC, 700 * G_USEC_PER_SEC);
g_assert_cmpint (away_count, ==, 1);
servers[0].is_away = TRUE;
autoaway_check (base + 905 * G_USEC_PER_SEC, 0);
g_assert_cmpint (back_count, ==, 1);
/* App input wins even if the native backend still has an old sample. */
autoaway_activity ();
autoaway_check (g_get_monotonic_time (), 900 * G_USEC_PER_SEC);
g_assert_cmpint (away_count, ==, 1);
}
static void
test_app_mode (void)
{
setup ();
prefs.hex_away_auto_system = FALSE;
autoaway_check (base + 600 * G_USEC_PER_SEC, 0);
g_assert_cmpint (away_count, ==, 1);
}
static void
test_manual_away (void)
{
setup ();
servers[0].is_away = TRUE;
servers[0].last_away_reason = g_strdup ("Lunch");
autoaway_check (base + 900 * G_USEC_PER_SEC, -1);
autoaway_check (base + 905 * G_USEC_PER_SEC, 0);
prefs.hex_away_auto_timeout = 0;
autoaway_reinit ();
g_assert_cmpint (away_count, ==, 0);
g_assert_cmpint (back_count, ==, 0);
g_assert_cmpstr (servers[0].last_away_reason, ==, "Lunch");
}
static void
test_manual_takeover (void)
{
setup ();
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
servers[0].is_away = TRUE;
/* This is also called for /AWAY with no reason, even if already away. */
autoaway_manual (&servers[0]);
autoaway_check (g_get_monotonic_time (), 0);
prefs.hex_away_auto_timeout = 0;
autoaway_reinit ();
g_assert_cmpint (back_count, ==, 0);
g_assert_true (servers[0].is_away);
}
static void
test_pending_away_cancel (void)
{
setup ();
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
g_assert_false (servers[0].is_away);
autoaway_activity ();
autoaway_check (g_get_monotonic_time (), -1);
g_assert_cmpint (back_count, ==, 1);
/* A late 306 must not make us own the away state again. */
servers[0].is_away = TRUE;
autoaway_check (g_get_monotonic_time (), -1);
g_assert_cmpint (back_count, ==, 1);
}
static void
test_login_and_multiple_servers (void)
{
setup ();
serv_list = g_slist_append (serv_list, &servers[1]);
serv_list = g_slist_append (serv_list, &servers[2]);
servers[1].is_away = TRUE;
servers[1].last_away_reason = g_strdup ("Manual");
servers[2].end_of_motd = FALSE;
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
servers[2].end_of_motd = TRUE;
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 2);
autoaway_check (base + 610 * G_USEC_PER_SEC, 0);
g_assert_cmpint (back_count, ==, 2);
g_assert_cmpstr (servers[1].last_away_reason, ==, "Manual");
}
static void
test_disconnect_and_reconnect (void)
{
setup ();
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
servers[0].connected = FALSE;
servers[0].is_away = TRUE;
servers[0].reconnect_away = TRUE;
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
g_assert_false (servers[0].autoaway_owned);
g_assert_false (servers[0].reconnect_away);
g_assert_null (servers[0].last_away_reason);
g_assert_cmpint (back_count, ==, 0);
servers[0].is_away = FALSE;
servers[0].connected = TRUE;
servers[0].end_of_motd = FALSE;
autoaway_check (base + 610 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
servers[0].end_of_motd = TRUE;
autoaway_check (base + 615 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 2);
/* server_set_defaults resets ownership even before the next timer tick. */
servers[0].reconnect_away = TRUE;
autoaway_reset (&servers[0]);
g_assert_false (servers[0].reconnect_away);
/* Existing manual reconnect-away must be left alone. */
servers[0].reconnect_away = TRUE;
servers[0].last_away_reason = g_strdup ("Manual");
autoaway_reset (&servers[0]);
g_assert_true (servers[0].reconnect_away);
g_assert_cmpstr (servers[0].last_away_reason, ==, "Manual");
}
static void
test_retry (void)
{
setup ();
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
autoaway_check (base + 629 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 1);
autoaway_check (base + 630 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 2);
}
static void
test_disable_and_timers (void)
{
setup ();
autoaway_reinit ();
autoaway_reinit ();
g_assert_cmpint (timer_adds, ==, 1);
base = g_get_monotonic_time ();
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
prefs.hex_away_auto_timeout = 0;
autoaway_reinit ();
g_assert_cmpint (back_count, ==, 1);
g_assert_cmpint (timer_removes, ==, 1);
g_assert_false (servers[0].autoaway_owned);
autoaway_reinit ();
g_assert_cmpint (back_count, ==, 1);
prefs.hex_away_auto_timeout = -1;
autoaway_reinit ();
g_assert_cmpint (away_count, ==, 1);
}
static void
test_empty_reason_and_large_timeout (void)
{
setup ();
prefs.hex_away_auto_reason[0] = 0;
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
g_assert_nonnull (sent_reason);
g_assert_cmpstr (sent_reason, !=, "");
setup ();
prefs.hex_away_auto_timeout = G_MAXINT;
autoaway_check (base + 900 * G_USEC_PER_SEC, -1);
g_assert_cmpint (away_count, ==, 0);
}
int
main (int argc, char **argv)
{
int result, i;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/autoaway/fallback-boundary", test_fallback_boundary);
g_test_add_func ("/autoaway/desktop-activity", test_desktop_activity);
g_test_add_func ("/autoaway/app-mode", test_app_mode);
g_test_add_func ("/autoaway/manual-away", test_manual_away);
g_test_add_func ("/autoaway/manual-takeover", test_manual_takeover);
g_test_add_func ("/autoaway/pending-cancel", test_pending_away_cancel);
g_test_add_func ("/autoaway/login-multiple-servers", test_login_and_multiple_servers);
g_test_add_func ("/autoaway/reconnect", test_disconnect_and_reconnect);
g_test_add_func ("/autoaway/retry", test_retry);
g_test_add_func ("/autoaway/disable-timers", test_disable_and_timers);
g_test_add_func ("/autoaway/empty-reason-large-timeout", test_empty_reason_and_large_timeout);
result = g_test_run ();
for (i = 0; i < 3; i++)
g_free (servers[i].last_away_reason);
g_free (sent_reason);
g_slist_free (serv_list);
return result;
}
+3
View File
@@ -46,6 +46,7 @@
#include "plugin.h" #include "plugin.h"
#include "plugin-identd.h" #include "plugin-identd.h"
#include "plugin-timer.h" #include "plugin-timer.h"
#include "autoaway.h"
#include "notify.h" #include "notify.h"
#include "server.h" #include "server.h"
#include "servlist.h" #include "servlist.h"
@@ -499,6 +500,8 @@ zoitechat_reinit_timers (void)
static int lag_check_tag = 0; static int lag_check_tag = 0;
static int away_tag = 0; static int away_tag = 0;
autoaway_reinit ();
if (prefs.hex_notify_timeout && notify_tag == 0) if (prefs.hex_notify_timeout && notify_tag == 0)
{ {
notify_tag = fe_timeout_add_seconds (prefs.hex_notify_timeout, notify_tag = fe_timeout_add_seconds (prefs.hex_notify_timeout,
+5
View File
@@ -105,6 +105,7 @@ struct zoitechatprefs
/* these are the rebranded, consistent, sorted zoitechat variables */ /* these are the rebranded, consistent, sorted zoitechat variables */
/* BOOLEANS */ /* BOOLEANS */
unsigned int hex_away_auto_system;
unsigned int hex_away_auto_unmark; unsigned int hex_away_auto_unmark;
unsigned int hex_away_omit_alerts; unsigned int hex_away_omit_alerts;
unsigned int hex_away_show_once; unsigned int hex_away_show_once;
@@ -233,6 +234,7 @@ struct zoitechatprefs
unsigned int hex_url_logging; unsigned int hex_url_logging;
/* NUMBERS */ /* NUMBERS */
int hex_away_auto_timeout;
int hex_away_size_max; int hex_away_size_max;
int hex_away_timeout; int hex_away_timeout;
int hex_completion_amount; int hex_completion_amount;
@@ -311,6 +313,7 @@ struct zoitechatprefs
int hex_url_grabber_limit; int hex_url_grabber_limit;
/* STRINGS */ /* STRINGS */
char hex_away_auto_reason[256];
char hex_away_reason[256]; char hex_away_reason[256];
char hex_completion_suffix[4]; /* Only ever holds a one-character string. */ char hex_completion_suffix[4]; /* Only ever holds a one-character string. */
char hex_dcc_completed_dir[PATHLEN + 1]; char hex_dcc_completed_dir[PATHLEN + 1];
@@ -597,6 +600,7 @@ typedef struct server
/*time_t connect_time;*/ /* when did it connect? */ /*time_t connect_time;*/ /* when did it connect? */
unsigned long lag_sent; /* we are still waiting for this ping response*/ unsigned long lag_sent; /* we are still waiting for this ping response*/
time_t ping_recv; /* when we last got a ping reply */ time_t ping_recv; /* when we last got a ping reply */
gint64 autoaway_sent_at;
time_t away_time; /* when we were marked away */ time_t away_time; /* when we were marked away */
char *encoding; char *encoding;
@@ -616,6 +620,7 @@ typedef struct server
unsigned int end_of_motd:1; /* end of motd reached (logged in) */ unsigned int end_of_motd:1; /* end of motd reached (logged in) */
unsigned int sent_quit:1; /* sent a QUIT already? */ unsigned int sent_quit:1; /* sent a QUIT already? */
unsigned int use_listargs:1; /* undernet and dalnet need /list >0,<10000 */ unsigned int use_listargs:1; /* undernet and dalnet need /list >0,<10000 */
unsigned int autoaway_owned:1;
unsigned int is_away:1; unsigned int is_away:1;
unsigned int reconnect_away:1; /* whether to reconnect in is_away state */ unsigned int reconnect_away:1; /* whether to reconnect in is_away state */
unsigned int dont_use_proxy:1; /* to proxy or not to proxy */ unsigned int dont_use_proxy:1; /* to proxy or not to proxy */
-10
View File
@@ -52,16 +52,6 @@ emoji_font_get_map (void)
return map; return map;
attempted = TRUE; attempted = TRUE;
#if defined(ZOITECHAT_INSTALL_EMOJI_FONT) && !ZOITECHAT_INSTALL_EMOJI_FONT
/* A distribution package can deliberately omit our private font. Keep
* its native/system emoji rendering unless an explicit font is given. */
{
const char *override = g_getenv ("ZOITECHAT_EMOJI_FONT");
if (!override || !*override)
return NULL;
}
#endif
path = emoji_font_path (); path = emoji_font_path ();
if (!path || !g_file_test (path, G_FILE_TEST_IS_REGULAR)) if (!path || !g_file_test (path, G_FILE_TEST_IS_REGULAR))
{ {
+2
View File
@@ -59,6 +59,7 @@
#include "notifygui.h" #include "notifygui.h"
#include "textgui.h" #include "textgui.h"
#include "fkeys.h" #include "fkeys.h"
#include "idle.h"
#include "plugin-tray.h" #include "plugin-tray.h"
#include "urlgrab.h" #include "urlgrab.h"
#include "setup.h" #include "setup.h"
@@ -638,6 +639,7 @@ fe_init (void)
{ {
theme_manager_init (); theme_manager_init ();
key_init (); key_init ();
idle_init ();
pixmaps_init (); pixmaps_init ();
#ifdef HAVE_GTK_MAC #ifdef HAVE_GTK_MAC
+3 -1
View File
@@ -31,7 +31,7 @@
</ClCompile> </ClCompile>
<Link> <Link>
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;pangoft2-1.0.lib;fontconfig.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;user32.lib;pangoft2-1.0.lib;fontconfig.lib;%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol> <EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
@@ -49,6 +49,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
</PreBuildEvent> </PreBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="idle.h" />
<ClInclude Include="ascii.h" /> <ClInclude Include="ascii.h" />
<ClInclude Include="banlist.h" /> <ClInclude Include="banlist.h" />
<ClInclude Include="chanlist.h" /> <ClInclude Include="chanlist.h" />
@@ -91,6 +92,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
<ClInclude Include="theme\theme-preferences.h" /> <ClInclude Include="theme\theme-preferences.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="idle.c" />
<ClCompile Include="ascii.c" /> <ClCompile Include="ascii.c" />
<ClCompile Include="banlist.c" /> <ClCompile Include="banlist.c" />
<ClCompile Include="chanlist.c" /> <ClCompile Include="chanlist.c" />
+6
View File
@@ -15,6 +15,9 @@
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="idle.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ascii.h"> <ClInclude Include="ascii.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
@@ -137,6 +140,9 @@
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="idle.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ascii.c"> <ClCompile Include="ascii.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
+98
View File
@@ -0,0 +1,98 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <gtk/gtk.h>
#include "../common/autoaway.h"
#include "idle.h"
#ifdef WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#include <math.h>
#elif defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#include <X11/extensions/scrnsaver.h>
#endif
/* Do not count focus changes, redraws, timers or incoming IRC traffic. */
static void
idle_event (GdkEvent *event, gpointer data)
{
switch (event->type)
{
case GDK_KEY_PRESS:
case GDK_BUTTON_PRESS:
case GDK_SCROLL:
case GDK_MOTION_NOTIFY:
case GDK_TOUCH_BEGIN:
case GDK_TOUCH_UPDATE:
autoaway_activity ();
break;
default:
break;
}
gtk_main_do_event (event);
}
void
idle_init (void)
{
autoaway_activity ();
/* Keep GTK's normal dispatch, including detached windows and dialogs. */
gdk_event_handler_set (idle_event, NULL, NULL);
}
/* Microseconds since desktop input, or -1 for the portable app fallback. */
gint64
fe_get_idle_time (void)
{
#ifdef WIN32
LASTINPUTINFO input = {0};
DWORD elapsed;
input.cbSize = sizeof input;
if (!GetLastInputInfo (&input))
return -1;
/* DWORD subtraction handles the 49-day GetTickCount wrap. Some injected
* input has a slightly newer timestamp; do not treat it as weeks idle. */
elapsed = GetTickCount () - input.dwTime;
if ((gint32)elapsed < 0)
elapsed = 0;
return (gint64)elapsed * 1000;
#elif defined(__APPLE__)
CFTimeInterval seconds = CGEventSourceSecondsSinceLastEventType (
kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType);
if (!isfinite (seconds) || seconds < 0 ||
seconds >= (double)G_MAXINT64 / G_USEC_PER_SEC)
return -1;
return (gint64)(seconds * G_USEC_PER_SEC);
#elif defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
GdkDisplay *display = gdk_display_get_default ();
XScreenSaverInfo info = {0};
Display *xdisplay;
int event_base, error_base, error;
Status ok;
const char *wayland = g_getenv ("WAYLAND_DISPLAY");
/* XWayland idle is not desktop-wide. Never pass a Wayland display to Xlib. */
if (!display || !GDK_IS_X11_DISPLAY (display) || (wayland && *wayland))
return -1;
xdisplay = gdk_x11_display_get_xdisplay (display);
gdk_x11_display_error_trap_push (display);
ok = XScreenSaverQueryExtension (xdisplay, &event_base, &error_base);
if (ok)
ok = XScreenSaverQueryInfo (xdisplay, DefaultRootWindow (xdisplay), &info);
error = gdk_x11_display_error_trap_pop (display);
if (!ok || error)
return -1;
return (gint64)info.idle * 1000;
#else
return -1;
#endif
}
+10
View File
@@ -0,0 +1,10 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef ZOITECHAT_IDLE_H
#define ZOITECHAT_IDLE_H
void idle_init (void);
#endif
-41
View File
@@ -1810,50 +1810,9 @@ menu_ctcpguiopen (void)
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help); editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
} }
static char *
menu_find_local_docs (void)
{
char *path;
#ifdef WIN32
char *base_path;
base_path = g_win32_get_package_installation_directory_of_module (NULL);
if (!base_path)
return NULL;
path = g_build_filename (base_path, "share", "doc", "zoitechat", "html", "index.html", NULL);
g_free (base_path);
#else
const char *appdir;
appdir = g_getenv ("APPDIR");
if (appdir && *appdir)
path = g_build_filename (appdir, "usr", "share", "doc", "zoitechat", "html", "index.html", NULL);
else
path = g_build_filename (ZOITECHAT_DOCDIR, "index.html", NULL);
#endif
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
return path;
g_free (path);
return NULL;
}
static void static void
menu_docs (GtkWidget *wid, gpointer none) menu_docs (GtkWidget *wid, gpointer none)
{ {
char *path;
path = menu_find_local_docs ();
if (path)
{
fe_open_url (path);
g_free (path);
return;
}
fe_open_url ("https://docs.zoitechat.org/en/latest/"); fe_open_url ("https://docs.zoitechat.org/en/latest/");
} }
+22 -1
View File
@@ -25,6 +25,7 @@ zoitechat_gtk_sources = [
'gtkutil.c', 'gtkutil.c',
'icon-resolver.c', 'icon-resolver.c',
'ignoregui.c', 'ignoregui.c',
'idle.c',
'inline-image.c', 'inline-image.c',
'joind.c', 'joind.c',
'menu.c', 'menu.c',
@@ -45,6 +46,7 @@ zoitechat_gtk_sources = [
] ]
zoitechat_gtk_cflags = [] zoitechat_gtk_cflags = []
idle_deps = []
zoitechat_gtk_deps = [ zoitechat_gtk_deps = [
zoitechat_common_dep, zoitechat_common_dep,
@@ -93,9 +95,17 @@ if host_machine.system() != 'windows'
zoitechat_gtk_deps += gdk_x11_dep zoitechat_gtk_deps += gdk_x11_dep
endif endif
xss_dep = dependency('xscrnsaver', required: false)
if gdk_x11_dep.found() and xss_dep.found()
zoitechat_gtk_deps += xss_dep
idle_deps += [gdk_x11_dep, xss_dep]
zoitechat_gtk_cflags += '-DHAVE_XSS'
endif
x11_dep = dependency('x11', required: false) x11_dep = dependency('x11', required: false)
if x11_dep.found() if x11_dep.found()
zoitechat_gtk_deps += x11_dep zoitechat_gtk_deps += x11_dep
idle_deps += x11_dep
endif endif
endif endif
@@ -105,13 +115,16 @@ zoitechat_gtk_objcflags = []
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
zoitechat_gtk_sources += 'notifications/notification-windows.c' zoitechat_gtk_sources += 'notifications/notification-windows.c'
zoitechat_gtk_deps += cc.find_library('dwmapi', required: true) zoitechat_gtk_deps += cc.find_library('dwmapi', required: true)
idle_deps += cc.find_library('user32', required: true)
zoitechat_gtk_deps += idle_deps
zoitechat_theme_deps += cc.find_library('dwmapi', required: true) zoitechat_theme_deps += cc.find_library('dwmapi', required: true)
elif host_machine.system() == 'darwin' elif host_machine.system() == 'darwin'
add_languages('objc', required: true) add_languages('objc', required: true)
zoitechat_gtk_sources += 'macos-shortcuts.c' zoitechat_gtk_sources += 'macos-shortcuts.c'
zoitechat_gtk_sources += 'notifications/notification-osx.m' zoitechat_gtk_sources += 'notifications/notification-osx.m'
zoitechat_gtk_deps += dependency('appleframeworks', modules: ['Cocoa', 'UserNotifications']) zoitechat_gtk_deps += dependency('appleframeworks', modules: ['Cocoa', 'UserNotifications', 'CoreGraphics'])
idle_deps += dependency('appleframeworks', modules: ['CoreGraphics'])
zoitechat_gtk_objcflags += '-fobjc-arc' zoitechat_gtk_objcflags += '-fobjc-arc'
else else
zoitechat_gtk_sources += 'notifications/notification-freedesktop.c' zoitechat_gtk_sources += 'notifications/notification-freedesktop.c'
@@ -302,3 +315,11 @@ xtext_tests = executable('xtext_tests',
dependencies: [gtk_dep], dependencies: [gtk_dep],
) )
test('XText Tests', xtext_tests, protocol: 'tap', timeout: 120) test('XText Tests', xtext_tests, protocol: 'tap', timeout: 120)
idle_tests = executable('idle_tests',
'tests/test-idle.c',
include_directories: [config_h_include],
dependencies: [gtk_dep] + idle_deps,
c_args: zoitechat_gtk_cflags,
)
test('Idle Backend Tests', idle_tests, protocol: 'tap', timeout: 30)
+4
View File
@@ -558,6 +558,10 @@ static const setting general_settings[] =
{ST_ENTRY, N_("Away:"), P_OFFSETNL(hex_away_reason), 0, 0, sizeof prefs.hex_away_reason}, {ST_ENTRY, N_("Away:"), P_OFFSETNL(hex_away_reason), 0, 0, sizeof prefs.hex_away_reason},
{ST_HEADER, N_("Away"),0,0,0}, {ST_HEADER, N_("Away"),0,0,0},
{ST_NUMBER, N_("Autoaway after:"), P_OFFINTNL(hex_away_auto_timeout), N_("Set to 0 to disable automatic away."), (const char **)N_("minutes (0 = off)."), 1440},
{ST_ENTRY, N_("Autoaway message:"), P_OFFSETNL(hex_away_auto_reason), 0, 0, sizeof prefs.hex_away_auto_reason},
{ST_TOGGLE, N_("Use desktop activity when available"), P_OFFINTNL(hex_away_auto_system), N_("Use desktop input on Windows, macOS and X11. Otherwise, use keyboard and mouse activity in ZoiteChat."), 0, 0},
{ST_LABEL, N_("On Wayland, autoaway uses activity in ZoiteChat.\nOnly automatically set away states are cleared when activity resumes.")},
{ST_TOGGLE, N_("Show away once"), P_OFFINTNL(hex_away_show_once), N_("Show identical away messages only once."), 0, 0}, {ST_TOGGLE, N_("Show away once"), P_OFFINTNL(hex_away_show_once), N_("Show identical away messages only once."), 0, 0},
{ST_TOGGLE, N_("Automatically unmark away"), P_OFFINTNL(hex_away_auto_unmark), N_("Unmark yourself as away before sending messages."), 0, 0}, {ST_TOGGLE, N_("Automatically unmark away"), P_OFFINTNL(hex_away_auto_unmark), N_("Unmark yourself as away before sending messages."), 0, 0},
+85
View File
@@ -0,0 +1,85 @@
/* ZoiteChat
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <gtk/gtk.h>
#include "../idle.h"
static int activity_count, dispatch_count;
static void test_gtk_dispatch (GdkEvent *event);
void
autoaway_activity (void)
{
activity_count++;
}
/* Exercise the real handler without delivering synthetic input to a widget. */
#define gtk_main_do_event test_gtk_dispatch
#include "../idle.c"
#undef gtk_main_do_event
static void
test_gtk_dispatch (GdkEvent *event)
{
dispatch_count++;
}
static void
test_user_input_and_dispatch (void)
{
GdkEvent event = {0};
GdkEventType input[] = {GDK_KEY_PRESS, GDK_BUTTON_PRESS, GDK_SCROLL,
GDK_MOTION_NOTIFY, GDK_TOUCH_BEGIN, GDK_TOUCH_UPDATE};
GdkEventType other[] = {GDK_FOCUS_CHANGE, GDK_CONFIGURE, GDK_WINDOW_STATE,
GDK_PROPERTY_NOTIFY, GDK_VISIBILITY_NOTIFY};
unsigned int i;
activity_count = dispatch_count = 0;
for (i = 0; i < G_N_ELEMENTS (input); i++)
{
event.type = input[i];
idle_event (&event, NULL);
}
g_assert_cmpint (activity_count, ==, G_N_ELEMENTS (input));
for (i = 0; i < G_N_ELEMENTS (other); i++)
{
event.type = other[i];
idle_event (&event, NULL);
}
g_assert_cmpint (activity_count, ==, G_N_ELEMENTS (input));
g_assert_cmpint (dispatch_count, ==, G_N_ELEMENTS (input) + G_N_ELEMENTS (other));
}
static void
test_native_idle (void)
{
gint64 idle = fe_get_idle_time ();
g_assert_cmpint (idle, >=, -1);
#if defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
if (gdk_display_get_default () && GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
/* XWayland must use the app fallback, even if XSS is compiled in. */
char *saved = g_strdup (g_getenv ("WAYLAND_DISPLAY"));
g_unsetenv ("WAYLAND_DISPLAY");
g_assert_cmpint (fe_get_idle_time (), >=, 0);
g_setenv ("WAYLAND_DISPLAY", "wayland-test", TRUE);
g_assert_cmpint (fe_get_idle_time (), ==, -1);
if (saved)
g_setenv ("WAYLAND_DISPLAY", saved, TRUE);
else
g_unsetenv ("WAYLAND_DISPLAY");
g_free (saved);
}
#endif
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
gtk_init_check (&argc, &argv);
g_test_add_func ("/idle/user-input-and-dispatch", test_user_input_and_dispatch);
g_test_add_func ("/idle/native-backend", test_native_idle);
return g_test_run ();
}
+6
View File
@@ -48,6 +48,12 @@
#endif #endif
gint64
fe_get_idle_time (void)
{
return -1;
}
static int done = FALSE; static int done = FALSE;
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{EE388A27-6992-43C0-BA9B-61D8968D42E3}</ProjectGuid>
<SolutionDir>$(MSBuildThisFileDirectory)</SolutionDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v142</PlatformToolset>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="zoitechat.props" />
<PropertyGroup>
<TargetName>autoaway-tests</TargetName>
<OutDir>$(ZoiteChatBuild)\$(ZoiteChatPlatform)\tests\</OutDir>
<IntDir>$(ZoiteChatObj)autoaway-tests\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>WIN32;_WIN64;_CONSOLE;$(OwnFlags);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ZoiteChatLib);$(DepsRoot)\include;$(OpenSslInclude);$(Glib);$(Gtk);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>gio-2.0.lib;gobject-2.0.lib;glib-2.0.lib;$(IntlLib);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\src\common\autoaway.c" />
<ClCompile Include="..\src\common\tests\test-autoaway.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>