mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-21 12:17:14 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bae96668a | ||
|
|
9b65e5be73 | ||
|
|
9547dd3711 | ||
|
|
abf49ce756 | ||
|
|
6bf2f34fca | ||
|
|
2ea74498ed | ||
|
|
7004c679f2 | ||
|
|
c9de3f2ce0 | ||
|
|
f77d4f6d59 | ||
|
|
89b9a32168 | ||
|
|
357b766369 | ||
|
|
45a8ba18a6 | ||
|
|
0e0f8f72b2 | ||
|
|
dc5f8f7631 | ||
|
|
8a03585611 |
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
# Flatpak shared modules
|
||||||
|
- package-ecosystem: "gitsubmodule"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
open-pull-requests-limit: 2
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
|
||||||
|
# GitHub Actions dependencies
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
|
||||||
|
commit-message:
|
||||||
|
prefix: "ci"
|
||||||
|
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
|
||||||
|
groups:
|
||||||
|
actions-minor-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- "minor"
|
||||||
|
- "patch"
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
artifact-metadata: write
|
artifact-metadata: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ 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 \
|
||||||
@@ -57,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: |
|
||||||
@@ -312,7 +312,7 @@ jobs:
|
|||||||
subject-path: Zoitechat-*-x86_64.AppImage
|
subject-path: Zoitechat-*-x86_64.AppImage
|
||||||
|
|
||||||
- name: Upload AppImage artifact
|
- name: Upload AppImage artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: zoitechat-appimage
|
name: zoitechat-appimage
|
||||||
path: Zoitechat-*-x86_64.AppImage
|
path: Zoitechat-*-x86_64.AppImage
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
options: --privileged
|
options: --privileged
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Flatpak Bundle
|
- name: Upload Flatpak Bundle
|
||||||
id: upload_flatpak
|
id: upload_flatpak
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: zoitechat.flatpak
|
name: zoitechat.flatpak
|
||||||
path: zoitechat.flatpak
|
path: zoitechat.flatpak
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
HOMEBREW_NO_AUTO_UPDATE: '1'
|
HOMEBREW_NO_AUTO_UPDATE: '1'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Install native dependencies
|
- name: Install native dependencies
|
||||||
run: brew bundle --file packaging/macos/Brewfile
|
run: brew bundle --file packaging/macos/Brewfile
|
||||||
- name: Test packaging logic
|
- name: Test packaging logic
|
||||||
@@ -112,7 +112,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
HOMEBREW_NO_AUTO_UPDATE: '1'
|
HOMEBREW_NO_AUTO_UPDATE: '1'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Validate release settings before building
|
- name: Validate release settings before building
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: '3.14.3'
|
python-version: '3.14.3'
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: '3.8.10'
|
python-version: '3.8.10'
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
@@ -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: |
|
||||||
@@ -222,7 +226,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Installer
|
- name: Upload Installer
|
||||||
id: upload_installer
|
id: upload_installer
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Installer ${{ matrix.arch }}
|
name: Installer ${{ matrix.arch }}
|
||||||
path: ZoiteChat-*.exe
|
path: ZoiteChat-*.exe
|
||||||
@@ -236,7 +240,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Build Files
|
- name: Upload Build Files
|
||||||
id: upload_buildfiles
|
id: upload_buildfiles
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: Build Files ${{ matrix.arch }}
|
name: Build Files ${{ matrix.arch }}
|
||||||
path: zoitechat-build
|
path: zoitechat-build
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: '3.14.3'
|
python-version: '3.14.3'
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: '3.8.10'
|
python-version: '3.8.10'
|
||||||
architecture: ${{ matrix.arch }}
|
architecture: ${{ matrix.arch }}
|
||||||
@@ -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
|
||||||
@@ -399,7 +403,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload MSI Installer
|
- name: Upload MSI Installer
|
||||||
id: upload_msi
|
id: upload_msi
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: MSI Installer ${{ matrix.arch }}
|
name: MSI Installer ${{ matrix.arch }}
|
||||||
path: ${{ steps.build_msi.outputs.path }}
|
path: ${{ steps.build_msi.outputs.path }}
|
||||||
@@ -414,7 +418,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload MSIX Package
|
- name: Upload MSIX Package
|
||||||
id: upload_msix
|
id: upload_msix
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.build_msix.outputs.artifact_name }}
|
name: ${{ steps.build_msix.outputs.artifact_name }}
|
||||||
path: ${{ steps.build_msix.outputs.path }}
|
path: ${{ steps.build_msix.outputs.path }}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
+1
-1
Submodule flatpak/shared-modules updated: 55a86b3da4...a58e654b91
@@ -25,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
|
||||||
|
|||||||
@@ -49,6 +49,52 @@ sources and build them in a controlled prefix, or maintain pinned Homebrew formu
|
|||||||
|
|
||||||
## 2. Verify portability and behavior
|
## 2. Verify portability and behavior
|
||||||
|
|
||||||
|
### Keyboard shortcuts
|
||||||
|
|
||||||
|
These defaults apply only to macOS; Linux and Windows shortcuts are unchanged.
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
| --- | --- |
|
||||||
|
| Command+C / X / V | Copy / cut / paste in text fields, including search and preferences |
|
||||||
|
| Command+A | Select all text in the focused field |
|
||||||
|
| Command+Left / Right | Move to the beginning / end of the line |
|
||||||
|
| Option+Left / Right | Move by words |
|
||||||
|
| Command+Up / Down | Move to the beginning / end of the text |
|
||||||
|
| Shift with the movement shortcuts above | Extend the selection |
|
||||||
|
| Option+Delete (Backspace) | Delete back to the beginning of the word |
|
||||||
|
| Command+Delete (Backspace) | Delete back to the beginning of the line |
|
||||||
|
| Command+, | Open Preferences |
|
||||||
|
| Command+M | Minimize the active window |
|
||||||
|
| Control+Tab | Completion in the opposite direction; Tab / Shift+Tab still complete normally |
|
||||||
|
| Command+Option+Left / Right | Move the current tab left / right |
|
||||||
|
| Command+Shift+R | Reset the chat marker; Command+Shift+M still moves to the marker |
|
||||||
|
|
||||||
|
The inherited Control+C/X/V clipboard bindings are removed on macOS only.
|
||||||
|
Command+C/X/V in multiline editors use plain UTF-8 text directly, avoiding
|
||||||
|
GTK3 Quartz's incomplete rich-text negotiation and pasteboard types without a
|
||||||
|
preferred MIME name. Text, newlines, selection replacement and editability
|
||||||
|
checks are preserved; rich clipboard formatting is not imported by this action.
|
||||||
|
Command+C copies selected input text first; if no input text is selected, it
|
||||||
|
copies the selected chat text. Command+X cuts editable text, never messages
|
||||||
|
from chat history. Password protection and read-only fields retain GTK's
|
||||||
|
existing behavior. Undo/redo is not added: GTK3's entry widget needs a separate
|
||||||
|
editing-history implementation before Command+Z can work reliably.
|
||||||
|
|
||||||
|
Exact old default assignments for Command+Tab, Option+Left/Right and Command+M
|
||||||
|
are remapped in memory when loading defaults or an existing configuration.
|
||||||
|
Other custom bindings are preserved. If a replacement key is already assigned,
|
||||||
|
both existing assignments are left alone; resolve that conflict in **Settings
|
||||||
|
→ Keyboard Shortcuts**. Saving there persists the updated assignments.
|
||||||
|
Command+Tab is left to macOS for switching applications. Plugins and custom
|
||||||
|
input bindings retain priority; the new native menu accelerators are omitted
|
||||||
|
when the same key is already assigned to an input action.
|
||||||
|
|
||||||
|
Before distributing a build, test the shortcuts in message input, search,
|
||||||
|
preferences, a detached chat window and a multiline editor, including Unicode,
|
||||||
|
selection replacement, and read-only/password fields. The Meson widget tests
|
||||||
|
exercise the GTK actions; the screenshot smoke check does not send real keys
|
||||||
|
through the macOS event loop.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 -m unittest discover -s packaging/macos/tests -v
|
python3 -m unittest discover -s packaging/macos/tests -v
|
||||||
python3 packaging/macos/bundle.py audit dist/ZoiteChat.app
|
python3 packaging/macos/bundle.py audit dist/ZoiteChat.app
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "fe-gtk.h"
|
#include "fe-gtk.h"
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include "../common/macos.h"
|
#include "../common/macos.h"
|
||||||
|
#include "macos-shortcuts.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef GDK_WINDOWING_WIN32
|
#ifdef GDK_WINDOWING_WIN32
|
||||||
@@ -58,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"
|
||||||
@@ -503,6 +505,8 @@ fe_args (int argc, char *argv[])
|
|||||||
gtk_init (&argc, &argv);
|
gtk_init (&argc, &argv);
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
macos_shortcuts_init (setup_open);
|
||||||
|
|
||||||
if (g_getenv ("ZOITECHAT_MACOS_SHARE"))
|
if (g_getenv ("ZOITECHAT_MACOS_SHARE"))
|
||||||
{
|
{
|
||||||
char *icons = g_build_filename (g_getenv ("ZOITECHAT_MACOS_SHARE"), "icons", NULL);
|
char *icons = g_build_filename (g_getenv ("ZOITECHAT_MACOS_SHARE"), "icons", NULL);
|
||||||
@@ -635,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
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -55,6 +55,9 @@
|
|||||||
#include "maingui.h"
|
#include "maingui.h"
|
||||||
#include "textgui.h"
|
#include "textgui.h"
|
||||||
#include "fkeys.h"
|
#include "fkeys.h"
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include "macos-shortcuts.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define ICON_FKEYS_NEW "document-new"
|
#define ICON_FKEYS_NEW "document-new"
|
||||||
#define ICON_FKEYS_DELETE "edit-delete"
|
#define ICON_FKEYS_DELETE "edit-delete"
|
||||||
@@ -570,6 +573,12 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess)
|
|||||||
list = g_slist_next (list);
|
list = g_slist_next (list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
/* Plugins and user-defined input shortcuts above retain priority. */
|
||||||
|
if (macos_shortcuts_copy_chat (wid, evt, GTK_XTEXT (sess->gui->xtext)))
|
||||||
|
return TRUE;
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (evt->keyval)
|
switch (evt->keyval)
|
||||||
{
|
{
|
||||||
case GDK_KEY_space:
|
case GDK_KEY_space:
|
||||||
@@ -1333,6 +1342,57 @@ key_save_kbs (void)
|
|||||||
#define KBSTATE_DT1 3
|
#define KBSTATE_DT1 3
|
||||||
#define KBSTATE_DT2 4
|
#define KBSTATE_DT2 4
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
static void
|
||||||
|
key_macos_adjust_defaults (GSList *bindings)
|
||||||
|
{
|
||||||
|
GSList *item, *other;
|
||||||
|
struct key_binding *kb, *existing;
|
||||||
|
guint keyval;
|
||||||
|
GdkModifierType mod;
|
||||||
|
|
||||||
|
for (item = bindings; item; item = item->next)
|
||||||
|
{
|
||||||
|
kb = item->data;
|
||||||
|
keyval = kb->keyval;
|
||||||
|
mod = kb->mod;
|
||||||
|
if (!macos_shortcuts_remap_default (&keyval, &mod,
|
||||||
|
key_actions[kb->action].name,
|
||||||
|
kb->data1, kb->data2))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Never let a migrated default shadow an existing user shortcut.
|
||||||
|
* In that case leave the old assignment for the user to resolve. */
|
||||||
|
for (other = bindings; other; other = other->next)
|
||||||
|
{
|
||||||
|
existing = other->data;
|
||||||
|
if (existing != kb &&
|
||||||
|
gdk_keyval_to_lower (existing->keyval) == gdk_keyval_to_lower (keyval) &&
|
||||||
|
existing->mod == mod)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!other)
|
||||||
|
{
|
||||||
|
kb->keyval = keyval;
|
||||||
|
kb->mod = mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
key_is_bound (guint keyval, GdkModifierType mod)
|
||||||
|
{
|
||||||
|
GSList *item;
|
||||||
|
for (item = keybind_list; item; item = item->next)
|
||||||
|
{
|
||||||
|
struct key_binding *kb = item->data;
|
||||||
|
if (kb->keyval == keyval && kb->mod == mod)
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#define STRIP_WHITESPACE \
|
#define STRIP_WHITESPACE \
|
||||||
while (buf[0] == ' ' || buf[0] == '\t') \
|
while (buf[0] == ' ' || buf[0] == '\t') \
|
||||||
buf++; \
|
buf++; \
|
||||||
@@ -1519,6 +1579,9 @@ key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_free (ibuf);
|
g_free (ibuf);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
key_macos_adjust_defaults (list);
|
||||||
|
#endif
|
||||||
*out_list = list;
|
*out_list = list;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
@@ -36,5 +36,8 @@ int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
|
|||||||
session *sess);
|
session *sess);
|
||||||
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
void key_check_replace_on_change (GtkEditable *editable, gpointer data);
|
||||||
gboolean key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod);
|
gboolean key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
gboolean key_is_bound (guint keyval, GdkModifierType mod);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
/* macOS text editing shortcuts. GPL-2.0-or-later; see COPYING. */
|
||||||
|
#include "macos-shortcuts.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "../common/zoitechat.h"
|
||||||
|
#include "xtext.h"
|
||||||
|
#include <gdk/gdkkeysyms.h>
|
||||||
|
|
||||||
|
static void (*preferences_callback) (void);
|
||||||
|
|
||||||
|
static void
|
||||||
|
copy_text (GtkTextView *view)
|
||||||
|
{
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
|
||||||
|
GtkTextIter start, end;
|
||||||
|
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
|
||||||
|
{
|
||||||
|
gchar *text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
||||||
|
gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (view),
|
||||||
|
GDK_SELECTION_CLIPBOARD), text, -1);
|
||||||
|
g_free (text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cut_text (GtkTextView *view)
|
||||||
|
{
|
||||||
|
copy_text (view);
|
||||||
|
gtk_text_buffer_delete_selection (gtk_text_view_get_buffer (view), TRUE,
|
||||||
|
gtk_text_view_get_editable (view));
|
||||||
|
gtk_text_view_reset_im_context (view);
|
||||||
|
gtk_text_view_scroll_mark_onscreen (view,
|
||||||
|
gtk_text_buffer_get_insert (gtk_text_view_get_buffer (view)));
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GtkTextView *view;
|
||||||
|
GtkTextBuffer *buffer;
|
||||||
|
} TextPaste;
|
||||||
|
|
||||||
|
static void
|
||||||
|
text_paste_received (GtkClipboard *clipboard, const gchar *text, gpointer userdata)
|
||||||
|
{
|
||||||
|
TextPaste *paste = userdata;
|
||||||
|
GtkTextView *view = paste->view;
|
||||||
|
GtkTextBuffer *buffer = paste->buffer;
|
||||||
|
|
||||||
|
/* Clipboard replies may arrive after a view was closed or switched to
|
||||||
|
* another buffer. Retain the request's objects, but never edit a new one. */
|
||||||
|
if (text && gtk_widget_get_realized (GTK_WIDGET (view)) &&
|
||||||
|
gtk_text_view_get_buffer (view) == buffer)
|
||||||
|
{
|
||||||
|
gboolean editable = gtk_text_view_get_editable (view);
|
||||||
|
gtk_text_buffer_begin_user_action (buffer);
|
||||||
|
gtk_text_buffer_delete_selection (buffer, TRUE, editable);
|
||||||
|
gtk_text_buffer_insert_interactive_at_cursor (buffer, text, -1, editable);
|
||||||
|
gtk_text_buffer_end_user_action (buffer);
|
||||||
|
gtk_text_view_reset_im_context (view);
|
||||||
|
gtk_text_view_scroll_mark_onscreen (view, gtk_text_buffer_get_insert (buffer));
|
||||||
|
g_signal_emit_by_name (buffer, "paste-done", clipboard);
|
||||||
|
}
|
||||||
|
g_object_unref (buffer);
|
||||||
|
g_object_unref (view);
|
||||||
|
g_free (paste);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
paste_text (GtkTextView *view)
|
||||||
|
{
|
||||||
|
TextPaste *paste = g_new (TextPaste, 1);
|
||||||
|
paste->view = g_object_ref (view);
|
||||||
|
paste->buffer = g_object_ref (gtk_text_view_get_buffer (view));
|
||||||
|
/* ZoiteChat's editors consume text. GTK3's Quartz rich-text fallback
|
||||||
|
* enumerates pasteboard UTIs without MIME names (a NULL atom assertion)
|
||||||
|
* and its rich-text request callback is unimplemented. Request UTF-8
|
||||||
|
* directly, retaining interactive selection and read-only behavior. */
|
||||||
|
gtk_clipboard_request_text (gtk_widget_get_clipboard (GTK_WIDGET (view),
|
||||||
|
GDK_SELECTION_CLIPBOARD), text_paste_received, paste);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
open_preferences (GtkWindow *window)
|
||||||
|
{
|
||||||
|
if (preferences_callback)
|
||||||
|
preferences_callback ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
minimize_window (GtkWindow *window)
|
||||||
|
{
|
||||||
|
gtk_window_iconify (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
macos_shortcuts_minimize_active (GtkWindow *fallback)
|
||||||
|
{
|
||||||
|
GList *windows = gtk_window_list_toplevels ();
|
||||||
|
GList *item;
|
||||||
|
GtkWindow *target = fallback;
|
||||||
|
|
||||||
|
for (item = windows; item; item = item->next)
|
||||||
|
{
|
||||||
|
if (gtk_window_is_active (GTK_WINDOW (item->data)))
|
||||||
|
{
|
||||||
|
target = GTK_WINDOW (item->data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target)
|
||||||
|
minimize_window (target);
|
||||||
|
g_list_free (windows);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_movement (GtkBindingSet *bindings, guint keyval, GdkModifierType mod,
|
||||||
|
GtkMovementStep step, gint count)
|
||||||
|
{
|
||||||
|
gtk_binding_entry_add_signal (bindings, keyval, mod, "move-cursor", 3,
|
||||||
|
GTK_TYPE_MOVEMENT_STEP, step,
|
||||||
|
G_TYPE_INT, count, G_TYPE_BOOLEAN, FALSE);
|
||||||
|
gtk_binding_entry_add_signal (bindings, keyval, mod | GDK_SHIFT_MASK,
|
||||||
|
"move-cursor", 3, GTK_TYPE_MOVEMENT_STEP, step,
|
||||||
|
G_TYPE_INT, count, G_TYPE_BOOLEAN, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delete_text_view_backwards (GtkTextView *view, gboolean word)
|
||||||
|
{
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gboolean editable = gtk_text_view_get_editable (view);
|
||||||
|
|
||||||
|
/* GTK3 leaves GTK_DELETE_DISPLAY_LINE_ENDS unimplemented in TextView.
|
||||||
|
* Preserve selection deletion and per-range editability instead of
|
||||||
|
* moving the cursor first or deleting protected text unconditionally. */
|
||||||
|
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
|
||||||
|
gtk_text_buffer_delete_selection (buffer, TRUE, editable);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_text_buffer_get_iter_at_mark (buffer, &end, gtk_text_buffer_get_insert (buffer));
|
||||||
|
start = end;
|
||||||
|
if (word)
|
||||||
|
gtk_text_iter_backward_word_start (&start);
|
||||||
|
else
|
||||||
|
gtk_text_view_backward_display_line_start (view, &start);
|
||||||
|
gtk_text_buffer_begin_user_action (buffer);
|
||||||
|
gtk_text_buffer_delete_interactive (buffer, &start, &end, editable);
|
||||||
|
gtk_text_buffer_end_user_action (buffer);
|
||||||
|
}
|
||||||
|
gtk_text_view_reset_im_context (view);
|
||||||
|
gtk_text_view_scroll_mark_onscreen (view, gtk_text_buffer_get_insert (buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delete_display_line_start (GtkTextView *view)
|
||||||
|
{
|
||||||
|
delete_text_view_backwards (view, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
delete_word_start (GtkTextView *view)
|
||||||
|
{
|
||||||
|
delete_text_view_backwards (view, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
install_text_bindings (GtkSettings *settings, GParamSpec *property,
|
||||||
|
GtkCssProvider *provider)
|
||||||
|
{
|
||||||
|
GtkWidget *widgets[2];
|
||||||
|
const char *selectors[] = {"entry", "textview"};
|
||||||
|
const char *names[] = {"zoitechat-macos-entry", "zoitechat-macos-textview"};
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GString *css = g_string_new (NULL);
|
||||||
|
guint i, j;
|
||||||
|
|
||||||
|
/* CSS key themes take precedence over class bindings. Put our small
|
||||||
|
* sets first, retaining the theme's remaining bindings as fallbacks.
|
||||||
|
* Remove our previous provider before reading a changed key theme. */
|
||||||
|
g_object_ref (provider);
|
||||||
|
gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider));
|
||||||
|
widgets[0] = gtk_entry_new ();
|
||||||
|
widgets[1] = gtk_text_view_new ();
|
||||||
|
for (i = 0; i < G_N_ELEMENTS (widgets); i++)
|
||||||
|
{
|
||||||
|
GPtrArray *fallbacks = NULL;
|
||||||
|
GtkStyleContext *context = gtk_widget_get_style_context (widgets[i]);
|
||||||
|
|
||||||
|
g_object_ref_sink (widgets[i]);
|
||||||
|
gtk_style_context_get (context, gtk_style_context_get_state (context),
|
||||||
|
"-gtk-key-bindings", &fallbacks, NULL);
|
||||||
|
g_string_append_printf (css, "%s { -gtk-key-bindings: %s", selectors[i], names[i]);
|
||||||
|
for (j = 0; fallbacks && j < fallbacks->len; j++)
|
||||||
|
{
|
||||||
|
GtkBindingSet *binding = g_ptr_array_index (fallbacks, j);
|
||||||
|
g_string_append_printf (css, ", %s", binding->set_name);
|
||||||
|
}
|
||||||
|
g_string_append (css, "; }\n");
|
||||||
|
if (fallbacks)
|
||||||
|
g_ptr_array_unref (fallbacks);
|
||||||
|
gtk_widget_destroy (widgets[i]);
|
||||||
|
g_object_unref (widgets[i]);
|
||||||
|
}
|
||||||
|
gtk_css_provider_load_from_data (provider, css->str, css->len, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider),
|
||||||
|
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
g_string_free (css, TRUE);
|
||||||
|
g_object_unref (provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
macos_shortcuts_init (void (*callback) (void))
|
||||||
|
{
|
||||||
|
GType types[] = {GTK_TYPE_ENTRY, GTK_TYPE_TEXT_VIEW};
|
||||||
|
const char *names[] = {"zoitechat-macos-entry", "zoitechat-macos-textview"};
|
||||||
|
GtkCssProvider *provider;
|
||||||
|
gpointer window_class;
|
||||||
|
GtkBindingSet *window_bindings;
|
||||||
|
static gboolean initialized = FALSE;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
preferences_callback = callback;
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
initialized = TRUE;
|
||||||
|
|
||||||
|
/* Use Command for these three actions on macOS, preserving GTK's
|
||||||
|
* selection, password-field and read-only checks. Entry subclasses
|
||||||
|
* (including the spell-checking input and search fields) inherit these. */
|
||||||
|
for (i = 0; i < G_N_ELEMENTS (types); i++)
|
||||||
|
{
|
||||||
|
gpointer klass = g_type_class_ref (types[i]);
|
||||||
|
GtkBindingSet *bindings = gtk_binding_set_new (names[i]);
|
||||||
|
|
||||||
|
gtk_binding_entry_skip (bindings, GDK_KEY_c, GDK_CONTROL_MASK);
|
||||||
|
gtk_binding_entry_skip (bindings, GDK_KEY_x, GDK_CONTROL_MASK);
|
||||||
|
gtk_binding_entry_skip (bindings, GDK_KEY_v, GDK_CONTROL_MASK);
|
||||||
|
if (types[i] == GTK_TYPE_TEXT_VIEW)
|
||||||
|
{
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-copy-text", types[i],
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (copy_text), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-cut-text", types[i],
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (cut_text), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-paste-text", types[i],
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (paste_text), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_v, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-paste-text", 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_c, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-copy-text", 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_x, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-cut-text", 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_c, GDK_META_MASK,
|
||||||
|
"copy-clipboard", 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_x, GDK_META_MASK,
|
||||||
|
"cut-clipboard", 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_v, GDK_META_MASK,
|
||||||
|
"paste-clipboard", 0);
|
||||||
|
}
|
||||||
|
/* Two movements select the whole buffer without changing its text. */
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_a, GDK_META_MASK,
|
||||||
|
"move-cursor", 3, GTK_TYPE_MOVEMENT_STEP,
|
||||||
|
GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, -1,
|
||||||
|
G_TYPE_BOOLEAN, FALSE);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_a, GDK_META_MASK,
|
||||||
|
"move-cursor", 3, GTK_TYPE_MOVEMENT_STEP,
|
||||||
|
GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, 1,
|
||||||
|
G_TYPE_BOOLEAN, TRUE);
|
||||||
|
add_movement (bindings, GDK_KEY_Left, GDK_META_MASK,
|
||||||
|
GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
|
||||||
|
add_movement (bindings, GDK_KEY_Right, GDK_META_MASK,
|
||||||
|
GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
|
||||||
|
add_movement (bindings, GDK_KEY_Left, GDK_MOD1_MASK,
|
||||||
|
GTK_MOVEMENT_WORDS, -1);
|
||||||
|
add_movement (bindings, GDK_KEY_Right, GDK_MOD1_MASK,
|
||||||
|
GTK_MOVEMENT_WORDS, 1);
|
||||||
|
add_movement (bindings, GDK_KEY_Up, GDK_META_MASK,
|
||||||
|
GTK_MOVEMENT_BUFFER_ENDS, -1);
|
||||||
|
add_movement (bindings, GDK_KEY_Down, GDK_META_MASK,
|
||||||
|
GTK_MOVEMENT_BUFFER_ENDS, 1);
|
||||||
|
if (types[i] == GTK_TYPE_TEXT_VIEW)
|
||||||
|
{
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-delete-word-start", types[i],
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (delete_word_start),
|
||||||
|
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-delete-line-start", types[i],
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (delete_display_line_start),
|
||||||
|
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_BackSpace, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-delete-line-start", 0);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_BackSpace, GDK_MOD1_MASK,
|
||||||
|
"zoitechat-macos-delete-word-start", 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_BackSpace, GDK_MOD1_MASK,
|
||||||
|
"delete-from-cursor", 2, GTK_TYPE_DELETE_TYPE,
|
||||||
|
GTK_DELETE_WORD_ENDS, G_TYPE_INT, -1);
|
||||||
|
gtk_binding_entry_add_signal (bindings, GDK_KEY_BackSpace, GDK_META_MASK,
|
||||||
|
"delete-from-cursor", 2, GTK_TYPE_DELETE_TYPE,
|
||||||
|
GTK_DELETE_DISPLAY_LINE_ENDS, G_TYPE_INT, -1);
|
||||||
|
}
|
||||||
|
g_type_class_unref (klass);
|
||||||
|
}
|
||||||
|
provider = gtk_css_provider_new ();
|
||||||
|
install_text_bindings (gtk_settings_get_default (), NULL, provider);
|
||||||
|
g_signal_connect_object (gtk_settings_get_default (), "notify::gtk-key-theme-name",
|
||||||
|
G_CALLBACK (install_text_bindings), provider, 0);
|
||||||
|
g_signal_connect_object (gtk_settings_get_default (), "notify::gtk-theme-name",
|
||||||
|
G_CALLBACK (install_text_bindings), provider, 0);
|
||||||
|
g_object_unref (provider);
|
||||||
|
|
||||||
|
/* Window-level bindings also work in dialogs without a main menu bar.
|
||||||
|
* The native menu uses the same actions when Quartz handles the key. */
|
||||||
|
window_class = g_type_class_ref (GTK_TYPE_WINDOW);
|
||||||
|
window_bindings = gtk_binding_set_by_class (window_class);
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-minimize", GTK_TYPE_WINDOW,
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (minimize_window), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
g_signal_new_class_handler ("zoitechat-macos-preferences", GTK_TYPE_WINDOW,
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
|
G_CALLBACK (open_preferences), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
gtk_binding_entry_add_signal (window_bindings, GDK_KEY_m, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-minimize", 0);
|
||||||
|
gtk_binding_entry_add_signal (window_bindings, GDK_KEY_comma, GDK_META_MASK,
|
||||||
|
"zoitechat-macos-preferences", 0);
|
||||||
|
g_type_class_unref (window_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
macos_shortcuts_remap_default (guint *keyval, GdkModifierType *mod,
|
||||||
|
const char *action, const char *data1,
|
||||||
|
const char *data2)
|
||||||
|
{
|
||||||
|
/* Only exact legacy defaults qualify. Custom actions, parameters and
|
||||||
|
* alternate shortcuts must survive loading a configuration unchanged. */
|
||||||
|
if (data2 && *data2)
|
||||||
|
return FALSE;
|
||||||
|
if (*keyval == GDK_KEY_Tab && *mod == GDK_META_MASK &&
|
||||||
|
!g_strcmp0 (action, "Complete nick/command") && !g_strcmp0 (data1, "Up"))
|
||||||
|
{
|
||||||
|
*mod = GDK_CONTROL_MASK;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if ((!data1 || !*data1) && *mod == GDK_MOD1_MASK &&
|
||||||
|
((*keyval == GDK_KEY_Left && !g_strcmp0 (action, "Move front tab left")) ||
|
||||||
|
(*keyval == GDK_KEY_Right && !g_strcmp0 (action, "Move front tab right"))))
|
||||||
|
{
|
||||||
|
*mod |= GDK_META_MASK;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (*keyval == GDK_KEY_m && *mod == GDK_META_MASK &&
|
||||||
|
!g_strcmp0 (action, "Menu Shortcut") && !g_strcmp0 (data1, "reset-marker"))
|
||||||
|
{
|
||||||
|
*keyval = GDK_KEY_R;
|
||||||
|
*mod |= GDK_SHIFT_MASK;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
macos_shortcuts_copy_chat (GtkWidget *entry, GdkEventKey *event, GtkXText *xtext)
|
||||||
|
{
|
||||||
|
gint start, end;
|
||||||
|
GdkModifierType modifiers = event->state &
|
||||||
|
(GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK |
|
||||||
|
GDK_META_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK);
|
||||||
|
|
||||||
|
/* XText leaves keyboard focus in the message input. Prefer an input
|
||||||
|
* selection; otherwise Command+C copies the selected chat text. Leave
|
||||||
|
* Command+Shift+C and all other shortcuts to their existing handlers. */
|
||||||
|
if (gdk_keyval_to_lower (event->keyval) != GDK_KEY_c || modifiers != GDK_META_MASK ||
|
||||||
|
gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gtk_xtext_copy_selection (xtext);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* macOS text editing shortcuts. GPL-2.0-or-later; see COPYING. */
|
||||||
|
#ifndef ZOITECHAT_MACOS_SHORTCUTS_H
|
||||||
|
#define ZOITECHAT_MACOS_SHORTCUTS_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
typedef struct _GtkXText GtkXText;
|
||||||
|
|
||||||
|
void macos_shortcuts_init (void (*open_preferences) (void));
|
||||||
|
void macos_shortcuts_minimize_active (GtkWindow *fallback);
|
||||||
|
gboolean macos_shortcuts_remap_default (guint *keyval, GdkModifierType *mod,
|
||||||
|
const char *action, const char *data1,
|
||||||
|
const char *data2);
|
||||||
|
gboolean macos_shortcuts_copy_chat (GtkWidget *entry, GdkEventKey *event,
|
||||||
|
GtkXText *xtext);
|
||||||
|
|
||||||
|
#endif
|
||||||
+59
-6
@@ -51,6 +51,9 @@
|
|||||||
#include "chanlist.h"
|
#include "chanlist.h"
|
||||||
#include "editlist.h"
|
#include "editlist.h"
|
||||||
#include "fkeys.h"
|
#include "fkeys.h"
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include "macos-shortcuts.h"
|
||||||
|
#endif
|
||||||
#include "gtkutil.h"
|
#include "gtkutil.h"
|
||||||
#include "maingui.h"
|
#include "maingui.h"
|
||||||
#include "notifygui.h"
|
#include "notifygui.h"
|
||||||
@@ -1322,6 +1325,17 @@ menu_settings (GtkWidget * wid, gpointer none)
|
|||||||
setup_open ();
|
setup_open ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
static void
|
||||||
|
menu_macos_minimize (GtkWidget *widget, gpointer userdata)
|
||||||
|
{
|
||||||
|
GtkWindow *fallback = NULL;
|
||||||
|
if (current_sess && current_sess->gui && current_sess->gui->window)
|
||||||
|
fallback = GTK_WINDOW (current_sess->gui->window);
|
||||||
|
macos_shortcuts_minimize_active (fallback);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_usermenu (void)
|
menu_usermenu (void)
|
||||||
{
|
{
|
||||||
@@ -2069,7 +2083,7 @@ static struct mymenu mymenu[] = {
|
|||||||
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
|
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
|
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||||
#define SEARCH_OFFSET (70)
|
#define SEARCH_OFFSET (71)
|
||||||
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
|
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||||
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
|
||||||
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
|
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
|
||||||
@@ -2106,11 +2120,11 @@ menu_get_key_action_name (int index)
|
|||||||
return "fullscreen-toggle";
|
return "fullscreen-toggle";
|
||||||
case AWAY_OFFSET:
|
case AWAY_OFFSET:
|
||||||
return "away-toggle";
|
return "away-toggle";
|
||||||
case 65:
|
|
||||||
return "reset-marker";
|
|
||||||
case 66:
|
case 66:
|
||||||
return "move-marker";
|
return "reset-marker";
|
||||||
case 67:
|
case 67:
|
||||||
|
return "move-marker";
|
||||||
|
case 68:
|
||||||
return "copy-selection";
|
return "copy-selection";
|
||||||
case SEARCH_OFFSET + 1:
|
case SEARCH_OFFSET + 1:
|
||||||
return "search-text";
|
return "search-text";
|
||||||
@@ -2118,7 +2132,7 @@ menu_get_key_action_name (int index)
|
|||||||
return "search-next";
|
return "search-next";
|
||||||
case SEARCH_OFFSET + 3:
|
case SEARCH_OFFSET + 3:
|
||||||
return "search-previous";
|
return "search-previous";
|
||||||
case 75:
|
case 77:
|
||||||
return "contents";
|
return "contents";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2142,6 +2156,20 @@ menu_add_keybinding_accel (GtkWidget *item, GtkAccelGroup *accel_group, const ch
|
|||||||
g_object_set_data (G_OBJECT (item), "zc-key-accel-mod", GUINT_TO_POINTER (mod));
|
g_object_set_data (G_OBJECT (item), "zc-key-accel-mod", GUINT_TO_POINTER (mod));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
static void
|
||||||
|
menu_add_macos_accel (GtkWidget *item, GtkAccelGroup *accel_group, guint keyval)
|
||||||
|
{
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-macos-default-key", GUINT_TO_POINTER (keyval));
|
||||||
|
if (!accel_group || key_is_bound (keyval, GDK_META_MASK))
|
||||||
|
return;
|
||||||
|
gtk_widget_add_accelerator (item, "activate", accel_group, keyval,
|
||||||
|
GDK_META_MASK, GTK_ACCEL_VISIBLE);
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-accel-key", GUINT_TO_POINTER (keyval));
|
||||||
|
g_object_set_data (G_OBJECT (item), "zc-key-accel-mod", GUINT_TO_POINTER (GDK_META_MASK));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void
|
static void
|
||||||
menu_refresh_keybinding_accels (GtkWidget *widget, gpointer data)
|
menu_refresh_keybinding_accels (GtkWidget *widget, gpointer data)
|
||||||
{
|
{
|
||||||
@@ -2164,6 +2192,12 @@ menu_refresh_keybinding_accels (GtkWidget *widget, gpointer data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
name = g_object_get_data (G_OBJECT (widget), "zc-key-action");
|
name = g_object_get_data (G_OBJECT (widget), "zc-key-action");
|
||||||
|
#ifdef __APPLE__
|
||||||
|
keyval = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "zc-macos-default-key"));
|
||||||
|
if (keyval)
|
||||||
|
menu_add_macos_accel (widget, accel_group, keyval);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
menu_add_keybinding_accel (widget, accel_group, name);
|
menu_add_keybinding_accel (widget, accel_group, name);
|
||||||
|
|
||||||
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
|
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
|
||||||
@@ -2217,7 +2251,12 @@ menu_key_action (const char *name, guint keyval, GdkModifierType state)
|
|||||||
menu_close (NULL, NULL);
|
menu_close (NULL, NULL);
|
||||||
else if (!strcmp (name, "quit"))
|
else if (!strcmp (name, "quit"))
|
||||||
{
|
{
|
||||||
if (!prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q && (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == STATE_CTRL)
|
if (!prefs.hex_gui_ctrlq_quit && keyval == GDK_KEY_q &&
|
||||||
|
(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK
|
||||||
|
#ifdef __APPLE__
|
||||||
|
| GDK_META_MASK
|
||||||
|
#endif
|
||||||
|
)) == STATE_CTRL)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
menu_quit (NULL, NULL);
|
menu_quit (NULL, NULL);
|
||||||
}
|
}
|
||||||
@@ -2841,6 +2880,16 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
#endif
|
#endif
|
||||||
gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item);
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item);
|
||||||
gtk_widget_show (menu_item);
|
gtk_widget_show (menu_item);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (bar && !strcmp (mymenu[i].text, "_Window"))
|
||||||
|
{
|
||||||
|
GtkWidget *minimize = gtk_menu_item_new_with_mnemonic (_("_Minimize"));
|
||||||
|
menu_add_macos_accel (minimize, accel_group, GDK_KEY_m);
|
||||||
|
g_signal_connect (minimize, "activate", G_CALLBACK (menu_macos_minimize), NULL);
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), minimize);
|
||||||
|
gtk_widget_show (minimize);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case M_MENUPIX:
|
case M_MENUPIX:
|
||||||
@@ -2854,6 +2903,10 @@ menu_create_main (void *accel_group, int bar, int away, int toplevel,
|
|||||||
case M_MENUITEM:
|
case M_MENUITEM:
|
||||||
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text));
|
||||||
normalitem:
|
normalitem:
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (bar && mymenu[i].callback == menu_settings)
|
||||||
|
menu_add_macos_accel (item, accel_group, GDK_KEY_comma);
|
||||||
|
#endif
|
||||||
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
g_object_set_data (G_OBJECT (item), "zc-key-action", (gpointer) menu_get_key_action_name (i));
|
||||||
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
menu_add_keybinding_accel (item, accel_group, menu_get_key_action_name (i));
|
||||||
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
if (mymenu[i].key != 0 && !(mymenu[i].id == MENU_ID_QUIT && !prefs.hex_gui_ctrlq_quit))
|
||||||
|
|||||||
+24
-2
@@ -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,12 +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 += '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'
|
||||||
@@ -296,8 +310,16 @@ test('Emoji Font Tests', emoji_font_tests,
|
|||||||
)
|
)
|
||||||
|
|
||||||
xtext_tests = executable('xtext_tests',
|
xtext_tests = executable('xtext_tests',
|
||||||
['tests/test-xtext.c', '../common/hexcolor.c'],
|
['tests/test-xtext.c', 'macos-shortcuts.c', '../common/hexcolor.c'],
|
||||||
include_directories: [config_h_include],
|
include_directories: [config_h_include],
|
||||||
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)
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|
||||||
@@ -567,7 +571,11 @@ static const setting general_settings[] =
|
|||||||
{ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
|
{ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
|
||||||
{ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
|
{ST_TOGGLE, N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
|
||||||
{ST_TOGGLE, N_("Hide hostmasks in join and part messages"), P_OFFINTNL(hex_irc_hide_join_part_hostmask), 0, 0, 0},
|
{ST_TOGGLE, N_("Hide hostmasks in join and part messages"), P_OFFINTNL(hex_irc_hide_join_part_hostmask), 0, 0, 0},
|
||||||
|
#ifdef __APPLE__
|
||||||
|
{ST_TOGGLE, N_("Enable Command+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
|
||||||
|
#else
|
||||||
{ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
|
{ST_TOGGLE, N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},
|
||||||
|
#endif
|
||||||
|
|
||||||
{ST_END, 0, 0, 0, 0, 0}
|
{ST_END, 0, 0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,5 +23,6 @@
|
|||||||
#include "theme/theme-manager.h"
|
#include "theme/theme-manager.h"
|
||||||
|
|
||||||
void setup_apply_real (const ThemeChangedEvent *event);
|
void setup_apply_real (const ThemeChangedEvent *event);
|
||||||
|
void setup_open (void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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 ();
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
* adding test-only symbols to the widget's public API. Application services
|
* adding test-only symbols to the widget's public API. Application services
|
||||||
* are stubbed; GTK, Pango, Cairo and the widget itself are exercised normally. */
|
* are stubbed; GTK, Pango, Cairo and the widget itself are exercised normally. */
|
||||||
#include "../xtext.c"
|
#include "../xtext.c"
|
||||||
|
#include "../macos-shortcuts.h"
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
struct zoitechatprefs prefs;
|
struct zoitechatprefs prefs;
|
||||||
@@ -252,6 +253,470 @@ static void test_selection_and_clipboard (Fixture *f, gconstpointer data)
|
|||||||
g_assert_false (gtk_xtext_is_selecting (f->xtext));
|
g_assert_false (gtk_xtext_is_selecting (f->xtext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void assert_clipboard (GtkWidget *widget, const char *expected)
|
||||||
|
{
|
||||||
|
char *text = gtk_clipboard_wait_for_text (
|
||||||
|
gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD));
|
||||||
|
g_assert_cmpstr (text, ==, expected);
|
||||||
|
g_free (text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkWidget *shortcut_window (GtkWidget *widget)
|
||||||
|
{
|
||||||
|
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
/* Give mapped editor fixtures a stable allocation before the first draw. */
|
||||||
|
gtk_window_set_default_size (GTK_WINDOW (window), 640, 240);
|
||||||
|
gtk_container_set_border_width (GTK_CONTAINER (window), 16);
|
||||||
|
if (GTK_IS_ENTRY (widget))
|
||||||
|
{
|
||||||
|
gtk_widget_set_size_request (widget, 320, 40);
|
||||||
|
gtk_widget_set_valign (widget, GTK_ALIGN_CENTER);
|
||||||
|
}
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), widget);
|
||||||
|
gtk_widget_show_all (window);
|
||||||
|
drain_events ();
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_entry_shortcuts (gconstpointer data)
|
||||||
|
{
|
||||||
|
gboolean search = GPOINTER_TO_INT (data);
|
||||||
|
GtkWidget *entry = g_object_ref_sink (search ? gtk_search_entry_new () : gtk_entry_new ());
|
||||||
|
GtkWidget *window;
|
||||||
|
GtkClipboard *clipboard;
|
||||||
|
gint64 deadline;
|
||||||
|
|
||||||
|
if (search)
|
||||||
|
{
|
||||||
|
/* Inheritance and clipboard actions do not require drawing. Quartz
|
||||||
|
* SearchEntry icon rendering can emit an unrelated fatal warning.
|
||||||
|
* Keep this variant attached to a screen but unmapped; the ordinary
|
||||||
|
* Entry variant below still exercises a mapped, editable widget. */
|
||||||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
gtk_container_add (GTK_CONTAINER (window), entry);
|
||||||
|
g_assert_true (gtk_widget_has_screen (entry));
|
||||||
|
g_assert_false (gtk_widget_get_mapped (entry));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window = shortcut_window (entry);
|
||||||
|
g_assert_true (gtk_widget_get_mapped (entry));
|
||||||
|
}
|
||||||
|
clipboard = gtk_widget_get_clipboard (entry, GDK_SELECTION_CLIPBOARD);
|
||||||
|
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (entry), "hello 猫");
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (entry), 6, 7);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_c, GDK_META_MASK));
|
||||||
|
assert_clipboard (entry, "猫");
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello 猫");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_x, GDK_META_MASK));
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello ");
|
||||||
|
assert_clipboard (entry, "猫");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_v, GDK_META_MASK));
|
||||||
|
deadline = g_get_monotonic_time () + G_TIME_SPAN_SECOND * 2;
|
||||||
|
while (strcmp (gtk_entry_get_text (GTK_ENTRY (entry)), "hello 猫") &&
|
||||||
|
g_get_monotonic_time () < deadline)
|
||||||
|
{
|
||||||
|
drain_events ();
|
||||||
|
g_usleep (1000);
|
||||||
|
}
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello 猫");
|
||||||
|
|
||||||
|
/* The old Control actions must no longer perform clipboard editing. */
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
|
||||||
|
gtk_clipboard_set_text (clipboard, "sentinel", -1);
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_c, GDK_CONTROL_MASK));
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_x, GDK_CONTROL_MASK));
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_v, GDK_CONTROL_MASK));
|
||||||
|
assert_clipboard (entry, "sentinel");
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello 猫");
|
||||||
|
|
||||||
|
/* Password fields are ordinary entries, not icon-bearing search fields. */
|
||||||
|
if (!search)
|
||||||
|
{
|
||||||
|
gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_c, GDK_META_MASK));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_x, GDK_META_MASK));
|
||||||
|
assert_clipboard (entry, "sentinel");
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello 猫");
|
||||||
|
gtk_entry_set_visibility (GTK_ENTRY (entry), TRUE);
|
||||||
|
}
|
||||||
|
gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_x, GDK_META_MASK));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_v, GDK_META_MASK));
|
||||||
|
drain_events ();
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "hello 猫");
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
g_object_unref (entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unexpected_textview_clipboard_action (GtkTextView *view, gpointer data)
|
||||||
|
{
|
||||||
|
guint *count = data;
|
||||||
|
GSignalInvocationHint *hint = g_signal_get_invocation_hint (view);
|
||||||
|
(*count)++;
|
||||||
|
/* Fail on incorrect dispatch, before GTK's Quartz rich-text path runs. */
|
||||||
|
g_signal_stop_emission (view, hint->signal_id, hint->detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_textview_shortcuts (void)
|
||||||
|
{
|
||||||
|
GtkWidget *view = g_object_ref_sink (gtk_text_view_new ());
|
||||||
|
GtkWidget *window = shortcut_window (view);
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
gint64 deadline;
|
||||||
|
guint default_actions = 0;
|
||||||
|
|
||||||
|
g_signal_connect (view, "copy-clipboard", G_CALLBACK (unexpected_textview_clipboard_action), &default_actions);
|
||||||
|
g_signal_connect (view, "cut-clipboard", G_CALLBACK (unexpected_textview_clipboard_action), &default_actions);
|
||||||
|
g_signal_connect (view, "paste-clipboard", G_CALLBACK (unexpected_textview_clipboard_action), &default_actions);
|
||||||
|
|
||||||
|
gtk_text_buffer_set_text (buffer, "two\nlines", -1);
|
||||||
|
g_test_message ("Select multiline text");
|
||||||
|
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||||
|
gtk_text_buffer_select_range (buffer, &start, &end);
|
||||||
|
g_test_message ("Copy multiline text");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_c, GDK_META_MASK));
|
||||||
|
g_assert_cmpuint (default_actions, ==, 0);
|
||||||
|
assert_clipboard (view, "two\nlines");
|
||||||
|
g_test_message ("Cut multiline text");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_x, GDK_META_MASK));
|
||||||
|
g_assert_cmpuint (default_actions, ==, 0);
|
||||||
|
g_assert_cmpint (gtk_text_buffer_get_char_count (buffer), ==, 0);
|
||||||
|
assert_clipboard (view, "two\nlines");
|
||||||
|
g_test_message ("Paste multiline text");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_v, GDK_META_MASK));
|
||||||
|
g_assert_cmpuint (default_actions, ==, 0);
|
||||||
|
deadline = g_get_monotonic_time () + G_TIME_SPAN_SECOND * 2;
|
||||||
|
while (gtk_text_buffer_get_char_count (buffer) == 0 &&
|
||||||
|
g_get_monotonic_time () < deadline)
|
||||||
|
{
|
||||||
|
drain_events ();
|
||||||
|
g_usleep (1000);
|
||||||
|
}
|
||||||
|
g_assert_cmpint (gtk_text_buffer_get_char_count (buffer), ==, 9);
|
||||||
|
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||||
|
{
|
||||||
|
char *text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
||||||
|
g_assert_cmpstr (text, ==, "two\nlines");
|
||||||
|
g_free (text);
|
||||||
|
}
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_c, GDK_CONTROL_MASK));
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_x, GDK_CONTROL_MASK));
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_v, GDK_CONTROL_MASK));
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
g_object_unref (view);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_chat_copy (Fixture *f, gconstpointer data)
|
||||||
|
{
|
||||||
|
GtkWidget *entry = g_object_ref_sink (gtk_entry_new ());
|
||||||
|
GtkWidget *window = shortcut_window (entry);
|
||||||
|
GdkEventKey event = {0};
|
||||||
|
|
||||||
|
append (f, "chat selection 猫");
|
||||||
|
select_entries (f, f->xtext->buffer->text_first, f->xtext->buffer->text_last);
|
||||||
|
event.keyval = GDK_KEY_C; /* Caps Lock must not disable chat copying. */
|
||||||
|
event.state = GDK_META_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK;
|
||||||
|
g_assert_true (macos_shortcuts_copy_chat (entry, &event, f->xtext));
|
||||||
|
assert_clipboard (entry, "chat selection 猫");
|
||||||
|
|
||||||
|
/* Selected input takes priority over an older selection in scrollback. */
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (entry), "input");
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
|
||||||
|
g_assert_false (macos_shortcuts_copy_chat (entry, &event, f->xtext));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_c, GDK_META_MASK));
|
||||||
|
assert_clipboard (entry, "input");
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (entry), 0, 0);
|
||||||
|
event.state = GDK_META_MASK | GDK_SHIFT_MASK;
|
||||||
|
g_assert_false (macos_shortcuts_copy_chat (entry, &event, f->xtext));
|
||||||
|
event.state = GDK_CONTROL_MASK;
|
||||||
|
g_assert_false (macos_shortcuts_copy_chat (entry, &event, f->xtext));
|
||||||
|
event.state = GDK_META_MASK;
|
||||||
|
event.keyval = GDK_KEY_x;
|
||||||
|
g_assert_false (macos_shortcuts_copy_chat (entry, &event, f->xtext));
|
||||||
|
assert_clipboard (entry, "input");
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
g_object_unref (entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assert_entry_selection (GtkWidget *entry, gint start, gint end)
|
||||||
|
{
|
||||||
|
gint actual_start, actual_end;
|
||||||
|
g_assert_true (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry),
|
||||||
|
&actual_start, &actual_end));
|
||||||
|
g_assert_cmpint (actual_start, ==, start);
|
||||||
|
g_assert_cmpint (actual_end, ==, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_key_theme_fallback (void)
|
||||||
|
{
|
||||||
|
GtkWidget *entry = gtk_entry_new ();
|
||||||
|
GtkWidget *window = shortcut_window (entry);
|
||||||
|
gint start, end;
|
||||||
|
|
||||||
|
/* A key-theme reload must keep our overrides first and retain the
|
||||||
|
* Mac theme's Emacs-style Control+A/E navigation for other keys. */
|
||||||
|
g_object_notify (G_OBJECT (gtk_settings_get_default ()), "gtk-key-theme-name");
|
||||||
|
g_object_notify (G_OBJECT (gtk_settings_get_default ()), "gtk-theme-name");
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (entry), "one two");
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), -1);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_a, GDK_CONTROL_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 0);
|
||||||
|
g_assert_false (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_e, GDK_CONTROL_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 7);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_a, GDK_META_MASK));
|
||||||
|
assert_entry_selection (entry, 0, 7);
|
||||||
|
g_assert_false (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_v, GDK_CONTROL_MASK));
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_entry_editing (void)
|
||||||
|
{
|
||||||
|
GtkWidget *entry = gtk_entry_new ();
|
||||||
|
GtkWidget *window = shortcut_window (entry);
|
||||||
|
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (entry), "one two three");
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 5);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_a, GDK_META_MASK));
|
||||||
|
assert_entry_selection (entry, 0, 13);
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 5);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Left, GDK_META_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 0);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Right, GDK_META_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 13);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Left, GDK_META_MASK | GDK_SHIFT_MASK));
|
||||||
|
assert_entry_selection (entry, 0, 13);
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 13);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Left, GDK_MOD1_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 8);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Left, GDK_MOD1_MASK | GDK_SHIFT_MASK));
|
||||||
|
assert_entry_selection (entry, 4, 8);
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 0);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Right, GDK_MOD1_MASK));
|
||||||
|
g_assert_cmpint (gtk_editable_get_position (GTK_EDITABLE (entry)), ==, 3);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_Right, GDK_MOD1_MASK | GDK_SHIFT_MASK));
|
||||||
|
assert_entry_selection (entry, 3, 7);
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 13);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_BackSpace, GDK_MOD1_MASK));
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "one two ");
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (entry), "猫 abc def");
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), 6);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "def");
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (entry), 0, 2);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "f");
|
||||||
|
gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
|
||||||
|
gtk_editable_set_position (GTK_EDITABLE (entry), -1);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_BackSpace, GDK_MOD1_MASK));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (entry), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "f");
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assert_buffer_text (GtkTextBuffer *buffer, const char *expected)
|
||||||
|
{
|
||||||
|
GtkTextIter start, end;
|
||||||
|
char *text;
|
||||||
|
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||||
|
text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
||||||
|
g_assert_cmpstr (text, ==, expected);
|
||||||
|
g_free (text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void paste_completed (GtkTextBuffer *buffer, GtkClipboard *clipboard, guint *count)
|
||||||
|
{
|
||||||
|
(*count)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_paste (guint *count, guint expected)
|
||||||
|
{
|
||||||
|
gint64 deadline = g_get_monotonic_time () + 2 * G_TIME_SPAN_SECOND;
|
||||||
|
while (*count < expected && g_get_monotonic_time () < deadline)
|
||||||
|
{
|
||||||
|
drain_events ();
|
||||||
|
g_usleep (1000);
|
||||||
|
}
|
||||||
|
g_assert_cmpuint (*count, ==, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void text_with_native_type (GtkClipboard *clipboard, GtkSelectionData *selection,
|
||||||
|
guint info, gpointer data)
|
||||||
|
{
|
||||||
|
if (info == 0)
|
||||||
|
gtk_selection_data_set_text (selection, data, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_multiline_paste (void)
|
||||||
|
{
|
||||||
|
GtkWidget *view = gtk_text_view_new ();
|
||||||
|
GtkWidget *window = shortcut_window (view);
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
|
||||||
|
GtkClipboard *clipboard = gtk_widget_get_clipboard (view, GDK_SELECTION_CLIPBOARD);
|
||||||
|
GtkTextIter start, end;
|
||||||
|
guint pasted = 0;
|
||||||
|
GtkTargetEntry targets[] = {
|
||||||
|
{(gchar *) "UTF8_STRING", 0, 0},
|
||||||
|
/* This base UTI has no preferred MIME name on Quartz. A text paste
|
||||||
|
* must not enumerate/convert it through GTK's rich-text fallback. */
|
||||||
|
{(gchar *) "public.item", 0, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
g_signal_connect (buffer, "paste-done", G_CALLBACK (paste_completed), &pasted);
|
||||||
|
gtk_text_buffer_set_text (buffer, "before after", -1);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 7);
|
||||||
|
gtk_text_buffer_get_end_iter (buffer, &end);
|
||||||
|
gtk_text_buffer_select_range (buffer, &start, &end);
|
||||||
|
g_assert_true (gtk_clipboard_set_with_data (clipboard, targets, G_N_ELEMENTS (targets),
|
||||||
|
text_with_native_type, NULL, (gpointer) "猫\npasted"));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_v, GDK_META_MASK));
|
||||||
|
wait_for_paste (&pasted, 1);
|
||||||
|
assert_buffer_text (buffer, "before 猫\npasted");
|
||||||
|
|
||||||
|
gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
|
||||||
|
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||||
|
gtk_text_buffer_select_range (buffer, &start, &end);
|
||||||
|
gtk_clipboard_set_text (clipboard, "blocked", -1);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_v, GDK_META_MASK));
|
||||||
|
wait_for_paste (&pasted, 2);
|
||||||
|
assert_buffer_text (buffer, "before 猫\npasted");
|
||||||
|
g_assert_false (gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_x, GDK_META_MASK));
|
||||||
|
assert_buffer_text (buffer, "before 猫\npasted");
|
||||||
|
assert_clipboard (view, "before 猫\npasted");
|
||||||
|
gtk_clipboard_clear (clipboard);
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_multiline_editing (void)
|
||||||
|
{
|
||||||
|
GtkWidget *view = gtk_text_view_new ();
|
||||||
|
GtkWidget *window = shortcut_window (view);
|
||||||
|
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
|
||||||
|
GtkTextIter start, end;
|
||||||
|
|
||||||
|
gtk_text_buffer_set_text (buffer, "first\nsecond", -1);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 9);
|
||||||
|
gtk_text_buffer_place_cursor (buffer, &start);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_Left, GDK_META_MASK));
|
||||||
|
gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&start), ==, 6);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_Right, GDK_META_MASK | GDK_SHIFT_MASK));
|
||||||
|
g_assert_true (gtk_text_buffer_get_selection_bounds (buffer, &start, &end));
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&start), ==, 6);
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&end), ==, 12);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_a, GDK_META_MASK));
|
||||||
|
g_assert_true (gtk_text_buffer_get_selection_bounds (buffer, &start, &end));
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&start), ==, 0);
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&end), ==, 12);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 9);
|
||||||
|
gtk_text_buffer_place_cursor (buffer, &start);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
assert_buffer_text (buffer, "first\nond");
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_Down, GDK_META_MASK));
|
||||||
|
gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&start), ==, 9);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_Up, GDK_META_MASK | GDK_SHIFT_MASK));
|
||||||
|
g_assert_true (gtk_text_buffer_get_selection_bounds (buffer, &start, &end));
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&start), ==, 0);
|
||||||
|
g_assert_cmpint (gtk_text_iter_get_offset (&end), ==, 9);
|
||||||
|
/* An existing selection is deleted without removing the rest of its line. */
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 7);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &end, 8);
|
||||||
|
gtk_text_buffer_select_range (buffer, &start, &end);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
assert_buffer_text (buffer, "first\nod");
|
||||||
|
gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
|
||||||
|
gtk_text_buffer_get_end_iter (buffer, &end);
|
||||||
|
gtk_text_buffer_place_cursor (buffer, &end);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
assert_buffer_text (buffer, "first\nod");
|
||||||
|
gtk_text_view_set_editable (GTK_TEXT_VIEW (view), TRUE);
|
||||||
|
{
|
||||||
|
GtkTextTag *protected = gtk_text_buffer_create_tag (buffer, "protected", "editable", FALSE, NULL);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 6);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &end, 7);
|
||||||
|
gtk_text_buffer_apply_tag (buffer, protected, &start, &end);
|
||||||
|
gtk_text_buffer_get_end_iter (buffer, &end);
|
||||||
|
gtk_text_buffer_place_cursor (buffer, &end);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_META_MASK));
|
||||||
|
assert_buffer_text (buffer, "first\no");
|
||||||
|
}
|
||||||
|
gtk_text_buffer_set_text (buffer, "one two\nthree four", -1);
|
||||||
|
gtk_text_buffer_get_end_iter (buffer, &end);
|
||||||
|
gtk_text_buffer_place_cursor (buffer, &end);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_MOD1_MASK));
|
||||||
|
assert_buffer_text (buffer, "one two\nthree ");
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &start, 4);
|
||||||
|
gtk_text_buffer_get_iter_at_offset (buffer, &end, 7);
|
||||||
|
gtk_text_buffer_select_range (buffer, &start, &end);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (view), GDK_KEY_BackSpace, GDK_MOD1_MASK));
|
||||||
|
assert_buffer_text (buffer, "one \nthree ");
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint preferences_opened;
|
||||||
|
|
||||||
|
static void test_open_preferences (void)
|
||||||
|
{
|
||||||
|
preferences_opened++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void count_minimize (GtkWindow *window, guint *count)
|
||||||
|
{
|
||||||
|
(*count)++;
|
||||||
|
/* Check action routing without requiring an X11 window manager. */
|
||||||
|
g_signal_stop_emission_by_name (window, "zoitechat-macos-minimize");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_window_shortcuts (void)
|
||||||
|
{
|
||||||
|
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
guint minimized = 0;
|
||||||
|
preferences_opened = 0;
|
||||||
|
g_signal_connect (window, "zoitechat-macos-minimize", G_CALLBACK (count_minimize), &minimized);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (window), GDK_KEY_comma, GDK_META_MASK));
|
||||||
|
g_assert_cmpuint (preferences_opened, ==, 1);
|
||||||
|
g_assert_true (gtk_bindings_activate (G_OBJECT (window), GDK_KEY_m, GDK_META_MASK));
|
||||||
|
g_assert_cmpuint (minimized, ==, 1);
|
||||||
|
gtk_widget_destroy (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_macos_legacy_defaults (void)
|
||||||
|
{
|
||||||
|
const struct {
|
||||||
|
guint key, new_key;
|
||||||
|
GdkModifierType mod, new_mod;
|
||||||
|
const char *action, *data1, *data2;
|
||||||
|
gboolean remapped;
|
||||||
|
} cases[] = {
|
||||||
|
{GDK_KEY_Tab, GDK_KEY_Tab, GDK_META_MASK, GDK_CONTROL_MASK, "Complete nick/command", "Up", NULL, TRUE},
|
||||||
|
{GDK_KEY_Left, GDK_KEY_Left, GDK_MOD1_MASK, GDK_META_MASK | GDK_MOD1_MASK, "Move front tab left", NULL, NULL, TRUE},
|
||||||
|
{GDK_KEY_Right, GDK_KEY_Right, GDK_MOD1_MASK, GDK_META_MASK | GDK_MOD1_MASK, "Move front tab right", "", "", TRUE},
|
||||||
|
{GDK_KEY_m, GDK_KEY_R, GDK_META_MASK, GDK_META_MASK | GDK_SHIFT_MASK, "Menu Shortcut", "reset-marker", NULL, TRUE},
|
||||||
|
{GDK_KEY_m, GDK_KEY_m, GDK_META_MASK, GDK_META_MASK, "Menu Shortcut", "search-text", NULL, FALSE},
|
||||||
|
{GDK_KEY_m, GDK_KEY_m, GDK_CONTROL_MASK, GDK_CONTROL_MASK, "Menu Shortcut", "reset-marker", NULL, FALSE},
|
||||||
|
{GDK_KEY_Left, GDK_KEY_Left, GDK_MOD1_MASK, GDK_MOD1_MASK, "Move front tab left", "custom", NULL, FALSE},
|
||||||
|
{GDK_KEY_Tab, GDK_KEY_Tab, GDK_META_MASK, GDK_META_MASK, "Complete nick/command", "Up", "custom", FALSE},
|
||||||
|
};
|
||||||
|
guint i;
|
||||||
|
for (i = 0; i < G_N_ELEMENTS (cases); i++)
|
||||||
|
{
|
||||||
|
guint key = cases[i].key;
|
||||||
|
GdkModifierType mod = cases[i].mod;
|
||||||
|
g_assert_cmpint (macos_shortcuts_remap_default (&key, &mod, cases[i].action,
|
||||||
|
cases[i].data1, cases[i].data2), ==, cases[i].remapped);
|
||||||
|
g_assert_cmpuint (key, ==, cases[i].new_key);
|
||||||
|
g_assert_cmpuint (mod, ==, cases[i].new_mod);
|
||||||
|
/* Saved migrated shortcuts must be stable on subsequent launches. */
|
||||||
|
g_assert_false (macos_shortcuts_remap_default (&key, &mod, cases[i].action,
|
||||||
|
cases[i].data1, cases[i].data2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void test_search (Fixture *f, gconstpointer data)
|
static void test_search (Fixture *f, gconstpointer data)
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
@@ -567,7 +1032,44 @@ int main (int argc, char **argv)
|
|||||||
g_test_message ("GTK display unavailable; run under Xvfb for widget coverage");
|
g_test_message ("GTK display unavailable; run under Xvfb for widget coverage");
|
||||||
return 77;
|
return 77;
|
||||||
}
|
}
|
||||||
|
g_object_set (gtk_settings_get_default (), "gtk-enable-animations", FALSE, NULL);
|
||||||
|
/* Quartz installs this CSS key theme automatically. Exercise its higher
|
||||||
|
* priority bindings on Linux too, rather than only testing GTK classes. */
|
||||||
|
g_object_set (gtk_settings_get_default (), "gtk-key-theme-name", "Mac", NULL);
|
||||||
|
if (!gtk_binding_set_find ("gtk-mac-cmd-c"))
|
||||||
|
{
|
||||||
|
/* Some Linux packages omit GTK's Mac theme. Reproduce the upstream
|
||||||
|
* CSS clipboard bindings so this regression is still tested there. */
|
||||||
|
GtkCssProvider *keys = gtk_css_provider_new ();
|
||||||
|
g_test_message ("Using portable Mac key-theme fixture");
|
||||||
|
gtk_css_provider_load_from_data (keys,
|
||||||
|
"@binding-set zoitechat-test-mac-clipboard {"
|
||||||
|
" bind '<meta>c' { 'copy-clipboard' () };"
|
||||||
|
" bind '<meta>x' { 'cut-clipboard' () };"
|
||||||
|
" bind '<meta>v' { 'paste-clipboard' () };"
|
||||||
|
" bind '<ctrl>a' { 'move-cursor' (paragraph-ends, -1, 0) };"
|
||||||
|
" bind '<ctrl>e' { 'move-cursor' (paragraph-ends, 1, 0) }; }"
|
||||||
|
"entry, textview { -gtk-key-bindings: zoitechat-test-mac-clipboard; }",
|
||||||
|
-1, NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
||||||
|
GTK_STYLE_PROVIDER (keys), GTK_STYLE_PROVIDER_PRIORITY_SETTINGS);
|
||||||
|
g_object_unref (keys);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
g_test_message ("Using GTK's installed Mac key theme");
|
||||||
prefs.hex_gui_mouse_scroll_speed = 10;
|
prefs.hex_gui_mouse_scroll_speed = 10;
|
||||||
|
/* Exercise the macOS implementation with real GTK widgets on every OS.
|
||||||
|
* Production only installs these bindings on macOS. */
|
||||||
|
macos_shortcuts_init (test_open_preferences);
|
||||||
|
g_test_add_data_func ("/macos-shortcuts/entry", GINT_TO_POINTER (FALSE), test_macos_entry_shortcuts);
|
||||||
|
g_test_add_data_func ("/macos-shortcuts/search-entry", GINT_TO_POINTER (TRUE), test_macos_entry_shortcuts);
|
||||||
|
g_test_add_func ("/macos-shortcuts/textview", test_macos_textview_shortcuts);
|
||||||
|
g_test_add_func ("/macos-shortcuts/key-theme-fallback", test_macos_key_theme_fallback);
|
||||||
|
g_test_add_func ("/macos-shortcuts/entry-editing", test_macos_entry_editing);
|
||||||
|
g_test_add_func ("/macos-shortcuts/multiline-editing", test_macos_multiline_editing);
|
||||||
|
g_test_add_func ("/macos-shortcuts/multiline-paste", test_macos_multiline_paste);
|
||||||
|
g_test_add_func ("/macos-shortcuts/window", test_macos_window_shortcuts);
|
||||||
|
g_test_add_func ("/macos-shortcuts/legacy-defaults", test_macos_legacy_defaults);
|
||||||
#define ADD(name, function) g_test_add ("/xtext/" name, Fixture, NULL, fixture_setup, function, fixture_teardown)
|
#define ADD(name, function) g_test_add ("/xtext/" name, Fixture, NULL, fixture_setup, function, fixture_teardown)
|
||||||
ADD ("append/empty-bounded", test_empty_and_bounded_append);
|
ADD ("append/empty-bounded", test_empty_and_bounded_append);
|
||||||
ADD ("append/utf8-limits", test_utf8_limits);
|
ADD ("append/utf8-limits", test_utf8_limits);
|
||||||
@@ -577,6 +1079,7 @@ int main (int argc, char **argv)
|
|||||||
ADD ("layout/wide-metrics", test_wide_metrics);
|
ADD ("layout/wide-metrics", test_wide_metrics);
|
||||||
ADD ("render/formatting-timestamps", test_formatting);
|
ADD ("render/formatting-timestamps", test_formatting);
|
||||||
ADD ("selection/clipboard", test_selection_and_clipboard);
|
ADD ("selection/clipboard", test_selection_and_clipboard);
|
||||||
|
ADD ("selection/macos-command-copy", test_macos_chat_copy);
|
||||||
ADD ("search/navigation-regex-clear", test_search);
|
ADD ("search/navigation-regex-clear", test_search);
|
||||||
ADD ("search/follow", test_follow_search);
|
ADD ("search/follow", test_follow_search);
|
||||||
ADD ("search/unicode-offsets", test_search_unicode_offsets);
|
ADD ("search/unicode-offsets", test_search_unicode_offsets);
|
||||||
|
|||||||
@@ -48,6 +48,12 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
gint64
|
||||||
|
fe_get_idle_time (void)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int done = FALSE;
|
static int done = FALSE;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user