From aac29d859500543761192e7a0d4e6df3fd953d50 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:35:30 +0100 Subject: [PATCH 001/122] chore(deps): update actions/setup-node digest to 53b8394 (#369) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0973bb7..03c7bf4f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: fetch-depth: 0 token: ${{ steps.get_token.outputs.token }} - name: Setup Node.js - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: "lts/*" - name: Install dependencies From 4d649b1b1e9a79182dc925886abc44e162911a18 Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:29:37 +0100 Subject: [PATCH 002/122] chore: do not use eds client connection timeout (#367) Co-authored-by: Leah Jennebach --- src/contacts/eds/EDSAddressBookFeeder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index 6c312caa..9f22031a 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -92,7 +92,7 @@ void EDSAddressBookFeeder::init() qCDebug(lcEDSAddressBookFeeder) << "Connecting to source" << sourceInfo; - e_book_client_connect(source, 5, nullptr, onEbookClientConnected, this); + e_book_client_connect(source, -1, nullptr, onEbookClientConnected, this); } m_sourcePromise->start(); From d7b9f5dfe82dcc8b1beb9b7a44ca1420cfc40843 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:14:24 +0100 Subject: [PATCH 003/122] chore(deps): update github/codeql-action digest to 0d579ff (#372) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index af89f10f..077a05b4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 with: category: "/language:${{matrix.language}}" From 65900abcc5030f33687ebfab95e03f05c1591c51 Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Sat, 7 Mar 2026 06:43:54 +0100 Subject: [PATCH 004/122] fix: potential crash on HeadsetDevice destroyed (#371) When HeadsetDevice gets destroyed, set m_device to nullptr again, do not dereference it anymore. This fixes potential crashes later on. Co-authored-by: Cajus Pollmeier --- src/usb/HeadsetDeviceProxy.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/usb/HeadsetDeviceProxy.cpp b/src/usb/HeadsetDeviceProxy.cpp index 84b23771..e0e7dd7f 100644 --- a/src/usb/HeadsetDeviceProxy.cpp +++ b/src/usb/HeadsetDeviceProxy.cpp @@ -156,6 +156,8 @@ bool HeadsetDeviceProxy::refreshDevice() if (devs.count()) { m_device = devs.first(); + connect(m_device, &HeadsetDevice::destroyed, this, [this]() { m_device = nullptr; }); + connect(m_device, &HeadsetDevice::hookSwitch, this, [this]() { if (isEnabled()) { Q_EMIT hookSwitch(); From 61f34075a0f8dae316bd2a7ed6f6cf171917ad2b Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Sat, 7 Mar 2026 06:44:26 +0100 Subject: [PATCH 005/122] feat: pause/resume media on windows (#370) Use GlobalSystemMediaTransportControlsSessionManager to obtain all active media sessions and pause all sessions that can be paused. Resume as-needed. Co-authored-by: Cajus Pollmeier --- src/CMakeLists.txt | 7 +- .../windows/SmtcExternalMediaManager.cpp | 137 ++++++++++++++++++ .../windows/SmtcExternalMediaManager.h | 25 ++++ 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/platform/windows/SmtcExternalMediaManager.cpp create mode 100644 src/platform/windows/SmtcExternalMediaManager.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3941b9a0..b4d7b330 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,7 +44,8 @@ else() pjproject::pjproject hidapi::hidapi libusb::libusb - wintoast::wintoast) + wintoast::wintoast + runtimeobject) endif() add_definitions(-DPJMEDIA_HAS_RTCP_XR=1 -DPJMEDIA_STREAM_ENABLE_XR=1) @@ -266,8 +267,6 @@ elseif(WIN32) set(PLATFORM_SOURCES platform/windows/WindowsUserInfo.cpp platform/windows/WindowsUserInfo.h - platform/dummy/DummyExternalMediaManager.cpp - platform/dummy/DummyExternalMediaManager.h platform/dummy/DummyBackgroundManager.cpp platform/dummy/DummyBackgroundManager.h platform/windows/WindowsThemeManager.cpp @@ -279,6 +278,8 @@ elseif(WIN32) platform/windows/WindowsNotificationManager.h platform/windows/WindowsNotificationManager.cpp platform/windows/WindowsInhibitHelper.h + platform/windows/SmtcExternalMediaManager.h + platform/windows/SmtcExternalMediaManager.cpp platform/windows/WindowsInhibitHelper.cpp platform/dummy/DummyGlobalShortcuts.h platform/dummy/DummyGlobalShortcuts.cpp diff --git a/src/platform/windows/SmtcExternalMediaManager.cpp b/src/platform/windows/SmtcExternalMediaManager.cpp new file mode 100644 index 00000000..43daa7c2 --- /dev/null +++ b/src/platform/windows/SmtcExternalMediaManager.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include + +#include "SmtcExternalMediaManager.h" + +#include +#include +#include +#include + +using namespace winrt::Windows::Media::Control; + +Q_LOGGING_CATEGORY(lcExternalMedia, "gonnect.external.media") + +// Dedicated thread for simplified GlobalSystemMediaTransportControlsSessionManager access. +// This allows us to use blocking API to obtain sessions. +class SmtcThread : public QThread +{ +public: + enum class Request { None, PauseSessions, ResumeSessions, Quit }; + +public: + void setRequest(Request request); + +protected: + void run() override; + void pauseSessions(); + void resumeSessions(); + +private: + QMutex m_mutex; + QWaitCondition m_waitCondition; + Request m_currentRequest = Request::None; + QSet m_pausedSessions; +}; + +ExternalMediaManager &ExternalMediaManager::instance() +{ + static ExternalMediaManager *_instance = nullptr; + if (!_instance) { + _instance = new SmtcExternalMediaManager; + } + + return *_instance; +} + +SmtcExternalMediaManager::SmtcExternalMediaManager() : m_thread(new SmtcThread) +{ + m_thread->start(); +} + +SmtcExternalMediaManager::~SmtcExternalMediaManager() +{ + m_thread->setRequest(SmtcThread::Request::Quit); + delete m_thread; +} + +void SmtcExternalMediaManager::pause() +{ + m_didPause = true; + m_thread->setRequest(SmtcThread::Request::PauseSessions); +} + +void SmtcExternalMediaManager::resume() +{ + m_didPause = false; + m_thread->setRequest(SmtcThread::Request::ResumeSessions); +} + +void SmtcThread::run() +{ + winrt::init_apartment(); // required once per thread + while (true) { + QMutexLocker lock(&m_mutex); + switch (m_currentRequest) { + case Request::Quit: + return; + case Request::PauseSessions: + pauseSessions(); + break; + case Request::ResumeSessions: + resumeSessions(); + break; + case Request::None: + break; + } + m_currentRequest = Request::None; + m_waitCondition.wait(&m_mutex); + } +} + +void SmtcThread::setRequest(Request request) +{ + QMutexLocker lock(&m_mutex); + if (m_currentRequest != Request::Quit) { + m_currentRequest = request; + } + m_waitCondition.wakeAll(); +} + +void SmtcThread::pauseSessions() +{ + auto sessions = + GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get().GetSessions(); + for (auto const &session : sessions) { + auto controls = session.GetPlaybackInfo().Controls(); + if (controls.IsPauseEnabled()) { + qCDebug(lcExternalMedia) << "pausing" << session.SourceAppUserModelId(); + m_pausedSessions.insert(session.SourceAppUserModelId()); + session.TryPauseAsync(); + } + } +} + +void SmtcThread::resumeSessions() +{ + if (m_pausedSessions.empty()) { + return; + } + auto sessions = + GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get().GetSessions(); + for (auto const &session : sessions) { + if (m_pausedSessions.contains(session.SourceAppUserModelId())) { + auto controls = session.GetPlaybackInfo().Controls(); + if (controls.IsPlayEnabled()) { + qCDebug(lcExternalMedia) << "resuming" << session.SourceAppUserModelId(); + session.TryPlayAsync(); + } else { + qCDebug(lcExternalMedia) + << "cannot resume paused session" << session.SourceAppUserModelId(); + } + } + } + m_pausedSessions.clear(); +} diff --git a/src/platform/windows/SmtcExternalMediaManager.h b/src/platform/windows/SmtcExternalMediaManager.h new file mode 100644 index 00000000..ecd9b972 --- /dev/null +++ b/src/platform/windows/SmtcExternalMediaManager.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "ExternalMediaManager.h" +#include + +class SmtcThread; + +class SmtcExternalMediaManager : public ExternalMediaManager +{ + Q_OBJECT + +public: + void pause() override; + void resume() override; + bool hasState() const override { return m_didPause; } + + explicit SmtcExternalMediaManager(); + ~SmtcExternalMediaManager(); + +private: + SmtcThread *m_thread = nullptr; + bool m_didPause = false; +}; From ef3e7226fb7260964efbb8656cd7bae46edcd0a9 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:53:50 +0100 Subject: [PATCH 006/122] chore(deps): update renovatebot/github-action action to v46.1.4 (#373) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 13b22658..fd64b32a 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@7b4b65bf31e07d4e3e51708d07700fb41bc03166 # v46.1.3 + uses: renovatebot/github-action@0b17c4eb901eca44d018fb25744a50a74b2042df # v46.1.4 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 4428e4e659af0c708fa3738dc521977b7b11d1c2 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 10 Mar 2026 11:16:49 +0100 Subject: [PATCH 007/122] feat: pre-fill missing it/es translations (#377) Updated translations --- CMakeLists.txt | 2 +- i18n/gonnect_de.ts | 2 +- i18n/gonnect_es.ts | 1288 +++++------ i18n/gonnect_fa.ts | 5186 ++++++++++++++++++++++++++++++++++++++++++++ i18n/gonnect_fr.ts | 5186 ++++++++++++++++++++++++++++++++++++++++++++ i18n/gonnect_it.ts | 1508 ++++++------- i18n/gonnect_ru.ts | 5186 ++++++++++++++++++++++++++++++++++++++++++++ i18n/gonnect_uk.ts | 5186 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 22144 insertions(+), 1400 deletions(-) create mode 100644 i18n/gonnect_fa.ts create mode 100644 i18n/gonnect_fr.ts create mode 100644 i18n/gonnect_ru.ts create mode 100644 i18n/gonnect_uk.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index bd489cbe..6ced10d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,7 +77,7 @@ endif() find_package(Qt6 REQUIRED COMPONENTS Gui ${PLATFORM_QT_MODULES} Qml Quick QuickControls2 LinguistTools) qt_standard_project_setup(REQUIRES 6.9 I18N_SOURCE_LANGUAGE en - I18N_TRANSLATED_LANGUAGES de es it + I18N_TRANSLATED_LANGUAGES de es it fa fr ru uk ) # Adjust target path for qm files diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts index 99a54dfa..22c16fd0 100644 --- a/i18n/gonnect_de.ts +++ b/i18n/gonnect_de.ts @@ -4046,7 +4046,7 @@ New Identity - Neue Identitätsregel + Neue Identität diff --git a/i18n/gonnect_es.ts b/i18n/gonnect_es.ts index c17e8ecb..e11b172a 100644 --- a/i18n/gonnect_es.ts +++ b/i18n/gonnect_es.ts @@ -11,12 +11,12 @@ GOnnect headline - + Titular de GOnnect GOnnect version - + Versión de GOnnect @@ -26,12 +26,12 @@ Copy to clipboard - + Copiar al portapapeles Copy the currently used version of Gonnect to the clipboard - + Copiar la versión actualmente utilizada de GOnnect al portapapeles @@ -44,7 +44,7 @@ Visit the project homepage - + Visitar la página de inicio del proyecto @@ -54,7 +54,7 @@ Visit the project bug tracker - + Visitar el rastreador de errores del proyecto @@ -64,7 +64,7 @@ Visit the online project documentation - + Visitar la documentación en línea del proyecto @@ -72,17 +72,17 @@ Additional caller related information - + Información adicional sobre el llamante List of informational items regarding the caller, such as open support tickets - + Lista de elementos informativos sobre el llamante, como tickets de soporte abiertos Expandable response section - + Sección de respuesta desplegable @@ -103,7 +103,7 @@ Audio environment error - + Error en el entorno de audio @@ -113,12 +113,12 @@ Input device selection header - + Encabezado de selección del dispositivo de entrada Header for the input device selection below - + Encabezado para la selección de dispositivo de entrada a continuación @@ -128,27 +128,27 @@ Input device selection box - + Cuadro de selección del dispositivo de entrada Select the input device that should be used - + Seleccionar el dispositivo de entrada que se debe usar Currently selected input device - + Dispositivo de entrada seleccionado actualmente Output device selection header - + Encabezado de selección del dispositivo de salida Header for the output device selection below - + Encabezado para la selección de dispositivo de salida a continuación @@ -158,22 +158,22 @@ Output device selection box - + Cuadro de selección del dispositivo de salida Select the output device that should be used - + Seleccionar el dispositivo de salida que se debe usar Currently selected output device - + Dispositivo de salida seleccionado actualmente Ring tone output device - + Dispositivo de salida para el tono de llamada @@ -183,17 +183,17 @@ Ring tone output device selection box - + Cuadro de selección del dispositivo de salida para el tono de llamada Select the output device that should be used for playing the ring tone - + Seleccionar el dispositivo de salida que se debe usar para reproducir el tono de llamada Currently selected ring tone output device - + Dispositivo de salida del tono de llamada seleccionado actualmente @@ -203,12 +203,12 @@ Close audio environment selection - + Cerrar la selección del entorno de audio Confirmation button to leave the audio environment selection window - + Botón de confirmación para salir de la ventana de selección del entorno de audio @@ -216,7 +216,7 @@ Change volume - + Cambiar el volumen @@ -237,7 +237,7 @@ Initials of this contact - + Iniciales de este contacto @@ -253,17 +253,17 @@ Base dashboard page grid - + Cuadrícula base de la página del panel Canvas for editable dashboard pages - + Lienzo para páginas del panel editables Add widgets - + Añadir widgets @@ -271,32 +271,32 @@ Drag widget - + Arrastrar widget Change the position of the widget - + Cambiar la posición del widget Remove widget - + Eliminar widget Remove the currently selected widget from the dashboard - + Eliminar el widget seleccionado actualmente del panel Resize widget - + Redimensionar widget Resize the widget according to the mouse direction - + Redimensionar el widget según la dirección del ratón @@ -311,47 +311,47 @@ Drag border - + Borde de arrastre Top left drag border for window resize operations - + Borde de arrastre superior izquierdo para cambiar el tamaño de la ventana Top drag border for window resize operations - + Borde de arrastre superior para cambiar el tamaño de la ventana Top right border for window resize operations - + Borde superior derecho para cambiar el tamaño de la ventana Right drag border for window resize operations - + Borde de arrastre derecho para cambiar el tamaño de la ventana Bottom right drag border for window resize operations - + Borde de arrastre inferior derecho para cambiar el tamaño de la ventana Bottom drag border for window resize operations - + Borde de arrastre inferior para cambiar el tamaño de la ventana Bottom left drag border for window resize operations - + Borde de arrastre inferior izquierdo para cambiar el tamaño de la ventana Left drag border for window resize operations - + Borde de arrastre izquierdo para cambiar el tamaño de la ventana @@ -359,7 +359,7 @@ Status bar - + Barra de estado @@ -372,12 +372,12 @@ Shortcuts... - Atajos de teclado + Atajos de teclado... Customize UI - + Personalizar la interfaz @@ -400,7 +400,7 @@ Drag bar - + Barra de arrastre @@ -408,178 +408,178 @@ %1@%2 kHz - + %1@%2 kHz Transmit - + Transmitir Call security level - + Nivel de seguridad de la llamada Security level of the ongoing call - + Nivel de seguridad de la llamada en curso Call security details - + Detalles de seguridad de la llamada Detailed call security status: %1 / %2 - + Estado detallado de seguridad de la llamada: %1 / %2 signaling encrypted - + señalización cifrada signaling unencrypted - + señalización sin cifrar media encrypted - + medios cifrados media unencrypted - + medios sin cifrar Call quality - + Calidad de la llamada Quality of the ongoing call - + Calidad de la llamada en curso Transmission statistics - + Estadísticas de transmisión Call quality metrics - + Métricas de calidad de la llamada MOS - + MOS Mean opinion score - + Puntuación de opinión media (MOS) Numerical metric assessing transmission-side voice call quality: %1 - + Métrica numérica que evalúa la calidad de voz en el lado de transmisión: %1 Packet loss - + Pérdida de paquetes %1% of packets lost in transmission - + %1% de paquetes perdidos en la transmisión Jitter - + Jitter Amount of transmission side jitter: %1 - + Jitter en el lado de transmisión: %1 Effective delay - + Retardo efectivo Effective transmission side call delay: %1 - + Retardo efectivo de llamada en el lado de transmisión: %1 Receiver statistics - + Estadísticas de recepción Receive - + Recibir Numerical metric assessing receiver-side voice/video call quality: %1 - + Métrica numérica que evalúa la calidad de voz/vídeo en el lado del receptor: %1 %1% of packets lost in receival - + %1% de paquetes perdidos en la recepción Amount of receiver side jitter: %1 - + Jitter en el lado del receptor: %1 Effective receiver side call delay: %1 - + Retardo efectivo de llamada en el lado del receptor: %1 Codec - + Códec Audio codec - + Códec de audio The currently used audio codec and frequency: %1 - + El códec de audio y la frecuencia utilizados actualmente: %1 Elapsed call time - + Tiempo transcurrido de la llamada The duration in seconds the call has been active for: %1 - + La duración en segundos que la llamada ha estado activa: %1 @@ -589,12 +589,12 @@ Screensharing control - + Control de compartición de pantalla Start sharing your screen - + Iniciar la compartición de pantalla @@ -604,12 +604,12 @@ Camera control - + Control de la cámara Enable your camera - + Activar la cámara @@ -624,17 +624,17 @@ Resume call - + Reanudar la llamada Hold call - + Poner la llamada en espera Update the call hold state - + Actualizar el estado de espera de la llamada @@ -644,12 +644,12 @@ Input control - + Control de entrada Set the mute state of the current input device - + Establecer el estado de silencio del dispositivo de entrada actual @@ -659,22 +659,22 @@ Output control - + Control de salida Change the current output devices - + Cambiar los dispositivos de salida actuales Accept call - + Aceptar llamada Hangup call - + Colgar llamada @@ -682,12 +682,12 @@ SIP call status code - + Código de estado de la llamada SIP The current status code of the call: %1 - + El código de estado actual de la llamada: %1 @@ -697,7 +697,7 @@ Switch to a Jitsi Meet session - + Cambiar a una sesión de Jitsi Meet @@ -712,17 +712,17 @@ Toggle the hold state to %1 - + Cambiar el estado de espera a %1 Accept call - + Aceptar llamada Hangup call - + Colgar llamada @@ -730,7 +730,7 @@ Failed to create directory %1 to store the call history database. - No se pudo crear el directorio %1 para guardar la base de datos del historial de llamadas. + No se pudo crear el directorio %1 para almacenar la base de datos del historial de llamadas. @@ -740,7 +740,7 @@ Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. - La base de datos de historia de llamadas es inconsistente. Retirar %1 y reiniciar la aplicación para reiniciar la base de datos. + La base de datos del historial de llamadas es inconsistente. Elimine %1 y reinicie la aplicación para reinicializar la base de datos. @@ -748,22 +748,22 @@ Call - Llamar + Llamar Selected call %1 - contact %2, company %3, location %4/%5, number %6 - + Llamada seleccionada %1 - contacto %2, empresa %3, ubicación %4/%5, número %6 Hangup button - + Botón de colgar Pressing this will end the call - + Pulsando esto se terminará la llamada @@ -776,7 +776,7 @@ List of active calls - + Lista de llamadas activas @@ -811,7 +811,7 @@ Caller name - + Nombre del llamante @@ -837,7 +837,7 @@ List of configurable options - + Lista de opciones configurables @@ -845,17 +845,17 @@ Message - + Mensaje Type message - + Escribir mensaje Enter the chat text message - + Introducir el mensaje de texto del chat @@ -863,17 +863,17 @@ Chat message list - + Lista de mensajes del chat List of all chat messages of the current chat room - + Lista de todos los mensajes del chat de la sala actual Auto scroll down - + Desplazamiento automático hacia abajo @@ -881,12 +881,12 @@ Chat message - + Mensaje de chat Selected chat message - from %1, at %2: %3 - + Mensaje de chat seleccionado - de %1, a las %2: %3 @@ -894,12 +894,12 @@ Chat room list - + Lista de salas de chat List of all chat rooms - + Lista de todas las salas de chat @@ -907,12 +907,12 @@ Chat room - + Sala de chat Selected chat room %1: %2 unread messages - + Sala de chat seleccionada %1: %2 mensajes no leídos @@ -920,37 +920,37 @@ Chat message list - + Lista de mensajes del chat List of all the messages in the current chat - + Lista de todos los mensajes del chat actual Chat message - + Mensaje de chat Selected chat message from %1 at %2: %3 - + Mensaje de chat seleccionado de %1 a las %2: %3 the server - + el servidor you - + Select emoji - + Seleccionar emoji @@ -963,27 +963,27 @@ Please enter your recovery key to decrypt messages: - + Por favor, introduzca su clave de recuperación para descifrar los mensajes: Recovery key - + Clave de recuperación Enter recovery key - + Introducir clave de recuperación Use key - + Usar clave Use recovery key - + Usar clave de recuperación @@ -991,7 +991,7 @@ Copy to clipboard: %1 - + Copiar al portapapeles: %1 @@ -1000,7 +1000,7 @@ Set room name - + Establecer nombre de sala @@ -1012,7 +1012,7 @@ Enter the room name - + Introducir el nombre de la sala @@ -1039,7 +1039,7 @@ Enter the password - + Introducir la contraseña @@ -1060,7 +1060,7 @@ Password required - + Se requiere contraseña @@ -1095,12 +1095,12 @@ Video quality - + Calidad de vídeo Change the video quality of this meeting - + Cambiar la calidad de vídeo de esta reunión @@ -1115,22 +1115,22 @@ Standard quality - + Calidad estándar Highest quality - + Calidad máxima Close - + Cerrar Drag bar - + Barra de arrastre @@ -1138,135 +1138,135 @@ %n minutes left - - - + + %n minuto restante + %n minutos restantes Conference room - + Sala de conferencias Share - + Compartir Copy room name - + Copiar nombre de sala Copy room link - + Copiar enlace de sala Open in browser - + Abrir en el navegador Show phone number - + Mostrar número de teléfono Raise - + Levantar la mano Resume - Continuar + Continuar Hold - + En espera View - + Vista Screen - Pantalla + Pantalla Share window - + Compartir ventana Share screen - + Compartir pantalla Camera - Cámera + Cámera Micro - Micrófono + Micrófono Output - Salida + Salida More - + Más Noise supression - + Supresión de ruido Toggle subtitles - + Activar/desactivar subtítulos Toggle whiteboard - + Activar/desactivar pizarra Video quality... - + Calidad de vídeo... Set room password... - + Establecer contraseña de sala... Mute everyone - + Silenciar a todos Leave conference - + Abandonar la conferencia End conference for all - + Terminar la conferencia para todos @@ -1282,7 +1282,7 @@ App menu - + Menú de la aplicación @@ -1290,12 +1290,12 @@ storing credentials failed: %1 - + Error al guardar las credenciales: %1 reading credentials failed: %1 - + Error al leer las credenciales: %1 @@ -1318,7 +1318,7 @@ Enter the password - + Introducir la contraseña @@ -1331,17 +1331,17 @@ GOnnect window header - + Encabezado de la ventana de GOnnect App menu - + Menú de la aplicación Close GOnnect window - + Cerrar la ventana de GOnnect @@ -1349,22 +1349,22 @@ Conference starting soon - + La conferencia comenzará pronto Appointment starting soon - + La cita comenzará pronto Join - + Unirse Open - + Abrir @@ -1372,53 +1372,53 @@ Date events - + Fechas List of all the currently active and upcoming date events - + Lista de todos los eventos de calendario activos y próximos Date event section - + Sección de eventos de calendario Header for %1 - + Encabezado para %1 Today - %1 - + Hoy - %1 yyyy/MM/dd - + dd/MM/yyyy Tomorrow - %1 - + Mañana - %1 dddd - yyyy/MM/dd - + dddd - dd/MM/yyyy Date event - + Fechas Currently selected date event: %1, starting time %2, remaining time %3 - + Evento de calendario seleccionado: %1, hora de inicio %2, tiempo restante %3 @@ -1429,57 +1429,57 @@ All day - + Todo el día till %1 - + hasta %1 in %1 - + en %1 Join - + Unirse Open - + Abrir Join meeting - + Unirse a la reunión Join the meeting associated with the currently selected event - + Unirse a la reunión asociada al evento seleccionado actualmente Copy room link - + Copiar enlace de sala Copy link - + Copiar enlace Copy meeting link - + Copiar enlace de la reunión Copy the meeting link associated with the currently selected event - + Copiar el enlace de la reunión asociada al evento seleccionado actualmente @@ -1487,27 +1487,27 @@ Appointments - + Citas Loading appointments... - + Cargando citas... No upcoming appointments - + No hay citas próximas Date event widget status - + Estado del widget de eventos de calendario Displays the current status of the widget: %1 - + Muestra el estado actual del widget: %1 @@ -1515,12 +1515,12 @@ Call one of the phone numbers below and use this code for authentication: - + Llame a uno de los números de teléfono siguientes y utilice este código para la autenticación: Close - + Cerrar @@ -1528,12 +1528,12 @@ Number pad - + Teclado numérico Character %1 - + Símbolo %1 @@ -1541,32 +1541,32 @@ Add page - + Añadir página Add a new dashboard page - + Añadir una nueva página al panel de control Add widget - + Añadir widget Add a new widget to the current dashboard page - + Añadir un nuevo widget a la página actual del panel de control Finished - + Finalizado Finish and save all dashboard and widget changes - + Finalizar y guardar todos los cambios del panel y los widgets @@ -1574,22 +1574,22 @@ Emergency Call - + Llamada de emergencia Incoming emergency call from %1 - + Llamada de emergencia entrante de %1 Answering the call will automatically terminate all other ongoing calls. - + Aceptar la llamada terminará automáticamente todas las demás llamadas en curso. Answer - + Contestar @@ -1597,12 +1597,12 @@ Emoji - + Emoji Selected Emoji: %1 - + Emoji seleccionado: %1 @@ -1610,12 +1610,12 @@ Switch Emoji category - + Cambiar categoría de emoji Select Emoji - + Seleccionar emoji @@ -1623,27 +1623,27 @@ Trying - + Intentando Ringing - + Llamando Call being forwarded - + Llamada siendo desviada Queued - + En cola Progress - + En progreso @@ -1653,68 +1653,68 @@ Accepted - + Aceptada Unauthorized - + No autorizado Rejected - + Rechazada Not found - + No encontrado Proxy authentication required - + Se requiere autenticación de proxy Request timeout - + Tiempo de espera de la solicitud agotado Temporarily unavailable - + Temporalmente no disponible Ambiguous - + Ambiguo Busy here - + Ocupado Request terminated - + Solicitud terminada Not acceptable here - + No aceptado aquí Internal server error - + Error interno del servidor Not implemented - + No implementado @@ -1749,12 +1749,12 @@ Not acceptable anywhere - + No aceptado en ningún lugar Unwanted - + No deseado @@ -1762,22 +1762,22 @@ Unknown - + Desconocido Commercial - Comercial + Comercial Home - + Privado Mobile - Móvil + Móvil @@ -1797,12 +1797,12 @@ SIP - SIP + SIP Jitsi Meet - Jitsi Meet + Jitsi Meet @@ -1810,12 +1810,12 @@ Set favorite - + Marcar como favorito Unset favorite - + Quitar de favoritos @@ -1823,22 +1823,22 @@ Favorite contact - + Contacto favorito Selected favorite %1: %2 - + Favorito seleccionado %1: %2 tap to start meeting %1 - + tocar para iniciar la reunión %1 tap to call %1 - + tocar para llamar a %1 @@ -1846,17 +1846,17 @@ Favorite contact - + Contacto favorito Selected favorite %1: %2 - + Favorito seleccionado %1: %2 tap to call %1 - + tocar para llamar a %1 @@ -1870,7 +1870,7 @@ List of all contacts that have been marked as favorites - + Lista de todos los contactos marcados como favoritos @@ -1878,12 +1878,12 @@ Favorites - Favoritos + Favoritos No favorites to display - + No hay favoritos para mostrar @@ -1891,32 +1891,32 @@ First Aid - + Primeros auxilios Clicking one of these buttons will end all current calls and start an emergency call. - + Al hacer clic en uno de estos botones se terminarán todas las llamadas activas y se iniciará una llamada de emergencia. Tap to call emergency contact: %1 (%2) - + Tocar para llamar al contacto de emergencia: %1 (%2) Close - + Cerrar Exit the first aid menu without initiating any action - + Salir del menú de primeros auxilios sin iniciar ninguna acción Close first aid menu - + Cerrar el menú de primeros auxilios @@ -1924,12 +1924,12 @@ Open first aid menu - + Abrir el menú de primeros auxilios First Aid - + Primeros auxilios @@ -1937,7 +1937,7 @@ Home - + Inicio @@ -1945,7 +1945,7 @@ MMM dd - + dd MMM @@ -1953,12 +1953,12 @@ Ringing - + Llamando Calling - + Llamando @@ -1966,28 +1966,28 @@ Call active - + Llamada activa Call waiting - + Llamada en espera On Hold - + En espera Call ended - + Llamada finalizada Phone conference - + Conferencia telefónica @@ -2001,32 +2001,32 @@ History - Historia + Historia Searchable list of past calls and meetings - + Lista de llamadas y reuniones pasadas con búsqueda History item section - + Sección de elementos del historial Header for the currently selected day: %1 - + Encabezado para el día seleccionado actualmente: %1 History item - + Elemento del historial Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 - + Elemento del historial seleccionado %1 - empresa %2, ubicación %3, número %4, hora %5, duración %6 @@ -2077,78 +2077,78 @@ History - Historia + Historia All - Todos/ todas + Todos/ todas SIP - SIP + SIP Jitsi Meet - Jitsi Meet + Jitsi Meet History call type picker - + Selector de tipo de llamada del historial Select the call type to filter by - + Seleccionar el tipo de llamada para filtrar Currently selected call type - + Tipo de llamada seleccionado actualmente Incoming - Entrando + Entrando Outgoing - Saliendo + Saliendo Missed - Perdido + Perdido History call origin picker - + Selector de origen de llamada del historial Select the call origin to filter by - + Seleccionar el origen de llamada para filtrar Currently selected call origin - + Origen de llamada seleccionado actualmente Hide history search - + Ocultar búsqueda del historial Show history search - + Mostrar búsqueda del historial @@ -2159,7 +2159,7 @@ Ad hoc conference - + Conferencia ad hoc @@ -2167,27 +2167,27 @@ Default - Predeterminado + Predeterminado Auto - + Auto Identity selection - + Selección de identidad Select the preferred identity to be used in calls - + Seleccionar la identidad preferida para las llamadas Currently selected identity - + Identidad seleccionada actualmente @@ -2203,32 +2203,32 @@ New chat message - + Nuevo mensaje de chat Unnamed participant - + Participante sin nombre Active conference - + Conferencia activa Hang up - + Colgar %1 has joined the conference - + %1 se ha unido a la conferencia %1 has left the conference - + %1 ha abandonado la conferencia @@ -2236,22 +2236,22 @@ Start conference - + Iniciar conferencia Remove favorite - Quitar favorito + Quitar favorito Add favorite - Añadir favorito + Añadir favorito Copy room name - + Copiar nombre de sala @@ -2259,14 +2259,14 @@ Failed to initialize LDAP connection - + Error al inicializar la conexión LDAP LDAP error: %1 - Error LDAP: %1 + Error LDAP: %1 @@ -2277,12 +2277,12 @@ LDAP timeout: %1 - + Tiempo de espera LDAP: %1 Failed to initialize LDAP connection: %1 - No se pudo inicializar la conexión LDAP: %1 + No se pudo inicializar la conexión LDAP: %1 @@ -2290,7 +2290,7 @@ Call - Llamar + Llamar @@ -2303,22 +2303,22 @@ GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. - + GOnnect permite un acceso rápido a sus funciones mediante la bandeja del sistema. Su entorno de escritorio no dispone de una. Information - + Información GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! - + GOnnect está siendo probado para su sistema operativo y aún no ha sido lanzado oficialmente. Todavía no hay paridad de funciones con la versión de Linux, y puede haber errores que aún no se han encontrado. Agradecemos que informe de estos problemas en GitHub. ¡Feliz prueba! There are still phone calls going on, do you really want to quit? - + Aún hay llamadas telefónicas en curso. ¿Realmente desea salir? @@ -2328,22 +2328,22 @@ Please enter the recovery key for %1: - + Por favor, introduzca la clave de recuperación para %1: End all calls - + Terminar todas las llamadas Do you really want to close this window and terminate all ongoing calls? - + ¿Realmente desea cerrar esta ventana y terminar todas las llamadas en curso? Please enter the password for the SIP account: - + Por favor, introduzca la contraseña de la cuenta SIP: @@ -2362,82 +2362,82 @@ Home - + Casa Conference - Conferencia + Conferencia No active conference - + No hay conferencia activa No active call - + No hay llamada activa Move up - + Mover arriba Move tab up - + Mover pestaña arriba Moves the currently selected tab up by one - + Mueve la pestaña seleccionada actualmente una posición hacia arriba Move down - + Mover abajo Move tab down - + Mover pestaña abajo Moves the currently selected tab down by one - + Mueve la pestaña seleccionada actualmente una posición hacia abajo Edit - + Editar Edit page - + Editar página Edit the currently selected dashboard page - + Editar la página del panel seleccionada actualmente Delete - Borrar + Borrar Delete page - + Eliminar página Delete the currently selected dashboard page - + Eliminar la página del panel seleccionada actualmente @@ -2447,28 +2447,28 @@ Selected tab - + Pestaña seleccionada The currently selected tab - + La pestaña seleccionada actualmente Selected tab options - + Opciones de la pestaña seleccionada The settings of the currently selected tab - + La configuración de la pestaña seleccionada actualmente Settings - + Ajustes @@ -2486,7 +2486,7 @@ Home - + Privado @@ -2494,92 +2494,92 @@ Create new dashboard page - + Crear nueva página del panel Edit dashboard page - + Editar página del panel Name - + Nombre Page name - + Nombre de página Enter the page name - + Introducir el nombre de la página Icon - + Icono Page icon label - + Etiqueta del icono de la página Page icon selection - + Selección del icono de la página Select the page icon for the dashboard page - + Seleccionar el icono de la página del panel Currently selected page icon option - + Opción de icono de página seleccionada actualmente Cancel - Cancelar + Cancelar Cancel page modifcation - + Cancelar modificación de página Cancel button to exit the page creation/update window - + Botón de cancelar para salir de la ventana de creación/actualización de página Create - + Crear Save - Guardar + Guardar page - + página Confirmation button to create the new dashboard page - + Botón de confirmación para crear la nueva página del panel Confirmation button to apply changes to the dashboard page - + Botón de confirmación para aplicar los cambios a la página del panel @@ -2587,42 +2587,42 @@ Participants list - + Lista de participantes List of all the participants of the current chat room - + Lista de todos los participantes de la sala de chat actual Chat participant - + Participante del chat Selected chat participant: %1 - + Participante del chat seleccionado: %1 moderator - + moderador it's you - + eres tú Kick - + Echar Make moderator - + Hacer moderador @@ -2630,7 +2630,7 @@ Anonymous - + Anónimo @@ -2638,32 +2638,32 @@ Phone Number Transmission - + Transmisión del número de teléfono Name - + Nombre Prefix - + Prefijo Identity - + Identidad Enabled - + Activado Automatic - + Automático @@ -2705,7 +2705,7 @@ Antigua and Barbuda - + Antigua y Barbuda @@ -2885,7 +2885,7 @@ Republic of the Congo - + República del Congo @@ -2895,7 +2895,7 @@ Ivory Coast - + Costa de Marfil @@ -2940,7 +2940,7 @@ Curacao - + Curazao @@ -3145,12 +3145,12 @@ Hong Kong - + Hong Kong Heard Island and McDonald Islands - + Islas Heard y McDonald @@ -3270,7 +3270,7 @@ Saint Kitts and Nevis - + San Cristóbal y Nieves @@ -3310,7 +3310,7 @@ Saint Lucia - + Santa Lucía @@ -3400,7 +3400,7 @@ Myanmar - + Myanmar @@ -3410,7 +3410,7 @@ Macao - + Macao @@ -3740,7 +3740,7 @@ Turks and Caicos Islands - + Islas Turcas y Caicos @@ -3770,7 +3770,7 @@ East Timor - + Timor Oriental @@ -3795,7 +3795,7 @@ Trinidad and Tobago - + Trinidad y Tobago @@ -3825,7 +3825,7 @@ United States Minor Outlying Islands - + Islas Ultramarinas Menores de los Estados Unidos @@ -3845,12 +3845,12 @@ Vatican - + Vaticano Saint Vincent and the Grenadines - + San Vicente y las Granadinas @@ -3880,7 +3880,7 @@ Wallis and Futuna - + Wallis y Futuna @@ -3921,7 +3921,7 @@ Additional Information - + Información adicional @@ -3929,27 +3929,27 @@ ringTone - + 425,0,1000,4000,0 busyTone - + 425,0,480,480,0 congestionTone - + 425,0,240,240,0 zip - + 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 endTone - + 425,0,200,200,200,200,200,200,-1 @@ -3957,32 +3957,32 @@ 'userUri' is no valid SIP URI: %1 - + 'userUri' no es una URI SIP válida: %1 'userUri' is required - + 'userUri' es obligatorio 'registrarUri' is no valid SIP URI: %1 - + 'registrarUri' no es una URI SIP válida: %1 'registrarUri' is required - + 'registrarUri' es obligatorio 'proxies' contains invalid SIP URI entry: %1 - + 'proxies' contiene una entrada de URI SIP no válida: %1 Failed to create %1: %2 - + Error al crear %1: %2 @@ -3990,7 +3990,7 @@ %1 is now available - + %1 está disponible ahora @@ -3998,12 +3998,12 @@ Active call with %1 - + Llamada activa con %1 Hang up - + Colgar @@ -4011,34 +4011,34 @@ %1 is calling - + %1 está llamando %1 (%2) is calling - + %1 (%2) está llamando Reject - + Rechazar Accept - + Aceptar Missed call from %1 - + Llamada perdida de %1 Call back - + Devolver la llamada @@ -4046,7 +4046,7 @@ New Identity - + Nueva identidad @@ -4055,17 +4055,17 @@ Failed to write to %1 - + Error al escribir en %1 Failed to copy %1 to the config space - + Error al copiar %1 en el directorio de configuración Source file %1 does not exist - + El archivo fuente %1 no existe @@ -4073,7 +4073,7 @@ Search number - + Buscar número @@ -4081,12 +4081,12 @@ Search result category filter %1 - + Filtro de categoría de resultados de búsqueda %1 Filter for the individual search result items by category - + Filtrar los elementos de resultados de búsqueda individuales por categoría @@ -4094,22 +4094,22 @@ Contacts - + Contactos Messages - + Mensajes Rooms and Teams - + Salas y equipos Files - + Archivos @@ -4117,42 +4117,42 @@ Select number - + Seleccionar número Activate search field - + Activar campo de búsqueda Number or contact - + Número o contacto Clear search field - + Limpiar campo de búsqueda Default - Predeterminado + Predeterminado Auto - + Auto Preferred identity - + Identidad preferida Select the preferred identity for outgoing calls - + Seleccionar la identidad preferida para las llamadas salientes @@ -4160,12 +4160,12 @@ Search for contacts or room names... - + Buscar contactos o nombres de sala... Clear search field - + Limpiar campo de búsqueda @@ -4173,12 +4173,12 @@ Search result category %1 - + Categoría de resultados de búsqueda %1 Divider for the individual search result items by category - + Divisor para los elementos de resultados de búsqueda individuales por categoría @@ -4186,12 +4186,12 @@ Search result - + Resultado de búsqueda Currently selected search result - + Resultado de búsqueda seleccionado actualmente @@ -4199,17 +4199,17 @@ Phone number - + Número de teléfono Selected favorite number %1 - + Número favorito seleccionado %1 Selected phone number %1 - + Número de teléfono seleccionado %1 @@ -4217,52 +4217,52 @@ Search filter and identity selection - + Filtro de búsqueda y selección de identidad Select search filter to be applied, as well as the outgoing identity - + Seleccionar el filtro de búsqueda a aplicar, así como la identidad saliente Outgoing identity - + Identidad saliente Search results - + Resultados de búsqueda All search results will be listed here in their respective categories - + Aquí se listarán todos los resultados de búsqueda en sus respectivas categorías Direct dial - + Marcación directa Call "%1" - + Llamar a "%1" Open room "%1" - + Abrir sala "%1" History - Historia + Historia Contacts - + Contactos @@ -4270,314 +4270,314 @@ Settings - + Ajustes Use dark mode tray icon - + Usar icono de bandeja en modo oscuro Inverse Accept / Reject buttons - + Invertir botones de aceptar/rechazar Show chat messages as desktop notifications - + Mostrar mensajes de chat como notificaciones de escritorio Enable USB headset driver [%1] - + Activar controlador de auriculares USB [%1] not detected - + no detectado Disable USB headset mute state propagation - + Desactivar la propagación del estado de silencio de los auriculares USB Show dial window on USB headset pick up - + Mostrar ventana de marcación al descolgar los auriculares USB Color scheme - + Esquema de color System default - + Predeterminado del sistema Light - + Claro Dark - + Oscuro Reload contacts from LDAP - + Recargar contactos desde LDAP Phoning - + Telefonía Appearance - + Apariencia Signalling busy when a call is active - + Señalizar ocupado cuando hay una llamada activa Rules for telephone number transmission - + Reglas para la transmisión del número de teléfono Standard preferred identity - + Identidad preferida estándar Default - Predeterminado + Predeterminado Auto - + Auto Prefer USB headset ring sound if available - + Preferir el sonido del tono del auricular USB si está disponible No preferred identities yet. - + Aún no hay identidades preferidas. Show main window on startup - + Mostrar ventana principal al iniciar Disable synchronisation with the system mute state - + Desactivar la sincronización con el estado de silencio del sistema restart required - + se requiere reinicio Use custom window decoration - + Usar decoración de ventana personalizada Theme selection box - + Cuadro de selección de tema Select the UI theme - + Seleccionar el tema de la interfaz Currently selected theme option - + Opción de tema seleccionada actualmente Prefererred identity selection - + Selección de identidad preferida Select the preferred identity - + Seleccionar la identidad preferida Currently selected identity option - + Opción de identidad seleccionada actualmente Currently highlighted preferred identity. Tap to edit. - + Identidad preferida destacada actualmente. Toque para editar. Standard - + Estándar Add identity - + Añadir identidad Add a new preferred identity entry - + Añadir una nueva entrada de identidad preferida Audio settings - + Ajustes de audio Input device - Dispositivo de entrada + Dispositivo de entrada Input device selection - + Selección del dispositivo de entrada Select the input device to be used - + Seleccionar el dispositivo de entrada que se debe usar Currently selected input option - + Opción de entrada seleccionada actualmente Output device - Dispositivo de salida + Dispositivo de salida Output device selection - + Selección del dispositivo de salida Select the output device to be used - + Seleccionar el dispositivo de salida que se debe usar Currently selected output option - + Opción de salida seleccionada actualmente Output device for ring tone - Dispositivo de salida para tono de llamada + Dispositivo de salida para tono de llamada Currently selected ring output option - + Opción de salida del tono de llamada seleccionada actualmente Ring tone - + Tono de llamada Reset ring tone - + Restablecer tono de llamada Reset the ring tone to its default option - + Restablecer el tono de llamada a su opción predeterminada Pick ring tone - + Elegir tono de llamada Select the ring tone you want to use for incoming calls - + Seleccionar el tono de llamada que desea usar para las llamadas entrantes Currently set to: - + Configurado actualmente en: Ring tone volume - + Volumen del tono de llamada Adjust %1 - + Ajustar %1 %1 % - + %1 % Pause between ring tones [s] - + Pausa entre tonos de llamada [s] Debugging - + Depuración Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. - + Use este botón para iniciar una sesión de depuración. La aplicación se reiniciará y comenzará a registrar información adicional útil para la depuración. Durante esta sesión, regrese aquí para descargar la información. Una sesión de depuración está limitada a 5 minutos, tras los cuales la aplicación se reiniciará automáticamente en modo normal. Start debug run (restart app) - + Iniciar sesión de depuración (reiniciar aplicación) Download debug information - + Descargar información de depuración @@ -4585,62 +4585,62 @@ Shortcuts - + Atajos de teclado Shortcut key: %1 - + Tecla de atajo: %1 Local shortcuts (work only when app is focused) - + Atajos locales (funcionan solo cuando la aplicación está en primer plano) Local shortcuts - + Atajos locales Ctrl + F - + Ctrl + F Activates the global search field - + Activa el campo de búsqueda global F11 - + F11 Toggles between fullsceen and normal window mode - + Alterna entre el modo de pantalla completa y el modo de ventana normal Ctrl + Shift + M - + Ctrl + Shift + M Toggles audio mute - + Activa/desactiva el silencio del audio Global shortcuts (work from anywhere) - + Atajos globales (funcionan desde cualquier lugar) Global shortcuts - + Atajos globales @@ -4648,143 +4648,143 @@ Initial configuration - + Configuración inicial Error: %1 - + Error: %1 SIP wizard notification - + Notificación del asistente SIP GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. - + GOnnect no puede encontrar una configuración SIP. Para comenzar, seleccione una de las plantillas a continuación y modifique el archivo de configuración resultante si es necesario. Please pick: - + Por favor, seleccione: Select SIP template - + Seleccionar plantilla SIP Select the SIP template to be used - + Seleccionar la plantilla SIP que se debe usar Currently selected SIP template - + Plantilla SIP seleccionada actualmente Next - + Siguiente Continue setup - + Continuar la configuración Confirmation button to continue the setup - + Botón de confirmación para continuar la configuración Template field list - + Lista de campos de la plantilla List of all the available SIP template options - + Lista de todas las opciones de plantilla SIP disponibles SIP template option - + Opción de plantilla SIP Currently selected SIP template option - + Opción de plantilla SIP seleccionada actualmente Display name of the SIP template option - + Nombre visible de la opción de plantilla SIP Description of the SIP template option - + Descripción de la opción de plantilla SIP Back - + Atrás Back button to return to the template selection menu - + Botón de retroceso para volver al menú de selección de plantillas Finish - + Finalizar Confirmation button to apply the changes to the SIP template - + Botón de confirmación para aplicar los cambios a la plantilla SIP Successful configuration file creation - + Archivo de configuración creado correctamente We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. - + Hemos creado un archivo de configuración para usted. Por favor, compruebe si es necesario realizar algún cambio para adaptarlo a sus necesidades y reinicie GOnnect para activarlo. The configuration has been saved to: - + La configuración se ha guardado en: Copy to clipboard - + Copiar al portapapeles Copy the full path of the configuration file to the clipboard - + Copiar la ruta completa del archivo de configuración al portapapeles Finish wizard - + Finalizar asistente Finish the SIP configuration wizard - + Finalizar el asistente de configuración SIP @@ -4792,27 +4792,27 @@ Show dial window and focus search field - + Mostrar la ventana de marcación y enfocar el campo de búsqueda End all calls - + Terminar todas las llamadas Redial last outgoing call - + Rellamar la última llamada realizada Toggle hold - + Alternar espera Phone calls are active - + Hay llamadas telefónicas activas @@ -4821,47 +4821,47 @@ Dial... - + Marcar... Not registered... - + No registrado... Settings - + Ajustes About - Sobre + Sobre Quit - Terminar + Terminar End conference - + Terminar conferencia Call with %1 has ended - + La llamada con %1 ha finalizado Accept call with %1 - + Aceptar llamada de %1 Hang up call with %1 - + Colgar llamada con %1 @@ -4869,32 +4869,32 @@ File path - + Ruta del archivo Enter the file path for %1 - + Introducir la ruta del archivo para %1 Choose... - + Elegir... Open file picker - + Abrir selector de archivo Select the file that should be used for %1 - + Seleccionar el archivo que se debe usar para %1 Certificate files (%1) - + Archivos de certificado (%1) @@ -4902,12 +4902,12 @@ Text input - + Entrada de texto Enter the desired value for %1 - + Introducir el valor deseado para %1 @@ -4915,7 +4915,7 @@ Failed to toggle the state of %1. - + Error al cambiar el estado de %1. @@ -4923,17 +4923,17 @@ Toggler list - + Lista de alternancia List of items that can be toggled - + Lista de elementos que se pueden alternar Toggle %1 - + Alternar %1 @@ -4941,7 +4941,7 @@ Level meter - + Medidor de nivel @@ -4949,7 +4949,7 @@ Virtual background - + Fondo virtual @@ -4957,36 +4957,36 @@ Save File - + Guardar archivo %n minute(s) - - - + + %n minuto + %n minutos 1 hour and %n minute(s) - - - + + 1 hora y %n minuto + 1 hora y %n minutos %n hour(s) - - - + + %n hora + %n horas Audio Files (%1) - + Archivos de audio (%1) @@ -4994,22 +4994,22 @@ Adjust the volume - + Ajustar el volumen Unmute - + Activar sonido Mute - + Silenciar Open audio settings - + Abrir ajustes de audio @@ -5017,147 +5017,147 @@ Add widget - + Añadir widget Widget - + Widget Widget selection header - + Encabezado de selección de widget Date Events - + Fechas List of upcoming appointments - + Lista de citas próximas Favorites - Favoritos + Favoritos Quick dial for your favorite contacts and conferences - + Marcación rápida para sus contactos y conferencias favoritos History - Historia + Historia Web View - + Vista web A web-based content display - + Visualizador de contenido web Widget selection - + Selección de widget Select the widget that should be added to the current dashboard page - + Seleccionar el widget que se debe añadir a la página actual del panel Currently selected widget option - + Opción de widget seleccionada actualmente Accept all certificates - + Aceptar todos los certificados Confirm widget selection - + Confirmar selección de widget Confirmation button to create and add the selected widget to the current dashboard - + Botón de confirmación para crear y añadir el widget seleccionado al panel actual Searchable call and conference history - + Historial de llamadas y conferencias con búsqueda Title - + Título URL - + URL URL (dark mode) - + URL (modo oscuro) Settings text input - + Entrada de texto de configuración Input for widget setting %1 - + Entrada para la configuración del widget %1 Settings checkbox - + Casilla de verificación de configuración Checkbox for widget setting %1 - + Casilla de verificación para la configuración del widget %1 Widget setting %1 - + Configuración del widget %1 Cancel - Cancelar + Cancelar Cancel widget selection - + Cancelar selección de widget Cancel button to exit widget selection selection without changes - + Botón de cancelar para salir de la selección de widget sin cambios Add - + Añadir @@ -5165,22 +5165,22 @@ GOnnect window header - + Encabezado de la ventana de GOnnect Minimize - + Minimizar Maximize - + Maximizar Close GOnnect window - + Cerrar la ventana de GOnnect diff --git a/i18n/gonnect_fa.ts b/i18n/gonnect_fa.ts new file mode 100644 index 00000000..67cb9bcf --- /dev/null +++ b/i18n/gonnect_fa.ts @@ -0,0 +1,5186 @@ + + + + + AboutWindow + + + About + درباره + + + + GOnnect headline + سرتیتر GOnnect + + + + GOnnect version + نسخه GOnnect + + + + Version: v%1 + نسخه: v%1 + + + + Copy to clipboard + کپی در کلیپ‌بورد + + + + Copy the currently used version of Gonnect to the clipboard + کپی نسخه فعلی GOnnect در کلیپ‌بورد + + + + + + + Homepage + صفحه اصلی + + + + Visit the project homepage + بازدید از صفحه اصلی پروژه + + + + Bug Tracker + ردیاب خطا + + + + Visit the project bug tracker + بازدید از ردیاب خطای پروژه + + + + Documentation + مستندات + + + + Visit the online project documentation + بازدید از مستندات آنلاین پروژه + + + + AdditionalInfo + + + Additional caller related information + اطلاعات بیشتر درباره تماس‌گیرنده + + + + List of informational items regarding the caller, such as open support tickets + فهرست اطلاعات تماس‌گیرنده مانند تیکت‌های پشتیبانی باز + + + + Expandable response section + بخش پاسخ قابل گسترش + + + + AudioDeviceMenu + + + Default + پیش‌فرض + + + + AudioEnvWindow + + + Unknown audio environment + محیط صوتی ناشناخته + + + + Audio environment error + خطای محیط صوتی + + + + No fitting audio environment could be found. Please select the desired audio devices. + محیط صوتی مناسب یافت نشد. لطفاً دستگاه‌های صوتی دلخواه را انتخاب کنید. + + + + Input device selection header + عنوان انتخاب دستگاه ورودی + + + + Header for the input device selection below + عنوان برای انتخاب دستگاه ورودی زیر + + + + Input device + دستگاه ورودی + + + + Input device selection box + کادر انتخاب دستگاه ورودی + + + + Select the input device that should be used + انتخاب دستگاه ورودی + + + + Currently selected input device + دستگاه ورودی انتخاب‌شده + + + + Output device selection header + عنوان انتخاب دستگاه خروجی + + + + Header for the output device selection below + عنوان برای انتخاب دستگاه خروجی زیر + + + + Output device + دستگاه خروجی + + + + Output device selection box + کادر انتخاب دستگاه خروجی + + + + Select the output device that should be used + انتخاب دستگاه خروجی + + + + Currently selected output device + دستگاه خروجی انتخاب‌شده + + + + Ring tone output device + دستگاه خروجی زنگ + + + + Output device for ring tone + دستگاه خروجی برای زنگ + + + + Ring tone output device selection box + کادر انتخاب دستگاه خروجی زنگ + + + + Select the output device that should be used for playing the ring tone + انتخاب دستگاه خروجی برای پخش زنگ + + + + Currently selected ring tone output device + دستگاه خروجی زنگ انتخاب‌شده + + + + Ok + تأیید + + + + Close audio environment selection + بستن انتخاب محیط صوتی + + + + Confirmation button to leave the audio environment selection window + دکمه تأیید برای خروج از پنجره انتخاب محیط صوتی + + + + AudioLevelButton + + + Change volume + تغییر صدا + + + + AudioManager + + + Default input + ورودی پیش‌فرض + + + + Default output + خروجی پیش‌فرض + + + + AvatarImage + + + Initials of this contact + حروف اول نام مخاطب + + + + BaseDialog + + + Dialog + گفتگو + + + + BasePage + + + Base dashboard page grid + شبکه پایه صفحه داشبورد + + + + Canvas for editable dashboard pages + بوم صفحات داشبورد قابل ویرایش + + + + Add widgets + افزودن ابزارک + + + + BaseWidget + + + Drag widget + کشیدن ابزارک + + + + Change the position of the widget + تغییر موقعیت ابزارک + + + + Remove widget + حذف ابزارک + + + + Remove the currently selected widget from the dashboard + حذف ابزارک انتخاب‌شده از داشبورد + + + + Resize widget + تغییر اندازه ابزارک + + + + Resize the widget according to the mouse direction + تغییر اندازه ابزارک بر اساس جهت ماوس + + + + BaseWindow + + + + + + + + + + Drag border + لبه کشیدنی + + + + Top left drag border for window resize operations + لبه کشیدنی بالا چپ برای تغییر اندازه پنجره + + + + Top drag border for window resize operations + لبه کشیدنی بالا برای تغییر اندازه پنجره + + + + Top right border for window resize operations + لبه بالا راست برای تغییر اندازه پنجره + + + + Right drag border for window resize operations + لبه کشیدنی راست برای تغییر اندازه پنجره + + + + Bottom right drag border for window resize operations + لبه کشیدنی پایین راست + + + + Bottom drag border for window resize operations + لبه کشیدنی پایین برای تغییر اندازه پنجره + + + + Bottom left drag border for window resize operations + لبه کشیدنی پایین چپ + + + + Left drag border for window resize operations + لبه کشیدنی چپ برای تغییر اندازه پنجره + + + + BottomStatusBar + + + Status bar + نوار وضعیت + + + + BurgerMenu + + + Toggle fullscreen + تغییر حالت تمام‌صفحه + + + + Shortcuts... + میانبرها... + + + + Customize UI + سفارشی‌سازی رابط کاربری + + + + About... + درباره... + + + + Quit + خروج + + + + Call + + + Conference + کنفرانس + + + + Drag bar + نوار کشیدنی + + + + CallButtonBar + + + %1@%2 kHz + %1@%2 کیلوهرتز + + + + Transmit + ارسال + + + + Call security level + سطح امنیت تماس + + + + Security level of the ongoing call + سطح امنیت تماس جاری + + + + Call security details + جزئیات امنیت تماس + + + + Detailed call security status: %1 / %2 + وضعیت دقیق امنیت تماس: %1 / %2 + + + + signaling encrypted + سیگنال‌دهی رمزنگاری‌شده + + + + signaling unencrypted + سیگنال‌دهی رمزنگاری‌نشده + + + + media encrypted + رسانه رمزنگاری‌شده + + + + media unencrypted + رسانه رمزنگاری‌نشده + + + + Call quality + کیفیت تماس + + + + Quality of the ongoing call + کیفیت تماس جاری + + + + Transmission statistics + آمار ارسال + + + + + Call quality metrics + معیارهای کیفیت تماس + + + + + MOS + MOS + + + + + Mean opinion score + امتیاز میانگین نظر (MOS) + + + + Numerical metric assessing transmission-side voice call quality: %1 + معیار عددی کیفیت صدا در سمت ارسال: %1 + + + + + Packet loss + اتلاف بسته + + + + %1% of packets lost in transmission + %1٪ از بسته‌ها در ارسال از دست رفت + + + + + Jitter + جیتر + + + + Amount of transmission side jitter: %1 + میزان جیتر در سمت ارسال: %1 + + + + + Effective delay + تأخیر مؤثر + + + + Effective transmission side call delay: %1 + تأخیر مؤثر تماس در سمت ارسال: %1 + + + + Receiver statistics + آمار دریافت + + + + Receive + دریافت + + + + Numerical metric assessing receiver-side voice/video call quality: %1 + معیار عددی کیفیت صدا/تصویر در سمت دریافت: %1 + + + + %1% of packets lost in receival + %1٪ از بسته‌ها در دریافت از دست رفت + + + + Amount of receiver side jitter: %1 + میزان جیتر در سمت دریافت: %1 + + + + Effective receiver side call delay: %1 + تأخیر مؤثر در سمت دریافت: %1 + + + + Codec + کدک + + + + Audio codec + کدک صوتی + + + + The currently used audio codec and frequency: %1 + کدک صوتی و فرکانس فعلی: %1 + + + + Elapsed call time + زمان سپری‌شده تماس + + + + The duration in seconds the call has been active for: %1 + مدت زمان فعال بودن تماس به ثانیه: %1 + + + + Screen + صفحه‌نمایش + + + + Screensharing control + کنترل اشتراک‌گذاری صفحه + + + + Start sharing your screen + شروع اشتراک‌گذاری صفحه + + + + Camera + دوربین + + + + Camera control + کنترل دوربین + + + + Enable your camera + فعال‌سازی دوربین + + + + Resume + ادامه + + + + Hold + نگه‌داشتن + + + + Resume call + ادامه تماس + + + + Hold call + نگه‌داشتن تماس + + + + Update the call hold state + به‌روزرسانی وضعیت نگه‌داری تماس + + + + Micro + میکروفون + + + + Input control + کنترل ورودی + + + + Set the mute state of the current input device + تنظیم وضعیت بی‌صدا کردن دستگاه ورودی + + + + Output + خروجی + + + + Output control + کنترل خروجی + + + + Change the current output devices + تغییر دستگاه‌های خروجی فعلی + + + + Accept call + پذیرفتن تماس + + + + Hangup call + قطع تماس + + + + CallDetails + + + SIP call status code + کد وضعیت تماس SIP + + + + The current status code of the call: %1 + کد وضعیت فعلی تماس: %1 + + + + Jitsi Meet + Jitsi Meet + + + + Switch to a Jitsi Meet session + رفتن به جلسه Jitsi Meet + + + + Unhold + برداشتن نگه‌داری + + + + Hold + نگه‌داشتن + + + + Toggle the hold state to %1 + تغییر وضعیت نگه‌داری به %1 + + + + Accept call + پذیرفتن تماس + + + + Hangup call + قطع تماس + + + + CallHistory + + + Failed to create directory %1 to store the call history database. + ایجاد پوشه %1 برای پایگاه داده تاریخچه تماس ناموفق بود. + + + + Failed to open call history database: %1 + باز کردن پایگاه داده تاریخچه تماس ناموفق بود: %1 + + + + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. + پایگاه داده تاریخچه تماس ناسازگار است. %1 را حذف کنید و برنامه را مجدداً راه‌اندازی کنید. + + + + CallItem + + + Call + تماس + + + + Selected call %1 - contact %2, company %3, location %4/%5, number %6 + تماس انتخاب‌شده %1 - مخاطب %2، شرکت %3، مکان %4/%5، شماره %6 + + + + Hangup button + دکمه قطع + + + + Pressing this will end the call + فشردن این دکمه تماس را پایان می‌دهد + + + + CallList + + + Drag callers onto each other to transfer call + تماس‌گیرندگان را روی هم بکشید تا تماس منتقل شود + + + + List of active calls + فهرست تماس‌های فعال + + + + + Create conference + ایجاد کنفرانس + + + + CallSideBar + + + Chat + گفتگو + + + + Person(s) + + %n نفر + %n نفر + + + + + Info + اطلاعات + + + + CallerBigAvatar + + + Caller name + نام تماس‌گیرنده + + + + is calling... + در حال تماس... + + + + Calling... + در حال برقراری تماس... + + + + CallsModel + + + unknown number + شماره ناشناس + + + + CardList + + + List of configurable options + فهرست گزینه‌های قابل تنظیم + + + + ChatMessageBox + + + Message + پیام + + + + Type message + پیام تایپ کنید + + + + Enter the chat text message + پیام متنی گفتگو را وارد کنید + + + + ChatMessageList + + + Chat message list + فهرست پیام‌های گفتگو + + + + List of all chat messages of the current chat room + فهرست همه پیام‌های اتاق گفتگوی فعلی + + + + Auto scroll down + پیمایش خودکار به پایین + + + + ChatMessageListItem + + + Chat message + پیام گفتگو + + + + Selected chat message - from %1, at %2: %3 + پیام انتخاب‌شده - از %1، در %2: %3 + + + + ChatRoomList + + + Chat room list + فهرست اتاق‌های گفتگو + + + + List of all chat rooms + فهرست همه اتاق‌های گفتگو + + + + ChatRoomListItem + + + Chat room + اتاق گفتگو + + + + Selected chat room %1: %2 unread messages + اتاق انتخاب‌شده %1: %2 پیام خوانده‌نشده + + + + ChatSideBar + + + Chat message list + فهرست پیام‌های گفتگو + + + + List of all the messages in the current chat + فهرست همه پیام‌های گفتگوی فعلی + + + + Chat message + پیام گفتگو + + + + Selected chat message from %1 at %2: %3 + پیام انتخاب‌شده از %1 در %2: %3 + + + + the server + سرور + + + + you + شما + + + + Select emoji + انتخاب شکلک + + + + Enter chat message... + پیام را وارد کنید... + + + + Chats + + + Please enter your recovery key to decrypt messages: + لطفاً کلید بازیابی را برای رمزگشایی پیام‌ها وارد کنید: + + + + Recovery key + کلید بازیابی + + + + Enter recovery key + کلید بازیابی را وارد کنید + + + + Use key + استفاده از کلید + + + + Use recovery key + استفاده از کلید بازیابی + + + + ClipboardButton + + + Copy to clipboard: %1 + کپی در کلیپ‌بورد: %1 + + + + Conference + + + + Set room name + تنظیم نام اتاق + + + + + Room name: + نام اتاق: + + + + + Enter the room name + نام اتاق را وارد کنید + + + + Authenticate + احراز هویت + + + + Please authenticate in the opened browser window... + لطفاً در پنجره مرورگر باز‌شده احراز هویت کنید... + + + + This conference is protected by a password. Please enter it to join the room. + این کنفرانس با رمز عبور محافظت شده است. رمز را برای ورود وارد کنید. + + + + + Password + رمز عبور + + + + + Enter the password + رمز عبور را وارد کنید + + + + Remember password + به‌خاطر سپردن رمز + + + + + Cancel + لغو + + + + Join Room + ورود به اتاق + + + + Password required + رمز عبور لازم است + + + + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. + رمز عبوری برای محافظت از اتاق وارد کنید. سایر شرکت‌کنندگان باید آن را پیش از ورود وارد کنند. + + + + This password has been set for the conference room and must be entered by participants before taking part in the session. + این رمز برای اتاق تنظیم شده و شرکت‌کنندگان باید آن را پیش از ورود وارد کنند. + + + + The room password has been set by someone else. + رمز اتاق توسط شخص دیگری تنظیم شده است. + + + + Show password + نمایش رمز + + + + Remove + حذف + + + + Save + ذخیره + + + + Video quality + کیفیت تصویر + + + + Change the video quality of this meeting + تغییر کیفیت تصویر این جلسه + + + + No video (audio only) + بدون تصویر (فقط صدا) + + + + Lowest quality + کمترین کیفیت + + + + Standard quality + کیفیت استاندارد + + + + Highest quality + بیشترین کیفیت + + + + Close + بستن + + + + Drag bar + نوار کشیدنی + + + + ConferenceButtonBar + + + %n minutes left + + %n دقیقه باقی‌مانده + %n دقیقه باقی‌مانده + + + + + Conference room + اتاق کنفرانس + + + + Share + اشتراک‌گذاری + + + + Copy room name + کپی نام اتاق + + + + Copy room link + کپی لینک اتاق + + + + Open in browser + باز کردن در مرورگر + + + + Show phone number + نمایش شماره تلفن + + + + Raise + بلند کردن دست + + + + Resume + ادامه + + + + Hold + نگه‌داشتن + + + + View + نما + + + + Screen + صفحه‌نمایش + + + + Share window + اشتراک‌گذاری پنجره + + + + Share screen + اشتراک‌گذاری صفحه + + + + Camera + دوربین + + + + Output + خروجی + + + + More + بیشتر + + + + Noise supression + حذف نویز + + + + Toggle subtitles + روشن/خاموش کردن زیرنویس + + + + Toggle whiteboard + روشن/خاموش کردن تخته سفید + + + + Video quality... + کیفیت تصویر... + + + + Set room password... + تنظیم رمز اتاق... + + + + Mute everyone + بی‌صدا کردن همه + + + + Leave conference + ترک کنفرانس + + + + End conference for all + پایان کنفرانس برای همه + + + + Micro + میکروفون + + + + ConfirmDialog + + + Cancel + لغو + + + + ControlBar + + + App menu + منوی برنامه + + + + Credentials + + + storing credentials failed: %1 + ذخیره اعتبارنامه ناموفق بود: %1 + + + + reading credentials failed: %1 + خواندن اعتبارنامه ناموفق بود: %1 + + + + CredentialsDialog + + + Authentication failed + احراز هویت ناموفق بود + + + + Please enter the password: + لطفاً رمز عبور را وارد کنید: + + + + Password + رمز عبور + + + + Enter the password + رمز عبور را وارد کنید + + + + Ok + تأیید + + + + CustomWindowHeader + + + GOnnect window header + عنوان پنجره GOnnect + + + + App menu + منوی برنامه + + + + Close GOnnect window + بستن پنجره GOnnect + + + + DateEventManager + + + Conference starting soon + کنفرانس به زودی شروع می‌شود + + + + Appointment starting soon + قرار ملاقات به زودی شروع می‌شود + + + + Join + پیوستن + + + + Open + باز کردن + + + + DateEventsList + + + Date events + رویدادهای تقویم + + + + List of all the currently active and upcoming date events + فهرست همه رویدادهای فعال و آینده + + + + Date event section + بخش رویدادها + + + + Header for %1 + عنوان برای %1 + + + + Today - %1 + امروز - %1 + + + + + yyyy/MM/dd + yyyy/MM/dd + + + + Tomorrow - %1 + فردا - %1 + + + + dddd - yyyy/MM/dd + dddd - yyyy/MM/dd + + + + Date event + رویداد تقویم + + + + Currently selected date event: %1, starting time %2, remaining time %3 + رویداد انتخاب‌شده: %1، شروع %2، باقی‌مانده %3 + + + + + hh:mm + hh:mm + + + + All day + تمام روز + + + + till %1 + تا %1 + + + + in %1 + در %1 + + + + Join + پیوستن + + + + Open + باز کردن + + + + Join meeting + پیوستن به جلسه + + + + Join the meeting associated with the currently selected event + پیوستن به جلسه رویداد انتخاب‌شده + + + + Copy room link + کپی لینک اتاق + + + + Copy link + کپی لینک + + + + Copy meeting link + کپی لینک جلسه + + + + Copy the meeting link associated with the currently selected event + کپی لینک جلسه رویداد انتخاب‌شده + + + + DateEventsWidget + + + Appointments + قرارها + + + + Loading appointments... + بارگذاری قرارها... + + + + No upcoming appointments + هیچ قراری در پیش نیست + + + + Date event widget status + وضعیت ابزارک رویدادها + + + + Displays the current status of the widget: %1 + نمایش وضعیت فعلی ابزارک: %1 + + + + DialInInfo + + + Call one of the phone numbers below and use this code for authentication: + با یکی از شماره‌های زیر تماس بگیرید و از این کد برای احراز هویت استفاده کنید: + + + + Close + بستن + + + + DtmfDialer + + + Number pad + صفحه‌کلید عددی + + + + Character %1 + کاراکتر %1 + + + + EditModeOptions + + + Add page + افزودن صفحه + + + + Add a new dashboard page + افزودن صفحه جدید به داشبورد + + + + Add widget + افزودن ابزارک + + + + Add a new widget to the current dashboard page + افزودن ابزارک جدید به صفحه فعلی + + + + Finished + تمام شد + + + + Finish and save all dashboard and widget changes + پایان و ذخیره همه تغییرات + + + + EmergencyCallIncomingWindow + + + Emergency Call + تماس اضطراری + + + + Incoming emergency call from %1 + تماس اضطراری ورودی از %1 + + + + Answering the call will automatically terminate all other ongoing calls. + پاسخ دادن به این تماس، سایر تماس‌های جاری را خودکار پایان می‌دهد. + + + + Answer + پاسخ + + + + EmojiButton + + + Emoji + شکلک + + + + Selected Emoji: %1 + شکلک انتخاب‌شده: %1 + + + + EmojiPicker + + + Switch Emoji category + تغییر دسته شکلک + + + + Select Emoji + انتخاب شکلک + + + + EnumTranslation + + + Trying + در حال تلاش + + + + Ringing + در حال زنگ زدن + + + + Call being forwarded + تماس در حال انتقال + + + + Queued + در صف + + + + Progress + در حال پیشرفت + + + + Ok + تأیید + + + + Accepted + پذیرفته شد + + + + Unauthorized + غیرمجاز + + + + + Rejected + رد شد + + + + Not found + یافت نشد + + + + Proxy authentication required + احراز هویت پروکسی لازم است + + + + Request timeout + مهلت درخواست به پایان رسید + + + + Temporarily unavailable + موقتاً در دسترس نیست + + + + Ambiguous + مبهم + + + + Busy here + اینجا مشغول است + + + + Request terminated + درخواست پایان یافت + + + + Not acceptable here + اینجا قابل قبول نیست + + + + Internal server error + خطای داخلی سرور + + + + Not implemented + پیاده‌سازی نشده + + + + Bad gateway + دروازه نامعتبر + + + + Service unavailable + سرویس در دسترس نیست + + + + Server timeout + مهلت سرور به پایان رسید + + + + Busy everywhere + همه جا مشغول + + + + Decline + رد + + + + Does not exist anywhere + هیچ جا وجود ندارد + + + + Not acceptable anywhere + هیچ جا قابل قبول نیست + + + + Unwanted + ناخواسته + + + + + + + Unknown + ناشناس + + + + Commercial + تجاری + + + + Home + خانه + + + + Mobile + موبایل + + + + Incoming + ورودی + + + + Outgoing + خروجی + + + + Blocked + مسدود + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + FavIcon + + + Set favorite + افزودن به علاقه‌مندی‌ها + + + + Unset favorite + حذف از علاقه‌مندی‌ها + + + + FavoriteListItemBig + + + Favorite contact + مخاطب مورد علاقه + + + + Selected favorite %1: %2 + مورد علاقه انتخاب‌شده %1: %2 + + + + tap to start meeting %1 + ضربه بزنید تا جلسه %1 را شروع کنید + + + + tap to call %1 + ضربه بزنید تا با %1 تماس بگیرید + + + + FavoriteListItemSmall + + + Favorite contact + مخاطب مورد علاقه + + + + Selected favorite %1: %2 + مورد علاقه انتخاب‌شده %1: %2 + + + + tap to call %1 + ضربه بزنید تا با %1 تماس بگیرید + + + + FavoritesList + + + + Favorites + علاقه‌مندی‌ها + + + + List of all contacts that have been marked as favorites + فهرست همه مخاطبان علاقه‌مندی + + + + FavoritesWidget + + + Favorites + علاقه‌مندی‌ها + + + + No favorites to display + هیچ موردی برای نمایش وجود ندارد + + + + FirstAid + + + First Aid + کمک‌های اولیه + + + + Clicking one of these buttons will end all current calls and start an emergency call. + کلیک بر این دکمه‌ها همه تماس‌های جاری را پایان داده و تماس اضطراری برقرار می‌کند. + + + + Tap to call emergency contact: %1 (%2) + ضربه بزنید تا با مخاطب اضطراری تماس بگیرید: %1 (%2) + + + + Close + بستن + + + + Exit the first aid menu without initiating any action + خروج از منوی کمک‌های اولیه بدون انجام اقدام + + + + Close first aid menu + بستن منوی کمک‌های اولیه + + + + FirstAidButton + + + Open first aid menu + باز کردن منوی کمک‌های اولیه + + + + First Aid + کمک‌های اولیه + + + + GonnectWindow + + + Home + خانه + + + + HeadsetDevice + + + MMM dd + dd MMM + + + + HeadsetDeviceProxy + + + Ringing + در حال زنگ زدن + + + + Calling + در حال تماس + + + + + + + Call active + تماس فعال + + + + Call waiting + تماس در انتظار + + + + On Hold + در انتظار + + + + + Call ended + تماس پایان یافت + + + + Phone conference + کنفرانس تلفنی + + + + HistoryList + + + + No past calls + هیچ تماس قبلی وجود ندارد + + + + History + تاریخچه + + + + Searchable list of past calls and meetings + فهرست قابل جستجوی تماس‌ها و جلسات گذشته + + + + History item section + بخش آیتم تاریخچه + + + + Header for the currently selected day: %1 + عنوان روز انتخاب‌شده: %1 + + + + History item + آیتم تاریخچه + + + + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 + آیتم انتخاب‌شده %1 - شرکت %2، مکان %3، شماره %4، زمان %5، مدت %6 + + + + hh:mm + hh:mm + + + + HistoryListContextMenu + + + Call + تماس + + + + Copy number + کپی شماره + + + + Remove favorite + حذف از علاقه‌مندی‌ها + + + + Add favorite + افزودن به علاقه‌مندی‌ها + + + + Remind when available + یادآوری هنگام در دسترس بودن + + + + Unblock + رفع انسداد + + + + Block for 8 hours + مسدود کردن برای ۸ ساعت + + + + HistoryWidget + + + History + تاریخچه + + + + + All + همه + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + History call type picker + انتخاب نوع تماس در تاریخچه + + + + Select the call type to filter by + نوع تماس برای فیلتر را انتخاب کنید + + + + Currently selected call type + نوع تماس انتخاب‌شده + + + + Incoming + ورودی + + + + Outgoing + خروجی + + + + Missed + از دست رفته + + + + History call origin picker + انتخاب منشأ تماس در تاریخچه + + + + Select the call origin to filter by + منشأ تماس برای فیلتر را انتخاب کنید + + + + Currently selected call origin + منشأ تماس انتخاب‌شده + + + + Hide history search + پنهان کردن جستجوی تاریخچه + + + + Show history search + نمایش جستجوی تاریخچه + + + + IMHandler + + + + + + Ad hoc conference + کنفرانس فوری + + + + IdentitySelector + + + Default + پیش‌فرض + + + + Auto + خودکار + + + + Identity selection + انتخاب هویت + + + + Select the preferred identity to be used in calls + انتخاب هویت ترجیحی برای تماس‌ها + + + + Currently selected identity + هویت انتخاب‌شده + + + + InfoDialog + + + Ok + تأیید + + + + JitsiConnector + + + New chat message + پیام جدید + + + + Unnamed participant + شرکت‌کننده بی‌نام + + + + Active conference + کنفرانس فعال + + + + Hang up + قطع تماس + + + + %1 has joined the conference + %1 به کنفرانس پیوست + + + + %1 has left the conference + %1 کنفرانس را ترک کرد + + + + JitsiHistoryListContextMenu + + + Start conference + شروع کنفرانس + + + + Remove favorite + حذف از علاقه‌مندی‌ها + + + + Add favorite + افزودن به علاقه‌مندی‌ها + + + + Copy room name + کپی نام اتاق + + + + LDAPAddressBookFeeder + + + Failed to initialize LDAP connection + راه‌اندازی اتصال LDAP ناموفق بود + + + + + + LDAP error: %1 + خطای LDAP: %1 + + + + + Parse error: %1 + خطای تجزیه: %1 + + + + LDAP timeout: %1 + مهلت LDAP: %1 + + + + Failed to initialize LDAP connection: %1 + راه‌اندازی اتصال LDAP ناموفق بود: %1 + + + + LinuxDesktopSearchProvider + + + Call + تماس + + + + Main + + + No system tray available + سینی سیستم در دسترس نیست + + + + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. + GOnnect از طریق سینی سیستم دسترسی سریع فراهم می‌کند. محیط کاری شما آن را ارائه نمی‌دهد. + + + + Information + اطلاعات + + + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + GOnnect برای سیستم‌عامل شما در حال آزمایش است و هنوز رسماً منتشر نشده. ممکن است خطاهایی وجود داشته باشد. آن‌ها را در GitHub گزارش دهید. آزمایش خوبی داشته باشید! + + + + There are still phone calls going on, do you really want to quit? + هنوز تماس‌های تلفنی در جریان است. آیا واقعاً می‌خواهید خارج شوید؟ + + + + Please enter the password for %1: + لطفاً رمز عبور %1 را وارد کنید: + + + + Please enter the recovery key for %1: + لطفاً کلید بازیابی %1 را وارد کنید: + + + + Please enter the password for the SIP account: + لطفاً رمز عبور حساب SIP را وارد کنید: + + + + End all calls + پایان دادن به همه تماس‌ها + + + + Do you really want to close this window and terminate all ongoing calls? + آیا می‌خواهید این پنجره بسته و همه تماس‌ها پایان یابد؟ + + + + Error + خطا + + + + Fatal Error + خطای مهلک + + + + MainTabBar + + + + Home + خانه + + + + Conference + کنفرانس + + + + No active conference + هیچ کنفرانس فعالی وجود ندارد + + + + No active call + هیچ تماس فعالی وجود ندارد + + + + Move up + انتقال به بالا + + + + Move tab up + انتقال زبانه به بالا + + + + Moves the currently selected tab up by one + زبانه انتخاب‌شده را یک موقعیت به بالا منتقل می‌کند + + + + Move down + انتقال به پایین + + + + Move tab down + انتقال زبانه به پایین + + + + Moves the currently selected tab down by one + زبانه انتخاب‌شده را یک موقعیت به پایین منتقل می‌کند + + + + Edit + ویرایش + + + + Edit page + ویرایش صفحه + + + + Edit the currently selected dashboard page + ویرایش صفحه داشبورد انتخاب‌شده + + + + Delete + حذف + + + + Delete page + حذف صفحه + + + + Delete the currently selected dashboard page + حذف صفحه داشبورد انتخاب‌شده + + + + Call + تماس + + + + Selected tab + زبانه انتخاب‌شده + + + + The currently selected tab + زبانه انتخاب‌شده فعلی + + + + Selected tab options + گزینه‌های زبانه انتخاب‌شده + + + + The settings of the currently selected tab + تنظیمات زبانه انتخاب‌شده فعلی + + + + + Settings + تنظیمات + + + + MenuContactInfo + + + Commercial + تجاری + + + + Mobile + موبایل + + + + Home + خانه + + + + PageCreationWindow + + + Create new dashboard page + ایجاد صفحه داشبورد جدید + + + + Edit dashboard page + ویرایش صفحه داشبورد + + + + Name + نام + + + + Page name + نام صفحه + + + + Enter the page name + نام صفحه را وارد کنید + + + + Icon + نماد + + + + Page icon label + برچسب نماد صفحه + + + + Page icon selection + انتخاب نماد صفحه + + + + Select the page icon for the dashboard page + انتخاب نماد صفحه داشبورد + + + + Currently selected page icon option + گزینه نماد صفحه انتخاب‌شده + + + + Cancel + لغو + + + + Cancel page modifcation + لغو تغییر صفحه + + + + Cancel button to exit the page creation/update window + دکمه لغو برای خروج از پنجره + + + + Create + ایجاد + + + + Save + ذخیره + + + + page + صفحه + + + + Confirmation button to create the new dashboard page + دکمه تأیید ایجاد صفحه جدید + + + + Confirmation button to apply changes to the dashboard page + دکمه تأیید اعمال تغییرات + + + + ParticipantsList + + + Participants list + فهرست شرکت‌کنندگان + + + + List of all the participants of the current chat room + فهرست همه شرکت‌کنندگان اتاق فعلی + + + + Chat participant + شرکت‌کننده گفتگو + + + + Selected chat participant: %1 + شرکت‌کننده انتخاب‌شده: %1 + + + + moderator + مدیر + + + + it's you + این شمایید + + + + Kick + اخراج + + + + Make moderator + تعیین به عنوان مدیر + + + + PhoneNumberUtil + + + Anonymous + ناشناس + + + + PreferredIdentityEditWindow + + + Phone Number Transmission + انتقال شماره تلفن + + + + Name + نام + + + + Prefix + پیشوند + + + + Identity + هویت + + + + Enabled + فعال + + + + Automatic + خودکار + + + + Delete + حذف + + + + Save + ذخیره + + + + QObject + + + Andorra + آندورا + + + + United Arab Emirates + امارات متحده عربی + + + + Afghanistan + افغانستان + + + + Antigua and Barbuda + آنتیگوا و باربودا + + + + Anguilla + آنگویلا + + + + Albania + آلبانی + + + + Armenia + ارمنستان + + + + Angola + آنگولا + + + + Argentina + آرژانتین + + + + American Samoa + ساموای آمریکایی + + + + Austria + اتریش + + + + Australia + استرالیا + + + + Aruba + آروبا + + + + Aland Islands + جزایر آلاند + + + + Azerbaijan + آذربایجان + + + + Bosnia and Herzegovina + بوسنی و هرزگوین + + + + Barbados + باربادوس + + + + Bangladesh + بنگلادش + + + + Belgium + بلژیک + + + + Burkina Faso + بورکینافاسو + + + + Bulgaria + بلغارستان + + + + Bahrain + بحرین + + + + Burundi + بوروندی + + + + Benin + بنین + + + + Saint Barthelemy + سن بارتلمی + + + + Bermuda + برمودا + + + + Brunei + برونئی + + + + Bolivia + بولیوی + + + + Bonaire, Saint Eustatius and Saba + بونایر، سینت استاتیوس و سابا + + + + Brazil + برزیل + + + + Bahamas + باهاما + + + + Bhutan + بوتان + + + + Botswana + بوتسوانا + + + + Belarus + بلاروس + + + + Belize + بلیز + + + + Canada + کانادا + + + + Cocos Islands + جزایر کوکوس + + + + Democratic Republic of the Congo + جمهوری دموکراتیک کنگو + + + + Central African Republic + جمهوری آفریقای مرکزی + + + + Republic of the Congo + جمهوری کنگو + + + + Switzerland + سوئیس + + + + Ivory Coast + ساحل عاج + + + + Cook Islands + جزایر کوک + + + + Chile + شیلی + + + + Cameroon + کامرون + + + + China + چین + + + + Colombia + کلمبیا + + + + Costa Rica + کاستاریکا + + + + Cuba + کوبا + + + + Cape Verde + کیپ ورد + + + + Curacao + کوراسائو + + + + Christmas Island + جزیره کریسمس + + + + Cyprus + قبرس + + + + Czech Republic + جمهوری چک + + + + Germany + آلمان + + + + Djibouti + جیبوتی + + + + Denmark + دانمارک + + + + Dominica + دومینیکا + + + + Dominican Republic + جمهوری دومینیکن + + + + Algeria + الجزایر + + + + Ecuador + اکوادور + + + + Estonia + استونی + + + + Egypt + مصر + + + + Western Sahara + صحرای غربی + + + + Eritrea + اریتره + + + + Spain + اسپانیا + + + + Ethiopia + اتیوپی + + + + Finland + فنلاند + + + + Fiji + فیجی + + + + Falkland Islands + جزایر فالکلند + + + + Micronesia + میکرونزی + + + + Faroe Islands + جزایر فارو + + + + France + فرانسه + + + + Gabon + گابن + + + + United Kingdom + بریتانیا + + + + Grenada + گرانادا + + + + Georgia + گرجستان + + + + French Guiana + گویان فرانسوی + + + + Guernsey + گرنزی + + + + Ghana + غنا + + + + Gibraltar + جبل‌الطارق + + + + Greenland + گرینلند + + + + Gambia + گامبیا + + + + Guinea + گینه + + + + Guadeloupe + گوادلوپ + + + + Equatorial Guinea + گینه استوایی + + + + Greece + یونان + + + + Guatemala + گواتمالا + + + + Guam + گوام + + + + Guinea-Bissau + گینه بیسائو + + + + Guyana + گویان + + + + Hong Kong + هنگ‌کنگ + + + + Heard Island and McDonald Islands + جزایر هرد و مک‌دونالد + + + + Honduras + هندوراس + + + + Croatia + کرواسی + + + + Haiti + هائیتی + + + + Hungary + مجارستان + + + + Indonesia + اندونزی + + + + Ireland + ایرلند + + + + Israel + اسرائیل + + + + Isle of Man + جزیره من + + + + India + هند + + + + British Indian Ocean Territory + قلمرو بریتانیا در اقیانوس هند + + + + Iraq + عراق + + + + Iran + ایران + + + + Iceland + ایسلند + + + + Italy + ایتالیا + + + + Jersey + جرزی + + + + Jamaica + جامائیکا + + + + Jordan + اردن + + + + Japan + ژاپن + + + + Kenya + کنیا + + + + Kyrgyzstan + قرقیزستان + + + + Cambodia + کامبوج + + + + Kiribati + کیریباتی + + + + Comoros + کومور + + + + Saint Kitts and Nevis + سنت کیتس و نویس + + + + North Korea + کره شمالی + + + + South Korea + کره جنوبی + + + + Kuwait + کویت + + + + Cayman Islands + جزایر کیمن + + + + Kazakhstan + قزاقستان + + + + Laos + لائوس + + + + Lebanon + لبنان + + + + Saint Lucia + سنت لوسیا + + + + Liechtenstein + لیختن‌اشتاین + + + + Sri Lanka + سری‌لانکا + + + + Liberia + لیبریا + + + + Lesotho + لسوتو + + + + Lithuania + لیتوانی + + + + Luxembourg + لوکزامبورگ + + + + Latvia + لتونی + + + + Libya + لیبی + + + + Morocco + مراکش + + + + Monaco + موناکو + + + + Moldova + مولداوی + + + + Montenegro + مونته‌نگرو + + + + Saint Martin + سن مارتن + + + + Madagascar + ماداگاسکار + + + + Marshall Islands + جزایر مارشال + + + + Macedonia + مقدونیه + + + + Mali + مالی + + + + Myanmar + میانمار + + + + Mongolia + مغولستان + + + + Macao + ماکائو + + + + Northern Mariana Islands + جزایر ماریانای شمالی + + + + Martinique + مارتینیک + + + + Mauritania + موریتانی + + + + Montserrat + مونتسرات + + + + Malta + مالت + + + + Mauritius + موریس + + + + Maldives + مالدیو + + + + Malawi + مالاوی + + + + Mexico + مکزیک + + + + Malaysia + مالزی + + + + Mozambique + موزامبیک + + + + Namibia + نامیبیا + + + + New Caledonia + کالدونیای جدید + + + + Niger + نیجر + + + + Norfolk Island + جزیره نورفولک + + + + Nigeria + نیجریه + + + + Nicaragua + نیکاراگوئه + + + + Netherlands + هلند + + + + Norway + نروژ + + + + Nepal + نپال + + + + Nauru + نائورو + + + + Niue + نیوئه + + + + New Zealand + نیوزیلند + + + + Oman + عمان + + + + Panama + پاناما + + + + Peru + پرو + + + + French Polynesia + پلی‌نزی فرانسه + + + + Papua New Guinea + پاپوا گینه نو + + + + Philippines + فیلیپین + + + + Pakistan + پاکستان + + + + Poland + لهستان + + + + Saint Pierre and Miquelon + سن پیر و میکلون + + + + Pitcairn + پیتکرن + + + + Puerto Rico + پورتوریکو + + + + Palestinian Territory + سرزمین‌های فلسطینی + + + + Portugal + پرتغال + + + + Palau + پالائو + + + + Paraguay + پاراگوئه + + + + Qatar + قطر + + + + Reunion + رئونیون + + + + Romania + رومانی + + + + Serbia + صربستان + + + + Russia + روسیه + + + + Rwanda + رواندا + + + + Saudi Arabia + عربستان سعودی + + + + Solomon Islands + جزایر سلیمان + + + + Seychelles + سیشل + + + + Sudan + سودان + + + + Sweden + سوئد + + + + Singapore + سنگاپور + + + + Saint Helena + سنت هلنا + + + + Slovenia + اسلوونی + + + + Svalbard and Jan Mayen + اسوالبارد و یان ماین + + + + Slovakia + اسلواکی + + + + Sierra Leone + سیرالئون + + + + San Marino + سن مارینو + + + + Senegal + سنگال + + + + Somalia + سومالی + + + + Suriname + سورینام + + + + South Sudan + سودان جنوبی + + + + Sao Tome and Principe + سائوتومه و پرینسیپ + + + + El Salvador + السالوادور + + + + Sint Maarten + سینت مارتن + + + + Syria + سوریه + + + + Swaziland + سوازیلند + + + + Turks and Caicos Islands + جزایر تورکس و کایکوس + + + + Chad + چاد + + + + Togo + توگو + + + + Thailand + تایلند + + + + Tajikistan + تاجیکستان + + + + Tokelau + توکلائو + + + + East Timor + تیمور شرقی + + + + Turkmenistan + ترکمنستان + + + + Tunisia + تونس + + + + Tonga + تونگا + + + + Turkey + ترکیه + + + + Trinidad and Tobago + ترینیداد و توباگو + + + + Tuvalu + تووالو + + + + Taiwan + تایوان + + + + Tanzania + تانزانیا + + + + Ukraine + اوکراین + + + + Uganda + اوگاندا + + + + United States Minor Outlying Islands + جزایر دورافتاده کوچک ایالات متحده + + + + United States + ایالات متحده + + + + Uruguay + اروگوئه + + + + Uzbekistan + ازبکستان + + + + Vatican + واتیکان + + + + Saint Vincent and the Grenadines + سنت وینسنت و گرنادین‌ها + + + + Venezuela + ونزوئلا + + + + British Virgin Islands + جزایر ویرجین بریتانیا + + + + U.S. Virgin Islands + جزایر ویرجین آمریکا + + + + Vietnam + ویتنام + + + + Vanuatu + وانواتو + + + + Wallis and Futuna + والیس و فوتونا + + + + Samoa + ساموا + + + + Yemen + یمن + + + + Mayotte + مایوت + + + + South Africa + آفریقای جنوبی + + + + Zambia + زامبیا + + + + Zimbabwe + زیمبابوه + + + + There are %n active call(s). + calls + + %n تماس فعال وجود دارد. + %n تماس فعال وجود دارد. + + + + + ResponseTreeModel + + + + + + Additional Information + اطلاعات بیشتر + + + + RingToneFactory + + + ringTone + 425,0,1000,4000,0 + + + + busyTone + 425,0,480,480,0 + + + + congestionTone + 425,0,240,240,0 + + + + zip + 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 + + + + endTone + 425,0,200,200,200,200,200,200,-1 + + + + SIPAccount + + + 'userUri' is no valid SIP URI: %1 + 'userUri' یک SIP URI معتبر نیست: %1 + + + + 'userUri' is required + 'userUri' الزامی است + + + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' یک SIP URI معتبر نیست: %1 + + + + 'registrarUri' is required + 'registrarUri' الزامی است + + + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' یک SIP URI نامعتبر دارد: %1 + + + + Failed to create %1: %2 + ایجاد %1 ناموفق بود: %2 + + + + SIPBuddy + + + %1 is now available + %1 اکنون در دسترس است + + + + SIPCall + + + Active call with %1 + تماس فعال با %1 + + + + Hang up + قطع تماس + + + + SIPCallManager + + + %1 is calling + %1 تماس می‌گیرد + + + + %1 (%2) is calling + %1 (%2) تماس می‌گیرد + + + + + Accept + پذیرفتن + + + + Call back + تماس مجدد + + + + + Reject + رد کردن + + + + Missed call from %1 + تماس از دست رفته از %1 + + + + SIPManager + + + New Identity + هویت جدید + + + + SIPTemplate + + + + Failed to write to %1 + نوشتن در %1 ناموفق بود + + + + Failed to copy %1 to the config space + کپی %1 به فضای تنظیمات ناموفق بود + + + + Source file %1 does not exist + فایل منبع %1 وجود ندارد + + + + SearchBox + + + Search number + جستجوی شماره + + + + SearchCategoryItem + + + Search result category filter %1 + فیلتر دسته نتایج جستجو %1 + + + + Filter for the individual search result items by category + فیلتر نتایج جستجو بر اساس دسته + + + + SearchCategoryList + + + Contacts + مخاطبان + + + + Messages + پیام‌ها + + + + Rooms and Teams + اتاق‌ها و تیم‌ها + + + + Files + فایل‌ها + + + + SearchDial + + + Select number + انتخاب شماره + + + + Activate search field + فعال‌سازی فیلد جستجو + + + + Number or contact + شماره یا مخاطب + + + + Clear search field + پاک کردن فیلد جستجو + + + + Default + پیش‌فرض + + + + Auto + خودکار + + + + Preferred identity + هویت ترجیحی + + + + Select the preferred identity for outgoing calls + انتخاب هویت ترجیحی برای تماس‌های خروجی + + + + SearchField + + + Search for contacts or room names... + جستجوی مخاطبان یا نام اتاق‌ها... + + + + Clear search field + پاک کردن فیلد جستجو + + + + SearchResultCategory + + + Search result category %1 + دسته نتایج جستجو %1 + + + + Divider for the individual search result items by category + جداکننده نتایج جستجو بر اساس دسته + + + + SearchResultItem + + + Search result + نتیجه جستجو + + + + Currently selected search result + نتیجه جستجوی انتخاب‌شده + + + + SearchResultNumberItem + + + Phone number + شماره تلفن + + + + Selected favorite number %1 + شماره مورد علاقه انتخاب‌شده %1 + + + + Selected phone number %1 + شماره تلفن انتخاب‌شده %1 + + + + SearchResultPopup + + + Search filter and identity selection + فیلتر جستجو و انتخاب هویت + + + + Select search filter to be applied, as well as the outgoing identity + انتخاب فیلتر جستجو و هویت خروجی + + + + Outgoing identity + هویت خروجی + + + + Search results + نتایج جستجو + + + + All search results will be listed here in their respective categories + همه نتایج جستجو اینجا بر اساس دسته فهرست می‌شوند + + + + Direct dial + شماره‌گیری مستقیم + + + + Call "%1" + تماس با «%1» + + + + Open room "%1" + باز کردن اتاق «%1» + + + + History + تاریخچه + + + + Contacts + مخاطبان + + + + SettingsPage + + + Settings + تنظیمات + + + + Show chat messages as desktop notifications + نمایش پیام‌های گفتگو به عنوان اعلان + + + + Enable USB headset driver [%1] + فعال‌سازی درایور هدست USB [%1] + + + + not detected + شناسایی نشد + + + + Show dial window on USB headset pick up + نمایش پنجره شماره‌گیری هنگام برداشتن هدست + + + + Color scheme + طرح رنگی + + + + System default + پیش‌فرض سیستم + + + + Light + روشن + + + + Dark + تیره + + + + Inverse Accept / Reject buttons + معکوس کردن دکمه‌های پذیرش/رد + + + + Use dark mode tray icon + استفاده از نماد سینی در حالت تاریک + + + + Disable USB headset mute state propagation + غیرفعال‌سازی همگام‌سازی وضعیت بی‌صدا هدست + + + + Reload contacts from LDAP + بارگذاری مجدد مخاطبان از LDAP + + + + Phoning + تلفن + + + + Show main window on startup + نمایش پنجره اصلی هنگام راه‌اندازی + + + + Disable synchronisation with the system mute state + غیرفعال‌سازی همگام‌سازی با وضعیت بی‌صدای سیستم + + + + + restart required + راه‌اندازی مجدد لازم است + + + + Appearance + ظاهر + + + + Use custom window decoration + استفاده از تزئین سفارشی پنجره + + + + Theme selection box + کادر انتخاب پوسته + + + + Select the UI theme + انتخاب پوسته رابط کاربری + + + + Currently selected theme option + گزینه پوسته انتخاب‌شده + + + + Signalling busy when a call is active + اعلام اشغال هنگام فعال بودن تماس + + + + Rules for telephone number transmission + قوانین انتقال شماره تلفن + + + + Standard preferred identity + هویت ترجیحی استاندارد + + + + + Default + پیش‌فرض + + + + + Auto + خودکار + + + + + Prefererred identity selection + انتخاب هویت ترجیحی + + + + + Select the preferred identity + انتخاب هویت ترجیحی + + + + Currently selected identity option + گزینه هویت انتخاب‌شده + + + + No preferred identities yet. + هنوز هویت ترجیحی وجود ندارد. + + + + Currently highlighted preferred identity. Tap to edit. + هویت ترجیحی فعلی. برای ویرایش ضربه بزنید. + + + + Standard + استاندارد + + + + Add identity + افزودن هویت + + + + Add a new preferred identity entry + افزودن هویت ترجیحی جدید + + + + Audio settings + تنظیمات صدا + + + + Input device + دستگاه ورودی + + + + Input device selection + انتخاب دستگاه ورودی + + + + Select the input device to be used + انتخاب دستگاه ورودی + + + + Currently selected input option + گزینه ورودی انتخاب‌شده + + + + Output device + دستگاه خروجی + + + + Output device selection + انتخاب دستگاه خروجی + + + + Select the output device to be used + انتخاب دستگاه خروجی + + + + Currently selected output option + گزینه خروجی انتخاب‌شده + + + + Output device for ring tone + دستگاه خروجی برای زنگ + + + + Currently selected ring output option + گزینه خروجی زنگ انتخاب‌شده + + + + Ring tone + زنگ + + + + Prefer USB headset ring sound if available + استفاده از زنگ هدست USB در صورت در دسترس بودن + + + + Reset ring tone + بازنشانی زنگ + + + + Reset the ring tone to its default option + بازنشانی زنگ به حالت پیش‌فرض + + + + Pick ring tone + انتخاب زنگ + + + + Select the ring tone you want to use for incoming calls + انتخاب زنگ برای تماس‌های ورودی + + + + + Currently set to: + مقدار فعلی: + + + + Ring tone volume + صدای زنگ + + + + + Adjust %1 + تنظیم %1 + + + + %1 % + %1 ٪ + + + + Pause between ring tones [s] + مکث بین زنگ‌ها [ثانیه] + + + + Debugging + اشکال‌زدایی + + + + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. + برای شروع اشکال‌زدایی این دکمه را فشار دهید. برنامه مجدداً راه‌اندازی شده و اطلاعات تشخیصی ثبت می‌کند. این حالت ۵ دقیقه است. + + + + Start debug run (restart app) + شروع اشکال‌زدایی (راه‌اندازی مجدد برنامه) + + + + Download debug information + دانلود اطلاعات اشکال‌زدایی + + + + ShortcutsWindow + + + Shortcuts + میانبرها + + + + Shortcut key: %1 + کلید میانبر: %1 + + + + Local shortcuts (work only when app is focused) + میانبرهای محلی (فقط هنگام فوکوس برنامه) + + + + Local shortcuts + میانبرهای محلی + + + + Ctrl + F + Ctrl + F + + + + Activates the global search field + فیلد جستجوی سراسری را فعال می‌کند + + + + F11 + F11 + + + + Toggles between fullsceen and normal window mode + بین حالت تمام‌صفحه و معمولی جابجا می‌شود + + + + Ctrl + Shift + M + Ctrl + Shift + M + + + + Toggles audio mute + صدا را روشن/خاموش می‌کند + + + + Global shortcuts (work from anywhere) + میانبرهای سراسری (از هر جایی) + + + + Global shortcuts + میانبرهای سراسری + + + + SipTemplateWizard + + + Initial configuration + پیکربندی اولیه + + + + Error: %1 + خطا: %1 + + + + SIP wizard notification + اعلان راهنمای SIP + + + + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. + GOnnect پیکربندی SIP پیدا نکرد. یک الگو انتخاب کنید و در صورت نیاز فایل را ویرایش کنید. + + + + Please pick: + لطفاً انتخاب کنید: + + + + Select SIP template + انتخاب الگوی SIP + + + + Select the SIP template to be used + انتخاب الگوی SIP + + + + Currently selected SIP template + الگوی SIP انتخاب‌شده + + + + Next + بعدی + + + + Continue setup + ادامه راه‌اندازی + + + + Confirmation button to continue the setup + دکمه تأیید برای ادامه راه‌اندازی + + + + Template field list + فهرست فیلدهای الگو + + + + List of all the available SIP template options + فهرست همه گزینه‌های الگوی SIP + + + + SIP template option + گزینه الگوی SIP + + + + Currently selected SIP template option + گزینه الگوی SIP انتخاب‌شده + + + + Display name of the SIP template option + نام نمایشی گزینه الگوی SIP + + + + Description of the SIP template option + توضیح گزینه الگوی SIP + + + + Back + برگشت + + + + Back button to return to the template selection menu + دکمه برگشت به منوی انتخاب الگو + + + + + Finish + پایان + + + + Confirmation button to apply the changes to the SIP template + دکمه تأیید اعمال تغییرات الگو + + + + Successful configuration file creation + فایل پیکربندی با موفقیت ایجاد شد + + + + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. + فایل پیکربندی ایجاد شد. بررسی کنید آیا تغییری لازم است و GOnnect را مجدداً راه‌اندازی کنید. + + + + The configuration has been saved to: + پیکربندی ذخیره شد در: + + + + Copy to clipboard + کپی در کلیپ‌بورد + + + + Copy the full path of the configuration file to the clipboard + کپی مسیر کامل فایل پیکربندی + + + + Finish wizard + پایان راهنما + + + + Finish the SIP configuration wizard + پایان راهنمای پیکربندی SIP + + + + StateManager + + + Show dial window and focus search field + نمایش پنجره شماره‌گیری و فوکوس فیلد جستجو + + + + End all calls + پایان دادن به همه تماس‌ها + + + + Redial last outgoing call + شماره‌گیری مجدد آخرین تماس خروجی + + + + Toggle hold + تغییر وضعیت نگه‌داری + + + + Phone calls are active + تماس‌ها فعال هستند + + + + SystemTrayMenu + + + + Dial... + شماره‌گیری... + + + + Not registered... + ثبت‌نشده... + + + + Settings + تنظیمات + + + + About + درباره + + + + Quit + خروج + + + + End conference + پایان کنفرانس + + + + Call with %1 has ended + تماس با %1 پایان یافت + + + + Hang up call with %1 + قطع تماس با %1 + + + + Accept call with %1 + پذیرفتن تماس از %1 + + + + TemplateFieldFile + + + File path + مسیر فایل + + + + Enter the file path for %1 + مسیر فایل را برای %1 وارد کنید + + + + Choose... + انتخاب... + + + + Open file picker + باز کردن انتخاب‌گر فایل + + + + Select the file that should be used for %1 + انتخاب فایل برای %1 + + + + Certificate files (%1) + فایل‌های گواهینامه (%1) + + + + TemplateFieldText + + + Text input + ورودی متن + + + + Enter the desired value for %1 + مقدار دلخواه را برای %1 وارد کنید + + + + Toggler + + + Failed to toggle the state of %1. + تغییر وضعیت %1 ناموفق بود. + + + + TogglerList + + + Toggler list + فهرست سوئیچ‌ها + + + + List of items that can be toggled + فهرست موارد قابل سوئیچ + + + + Toggle %1 + سوئیچ %1 + + + + VerticalLevelMeter + + + Level meter + نمایشگر سطح + + + + VideoDeviceMenu + + + Virtual background + پس‌زمینه مجازی + + + + ViewHelper + + + Save File + ذخیره فایل + + + + %n minute(s) + + %n دقیقه + %n دقیقه + + + + + 1 hour and %n minute(s) + + ۱ ساعت و %n دقیقه + ۱ ساعت و %n دقیقه + + + + + %n hour(s) + + %n ساعت + %n ساعت + + + + + Audio Files (%1) + فایل‌های صوتی (%1) + + + + VolumePopup + + + Adjust the volume + تنظیم صدا + + + + Unmute + باز کردن صدا + + + + Mute + بی‌صدا کردن + + + + Open audio settings + باز کردن تنظیمات صدا + + + + WidgetSelectionWindow + + + Add widget + افزودن ابزارک + + + + Widget + ابزارک + + + + Widget selection header + عنوان انتخاب ابزارک + + + + Date Events + رویدادها + + + + List of upcoming appointments + فهرست قرارهای آینده + + + + Favorites + علاقه‌مندی‌ها + + + + Quick dial for your favorite contacts and conferences + شماره‌گیری سریع مخاطبان مورد علاقه + + + + History + تاریخچه + + + + Web View + نمای وب + + + + A web-based content display + نمایش محتوای مبتنی بر وب + + + + Widget selection + انتخاب ابزارک + + + + Select the widget that should be added to the current dashboard page + انتخاب ابزارک برای افزودن به صفحه فعلی + + + + Currently selected widget option + گزینه ابزارک انتخاب‌شده + + + + Accept all certificates + پذیرفتن همه گواهینامه‌ها + + + + Confirm widget selection + تأیید انتخاب ابزارک + + + + Confirmation button to create and add the selected widget to the current dashboard + دکمه ایجاد و افزودن ابزارک + + + + Searchable call and conference history + تاریخچه قابل جستجوی تماس‌ها و کنفرانس‌ها + + + + Title + عنوان + + + + URL + URL + + + + URL (dark mode) + URL (حالت تاریک) + + + + Settings text input + ورودی متن تنظیمات + + + + Input for widget setting %1 + ورودی برای تنظیم ابزارک %1 + + + + Settings checkbox + چک‌باکس تنظیمات + + + + Checkbox for widget setting %1 + چک‌باکس برای تنظیم ابزارک %1 + + + + Widget setting %1 + تنظیم ابزارک %1 + + + + Cancel + لغو + + + + Cancel widget selection + لغو انتخاب ابزارک + + + + Cancel button to exit widget selection selection without changes + دکمه لغو برای خروج بدون تغییر + + + + Add + افزودن + + + + WindowHeader + + + GOnnect window header + عنوان پنجره GOnnect + + + + Minimize + کوچک کردن + + + + Maximize + بزرگ کردن + + + + Close GOnnect window + بستن پنجره GOnnect + + + \ No newline at end of file diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts new file mode 100644 index 00000000..b9cceaa4 --- /dev/null +++ b/i18n/gonnect_fr.ts @@ -0,0 +1,5186 @@ + + + + + AboutWindow + + + About + À propos + + + + GOnnect headline + Titre de GOnnect + + + + GOnnect version + Version de GOnnect + + + + Version: v%1 + Version : v%1 + + + + Copy to clipboard + Copier dans le presse-papiers + + + + Copy the currently used version of Gonnect to the clipboard + Copier la version actuelle de GOnnect dans le presse-papiers + + + + + + + Homepage + Page d'accueil + + + + Visit the project homepage + Visiter la page d'accueil du projet + + + + Bug Tracker + Suivi des bogues + + + + Visit the project bug tracker + Visiter le suivi des bogues du projet + + + + Documentation + Documentation + + + + Visit the online project documentation + Visiter la documentation en ligne du projet + + + + AdditionalInfo + + + Additional caller related information + Informations supplémentaires sur l'appelant + + + + List of informational items regarding the caller, such as open support tickets + Liste d'informations sur l'appelant, comme les tickets de support ouverts + + + + Expandable response section + Section de réponse extensible + + + + AudioDeviceMenu + + + Default + Par défaut + + + + AudioEnvWindow + + + Unknown audio environment + Environnement audio inconnu + + + + Audio environment error + Erreur d'environnement audio + + + + No fitting audio environment could be found. Please select the desired audio devices. + Aucun environnement audio adapté n'a été trouvé. Veuillez sélectionner les périphériques audio souhaités. + + + + Input device selection header + En-tête de sélection du périphérique d'entrée + + + + Header for the input device selection below + En-tête pour la sélection du périphérique d'entrée ci-dessous + + + + Input device + Périphérique d'entrée + + + + Input device selection box + Zone de sélection du périphérique d'entrée + + + + Select the input device that should be used + Sélectionner le périphérique d'entrée à utiliser + + + + Currently selected input device + Périphérique d'entrée actuellement sélectionné + + + + Output device selection header + En-tête de sélection du périphérique de sortie + + + + Header for the output device selection below + En-tête pour la sélection du périphérique de sortie ci-dessous + + + + Output device + Périphérique de sortie + + + + Output device selection box + Zone de sélection du périphérique de sortie + + + + Select the output device that should be used + Sélectionner le périphérique de sortie à utiliser + + + + Currently selected output device + Périphérique de sortie actuellement sélectionné + + + + Ring tone output device + Périphérique de sortie pour la sonnerie + + + + Output device for ring tone + Périphérique de sortie pour la sonnerie + + + + Ring tone output device selection box + Zone de sélection du périphérique de sortie pour la sonnerie + + + + Select the output device that should be used for playing the ring tone + Sélectionner le périphérique de sortie pour la sonnerie + + + + Currently selected ring tone output device + Périphérique de sortie de sonnerie actuellement sélectionné + + + + Ok + OK + + + + Close audio environment selection + Fermer la sélection de l'environnement audio + + + + Confirmation button to leave the audio environment selection window + Bouton de confirmation pour quitter la fenêtre de sélection de l'environnement audio + + + + AudioLevelButton + + + Change volume + Modifier le volume + + + + AudioManager + + + Default input + Entrée par défaut + + + + Default output + Sortie par défaut + + + + AvatarImage + + + Initials of this contact + Initiales de ce contact + + + + BaseDialog + + + Dialog + Dialogue + + + + BasePage + + + Base dashboard page grid + Grille de base de la page du tableau de bord + + + + Canvas for editable dashboard pages + Zone de travail pour les pages du tableau de bord modifiables + + + + Add widgets + Ajouter des widgets + + + + BaseWidget + + + Drag widget + Déplacer le widget + + + + Change the position of the widget + Modifier la position du widget + + + + Remove widget + Supprimer le widget + + + + Remove the currently selected widget from the dashboard + Supprimer le widget actuellement sélectionné du tableau de bord + + + + Resize widget + Redimensionner le widget + + + + Resize the widget according to the mouse direction + Redimensionner le widget selon la direction de la souris + + + + BaseWindow + + + + + + + + + + Drag border + Bordure de déplacement + + + + Top left drag border for window resize operations + Bordure de déplacement en haut à gauche pour le redimensionnement de la fenêtre + + + + Top drag border for window resize operations + Bordure de déplacement en haut pour le redimensionnement de la fenêtre + + + + Top right border for window resize operations + Bordure en haut à droite pour le redimensionnement de la fenêtre + + + + Right drag border for window resize operations + Bordure de déplacement à droite pour le redimensionnement de la fenêtre + + + + Bottom right drag border for window resize operations + Bordure de déplacement en bas à droite pour le redimensionnement de la fenêtre + + + + Bottom drag border for window resize operations + Bordure de déplacement en bas pour le redimensionnement de la fenêtre + + + + Bottom left drag border for window resize operations + Bordure de déplacement en bas à gauche pour le redimensionnement de la fenêtre + + + + Left drag border for window resize operations + Bordure de déplacement à gauche pour le redimensionnement de la fenêtre + + + + BottomStatusBar + + + Status bar + Barre d'état + + + + BurgerMenu + + + Toggle fullscreen + Basculer en plein écran + + + + Shortcuts... + Raccourcis... + + + + Customize UI + Personnaliser l'interface + + + + About... + À propos... + + + + Quit + Quitter + + + + Call + + + Conference + Conférence + + + + Drag bar + Barre de déplacement + + + + CallButtonBar + + + %1@%2 kHz + %1@%2 kHz + + + + Transmit + Émettre + + + + Call security level + Niveau de sécurité de l'appel + + + + Security level of the ongoing call + Niveau de sécurité de l'appel en cours + + + + Call security details + Détails de sécurité de l'appel + + + + Detailed call security status: %1 / %2 + État détaillé de la sécurité de l'appel : %1 / %2 + + + + signaling encrypted + signalisation chiffrée + + + + signaling unencrypted + signalisation non chiffrée + + + + media encrypted + médias chiffrés + + + + media unencrypted + médias non chiffrés + + + + Call quality + Qualité de l'appel + + + + Quality of the ongoing call + Qualité de l'appel en cours + + + + Transmission statistics + Statistiques d'émission + + + + + Call quality metrics + Métriques de qualité d'appel + + + + + MOS + MOS + + + + + Mean opinion score + Score d'opinion moyen (MOS) + + + + Numerical metric assessing transmission-side voice call quality: %1 + Métrique numérique évaluant la qualité vocale côté émission : %1 + + + + + Packet loss + Perte de paquets + + + + %1% of packets lost in transmission + %1 % de paquets perdus en transmission + + + + + Jitter + Gigue + + + + Amount of transmission side jitter: %1 + Gigue côté émission : %1 + + + + + Effective delay + Délai effectif + + + + Effective transmission side call delay: %1 + Délai d'appel effectif côté émission : %1 + + + + Receiver statistics + Statistiques de réception + + + + Receive + Réception + + + + Numerical metric assessing receiver-side voice/video call quality: %1 + Métrique numérique évaluant la qualité voix/vidéo côté réception : %1 + + + + %1% of packets lost in receival + %1 % de paquets perdus en réception + + + + Amount of receiver side jitter: %1 + Gigue côté réception : %1 + + + + Effective receiver side call delay: %1 + Délai d'appel effectif côté réception : %1 + + + + Codec + Codec + + + + Audio codec + Codec audio + + + + The currently used audio codec and frequency: %1 + Le codec audio et la fréquence actuellement utilisés : %1 + + + + Elapsed call time + Durée écoulée de l'appel + + + + The duration in seconds the call has been active for: %1 + La durée en secondes pendant laquelle l'appel a été actif : %1 + + + + Screen + Écran + + + + Screensharing control + Contrôle du partage d'écran + + + + Start sharing your screen + Commencer le partage d'écran + + + + Camera + Caméra + + + + Camera control + Contrôle de la caméra + + + + Enable your camera + Activer la caméra + + + + Resume + Reprendre + + + + Hold + En attente + + + + Resume call + Reprendre l'appel + + + + Hold call + Mettre l'appel en attente + + + + Update the call hold state + Mettre à jour l'état de mise en attente de l'appel + + + + Micro + Micro + + + + Input control + Contrôle d'entrée + + + + Set the mute state of the current input device + Définir l'état de silence du périphérique d'entrée actuel + + + + Output + Sortie + + + + Output control + Contrôle de sortie + + + + Change the current output devices + Modifier les périphériques de sortie actuels + + + + Accept call + Accepter l'appel + + + + Hangup call + Raccrocher + + + + CallDetails + + + SIP call status code + Code d'état de l'appel SIP + + + + The current status code of the call: %1 + Le code d'état actuel de l'appel : %1 + + + + Jitsi Meet + Jitsi Meet + + + + Switch to a Jitsi Meet session + Passer à une session Jitsi Meet + + + + Unhold + Reprendre + + + + Hold + En attente + + + + Toggle the hold state to %1 + Basculer l'état de mise en attente vers %1 + + + + Accept call + Accepter l'appel + + + + Hangup call + Raccrocher + + + + CallHistory + + + Failed to create directory %1 to store the call history database. + Échec de la création du répertoire %1 destiné à stocker la base de données de l'historique des appels. + + + + Failed to open call history database: %1 + Impossible d'ouvrir la base de données de l'historique des appels : %1 + + + + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. + La base de données de l'historique des appels est incohérente. Veuillez supprimer %1 et redémarrer l'application pour réinitialiser la base de données. + + + + CallItem + + + Call + Appel + + + + Selected call %1 - contact %2, company %3, location %4/%5, number %6 + Appel sélectionné %1 - contact %2, société %3, lieu %4/%5, numéro %6 + + + + Hangup button + Bouton de raccrochage + + + + Pressing this will end the call + Appuyer sur ce bouton mettra fin à l'appel + + + + CallList + + + Drag callers onto each other to transfer call + Faire glisser les appelants l'un sur l'autre pour transférer l'appel + + + + List of active calls + Liste des appels actifs + + + + + Create conference + Créer une conférence + + + + CallSideBar + + + Chat + Discussion + + + + Person(s) + + participant + participants + + + + + Info + Informations + + + + CallerBigAvatar + + + Caller name + Nom de l'appelant + + + + is calling... + appelle... + + + + Calling... + Appel en cours... + + + + CallsModel + + + unknown number + numéro inconnu + + + + CardList + + + List of configurable options + Liste des options configurables + + + + ChatMessageBox + + + Message + Message + + + + Type message + Saisir un message + + + + Enter the chat text message + Saisir le message texte de discussion + + + + ChatMessageList + + + Chat message list + Liste des messages de discussion + + + + List of all chat messages of the current chat room + Liste de tous les messages de la salle de discussion actuelle + + + + Auto scroll down + Défilement automatique vers le bas + + + + ChatMessageListItem + + + Chat message + Message de discussion + + + + Selected chat message - from %1, at %2: %3 + Message de discussion sélectionné - de %1, à %2 : %3 + + + + ChatRoomList + + + Chat room list + Liste des salles de discussion + + + + List of all chat rooms + Liste de toutes les salles de discussion + + + + ChatRoomListItem + + + Chat room + Salle de discussion + + + + Selected chat room %1: %2 unread messages + Salle de discussion sélectionnée %1 : %2 messages non lus + + + + ChatSideBar + + + Chat message list + Liste des messages de discussion + + + + List of all the messages in the current chat + Liste de tous les messages de la discussion actuelle + + + + Chat message + Message de discussion + + + + Selected chat message from %1 at %2: %3 + Message de discussion sélectionné de %1 à %2 : %3 + + + + the server + le serveur + + + + you + vous + + + + Select emoji + Sélectionner un emoji + + + + Enter chat message... + Saisir un message... + + + + Chats + + + Please enter your recovery key to decrypt messages: + Veuillez saisir votre clé de récupération pour déchiffrer les messages : + + + + Recovery key + Clé de récupération + + + + Enter recovery key + Saisir la clé de récupération + + + + Use key + Utiliser la clé + + + + Use recovery key + Utiliser la clé de récupération + + + + ClipboardButton + + + Copy to clipboard: %1 + Copier dans le presse-papiers : %1 + + + + Conference + + + + Set room name + Définir le nom de la salle + + + + + Room name: + Nom de la salle : + + + + + Enter the room name + Saisir le nom de la salle + + + + Authenticate + S'authentifier + + + + Please authenticate in the opened browser window... + Veuillez vous authentifier dans la fenêtre du navigateur ouverte... + + + + This conference is protected by a password. Please enter it to join the room. + Cette conférence est protégée par un mot de passe. Veuillez le saisir pour rejoindre la salle. + + + + + Password + Mot de passe + + + + + Enter the password + Saisir le mot de passe + + + + Remember password + Mémoriser le mot de passe + + + + + Cancel + Annuler + + + + Join Room + Rejoindre la salle + + + + Password required + Mot de passe requis + + + + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. + Saisir un mot de passe pour protéger cette salle de conférence. Les autres participants devront le saisir avant de rejoindre la session. + + + + This password has been set for the conference room and must be entered by participants before taking part in the session. + Ce mot de passe a été défini pour la salle de conférence et doit être saisi par les participants avant de rejoindre la session. + + + + The room password has been set by someone else. + Le mot de passe de la salle a été défini par quelqu'un d'autre. + + + + Show password + Afficher le mot de passe + + + + Remove + Supprimer + + + + Save + Enregistrer + + + + Video quality + Qualité vidéo + + + + Change the video quality of this meeting + Modifier la qualité vidéo de cette réunion + + + + No video (audio only) + Sans vidéo (audio seulement) + + + + Lowest quality + Qualité minimale + + + + Standard quality + Qualité standard + + + + Highest quality + Qualité maximale + + + + Close + Fermer + + + + Drag bar + Barre de déplacement + + + + ConferenceButtonBar + + + %n minutes left + + %n minute restante + %n minutes restantes + + + + + Conference room + Salle de conférence + + + + Share + Partager + + + + Copy room name + Copier le nom de la salle + + + + Copy room link + Copier le lien de la salle + + + + Open in browser + Ouvrir dans le navigateur + + + + Show phone number + Afficher le numéro de téléphone + + + + Raise + Lever la main + + + + Resume + Reprendre + + + + Hold + En attente + + + + View + Vue + + + + Screen + Écran + + + + Share window + Partager la fenêtre + + + + Share screen + Partager l'écran + + + + Camera + Caméra + + + + Micro + Micro + + + + Output + Sortie + + + + More + Plus + + + + Noise supression + Suppression du bruit + + + + Toggle subtitles + Activer/désactiver les sous-titres + + + + Toggle whiteboard + Activer/désactiver le tableau blanc + + + + Video quality... + Qualité vidéo... + + + + Set room password... + Définir le mot de passe de la salle... + + + + Mute everyone + Couper le son de tous + + + + Leave conference + Quitter la conférence + + + + End conference for all + Terminer la conférence pour tous + + + + ConfirmDialog + + + Cancel + Annuler + + + + ControlBar + + + App menu + Menu de l'application + + + + Credentials + + + storing credentials failed: %1 + Échec de l'enregistrement des identifiants : %1 + + + + reading credentials failed: %1 + Échec de la lecture des identifiants : %1 + + + + CredentialsDialog + + + Authentication failed + Échec de l'authentification + + + + Please enter the password: + Veuillez saisir le mot de passe : + + + + Password + Mot de passe + + + + Enter the password + Saisir le mot de passe + + + + Ok + OK + + + + CustomWindowHeader + + + GOnnect window header + En-tête de la fenêtre GOnnect + + + + App menu + Menu de l'application + + + + Close GOnnect window + Fermer la fenêtre GOnnect + + + + DateEventManager + + + Conference starting soon + La conférence commence bientôt + + + + Appointment starting soon + Le rendez-vous commence bientôt + + + + Join + Rejoindre + + + + Open + Ouvrir + + + + DateEventsList + + + Date events + Dates + + + + List of all the currently active and upcoming date events + Liste de tous les événements actifs et à venir du calendrier + + + + Date event section + Section des événements du calendrier + + + + Header for %1 + En-tête pour %1 + + + + Today - %1 + Aujourd'hui - %1 + + + + + yyyy/MM/dd + dd/MM/yyyy + + + + Tomorrow - %1 + Demain - %1 + + + + dddd - yyyy/MM/dd + dddd - dd/MM/yyyy + + + + Date event + Dates + + + + Currently selected date event: %1, starting time %2, remaining time %3 + Événement sélectionné : %1, heure de début %2, temps restant %3 + + + + + hh:mm + hh:mm + + + + All day + Toute la journée + + + + till %1 + jusqu'à %1 + + + + in %1 + dans %1 + + + + Join + Rejoindre + + + + Open + Ouvrir + + + + Join meeting + Rejoindre la réunion + + + + Join the meeting associated with the currently selected event + Rejoindre la réunion associée à l'événement actuellement sélectionné + + + + Copy room link + Copier le lien de la salle + + + + Copy link + Copier le lien + + + + Copy meeting link + Copier le lien de la réunion + + + + Copy the meeting link associated with the currently selected event + Copier le lien de la réunion associé à l'événement actuellement sélectionné + + + + DateEventsWidget + + + Appointments + Rendez-vous + + + + Loading appointments... + Chargement des rendez-vous... + + + + No upcoming appointments + Aucun rendez-vous à venir + + + + Date event widget status + État du widget des événements du calendrier + + + + Displays the current status of the widget: %1 + Affiche l'état actuel du widget : %1 + + + + DialInInfo + + + Call one of the phone numbers below and use this code for authentication: + Appelez l'un des numéros de téléphone ci-dessous et utilisez ce code pour l'authentification : + + + + Close + Fermer + + + + DtmfDialer + + + Number pad + Pavé numérique + + + + Character %1 + Caractère %1 + + + + EditModeOptions + + + Add page + Ajouter une page + + + + Add a new dashboard page + Ajouter une nouvelle page au tableau de bord + + + + Add widget + Ajouter un widget + + + + Add a new widget to the current dashboard page + Ajouter un nouveau widget à la page actuelle du tableau de bord + + + + Finished + Terminé + + + + Finish and save all dashboard and widget changes + Terminer et enregistrer toutes les modifications du tableau de bord et des widgets + + + + EmergencyCallIncomingWindow + + + Emergency Call + Appel d'urgence + + + + Incoming emergency call from %1 + Appel d'urgence entrant de %1 + + + + Answering the call will automatically terminate all other ongoing calls. + Répondre à l'appel mettra automatiquement fin à tous les autres appels en cours. + + + + Answer + Répondre + + + + EmojiButton + + + Emoji + Emoji + + + + Selected Emoji: %1 + Emoji sélectionné : %1 + + + + EmojiPicker + + + Switch Emoji category + Changer de catégorie d'emoji + + + + Select Emoji + Sélectionner un emoji + + + + EnumTranslation + + + Trying + Tentative + + + + Ringing + Sonnerie + + + + Call being forwarded + Appel en cours de transfert + + + + Queued + En file d'attente + + + + Progress + En cours + + + + Ok + OK + + + + Accepted + Accepté + + + + Unauthorized + Non autorisé + + + + + Rejected + Rejeté + + + + Not found + Non trouvé + + + + Proxy authentication required + Authentification proxy requise + + + + Request timeout + Délai d'attente de la requête dépassé + + + + Temporarily unavailable + Temporairement indisponible + + + + Ambiguous + Ambigu + + + + Busy here + Occupé + + + + Request terminated + Requête terminée + + + + Not acceptable here + Non acceptable ici + + + + Internal server error + Erreur interne du serveur + + + + Not implemented + Non implémenté + + + + Bad gateway + Passerelle incorrecte + + + + Service unavailable + Service indisponible + + + + Server timeout + Délai d'attente du serveur dépassé + + + + Busy everywhere + Occupé partout + + + + Decline + Refus + + + + Does not exist anywhere + N'existe nulle part + + + + Not acceptable anywhere + Non acceptable nulle part + + + + Unwanted + Indésirable + + + + + + + Unknown + Inconnu + + + + Commercial + Professionnel + + + + Home + Privé + + + + Mobile + Mobile + + + + Incoming + Entrant + + + + Outgoing + Sortant + + + + Blocked + Bloqué + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + FavIcon + + + Set favorite + Ajouter aux favoris + + + + Unset favorite + Retirer des favoris + + + + FavoriteListItemBig + + + Favorite contact + Contact favori + + + + Selected favorite %1: %2 + Favori sélectionné %1 : %2 + + + + tap to start meeting %1 + appuyer pour démarrer la réunion %1 + + + + tap to call %1 + appuyer pour appeler %1 + + + + FavoriteListItemSmall + + + Favorite contact + Contact favori + + + + Selected favorite %1: %2 + Favori sélectionné %1 : %2 + + + + tap to call %1 + appuyer pour appeler %1 + + + + FavoritesList + + + + Favorites + Favoris + + + + List of all contacts that have been marked as favorites + Liste de tous les contacts marqués comme favoris + + + + FavoritesWidget + + + Favorites + Favoris + + + + No favorites to display + Aucun favori à afficher + + + + FirstAid + + + First Aid + Premiers secours + + + + Clicking one of these buttons will end all current calls and start an emergency call. + Cliquer sur l'un de ces boutons mettra fin à tous les appels en cours et déclenchera un appel d'urgence. + + + + Tap to call emergency contact: %1 (%2) + Appuyer pour appeler le contact d'urgence : %1 (%2) + + + + Close + Fermer + + + + Exit the first aid menu without initiating any action + Quitter le menu premiers secours sans initier aucune action + + + + Close first aid menu + Fermer le menu premiers secours + + + + FirstAidButton + + + Open first aid menu + Ouvrir le menu premiers secours + + + + First Aid + Premiers secours + + + + GonnectWindow + + + Home + Accueil + + + + HeadsetDevice + + + MMM dd + dd MMM + + + + HeadsetDeviceProxy + + + Ringing + Sonnerie + + + + Calling + Appel en cours + + + + + + + Call active + Appel actif + + + + Call waiting + Appel en attente + + + + On Hold + En attente + + + + + Call ended + Appel terminé + + + + Phone conference + Conférence téléphonique + + + + HistoryList + + + + No past calls + Aucun appel passé + + + + History + Historique + + + + Searchable list of past calls and meetings + Liste consultable des appels et réunions passés + + + + History item section + Section des éléments d'historique + + + + Header for the currently selected day: %1 + En-tête pour le jour actuellement sélectionné : %1 + + + + History item + Élément d'historique + + + + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 + Élément d'historique sélectionné %1 - société %2, lieu %3, numéro %4, heure %5, durée %6 + + + + hh:mm + hh:mm + + + + HistoryListContextMenu + + + Call + Appel + + + + Copy number + Copier le numéro + + + + Remove favorite + Retirer des favoris + + + + Add favorite + Ajouter aux favoris + + + + Remind when available + Rappeler quand disponible + + + + Unblock + Débloquer + + + + Block for 8 hours + Bloquer pendant 8 heures + + + + HistoryWidget + + + History + Historique + + + + + All + Tous + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + History call type picker + Sélecteur de type d'appel de l'historique + + + + Select the call type to filter by + Sélectionner le type d'appel pour filtrer + + + + Currently selected call type + Type d'appel actuellement sélectionné + + + + Incoming + Entrant + + + + Outgoing + Sortant + + + + Missed + Manqués + + + + History call origin picker + Sélecteur d'origine d'appel de l'historique + + + + Select the call origin to filter by + Sélectionner l'origine de l'appel pour filtrer + + + + Currently selected call origin + Origine de l'appel actuellement sélectionnée + + + + Hide history search + Masquer la recherche dans l'historique + + + + Show history search + Afficher la recherche dans l'historique + + + + IMHandler + + + + + + Ad hoc conference + Conférence ad hoc + + + + IdentitySelector + + + Default + Par défaut + + + + Auto + Auto + + + + Identity selection + Sélection d'identité + + + + Select the preferred identity to be used in calls + Sélectionner l'identité préférée à utiliser pour les appels + + + + Currently selected identity + Identité actuellement sélectionnée + + + + InfoDialog + + + Ok + OK + + + + JitsiConnector + + + New chat message + Nouveau message de discussion + + + + Unnamed participant + Participant sans nom + + + + Active conference + Conférence active + + + + Hang up + Raccrocher + + + + %1 has joined the conference + %1 a rejoint la conférence + + + + %1 has left the conference + %1 a quitté la conférence + + + + JitsiHistoryListContextMenu + + + Start conference + Démarrer une conférence + + + + Remove favorite + Retirer des favoris + + + + Add favorite + Ajouter aux favoris + + + + Copy room name + Copier le nom de la salle + + + + LDAPAddressBookFeeder + + + Failed to initialize LDAP connection + Échec de l'initialisation de la connexion LDAP + + + + + + LDAP error: %1 + Erreur LDAP : %1 + + + + + Parse error: %1 + Erreur d'analyse : %1 + + + + LDAP timeout: %1 + Délai LDAP dépassé : %1 + + + + Failed to initialize LDAP connection: %1 + Échec de l'initialisation de la connexion LDAP : %1 + + + + LinuxDesktopSearchProvider + + + Call + Appel + + + + Main + + + No system tray available + Aucune zone de notification disponible + + + + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. + GOnnect permet un accès rapide aux fonctionnalités via la zone de notification. Votre environnement de bureau n'en fournit pas. + + + + Information + Information + + + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + GOnnect est en cours de test pour votre système d'exploitation et n'a pas encore été officiellement publié. Il n'y a pas encore de parité de fonctionnalités avec la version Linux, et il peut y avoir des bogues que nous n'avons pas encore trouvés. N'hésitez pas à signaler ces problèmes sur GitHub. Bonne exploration ! + + + + There are still phone calls going on, do you really want to quit? + Des appels téléphoniques sont encore en cours. Voulez-vous vraiment quitter ? + + + + Please enter the password for %1: + Veuillez saisir le mot de passe pour %1 : + + + + Please enter the recovery key for %1: + Veuillez saisir la clé de récupération pour %1 : + + + + End all calls + Terminer tous les appels + + + + Do you really want to close this window and terminate all ongoing calls? + Voulez-vous vraiment fermer cette fenêtre et terminer tous les appels en cours ? + + + + Please enter the password for the SIP account: + Veuillez saisir le mot de passe du compte SIP : + + + + Error + Erreur + + + + Fatal Error + Erreur fatale + + + + MainTabBar + + + + Home + Domicile + + + + Conference + Conférence + + + + No active conference + Aucune conférence active + + + + No active call + Aucun appel actif + + + + Move up + Monter + + + + Move tab up + Monter l'onglet + + + + Moves the currently selected tab up by one + Déplace l'onglet actuellement sélectionné d'une position vers le haut + + + + Move down + Descendre + + + + Move tab down + Descendre l'onglet + + + + Moves the currently selected tab down by one + Déplace l'onglet actuellement sélectionné d'une position vers le bas + + + + Edit + Modifier + + + + Edit page + Modifier la page + + + + Edit the currently selected dashboard page + Modifier la page du tableau de bord actuellement sélectionnée + + + + Delete + Supprimer + + + + Delete page + Supprimer la page + + + + Delete the currently selected dashboard page + Supprimer la page du tableau de bord actuellement sélectionnée + + + + Call + Appel + + + + Selected tab + Onglet sélectionné + + + + The currently selected tab + L'onglet actuellement sélectionné + + + + Selected tab options + Options de l'onglet sélectionné + + + + The settings of the currently selected tab + Les paramètres de l'onglet actuellement sélectionné + + + + + Settings + Paramètres + + + + MenuContactInfo + + + Commercial + Professionnel + + + + Mobile + Mobile + + + + Home + Privé + + + + PageCreationWindow + + + Create new dashboard page + Créer une nouvelle page du tableau de bord + + + + Edit dashboard page + Modifier la page du tableau de bord + + + + Name + Nom + + + + Page name + Nom de la page + + + + Enter the page name + Saisir le nom de la page + + + + Icon + Icône + + + + Page icon label + Étiquette de l'icône de la page + + + + Page icon selection + Sélection de l'icône de la page + + + + Select the page icon for the dashboard page + Sélectionner l'icône de la page du tableau de bord + + + + Currently selected page icon option + Option d'icône de page actuellement sélectionnée + + + + Cancel + Annuler + + + + Cancel page modifcation + Annuler la modification de la page + + + + Cancel button to exit the page creation/update window + Bouton Annuler pour quitter la fenêtre de création/mise à jour de la page + + + + Create + Créer + + + + Save + Enregistrer + + + + page + page + + + + Confirmation button to create the new dashboard page + Bouton de confirmation pour créer la nouvelle page du tableau de bord + + + + Confirmation button to apply changes to the dashboard page + Bouton de confirmation pour appliquer les modifications à la page du tableau de bord + + + + ParticipantsList + + + Participants list + Liste des participants + + + + List of all the participants of the current chat room + Liste de tous les participants de la salle de discussion actuelle + + + + Chat participant + Participant à la discussion + + + + Selected chat participant: %1 + Participant à la discussion sélectionné : %1 + + + + moderator + modérateur + + + + it's you + c'est vous + + + + Kick + Expulser + + + + Make moderator + Rendre modérateur + + + + PhoneNumberUtil + + + Anonymous + Anonyme + + + + PreferredIdentityEditWindow + + + Phone Number Transmission + Transmission du numéro de téléphone + + + + Name + Nom + + + + Prefix + Préfixe + + + + Identity + Identité + + + + Enabled + Activé + + + + Automatic + Automatique + + + + Delete + Supprimer + + + + Save + Enregistrer + + + + QObject + + + There are %n active call(s). + calls + + Il y a %n appel actif. + Il y a %n appels actifs. + + + + + Andorra + Andorre + + + + United Arab Emirates + Émirats arabes unis + + + + Afghanistan + Afghanistan + + + + Antigua and Barbuda + Antigua-et-Barbuda + + + + Anguilla + Anguilla + + + + Albania + Albanie + + + + Armenia + Arménie + + + + Angola + Angola + + + + Argentina + Argentine + + + + American Samoa + Samoa américaines + + + + Austria + Autriche + + + + Australia + Australie + + + + Aruba + Aruba + + + + Aland Islands + Îles Åland + + + + Azerbaijan + Azerbaïdjan + + + + Bosnia and Herzegovina + Bosnie-Herzégovine + + + + Barbados + Barbade + + + + Bangladesh + Bangladesh + + + + Belgium + Belgique + + + + Burkina Faso + Burkina Faso + + + + Bulgaria + Bulgarie + + + + Bahrain + Bahreïn + + + + Burundi + Burundi + + + + Benin + Bénin + + + + Saint Barthelemy + Saint-Barthélemy + + + + Bermuda + Bermudes + + + + Brunei + Brunéi Darussalam + + + + Bolivia + Bolivie + + + + Bonaire, Saint Eustatius and Saba + Bonaire, Saint-Eustache et Saba + + + + Brazil + Brésil + + + + Bahamas + Bahamas + + + + Bhutan + Bhoutan + + + + Botswana + Botswana + + + + Belarus + Biélorussie + + + + Belize + Belize + + + + Canada + Canada + + + + Cocos Islands + Îles Cocos + + + + Democratic Republic of the Congo + République démocratique du Congo + + + + Central African Republic + République centrafricaine + + + + Republic of the Congo + République du Congo + + + + Switzerland + Suisse + + + + Ivory Coast + Côte d'Ivoire + + + + Cook Islands + Îles Cook + + + + Chile + Chili + + + + Cameroon + Cameroun + + + + China + Chine + + + + Colombia + Colombie + + + + Costa Rica + Costa Rica + + + + Cuba + Cuba + + + + Cape Verde + Cap-Vert + + + + Curacao + Curaçao + + + + Christmas Island + Île Christmas + + + + Cyprus + Chypre + + + + Czech Republic + République tchèque + + + + Germany + Allemagne + + + + Djibouti + Djibouti + + + + Denmark + Danemark + + + + Dominica + Dominique + + + + Dominican Republic + République dominicaine + + + + Algeria + Algérie + + + + Ecuador + Équateur + + + + Estonia + Estonie + + + + Egypt + Égypte + + + + Western Sahara + Sahara occidental + + + + Eritrea + Érythrée + + + + Spain + Espagne + + + + Ethiopia + Éthiopie + + + + Finland + Finlande + + + + Fiji + Fidji + + + + Falkland Islands + Îles Malouines + + + + Micronesia + Micronésie + + + + Faroe Islands + Îles Féroé + + + + France + France + + + + Gabon + Gabon + + + + United Kingdom + Royaume-Uni + + + + Grenada + Grenade + + + + Georgia + Géorgie + + + + French Guiana + Guyane française + + + + Guernsey + Guernesey + + + + Ghana + Ghana + + + + Gibraltar + Gibraltar + + + + Greenland + Groenland + + + + Gambia + Gambie + + + + Guinea + Guinée + + + + Guadeloupe + Guadeloupe + + + + Equatorial Guinea + Guinée équatoriale + + + + Greece + Grèce + + + + Guatemala + Guatemala + + + + Guam + Guam + + + + Guinea-Bissau + Guinée-Bissau + + + + Guyana + Guyana + + + + Hong Kong + Hong Kong + + + + Heard Island and McDonald Islands + Îles Heard-et-MacDonald + + + + Honduras + Honduras + + + + Croatia + Croatie + + + + Haiti + Haïti + + + + Hungary + Hongrie + + + + Indonesia + Indonésie + + + + Ireland + Irlande + + + + Israel + Israël + + + + Isle of Man + Île de Man + + + + India + Inde + + + + British Indian Ocean Territory + Territoire britannique de l'océan Indien + + + + Iraq + Irak + + + + Iran + Iran + + + + Iceland + Islande + + + + Italy + Italie + + + + Jersey + Jersey + + + + Jamaica + Jamaïque + + + + Jordan + Jordanie + + + + Japan + Japon + + + + Kenya + Kenya + + + + Kyrgyzstan + Kirghizstan + + + + Cambodia + Cambodge + + + + Kiribati + Kiribati + + + + Comoros + Comores + + + + Saint Kitts and Nevis + Saint-Kitts-et-Nevis + + + + North Korea + Corée du Nord + + + + South Korea + Corée du Sud + + + + Kuwait + Koweït + + + + Cayman Islands + Îles Caïmans + + + + Kazakhstan + Kazakhstan + + + + Laos + Laos + + + + Lebanon + Liban + + + + Saint Lucia + Sainte-Lucie + + + + Liechtenstein + Liechtenstein + + + + Sri Lanka + Sri Lanka + + + + Liberia + Libéria + + + + Lesotho + Lesotho + + + + Lithuania + Lituanie + + + + Luxembourg + Luxembourg + + + + Latvia + Lettonie + + + + Libya + Libye + + + + Morocco + Maroc + + + + Monaco + Monaco + + + + Moldova + Moldavie + + + + Montenegro + Monténégro + + + + Saint Martin + Saint-Martin + + + + Madagascar + Madagascar + + + + Marshall Islands + Îles Marshall + + + + Macedonia + Macédoine + + + + Mali + Mali + + + + Myanmar + Myanmar + + + + Mongolia + Mongolie + + + + Macao + Macao + + + + Northern Mariana Islands + Îles Mariannes du Nord + + + + Martinique + Martinique + + + + Mauritania + Mauritanie + + + + Montserrat + Montserrat + + + + Malta + Malte + + + + Mauritius + Maurice + + + + Maldives + Maldives + + + + Malawi + Malawi + + + + Mexico + Mexique + + + + Malaysia + Malaisie + + + + Mozambique + Mozambique + + + + Namibia + Namibie + + + + New Caledonia + Nouvelle-Calédonie + + + + Niger + Niger + + + + Norfolk Island + Île Norfolk + + + + Nigeria + Nigéria + + + + Nicaragua + Nicaragua + + + + Netherlands + Pays-Bas + + + + Norway + Norvège + + + + Nepal + Népal + + + + Nauru + Nauru + + + + Niue + Niue + + + + New Zealand + Nouvelle-Zélande + + + + Oman + Oman + + + + Panama + Panama + + + + Peru + Pérou + + + + French Polynesia + Polynésie française + + + + Papua New Guinea + Papouasie-Nouvelle-Guinée + + + + Philippines + Philippines + + + + Pakistan + Pakistan + + + + Poland + Pologne + + + + Saint Pierre and Miquelon + Saint-Pierre-et-Miquelon + + + + Pitcairn + Îles Pitcairn + + + + Puerto Rico + Porto Rico + + + + Palestinian Territory + Territoire palestinien + + + + Portugal + Portugal + + + + Palau + Palaos + + + + Paraguay + Paraguay + + + + Qatar + Qatar + + + + Reunion + La Réunion + + + + Romania + Roumanie + + + + Serbia + Serbie + + + + Russia + Russie + + + + Rwanda + Rwanda + + + + Saudi Arabia + Arabie saoudite + + + + Solomon Islands + Îles Salomon + + + + Seychelles + Seychelles + + + + Sudan + Soudan + + + + Sweden + Suède + + + + Singapore + Singapour + + + + Saint Helena + Sainte-Hélène + + + + Slovenia + Slovénie + + + + Svalbard and Jan Mayen + Svalbard et Jan Mayen + + + + Slovakia + Slovaquie + + + + Sierra Leone + Sierra Leone + + + + San Marino + Saint-Marin + + + + Senegal + Sénégal + + + + Somalia + Somalie + + + + Suriname + Suriname + + + + South Sudan + Soudan du Sud + + + + Sao Tome and Principe + Sao Tomé-et-Principe + + + + El Salvador + El Salvador + + + + Sint Maarten + Saint-Martin (partie néerlandaise) + + + + Syria + Syrie + + + + Swaziland + Swaziland + + + + Turks and Caicos Islands + Îles Turques-et-Caïques + + + + Chad + Tchad + + + + Togo + Togo + + + + Thailand + Thaïlande + + + + Tajikistan + Tadjikistan + + + + Tokelau + Tokelau + + + + East Timor + Timor oriental + + + + Turkmenistan + Turkménistan + + + + Tunisia + Tunisie + + + + Tonga + Tonga + + + + Turkey + Turquie + + + + Trinidad and Tobago + Trinité-et-Tobago + + + + Tuvalu + Tuvalu + + + + Taiwan + Taïwan + + + + Tanzania + Tanzanie + + + + Ukraine + Ukraine + + + + Uganda + Ouganda + + + + United States Minor Outlying Islands + Îles mineures éloignées des États-Unis + + + + United States + États-Unis + + + + Uruguay + Uruguay + + + + Uzbekistan + Ouzbékistan + + + + Vatican + Vatican + + + + Saint Vincent and the Grenadines + Saint-Vincent-et-les-Grenadines + + + + Venezuela + Venezuela + + + + British Virgin Islands + Îles Vierges britanniques + + + + U.S. Virgin Islands + Îles Vierges américaines + + + + Vietnam + Viêt Nam + + + + Vanuatu + Vanuatu + + + + Wallis and Futuna + Wallis-et-Futuna + + + + Samoa + Samoa + + + + Yemen + Yémen + + + + Mayotte + Mayotte + + + + South Africa + Afrique du Sud + + + + Zambia + Zambie + + + + Zimbabwe + Zimbabwe + + + + ResponseTreeModel + + + + + + Additional Information + Informations supplémentaires + + + + RingToneFactory + + + ringTone + 440,0,1500,3500,0 + + + + busyTone + 440,0,500,500,0 + + + + congestionTone + 440,0,250,250,0 + + + + zip + 440,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 + + + + endTone + 440,0,200,200,200,200,200,200,-1 + + + + SIPAccount + + + 'userUri' is no valid SIP URI: %1 + 'userUri' n'est pas une URI SIP valide : %1 + + + + 'userUri' is required + 'userUri' est obligatoire + + + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' n'est pas une URI SIP valide : %1 + + + + 'registrarUri' is required + 'registrarUri' est obligatoire + + + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' contient une entrée URI SIP invalide : %1 + + + + Failed to create %1: %2 + Échec de la création de %1 : %2 + + + + SIPBuddy + + + %1 is now available + %1 est maintenant disponible + + + + SIPCall + + + Active call with %1 + Appel actif avec %1 + + + + Hang up + Raccrocher + + + + SIPCallManager + + + %1 is calling + %1 appelle + + + + %1 (%2) is calling + %1 (%2) appelle + + + + + Reject + Rejeter + + + + + Accept + Accepter + + + + Missed call from %1 + Appel manqué de %1 + + + + Call back + Rappeler + + + + SIPManager + + + New Identity + Nouvelle identité + + + + SIPTemplate + + + + Failed to write to %1 + Échec de l'écriture dans %1 + + + + Failed to copy %1 to the config space + Échec de la copie de %1 dans le répertoire de configuration + + + + Source file %1 does not exist + Le fichier source %1 n'existe pas + + + + SearchBox + + + Search number + Rechercher un numéro + + + + SearchCategoryItem + + + Search result category filter %1 + Filtre de catégorie des résultats de recherche %1 + + + + Filter for the individual search result items by category + Filtrer les éléments de résultats de recherche individuels par catégorie + + + + SearchCategoryList + + + Contacts + Contacts + + + + Messages + Messages + + + + Rooms and Teams + Salles et équipes + + + + Files + Fichiers + + + + SearchDial + + + Select number + Sélectionner un numéro + + + + Activate search field + Activer le champ de recherche + + + + Number or contact + Numéro ou contact + + + + Clear search field + Effacer le champ de recherche + + + + Default + Par défaut + + + + Auto + Auto + + + + Preferred identity + Identité préférée + + + + Select the preferred identity for outgoing calls + Sélectionner l'identité préférée pour les appels sortants + + + + SearchField + + + Search for contacts or room names... + Rechercher des contacts ou des noms de salle... + + + + Clear search field + Effacer le champ de recherche + + + + SearchResultCategory + + + Search result category %1 + Catégorie de résultats de recherche %1 + + + + Divider for the individual search result items by category + Séparateur pour les éléments de résultats de recherche individuels par catégorie + + + + SearchResultItem + + + Search result + Résultat de recherche + + + + Currently selected search result + Résultat de recherche actuellement sélectionné + + + + SearchResultNumberItem + + + Phone number + Numéro de téléphone + + + + Selected favorite number %1 + Numéro favori sélectionné %1 + + + + Selected phone number %1 + Numéro de téléphone sélectionné %1 + + + + SearchResultPopup + + + Search filter and identity selection + Filtre de recherche et sélection d'identité + + + + Select search filter to be applied, as well as the outgoing identity + Sélectionner le filtre de recherche à appliquer, ainsi que l'identité sortante + + + + Outgoing identity + Identité sortante + + + + Search results + Résultats de recherche + + + + All search results will be listed here in their respective categories + Tous les résultats de recherche seront répertoriés ici dans leurs catégories respectives + + + + Direct dial + Numérotation directe + + + + Call "%1" + Appeler « %1 » + + + + Open room "%1" + Ouvrir la salle « %1 » + + + + History + Historique + + + + Contacts + Contacts + + + + SettingsPage + + + Settings + Paramètres + + + + Use dark mode tray icon + Utiliser l'icône de zone de notification en mode sombre + + + + Inverse Accept / Reject buttons + Inverser les boutons Accepter/Rejeter + + + + Show chat messages as desktop notifications + Afficher les messages de discussion comme notifications du bureau + + + + Enable USB headset driver [%1] + Activer le pilote de casque USB [%1] + + + + not detected + non détecté + + + + Disable USB headset mute state propagation + Désactiver la propagation de l'état de silence du casque USB + + + + Show dial window on USB headset pick up + Afficher la fenêtre de numérotation lors du décrochage du casque USB + + + + Color scheme + Jeu de couleurs + + + + System default + Valeur par défaut du système + + + + Light + Clair + + + + Dark + Sombre + + + + Reload contacts from LDAP + Recharger les contacts depuis LDAP + + + + Phoning + Téléphonie + + + + Appearance + Apparence + + + + Signalling busy when a call is active + Signaler occupé lorsqu'un appel est actif + + + + Rules for telephone number transmission + Règles pour la transmission du numéro de téléphone + + + + Standard preferred identity + Identité préférée standard + + + + + Default + Par défaut + + + + + Auto + Auto + + + + Prefer USB headset ring sound if available + Préférer la sonnerie du casque USB si disponible + + + + No preferred identities yet. + Aucune identité préférée pour l'instant. + + + + Show main window on startup + Afficher la fenêtre principale au démarrage + + + + Disable synchronisation with the system mute state + Désactiver la synchronisation avec l'état de silence du système + + + + + restart required + redémarrage requis + + + + Use custom window decoration + Utiliser la décoration de fenêtre personnalisée + + + + Theme selection box + Zone de sélection du thème + + + + Select the UI theme + Sélectionner le thème de l'interface + + + + Currently selected theme option + Option de thème actuellement sélectionnée + + + + + Prefererred identity selection + Sélection de l'identité préférée + + + + + Select the preferred identity + Sélectionner l'identité préférée + + + + Currently selected identity option + Option d'identité actuellement sélectionnée + + + + Currently highlighted preferred identity. Tap to edit. + Identité préférée actuellement mise en évidence. Appuyer pour modifier. + + + + Standard + Standard + + + + Add identity + Ajouter une identité + + + + Add a new preferred identity entry + Ajouter une nouvelle entrée d'identité préférée + + + + Audio settings + Paramètres audio + + + + Input device + Périphérique d'entrée + + + + Input device selection + Sélection du périphérique d'entrée + + + + Select the input device to be used + Sélectionner le périphérique d'entrée à utiliser + + + + Currently selected input option + Option d'entrée actuellement sélectionnée + + + + Output device + Périphérique de sortie + + + + Output device selection + Sélection du périphérique de sortie + + + + Select the output device to be used + Sélectionner le périphérique de sortie à utiliser + + + + Currently selected output option + Option de sortie actuellement sélectionnée + + + + Output device for ring tone + Périphérique de sortie pour la sonnerie + + + + Currently selected ring output option + Option de sortie de sonnerie actuellement sélectionnée + + + + Ring tone + Sonnerie + + + + Reset ring tone + Réinitialiser la sonnerie + + + + Reset the ring tone to its default option + Réinitialiser la sonnerie à son option par défaut + + + + Pick ring tone + Choisir une sonnerie + + + + Select the ring tone you want to use for incoming calls + Sélectionner la sonnerie à utiliser pour les appels entrants + + + + + Currently set to: + Actuellement défini sur : + + + + Ring tone volume + Volume de la sonnerie + + + + + Adjust %1 + Régler %1 + + + + %1 % + %1 % + + + + Pause between ring tones [s] + Pause entre les sonneries [s] + + + + Debugging + Débogage + + + + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. + Utilisez ce bouton pour démarrer une session de débogage. L'application redémarrera et commencera à enregistrer des informations supplémentaires utiles au débogage. Pendant cette session, revenez ici pour télécharger les informations. Une session de débogage est limitée à 5 minutes, après quoi l'application redémarrera automatiquement en mode normal. + + + + Start debug run (restart app) + Démarrer la session de débogage (redémarrer l'application) + + + + Download debug information + Télécharger les informations de débogage + + + + ShortcutsWindow + + + Shortcuts + Raccourcis clavier + + + + Shortcut key: %1 + Touche de raccourci : %1 + + + + Local shortcuts (work only when app is focused) + Raccourcis locaux (fonctionnent uniquement quand l'application est au premier plan) + + + + Local shortcuts + Raccourcis locaux + + + + Ctrl + F + Ctrl + F + + + + Activates the global search field + Active le champ de recherche global + + + + F11 + F11 + + + + Toggles between fullsceen and normal window mode + Bascule entre le mode plein écran et le mode fenêtré normal + + + + Ctrl + Shift + M + Ctrl + Maj + M + + + + Toggles audio mute + Active/désactive le silence audio + + + + Global shortcuts (work from anywhere) + Raccourcis globaux (fonctionnent depuis n'importe où) + + + + Global shortcuts + Raccourcis globaux + + + + SipTemplateWizard + + + Initial configuration + Configuration initiale + + + + Error: %1 + Erreur : %1 + + + + SIP wizard notification + Notification de l'assistant SIP + + + + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. + GOnnect ne trouve pas de configuration SIP. Pour commencer, sélectionnez l'un des modèles ci-dessous et modifiez le fichier de configuration résultant si nécessaire. + + + + Please pick: + Veuillez sélectionner : + + + + Select SIP template + Sélectionner un modèle SIP + + + + Select the SIP template to be used + Sélectionner le modèle SIP à utiliser + + + + Currently selected SIP template + Modèle SIP actuellement sélectionné + + + + Next + Suivant + + + + Continue setup + Continuer la configuration + + + + Confirmation button to continue the setup + Bouton de confirmation pour continuer la configuration + + + + Template field list + Liste des champs du modèle + + + + List of all the available SIP template options + Liste de toutes les options de modèle SIP disponibles + + + + SIP template option + Option de modèle SIP + + + + Currently selected SIP template option + Option de modèle SIP actuellement sélectionnée + + + + Display name of the SIP template option + Nom d'affichage de l'option de modèle SIP + + + + Description of the SIP template option + Description de l'option de modèle SIP + + + + Back + Retour + + + + Back button to return to the template selection menu + Bouton Retour pour revenir au menu de sélection de modèle + + + + + Finish + Terminer + + + + Confirmation button to apply the changes to the SIP template + Bouton de confirmation pour appliquer les modifications au modèle SIP + + + + Successful configuration file creation + Fichier de configuration créé avec succès + + + + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. + Un fichier de configuration a été créé pour vous. Veuillez vérifier si des modifications sont nécessaires pour répondre à vos besoins et redémarrer GOnnect pour les activer. + + + + The configuration has been saved to: + La configuration a été enregistrée dans : + + + + Copy to clipboard + Copier dans le presse-papiers + + + + Copy the full path of the configuration file to the clipboard + Copier le chemin complet du fichier de configuration dans le presse-papiers + + + + Finish wizard + Terminer l'assistant + + + + Finish the SIP configuration wizard + Terminer l'assistant de configuration SIP + + + + StateManager + + + Show dial window and focus search field + Afficher la fenêtre de numérotation et activer le champ de recherche + + + + End all calls + Terminer tous les appels + + + + Redial last outgoing call + Recomposer le dernier numéro appelé + + + + Toggle hold + Basculer la mise en attente + + + + Phone calls are active + Des appels téléphoniques sont actifs + + + + SystemTrayMenu + + + + Dial... + Numéroter... + + + + Not registered... + Non enregistré... + + + + Settings + Paramètres + + + + About + À propos + + + + Quit + Quitter + + + + End conference + Terminer la conférence + + + + Call with %1 has ended + L'appel avec %1 est terminé + + + + Accept call with %1 + Accepter l'appel de %1 + + + + Hang up call with %1 + Raccrocher l'appel avec %1 + + + + TemplateFieldFile + + + File path + Chemin du fichier + + + + Enter the file path for %1 + Saisir le chemin du fichier pour %1 + + + + Choose... + Choisir... + + + + Open file picker + Ouvrir le sélecteur de fichier + + + + Select the file that should be used for %1 + Sélectionner le fichier à utiliser pour %1 + + + + Certificate files (%1) + Fichiers de certificat (%1) + + + + TemplateFieldText + + + Text input + Saisie de texte + + + + Enter the desired value for %1 + Saisir la valeur souhaitée pour %1 + + + + Toggler + + + Failed to toggle the state of %1. + Échec du basculement de l'état de %1. + + + + TogglerList + + + Toggler list + Liste des bascules + + + + List of items that can be toggled + Liste des éléments pouvant être basculés + + + + Toggle %1 + Basculer %1 + + + + VerticalLevelMeter + + + Level meter + Indicateur de niveau + + + + VideoDeviceMenu + + + Virtual background + Arrière-plan virtuel + + + + ViewHelper + + + Save File + Enregistrer le fichier + + + + %n minute(s) + + %n minute + %n minutes + + + + + 1 hour and %n minute(s) + + 1 heure et %n minute + 1 heure et %n minutes + + + + + %n hour(s) + + %n heure + %n heures + + + + + Audio Files (%1) + Fichiers audio (%1) + + + + VolumePopup + + + Adjust the volume + Régler le volume + + + + Unmute + Réactiver le son + + + + Mute + Couper le son + + + + Open audio settings + Ouvrir les paramètres audio + + + + WidgetSelectionWindow + + + Add widget + Ajouter un widget + + + + Widget + Widget + + + + Widget selection header + En-tête de sélection des widgets + + + + Date Events + Dates + + + + List of upcoming appointments + Liste des rendez-vous à venir + + + + Favorites + Favoris + + + + Quick dial for your favorite contacts and conferences + Numérotation rapide pour vos contacts et conférences favoris + + + + History + Historique + + + + Web View + Vue web + + + + A web-based content display + Affichage de contenu web + + + + Widget selection + Sélection de widget + + + + Select the widget that should be added to the current dashboard page + Sélectionner le widget à ajouter à la page actuelle du tableau de bord + + + + Currently selected widget option + Option de widget actuellement sélectionnée + + + + Accept all certificates + Accepter tous les certificats + + + + Confirm widget selection + Confirmer la sélection du widget + + + + Confirmation button to create and add the selected widget to the current dashboard + Bouton de confirmation pour créer et ajouter le widget sélectionné au tableau de bord actuel + + + + Searchable call and conference history + Historique consultable des appels et conférences + + + + Title + Titre + + + + URL + URL + + + + URL (dark mode) + URL (mode sombre) + + + + Settings text input + Champ de saisie des paramètres + + + + Input for widget setting %1 + Champ pour le paramètre de widget %1 + + + + Settings checkbox + Case à cocher des paramètres + + + + Checkbox for widget setting %1 + Case à cocher pour le paramètre de widget %1 + + + + Widget setting %1 + Paramètre de widget %1 + + + + Cancel + Annuler + + + + Cancel widget selection + Annuler la sélection du widget + + + + Cancel button to exit widget selection selection without changes + Bouton Annuler pour quitter la sélection du widget sans modifications + + + + Add + Ajouter + + + + WindowHeader + + + GOnnect window header + En-tête de la fenêtre GOnnect + + + + Minimize + Réduire + + + + Maximize + Agrandir + + + + Close GOnnect window + Fermer la fenêtre GOnnect + + + diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts index 4973e263..33bafc0e 100644 --- a/i18n/gonnect_it.ts +++ b/i18n/gonnect_it.ts @@ -6,32 +6,32 @@ About - + Informazioni GOnnect headline - + Titolo di GOnnect GOnnect version - + Versione di GOnnect Version: v%1 - + Versione: v%1 Copy to clipboard - + Copia negli appunti Copy the currently used version of Gonnect to the clipboard - + Copia la versione attualmente utilizzata di GOnnect negli appunti @@ -39,32 +39,32 @@ Homepage - + Pagina iniziale Visit the project homepage - + Visita la pagina iniziale del progetto Bug Tracker - + Bug Tracker Visit the project bug tracker - + Visita il bug tracker del progetto Documentation - + Documentazione Visit the online project documentation - + Visita la documentazione online del progetto @@ -72,17 +72,17 @@ Additional caller related information - + Informazioni aggiuntive relative al chiamante List of informational items regarding the caller, such as open support tickets - + Elenco di informazioni sul chiamante, come ticket di supporto aperti Expandable response section - + Sezione di risposta espandibile @@ -90,7 +90,7 @@ Default - + Predefinito @@ -98,117 +98,117 @@ Unknown audio environment - + Ambiente audio sconosciuto Audio environment error - + Errore nell'ambiente audio No fitting audio environment could be found. Please select the desired audio devices. - + Nessun ambiente audio adatto trovato. Selezionare i dispositivi audio desiderati. Input device selection header - + Intestazione della selezione del dispositivo di ingresso Header for the input device selection below - + Intestazione per la selezione del dispositivo di ingresso qui sotto Input device - + Dispositivo di ingresso Input device selection box - + Casella di selezione del dispositivo di ingresso Select the input device that should be used - + Selezionare il dispositivo di ingresso da utilizzare Currently selected input device - + Dispositivo di ingresso attualmente selezionato Output device selection header - + Intestazione della selezione del dispositivo di uscita Header for the output device selection below - + Intestazione per la selezione del dispositivo di uscita qui sotto Output device - + Dispositivo di uscita Output device selection box - + Casella di selezione del dispositivo di uscita Select the output device that should be used - + Selezionare il dispositivo di uscita da utilizzare Currently selected output device - + Dispositivo di uscita attualmente selezionato Ring tone output device - + Dispositivo di uscita per la suoneria Output device for ring tone - + Dispositivo di uscita per la suoneria Ring tone output device selection box - + Casella di selezione del dispositivo di uscita per la suoneria Select the output device that should be used for playing the ring tone - + Selezionare il dispositivo di uscita da utilizzare per la suoneria Currently selected ring tone output device - + Dispositivo di uscita suoneria attualmente selezionato Ok - + Ok Close audio environment selection - + Chiudi la selezione dell'ambiente audio Confirmation button to leave the audio environment selection window - + Pulsante di conferma per uscire dalla finestra di selezione dell'ambiente audio @@ -216,7 +216,7 @@ Change volume - + Cambia volume @@ -224,12 +224,12 @@ Default input - + Ingresso predefinito Default output - + Uscita predefinita @@ -237,7 +237,7 @@ Initials of this contact - + Iniziali di questo contatto @@ -245,7 +245,7 @@ Dialog - + Dialogo @@ -253,17 +253,17 @@ Base dashboard page grid - + Griglia di base della pagina del pannello Canvas for editable dashboard pages - + Area di disegno per le pagine del pannello modificabili Add widgets - + Aggiungi widget @@ -271,32 +271,32 @@ Drag widget - + Trascina widget Change the position of the widget - + Cambia la posizione del widget Remove widget - + Rimuovi widget Remove the currently selected widget from the dashboard - + Rimuovi il widget attualmente selezionato dal pannello Resize widget - + Ridimensiona widget Resize the widget according to the mouse direction - + Ridimensiona il widget in base alla direzione del mouse @@ -311,47 +311,47 @@ Drag border - + Bordo di trascinamento Top left drag border for window resize operations - + Bordo di trascinamento in alto a sinistra per ridimensionare la finestra Top drag border for window resize operations - + Bordo di trascinamento in alto per ridimensionare la finestra Top right border for window resize operations - + Bordo in alto a destra per ridimensionare la finestra Right drag border for window resize operations - + Bordo di trascinamento a destra per ridimensionare la finestra Bottom right drag border for window resize operations - + Bordo di trascinamento in basso a destra per ridimensionare la finestra Bottom drag border for window resize operations - + Bordo di trascinamento in basso per ridimensionare la finestra Bottom left drag border for window resize operations - + Bordo di trascinamento in basso a sinistra per ridimensionare la finestra Left drag border for window resize operations - + Bordo di trascinamento a sinistra per ridimensionare la finestra @@ -359,7 +359,7 @@ Status bar - + Barra di stato @@ -367,27 +367,27 @@ Toggle fullscreen - + Passa alla modalità a schermo intero Shortcuts... - + Scorciatoie... Customize UI - + Personalizza l'interfaccia About... - + Informazioni... Quit - + Esci @@ -395,12 +395,12 @@ Conference - + Conferenza Drag bar - + Barra di trascinamento @@ -408,273 +408,273 @@ %1@%2 kHz - + %1@%2 kHz Transmit - + Trasmetti Call security level - + Livello di sicurezza della chiamata Security level of the ongoing call - + Livello di sicurezza della chiamata in corso Call security details - + Dettagli di sicurezza della chiamata Detailed call security status: %1 / %2 - + Stato dettagliato della sicurezza della chiamata: %1 / %2 signaling encrypted - + segnalazione cifrata signaling unencrypted - + segnalazione non cifrata media encrypted - + media cifrati media unencrypted - + media non cifrati Call quality - + Qualità della chiamata Quality of the ongoing call - + Qualità della chiamata in corso Transmission statistics - + Statistiche di trasmissione Call quality metrics - + Metriche di qualità della chiamata MOS - + MOS Mean opinion score - + Punteggio di opinione medio (MOS) Numerical metric assessing transmission-side voice call quality: %1 - + Metrica numerica che valuta la qualità vocale dal lato trasmissione: %1 Packet loss - + Perdita di pacchetti %1% of packets lost in transmission - + %1% dei pacchetti persi in trasmissione Jitter - + Jitter Amount of transmission side jitter: %1 - + Jitter dal lato trasmissione: %1 Effective delay - + Ritardo effettivo Effective transmission side call delay: %1 - + Ritardo effettivo della chiamata dal lato trasmissione: %1 Receiver statistics - + Statistiche di ricezione Receive - + Ricevi Numerical metric assessing receiver-side voice/video call quality: %1 - + Metrica numerica che valuta la qualità voce/video dal lato ricevitore: %1 %1% of packets lost in receival - + %1% dei pacchetti persi in ricezione Amount of receiver side jitter: %1 - + Jitter dal lato ricevitore: %1 Effective receiver side call delay: %1 - + Ritardo effettivo della chiamata dal lato ricevitore: %1 Codec - + Codec Audio codec - + Codec audio The currently used audio codec and frequency: %1 - + Il codec audio e la frequenza attualmente in uso: %1 Elapsed call time - + Durata trascorsa della chiamata The duration in seconds the call has been active for: %1 - + La durata in secondi in cui la chiamata è stata attiva: %1 Screen - + Schermo Screensharing control - + Controllo condivisione schermo Start sharing your screen - + Avvia la condivisione dello schermo Camera - + Fotocamera Camera control - + Controllo fotocamera Enable your camera - + Attiva la fotocamera Resume - + Riprendi Hold - + In attesa Resume call - + Riprendi la chiamata Hold call - + Metti in attesa la chiamata Update the call hold state - + Aggiorna lo stato di attesa della chiamata Micro - + Microfono Input control - + Controllo ingresso Set the mute state of the current input device - + Imposta lo stato di silenziamento del dispositivo di ingresso corrente Output - + Uscita Output control - + Controllo uscita Change the current output devices - + Cambia i dispositivi di uscita correnti Accept call - + Accetta chiamata Hangup call - + Termina chiamata @@ -682,47 +682,47 @@ SIP call status code - + Codice di stato chiamata SIP The current status code of the call: %1 - + Il codice di stato corrente della chiamata: %1 Jitsi Meet - + Jitsi Meet Switch to a Jitsi Meet session - + Passa a una sessione Jitsi Meet Unhold - + Togli attesa Hold - + In attesa Toggle the hold state to %1 - + Commuta lo stato di attesa a %1 Accept call - + Accetta chiamata Hangup call - + Termina chiamata @@ -730,17 +730,17 @@ Failed to create directory %1 to store the call history database. - + Impossibile creare la directory %1 per memorizzare il database della cronologia delle chiamate. Failed to open call history database: %1 - + Impossibile aprire il database della cronologia chiamate: %1 Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. - + Il database della cronologia delle chiamate non è coerente. Rimuovere %1 e riavviare l'app per reinizializzare il database. @@ -748,22 +748,22 @@ Call - + Chiamata Selected call %1 - contact %2, company %3, location %4/%5, number %6 - + Chiamata selezionata %1 - contatto %2, azienda %3, posizione %4/%5, numero %6 Hangup button - + Pulsante di fine chiamata Pressing this will end the call - + Premendo questo si termina la chiamata @@ -771,18 +771,18 @@ Drag callers onto each other to transfer call - + Trascina i chiamanti uno sull'altro per trasferire la chiamata List of active calls - + Elenco delle chiamate attive Create conference - + Crea conferenza @@ -790,20 +790,20 @@ Chat - + Chat Person(s) - - - + + Persona + Persone Info - + Informazioni @@ -811,17 +811,17 @@ Caller name - + Nome del chiamante is calling... - + sta chiamando... Calling... - + Chiamata in corso... @@ -829,7 +829,7 @@ unknown number - + numero sconosciuto @@ -837,7 +837,7 @@ List of configurable options - + Elenco delle opzioni configurabili @@ -845,17 +845,17 @@ Message - + Messaggio Type message - + Scrivi messaggio Enter the chat text message - + Inserisci il messaggio di testo della chat @@ -863,17 +863,17 @@ Chat message list - + Elenco messaggi chat List of all chat messages of the current chat room - + Elenco di tutti i messaggi della stanza chat corrente Auto scroll down - + Scorrimento automatico verso il basso @@ -881,12 +881,12 @@ Chat message - + Messaggio chat Selected chat message - from %1, at %2: %3 - + Messaggio chat selezionato - da %1, alle %2: %3 @@ -894,12 +894,12 @@ Chat room list - + Elenco stanze chat List of all chat rooms - + Elenco di tutte le stanze chat @@ -907,12 +907,12 @@ Chat room - + Stanza chat Selected chat room %1: %2 unread messages - + Stanza chat selezionata %1: %2 messaggi non letti @@ -920,42 +920,42 @@ Chat message list - + Elenco messaggi chat List of all the messages in the current chat - + Elenco di tutti i messaggi nella chat corrente Chat message - + Messaggio chat Selected chat message from %1 at %2: %3 - + Messaggio chat selezionato da %1 alle %2: %3 the server - + il server you - + tu Select emoji - + Seleziona emoji Enter chat message... - + Scrivi un messaggio... @@ -963,27 +963,27 @@ Please enter your recovery key to decrypt messages: - + Inserire la chiave di ripristino per decifrare i messaggi: Recovery key - + Chiave di ripristino Enter recovery key - + Inserisci la chiave di ripristino Use key - + Usa la chiave Use recovery key - + Usa la chiave di ripristino @@ -991,7 +991,7 @@ Copy to clipboard: %1 - + Copia negli appunti: %1 @@ -1000,137 +1000,137 @@ Set room name - + Imposta nome stanza Room name: - + Nome stanza: Enter the room name - + Inserisci il nome della stanza Authenticate - + Autenticati Please authenticate in the opened browser window... - + Eseguire l'autenticazione nella finestra del browser aperta... This conference is protected by a password. Please enter it to join the room. - + Questa conferenza è protetta da una password. Inserirla per accedere alla stanza. Password - + Password Enter the password - + Inserisci la password Remember password - + Ricorda la password Cancel - + Annulla Join Room - + Entra nella stanza Password required - + Password richiesta Enter a password to protect this conference room. Other participants must enter it before taking part in the session. - + Inserire una password per proteggere questa stanza conferenza. Gli altri partecipanti dovranno inserirla prima di accedere alla sessione. This password has been set for the conference room and must be entered by participants before taking part in the session. - + Questa password è stata impostata per la stanza conferenza e deve essere inserita dai partecipanti prima di accedere alla sessione. The room password has been set by someone else. - + La password della stanza è stata impostata da qualcun altro. Show password - + Mostra password Remove - + Rimuovi Save - + Salva Video quality - + Qualità video Change the video quality of this meeting - + Modifica la qualità video di questa riunione No video (audio only) - + Nessun video (solo audio) Lowest quality - + Qualità minima Standard quality - + Qualità standard Highest quality - + Qualità massima Close - + Chiudi Drag bar - + Barra di trascinamento @@ -1138,135 +1138,135 @@ %n minutes left - - - + + %n minuto rimanente + %n minuti rimanenti Conference room - + Stanza conferenza Share - + Condividi Copy room name - + Copia nome stanza Copy room link - + Copia link stanza Open in browser - + Apri nel browser Show phone number - + Mostra numero di telefono Raise - + Alza la mano Resume - + Riprendi Hold - + In attesa View - + Vista Screen - + Schermo Share window - + Condividi finestra Share screen - + Condividi schermo Camera - + Fotocamera Micro - + Microfono Output - + Uscita More - + Altro Noise supression - + Soppressione del rumore Toggle subtitles - + Attiva/disattiva sottotitoli Toggle whiteboard - + Attiva/disattiva lavagna Video quality... - + Qualità video... Set room password... - + Imposta password stanza... Mute everyone - + Silenzia tutti Leave conference - + Abbandona la conferenza End conference for all - + Termina la conferenza per tutti @@ -1274,7 +1274,7 @@ Cancel - + Annulla @@ -1282,7 +1282,7 @@ App menu - + Menu applicazione @@ -1290,12 +1290,12 @@ storing credentials failed: %1 - + Salvataggio delle credenziali non riuscito: %1 reading credentials failed: %1 - + Lettura delle credenziali non riuscita: %1 @@ -1303,27 +1303,27 @@ Authentication failed - + Autenticazione non riuscita Please enter the password: - + Inserisci la password: Password - + Password Enter the password - + Inserisci la password Ok - + Ok @@ -1331,17 +1331,17 @@ GOnnect window header - + Intestazione della finestra GOnnect App menu - + Menu applicazione Close GOnnect window - + Chiudi la finestra GOnnect @@ -1349,22 +1349,22 @@ Conference starting soon - + La conferenza inizia a breve Appointment starting soon - + L'appuntamento inizia a breve Join - + Partecipa Open - + Apri @@ -1372,114 +1372,114 @@ Date events - + Appuntamenti List of all the currently active and upcoming date events - + Elenco di tutti gli eventi del calendario attivi e prossimi Date event section - + Sezione eventi del calendario Header for %1 - + Intestazione per %1 Today - %1 - + Oggi - %1 yyyy/MM/dd - + dd/MM/yyyy Tomorrow - %1 - + Domani - %1 dddd - yyyy/MM/dd - + dddd - dd/MM/yyyy Date event - + Appuntamenti Currently selected date event: %1, starting time %2, remaining time %3 - + Evento del calendario attualmente selezionato: %1, ora di inizio %2, tempo rimanente %3 hh:mm - + hh:mm All day - + Tutto il giorno till %1 - + fino alle %1 in %1 - + tra %1 Join - + Partecipa Open - + Apri Join meeting - + Partecipa alla riunione Join the meeting associated with the currently selected event - + Partecipa alla riunione associata all'evento attualmente selezionato Copy room link - + Copia link stanza Copy link - + Copia link Copy meeting link - + Copia link riunione Copy the meeting link associated with the currently selected event - + Copia il link della riunione associato all'evento attualmente selezionato @@ -1487,27 +1487,27 @@ Appointments - + Appuntamenti Loading appointments... - + Caricamento appuntamenti... No upcoming appointments - + Nessun appuntamento in programma Date event widget status - + Stato del widget eventi del calendario Displays the current status of the widget: %1 - + Mostra lo stato corrente del widget: %1 @@ -1515,12 +1515,12 @@ Call one of the phone numbers below and use this code for authentication: - + Chiamare uno dei numeri di telefono qui sotto e utilizzare questo codice per l'autenticazione: Close - + Chiudi @@ -1528,12 +1528,12 @@ Number pad - + Tastierino numerico Character %1 - + Simbolo %1 @@ -1541,32 +1541,32 @@ Add page - + Aggiungi pagina Add a new dashboard page - + Aggiungi una nuova pagina al dashboard Add widget - + Aggiungi widget Add a new widget to the current dashboard page - + Aggiungi un nuovo widget alla pagina corrente del dashboard Finished - + Completato Finish and save all dashboard and widget changes - + Completa e salva tutte le modifiche al pannello e ai widget @@ -1574,22 +1574,22 @@ Emergency Call - + Chiamata di emergenza Incoming emergency call from %1 - + Chiamata di emergenza in entrata da %1 Answering the call will automatically terminate all other ongoing calls. - + Accettando la chiamata si termineranno automaticamente tutte le altre chiamate in corso. Answer - + Rispondi @@ -1597,12 +1597,12 @@ Emoji - + Emoji Selected Emoji: %1 - + Emoji selezionata: %1 @@ -1610,12 +1610,12 @@ Switch Emoji category - + Cambia categoria emoji Select Emoji - + Seleziona emoji @@ -1623,138 +1623,138 @@ Trying - + Tentativo in corso Ringing - + In attesa Call being forwarded - + Chiamata in inoltro Queued - + In coda Progress - + In corso Ok - + Ok Accepted - + Accettata Unauthorized - + Non autorizzato Rejected - + Rifiutata Not found - + Non trovato Proxy authentication required - + Autenticazione proxy richiesta Request timeout - + Timeout della richiesta Temporarily unavailable - + Temporaneamente non disponibile Ambiguous - + Ambiguo Busy here - + Occupato Request terminated - + Richiesta terminata Not acceptable here - + Non accettabile qui Internal server error - + Errore interno del server Not implemented - + Non implementato Bad gateway - + Gateway non valido Service unavailable - + Servizio non disponibile Server timeout - + Timeout del server Busy everywhere - + Occupato ovunque Decline - + Rifiuto Does not exist anywhere - + Non esiste da nessuna parte Not acceptable anywhere - + Non accettabile ovunque Unwanted - + Indesiderato @@ -1762,47 +1762,47 @@ Unknown - + Sconosciuto Commercial - + Lavoro Home - + Privato Mobile - + Cellulare Incoming - + In entrata Outgoing - + In uscita Blocked - + Bloccato SIP - + SIP Jitsi Meet - + Jitsi Meet @@ -1810,12 +1810,12 @@ Set favorite - + Aggiungi ai preferiti Unset favorite - + Rimuovi dai preferiti @@ -1823,22 +1823,22 @@ Favorite contact - + Contatto preferito Selected favorite %1: %2 - + Preferito selezionato %1: %2 tap to start meeting %1 - + tocca per avviare la riunione %1 tap to call %1 - + tocca per chiamare %1 @@ -1846,17 +1846,17 @@ Favorite contact - + Contatto preferito Selected favorite %1: %2 - + Preferito selezionato %1: %2 tap to call %1 - + tocca per chiamare %1 @@ -1865,12 +1865,12 @@ Favorites - + Preferiti List of all contacts that have been marked as favorites - + Elenco di tutti i contatti contrassegnati come preferiti @@ -1878,12 +1878,12 @@ Favorites - + Preferiti No favorites to display - + Nessun preferito da visualizzare @@ -1891,32 +1891,32 @@ First Aid - + Pronto soccorso Clicking one of these buttons will end all current calls and start an emergency call. - + Facendo clic su uno di questi pulsanti si termineranno tutte le chiamate attive e si avvierà una chiamata di emergenza. Tap to call emergency contact: %1 (%2) - + Tocca per chiamare il contatto di emergenza: %1 (%2) Close - + Chiudi Exit the first aid menu without initiating any action - + Esci dal menu pronto soccorso senza avviare alcuna azione Close first aid menu - + Chiudi il menu pronto soccorso @@ -1924,12 +1924,12 @@ Open first aid menu - + Apri il menu pronto soccorso First Aid - + Pronto soccorso @@ -1937,7 +1937,7 @@ Home - + Home @@ -1945,7 +1945,7 @@ MMM dd - + dd MMM @@ -1953,12 +1953,12 @@ Ringing - + In attesa Call waiting - + Chiamata in attesa @@ -1966,28 +1966,28 @@ Call active - + Chiamata attiva Calling - + Chiamata in corso On Hold - + In attesa Call ended - + Chiamata terminata Phone conference - + Conferenza telefonica @@ -1996,42 +1996,42 @@ No past calls - + Nessuna chiamata precedente History - + Cronologia Searchable list of past calls and meetings - + Elenco ricercabile di chiamate e riunioni passate History item section - + Sezione elementi cronologia Header for the currently selected day: %1 - + Intestazione per il giorno attualmente selezionato: %1 History item - + Elemento cronologia Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 - + Elemento cronologia selezionato %1 - azienda %2, posizione %3, numero %4, ora %5, durata %6 hh:mm - + hh:mm @@ -2039,37 +2039,37 @@ Call - + Chiamata Copy number - + Copia numero Remove favorite - + Rimuovi dai preferiti Add favorite - + Aggiungi ai preferiti Remind when available - + Ricordami quando disponibile Unblock - + Sblocca Block for 8 hours - + Blocca per 8 ore @@ -2077,78 +2077,78 @@ History - + Cronologia All - + Tutti SIP - + SIP Jitsi Meet - + Jitsi Meet History call type picker - + Selettore tipo di chiamata cronologia Select the call type to filter by - + Seleziona il tipo di chiamata per filtrare Currently selected call type - + Tipo di chiamata attualmente selezionato Incoming - + In entrata Outgoing - + In uscita Missed - + Perse History call origin picker - + Selettore origine chiamata cronologia Select the call origin to filter by - + Seleziona l'origine della chiamata per filtrare Currently selected call origin - + Origine della chiamata attualmente selezionata Hide history search - + Nascondi ricerca cronologia Show history search - + Mostra ricerca cronologia @@ -2159,7 +2159,7 @@ Ad hoc conference - + Conferenza ad hoc @@ -2167,27 +2167,27 @@ Default - + Predefinito Auto - + Auto Identity selection - + Selezione identità Select the preferred identity to be used in calls - + Seleziona l'identità preferita da utilizzare nelle chiamate Currently selected identity - + Identità attualmente selezionata @@ -2195,7 +2195,7 @@ Ok - + Ok @@ -2203,32 +2203,32 @@ New chat message - + Nuovo messaggio chat Unnamed participant - + Partecipante senza nome Active conference - + Conferenza attiva Hang up - + Riaggancia %1 has joined the conference - + %1 ha partecipato alla conferenza %1 has left the conference - + %1 ha abbandonato la conferenza @@ -2236,22 +2236,22 @@ Start conference - + Avvia conferenza Remove favorite - + Rimuovi dai preferiti Add favorite - + Aggiungi ai preferiti Copy room name - + Copia nome stanza @@ -2259,30 +2259,30 @@ Failed to initialize LDAP connection - + Inizializzazione della connessione LDAP non riuscita LDAP error: %1 - + Errore LDAP: %1 Parse error: %1 - + Errore di analisi: %1 LDAP timeout: %1 - + Timeout LDAP: %1 Failed to initialize LDAP connection: %1 - + Inizializzazione della connessione LDAP non riuscita: %1 @@ -2290,7 +2290,7 @@ Call - + Chiamata @@ -2298,62 +2298,62 @@ No system tray available - + Nessuna area di notifica disponibile GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. - + GOnnect consente un accesso rapido alle funzionalità tramite l'area di notifica. Il tuo ambiente desktop non ne dispone. Information - + Informazioni GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! - + GOnnect è in fase di test per il tuo sistema operativo e non è ancora stato rilasciato ufficialmente. Al momento non c'è parità di funzionalità con la versione Linux, e potrebbero esserci bug non ancora trovati. Sei invitato a segnalare questi problemi su GitHub. Buon test! There are still phone calls going on, do you really want to quit? - + Ci sono ancora chiamate telefoniche in corso. Vuoi davvero uscire? Please enter the password for %1: - + Inserisci la password per %1: Please enter the recovery key for %1: - + Inserisci la chiave di ripristino per %1: End all calls - + Termina tutte le chiamate Do you really want to close this window and terminate all ongoing calls? - + Vuoi davvero chiudere questa finestra e terminare tutte le chiamate in corso? Please enter the password for the SIP account: - + Inserisci la password per l'account SIP: Error - + Errore Fatal Error - + Errore fatale @@ -2361,114 +2361,114 @@ Selected tab - + Scheda selezionata The currently selected tab - + La scheda attualmente selezionata Selected tab options - + Opzioni scheda selezionata The settings of the currently selected tab - + Le impostazioni della scheda attualmente selezionata Home - + Casa Conference - + Conferenza No active conference - + Nessuna conferenza attiva Call - + Chiamata No active call - + Nessuna chiamata attiva Settings - + Impostazioni Move up - + Sposta su Move tab up - + Sposta scheda su Moves the currently selected tab up by one - + Sposta la scheda attualmente selezionata di una posizione verso l'alto Move down - + Sposta giù Move tab down - + Sposta scheda giù Moves the currently selected tab down by one - + Sposta la scheda attualmente selezionata di una posizione verso il basso Edit - + Modifica Edit page - + Modifica pagina Edit the currently selected dashboard page - + Modifica la pagina del pannello attualmente selezionata Delete - + Elimina Delete page - + Elimina pagina Delete the currently selected dashboard page - + Elimina la pagina del pannello attualmente selezionata @@ -2476,17 +2476,17 @@ Commercial - + Lavoro Mobile - + Cellulare Home - + Privato @@ -2494,92 +2494,92 @@ Create new dashboard page - + Crea nuova pagina pannello Edit dashboard page - + Modifica pagina pannello Name - + Nome Page name - + Nome pagina Enter the page name - + Inserisci il nome della pagina Icon - + Icona Page icon label - + Etichetta icona della pagina Page icon selection - + Selezione icona della pagina Select the page icon for the dashboard page - + Seleziona l'icona della pagina per la pagina del pannello Currently selected page icon option - + Opzione icona pagina attualmente selezionata Cancel - + Annulla Cancel page modifcation - + Annulla modifica pagina Cancel button to exit the page creation/update window - + Pulsante Annulla per uscire dalla finestra di creazione/modifica pagina Create - + Crea Save - + Salva page - + pagina Confirmation button to create the new dashboard page - + Pulsante di conferma per creare la nuova pagina del pannello Confirmation button to apply changes to the dashboard page - + Pulsante di conferma per applicare le modifiche alla pagina del pannello @@ -2587,42 +2587,42 @@ Participants list - + Elenco partecipanti List of all the participants of the current chat room - + Elenco di tutti i partecipanti della stanza chat corrente Chat participant - + Partecipante alla chat Selected chat participant: %1 - + Partecipante alla chat selezionato: %1 moderator - + moderatore it's you - + sei tu Kick - + Cacciare Make moderator - + Rendi moderatore @@ -2630,7 +2630,7 @@ Anonymous - + Anonimo @@ -2638,42 +2638,42 @@ Phone Number Transmission - + Trasmissione numero di telefono Name - + Nome Prefix - + Prefisso Identity - + Identità Enabled - + Abilitato Automatic - + Automatico Delete - + Elimina Save - + Salva @@ -2682,9 +2682,9 @@ There are %n active call(s). calls - - - + + È presente %n chiamata attiva. + Sono presenti %n chiamate attive. @@ -2705,7 +2705,7 @@ Antigua and Barbuda - + Antigua e Barbuda @@ -2755,7 +2755,7 @@ Aland Islands - + Isole Åland @@ -2765,7 +2765,7 @@ Bosnia and Herzegovina - + Bosnia ed Erzegovina @@ -2810,7 +2810,7 @@ Saint Barthelemy - + Saint-Barthélemy @@ -2830,7 +2830,7 @@ Bonaire, Saint Eustatius and Saba - + Bonaire, Sint Eustatius e Saba @@ -2870,12 +2870,12 @@ Cocos Islands - + Isole Cocos Democratic Republic of the Congo - + Repubblica Democratica del Congo @@ -2885,7 +2885,7 @@ Republic of the Congo - + Repubblica del Congo @@ -2895,7 +2895,7 @@ Ivory Coast - + Costa d'Avorio @@ -2940,7 +2940,7 @@ Curacao - + Curaçao @@ -3145,12 +3145,12 @@ Hong Kong - + Hong Kong Heard Island and McDonald Islands - + Isole Heard e McDonald @@ -3270,7 +3270,7 @@ Saint Kitts and Nevis - + Saint Kitts e Nevis @@ -3310,7 +3310,7 @@ Saint Lucia - + Santa Lucia @@ -3400,7 +3400,7 @@ Myanmar - + Myanmar @@ -3410,7 +3410,7 @@ Macao - + Macao @@ -3570,12 +3570,12 @@ Saint Pierre and Miquelon - + Saint-Pierre e Miquelon Pitcairn - + Isole Pitcairn @@ -3585,7 +3585,7 @@ Palestinian Territory - + Territorio Palestinese @@ -3610,7 +3610,7 @@ Reunion - + Riunione @@ -3665,7 +3665,7 @@ Saint Helena - + Sant'Elena @@ -3675,7 +3675,7 @@ Svalbard and Jan Mayen - + Svalbard e Jan Mayen @@ -3715,7 +3715,7 @@ Sao Tome and Principe - + São Tomé e Príncipe @@ -3725,7 +3725,7 @@ Sint Maarten - + Sint Maarten @@ -3740,7 +3740,7 @@ Turks and Caicos Islands - + Isole Turks e Caicos @@ -3770,7 +3770,7 @@ East Timor - + Timor Est @@ -3795,7 +3795,7 @@ Trinidad and Tobago - + Trinidad e Tobago @@ -3825,7 +3825,7 @@ United States Minor Outlying Islands - + Isole minori degli Stati Uniti @@ -3845,12 +3845,12 @@ Vatican - + Vaticano Saint Vincent and the Grenadines - + Saint Vincent e Grenadine @@ -3880,7 +3880,7 @@ Wallis and Futuna - + Wallis e Futuna @@ -3921,7 +3921,7 @@ Additional Information - + Informazioni aggiuntive @@ -3929,27 +3929,27 @@ ringTone - + 425,0,1000,4000,0 busyTone - + 425,0,480,480,0 congestionTone - + 425,0,240,240,0 zip - + 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 endTone - + 425,0,200,200,200,200,200,200,-1 @@ -3957,32 +3957,32 @@ 'userUri' is no valid SIP URI: %1 - + 'userUri' non è un URI SIP valido: %1 'userUri' is required - + 'userUri' è obbligatorio 'registrarUri' is no valid SIP URI: %1 - + 'registrarUri' non è un URI SIP valido: %1 'registrarUri' is required - + 'registrarUri' è obbligatorio 'proxies' contains invalid SIP URI entry: %1 - + 'proxies' contiene una voce URI SIP non valida: %1 Failed to create %1: %2 - + Impossibile creare %1: %2 @@ -3990,7 +3990,7 @@ %1 is now available - + %1 è ora disponibile @@ -3998,12 +3998,12 @@ Active call with %1 - + Chiamata attiva con %1 Hang up - + Riaggancia @@ -4011,34 +4011,34 @@ %1 is calling - + %1 sta chiamando %1 (%2) is calling - + %1 (%2) sta chiamando Reject - + Rifiuta Accept - + Accetta Missed call from %1 - + Chiamata persa da %1 Call back - + Richiama @@ -4046,7 +4046,7 @@ New Identity - + Nuova identità @@ -4055,17 +4055,17 @@ Failed to write to %1 - + Impossibile scrivere su %1 Failed to copy %1 to the config space - + Impossibile copiare %1 nella directory di configurazione Source file %1 does not exist - + Il file sorgente %1 non esiste @@ -4073,7 +4073,7 @@ Search number - + Cerca numero @@ -4081,12 +4081,12 @@ Search result category filter %1 - + Filtro categoria risultati di ricerca %1 Filter for the individual search result items by category - + Filtra i singoli risultati di ricerca per categoria @@ -4094,22 +4094,22 @@ Contacts - + Contatti Messages - + Messaggi Rooms and Teams - + Stanze e team Files - + File @@ -4117,42 +4117,42 @@ Select number - + Seleziona numero Activate search field - + Attiva campo di ricerca Number or contact - + Numero o contatto Clear search field - + Cancella campo di ricerca Default - + Predefinito Auto - + Auto Preferred identity - + Identità preferita Select the preferred identity for outgoing calls - + Seleziona l'identità preferita per le chiamate in uscita @@ -4160,12 +4160,12 @@ Search for contacts or room names... - + Cerca contatti o nomi stanza... Clear search field - + Cancella campo di ricerca @@ -4173,12 +4173,12 @@ Search result category %1 - + Categoria risultati di ricerca %1 Divider for the individual search result items by category - + Divisore per i singoli risultati di ricerca per categoria @@ -4186,12 +4186,12 @@ Search result - + Risultato di ricerca Currently selected search result - + Risultato di ricerca attualmente selezionato @@ -4199,17 +4199,17 @@ Phone number - + Numero di telefono Selected favorite number %1 - + Numero preferito selezionato %1 Selected phone number %1 - + Numero di telefono selezionato %1 @@ -4217,52 +4217,52 @@ Search filter and identity selection - + Filtro di ricerca e selezione identità Select search filter to be applied, as well as the outgoing identity - + Seleziona il filtro di ricerca da applicare e l'identità in uscita Outgoing identity - + Identità in uscita Search results - + Risultati di ricerca All search results will be listed here in their respective categories - + Tutti i risultati di ricerca saranno elencati qui nelle rispettive categorie Direct dial - + Composizione diretta Call "%1" - + Chiama "%1" Open room "%1" - + Apri stanza "%1" History - + Cronologia Contacts - + Contatti @@ -4270,314 +4270,314 @@ Settings - + Impostazioni Use dark mode tray icon - + Usa l'icona area notifica in modalità scura Inverse Accept / Reject buttons - + Inverti i pulsanti Accetta/Rifiuta Show main window on startup - + Mostra la finestra principale all'avvio Show chat messages as desktop notifications - + Mostra i messaggi chat come notifiche desktop Enable USB headset driver [%1] - + Abilita il driver per cuffie USB [%1] not detected - + non rilevato Disable USB headset mute state propagation - + Disabilita la propagazione dello stato silenziamento per cuffie USB Show dial window on USB headset pick up - + Mostra la finestra di composizione quando si indossano le cuffie USB Appearance - + Aspetto Color scheme - + Schema colori System default - + Predefinito di sistema Light - + Chiaro Dark - + Scuro Theme selection box - + Casella di selezione tema Select the UI theme - + Seleziona il tema dell'interfaccia Currently selected theme option - + Opzione tema attualmente selezionata Phoning - + Telefonia Signalling busy when a call is active - + Segnala occupato quando una chiamata è attiva Rules for telephone number transmission - + Regole per la trasmissione del numero di telefono Standard preferred identity - + Identità preferita standard Default - + Predefinito Auto - + Auto Prefererred identity selection - + Selezione identità preferita Select the preferred identity - + Seleziona l'identità preferita Currently selected identity option - + Opzione identità attualmente selezionata Currently highlighted preferred identity. Tap to edit. - + Identità preferita attualmente evidenziata. Tocca per modificare. Add a new preferred identity entry - + Aggiungi una nuova voce di identità preferita Prefer USB headset ring sound if available - + Preferisci la suoneria delle cuffie USB se disponibile No preferred identities yet. - + Nessuna identità preferita ancora. Disable synchronisation with the system mute state - + Disabilita la sincronizzazione con lo stato di silenziamento del sistema restart required - + riavvio necessario Use custom window decoration - + Usa decorazione finestra personalizzata Standard - + Standard Add identity - + Aggiungi identità Audio settings - + Impostazioni audio Input device - + Dispositivo di ingresso Input device selection - + Selezione dispositivo di ingresso Select the input device to be used - + Seleziona il dispositivo di ingresso da utilizzare Currently selected input option - + Opzione di ingresso attualmente selezionata Output device - + Dispositivo di uscita Output device selection - + Selezione dispositivo di uscita Select the output device to be used - + Seleziona il dispositivo di uscita da utilizzare Currently selected output option - + Opzione di uscita attualmente selezionata Output device for ring tone - + Dispositivo di uscita per la suoneria Currently selected ring output option - + Opzione di uscita suoneria attualmente selezionata Ring tone - + Suoneria Reset ring tone - + Reimposta suoneria Reset the ring tone to its default option - + Reimposta la suoneria alla sua opzione predefinita Pick ring tone - + Scegli suoneria Select the ring tone you want to use for incoming calls - + Seleziona la suoneria da utilizzare per le chiamate in entrata Currently set to: - + Attualmente impostata su: Ring tone volume - + Volume suoneria Adjust %1 - + Regola %1 %1 % - + %1 % Pause between ring tones [s] - + Pausa tra le suonerie [s] Debugging - + Debug Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. - + Usa questo pulsante per avviare una sessione di debug. L'app si riavvierà e inizierà a registrare informazioni aggiuntive utili per il debug. Durante questa sessione, torna qui per scaricare le informazioni. Una sessione di debug è limitata a 5 minuti, dopodiché l'app si riavvierà automaticamente in modalità normale. Start debug run (restart app) - + Avvia sessione di debug (riavvia app) Download debug information - + Scarica informazioni di debug Reload contacts from LDAP - + Ricarica contatti da LDAP @@ -4585,62 +4585,62 @@ Shortcuts - + Scorciatoie da tastiera Shortcut key: %1 - + Tasto scorciatoia: %1 Local shortcuts (work only when app is focused) - + Scorciatoie locali (funzionano solo quando l'app è in primo piano) Local shortcuts - + Scorciatoie locali Ctrl + F - + Ctrl + F Activates the global search field - + Attiva il campo di ricerca globale F11 - + F11 Toggles between fullsceen and normal window mode - + Commuta tra la modalità schermo intero e quella finestra normale Ctrl + Shift + M - + Ctrl + Maiusc + M Toggles audio mute - + Attiva/disattiva il silenziamento dell'audio Global shortcuts (work from anywhere) - + Scorciatoie globali (funzionano ovunque) Global shortcuts - + Scorciatoie globali @@ -4648,143 +4648,143 @@ Initial configuration - + Configurazione iniziale Error: %1 - + Errore: %1 SIP wizard notification - + Notifica procedura guidata SIP GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. - + GOnnect non riesce a trovare una configurazione SIP. Per iniziare, seleziona uno dei modelli qui sotto e modifica il file di configurazione risultante se necessario. Please pick: - + Seleziona: Select SIP template - + Seleziona modello SIP Select the SIP template to be used - + Seleziona il modello SIP da utilizzare Currently selected SIP template - + Modello SIP attualmente selezionato Next - + Avanti Continue setup - + Continua la configurazione Confirmation button to continue the setup - + Pulsante di conferma per continuare la configurazione Template field list - + Elenco campi modello List of all the available SIP template options - + Elenco di tutte le opzioni di modello SIP disponibili SIP template option - + Opzione modello SIP Currently selected SIP template option - + Opzione modello SIP attualmente selezionata Display name of the SIP template option - + Nome visualizzato dell'opzione modello SIP Description of the SIP template option - + Descrizione dell'opzione modello SIP Back - + Indietro Back button to return to the template selection menu - + Pulsante Indietro per tornare al menu di selezione modello Finish - + Fine Confirmation button to apply the changes to the SIP template - + Pulsante di conferma per applicare le modifiche al modello SIP Successful configuration file creation - + File di configurazione creato con successo We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. - + È stato creato un file di configurazione. Verificare se sono necessarie modifiche per soddisfare le proprie esigenze e riavviare GOnnect per attivarle. The configuration has been saved to: - + La configurazione è stata salvata in: Copy to clipboard - + Copia negli appunti Copy the full path of the configuration file to the clipboard - + Copia il percorso completo del file di configurazione negli appunti Finish wizard - + Completa la procedura guidata Finish the SIP configuration wizard - + Completa la procedura guidata di configurazione SIP @@ -4792,27 +4792,27 @@ Show dial window and focus search field - + Mostra la finestra di composizione e metti a fuoco il campo di ricerca End all calls - + Termina tutte le chiamate Redial last outgoing call - + Ripeti l'ultima chiamata effettuata Toggle hold - + Commuta attesa Phone calls are active - + Ci sono chiamate telefoniche attive @@ -4821,47 +4821,47 @@ Dial... - + Componi... Not registered... - + Non registrato... Settings - + Impostazioni About - + Informazioni Quit - + Esci End conference - + Termina conferenza Call with %1 has ended - + La chiamata con %1 è terminata Accept call with %1 - + Accetta chiamata da %1 Hang up call with %1 - + Riaggancia chiamata con %1 @@ -4869,32 +4869,32 @@ File path - + Percorso file Enter the file path for %1 - + Inserisci il percorso del file per %1 Choose... - + Scegli... Open file picker - + Apri selettore file Select the file that should be used for %1 - + Seleziona il file da utilizzare per %1 Certificate files (%1) - + File certificato (%1) @@ -4902,12 +4902,12 @@ Text input - + Campo di testo Enter the desired value for %1 - + Inserisci il valore desiderato per %1 @@ -4915,7 +4915,7 @@ Failed to toggle the state of %1. - + Errore durante la modifica dello stato di %1. @@ -4923,17 +4923,17 @@ Toggler list - + Elenco di opzioni List of items that can be toggled - + Elenco degli elementi commutabili Toggle %1 - + Commuta %1 @@ -4941,7 +4941,7 @@ Level meter - + Indicatore di livello @@ -4949,7 +4949,7 @@ Virtual background - + Sfondo virtuale @@ -4957,36 +4957,36 @@ Save File - + Salva file %n minute(s) - - - + + %n minuto + %n minuti 1 hour and %n minute(s) - - - + + 1 ora e %n minuto + 1 ora e %n minuti %n hour(s) - - - + + %n ora + %n ore Audio Files (%1) - + File audio (%1) @@ -4994,22 +4994,22 @@ Adjust the volume - + Regola il volume Unmute - + Riattiva audio Mute - + Silenzia Open audio settings - + Apri impostazioni audio @@ -5017,147 +5017,147 @@ Add widget - + Aggiungi widget Widget - + Widget Widget selection header - + Intestazione selezione widget Date Events - + Appuntamenti List of upcoming appointments - + Elenco degli appuntamenti in programma Favorites - + Preferiti Quick dial for your favorite contacts and conferences - + Composizione rapida per i tuoi contatti e conferenze preferiti History - + Cronologia Web View - + Visualizzazione web A web-based content display - + Visualizzatore di contenuti web Widget selection - + Selezione widget Select the widget that should be added to the current dashboard page - + Seleziona il widget da aggiungere alla pagina corrente del pannello Currently selected widget option - + Opzione widget attualmente selezionata Accept all certificates - + Accetta tutti i certificati Confirm widget selection - + Conferma selezione widget Confirmation button to create and add the selected widget to the current dashboard - + Pulsante di conferma per creare e aggiungere il widget selezionato al pannello corrente Searchable call and conference history - + Cronologia ricercabile di chiamate e conferenze Title - + Titolo URL - + URL URL (dark mode) - + URL (modalità scura) Settings text input - + Campo di testo impostazioni Input for widget setting %1 - + Campo per l'impostazione widget %1 Settings checkbox - + Casella di controllo impostazioni Checkbox for widget setting %1 - + Casella di controllo per l'impostazione widget %1 Widget setting %1 - + Impostazione widget %1 Cancel - + Annulla Cancel widget selection - + Annulla selezione widget Cancel button to exit widget selection selection without changes - + Pulsante Annulla per uscire dalla selezione widget senza modifiche Add - + Aggiungi @@ -5165,22 +5165,22 @@ GOnnect window header - + Intestazione della finestra GOnnect Minimize - + Minimizza Maximize - + Massimizza Close GOnnect window - + Chiudi la finestra GOnnect diff --git a/i18n/gonnect_ru.ts b/i18n/gonnect_ru.ts new file mode 100644 index 00000000..a5b4a933 --- /dev/null +++ b/i18n/gonnect_ru.ts @@ -0,0 +1,5186 @@ + + + + + AboutWindow + + + About + О программе + + + + GOnnect headline + Заголовок GOnnect + + + + GOnnect version + Версия GOnnect + + + + Version: v%1 + Версия: v%1 + + + + Copy to clipboard + Копировать в буфер обмена + + + + Copy the currently used version of Gonnect to the clipboard + Скопировать текущую версию GOnnect в буфер обмена + + + + + + + Homepage + Домашняя страница + + + + Visit the project homepage + Посетить домашнюю страницу проекта + + + + Bug Tracker + Трекер ошибок + + + + Visit the project bug tracker + Посетить трекер ошибок проекта + + + + Documentation + Документация + + + + Visit the online project documentation + Посетить онлайн-документацию проекта + + + + AdditionalInfo + + + Additional caller related information + Дополнительная информация об абоненте + + + + List of informational items regarding the caller, such as open support tickets + Список информации об абоненте, например открытые тикеты поддержки + + + + Expandable response section + Раскрываемый раздел ответа + + + + AudioDeviceMenu + + + Default + По умолчанию + + + + AudioEnvWindow + + + Unknown audio environment + Неизвестная аудиосреда + + + + Audio environment error + Ошибка аудиосреды + + + + No fitting audio environment could be found. Please select the desired audio devices. + Подходящая аудиосреда не найдена. Выберите нужные аудиоустройства. + + + + Input device selection header + Заголовок выбора устройства ввода + + + + Header for the input device selection below + Заголовок для выбора устройства ввода ниже + + + + Input device + Устройство ввода + + + + Input device selection box + Поле выбора устройства ввода + + + + Select the input device that should be used + Выбрать устройство ввода + + + + Currently selected input device + Текущее устройство ввода + + + + Output device selection header + Заголовок выбора устройства вывода + + + + Header for the output device selection below + Заголовок для выбора устройства вывода ниже + + + + Output device + Устройство вывода + + + + Output device selection box + Поле выбора устройства вывода + + + + Select the output device that should be used + Выбрать устройство вывода + + + + Currently selected output device + Текущее устройство вывода + + + + Ring tone output device + Устройство вывода мелодии звонка + + + + Output device for ring tone + Устройство вывода для мелодии звонка + + + + Ring tone output device selection box + Поле выбора устройства вывода мелодии + + + + Select the output device that should be used for playing the ring tone + Выбрать устройство для воспроизведения мелодии звонка + + + + Currently selected ring tone output device + Текущее устройство вывода мелодии + + + + Ok + ОК + + + + Close audio environment selection + Закрыть выбор аудиосреды + + + + Confirmation button to leave the audio environment selection window + Кнопка подтверждения для выхода из окна выбора аудиосреды + + + + AudioLevelButton + + + Change volume + Изменить громкость + + + + AudioManager + + + Default input + Вход по умолчанию + + + + Default output + Выход по умолчанию + + + + AvatarImage + + + Initials of this contact + Инициалы контакта + + + + BaseDialog + + + Dialog + Диалог + + + + BasePage + + + Base dashboard page grid + Базовая сетка страницы панели управления + + + + Canvas for editable dashboard pages + Холст для редактируемых страниц панели управления + + + + Add widgets + Добавить виджеты + + + + BaseWidget + + + Drag widget + Перетащить виджет + + + + Change the position of the widget + Изменить положение виджета + + + + Remove widget + Удалить виджет + + + + Remove the currently selected widget from the dashboard + Удалить выбранный виджет с панели управления + + + + Resize widget + Изменить размер виджета + + + + Resize the widget according to the mouse direction + Изменить размер виджета по направлению мыши + + + + BaseWindow + + + + + + + + + + Drag border + Граница перетаскивания + + + + Top left drag border for window resize operations + Граница перетаскивания вверху слева + + + + Top drag border for window resize operations + Верхняя граница изменения размера окна + + + + Top right border for window resize operations + Граница вверху справа + + + + Right drag border for window resize operations + Правая граница изменения размера окна + + + + Bottom right drag border for window resize operations + Граница внизу справа + + + + Bottom drag border for window resize operations + Нижняя граница изменения размера окна + + + + Bottom left drag border for window resize operations + Граница внизу слева + + + + Left drag border for window resize operations + Левая граница изменения размера окна + + + + BottomStatusBar + + + Status bar + Строка состояния + + + + BurgerMenu + + + Toggle fullscreen + Полноэкранный режим + + + + Shortcuts... + Горячие клавиши... + + + + Customize UI + Настроить интерфейс + + + + About... + О программе... + + + + Quit + Выход + + + + Call + + + Conference + Конференция + + + + Drag bar + Панель перетаскивания + + + + CallButtonBar + + + %1@%2 kHz + %1@%2 кГц + + + + Transmit + Передача + + + + Call security level + Уровень безопасности звонка + + + + Security level of the ongoing call + Уровень безопасности текущего звонка + + + + Call security details + Сведения о безопасности звонка + + + + Detailed call security status: %1 / %2 + Подробный статус безопасности: %1 / %2 + + + + signaling encrypted + сигнализация зашифрована + + + + signaling unencrypted + сигнализация не зашифрована + + + + media encrypted + медиа зашифровано + + + + media unencrypted + медиа не зашифровано + + + + Call quality + Качество звонка + + + + Quality of the ongoing call + Качество текущего звонка + + + + Transmission statistics + Статистика передачи + + + + + Call quality metrics + Метрики качества звонка + + + + + MOS + MOS + + + + + Mean opinion score + Средняя оценка качества (MOS) + + + + Numerical metric assessing transmission-side voice call quality: %1 + Числовая метрика качества голосовой связи на стороне передачи: %1 + + + + + Packet loss + Потеря пакетов + + + + %1% of packets lost in transmission + %1% пакетов потеряно при передаче + + + + + Jitter + Джиттер + + + + Amount of transmission side jitter: %1 + Джиттер на стороне передачи: %1 + + + + + Effective delay + Эффективная задержка + + + + Effective transmission side call delay: %1 + Эффективная задержка на стороне передачи: %1 + + + + Receiver statistics + Статистика приёма + + + + Receive + Приём + + + + Numerical metric assessing receiver-side voice/video call quality: %1 + Числовая метрика качества на стороне приёма: %1 + + + + %1% of packets lost in receival + %1% пакетов потеряно при приёме + + + + Amount of receiver side jitter: %1 + Джиттер на стороне приёма: %1 + + + + Effective receiver side call delay: %1 + Эффективная задержка на стороне приёма: %1 + + + + Codec + Кодек + + + + Audio codec + Аудиокодек + + + + The currently used audio codec and frequency: %1 + Текущий аудиокодек и частота: %1 + + + + Elapsed call time + Прошедшее время звонка + + + + The duration in seconds the call has been active for: %1 + Продолжительность звонка в секундах: %1 + + + + Screen + Экран + + + + Screensharing control + Управление демонстрацией экрана + + + + Start sharing your screen + Начать демонстрацию экрана + + + + Camera + Камера + + + + Camera control + Управление камерой + + + + Enable your camera + Включить камеру + + + + Resume + Продолжить + + + + Hold + Удержание + + + + Resume call + Продолжить звонок + + + + Hold call + Удержать звонок + + + + Update the call hold state + Обновить состояние удержания + + + + Micro + Микрофон + + + + Input control + Управление входом + + + + Set the mute state of the current input device + Установить режим отключения звука устройства ввода + + + + Output + Выход + + + + Output control + Управление выходом + + + + Change the current output devices + Изменить текущие устройства вывода + + + + Accept call + Принять звонок + + + + Hangup call + Завершить звонок + + + + CallDetails + + + SIP call status code + Код состояния SIP-звонка + + + + The current status code of the call: %1 + Текущий код состояния звонка: %1 + + + + Jitsi Meet + Jitsi Meet + + + + Switch to a Jitsi Meet session + Перейти к сеансу Jitsi Meet + + + + Unhold + Снять с удержания + + + + Hold + Удержание + + + + Toggle the hold state to %1 + Переключить удержание на %1 + + + + Accept call + Принять звонок + + + + Hangup call + Завершить звонок + + + + CallHistory + + + Failed to create directory %1 to store the call history database. + Не удалось создать каталог %1 для базы данных истории звонков. + + + + Failed to open call history database: %1 + Не удалось открыть базу данных истории звонков: %1 + + + + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. + База данных истории звонков несовместима. Удалите %1 и перезапустите приложение. + + + + CallItem + + + Call + Звонок + + + + Selected call %1 - contact %2, company %3, location %4/%5, number %6 + Выбранный звонок %1 — контакт %2, компания %3, место %4/%5, номер %6 + + + + Hangup button + Кнопка завершения + + + + Pressing this will end the call + Нажатие завершит звонок + + + + CallList + + + Drag callers onto each other to transfer call + Перетащите абонентов друг на друга для переадресации + + + + List of active calls + Список активных звонков + + + + + Create conference + Создать конференцию + + + + CallSideBar + + + Chat + Чат + + + + Person(s) + + %n участник + %n участников + + + + + Info + Сведения + + + + CallerBigAvatar + + + Caller name + Имя звонящего + + + + is calling... + звонит... + + + + Calling... + Вызов... + + + + CallsModel + + + unknown number + неизвестный номер + + + + CardList + + + List of configurable options + Список настраиваемых параметров + + + + ChatMessageBox + + + Message + Сообщение + + + + Type message + Введите сообщение + + + + Enter the chat text message + Введите текстовое сообщение чата + + + + ChatMessageList + + + Chat message list + Список сообщений чата + + + + List of all chat messages of the current chat room + Список всех сообщений текущей комнаты чата + + + + Auto scroll down + Автопрокрутка вниз + + + + ChatMessageListItem + + + Chat message + Сообщение чата + + + + Selected chat message - from %1, at %2: %3 + Выбранное сообщение — от %1, в %2: %3 + + + + ChatRoomList + + + Chat room list + Список комнат чата + + + + List of all chat rooms + Список всех комнат чата + + + + ChatRoomListItem + + + Chat room + Комната чата + + + + Selected chat room %1: %2 unread messages + Выбранная комната %1: непрочитанных %2 + + + + ChatSideBar + + + Chat message list + Список сообщений чата + + + + List of all the messages in the current chat + Список всех сообщений текущего чата + + + + Chat message + Сообщение чата + + + + Selected chat message from %1 at %2: %3 + Выбранное сообщение от %1 в %2: %3 + + + + the server + сервера + + + + you + вас + + + + Select emoji + Выбрать эмодзи + + + + Enter chat message... + Введите сообщение... + + + + Chats + + + Please enter your recovery key to decrypt messages: + Введите ключ восстановления для расшифровки сообщений: + + + + Recovery key + Ключ восстановления + + + + Enter recovery key + Введите ключ восстановления + + + + Use key + Использовать ключ + + + + Use recovery key + Использовать ключ восстановления + + + + ClipboardButton + + + Copy to clipboard: %1 + Скопировать в буфер: %1 + + + + Conference + + + + Set room name + Задать имя комнаты + + + + + Room name: + Имя комнаты: + + + + + Enter the room name + Введите имя комнаты + + + + Authenticate + Аутентифицировать + + + + Please authenticate in the opened browser window... + Выполните аутентификацию в открытом окне браузера... + + + + This conference is protected by a password. Please enter it to join the room. + Конференция защищена паролем. Введите пароль для входа. + + + + + Password + Пароль + + + + + Enter the password + Введите пароль + + + + Remember password + Запомнить пароль + + + + + Cancel + Отмена + + + + Join Room + Войти в комнату + + + + Password required + Требуется пароль + + + + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. + Введите пароль для защиты конференции. Остальные участники должны ввести его перед входом. + + + + This password has been set for the conference room and must be entered by participants before taking part in the session. + Этот пароль установлен для комнаты конференции и должен вводиться участниками перед входом. + + + + The room password has been set by someone else. + Пароль комнаты установлен другим участником. + + + + Show password + Показать пароль + + + + Remove + Удалить + + + + Save + Сохранить + + + + Video quality + Качество видео + + + + Change the video quality of this meeting + Изменить качество видео встречи + + + + No video (audio only) + Без видео (только аудио) + + + + Lowest quality + Наименьшее качество + + + + Standard quality + Стандартное качество + + + + Highest quality + Наилучшее качество + + + + Close + Закрыть + + + + Drag bar + Панель перетаскивания + + + + ConferenceButtonBar + + + %n minutes left + + осталась %n минута + осталось %n минут + + + + + Conference room + Комната конференции + + + + Share + Поделиться + + + + Copy room name + Скопировать имя комнаты + + + + Copy room link + Скопировать ссылку на комнату + + + + Open in browser + Открыть в браузере + + + + Show phone number + Показать номер телефона + + + + Raise + Поднять руку + + + + Resume + Продолжить + + + + Hold + Удержание + + + + View + Вид + + + + Screen + Экран + + + + Share window + Поделиться окном + + + + Share screen + Поделиться экраном + + + + Camera + Камера + + + + Output + Выход + + + + More + Ещё + + + + Noise supression + Шумоподавление + + + + Toggle subtitles + Включить/выключить субтитры + + + + Toggle whiteboard + Включить/выключить доску + + + + Video quality... + Качество видео... + + + + Set room password... + Установить пароль комнаты... + + + + Mute everyone + Выключить звук у всех + + + + Leave conference + Покинуть конференцию + + + + End conference for all + Завершить конференцию для всех + + + + Micro + Микрофон + + + + ConfirmDialog + + + Cancel + Отмена + + + + ControlBar + + + App menu + Меню приложения + + + + Credentials + + + storing credentials failed: %1 + Ошибка сохранения учётных данных: %1 + + + + reading credentials failed: %1 + Ошибка чтения учётных данных: %1 + + + + CredentialsDialog + + + Authentication failed + Ошибка аутентификации + + + + Please enter the password: + Введите пароль: + + + + Password + Пароль + + + + Enter the password + Введите пароль + + + + Ok + ОК + + + + CustomWindowHeader + + + GOnnect window header + Заголовок окна GOnnect + + + + App menu + Меню приложения + + + + Close GOnnect window + Закрыть окно GOnnect + + + + DateEventManager + + + Conference starting soon + Конференция скоро начнётся + + + + Appointment starting soon + Встреча скоро начнётся + + + + Join + Присоединиться + + + + Open + Открыть + + + + DateEventsList + + + Date events + События календаря + + + + List of all the currently active and upcoming date events + Список всех активных и предстоящих событий + + + + Date event section + Раздел событий + + + + Header for %1 + Заголовок для %1 + + + + Today - %1 + Сегодня — %1 + + + + + yyyy/MM/dd + dd.MM.yyyy + + + + Tomorrow - %1 + Завтра — %1 + + + + dddd - yyyy/MM/dd + dddd - dd.MM.yyyy + + + + Date event + Событие календаря + + + + Currently selected date event: %1, starting time %2, remaining time %3 + Выбранное событие: %1, начало %2, осталось %3 + + + + + hh:mm + hh:mm + + + + All day + Весь день + + + + till %1 + до %1 + + + + in %1 + через %1 + + + + Join + Присоединиться + + + + Open + Открыть + + + + Join meeting + Присоединиться к встрече + + + + Join the meeting associated with the currently selected event + Присоединиться к встрече выбранного события + + + + Copy room link + Скопировать ссылку на комнату + + + + Copy link + Скопировать ссылку + + + + Copy meeting link + Скопировать ссылку на встречу + + + + Copy the meeting link associated with the currently selected event + Скопировать ссылку на встречу выбранного события + + + + DateEventsWidget + + + Appointments + Встречи + + + + Loading appointments... + Загрузка встреч... + + + + No upcoming appointments + Нет предстоящих встреч + + + + Date event widget status + Статус виджета событий + + + + Displays the current status of the widget: %1 + Отображает текущий статус виджета: %1 + + + + DialInInfo + + + Call one of the phone numbers below and use this code for authentication: + Позвоните по одному из номеров ниже и используйте этот код для аутентификации: + + + + Close + Закрыть + + + + DtmfDialer + + + Number pad + Набор номера + + + + Character %1 + Символ %1 + + + + EditModeOptions + + + Add page + Добавить страницу + + + + Add a new dashboard page + Добавить новую страницу панели + + + + Add widget + Добавить виджет + + + + Add a new widget to the current dashboard page + Добавить новый виджет на текущую страницу + + + + Finished + Готово + + + + Finish and save all dashboard and widget changes + Завершить и сохранить все изменения + + + + EmergencyCallIncomingWindow + + + Emergency Call + Экстренный вызов + + + + Incoming emergency call from %1 + Входящий экстренный вызов от %1 + + + + Answering the call will automatically terminate all other ongoing calls. + Ответ автоматически завершит все остальные активные звонки. + + + + Answer + Ответить + + + + EmojiButton + + + Emoji + Эмодзи + + + + Selected Emoji: %1 + Выбранное эмодзи: %1 + + + + EmojiPicker + + + Switch Emoji category + Сменить категорию эмодзи + + + + Select Emoji + Выбрать эмодзи + + + + EnumTranslation + + + Trying + Попытка + + + + Ringing + Вызов + + + + Call being forwarded + Звонок переадресован + + + + Queued + В очереди + + + + Progress + В процессе + + + + Ok + ОК + + + + Accepted + Принято + + + + Unauthorized + Не авторизован + + + + + Rejected + Отклонено + + + + Not found + Не найдено + + + + Proxy authentication required + Требуется аутентификация прокси + + + + Request timeout + Время запроса истекло + + + + Temporarily unavailable + Временно недоступно + + + + Ambiguous + Неоднозначно + + + + Busy here + Занято + + + + Request terminated + Запрос завершён + + + + Not acceptable here + Неприемлемо + + + + Internal server error + Внутренняя ошибка сервера + + + + Not implemented + Не реализовано + + + + Bad gateway + Неверный шлюз + + + + Service unavailable + Сервис недоступен + + + + Server timeout + Тайм-аут сервера + + + + Busy everywhere + Занято везде + + + + Decline + Отклонено + + + + Does not exist anywhere + Не существует нигде + + + + Not acceptable anywhere + Нигде неприемлемо + + + + Unwanted + Нежелательный + + + + + + + Unknown + Неизвестно + + + + Commercial + Рабочий + + + + Home + Домашний + + + + Mobile + Мобильный + + + + Incoming + Входящий + + + + Outgoing + Исходящий + + + + Blocked + Заблокирован + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + FavIcon + + + Set favorite + Добавить в избранное + + + + Unset favorite + Удалить из избранного + + + + FavoriteListItemBig + + + Favorite contact + Избранный контакт + + + + Selected favorite %1: %2 + Избранное %1: %2 + + + + tap to start meeting %1 + нажмите, чтобы начать встречу %1 + + + + tap to call %1 + нажмите, чтобы позвонить %1 + + + + FavoriteListItemSmall + + + Favorite contact + Избранный контакт + + + + Selected favorite %1: %2 + Избранное %1: %2 + + + + tap to call %1 + нажмите, чтобы позвонить %1 + + + + FavoritesList + + + + Favorites + Избранное + + + + List of all contacts that have been marked as favorites + Список всех контактов, отмеченных как избранные + + + + FavoritesWidget + + + Favorites + Избранное + + + + No favorites to display + Нет избранных контактов + + + + FirstAid + + + First Aid + Экстренная помощь + + + + Clicking one of these buttons will end all current calls and start an emergency call. + Нажатие кнопки завершит все текущие звонки и инициирует экстренный вызов. + + + + Tap to call emergency contact: %1 (%2) + Нажмите, чтобы позвонить на экстренный контакт: %1 (%2) + + + + Close + Закрыть + + + + Exit the first aid menu without initiating any action + Выйти из меню экстренной помощи без действий + + + + Close first aid menu + Закрыть меню экстренной помощи + + + + FirstAidButton + + + Open first aid menu + Открыть меню экстренной помощи + + + + First Aid + Экстренная помощь + + + + GonnectWindow + + + Home + Домашний + + + + HeadsetDevice + + + MMM dd + dd MMM + + + + HeadsetDeviceProxy + + + Ringing + Вызов + + + + Calling + Вызов + + + + + + + Call active + Звонок активен + + + + Call waiting + Звонок ожидает + + + + On Hold + На удержании + + + + + Call ended + Звонок завершён + + + + Phone conference + Телефонная конференция + + + + HistoryList + + + + No past calls + Нет прошлых звонков + + + + History + История + + + + Searchable list of past calls and meetings + Список прошлых звонков и встреч + + + + History item section + Раздел элемента истории + + + + Header for the currently selected day: %1 + Заголовок выбранного дня: %1 + + + + History item + Элемент истории + + + + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 + Выбранный элемент %1 — компания %2, место %3, номер %4, время %5, длительность %6 + + + + hh:mm + hh:mm + + + + HistoryListContextMenu + + + Call + Звонок + + + + Copy number + Скопировать номер + + + + Remove favorite + Удалить из избранного + + + + Add favorite + Добавить в избранное + + + + Remind when available + Напомнить при доступности + + + + Unblock + Разблокировать + + + + Block for 8 hours + Заблокировать на 8 часов + + + + HistoryWidget + + + History + История + + + + + All + Все + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + History call type picker + Выбор типа звонка в истории + + + + Select the call type to filter by + Выбрать тип звонка для фильтрации + + + + Currently selected call type + Текущий тип звонка + + + + Incoming + Входящий + + + + Outgoing + Исходящий + + + + Missed + Пропущенные + + + + History call origin picker + Выбор направления звонка в истории + + + + Select the call origin to filter by + Выбрать направление звонка для фильтрации + + + + Currently selected call origin + Текущее направление звонка + + + + Hide history search + Скрыть поиск в истории + + + + Show history search + Показать поиск в истории + + + + IMHandler + + + + + + Ad hoc conference + Спонтанная конференция + + + + IdentitySelector + + + Default + По умолчанию + + + + Auto + Авто + + + + Identity selection + Выбор идентификатора + + + + Select the preferred identity to be used in calls + Выбрать предпочтительный идентификатор для звонков + + + + Currently selected identity + Текущий идентификатор + + + + InfoDialog + + + Ok + ОК + + + + JitsiConnector + + + New chat message + Новое сообщение + + + + Unnamed participant + Безымянный участник + + + + Active conference + Активная конференция + + + + Hang up + Завершить + + + + %1 has joined the conference + %1 присоединился к конференции + + + + %1 has left the conference + %1 покинул конференцию + + + + JitsiHistoryListContextMenu + + + Start conference + Начать конференцию + + + + Remove favorite + Удалить из избранного + + + + Add favorite + Добавить в избранное + + + + Copy room name + Скопировать имя комнаты + + + + LDAPAddressBookFeeder + + + Failed to initialize LDAP connection + Ошибка инициализации LDAP-соединения + + + + + + LDAP error: %1 + Ошибка LDAP: %1 + + + + + Parse error: %1 + Ошибка синтаксического анализа: %1 + + + + LDAP timeout: %1 + Тайм-аут LDAP: %1 + + + + Failed to initialize LDAP connection: %1 + Ошибка инициализации LDAP: %1 + + + + LinuxDesktopSearchProvider + + + Call + Звонок + + + + Main + + + No system tray available + Системный трей недоступен + + + + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. + GOnnect обеспечивает быстрый доступ через системный трей. Ваша среда рабочего стола не поддерживает его. + + + + Information + Информация + + + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + GOnnect тестируется для вашей операционной системы и ещё не выпущен официально. Возможны ошибки — сообщайте о них на GitHub. Удачного тестирования! + + + + There are still phone calls going on, do you really want to quit? + Ещё есть активные звонки. Вы действительно хотите выйти? + + + + Please enter the password for %1: + Введите пароль для %1: + + + + Please enter the recovery key for %1: + Введите ключ восстановления для %1: + + + + Please enter the password for the SIP account: + Введите пароль для SIP-аккаунта: + + + + End all calls + Завершить все звонки + + + + Do you really want to close this window and terminate all ongoing calls? + Закрыть окно и завершить все активные звонки? + + + + Error + Ошибка + + + + Fatal Error + Критическая ошибка + + + + MainTabBar + + + + Home + Домашний + + + + Conference + Конференция + + + + No active conference + Нет активной конференции + + + + No active call + Нет активного звонка + + + + Move up + Переместить вверх + + + + Move tab up + Переместить вкладку вверх + + + + Moves the currently selected tab up by one + Перемещает выбранную вкладку вверх на одну позицию + + + + Move down + Переместить вниз + + + + Move tab down + Переместить вкладку вниз + + + + Moves the currently selected tab down by one + Перемещает выбранную вкладку вниз на одну позицию + + + + Edit + Редактировать + + + + Edit page + Редактировать страницу + + + + Edit the currently selected dashboard page + Редактировать выбранную страницу панели + + + + Delete + Удалить + + + + Delete page + Удалить страницу + + + + Delete the currently selected dashboard page + Удалить выбранную страницу панели + + + + Call + Звонок + + + + Selected tab + Выбранная вкладка + + + + The currently selected tab + Текущая выбранная вкладка + + + + Selected tab options + Параметры выбранной вкладки + + + + The settings of the currently selected tab + Настройки текущей выбранной вкладки + + + + + Settings + Настройки + + + + MenuContactInfo + + + Commercial + Рабочий + + + + Mobile + Мобильный + + + + Home + Домашний + + + + PageCreationWindow + + + Create new dashboard page + Создать страницу панели + + + + Edit dashboard page + Редактировать страницу панели + + + + Name + Имя + + + + Page name + Имя страницы + + + + Enter the page name + Введите имя страницы + + + + Icon + Значок + + + + Page icon label + Метка значка страницы + + + + Page icon selection + Выбор значка страницы + + + + Select the page icon for the dashboard page + Выбрать значок страницы панели + + + + Currently selected page icon option + Текущий параметр значка страницы + + + + Cancel + Отмена + + + + Cancel page modifcation + Отменить изменение страницы + + + + Cancel button to exit the page creation/update window + Кнопка отмены для выхода из окна + + + + Create + Создать + + + + Save + Сохранить + + + + page + страница + + + + Confirmation button to create the new dashboard page + Кнопка подтверждения создания страницы + + + + Confirmation button to apply changes to the dashboard page + Кнопка подтверждения применения изменений + + + + ParticipantsList + + + Participants list + Список участников + + + + List of all the participants of the current chat room + Список всех участников текущей комнаты + + + + Chat participant + Участник чата + + + + Selected chat participant: %1 + Выбранный участник: %1 + + + + moderator + модератор + + + + it's you + это вы + + + + Kick + Исключить + + + + Make moderator + Сделать модератором + + + + PhoneNumberUtil + + + Anonymous + Аноним + + + + PreferredIdentityEditWindow + + + Phone Number Transmission + Передача номера телефона + + + + Name + Имя + + + + Prefix + Префикс + + + + Identity + Идентификатор + + + + Enabled + Включено + + + + Automatic + Автоматически + + + + Delete + Удалить + + + + Save + Сохранить + + + + QObject + + + Andorra + Андорра + + + + United Arab Emirates + Объединённые Арабские Эмираты + + + + Afghanistan + Афганистан + + + + Antigua and Barbuda + Антигуа и Барбуда + + + + Anguilla + Ангилья + + + + Albania + Албания + + + + Armenia + Армения + + + + Angola + Ангола + + + + Argentina + Аргентина + + + + American Samoa + Американское Самоа + + + + Austria + Австрия + + + + Australia + Австралия + + + + Aruba + Аруба + + + + Aland Islands + Аландские острова + + + + Azerbaijan + Азербайджан + + + + Bosnia and Herzegovina + Босния и Герцеговина + + + + Barbados + Барбадос + + + + Bangladesh + Бангладеш + + + + Belgium + Бельгия + + + + Burkina Faso + Буркина-Фасо + + + + Bulgaria + Болгария + + + + Bahrain + Бахрейн + + + + Burundi + Бурунди + + + + Benin + Бенин + + + + Saint Barthelemy + Сен-Бартелеми + + + + Bermuda + Бермуды + + + + Brunei + Бруней + + + + Bolivia + Боливия + + + + Bonaire, Saint Eustatius and Saba + Бонэйр, Синт-Эстатиус и Саба + + + + Brazil + Бразилия + + + + Bahamas + Багамы + + + + Bhutan + Бутан + + + + Botswana + Ботсвана + + + + Belarus + Беларусь + + + + Belize + Белиз + + + + Canada + Канада + + + + Cocos Islands + Кокосовые острова + + + + Democratic Republic of the Congo + Демократическая Республика Конго + + + + Central African Republic + Центральноафриканская Республика + + + + Republic of the Congo + Республика Конго + + + + Switzerland + Швейцария + + + + Ivory Coast + Кот-д'Ивуар + + + + Cook Islands + Острова Кука + + + + Chile + Чили + + + + Cameroon + Камерун + + + + China + Китай + + + + Colombia + Колумбия + + + + Costa Rica + Коста-Рика + + + + Cuba + Куба + + + + Cape Verde + Кабо-Верде + + + + Curacao + Кюрасао + + + + Christmas Island + Остров Рождества + + + + Cyprus + Кипр + + + + Czech Republic + Чехия + + + + Germany + Германия + + + + Djibouti + Джибути + + + + Denmark + Дания + + + + Dominica + Доминика + + + + Dominican Republic + Доминиканская Республика + + + + Algeria + Алжир + + + + Ecuador + Эквадор + + + + Estonia + Эстония + + + + Egypt + Египет + + + + Western Sahara + Западная Сахара + + + + Eritrea + Эритрея + + + + Spain + Испания + + + + Ethiopia + Эфиопия + + + + Finland + Финляндия + + + + Fiji + Фиджи + + + + Falkland Islands + Фолклендские острова + + + + Micronesia + Микронезия + + + + Faroe Islands + Фарерские острова + + + + France + Франция + + + + Gabon + Габон + + + + United Kingdom + Великобритания + + + + Grenada + Гренада + + + + Georgia + Грузия + + + + French Guiana + Французская Гвиана + + + + Guernsey + Гернси + + + + Ghana + Гана + + + + Gibraltar + Гибралтар + + + + Greenland + Гренландия + + + + Gambia + Гамбия + + + + Guinea + Гвинея + + + + Guadeloupe + Гваделупа + + + + Equatorial Guinea + Экваториальная Гвинея + + + + Greece + Греция + + + + Guatemala + Гватемала + + + + Guam + Гуам + + + + Guinea-Bissau + Гвинея-Бисау + + + + Guyana + Гайана + + + + Hong Kong + Гонконг + + + + Heard Island and McDonald Islands + Острова Херд и Макдональд + + + + Honduras + Гондурас + + + + Croatia + Хорватия + + + + Haiti + Гаити + + + + Hungary + Венгрия + + + + Indonesia + Индонезия + + + + Ireland + Ирландия + + + + Israel + Израиль + + + + Isle of Man + Остров Мэн + + + + India + Индия + + + + British Indian Ocean Territory + Британская территория в Индийском океане + + + + Iraq + Ирак + + + + Iran + Иран + + + + Iceland + Исландия + + + + Italy + Италия + + + + Jersey + Джерси + + + + Jamaica + Ямайка + + + + Jordan + Иордания + + + + Japan + Япония + + + + Kenya + Кения + + + + Kyrgyzstan + Кыргызстан + + + + Cambodia + Камбоджа + + + + Kiribati + Кирибати + + + + Comoros + Коморы + + + + Saint Kitts and Nevis + Сент-Китс и Невис + + + + North Korea + Северная Корея + + + + South Korea + Южная Корея + + + + Kuwait + Кувейт + + + + Cayman Islands + Каймановы острова + + + + Kazakhstan + Казахстан + + + + Laos + Лаос + + + + Lebanon + Ливан + + + + Saint Lucia + Сент-Люсия + + + + Liechtenstein + Лихтенштейн + + + + Sri Lanka + Шри-Ланка + + + + Liberia + Либерия + + + + Lesotho + Лесото + + + + Lithuania + Литва + + + + Luxembourg + Люксембург + + + + Latvia + Латвия + + + + Libya + Ливия + + + + Morocco + Марокко + + + + Monaco + Монако + + + + Moldova + Молдова + + + + Montenegro + Черногория + + + + Saint Martin + Сен-Мартен + + + + Madagascar + Мадагаскар + + + + Marshall Islands + Маршалловы острова + + + + Macedonia + Македония + + + + Mali + Мали + + + + Myanmar + Мьянма + + + + Mongolia + Монголия + + + + Macao + Макао + + + + Northern Mariana Islands + Северные Марианские острова + + + + Martinique + Мартиника + + + + Mauritania + Мавритания + + + + Montserrat + Монтсеррат + + + + Malta + Мальта + + + + Mauritius + Маврикий + + + + Maldives + Мальдивы + + + + Malawi + Малави + + + + Mexico + Мексика + + + + Malaysia + Малайзия + + + + Mozambique + Мозамбик + + + + Namibia + Намибия + + + + New Caledonia + Новая Каледония + + + + Niger + Нигер + + + + Norfolk Island + Остров Норфолк + + + + Nigeria + Нигерия + + + + Nicaragua + Никарагуа + + + + Netherlands + Нидерланды + + + + Norway + Норвегия + + + + Nepal + Непал + + + + Nauru + Науру + + + + Niue + Ниуэ + + + + New Zealand + Новая Зеландия + + + + Oman + Оман + + + + Panama + Панама + + + + Peru + Перу + + + + French Polynesia + Французская Полинезия + + + + Papua New Guinea + Папуа — Новая Гвинея + + + + Philippines + Филиппины + + + + Pakistan + Пакистан + + + + Poland + Польша + + + + Saint Pierre and Miquelon + Сен-Пьер и Микелон + + + + Pitcairn + Питкэрн + + + + Puerto Rico + Пуэрто-Рико + + + + Palestinian Territory + Палестинская территория + + + + Portugal + Португалия + + + + Palau + Палау + + + + Paraguay + Парагвай + + + + Qatar + Катар + + + + Reunion + Реюньон + + + + Romania + Румыния + + + + Serbia + Сербия + + + + Russia + Россия + + + + Rwanda + Руанда + + + + Saudi Arabia + Саудовская Аравия + + + + Solomon Islands + Соломоновы острова + + + + Seychelles + Сейшелы + + + + Sudan + Судан + + + + Sweden + Швеция + + + + Singapore + Сингапур + + + + Saint Helena + Остров Святой Елены + + + + Slovenia + Словения + + + + Svalbard and Jan Mayen + Шпицберген и Ян-Майен + + + + Slovakia + Словакия + + + + Sierra Leone + Сьерра-Леоне + + + + San Marino + Сан-Марино + + + + Senegal + Сенегал + + + + Somalia + Сомали + + + + Suriname + Суринам + + + + South Sudan + Южный Судан + + + + Sao Tome and Principe + Сан-Томе и Принсипи + + + + El Salvador + Сальвадор + + + + Sint Maarten + Синт-Мартен + + + + Syria + Сирия + + + + Swaziland + Свазиленд + + + + Turks and Caicos Islands + Острова Тёркс и Кайкос + + + + Chad + Чад + + + + Togo + Того + + + + Thailand + Таиланд + + + + Tajikistan + Таджикистан + + + + Tokelau + Токелау + + + + East Timor + Восточный Тимор + + + + Turkmenistan + Туркменистан + + + + Tunisia + Тунис + + + + Tonga + Тонга + + + + Turkey + Турция + + + + Trinidad and Tobago + Тринидад и Тобаго + + + + Tuvalu + Тувалу + + + + Taiwan + Тайвань + + + + Tanzania + Танзания + + + + Ukraine + Украина + + + + Uganda + Уганда + + + + United States Minor Outlying Islands + Внешние малые острова США + + + + United States + Соединённые Штаты + + + + Uruguay + Уругвай + + + + Uzbekistan + Узбекистан + + + + Vatican + Ватикан + + + + Saint Vincent and the Grenadines + Сент-Винсент и Гренадины + + + + Venezuela + Венесуэла + + + + British Virgin Islands + Британские Виргинские острова + + + + U.S. Virgin Islands + Американские Виргинские острова + + + + Vietnam + Вьетнам + + + + Vanuatu + Вануату + + + + Wallis and Futuna + Уоллис и Футуна + + + + Samoa + Самоа + + + + Yemen + Йемен + + + + Mayotte + Майотта + + + + South Africa + Южная Африка + + + + Zambia + Замбия + + + + Zimbabwe + Зимбабве + + + + There are %n active call(s). + calls + + Есть %n активный звонок. + Есть %n активных звонков. + + + + + ResponseTreeModel + + + + + + Additional Information + Дополнительная информация + + + + RingToneFactory + + + ringTone + 425,0,1000,4000,0 + + + + busyTone + 425,0,480,480,0 + + + + congestionTone + 425,0,240,240,0 + + + + zip + 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 + + + + endTone + 425,0,200,200,200,200,200,200,-1 + + + + SIPAccount + + + 'userUri' is no valid SIP URI: %1 + 'userUri' не является допустимым SIP URI: %1 + + + + 'userUri' is required + 'userUri' обязателен + + + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' не является допустимым SIP URI: %1 + + + + 'registrarUri' is required + 'registrarUri' обязателен + + + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' содержит недопустимый SIP URI: %1 + + + + Failed to create %1: %2 + Не удалось создать %1: %2 + + + + SIPBuddy + + + %1 is now available + %1 теперь доступен + + + + SIPCall + + + Active call with %1 + Активный звонок с %1 + + + + Hang up + Завершить + + + + SIPCallManager + + + %1 is calling + %1 звонит + + + + %1 (%2) is calling + %1 (%2) звонит + + + + + Accept + Принять + + + + Call back + Перезвонить + + + + + Reject + Отклонить + + + + Missed call from %1 + Пропущенный звонок от %1 + + + + SIPManager + + + New Identity + Новый идентификатор + + + + SIPTemplate + + + + Failed to write to %1 + Ошибка записи в %1 + + + + Failed to copy %1 to the config space + Ошибка копирования %1 в каталог конфигурации + + + + Source file %1 does not exist + Исходный файл %1 не существует + + + + SearchBox + + + Search number + Поиск номера + + + + SearchCategoryItem + + + Search result category filter %1 + Фильтр категории результатов поиска %1 + + + + Filter for the individual search result items by category + Фильтр результатов поиска по категории + + + + SearchCategoryList + + + Contacts + Контакты + + + + Messages + Сообщения + + + + Rooms and Teams + Комнаты и команды + + + + Files + Файлы + + + + SearchDial + + + Select number + Выбрать номер + + + + Activate search field + Активировать поле поиска + + + + Number or contact + Номер или контакт + + + + Clear search field + Очистить поле поиска + + + + Default + По умолчанию + + + + Auto + Авто + + + + Preferred identity + Предпочтительный идентификатор + + + + Select the preferred identity for outgoing calls + Выбрать предпочтительный идентификатор для исходящих звонков + + + + SearchField + + + Search for contacts or room names... + Поиск контактов или комнат... + + + + Clear search field + Очистить поле поиска + + + + SearchResultCategory + + + Search result category %1 + Категория результатов поиска %1 + + + + Divider for the individual search result items by category + Разделитель результатов поиска по категории + + + + SearchResultItem + + + Search result + Результат поиска + + + + Currently selected search result + Текущий результат поиска + + + + SearchResultNumberItem + + + Phone number + Номер телефона + + + + Selected favorite number %1 + Выбранный избранный номер %1 + + + + Selected phone number %1 + Выбранный номер телефона %1 + + + + SearchResultPopup + + + Search filter and identity selection + Фильтр поиска и выбор идентификатора + + + + Select search filter to be applied, as well as the outgoing identity + Выбрать фильтр поиска и исходящий идентификатор + + + + Outgoing identity + Исходящий идентификатор + + + + Search results + Результаты поиска + + + + All search results will be listed here in their respective categories + Все результаты поиска будут перечислены по категориям + + + + Direct dial + Прямой набор + + + + Call "%1" + Позвонить «%1» + + + + Open room "%1" + Открыть комнату «%1» + + + + History + История + + + + Contacts + Контакты + + + + SettingsPage + + + Settings + Настройки + + + + Show chat messages as desktop notifications + Показывать сообщения чата как уведомления + + + + Enable USB headset driver [%1] + Включить драйвер USB-гарнитуры [%1] + + + + not detected + не обнаружено + + + + Show dial window on USB headset pick up + Показывать окно набора при снятии USB-гарнитуры + + + + Color scheme + Цветовая схема + + + + System default + Системная по умолчанию + + + + Light + Светлая + + + + Dark + Тёмная + + + + Inverse Accept / Reject buttons + Инвертировать кнопки принятия/отклонения + + + + Use dark mode tray icon + Использовать значок трея для тёмного режима + + + + Disable USB headset mute state propagation + Отключить синхронизацию состояния отключения USB-гарнитуры + + + + Reload contacts from LDAP + Перезагрузить контакты из LDAP + + + + Phoning + Телефония + + + + Show main window on startup + Показывать главное окно при запуске + + + + Disable synchronisation with the system mute state + Отключить синхронизацию с системным отключением звука + + + + + restart required + требуется перезапуск + + + + Appearance + Внешний вид + + + + Use custom window decoration + Использовать пользовательское оформление окна + + + + Theme selection box + Поле выбора темы + + + + Select the UI theme + Выбрать тему интерфейса + + + + Currently selected theme option + Текущий параметр темы + + + + Signalling busy when a call is active + Сигнализировать занято при активном звонке + + + + Rules for telephone number transmission + Правила передачи номера телефона + + + + Standard preferred identity + Стандартный предпочтительный идентификатор + + + + + Default + По умолчанию + + + + + Auto + Авто + + + + + Prefererred identity selection + Выбор предпочтительного идентификатора + + + + + Select the preferred identity + Выбрать предпочтительный идентификатор + + + + Currently selected identity option + Текущий параметр идентификатора + + + + No preferred identities yet. + Нет предпочтительных идентификаторов. + + + + Currently highlighted preferred identity. Tap to edit. + Текущий предпочтительный идентификатор. Нажмите для редактирования. + + + + Standard + Стандарт + + + + Add identity + Добавить идентификатор + + + + Add a new preferred identity entry + Добавить новый предпочтительный идентификатор + + + + Audio settings + Настройки звука + + + + Input device + Устройство ввода + + + + Input device selection + Выбор устройства ввода + + + + Select the input device to be used + Выбрать устройство ввода + + + + Currently selected input option + Текущий параметр ввода + + + + Output device + Устройство вывода + + + + Output device selection + Выбор устройства вывода + + + + Select the output device to be used + Выбрать устройство вывода + + + + Currently selected output option + Текущий параметр вывода + + + + Output device for ring tone + Устройство вывода для мелодии звонка + + + + Currently selected ring output option + Текущий параметр вывода звонка + + + + Ring tone + Мелодия звонка + + + + Prefer USB headset ring sound if available + Использовать звонок USB-гарнитуры при наличии + + + + Reset ring tone + Сбросить мелодию звонка + + + + Reset the ring tone to its default option + Восстановить мелодию звонка по умолчанию + + + + Pick ring tone + Выбрать мелодию звонка + + + + Select the ring tone you want to use for incoming calls + Выбрать мелодию для входящих звонков + + + + + Currently set to: + Текущее значение: + + + + Ring tone volume + Громкость мелодии звонка + + + + + Adjust %1 + Настроить %1 + + + + %1 % + %1 % + + + + Pause between ring tones [s] + Пауза между звонками [с] + + + + Debugging + Отладка + + + + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. + Нажмите для запуска сеанса отладки. Приложение перезапустится и начнёт запись дополнительной информации. Сеанс ограничен 5 минутами. + + + + Start debug run (restart app) + Начать отладку (перезапустить приложение) + + + + Download debug information + Загрузить отладочную информацию + + + + ShortcutsWindow + + + Shortcuts + Горячие клавиши + + + + Shortcut key: %1 + Клавиша быстрого доступа: %1 + + + + Local shortcuts (work only when app is focused) + Локальные сочетания (работают только при фокусе на приложении) + + + + Local shortcuts + Локальные сочетания клавиш + + + + Ctrl + F + Ctrl + F + + + + Activates the global search field + Активирует глобальное поле поиска + + + + F11 + F11 + + + + Toggles between fullsceen and normal window mode + Переключает между полноэкранным и обычным режимом + + + + Ctrl + Shift + M + Ctrl + Shift + M + + + + Toggles audio mute + Включает/выключает звук + + + + Global shortcuts (work from anywhere) + Глобальные сочетания (работают в любом месте) + + + + Global shortcuts + Глобальные сочетания клавиш + + + + SipTemplateWizard + + + Initial configuration + Начальная конфигурация + + + + Error: %1 + Ошибка: %1 + + + + SIP wizard notification + Уведомление мастера SIP + + + + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. + GOnnect не может найти конфигурацию SIP. Выберите шаблон ниже и при необходимости измените файл конфигурации. + + + + Please pick: + Выберите: + + + + Select SIP template + Выбрать шаблон SIP + + + + Select the SIP template to be used + Выбрать используемый шаблон SIP + + + + Currently selected SIP template + Текущий шаблон SIP + + + + Next + Далее + + + + Continue setup + Продолжить настройку + + + + Confirmation button to continue the setup + Кнопка подтверждения для продолжения настройки + + + + Template field list + Список полей шаблона + + + + List of all the available SIP template options + Список всех доступных параметров шаблона SIP + + + + SIP template option + Параметр шаблона SIP + + + + Currently selected SIP template option + Текущий параметр шаблона SIP + + + + Display name of the SIP template option + Отображаемое имя параметра шаблона SIP + + + + Description of the SIP template option + Описание параметра шаблона SIP + + + + Back + Назад + + + + Back button to return to the template selection menu + Кнопка возврата к меню выбора шаблона + + + + + Finish + Завершить + + + + Confirmation button to apply the changes to the SIP template + Кнопка подтверждения применения изменений шаблона + + + + Successful configuration file creation + Файл конфигурации успешно создан + + + + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. + Файл конфигурации создан. Проверьте, нужны ли изменения, и перезапустите GOnnect. + + + + The configuration has been saved to: + Конфигурация сохранена в: + + + + Copy to clipboard + Копировать в буфер обмена + + + + Copy the full path of the configuration file to the clipboard + Скопировать полный путь к файлу конфигурации + + + + Finish wizard + Завершить мастер + + + + Finish the SIP configuration wizard + Завершить мастер настройки SIP + + + + StateManager + + + Show dial window and focus search field + Показать окно набора и перейти в поле поиска + + + + End all calls + Завершить все звонки + + + + Redial last outgoing call + Повторить последний исходящий звонок + + + + Toggle hold + Переключить удержание + + + + Phone calls are active + Звонки активны + + + + SystemTrayMenu + + + + Dial... + Набор номера... + + + + Not registered... + Не зарегистрирован... + + + + Settings + Настройки + + + + About + О программе + + + + Quit + Выход + + + + End conference + Завершить конференцию + + + + Call with %1 has ended + Звонок с %1 завершён + + + + Hang up call with %1 + Завершить звонок с %1 + + + + Accept call with %1 + Принять звонок от %1 + + + + TemplateFieldFile + + + File path + Путь к файлу + + + + Enter the file path for %1 + Введите путь к файлу для %1 + + + + Choose... + Выбрать... + + + + Open file picker + Открыть выбор файла + + + + Select the file that should be used for %1 + Выбрать файл для %1 + + + + Certificate files (%1) + Файлы сертификатов (%1) + + + + TemplateFieldText + + + Text input + Текстовый ввод + + + + Enter the desired value for %1 + Введите нужное значение для %1 + + + + Toggler + + + Failed to toggle the state of %1. + Не удалось переключить состояние %1. + + + + TogglerList + + + Toggler list + Список переключателей + + + + List of items that can be toggled + Список переключаемых элементов + + + + Toggle %1 + Переключить %1 + + + + VerticalLevelMeter + + + Level meter + Индикатор уровня + + + + VideoDeviceMenu + + + Virtual background + Виртуальный фон + + + + ViewHelper + + + Save File + Сохранить файл + + + + %n minute(s) + + %n минута + %n минут + + + + + 1 hour and %n minute(s) + + 1 час и %n минута + 1 час и %n минут + + + + + %n hour(s) + + %n час + %n часов + + + + + Audio Files (%1) + Аудиофайлы (%1) + + + + VolumePopup + + + Adjust the volume + Настроить громкость + + + + Unmute + Включить звук + + + + Mute + Выключить звук + + + + Open audio settings + Открыть настройки звука + + + + WidgetSelectionWindow + + + Add widget + Добавить виджет + + + + Widget + Виджет + + + + Widget selection header + Заголовок выбора виджета + + + + Date Events + События + + + + List of upcoming appointments + Список предстоящих встреч + + + + Favorites + Избранное + + + + Quick dial for your favorite contacts and conferences + Быстрый набор избранных контактов и конференций + + + + History + История + + + + Web View + Веб-просмотр + + + + A web-based content display + Отображение веб-содержимого + + + + Widget selection + Выбор виджета + + + + Select the widget that should be added to the current dashboard page + Выбрать виджет для добавления на текущую страницу + + + + Currently selected widget option + Текущий параметр виджета + + + + Accept all certificates + Принять все сертификаты + + + + Confirm widget selection + Подтвердить выбор виджета + + + + Confirmation button to create and add the selected widget to the current dashboard + Кнопка создания и добавления виджета на панель управления + + + + Searchable call and conference history + История звонков и конференций + + + + Title + Название + + + + URL + URL + + + + URL (dark mode) + URL (тёмный режим) + + + + Settings text input + Поле настроек + + + + Input for widget setting %1 + Ввод для параметра виджета %1 + + + + Settings checkbox + Флажок настроек + + + + Checkbox for widget setting %1 + Флажок для параметра виджета %1 + + + + Widget setting %1 + Параметр виджета %1 + + + + Cancel + Отмена + + + + Cancel widget selection + Отменить выбор виджета + + + + Cancel button to exit widget selection selection without changes + Кнопка отмены для выхода без изменений + + + + Add + Добавить + + + + WindowHeader + + + GOnnect window header + Заголовок окна GOnnect + + + + Minimize + Свернуть + + + + Maximize + Развернуть + + + + Close GOnnect window + Закрыть окно GOnnect + + + \ No newline at end of file diff --git a/i18n/gonnect_uk.ts b/i18n/gonnect_uk.ts new file mode 100644 index 00000000..d8969d0c --- /dev/null +++ b/i18n/gonnect_uk.ts @@ -0,0 +1,5186 @@ + + + + + AboutWindow + + + About + Про програму + + + + GOnnect headline + Заголовок GOnnect + + + + GOnnect version + Версія GOnnect + + + + Version: v%1 + Версія: v%1 + + + + Copy to clipboard + Скопіювати до буфера обміну + + + + Copy the currently used version of Gonnect to the clipboard + Скопіювати поточну версію GOnnect до буфера обміну + + + + + + + Homepage + Домашня сторінка + + + + Visit the project homepage + Відвідати домашню сторінку проєкту + + + + Bug Tracker + Трекер помилок + + + + Visit the project bug tracker + Відвідати трекер помилок проєкту + + + + Documentation + Документація + + + + Visit the online project documentation + Відвідати онлайн-документацію проєкту + + + + AdditionalInfo + + + Additional caller related information + Додаткова інформація про абонента + + + + List of informational items regarding the caller, such as open support tickets + Список інформації про абонента, наприклад відкриті тікети підтримки + + + + Expandable response section + Розгортуваний розділ відповіді + + + + AudioDeviceMenu + + + Default + За замовчуванням + + + + AudioEnvWindow + + + Unknown audio environment + Невідоме аудіосередовище + + + + Audio environment error + Помилка аудіосередовища + + + + No fitting audio environment could be found. Please select the desired audio devices. + Відповідне аудіосередовище не знайдено. Виберіть потрібні аудіопристрої. + + + + Input device selection header + Заголовок вибору пристрою вводу + + + + Header for the input device selection below + Заголовок для вибору пристрою вводу нижче + + + + Input device + Пристрій вводу + + + + Input device selection box + Поле вибору пристрою вводу + + + + Select the input device that should be used + Вибрати пристрій вводу + + + + Currently selected input device + Поточний пристрій вводу + + + + Output device selection header + Заголовок вибору пристрою виводу + + + + Header for the output device selection below + Заголовок для вибору пристрою виводу нижче + + + + Output device + Пристрій виводу + + + + Output device selection box + Поле вибору пристрою виводу + + + + Select the output device that should be used + Вибрати пристрій виводу + + + + Currently selected output device + Поточний пристрій виводу + + + + Ring tone output device + Пристрій виводу мелодії дзвінка + + + + Output device for ring tone + Пристрій виводу для мелодії дзвінка + + + + Ring tone output device selection box + Поле вибору пристрою виводу мелодії + + + + Select the output device that should be used for playing the ring tone + Вибрати пристрій для відтворення мелодії дзвінка + + + + Currently selected ring tone output device + Поточний пристрій виводу мелодії + + + + Ok + ОК + + + + Close audio environment selection + Закрити вибір аудіосередовища + + + + Confirmation button to leave the audio environment selection window + Кнопка підтвердження для виходу з вікна вибору аудіосередовища + + + + AudioLevelButton + + + Change volume + Змінити гучність + + + + AudioManager + + + Default input + Вхід за замовчуванням + + + + Default output + Вихід за замовчуванням + + + + AvatarImage + + + Initials of this contact + Ініціали цього контакту + + + + BaseDialog + + + Dialog + Діалог + + + + BasePage + + + Base dashboard page grid + Базова сітка сторінки панелі управління + + + + Canvas for editable dashboard pages + Полотно для редагованих сторінок панелі + + + + Add widgets + Додати віджети + + + + BaseWidget + + + Drag widget + Перетягнути віджет + + + + Change the position of the widget + Змінити положення віджета + + + + Remove widget + Видалити віджет + + + + Remove the currently selected widget from the dashboard + Видалити вибраний віджет з панелі + + + + Resize widget + Змінити розмір віджета + + + + Resize the widget according to the mouse direction + Змінити розмір віджета за напрямком миші + + + + BaseWindow + + + + + + + + + + Drag border + Межа перетягування + + + + Top left drag border for window resize operations + Ліва верхня межа для зміни розміру вікна + + + + Top drag border for window resize operations + Верхня межа для зміни розміру вікна + + + + Top right border for window resize operations + Права верхня межа для зміни розміру вікна + + + + Right drag border for window resize operations + Права межа для зміни розміру вікна + + + + Bottom right drag border for window resize operations + Права нижня межа для зміни розміру вікна + + + + Bottom drag border for window resize operations + Нижня межа для зміни розміру вікна + + + + Bottom left drag border for window resize operations + Ліва нижня межа для зміни розміру вікна + + + + Left drag border for window resize operations + Ліва межа для зміни розміру вікна + + + + BottomStatusBar + + + Status bar + Рядок стану + + + + BurgerMenu + + + Toggle fullscreen + Перемкнути повноекранний режим + + + + Shortcuts... + Гарячі клавіші... + + + + Customize UI + Налаштувати інтерфейс + + + + About... + Про програму... + + + + Quit + Вийти + + + + Call + + + Conference + Конференція + + + + Drag bar + Панель перетягування + + + + CallButtonBar + + + %1@%2 kHz + %1@%2 кГц + + + + Transmit + Передача + + + + Call security level + Рівень безпеки дзвінка + + + + Security level of the ongoing call + Рівень безпеки поточного дзвінка + + + + Call security details + Деталі безпеки дзвінка + + + + Detailed call security status: %1 / %2 + Детальний статус безпеки: %1 / %2 + + + + signaling encrypted + сигналізацію зашифровано + + + + signaling unencrypted + сигналізацію не зашифровано + + + + media encrypted + медіа зашифровано + + + + media unencrypted + медіа не зашифровано + + + + Call quality + Якість дзвінка + + + + Quality of the ongoing call + Якість поточного дзвінка + + + + Transmission statistics + Статистика передачі + + + + + Call quality metrics + Метрики якості дзвінка + + + + + MOS + MOS + + + + + Mean opinion score + Середня оцінка якості (MOS) + + + + Numerical metric assessing transmission-side voice call quality: %1 + Числова метрика якості голосового зв'язку з боку передачі: %1 + + + + + Packet loss + Втрата пакетів + + + + %1% of packets lost in transmission + %1% пакетів втрачено при передачі + + + + + Jitter + Джитер + + + + Amount of transmission side jitter: %1 + Джитер з боку передачі: %1 + + + + + Effective delay + Ефективна затримка + + + + Effective transmission side call delay: %1 + Ефективна затримка з боку передачі: %1 + + + + Receiver statistics + Статистика прийому + + + + Receive + Прийом + + + + Numerical metric assessing receiver-side voice/video call quality: %1 + Числова метрика якості з боку прийому: %1 + + + + %1% of packets lost in receival + %1% пакетів втрачено при прийомі + + + + Amount of receiver side jitter: %1 + Джитер з боку прийому: %1 + + + + Effective receiver side call delay: %1 + Ефективна затримка з боку прийому: %1 + + + + Codec + Кодек + + + + Audio codec + Аудіокодек + + + + The currently used audio codec and frequency: %1 + Поточний аудіокодек і частота: %1 + + + + Elapsed call time + Тривалість дзвінка + + + + The duration in seconds the call has been active for: %1 + Тривалість дзвінка у секундах: %1 + + + + Screen + Екран + + + + Screensharing control + Керування демонстрацією екрана + + + + Start sharing your screen + Розпочати демонстрацію екрана + + + + Camera + Камера + + + + Camera control + Керування камерою + + + + Enable your camera + Увімкнути камеру + + + + Resume + Продовжити + + + + Hold + Утримання + + + + Resume call + Відновити дзвінок + + + + Hold call + Утримати дзвінок + + + + Update the call hold state + Оновити стан утримання + + + + Micro + Мікрофон + + + + Input control + Керування входом + + + + Set the mute state of the current input device + Встановити стан вимкнення звуку пристрою вводу + + + + Output + Вихід + + + + Output control + Керування виходом + + + + Change the current output devices + Змінити поточні пристрої виводу + + + + Accept call + Прийняти дзвінок + + + + Hangup call + Завершити дзвінок + + + + CallDetails + + + SIP call status code + Код стану SIP-дзвінка + + + + The current status code of the call: %1 + Поточний код стану дзвінка: %1 + + + + Jitsi Meet + Jitsi Meet + + + + Switch to a Jitsi Meet session + Перейти до сеансу Jitsi Meet + + + + Unhold + Зняти з утримання + + + + Hold + Утримання + + + + Toggle the hold state to %1 + Перемкнути стан утримання на %1 + + + + Accept call + Прийняти дзвінок + + + + Hangup call + Завершити дзвінок + + + + CallHistory + + + Failed to create directory %1 to store the call history database. + Не вдалося створити каталог %1 для бази даних історії дзвінків. + + + + Failed to open call history database: %1 + Не вдалося відкрити базу даних історії дзвінків: %1 + + + + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. + База даних пошкоджена. Видаліть %1 і перезапустіть додаток. + + + + CallItem + + + Call + Дзвінок + + + + Selected call %1 - contact %2, company %3, location %4/%5, number %6 + Вибраний дзвінок %1 — контакт %2, компанія %3, місце %4/%5, номер %6 + + + + Hangup button + Кнопка завершення + + + + Pressing this will end the call + Натискання завершить дзвінок + + + + CallList + + + Drag callers onto each other to transfer call + Перетягніть абонентів один на одного для переадресації + + + + List of active calls + Список активних дзвінків + + + + + Create conference + Створити конференцію + + + + CallSideBar + + + Chat + Чат + + + + Person(s) + + %n учасник + %n учасників + + + + + Info + Відомості + + + + CallerBigAvatar + + + Caller name + Ім'я абонента + + + + is calling... + дзвонить... + + + + Calling... + Виклик... + + + + CallsModel + + + unknown number + невідомий номер + + + + CardList + + + List of configurable options + Список параметрів + + + + ChatMessageBox + + + Message + Повідомлення + + + + Type message + Введіть повідомлення + + + + Enter the chat text message + Введіть текстове повідомлення чату + + + + ChatMessageList + + + Chat message list + Список повідомлень чату + + + + List of all chat messages of the current chat room + Список усіх повідомлень поточної кімнати чату + + + + Auto scroll down + Автопрокрутка вниз + + + + ChatMessageListItem + + + Chat message + Повідомлення чату + + + + Selected chat message - from %1, at %2: %3 + Вибране повідомлення — від %1, о %2: %3 + + + + ChatRoomList + + + Chat room list + Список кімнат чату + + + + List of all chat rooms + Список усіх кімнат чату + + + + ChatRoomListItem + + + Chat room + Кімната чату + + + + Selected chat room %1: %2 unread messages + Вибрана кімната %1: непрочитаних %2 + + + + ChatSideBar + + + Chat message list + Список повідомлень чату + + + + List of all the messages in the current chat + Список усіх повідомлень поточного чату + + + + Chat message + Повідомлення чату + + + + Selected chat message from %1 at %2: %3 + Вибране повідомлення від %1 о %2: %3 + + + + the server + сервера + + + + you + вас + + + + Select emoji + Вибрати емодзі + + + + Enter chat message... + Введіть повідомлення... + + + + Chats + + + Please enter your recovery key to decrypt messages: + Введіть ключ відновлення для розшифрування повідомлень: + + + + Recovery key + Ключ відновлення + + + + Enter recovery key + Введіть ключ відновлення + + + + Use key + Використати ключ + + + + Use recovery key + Використати ключ відновлення + + + + ClipboardButton + + + Copy to clipboard: %1 + Скопіювати до буфера: %1 + + + + Conference + + + + Set room name + Задати назву кімнати + + + + + Room name: + Назва кімнати: + + + + + Enter the room name + Введіть назву кімнати + + + + Authenticate + Автентифікувати + + + + Please authenticate in the opened browser window... + Виконайте автентифікацію у відкритому вікні браузера... + + + + This conference is protected by a password. Please enter it to join the room. + Конференція захищена паролем. Введіть пароль для входу. + + + + + Password + Пароль + + + + + Enter the password + Введіть пароль + + + + Remember password + Запам'ятати пароль + + + + + Cancel + Скасувати + + + + Join Room + Увійти до кімнати + + + + Password required + Потрібен пароль + + + + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. + Введіть пароль для захисту конференції. Інші учасники мають ввести його перед входом. + + + + This password has been set for the conference room and must be entered by participants before taking part in the session. + Цей пароль встановлено для кімнати і має вводитися учасниками перед входом. + + + + The room password has been set by someone else. + Пароль кімнати встановлено іншим учасником. + + + + Show password + Показати пароль + + + + Remove + Видалити + + + + Save + Зберегти + + + + Video quality + Якість відео + + + + Change the video quality of this meeting + Змінити якість відео зустрічі + + + + No video (audio only) + Без відео (лише аудіо) + + + + Lowest quality + Найнижча якість + + + + Standard quality + Стандартна якість + + + + Highest quality + Найвища якість + + + + Close + Закрити + + + + Drag bar + Панель перетягування + + + + ConferenceButtonBar + + + %n minutes left + + залишилася %n хвилина + залишилося %n хвилин + + + + + Conference room + Кімната конференції + + + + Share + Поділитися + + + + Copy room name + Скопіювати назву кімнати + + + + Copy room link + Скопіювати посилання на кімнату + + + + Open in browser + Відкрити у браузері + + + + Show phone number + Показати номер телефону + + + + Raise + Підняти руку + + + + Resume + Продовжити + + + + Hold + Утримання + + + + View + Вигляд + + + + Screen + Екран + + + + Share window + Поділитися вікном + + + + Share screen + Поділитися екраном + + + + Camera + Камера + + + + Output + Вихід + + + + More + Ще + + + + Noise supression + Шумоподавлення + + + + Toggle subtitles + Увімкнути/вимкнути субтитри + + + + Toggle whiteboard + Увімкнути/вимкнути дошку + + + + Video quality... + Якість відео... + + + + Set room password... + Встановити пароль кімнати... + + + + Mute everyone + Вимкнути звук усіх + + + + Leave conference + Покинути конференцію + + + + End conference for all + Завершити конференцію для всіх + + + + Micro + Мікрофон + + + + ConfirmDialog + + + Cancel + Скасувати + + + + ControlBar + + + App menu + Меню застосунку + + + + Credentials + + + storing credentials failed: %1 + Помилка збереження облікових даних: %1 + + + + reading credentials failed: %1 + Помилка читання облікових даних: %1 + + + + CredentialsDialog + + + Authentication failed + Помилка автентифікації + + + + Please enter the password: + Введіть пароль: + + + + Password + Пароль + + + + Enter the password + Введіть пароль + + + + Ok + ОК + + + + CustomWindowHeader + + + GOnnect window header + Заголовок вікна GOnnect + + + + App menu + Меню застосунку + + + + Close GOnnect window + Закрити вікно GOnnect + + + + DateEventManager + + + Conference starting soon + Конференція незабаром розпочнеться + + + + Appointment starting soon + Зустріч незабаром розпочнеться + + + + Join + Приєднатися + + + + Open + Відкрити + + + + DateEventsList + + + Date events + Події календаря + + + + List of all the currently active and upcoming date events + Список усіх активних та майбутніх подій + + + + Date event section + Розділ подій + + + + Header for %1 + Заголовок для %1 + + + + Today - %1 + Сьогодні — %1 + + + + + yyyy/MM/dd + dd.MM.yyyy + + + + Tomorrow - %1 + Завтра — %1 + + + + dddd - yyyy/MM/dd + dddd - dd.MM.yyyy + + + + Date event + Подія календаря + + + + Currently selected date event: %1, starting time %2, remaining time %3 + Вибрана подія: %1, початок %2, залишилося %3 + + + + + hh:mm + hh:mm + + + + All day + Весь день + + + + till %1 + до %1 + + + + in %1 + через %1 + + + + Join + Приєднатися + + + + Open + Відкрити + + + + Join meeting + Приєднатися до зустрічі + + + + Join the meeting associated with the currently selected event + Приєднатися до зустрічі вибраної події + + + + Copy room link + Скопіювати посилання на кімнату + + + + Copy link + Скопіювати посилання + + + + Copy meeting link + Скопіювати посилання на зустріч + + + + Copy the meeting link associated with the currently selected event + Скопіювати посилання на зустріч вибраної події + + + + DateEventsWidget + + + Appointments + Зустрічі + + + + Loading appointments... + Завантаження зустрічей... + + + + No upcoming appointments + Немає майбутніх зустрічей + + + + Date event widget status + Статус віджета подій + + + + Displays the current status of the widget: %1 + Відображає поточний статус віджета: %1 + + + + DialInInfo + + + Call one of the phone numbers below and use this code for authentication: + Зателефонуйте за одним із наведених номерів і використайте цей код для автентифікації: + + + + Close + Закрити + + + + DtmfDialer + + + Number pad + Набір номера + + + + Character %1 + Символ %1 + + + + EditModeOptions + + + Add page + Додати сторінку + + + + Add a new dashboard page + Додати нову сторінку панелі + + + + Add widget + Додати віджет + + + + Add a new widget to the current dashboard page + Додати новий віджет на поточну сторінку + + + + Finished + Готово + + + + Finish and save all dashboard and widget changes + Завершити та зберегти всі зміни + + + + EmergencyCallIncomingWindow + + + Emergency Call + Екстрений виклик + + + + Incoming emergency call from %1 + Вхідний екстрений виклик від %1 + + + + Answering the call will automatically terminate all other ongoing calls. + Відповідь автоматично завершить усі інші активні дзвінки. + + + + Answer + Відповісти + + + + EmojiButton + + + Emoji + Емодзі + + + + Selected Emoji: %1 + Вибране емодзі: %1 + + + + EmojiPicker + + + Switch Emoji category + Змінити категорію емодзі + + + + Select Emoji + Вибрати емодзі + + + + EnumTranslation + + + Trying + Спроба + + + + Ringing + Виклик + + + + Call being forwarded + Дзвінок переадресовано + + + + Queued + У черзі + + + + Progress + В процесі + + + + Ok + ОК + + + + Accepted + Прийнято + + + + Unauthorized + Не авторизовано + + + + + Rejected + Відхилено + + + + Not found + Не знайдено + + + + Proxy authentication required + Потрібна автентифікація проксі + + + + Request timeout + Час запиту вийшов + + + + Temporarily unavailable + Тимчасово недоступно + + + + Ambiguous + Неоднозначно + + + + Busy here + Зайнято + + + + Request terminated + Запит завершено + + + + Not acceptable here + Неприйнятно + + + + Internal server error + Внутрішня помилка сервера + + + + Not implemented + Не реалізовано + + + + Bad gateway + Неправильний шлюз + + + + Service unavailable + Сервіс недоступний + + + + Server timeout + Тайм-аут сервера + + + + Busy everywhere + Зайнято всюди + + + + Decline + Відхилено + + + + Does not exist anywhere + Не існує ніде + + + + Not acceptable anywhere + Ніде неприйнятно + + + + Unwanted + Небажаний + + + + + + + Unknown + Невідомо + + + + Commercial + Робочий + + + + Home + Домашній + + + + Mobile + Мобільний + + + + Incoming + Вхідний + + + + Outgoing + Вихідний + + + + Blocked + Заблокований + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + FavIcon + + + Set favorite + Додати до обраного + + + + Unset favorite + Видалити з обраного + + + + FavoriteListItemBig + + + Favorite contact + Обраний контакт + + + + Selected favorite %1: %2 + Обране %1: %2 + + + + tap to start meeting %1 + натисніть, щоб розпочати зустріч %1 + + + + tap to call %1 + натисніть, щоб зателефонувати %1 + + + + FavoriteListItemSmall + + + Favorite contact + Обраний контакт + + + + Selected favorite %1: %2 + Обране %1: %2 + + + + tap to call %1 + натисніть, щоб зателефонувати %1 + + + + FavoritesList + + + + Favorites + Обране + + + + List of all contacts that have been marked as favorites + Список усіх контактів, позначених як обрані + + + + FavoritesWidget + + + Favorites + Обране + + + + No favorites to display + Немає обраних контактів + + + + FirstAid + + + First Aid + Перша допомога + + + + Clicking one of these buttons will end all current calls and start an emergency call. + Натискання кнопки завершить усі поточні дзвінки та ініціює екстрений виклик. + + + + Tap to call emergency contact: %1 (%2) + Натисніть, щоб зателефонувати на екстрений контакт: %1 (%2) + + + + Close + Закрити + + + + Exit the first aid menu without initiating any action + Вийти з меню першої допомоги без дій + + + + Close first aid menu + Закрити меню першої допомоги + + + + FirstAidButton + + + Open first aid menu + Відкрити меню першої допомоги + + + + First Aid + Перша допомога + + + + GonnectWindow + + + Home + Домашній + + + + HeadsetDevice + + + MMM dd + dd MMM + + + + HeadsetDeviceProxy + + + Ringing + Виклик + + + + Calling + Виклик + + + + + + + Call active + Дзвінок активний + + + + Call waiting + Дзвінок очікує + + + + On Hold + На утриманні + + + + + Call ended + Дзвінок завершено + + + + Phone conference + Телефонна конференція + + + + HistoryList + + + + No past calls + Немає минулих дзвінків + + + + History + Історія + + + + Searchable list of past calls and meetings + Список минулих дзвінків та зустрічей + + + + History item section + Розділ елементів історії + + + + Header for the currently selected day: %1 + Заголовок вибраного дня: %1 + + + + History item + Елемент історії + + + + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 + Вибраний елемент %1 — компанія %2, місце %3, номер %4, час %5, тривалість %6 + + + + hh:mm + hh:mm + + + + HistoryListContextMenu + + + Call + Дзвінок + + + + Copy number + Скопіювати номер + + + + Remove favorite + Видалити з обраного + + + + Add favorite + Додати до обраного + + + + Remind when available + Нагадати при доступності + + + + Unblock + Розблокувати + + + + Block for 8 hours + Заблокувати на 8 годин + + + + HistoryWidget + + + History + Історія + + + + + All + Усі + + + + SIP + SIP + + + + Jitsi Meet + Jitsi Meet + + + + History call type picker + Вибір типу дзвінка в історії + + + + Select the call type to filter by + Вибрати тип дзвінка для фільтрації + + + + Currently selected call type + Поточний тип дзвінка + + + + Incoming + Вхідний + + + + Outgoing + Вихідний + + + + Missed + Пропущені + + + + History call origin picker + Вибір напрямку дзвінка в історії + + + + Select the call origin to filter by + Вибрати напрямок дзвінка для фільтрації + + + + Currently selected call origin + Поточний напрямок дзвінка + + + + Hide history search + Приховати пошук в історії + + + + Show history search + Показати пошук в історії + + + + IMHandler + + + + + + Ad hoc conference + Спонтанна конференція + + + + IdentitySelector + + + Default + За замовчуванням + + + + Auto + Авто + + + + Identity selection + Вибір ідентифікатора + + + + Select the preferred identity to be used in calls + Вибрати бажаний ідентифікатор для дзвінків + + + + Currently selected identity + Поточний ідентифікатор + + + + InfoDialog + + + Ok + ОК + + + + JitsiConnector + + + New chat message + Нове повідомлення + + + + Unnamed participant + Безіменний учасник + + + + Active conference + Активна конференція + + + + Hang up + Завершити + + + + %1 has joined the conference + %1 приєднався до конференції + + + + %1 has left the conference + %1 покинув конференцію + + + + JitsiHistoryListContextMenu + + + Start conference + Розпочати конференцію + + + + Remove favorite + Видалити з обраного + + + + Add favorite + Додати до обраного + + + + Copy room name + Скопіювати назву кімнати + + + + LDAPAddressBookFeeder + + + Failed to initialize LDAP connection + Помилка ініціалізації LDAP-з'єднання + + + + + + LDAP error: %1 + Помилка LDAP: %1 + + + + + Parse error: %1 + Помилка синтаксичного аналізу: %1 + + + + LDAP timeout: %1 + Тайм-аут LDAP: %1 + + + + Failed to initialize LDAP connection: %1 + Помилка ініціалізації LDAP: %1 + + + + LinuxDesktopSearchProvider + + + Call + Дзвінок + + + + Main + + + No system tray available + Системний трей недоступний + + + + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. + GOnnect забезпечує швидкий доступ через системний трей. Ваше середовище його не підтримує. + + + + Information + Інформація + + + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + GOnnect тестується для вашої операційної системи і ще не випущений офіційно. Можливі помилки — повідомляйте про них на GitHub. Вдалого тестування! + + + + There are still phone calls going on, do you really want to quit? + Ще є активні дзвінки. Ви справді хочете вийти? + + + + Please enter the password for %1: + Введіть пароль для %1: + + + + Please enter the recovery key for %1: + Введіть ключ відновлення для %1: + + + + Please enter the password for the SIP account: + Введіть пароль для SIP-акаунта: + + + + End all calls + Завершити всі дзвінки + + + + Do you really want to close this window and terminate all ongoing calls? + Закрити вікно та завершити всі активні дзвінки? + + + + Error + Помилка + + + + Fatal Error + Критична помилка + + + + MainTabBar + + + + Home + Домашній + + + + Conference + Конференція + + + + No active conference + Немає активної конференції + + + + No active call + Немає активного дзвінка + + + + Move up + Перемістити вгору + + + + Move tab up + Перемістити вкладку вгору + + + + Moves the currently selected tab up by one + Переміщує вибрану вкладку вгору на одну позицію + + + + Move down + Перемістити вниз + + + + Move tab down + Перемістити вкладку вниз + + + + Moves the currently selected tab down by one + Переміщує вибрану вкладку вниз на одну позицію + + + + Edit + Редагувати + + + + Edit page + Редагувати сторінку + + + + Edit the currently selected dashboard page + Редагувати вибрану сторінку панелі + + + + Delete + Видалити + + + + Delete page + Видалити сторінку + + + + Delete the currently selected dashboard page + Видалити вибрану сторінку панелі + + + + Call + Дзвінок + + + + Selected tab + Вибрана вкладка + + + + The currently selected tab + Поточна вибрана вкладка + + + + Selected tab options + Параметри вибраної вкладки + + + + The settings of the currently selected tab + Налаштування поточної вибраної вкладки + + + + + Settings + Налаштування + + + + MenuContactInfo + + + Commercial + Робочий + + + + Mobile + Мобільний + + + + Home + Домашній + + + + PageCreationWindow + + + Create new dashboard page + Створити сторінку панелі + + + + Edit dashboard page + Редагувати сторінку панелі + + + + Name + Ім'я + + + + Page name + Назва сторінки + + + + Enter the page name + Введіть назву сторінки + + + + Icon + Значок + + + + Page icon label + Мітка значка сторінки + + + + Page icon selection + Вибір значка сторінки + + + + Select the page icon for the dashboard page + Вибрати значок для сторінки панелі + + + + Currently selected page icon option + Поточний параметр значка сторінки + + + + Cancel + Скасувати + + + + Cancel page modifcation + Скасувати зміну сторінки + + + + Cancel button to exit the page creation/update window + Кнопка скасування для виходу з вікна + + + + Create + Створити + + + + Save + Зберегти + + + + page + сторінка + + + + Confirmation button to create the new dashboard page + Кнопка підтвердження створення нової сторінки + + + + Confirmation button to apply changes to the dashboard page + Кнопка підтвердження застосування змін + + + + ParticipantsList + + + Participants list + Список учасників + + + + List of all the participants of the current chat room + Список усіх учасників поточної кімнати + + + + Chat participant + Учасник чату + + + + Selected chat participant: %1 + Вибраний учасник: %1 + + + + moderator + модератор + + + + it's you + це ви + + + + Kick + Виключити + + + + Make moderator + Зробити модератором + + + + PhoneNumberUtil + + + Anonymous + Анонім + + + + PreferredIdentityEditWindow + + + Phone Number Transmission + Передача номера телефону + + + + Name + Ім'я + + + + Prefix + Префікс + + + + Identity + Ідентифікатор + + + + Enabled + Увімкнено + + + + Automatic + Автоматично + + + + Delete + Видалити + + + + Save + Зберегти + + + + QObject + + + Andorra + Андорра + + + + United Arab Emirates + Об'єднані Арабські Емірати + + + + Afghanistan + Афганістан + + + + Antigua and Barbuda + Антигуа і Барбуда + + + + Anguilla + Ангілья + + + + Albania + Албанія + + + + Armenia + Вірменія + + + + Angola + Ангола + + + + Argentina + Аргентина + + + + American Samoa + Американське Самоа + + + + Austria + Австрія + + + + Australia + Австралія + + + + Aruba + Аруба + + + + Aland Islands + Аландські острови + + + + Azerbaijan + Азербайджан + + + + Bosnia and Herzegovina + Боснія і Герцеговина + + + + Barbados + Барбадос + + + + Bangladesh + Бангладеш + + + + Belgium + Бельгія + + + + Burkina Faso + Буркіна-Фасо + + + + Bulgaria + Болгарія + + + + Bahrain + Бахрейн + + + + Burundi + Бурунді + + + + Benin + Бенін + + + + Saint Barthelemy + Сен-Бартелемі + + + + Bermuda + Бермуди + + + + Brunei + Бруней + + + + Bolivia + Болівія + + + + Bonaire, Saint Eustatius and Saba + Бонайре, Сінт-Естатіус і Саба + + + + Brazil + Бразилія + + + + Bahamas + Багами + + + + Bhutan + Бутан + + + + Botswana + Ботсвана + + + + Belarus + Білорусь + + + + Belize + Беліз + + + + Canada + Канада + + + + Cocos Islands + Кокосові острови + + + + Democratic Republic of the Congo + Демократична Республіка Конго + + + + Central African Republic + Центральноафриканська Республіка + + + + Republic of the Congo + Республіка Конго + + + + Switzerland + Швейцарія + + + + Ivory Coast + Кот-д'Івуар + + + + Cook Islands + Острови Кука + + + + Chile + Чилі + + + + Cameroon + Камерун + + + + China + Китай + + + + Colombia + Колумбія + + + + Costa Rica + Коста-Рика + + + + Cuba + Куба + + + + Cape Verde + Кабо-Верде + + + + Curacao + Кюрасао + + + + Christmas Island + Острів Різдва + + + + Cyprus + Кіпр + + + + Czech Republic + Чехія + + + + Germany + Німеччина + + + + Djibouti + Джибуті + + + + Denmark + Данія + + + + Dominica + Домініка + + + + Dominican Republic + Домініканська Республіка + + + + Algeria + Алжир + + + + Ecuador + Еквадор + + + + Estonia + Естонія + + + + Egypt + Єгипет + + + + Western Sahara + Західна Сахара + + + + Eritrea + Еритрея + + + + Spain + Іспанія + + + + Ethiopia + Ефіопія + + + + Finland + Фінляндія + + + + Fiji + Фіджі + + + + Falkland Islands + Фолклендські острови + + + + Micronesia + Мікронезія + + + + Faroe Islands + Фарерські острови + + + + France + Франція + + + + Gabon + Габон + + + + United Kingdom + Велика Британія + + + + Grenada + Гренада + + + + Georgia + Грузія + + + + French Guiana + Французька Гвіана + + + + Guernsey + Гернсі + + + + Ghana + Гана + + + + Gibraltar + Гібралтар + + + + Greenland + Гренландія + + + + Gambia + Гамбія + + + + Guinea + Гвінея + + + + Guadeloupe + Гваделупа + + + + Equatorial Guinea + Екваторіальна Гвінея + + + + Greece + Греція + + + + Guatemala + Гватемала + + + + Guam + Гуам + + + + Guinea-Bissau + Гвінея-Бісау + + + + Guyana + Гайана + + + + Hong Kong + Гонконг + + + + Heard Island and McDonald Islands + Острови Херд і Макдональд + + + + Honduras + Гондурас + + + + Croatia + Хорватія + + + + Haiti + Гаїті + + + + Hungary + Угорщина + + + + Indonesia + Індонезія + + + + Ireland + Ірландія + + + + Israel + Ізраїль + + + + Isle of Man + Острів Мен + + + + India + Індія + + + + British Indian Ocean Territory + Британська територія в Індійському океані + + + + Iraq + Ірак + + + + Iran + Іран + + + + Iceland + Ісландія + + + + Italy + Італія + + + + Jersey + Джерсі + + + + Jamaica + Ямайка + + + + Jordan + Йорданія + + + + Japan + Японія + + + + Kenya + Кенія + + + + Kyrgyzstan + Киргизстан + + + + Cambodia + Камбоджа + + + + Kiribati + Кірібаті + + + + Comoros + Комори + + + + Saint Kitts and Nevis + Сент-Кітс і Невіс + + + + North Korea + Північна Корея + + + + South Korea + Південна Корея + + + + Kuwait + Кувейт + + + + Cayman Islands + Кайманові острови + + + + Kazakhstan + Казахстан + + + + Laos + Лаос + + + + Lebanon + Ліван + + + + Saint Lucia + Сент-Люсія + + + + Liechtenstein + Ліхтенштейн + + + + Sri Lanka + Шрі-Ланка + + + + Liberia + Ліберія + + + + Lesotho + Лесото + + + + Lithuania + Литва + + + + Luxembourg + Люксембург + + + + Latvia + Латвія + + + + Libya + Лівія + + + + Morocco + Марокко + + + + Monaco + Монако + + + + Moldova + Молдова + + + + Montenegro + Чорногорія + + + + Saint Martin + Сен-Мартен + + + + Madagascar + Мадагаскар + + + + Marshall Islands + Маршаллові острови + + + + Macedonia + Македонія + + + + Mali + Малі + + + + Myanmar + М'янма + + + + Mongolia + Монголія + + + + Macao + Макао + + + + Northern Mariana Islands + Північні Маріанські острови + + + + Martinique + Мартиніка + + + + Mauritania + Мавританія + + + + Montserrat + Монтсеррат + + + + Malta + Мальта + + + + Mauritius + Маврикій + + + + Maldives + Мальдіви + + + + Malawi + Малаві + + + + Mexico + Мексика + + + + Malaysia + Малайзія + + + + Mozambique + Мозамбік + + + + Namibia + Намібія + + + + New Caledonia + Нова Каледонія + + + + Niger + Нігер + + + + Norfolk Island + Острів Норфолк + + + + Nigeria + Нігерія + + + + Nicaragua + Нікарагуа + + + + Netherlands + Нідерланди + + + + Norway + Норвегія + + + + Nepal + Непал + + + + Nauru + Науру + + + + Niue + Ніуе + + + + New Zealand + Нова Зеландія + + + + Oman + Оман + + + + Panama + Панама + + + + Peru + Перу + + + + French Polynesia + Французька Полінезія + + + + Papua New Guinea + Папуа Нова Гвінея + + + + Philippines + Філіппіни + + + + Pakistan + Пакистан + + + + Poland + Польща + + + + Saint Pierre and Miquelon + Сен-П'єр і Мікелон + + + + Pitcairn + Піткерн + + + + Puerto Rico + Пуерто-Рико + + + + Palestinian Territory + Палестинська територія + + + + Portugal + Португалія + + + + Palau + Палау + + + + Paraguay + Парагвай + + + + Qatar + Катар + + + + Reunion + Реюньйон + + + + Romania + Румунія + + + + Serbia + Сербія + + + + Russia + Росія + + + + Rwanda + Руанда + + + + Saudi Arabia + Саудівська Аравія + + + + Solomon Islands + Соломонові острови + + + + Seychelles + Сейшельські острови + + + + Sudan + Судан + + + + Sweden + Швеція + + + + Singapore + Сингапур + + + + Saint Helena + Острів Святої Єлени + + + + Slovenia + Словенія + + + + Svalbard and Jan Mayen + Шпіцберген і Ян-Маєн + + + + Slovakia + Словаччина + + + + Sierra Leone + Сьєрра-Леоне + + + + San Marino + Сан-Маріно + + + + Senegal + Сенегал + + + + Somalia + Сомалі + + + + Suriname + Суринам + + + + South Sudan + Південний Судан + + + + Sao Tome and Principe + Сан-Томе і Принсіпі + + + + El Salvador + Сальвадор + + + + Sint Maarten + Сінт-Мартен + + + + Syria + Сирія + + + + Swaziland + Свазіленд + + + + Turks and Caicos Islands + Острови Теркс і Кайкос + + + + Chad + Чад + + + + Togo + Того + + + + Thailand + Таїланд + + + + Tajikistan + Таджикистан + + + + Tokelau + Токелау + + + + East Timor + Східний Тимор + + + + Turkmenistan + Туркменістан + + + + Tunisia + Туніс + + + + Tonga + Тонга + + + + Turkey + Туреччина + + + + Trinidad and Tobago + Тринідад і Тобаго + + + + Tuvalu + Тувалу + + + + Taiwan + Тайвань + + + + Tanzania + Танзанія + + + + Ukraine + Україна + + + + Uganda + Уганда + + + + United States Minor Outlying Islands + Зовнішні малі острови США + + + + United States + Сполучені Штати + + + + Uruguay + Уругвай + + + + Uzbekistan + Узбекистан + + + + Vatican + Ватикан + + + + Saint Vincent and the Grenadines + Сент-Вінсент і Гренадини + + + + Venezuela + Венесуела + + + + British Virgin Islands + Британські Віргінські острови + + + + U.S. Virgin Islands + Американські Віргінські острови + + + + Vietnam + В'єтнам + + + + Vanuatu + Вануату + + + + Wallis and Futuna + Волліс і Футуна + + + + Samoa + Самоа + + + + Yemen + Ємен + + + + Mayotte + Майотта + + + + South Africa + Південна Африка + + + + Zambia + Замбія + + + + Zimbabwe + Зімбабве + + + + There are %n active call(s). + calls + + Є %n активний дзвінок. + Є %n активних дзвінків. + + + + + ResponseTreeModel + + + + + + Additional Information + Додаткова інформація + + + + RingToneFactory + + + ringTone + 425,0,1000,4000,0 + + + + busyTone + 425,0,480,480,0 + + + + congestionTone + 425,0,240,240,0 + + + + zip + 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 + + + + endTone + 425,0,200,200,200,200,200,200,-1 + + + + SIPAccount + + + 'userUri' is no valid SIP URI: %1 + 'userUri' не є допустимим SIP URI: %1 + + + + 'userUri' is required + 'userUri' обов'язковий + + + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' не є допустимим SIP URI: %1 + + + + 'registrarUri' is required + 'registrarUri' обов'язковий + + + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' містить недопустимий SIP URI: %1 + + + + Failed to create %1: %2 + Не вдалося створити %1: %2 + + + + SIPBuddy + + + %1 is now available + %1 тепер доступний + + + + SIPCall + + + Active call with %1 + Активний дзвінок з %1 + + + + Hang up + Завершити + + + + SIPCallManager + + + %1 is calling + %1 дзвонить + + + + %1 (%2) is calling + %1 (%2) дзвонить + + + + + Accept + Прийняти + + + + Call back + Передзвонити + + + + + Reject + Відхилити + + + + Missed call from %1 + Пропущений дзвінок від %1 + + + + SIPManager + + + New Identity + Новий ідентифікатор + + + + SIPTemplate + + + + Failed to write to %1 + Помилка запису в %1 + + + + Failed to copy %1 to the config space + Помилка копіювання %1 до каталогу конфігурації + + + + Source file %1 does not exist + Вихідний файл %1 не існує + + + + SearchBox + + + Search number + Пошук номера + + + + SearchCategoryItem + + + Search result category filter %1 + Фільтр категорії результатів пошуку %1 + + + + Filter for the individual search result items by category + Фільтр результатів пошуку за категорією + + + + SearchCategoryList + + + Contacts + Контакти + + + + Messages + Повідомлення + + + + Rooms and Teams + Кімнати та команди + + + + Files + Файли + + + + SearchDial + + + Select number + Вибрати номер + + + + Activate search field + Активувати поле пошуку + + + + Number or contact + Номер або контакт + + + + Clear search field + Очистити поле пошуку + + + + Default + За замовчуванням + + + + Auto + Авто + + + + Preferred identity + Бажаний ідентифікатор + + + + Select the preferred identity for outgoing calls + Вибрати бажаний ідентифікатор для вихідних дзвінків + + + + SearchField + + + Search for contacts or room names... + Пошук контактів або кімнат... + + + + Clear search field + Очистити поле пошуку + + + + SearchResultCategory + + + Search result category %1 + Категорія результатів пошуку %1 + + + + Divider for the individual search result items by category + Роздільник результатів пошуку за категорією + + + + SearchResultItem + + + Search result + Результат пошуку + + + + Currently selected search result + Поточний результат пошуку + + + + SearchResultNumberItem + + + Phone number + Номер телефону + + + + Selected favorite number %1 + Вибраний обраний номер %1 + + + + Selected phone number %1 + Вибраний номер телефону %1 + + + + SearchResultPopup + + + Search filter and identity selection + Фільтр пошуку та вибір ідентифікатора + + + + Select search filter to be applied, as well as the outgoing identity + Вибрати фільтр пошуку та вихідний ідентифікатор + + + + Outgoing identity + Вихідний ідентифікатор + + + + Search results + Результати пошуку + + + + All search results will be listed here in their respective categories + Усі результати пошуку будуть перераховані за категоріями + + + + Direct dial + Прямий набір + + + + Call "%1" + Зателефонувати «%1» + + + + Open room "%1" + Відкрити кімнату «%1» + + + + History + Історія + + + + Contacts + Контакти + + + + SettingsPage + + + Settings + Налаштування + + + + Show chat messages as desktop notifications + Показувати повідомлення чату як сповіщення + + + + Enable USB headset driver [%1] + Увімкнути драйвер USB-гарнітури [%1] + + + + not detected + не виявлено + + + + Show dial window on USB headset pick up + Показувати вікно набору при знятті USB-гарнітури + + + + Color scheme + Колірна схема + + + + System default + Системна за замовчуванням + + + + Light + Світла + + + + Dark + Темна + + + + Inverse Accept / Reject buttons + Інвертувати кнопки прийняття/відхилення + + + + Use dark mode tray icon + Використовувати значок трея для темного режиму + + + + Disable USB headset mute state propagation + Вимкнути синхронізацію стану вимкнення USB-гарнітури + + + + Reload contacts from LDAP + Перезавантажити контакти з LDAP + + + + Phoning + Телефонія + + + + Show main window on startup + Показувати головне вікно при запуску + + + + Disable synchronisation with the system mute state + Вимкнути синхронізацію із системним вимкненням звуку + + + + + restart required + потрібен перезапуск + + + + Appearance + Зовнішній вигляд + + + + Use custom window decoration + Використовувати власне оформлення вікна + + + + Theme selection box + Поле вибору теми + + + + Select the UI theme + Вибрати тему інтерфейсу + + + + Currently selected theme option + Поточний параметр теми + + + + Signalling busy when a call is active + Сигналізувати зайнятість при активному дзвінку + + + + Rules for telephone number transmission + Правила передачі номера телефону + + + + Standard preferred identity + Стандартний бажаний ідентифікатор + + + + + Default + За замовчуванням + + + + + Auto + Авто + + + + + Prefererred identity selection + Вибір бажаного ідентифікатора + + + + + Select the preferred identity + Вибрати бажаний ідентифікатор + + + + Currently selected identity option + Поточний параметр ідентифікатора + + + + No preferred identities yet. + Немає бажаних ідентифікаторів. + + + + Currently highlighted preferred identity. Tap to edit. + Поточний бажаний ідентифікатор. Натисніть для редагування. + + + + Standard + Стандарт + + + + Add identity + Додати ідентифікатор + + + + Add a new preferred identity entry + Додати новий бажаний ідентифікатор + + + + Audio settings + Налаштування звуку + + + + Input device + Пристрій вводу + + + + Input device selection + Вибір пристрою вводу + + + + Select the input device to be used + Вибрати пристрій вводу + + + + Currently selected input option + Поточний параметр вводу + + + + Output device + Пристрій виводу + + + + Output device selection + Вибір пристрою виводу + + + + Select the output device to be used + Вибрати пристрій виводу + + + + Currently selected output option + Поточний параметр виводу + + + + Output device for ring tone + Пристрій виводу для мелодії дзвінка + + + + Currently selected ring output option + Поточний параметр виводу дзвінка + + + + Ring tone + Мелодія дзвінка + + + + Prefer USB headset ring sound if available + Використовувати дзвінок USB-гарнітури при наявності + + + + Reset ring tone + Скинути мелодію дзвінка + + + + Reset the ring tone to its default option + Відновити стандартну мелодію дзвінка + + + + Pick ring tone + Вибрати мелодію + + + + Select the ring tone you want to use for incoming calls + Вибрати мелодію для вхідних дзвінків + + + + + Currently set to: + Поточне значення: + + + + Ring tone volume + Гучність мелодії дзвінка + + + + + Adjust %1 + Налаштувати %1 + + + + %1 % + %1 % + + + + Pause between ring tones [s] + Пауза між дзвінками [с] + + + + Debugging + Налагодження + + + + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. + Натисніть для запуску сеансу налагодження. Застосунок перезапуститься і почне записувати додаткову інформацію. Сеанс обмежено 5 хвилинами. + + + + Start debug run (restart app) + Почати налагодження (перезапустити застосунок) + + + + Download debug information + Завантажити інформацію налагодження + + + + ShortcutsWindow + + + Shortcuts + Гарячі клавіші + + + + Shortcut key: %1 + Клавіша швидкого доступу: %1 + + + + Local shortcuts (work only when app is focused) + Локальні сполучення (працюють лише при фокусі на застосунку) + + + + Local shortcuts + Локальні сполучення + + + + Ctrl + F + Ctrl + F + + + + Activates the global search field + Активує глобальне поле пошуку + + + + F11 + F11 + + + + Toggles between fullsceen and normal window mode + Перемикає між повноекранним і звичайним режимом + + + + Ctrl + Shift + M + Ctrl + Shift + M + + + + Toggles audio mute + Вмикає/вимикає звук + + + + Global shortcuts (work from anywhere) + Глобальні сполучення (працюють будь-де) + + + + Global shortcuts + Глобальні сполучення + + + + SipTemplateWizard + + + Initial configuration + Початкова конфігурація + + + + Error: %1 + Помилка: %1 + + + + SIP wizard notification + Сповіщення майстра SIP + + + + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. + GOnnect не може знайти конфігурацію SIP. Виберіть шаблон нижче та за потреби змініть файл. + + + + Please pick: + Будь ласка, виберіть: + + + + Select SIP template + Вибрати шаблон SIP + + + + Select the SIP template to be used + Вибрати шаблон SIP + + + + Currently selected SIP template + Поточний шаблон SIP + + + + Next + Далі + + + + Continue setup + Продовжити налаштування + + + + Confirmation button to continue the setup + Кнопка підтвердження для продовження + + + + Template field list + Список полів шаблону + + + + List of all the available SIP template options + Список усіх доступних параметрів шаблону SIP + + + + SIP template option + Параметр шаблону SIP + + + + Currently selected SIP template option + Поточний параметр шаблону SIP + + + + Display name of the SIP template option + Відображуване ім'я параметра шаблону SIP + + + + Description of the SIP template option + Опис параметра шаблону SIP + + + + Back + Назад + + + + Back button to return to the template selection menu + Кнопка повернення до меню вибору шаблону + + + + + Finish + Завершити + + + + Confirmation button to apply the changes to the SIP template + Кнопка підтвердження застосування змін + + + + Successful configuration file creation + Файл конфігурації успішно створено + + + + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. + Файл конфігурації створено. Перевірте, чи потрібні зміни, та перезапустіть GOnnect. + + + + The configuration has been saved to: + Конфігурацію збережено до: + + + + Copy to clipboard + Скопіювати до буфера обміну + + + + Copy the full path of the configuration file to the clipboard + Скопіювати повний шлях до файлу конфігурації + + + + Finish wizard + Завершити майстер + + + + Finish the SIP configuration wizard + Завершити майстер налаштування SIP + + + + StateManager + + + Show dial window and focus search field + Показати вікно набору та перейти до поля пошуку + + + + End all calls + Завершити всі дзвінки + + + + Redial last outgoing call + Повторити останній вихідний дзвінок + + + + Toggle hold + Перемкнути утримання + + + + Phone calls are active + Дзвінки активні + + + + SystemTrayMenu + + + + Dial... + Набір номера... + + + + Not registered... + Не зареєстровано... + + + + Settings + Налаштування + + + + About + Про програму + + + + Quit + Вийти + + + + End conference + Завершити конференцію + + + + Call with %1 has ended + Дзвінок з %1 завершено + + + + Hang up call with %1 + Завершити дзвінок з %1 + + + + Accept call with %1 + Прийняти дзвінок від %1 + + + + TemplateFieldFile + + + File path + Шлях до файлу + + + + Enter the file path for %1 + Введіть шлях до файлу для %1 + + + + Choose... + Вибрати... + + + + Open file picker + Відкрити вибір файлу + + + + Select the file that should be used for %1 + Вибрати файл для %1 + + + + Certificate files (%1) + Файли сертифікатів (%1) + + + + TemplateFieldText + + + Text input + Текстовий ввід + + + + Enter the desired value for %1 + Введіть потрібне значення для %1 + + + + Toggler + + + Failed to toggle the state of %1. + Не вдалося перемкнути стан %1. + + + + TogglerList + + + Toggler list + Список перемикачів + + + + List of items that can be toggled + Список перемикуваних елементів + + + + Toggle %1 + Перемкнути %1 + + + + VerticalLevelMeter + + + Level meter + Індикатор рівня + + + + VideoDeviceMenu + + + Virtual background + Віртуальний фон + + + + ViewHelper + + + Save File + Зберегти файл + + + + %n minute(s) + + %n хвилина + %n хвилин + + + + + 1 hour and %n minute(s) + + 1 година і %n хвилина + 1 година і %n хвилин + + + + + %n hour(s) + + %n година + %n годин + + + + + Audio Files (%1) + Аудіофайли (%1) + + + + VolumePopup + + + Adjust the volume + Налаштувати гучність + + + + Unmute + Увімкнути звук + + + + Mute + Вимкнути звук + + + + Open audio settings + Відкрити налаштування звуку + + + + WidgetSelectionWindow + + + Add widget + Додати віджет + + + + Widget + Віджет + + + + Widget selection header + Заголовок вибору віджета + + + + Date Events + Події + + + + List of upcoming appointments + Список майбутніх зустрічей + + + + Favorites + Обране + + + + Quick dial for your favorite contacts and conferences + Швидкий набір обраних контактів + + + + History + Історія + + + + Web View + Веб-перегляд + + + + A web-based content display + Відображення веб-вмісту + + + + Widget selection + Вибір віджета + + + + Select the widget that should be added to the current dashboard page + Вибрати віджет для додавання на поточну сторінку + + + + Currently selected widget option + Поточний параметр віджета + + + + Accept all certificates + Прийняти всі сертифікати + + + + Confirm widget selection + Підтвердити вибір віджета + + + + Confirmation button to create and add the selected widget to the current dashboard + Кнопка створення та додавання віджета + + + + Searchable call and conference history + Історія дзвінків та конференцій + + + + Title + Назва + + + + URL + URL + + + + URL (dark mode) + URL (темний режим) + + + + Settings text input + Поле налаштувань + + + + Input for widget setting %1 + Ввід для параметра віджета %1 + + + + Settings checkbox + Прапорець налаштувань + + + + Checkbox for widget setting %1 + Прапорець для параметра віджета %1 + + + + Widget setting %1 + Параметр віджета %1 + + + + Cancel + Скасувати + + + + Cancel widget selection + Скасувати вибір віджета + + + + Cancel button to exit widget selection selection without changes + Кнопка скасування без змін + + + + Add + Додати + + + + WindowHeader + + + GOnnect window header + Заголовок вікна GOnnect + + + + Minimize + Згорнути + + + + Maximize + Розгорнути + + + + Close GOnnect window + Закрити вікно GOnnect + + + \ No newline at end of file From f1c609da8dc0214e2f0c4909ef7b645b08352c11 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 10 Mar 2026 13:38:46 +0100 Subject: [PATCH 008/122] fix: code review loop (#374) * Fix mute/unmute loop * Ask for SIP proxy auth * Re enable litra support * Enhance USB device robustness --- CMakeLists.txt | 2 +- README.md | 9 +++ conanfile.py | 2 +- .../conan/recipes/openldap/all/conandata.yml | 8 +-- resources/conan/recipes/openldap/config.yml | 2 +- resources/flatpak/de.gonicus.gonnect.yml | 4 +- src/CMakeLists.txt | 4 +- src/GlobalCallState.cpp | 1 + src/contacts/Contact.cpp | 2 + src/dbus/portal/GlobalShortcutPortal.cpp | 4 +- src/main.cpp | 4 ++ src/media/AudioManager.cpp | 6 ++ src/media/AudioManager.h | 1 + src/sip/IMHandler.cpp | 2 +- src/sip/SIPAccount.cpp | 13 ++-- src/sip/SIPCall.cpp | 8 ++- src/sip/SIPCallManager.cpp | 2 +- src/ui/HistoryModel.cpp | 2 +- src/ui/ViewHelper.cpp | 2 +- .../controls/FavoriteListItemBig.qml | 2 +- src/usb/HeadsetDevice.cpp | 12 ++-- src/usb/HeadsetDevice.h | 2 + src/usb/HeadsetDeviceProxy.cpp | 1 + src/usb/USBDevices.cpp | 44 ++++++++++++- src/usb/USBDevices.h | 3 + src/usb/busylight/BusylightDeviceManager.cpp | 12 +++- src/usb/busylight/BusylightDeviceManager.h | 4 ++ src/usb/busylight/IBusylightDevice.cpp | 7 +++ src/usb/busylight/IBusylightDevice.h | 6 +- src/usb/busylight/LitraBeamLX.cpp | 63 ++++++++++++++----- src/usb/busylight/LitraBeamLX.h | 6 ++ src/usb/busylight/LitraGlow.cpp | 11 ++-- src/usb/busylight/LitraGlow.h | 2 + 33 files changed, 195 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ced10d5..a733a9a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.0.9 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/README.md b/README.md index 9a30d80e..40d9d5d7 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,15 @@ udevadm trigger | ------------ | ------------------ | | kuando | Busylight UC Omega | | Luxafor | Flag | +| Logitech | Litra Beam LX | + + +# Automatic video light switching + +| Manufacturer | Model | +| ------------ | ------------------ | +| Logitech | Litra Beam LX | +| Logitech | Litra Glow | # Installing _GOnnect_ diff --git a/conanfile.py b/conanfile.py index c5b0ff01..9aed2237 100644 --- a/conanfile.py +++ b/conanfile.py @@ -62,7 +62,7 @@ def package(self): def requirements(self): self.requires("hidapi/0.15.0") self.requires("pjproject/2.16") - self.requires("openldap/2.6.12") + self.requires("openldap/2.6.13") self.requires("libical/3.0.20") self.requires("vcard/cci.20250408") self.requires("logfault/0.8.1-1") diff --git a/resources/conan/recipes/openldap/all/conandata.yml b/resources/conan/recipes/openldap/all/conandata.yml index d8192208..b5ee2a22 100644 --- a/resources/conan/recipes/openldap/all/conandata.yml +++ b/resources/conan/recipes/openldap/all/conandata.yml @@ -1,7 +1,7 @@ sources: - 2.6.12: - url: https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.12.tgz - sha256: 1716ad779e85d743694c3e3b05277fb71b6a5eadca43c7a958aa62683b22208e + 2.6.13: + url: https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.13.tgz + sha256: d693b49517a42efb85a1a364a310aed16a53d428d1b46c0d31ef3fba78fcb656 patches: - 2.6.12: + 2.6.13: - patch_file: patches/0001-Support-CMake-build-with-Visual-Studio.patch diff --git a/resources/conan/recipes/openldap/config.yml b/resources/conan/recipes/openldap/config.yml index 2b538c3b..5099a937 100644 --- a/resources/conan/recipes/openldap/config.yml +++ b/resources/conan/recipes/openldap/config.yml @@ -1,5 +1,5 @@ versions: - 2.6.12: + 2.6.13: folder: all updater: source: anitya diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index 6b3bfb80..cbd70f79 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -163,8 +163,8 @@ modules: - /share/man sources: - type: archive - url: https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.12.tgz - sha256: 1716ad779e85d743694c3e3b05277fb71b6a5eadca43c7a958aa62683b22208e + url: https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.6.13.tgz + sha256: d693b49517a42efb85a1a364a310aed16a53d428d1b46c0d31ef3fba78fcb656 x-checker-data: type: anitya stable-only: true diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4d7b330..afd00682 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -702,8 +702,8 @@ qt_add_qml_module(gonnect usb/busylight/KuandoOmega.cpp usb/busylight/LuxaforFlag.h usb/busylight/LuxaforFlag.cpp -# usb/busylight/LitraBeamLX.h -# usb/busylight/LitraBeamLX.cpp + usb/busylight/LitraBeamLX.h + usb/busylight/LitraBeamLX.cpp usb/busylight/LitraGlow.h usb/busylight/LitraGlow.cpp ) diff --git a/src/GlobalCallState.cpp b/src/GlobalCallState.cpp index 10c77a07..6427d36c 100644 --- a/src/GlobalCallState.cpp +++ b/src/GlobalCallState.cpp @@ -213,6 +213,7 @@ void GlobalCallState::onCallInForegroundChanged() } if (m_callInForeground) { + m_foregroundCallContext = new QObject(this); connect(m_callInForeground, &QObject::destroyed, m_foregroundCallContext, [this]() { setProperty("callInForeground", QVariant::fromValue(nullptr)); }); } diff --git a/src/contacts/Contact.cpp b/src/contacts/Contact.cpp index 9cbd7cf1..43aa3cc6 100644 --- a/src/contacts/Contact.cpp +++ b/src/contacts/Contact.cpp @@ -56,6 +56,7 @@ Contact::Contact(const Contact &other) : QObject{ other.parent() } m_mail = other.m_mail; m_lastModified = other.m_lastModified; m_sipStatusSubscriptable = other.m_sipStatusSubscriptable; + m_hasAvatar = other.m_hasAvatar; init(); } @@ -72,6 +73,7 @@ Contact &Contact::operator=(const Contact &other) m_mail = other.m_mail; m_lastModified = other.m_lastModified; m_sipStatusSubscriptable = other.m_sipStatusSubscriptable; + m_hasAvatar = other.m_hasAvatar; init(); return *this; } diff --git a/src/dbus/portal/GlobalShortcutPortal.cpp b/src/dbus/portal/GlobalShortcutPortal.cpp index f3077990..316951b0 100644 --- a/src/dbus/portal/GlobalShortcutPortal.cpp +++ b/src/dbus/portal/GlobalShortcutPortal.cpp @@ -44,9 +44,9 @@ void GlobalShortcutPortal::initialize() << response.value("error").toString(); break; } - }); - Q_EMIT initialized(); + Q_EMIT initialized(); + }); } void GlobalShortcutPortal::registerShortcuts() diff --git a/src/main.cpp b/src/main.cpp index 0ea314d6..0657c72c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,10 @@ int main(int argc, char *argv[]) engine.loadFromModule("base", "Main"); const auto &objs = engine.rootObjects(); + if (objs.isEmpty()) { + qFatal() << "no QML windows registered"; + } + const auto &itemObjs = objs.first(); QQuickWindow *mainWindow = itemObjs->findChild("gonnectWindow"); diff --git a/src/media/AudioManager.cpp b/src/media/AudioManager.cpp index 735efc08..d9eb1da2 100644 --- a/src/media/AudioManager.cpp +++ b/src/media/AudioManager.cpp @@ -52,6 +52,7 @@ AudioManager::AudioManager(QObject *parent) : QObject(parent) if (!noSyncSystemMute()) { connect(this, &AudioManager::isAudioCaptureMutedChanged, this, [this]() { if (m_captureAudioPort) { + m_paCallbackSuppress++; paMuteInputByName(m_captureAudioPort->getSystemDeviceID(), m_isAudioCaptureMuted); qCInfo(lcAudioManager) << "Sent mute state" << m_isAudioCaptureMuted << "to system"; } else { @@ -140,6 +141,11 @@ void AudioManager::paInputMuteStateCallback(pa_context *, const pa_source_info * { AudioManager &instance = AudioManager::instance(); if (!end && instance.m_captureAudioPort->getSystemDeviceID() == QString(source->name)) { + if (instance.m_paCallbackSuppress > 0) { + instance.m_paCallbackSuppress--; + return; + } + auto &gms = GlobalMuteState::instance(); if (source->mute != gms.isMuted()) { gms.toggleMute(); diff --git a/src/media/AudioManager.h b/src/media/AudioManager.h index f4ac548d..921d082a 100644 --- a/src/media/AudioManager.h +++ b/src/media/AudioManager.h @@ -152,6 +152,7 @@ private Q_SLOTS: pa_mainloop *m_paMainloop = nullptr; QTimer m_paMainloopTimer; pa_context *m_paContext = nullptr; + int m_paCallbackSuppress = 0; #endif }; diff --git a/src/sip/IMHandler.cpp b/src/sip/IMHandler.cpp index 703eba2a..4a8f2ee6 100644 --- a/src/sip/IMHandler.cpp +++ b/src/sip/IMHandler.cpp @@ -138,7 +138,7 @@ bool IMHandler::requestMeeting(bool hangup, QPointer callHistor bool IMHandler::sendCapabilities() { - if (--m_capabilitySendingTries == 0) { + if (m_capabilitySendingTries-- == 0) { return false; } diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index d6980784..a3c08f16 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -164,7 +164,7 @@ void SIPAccount::initialize() } else if (m_transportNet == TRANSPORT_NET::IPv6) { m_accountConfig.sipConfig.ipv6Use = PJSUA_IPV6_ENABLED_USE_IPV6_ONLY; m_accountConfig.mediaConfig.ipv6Use = PJSUA_IPV6_ENABLED_USE_IPV6_ONLY; - } else if (m_transportNet == TRANSPORT_NET::AUTO || m_transportNet == TRANSPORT_NET::IPv6) { + } else if (m_transportNet == TRANSPORT_NET::AUTO) { m_accountConfig.sipConfig.ipv6Use = PJSUA_IPV6_ENABLED_PREFER_IPV6; m_accountConfig.mediaConfig.ipv6Use = PJSUA_IPV6_ENABLED_PREFER_IPV6; } @@ -335,8 +335,6 @@ void SIPAccount::initialize() m_settings.value("sipOutboundRegId").toString().toStdString(); } - m_accountConfig.natConfig.contactRewriteUse = 0; - m_accountConfig.mediaConfig.rtcpXrEnabled = m_settings.value("rtcpXrEnabled", false).toBool(); m_accountConfig.mediaConfig.rtcpMuxEnabled = m_settings.value("rtcpMuxEnabled", false).toBool(); @@ -524,12 +522,13 @@ SIPBuddyState::STATUS SIPAccount::buddyStatus(const QString &var) // We don't have a buddy yet - try to subscribe auto buddy = new SIPBuddy(this, sipUrl); - qCInfo(lcSIPAccount) << "subscribing to buddy" << buddy->uri(); + QString uri = buddy->uri(); + qCInfo(lcSIPAccount) << "subscribing to buddy" << uri; if (buddy->initialize()) { m_buddies.push_back(buddy); - connect(buddy, &SIPBuddy::destroyed, this, [buddy, this]() { - qCCritical(lcSIPAccount) << "removing buddy" << buddy->uri(); + connect(buddy, &SIPBuddy::destroyed, this, [buddy, uri, this]() { + qCCritical(lcSIPAccount) << "removing buddy" << uri; m_buddies.removeAll(buddy); }); } else { @@ -739,7 +738,7 @@ void SIPAccount::onRegState(pj::OnRegStateParam &prm) Q_EMIT isRegisteredChanged(); } - if (prm.code == PJSIP_SC_UNAUTHORIZED) { + if (prm.code == PJSIP_SC_UNAUTHORIZED || prm.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED) { Q_EMIT authorizationFailed(); } diff --git a/src/sip/SIPCall.cpp b/src/sip/SIPCall.cpp index dcc13046..9b214e7c 100644 --- a/src/sip/SIPCall.cpp +++ b/src/sip/SIPCall.cpp @@ -413,8 +413,8 @@ void SIPCall::onCallRxText(pj::OnCallRxTextParam &prm) } // Detect if we're missing a sequence - if (m_lastRttSequence >= 0 && prm.seq > m_lastRttSequence) { - quint16 lostSequences = prm.seq - m_lastRttSequence; + if (m_lastRttSequence >= 0 && prm.seq > m_lastRttSequence + 1) { + quint16 lostSequences = prm.seq - m_lastRttSequence - 1; m_currentRttBubble += QString("�").repeated(lostSequences); } m_lastRttSequence = prm.seq; @@ -787,6 +787,10 @@ void SIPCall::createOngoingCallNotification() void SIPCall::addMetadata(const QString &data) { ResponseLoader loader(data); + + qDeleteAll(m_metadata); + m_metadata.clear(); + m_metadata = loader.loadResponse(); if (!m_metadata.empty()) { m_hasMetadata = true; diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp index afd64550..9db7cf53 100644 --- a/src/sip/SIPCallManager.cpp +++ b/src/sip/SIPCallManager.cpp @@ -540,7 +540,7 @@ void SIPCallManager::transferCall(const QString &fromAccountId, int fromCallId, auto toCall = findCall(toAccountId, toCallId); if (!toCall) { qCCritical(lcSIPCallManager) - << "Cannot find call" << toAccountId << "in account" << toCallId; + << "Cannot find call" << toCallId << "in account" << toAccountId; return; } diff --git a/src/ui/HistoryModel.cpp b/src/ui/HistoryModel.cpp index 7c18b48b..1466d335 100644 --- a/src/ui/HistoryModel.cpp +++ b/src/ui/HistoryModel.cpp @@ -154,7 +154,7 @@ QVariant HistoryModel::data(const QModelIndex &index, int role) const if (!str.isEmpty()) { str += ", "; } - str = contactInfo.countries.join(", "); + str += contactInfo.countries.join(", "); } return str; } diff --git a/src/ui/ViewHelper.cpp b/src/ui/ViewHelper.cpp index 0652c9e2..4a4eefa7 100644 --- a/src/ui/ViewHelper.cpp +++ b/src/ui/ViewHelper.cpp @@ -237,7 +237,7 @@ void ViewHelper::toggleFavorite(const QString &phoneNumber, QString ViewHelper::initials(const QString &name) const { - const auto splitted = name.split(QChar(' ')); + const auto splitted = name.simplified().split(QChar(' ')); if (name.length() == 0 || splitted.length() == 0) { return ""; } else if (splitted.length() == 1) { diff --git a/src/ui/components/controls/FavoriteListItemBig.qml b/src/ui/components/controls/FavoriteListItemBig.qml index 56e3a83d..a380e1e7 100644 --- a/src/ui/components/controls/FavoriteListItemBig.qml +++ b/src/ui/components/controls/FavoriteListItemBig.qml @@ -46,7 +46,7 @@ Item { Accessible.role: Accessible.ListItem Accessible.name: qsTr("Favorite contact") - Accessible.description: qsTr("Selected favorite %1: %2").arg(delg.name).arg(delg.isJitsiUrl ? qsTr("tap to start meeting %1").arg(dlg.phoneNumber) : qsTr("tap to call %1").arg(delg.phoneNumber)) + Accessible.description: qsTr("Selected favorite %1: %2").arg(delg.name).arg(delg.isJitsiUrl ? qsTr("tap to start meeting %1").arg(delg.phoneNumber) : qsTr("tap to call %1").arg(delg.phoneNumber)) Accessible.focusable: true Accessible.onPressAction: () => delg.startMeetingOrCall() diff --git a/src/usb/HeadsetDevice.cpp b/src/usb/HeadsetDevice.cpp index 4a7adcd1..4ed6e0b0 100644 --- a/src/usb/HeadsetDevice.cpp +++ b/src/usb/HeadsetDevice.cpp @@ -221,12 +221,6 @@ void HeadsetDevice::setMute(bool flag) return; } - if (m_ignoreNextMuteUpdate) { - m_ignoreNextMuteUpdate = false; - qCDebug(lcHeadset).noquote().nospace() << "Ignoring setMute(" << flag << ") call"; - return; - } - m_muted = flag; if (m_displaySupported && m_teamsUsageMapping.contains(UsageId::Teams_IconsControl)) { @@ -365,6 +359,11 @@ void HeadsetDevice::setTeamsUsageMapping(QHash teamsUsageMappi void HeadsetDevice::processEvents() { + if (m_ignoreNextMuteUpdate) { + m_ignoreNextMuteUpdate = false; + return; + } + unsigned char data[64]; memset(data, 0, sizeof(data)); @@ -409,6 +408,7 @@ void HeadsetDevice::processEvents() qCDebug(lcHeadset) << " Muted changed to" << m_muted; setMute(m_muted); + m_ignoreNextMuteUpdate = true; } } diff --git a/src/usb/HeadsetDevice.h b/src/usb/HeadsetDevice.h index c0c482e0..9da2c07a 100644 --- a/src/usb/HeadsetDevice.h +++ b/src/usb/HeadsetDevice.h @@ -43,6 +43,8 @@ class HeadsetDevice final : public IHeadsetDevice void setMute(bool flag); bool getMute() const { return m_muted; } + QString path() const { return m_path; } + void setIdle(); void syncDateAndTime(); diff --git a/src/usb/HeadsetDeviceProxy.cpp b/src/usb/HeadsetDeviceProxy.cpp index e0e7dd7f..a39cbf38 100644 --- a/src/usb/HeadsetDeviceProxy.cpp +++ b/src/usb/HeadsetDeviceProxy.cpp @@ -150,6 +150,7 @@ bool HeadsetDeviceProxy::refreshDevice() { auto devs = USBDevices::instance().headsetDevices(); if (m_device) { + disconnect(m_device, nullptr, this, nullptr); m_device = nullptr; } diff --git a/src/usb/USBDevices.cpp b/src/usb/USBDevices.cpp index 42d3cf84..4c56d274 100644 --- a/src/usb/USBDevices.cpp +++ b/src/usb/USBDevices.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "IBusylightDevice.h" #include "ReportDescriptorParser.h" #include "ReportDescriptorEnums.h" #include "ReportDescriptorStructs.h" @@ -147,16 +148,48 @@ void USBDevices::refresh() QString lastPath; auto &busylightDeviceManager = BusylightDeviceManager::instance(); - clearDevices(); + // Collect devices previously in use + QHash previousPaths; + for (auto hd : std::as_const(m_headsetDevices)) { + previousPaths.insert(hd->path(), USBDeviceType::Headset); + } - struct hid_device_info *devs, *deviceInfo; + auto bdevs = busylightDeviceManager.devices(); + for (auto bd : std::as_const(bdevs)) { + previousPaths.insert(bd->path(), USBDeviceType::Busylight); + } + struct hid_device_info *devs, *deviceInfo; deviceInfo = devs = hid_enumerate(0, 0); + // Collect current paths + QSet currentPaths; + for (auto di = devs; di; di = di->next) { + currentPaths.insert(di->path); + } + + // Remove headset devices that are gone + QMutableListIterator it(m_headsetDevices); + while (it.hasNext()) { + auto hd = it.next(); + if (!currentPaths.contains(hd->path())) { + delete hd; + it.remove(); + } + } + + // Remove busylights that are gone + for (auto bd : std::as_const(bdevs)) { + if (!currentPaths.contains(bd->path())) { + busylightDeviceManager.removeDevice(bd); + } + } + + // Initialize new devices for (; deviceInfo; deviceInfo = deviceInfo->next) { QString path = deviceInfo->path; - if (path == lastPath) { + if (previousPaths.contains(path) || path == lastPath) { continue; } @@ -185,6 +218,11 @@ void USBDevices::clearDevices() HeadsetDevice *USBDevices::parseReportDescriptor(const hid_device_info *deviceInfo) { + // Only look for telephony devices + if (deviceInfo->usage_page != 0x0B) { + return nullptr; + } + unsigned char descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; hid_device *device = hid_open_path(deviceInfo->path); diff --git a/src/usb/USBDevices.h b/src/usb/USBDevices.h index 75814ebc..1ee06494 100644 --- a/src/usb/USBDevices.h +++ b/src/usb/USBDevices.h @@ -28,6 +28,9 @@ class USBDevices : public QObject return *_instance; } + enum USBDeviceType { Headset, Busylight }; + Q_ENUM(USBDeviceType) + void initialize(); void shutdown(); diff --git a/src/usb/busylight/BusylightDeviceManager.cpp b/src/usb/busylight/BusylightDeviceManager.cpp index 7be10331..b5ee1e3e 100644 --- a/src/usb/busylight/BusylightDeviceManager.cpp +++ b/src/usb/busylight/BusylightDeviceManager.cpp @@ -1,6 +1,6 @@ #include "BusylightDeviceManager.h" -// #include "LitraBeamLX.h" +#include "LitraBeamLX.h" #include "LitraGlow.h" #include "LuxaforFlag.h" #include "KuandoOmega.h" @@ -34,8 +34,8 @@ bool BusylightDeviceManager::createBusylightDevice(const hid_device_info &device } else if (vendor == 0x046D && product == 0xC900) { device = new LitraGlow(deviceInfo, this); - // } else if (vendor == 0x046D && product == 0xC903) { - // device = new LitraBeamLX(deviceInfo, this); + } else if (vendor == 0x046D && product == 0xC903) { + device = new LitraBeamLX(deviceInfo, this); } if (device) { @@ -45,6 +45,12 @@ bool BusylightDeviceManager::createBusylightDevice(const hid_device_info &device return device; } +void BusylightDeviceManager::removeDevice(IBusylightDevice *dev) +{ + m_devices.removeAll(dev); + delete dev; +} + void BusylightDeviceManager::clearDevices() { qDeleteAll(m_devices); diff --git a/src/usb/busylight/BusylightDeviceManager.h b/src/usb/busylight/BusylightDeviceManager.h index fb6d5156..0edd8698 100644 --- a/src/usb/busylight/BusylightDeviceManager.h +++ b/src/usb/busylight/BusylightDeviceManager.h @@ -21,6 +21,8 @@ class BusylightDeviceManager : public QObject } bool createBusylightDevice(const struct hid_device_info &deviceInfo); + void removeDevice(IBusylightDevice *dev); + void clearDevices(); void switchOn(QColor color) const; @@ -32,6 +34,8 @@ class BusylightDeviceManager : public QObject void switchStreamlightOn() const; void switchStreamlightOff() const; + QList devices() const { return m_devices; } + private Q_SLOTS: void updateBusylightState(); diff --git a/src/usb/busylight/IBusylightDevice.cpp b/src/usb/busylight/IBusylightDevice.cpp index 60b35992..7d707a8e 100644 --- a/src/usb/busylight/IBusylightDevice.cpp +++ b/src/usb/busylight/IBusylightDevice.cpp @@ -10,9 +10,14 @@ IBusylightDevice::IBusylightDevice(const hid_device_info &deviceInfo, QObject *p m_path = deviceInfo.path; m_blinkTimer.setInterval(750); + m_blinkTimer.setSingleShot(true); m_blinkTimer.callOnTimeout(this, [this]() { m_isOn = !m_isOn; send(m_isOn); + + if (m_isRunning) { + m_blinkTimer.start(); + } }); } @@ -64,12 +69,14 @@ void IBusylightDevice::switchOff() void IBusylightDevice::startBlinking(QColor color) { m_color = color; + m_isRunning = true; m_blinkTimer.start(); } void IBusylightDevice::stopBlinking() { if (m_blinkTimer.isActive()) { + m_isRunning = false; m_blinkTimer.stop(); switchOff(); } diff --git a/src/usb/busylight/IBusylightDevice.h b/src/usb/busylight/IBusylightDevice.h index 5bc4d403..da4283f0 100644 --- a/src/usb/busylight/IBusylightDevice.h +++ b/src/usb/busylight/IBusylightDevice.h @@ -19,15 +19,18 @@ class IBusylightDevice : public QObject explicit IBusylightDevice(const hid_device_info &deviceInfo, QObject *parent = nullptr); virtual ~IBusylightDevice(); + virtual void switchStreamlight(bool on) { Q_UNUSED(on); }; + bool open(); void close(); void switchOn(QColor color); void switchOff(); - virtual void switchStreamlight(bool on) { Q_UNUSED(on); }; void startBlinking(QColor color); void stopBlinking(); + QString path() const { return m_path; } + protected: virtual void send(bool on) = 0; hid_device *m_device = nullptr; @@ -37,4 +40,5 @@ class IBusylightDevice : public QObject QTimer m_blinkTimer; QString m_path; bool m_isOn = false; + bool m_isRunning = false; }; diff --git a/src/usb/busylight/LitraBeamLX.cpp b/src/usb/busylight/LitraBeamLX.cpp index 4f157f70..0c4164e7 100644 --- a/src/usb/busylight/LitraBeamLX.cpp +++ b/src/usb/busylight/LitraBeamLX.cpp @@ -24,6 +24,30 @@ QSet LitraBeamLX::supportedCommands() return _commands; } +bool LitraBeamLX::hidppTransaction(unsigned char *buf, size_t len) +{ + // TODO: Make a hid++ support library and do this in a non hardcoded way + // because index and feature versions are not guaranteed to stay this + // way. + + if (hid_write(m_device, buf, len) < 0) { + return false; + } + + unsigned char resp[20]; + int n = hid_read_timeout(m_device, resp, sizeof(resp), 200); + if (n <= 0) { + return false; + } + + // Check for device error report + if (n >= 4 && resp[0] == 0x11 && resp[2] == 0xFF) { + return false; + } + + return true; +} + void LitraBeamLX::switchStreamlight(bool on) { if (!m_device) { @@ -31,11 +55,13 @@ void LitraBeamLX::switchStreamlight(bool on) return; } - unsigned char buf[20]; + // Don't send the same state again + if (m_state == on) { + return; + } + m_state = on; - // TODO: Make a hid++ support library and do this in a non hardcoded way - // because index and feature versions are not guaranteed to stay this - // way. + unsigned char buf[20]; // Switch on or off memset(buf, 0, sizeof(buf)); @@ -45,7 +71,7 @@ void LitraBeamLX::switchStreamlight(bool on) buf[3] = LX_SET_ILLUMINATION | LX_SOFTWARE_ID; buf[4] = on ? 1 : 0; - hid_write(m_device, buf, sizeof(buf)); + hidppTransaction(buf, sizeof(buf)); } void LitraBeamLX::send(bool on) @@ -55,6 +81,12 @@ void LitraBeamLX::send(bool on) return; } + // Don't send the same state again + if (m_blinkState == on) { + return; + } + m_blinkState = on; + unsigned char buf[20]; // TODO: Make a hid++ support library and do this in a non hardcoded way @@ -69,9 +101,9 @@ void LitraBeamLX::send(bool on) buf[3] = LX_SET_ILLUMINATION_RGB | LX_SOFTWARE_ID; buf[4] = on ? 1 : 0; - hid_write(m_device, buf, sizeof(buf)); - - hid_read_timeout(m_device, buf, 20, 1000); + if (!hidppTransaction(buf, sizeof(buf))) { + return; + } if (on) { // rgb values @@ -87,8 +119,9 @@ void LitraBeamLX::send(bool on) buf[7 + i * 4] = m_color.blue(); } - hid_write(m_device, buf, sizeof(buf)); - hid_read_timeout(m_device, buf, 20, 1000); + if (!hidppTransaction(buf, sizeof(buf))) { + return; + } memset(buf, 0, sizeof(buf)); buf[0] = LX_REPORT_ID; @@ -102,8 +135,9 @@ void LitraBeamLX::send(bool on) buf[7 + i * 4] = m_color.blue(); } - hid_write(m_device, buf, sizeof(buf)); - hid_read_timeout(m_device, buf, 20, 1000); + if (!hidppTransaction(buf, sizeof(buf))) { + return; + } // Flush memset(buf, 0, sizeof(buf)); @@ -113,7 +147,8 @@ void LitraBeamLX::send(bool on) buf[3] = LX_FRAME_END | LX_SOFTWARE_ID; buf[4] = 1; // Persist - hid_write(m_device, buf, sizeof(buf)); - hid_read_timeout(m_device, buf, 20, 1000); + if (!hidppTransaction(buf, sizeof(buf))) { + return; + } } } diff --git a/src/usb/busylight/LitraBeamLX.h b/src/usb/busylight/LitraBeamLX.h index bf5858cc..68aefc97 100644 --- a/src/usb/busylight/LitraBeamLX.h +++ b/src/usb/busylight/LitraBeamLX.h @@ -16,6 +16,12 @@ class LitraBeamLX : public IBusylightDevice QSet supportedCommands() override; void switchStreamlight(bool on) override; +private: + bool hidppTransaction(unsigned char *buf, size_t len); + protected: void send(bool on) override; + + bool m_state = false; + bool m_blinkState = false; }; diff --git a/src/usb/busylight/LitraGlow.cpp b/src/usb/busylight/LitraGlow.cpp index b7d4e1cf..df5eb0b1 100644 --- a/src/usb/busylight/LitraGlow.cpp +++ b/src/usb/busylight/LitraGlow.cpp @@ -24,11 +24,13 @@ void LitraGlow::switchStreamlight(bool on) return; } - unsigned char buf[20]; + // Don't send the same state again + if (m_state == on) { + return; + } + m_state = on; - // TODO: Make a hid++ support library and do this in a non hardcoded way - // because index and feature versions are not guaranteed to stay this - // way. + unsigned char buf[20]; // Switch on or off memset(buf, 0, sizeof(buf)); @@ -39,6 +41,7 @@ void LitraGlow::switchStreamlight(bool on) buf[4] = on ? 1 : 0; hid_write(m_device, buf, sizeof(buf)); + hid_read_timeout(m_device, buf, sizeof(buf), 200); } void LitraGlow::send(bool on) diff --git a/src/usb/busylight/LitraGlow.h b/src/usb/busylight/LitraGlow.h index a26f8f13..d80b9ec1 100644 --- a/src/usb/busylight/LitraGlow.h +++ b/src/usb/busylight/LitraGlow.h @@ -18,4 +18,6 @@ class LitraGlow : public IBusylightDevice protected: void send(bool on) override; + + bool m_state = false; }; From 31ee0cd138b21d34c3945274a4f6f08e344eae96 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 10 Mar 2026 20:43:17 +0100 Subject: [PATCH 009/122] fix: more sip codes lead to auth request (#379) * Updated translations * Added more fail codes for auth --- i18n/gonnect_de.ts | 162 +- i18n/gonnect_es.ts | 162 +- i18n/gonnect_fa.ts | 2146 +++++++++++----------- i18n/gonnect_fr.ts | 162 +- i18n/gonnect_it.ts | 162 +- i18n/gonnect_ru.ts | 2152 +++++++++++----------- i18n/gonnect_uk.ts | 2170 ++++++++++++----------- src/AuthManager.cpp | 2 +- src/Main.qml | 8 + src/calendar/DateEventFeederManager.cpp | 2 +- src/chat/ChatConnectorManager.cpp | 4 +- src/contacts/AddressBookManager.cpp | 2 +- src/sip/SIPAccount.cpp | 17 +- src/sip/SIPAccount.h | 1 + src/ui/GonnectWindow.qml | 3 + src/ui/JitsiConnector.cpp | 5 +- 16 files changed, 3804 insertions(+), 3356 deletions(-) diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts index 22c16fd0..6bf5f247 100644 --- a/i18n/gonnect_de.ts +++ b/i18n/gonnect_de.ts @@ -30,8 +30,8 @@ - Copy the currently used version of Gonnect to the clipboard - Die aktuell verwendete Version von GOnnect in die Zwischenablage kopieren + Copy the currently used version number of GOnnect to the clipboard + Kopiere die aktuell verwendete Versionsnummer von GOnnect in die Zwischenablage @@ -85,6 +85,14 @@ Aufklappbarer Antwortbereich + + AddressBookManager + + + Failed to persist address book credentials: %1 + Speichern der Anmeldedaten für das Adressbuch fehlgeschlagen: %1 + + AudioDeviceMenu @@ -222,16 +230,24 @@ AudioManager - + Default input Standard-Eingang - + Default output Standard-Ausgang + + AuthManager + + + Failed to persist jitsi refresh token: %1 + Fehler beim Speichern des Jitsi-Aktualisierungstokens: %1 + + AvatarImage @@ -840,6 +856,19 @@ Liste der Einstellungen + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + Fehler beim Speichern des Chat-Wiederherstellungscodes: %1 + + + + Failed to persist chat access token: %1 + Fehler beim Speichern des Chat-Zugriffstokens: %1 + + ChatMessageBox @@ -953,7 +982,7 @@ Emoji auswählen - + Enter chat message... Chat-Nachricht eingeben... @@ -1288,14 +1317,14 @@ Credentials - - storing credentials failed: %1 - Speichern der Zugangsdaten fehlgeschlagen: %1 + + Storing credentials for %1 failed: %2 + Speichern der Anmeldedaten für %1 fehlgeschlagen: %2 - - reading credentials failed: %1 - Lesen der Zugangsdaten fehlgeschlagen: %1 + + reading credentials for %1 failed: %2 + Lesen der Anmeldedaten für %1 fehlgeschlagen: %2 @@ -1344,6 +1373,14 @@ GOnnect-Fenster schließen + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + Kalender-Anmeldedaten konnten nicht gespeichert werden: %1 + + DateEventManager @@ -1935,7 +1972,7 @@ GonnectWindow - + Home Home @@ -2201,32 +2238,38 @@ JitsiConnector - + New chat message Neue Textnachricht - + Unnamed participant Unbekannter Teilnehmer - + Active conference Aktive Konferenz - + Hang up Auflegen - + %1 has joined the conference %1 hat die Konferenz betreten - + + + Failed to persist room password: %1 + Speichern des Raumkennworts fehlgeschlagen: %1 + + + %1 has left the conference %1 hat die Konferenz verlassen @@ -2257,30 +2300,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - LDAP-Verbindung konnte nicht initialisiert werden - - - - - + + + LDAP error: %1 LDAP-Fehler: %1 - - + + Parse error: %1 Syntaxfehler: %1 - + LDAP timeout: %1 LDAP Zeitüberschreitung: %1 - + + Failed to initialize LDAP connection: %1 Kann die LDAP-Verbindung nicht initialisieren: %1 @@ -2296,62 +2335,78 @@ Main - + No system tray available Kein System-Tray verfügbar - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect kann das System Tray für einen schnelleren Zugriff verwenden. Dein Desktop-Environment stellt keines zur Verfügung. - + Information Information - + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect wird für Dein Betriebsystem gerade getestet und ist noch nicht offiziell freigegeben. Zur Zeit sind nicht alle Features der Linux-Version umgesetzt, und es könnte Fehler geben, die wir noch nicht gefunden haben. Gerne können diese Fehler auf github gemeldet werden. Frohes Testen! - + There are still phone calls going on, do you really want to quit? Es sind noch aktive Anrufe vorhanden. Möchtest Du wirklich beenden? - + Please enter the password for %1: Bitte geben Sie das Passwort für %1 ein: - + Please enter the recovery key for %1: Bitte geben Sie den Wiederherstellungsschlüssel für %1 ein: - + Please enter the password for the SIP account: Bitte gib das Passwort für den SIP-Account ein: - + End all calls Alle Anrufe beenden - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + Do you really want to close this window and terminate all ongoing calls? Möchtest Du das Fenster schliessen und alle aktiven Anrufe beenden? - + + Registration failed + Registrierung fehlgeschlagen + + + + Registration failed with with status %1: %2 + Die Registrierung ist mit dem Status %1 fehlgeschlagen: %2 + + + Error Fehler - + Fatal Error Fataler Fehler @@ -3955,35 +4010,40 @@ SIPAccount - + 'userUri' is no valid SIP URI: %1 'userUri' ist keine gültige SIP URI: %1 - + 'userUri' is required 'userUri' ist erforderlich - + 'registrarUri' is no valid SIP URI: %1 'registrarUri' ist keine gültige SIP URI: %1 - + 'registrarUri' is required 'registrarUri' ist erforderlich - + 'proxies' contains invalid SIP URI entry: %1 'proxies' enthält eine ungültige SIP URI: %1 - + Failed to create %1: %2 Fehler beim Erzeugen von %1: %2 + + + Failed to persist SIP credentials: %1 + SIP-Anmeldedaten konnten nicht gespeichert werden: %1 + SIPBuddy @@ -3996,12 +4056,12 @@ SIPCall - + Active call with %1 Aktiver Anruf mit %1 - + Hang up Auflegen @@ -4044,7 +4104,7 @@ SIPManager - + New Identity Neue Identität diff --git a/i18n/gonnect_es.ts b/i18n/gonnect_es.ts index e11b172a..d96c130f 100644 --- a/i18n/gonnect_es.ts +++ b/i18n/gonnect_es.ts @@ -30,8 +30,8 @@ - Copy the currently used version of Gonnect to the clipboard - Copiar la versión actualmente utilizada de GOnnect al portapapeles + Copy the currently used version number of GOnnect to the clipboard + Copia el número de versión actual de GOnnect en el portapapeles @@ -85,6 +85,14 @@ Sección de respuesta desplegable + + AddressBookManager + + + Failed to persist address book credentials: %1 + No se pudieron conservar las credenciales de la libreta de direcciones: %1 + + AudioDeviceMenu @@ -222,16 +230,24 @@ AudioManager - + Default input Entrada predeterminada - + Default output Salida predeterminada + + AuthManager + + + Failed to persist jitsi refresh token: %1 + No se ha podido conservar el token de actualización de Jitsi: %1 + + AvatarImage @@ -840,6 +856,19 @@ Lista de opciones configurables + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + No se ha podido conservar el código de recuperación del chat: %1 + + + + Failed to persist chat access token: %1 + No se pudo conservar el token de acceso al chat: %1 + + ChatMessageBox @@ -953,7 +982,7 @@ Seleccionar emoji - + Enter chat message... Introduce el mensaje del chat... @@ -1288,14 +1317,14 @@ Credentials - - storing credentials failed: %1 - Error al guardar las credenciales: %1 + + Storing credentials for %1 failed: %2 + Error al guardar los datos de inicio de sesión para %1: %2 - - reading credentials failed: %1 - Error al leer las credenciales: %1 + + reading credentials for %1 failed: %2 + Error al leer los datos de inicio de sesión para %1: %2 @@ -1344,6 +1373,14 @@ Cerrar la ventana de GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + No se pudieron conservar las credenciales del calendario: %1 + + DateEventManager @@ -1935,7 +1972,7 @@ GonnectWindow - + Home Inicio @@ -2201,32 +2238,38 @@ JitsiConnector - + New chat message Nuevo mensaje de chat - + Unnamed participant Participante sin nombre - + Active conference Conferencia activa - + Hang up Colgar - + %1 has joined the conference %1 se ha unido a la conferencia - + + + Failed to persist room password: %1 + No se ha podido guardar la contraseña de la sala: %1 + + + %1 has left the conference %1 ha abandonado la conferencia @@ -2257,30 +2300,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - Error al inicializar la conexión LDAP - - - - - + + + LDAP error: %1 Error LDAP: %1 - - + + Parse error: %1 Error de sintaxis: %1 - + LDAP timeout: %1 Tiempo de espera LDAP: %1 - + + Failed to initialize LDAP connection: %1 No se pudo inicializar la conexión LDAP: %1 @@ -2296,62 +2335,78 @@ Main - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + No system tray available No hay bandeja de sistema disponible - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect permite un acceso rápido a sus funciones mediante la bandeja del sistema. Su entorno de escritorio no dispone de una. - + Information Información - + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect está siendo probado para su sistema operativo y aún no ha sido lanzado oficialmente. Todavía no hay paridad de funciones con la versión de Linux, y puede haber errores que aún no se han encontrado. Agradecemos que informe de estos problemas en GitHub. ¡Feliz prueba! - + There are still phone calls going on, do you really want to quit? Aún hay llamadas telefónicas en curso. ¿Realmente desea salir? - + Please enter the password for %1: Por favor introduzca la contraseña para %1: - + Please enter the recovery key for %1: Por favor, introduzca la clave de recuperación para %1: - + End all calls Terminar todas las llamadas - + Do you really want to close this window and terminate all ongoing calls? ¿Realmente desea cerrar esta ventana y terminar todas las llamadas en curso? - + Please enter the password for the SIP account: Por favor, introduzca la contraseña de la cuenta SIP: - + + Registration failed + El registro ha fallado + + + + Registration failed with with status %1: %2 + El registro ha fallado con el estado %1: %2 + + + Error Error - + Fatal Error Error fatal @@ -3955,35 +4010,40 @@ SIPAccount - + 'userUri' is no valid SIP URI: %1 'userUri' no es una URI SIP válida: %1 - + 'userUri' is required 'userUri' es obligatorio - + 'registrarUri' is no valid SIP URI: %1 'registrarUri' no es una URI SIP válida: %1 - + 'registrarUri' is required 'registrarUri' es obligatorio - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contiene una entrada de URI SIP no válida: %1 - + Failed to create %1: %2 Error al crear %1: %2 + + + Failed to persist SIP credentials: %1 + No se pudieron conservar las credenciales SIP: %1 + SIPBuddy @@ -3996,12 +4056,12 @@ SIPCall - + Active call with %1 Llamada activa con %1 - + Hang up Colgar @@ -4044,7 +4104,7 @@ SIPManager - + New Identity Nueva identidad diff --git a/i18n/gonnect_fa.ts b/i18n/gonnect_fa.ts index 67cb9bcf..08429388 100644 --- a/i18n/gonnect_fa.ts +++ b/i18n/gonnect_fa.ts @@ -4,65 +4,65 @@ AboutWindow - + About درباره - + GOnnect headline سرتیتر GOnnect - + GOnnect version نسخه GOnnect - + Version: v%1 نسخه: v%1 - + Copy to clipboard کپی در کلیپ‌بورد - - Copy the currently used version of Gonnect to the clipboard - کپی نسخه فعلی GOnnect در کلیپ‌بورد + + Copy the currently used version number of GOnnect to the clipboard + شماره نسخهٔ در حال استفاده از GOnnect را به کلیپ‌بورد کپی کنید - - - - + + + + Homepage صفحه اصلی - + Visit the project homepage بازدید از صفحه اصلی پروژه - + Bug Tracker ردیاب خطا - + Visit the project bug tracker بازدید از ردیاب خطای پروژه - + Documentation مستندات - + Visit the online project documentation بازدید از مستندات آنلاین پروژه @@ -70,25 +70,33 @@ AdditionalInfo - + Additional caller related information اطلاعات بیشتر درباره تماس‌گیرنده - + List of informational items regarding the caller, such as open support tickets فهرست اطلاعات تماس‌گیرنده مانند تیکت‌های پشتیبانی باز - + Expandable response section بخش پاسخ قابل گسترش + + AddressBookManager + + + Failed to persist address book credentials: %1 + ذخیرهٔ اعتبارنامه‌های دفترچهٔ آدرس ناموفق بود: %1 + + AudioDeviceMenu - + Default پیش‌فرض @@ -96,117 +104,117 @@ AudioEnvWindow - + Unknown audio environment محیط صوتی ناشناخته - + Audio environment error خطای محیط صوتی - + No fitting audio environment could be found. Please select the desired audio devices. محیط صوتی مناسب یافت نشد. لطفاً دستگاه‌های صوتی دلخواه را انتخاب کنید. - + Input device selection header عنوان انتخاب دستگاه ورودی - + Header for the input device selection below عنوان برای انتخاب دستگاه ورودی زیر - + Input device دستگاه ورودی - + Input device selection box کادر انتخاب دستگاه ورودی - + Select the input device that should be used انتخاب دستگاه ورودی - + Currently selected input device دستگاه ورودی انتخاب‌شده - + Output device selection header عنوان انتخاب دستگاه خروجی - + Header for the output device selection below عنوان برای انتخاب دستگاه خروجی زیر - + Output device دستگاه خروجی - + Output device selection box کادر انتخاب دستگاه خروجی - + Select the output device that should be used انتخاب دستگاه خروجی - + Currently selected output device دستگاه خروجی انتخاب‌شده - + Ring tone output device دستگاه خروجی زنگ - + Output device for ring tone دستگاه خروجی برای زنگ - + Ring tone output device selection box کادر انتخاب دستگاه خروجی زنگ - + Select the output device that should be used for playing the ring tone انتخاب دستگاه خروجی برای پخش زنگ - + Currently selected ring tone output device دستگاه خروجی زنگ انتخاب‌شده - + Ok تأیید - + Close audio environment selection بستن انتخاب محیط صوتی - + Confirmation button to leave the audio environment selection window دکمه تأیید برای خروج از پنجره انتخاب محیط صوتی @@ -214,7 +222,7 @@ AudioLevelButton - + Change volume تغییر صدا @@ -222,20 +230,28 @@ AudioManager - + Default input ورودی پیش‌فرض - + Default output خروجی پیش‌فرض + + AuthManager + + + Failed to persist jitsi refresh token: %1 + ثبت توکن تازه‌سازی جیتسی ناموفق بود: %1 + + AvatarImage - + Initials of this contact حروف اول نام مخاطب @@ -243,7 +259,7 @@ BaseDialog - + Dialog گفتگو @@ -251,17 +267,17 @@ BasePage - + Base dashboard page grid شبکه پایه صفحه داشبورد - + Canvas for editable dashboard pages بوم صفحات داشبورد قابل ویرایش - + Add widgets افزودن ابزارک @@ -269,32 +285,32 @@ BaseWidget - + Drag widget کشیدن ابزارک - + Change the position of the widget تغییر موقعیت ابزارک - + Remove widget حذف ابزارک - + Remove the currently selected widget from the dashboard حذف ابزارک انتخاب‌شده از داشبورد - + Resize widget تغییر اندازه ابزارک - + Resize the widget according to the mouse direction تغییر اندازه ابزارک بر اساس جهت ماوس @@ -302,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border لبه کشیدنی - + Top left drag border for window resize operations لبه کشیدنی بالا چپ برای تغییر اندازه پنجره - + Top drag border for window resize operations لبه کشیدنی بالا برای تغییر اندازه پنجره - + Top right border for window resize operations لبه بالا راست برای تغییر اندازه پنجره - + Right drag border for window resize operations لبه کشیدنی راست برای تغییر اندازه پنجره - + Bottom right drag border for window resize operations لبه کشیدنی پایین راست - + Bottom drag border for window resize operations لبه کشیدنی پایین برای تغییر اندازه پنجره - + Bottom left drag border for window resize operations لبه کشیدنی پایین چپ - + Left drag border for window resize operations لبه کشیدنی چپ برای تغییر اندازه پنجره @@ -357,7 +373,7 @@ BottomStatusBar - + Status bar نوار وضعیت @@ -365,27 +381,27 @@ BurgerMenu - + Toggle fullscreen تغییر حالت تمام‌صفحه - + Shortcuts... میانبرها... - + Customize UI سفارشی‌سازی رابط کاربری - + About... درباره... - + Quit خروج @@ -393,12 +409,12 @@ Call - + Conference کنفرانس - + Drag bar نوار کشیدنی @@ -406,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 کیلوهرتز - + Transmit ارسال - + Call security level سطح امنیت تماس - + Security level of the ongoing call سطح امنیت تماس جاری - + Call security details جزئیات امنیت تماس - + Detailed call security status: %1 / %2 وضعیت دقیق امنیت تماس: %1 / %2 - + signaling encrypted سیگنال‌دهی رمزنگاری‌شده - + signaling unencrypted سیگنال‌دهی رمزنگاری‌نشده - + media encrypted رسانه رمزنگاری‌شده - + media unencrypted رسانه رمزنگاری‌نشده - + Call quality کیفیت تماس - + Quality of the ongoing call کیفیت تماس جاری - + Transmission statistics آمار ارسال - - + + Call quality metrics معیارهای کیفیت تماس - - + + MOS MOS - - + + Mean opinion score امتیاز میانگین نظر (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 معیار عددی کیفیت صدا در سمت ارسال: %1 - - + + Packet loss اتلاف بسته - + %1% of packets lost in transmission %1٪ از بسته‌ها در ارسال از دست رفت - - + + Jitter جیتر - + Amount of transmission side jitter: %1 میزان جیتر در سمت ارسال: %1 - - + + Effective delay تأخیر مؤثر - + Effective transmission side call delay: %1 تأخیر مؤثر تماس در سمت ارسال: %1 - + Receiver statistics آمار دریافت - + Receive دریافت - + Numerical metric assessing receiver-side voice/video call quality: %1 معیار عددی کیفیت صدا/تصویر در سمت دریافت: %1 - + %1% of packets lost in receival %1٪ از بسته‌ها در دریافت از دست رفت - + Amount of receiver side jitter: %1 میزان جیتر در سمت دریافت: %1 - + Effective receiver side call delay: %1 تأخیر مؤثر در سمت دریافت: %1 - + Codec کدک - + Audio codec کدک صوتی - + The currently used audio codec and frequency: %1 کدک صوتی و فرکانس فعلی: %1 - + Elapsed call time زمان سپری‌شده تماس - + The duration in seconds the call has been active for: %1 مدت زمان فعال بودن تماس به ثانیه: %1 - + Screen صفحه‌نمایش - + Screensharing control کنترل اشتراک‌گذاری صفحه - + Start sharing your screen شروع اشتراک‌گذاری صفحه - + Camera دوربین - + Camera control کنترل دوربین - + Enable your camera فعال‌سازی دوربین - + Resume ادامه - + Hold نگه‌داشتن - + Resume call ادامه تماس - + Hold call نگه‌داشتن تماس - + Update the call hold state به‌روزرسانی وضعیت نگه‌داری تماس - + Micro میکروفون - + Input control کنترل ورودی - + Set the mute state of the current input device تنظیم وضعیت بی‌صدا کردن دستگاه ورودی - + Output خروجی - + Output control کنترل خروجی - + Change the current output devices تغییر دستگاه‌های خروجی فعلی - + Accept call پذیرفتن تماس - + Hangup call قطع تماس @@ -680,47 +696,47 @@ CallDetails - + SIP call status code کد وضعیت تماس SIP - + The current status code of the call: %1 کد وضعیت فعلی تماس: %1 - + Jitsi Meet Jitsi Meet - + Switch to a Jitsi Meet session رفتن به جلسه Jitsi Meet - + Unhold برداشتن نگه‌داری - + Hold نگه‌داشتن - + Toggle the hold state to %1 تغییر وضعیت نگه‌داری به %1 - + Accept call پذیرفتن تماس - + Hangup call قطع تماس @@ -728,17 +744,17 @@ CallHistory - + Failed to create directory %1 to store the call history database. ایجاد پوشه %1 برای پایگاه داده تاریخچه تماس ناموفق بود. - + Failed to open call history database: %1 باز کردن پایگاه داده تاریخچه تماس ناموفق بود: %1 - + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. پایگاه داده تاریخچه تماس ناسازگار است. %1 را حذف کنید و برنامه را مجدداً راه‌اندازی کنید. @@ -746,22 +762,22 @@ CallItem - + Call تماس - + Selected call %1 - contact %2, company %3, location %4/%5, number %6 تماس انتخاب‌شده %1 - مخاطب %2، شرکت %3، مکان %4/%5، شماره %6 - + Hangup button دکمه قطع - + Pressing this will end the call فشردن این دکمه تماس را پایان می‌دهد @@ -769,18 +785,18 @@ CallList - + Drag callers onto each other to transfer call تماس‌گیرندگان را روی هم بکشید تا تماس منتقل شود - + List of active calls فهرست تماس‌های فعال - - + + Create conference ایجاد کنفرانس @@ -788,20 +804,19 @@ CallSideBar - + Chat گفتگو - + Person(s) %n نفر - %n نفر - + Info اطلاعات @@ -809,17 +824,17 @@ CallerBigAvatar - + Caller name نام تماس‌گیرنده - + is calling... در حال تماس... - + Calling... در حال برقراری تماس... @@ -827,7 +842,7 @@ CallsModel - + unknown number شماره ناشناس @@ -835,25 +850,38 @@ CardList - + List of configurable options فهرست گزینه‌های قابل تنظیم + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + ذخیره کد بازیابی چت ناموفق بود: %1 + + + + Failed to persist chat access token: %1 + ذخیره کردن توکن دسترسی چت ناموفق بود: %1 + + ChatMessageBox - + Message پیام - + Type message پیام تایپ کنید - + Enter the chat text message پیام متنی گفتگو را وارد کنید @@ -861,17 +889,17 @@ ChatMessageList - + Chat message list فهرست پیام‌های گفتگو - + List of all chat messages of the current chat room فهرست همه پیام‌های اتاق گفتگوی فعلی - + Auto scroll down پیمایش خودکار به پایین @@ -879,12 +907,12 @@ ChatMessageListItem - + Chat message پیام گفتگو - + Selected chat message - from %1, at %2: %3 پیام انتخاب‌شده - از %1، در %2: %3 @@ -892,12 +920,12 @@ ChatRoomList - + Chat room list فهرست اتاق‌های گفتگو - + List of all chat rooms فهرست همه اتاق‌های گفتگو @@ -905,12 +933,12 @@ ChatRoomListItem - + Chat room اتاق گفتگو - + Selected chat room %1: %2 unread messages اتاق انتخاب‌شده %1: %2 پیام خوانده‌نشده @@ -918,42 +946,42 @@ ChatSideBar - + Chat message list فهرست پیام‌های گفتگو - + List of all the messages in the current chat فهرست همه پیام‌های گفتگوی فعلی - + Chat message پیام گفتگو - + Selected chat message from %1 at %2: %3 پیام انتخاب‌شده از %1 در %2: %3 - + the server سرور - + you شما - + Select emoji انتخاب شکلک - + Enter chat message... پیام را وارد کنید... @@ -961,27 +989,27 @@ Chats - + Please enter your recovery key to decrypt messages: لطفاً کلید بازیابی را برای رمزگشایی پیام‌ها وارد کنید: - + Recovery key کلید بازیابی - + Enter recovery key کلید بازیابی را وارد کنید - + Use key استفاده از کلید - + Use recovery key استفاده از کلید بازیابی @@ -989,7 +1017,7 @@ ClipboardButton - + Copy to clipboard: %1 کپی در کلیپ‌بورد: %1 @@ -997,138 +1025,138 @@ Conference - - + + Set room name تنظیم نام اتاق - - + + Room name: نام اتاق: - - + + Enter the room name نام اتاق را وارد کنید - + Authenticate احراز هویت - + Please authenticate in the opened browser window... لطفاً در پنجره مرورگر باز‌شده احراز هویت کنید... - + This conference is protected by a password. Please enter it to join the room. این کنفرانس با رمز عبور محافظت شده است. رمز را برای ورود وارد کنید. - - + + Password رمز عبور - - + + Enter the password رمز عبور را وارد کنید - + Remember password به‌خاطر سپردن رمز - - + + Cancel لغو - + Join Room ورود به اتاق - + Password required رمز عبور لازم است - + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. رمز عبوری برای محافظت از اتاق وارد کنید. سایر شرکت‌کنندگان باید آن را پیش از ورود وارد کنند. - + This password has been set for the conference room and must be entered by participants before taking part in the session. این رمز برای اتاق تنظیم شده و شرکت‌کنندگان باید آن را پیش از ورود وارد کنند. - + The room password has been set by someone else. رمز اتاق توسط شخص دیگری تنظیم شده است. - + Show password نمایش رمز - + Remove حذف - + Save ذخیره - + Video quality کیفیت تصویر - + Change the video quality of this meeting تغییر کیفیت تصویر این جلسه - + No video (audio only) بدون تصویر (فقط صدا) - + Lowest quality کمترین کیفیت - + Standard quality کیفیت استاندارد - + Highest quality بیشترین کیفیت - + Close بستن - + Drag bar نوار کشیدنی @@ -1136,135 +1164,134 @@ ConferenceButtonBar - + %n minutes left %n دقیقه باقی‌مانده - %n دقیقه باقی‌مانده - + Conference room اتاق کنفرانس - + Share اشتراک‌گذاری - + Copy room name کپی نام اتاق - + Copy room link کپی لینک اتاق - + Open in browser باز کردن در مرورگر - + Show phone number نمایش شماره تلفن - + Raise بلند کردن دست - + Resume ادامه - + Hold نگه‌داشتن - + View نما - + Screen صفحه‌نمایش - + Share window اشتراک‌گذاری پنجره - + Share screen اشتراک‌گذاری صفحه - + Camera دوربین - + Output خروجی - + More بیشتر - + Noise supression حذف نویز - + Toggle subtitles روشن/خاموش کردن زیرنویس - + Toggle whiteboard روشن/خاموش کردن تخته سفید - + Video quality... کیفیت تصویر... - + Set room password... تنظیم رمز اتاق... - + Mute everyone بی‌صدا کردن همه - + Leave conference ترک کنفرانس - + End conference for all پایان کنفرانس برای همه - + Micro میکروفون @@ -1272,7 +1299,7 @@ ConfirmDialog - + Cancel لغو @@ -1280,7 +1307,7 @@ ControlBar - + App menu منوی برنامه @@ -1288,40 +1315,40 @@ Credentials - - storing credentials failed: %1 - ذخیره اعتبارنامه ناموفق بود: %1 + + Storing credentials for %1 failed: %2 + ذخیره جزئیات ورود برای %1 ناموفق بود: %2 - - reading credentials failed: %1 - خواندن اعتبارنامه ناموفق بود: %1 + + reading credentials for %1 failed: %2 + خواندن جزئیات ورود برای %1 ناموفق بود: %2 CredentialsDialog - + Authentication failed احراز هویت ناموفق بود - + Please enter the password: لطفاً رمز عبور را وارد کنید: - + Password رمز عبور - + Enter the password رمز عبور را وارد کنید - + Ok تأیید @@ -1329,40 +1356,48 @@ CustomWindowHeader - + GOnnect window header عنوان پنجره GOnnect - + App menu منوی برنامه - + Close GOnnect window بستن پنجره GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + ذخیرهٔ اعتبارنامه‌های تقویم ناموفق بود: %1 + + DateEventManager - + Conference starting soon کنفرانس به زودی شروع می‌شود - + Appointment starting soon قرار ملاقات به زودی شروع می‌شود - + Join پیوستن - + Open باز کردن @@ -1370,114 +1405,114 @@ DateEventsList - + Date events رویدادهای تقویم - + List of all the currently active and upcoming date events فهرست همه رویدادهای فعال و آینده - + Date event section بخش رویدادها - + Header for %1 عنوان برای %1 - + Today - %1 امروز - %1 - - + + yyyy/MM/dd yyyy/MM/dd - + Tomorrow - %1 فردا - %1 - + dddd - yyyy/MM/dd dddd - yyyy/MM/dd - + Date event رویداد تقویم - + Currently selected date event: %1, starting time %2, remaining time %3 رویداد انتخاب‌شده: %1، شروع %2، باقی‌مانده %3 - - + + hh:mm hh:mm - + All day تمام روز - + till %1 تا %1 - + in %1 در %1 - + Join پیوستن - + Open باز کردن - + Join meeting پیوستن به جلسه - + Join the meeting associated with the currently selected event پیوستن به جلسه رویداد انتخاب‌شده - + Copy room link کپی لینک اتاق - + Copy link کپی لینک - + Copy meeting link کپی لینک جلسه - + Copy the meeting link associated with the currently selected event کپی لینک جلسه رویداد انتخاب‌شده @@ -1485,27 +1520,27 @@ DateEventsWidget - + Appointments قرارها - + Loading appointments... بارگذاری قرارها... - + No upcoming appointments هیچ قراری در پیش نیست - + Date event widget status وضعیت ابزارک رویدادها - + Displays the current status of the widget: %1 نمایش وضعیت فعلی ابزارک: %1 @@ -1513,12 +1548,12 @@ DialInInfo - + Call one of the phone numbers below and use this code for authentication: با یکی از شماره‌های زیر تماس بگیرید و از این کد برای احراز هویت استفاده کنید: - + Close بستن @@ -1526,12 +1561,12 @@ DtmfDialer - + Number pad صفحه‌کلید عددی - + Character %1 کاراکتر %1 @@ -1539,32 +1574,32 @@ EditModeOptions - + Add page افزودن صفحه - + Add a new dashboard page افزودن صفحه جدید به داشبورد - + Add widget افزودن ابزارک - + Add a new widget to the current dashboard page افزودن ابزارک جدید به صفحه فعلی - + Finished تمام شد - + Finish and save all dashboard and widget changes پایان و ذخیره همه تغییرات @@ -1572,22 +1607,22 @@ EmergencyCallIncomingWindow - + Emergency Call تماس اضطراری - + Incoming emergency call from %1 تماس اضطراری ورودی از %1 - + Answering the call will automatically terminate all other ongoing calls. پاسخ دادن به این تماس، سایر تماس‌های جاری را خودکار پایان می‌دهد. - + Answer پاسخ @@ -1595,12 +1630,12 @@ EmojiButton - + Emoji شکلک - + Selected Emoji: %1 شکلک انتخاب‌شده: %1 @@ -1608,12 +1643,12 @@ EmojiPicker - + Switch Emoji category تغییر دسته شکلک - + Select Emoji انتخاب شکلک @@ -1621,186 +1656,186 @@ EnumTranslation - + Trying در حال تلاش - + Ringing در حال زنگ زدن - + Call being forwarded تماس در حال انتقال - + Queued در صف - + Progress در حال پیشرفت - + Ok تأیید - + Accepted پذیرفته شد - + Unauthorized غیرمجاز - - + + Rejected رد شد - + Not found یافت نشد - + Proxy authentication required احراز هویت پروکسی لازم است - + Request timeout مهلت درخواست به پایان رسید - + Temporarily unavailable موقتاً در دسترس نیست - + Ambiguous مبهم - + Busy here اینجا مشغول است - + Request terminated درخواست پایان یافت - + Not acceptable here اینجا قابل قبول نیست - + Internal server error خطای داخلی سرور - + Not implemented پیاده‌سازی نشده - + Bad gateway دروازه نامعتبر - + Service unavailable سرویس در دسترس نیست - + Server timeout مهلت سرور به پایان رسید - + Busy everywhere همه جا مشغول - + Decline رد - + Does not exist anywhere هیچ جا وجود ندارد - + Not acceptable anywhere هیچ جا قابل قبول نیست - + Unwanted ناخواسته - - - - + + + + Unknown ناشناس - + Commercial تجاری - + Home خانه - + Mobile موبایل - + Incoming ورودی - + Outgoing خروجی - + Blocked مسدود - + SIP SIP - + Jitsi Meet Jitsi Meet @@ -1808,12 +1843,12 @@ FavIcon - + Set favorite افزودن به علاقه‌مندی‌ها - + Unset favorite حذف از علاقه‌مندی‌ها @@ -1821,22 +1856,22 @@ FavoriteListItemBig - + Favorite contact مخاطب مورد علاقه - + Selected favorite %1: %2 مورد علاقه انتخاب‌شده %1: %2 - + tap to start meeting %1 ضربه بزنید تا جلسه %1 را شروع کنید - + tap to call %1 ضربه بزنید تا با %1 تماس بگیرید @@ -1844,17 +1879,17 @@ FavoriteListItemSmall - + Favorite contact مخاطب مورد علاقه - + Selected favorite %1: %2 مورد علاقه انتخاب‌شده %1: %2 - + tap to call %1 ضربه بزنید تا با %1 تماس بگیرید @@ -1862,13 +1897,13 @@ FavoritesList - - + + Favorites علاقه‌مندی‌ها - + List of all contacts that have been marked as favorites فهرست همه مخاطبان علاقه‌مندی @@ -1876,12 +1911,12 @@ FavoritesWidget - + Favorites علاقه‌مندی‌ها - + No favorites to display هیچ موردی برای نمایش وجود ندارد @@ -1889,32 +1924,32 @@ FirstAid - + First Aid کمک‌های اولیه - + Clicking one of these buttons will end all current calls and start an emergency call. کلیک بر این دکمه‌ها همه تماس‌های جاری را پایان داده و تماس اضطراری برقرار می‌کند. - + Tap to call emergency contact: %1 (%2) ضربه بزنید تا با مخاطب اضطراری تماس بگیرید: %1 (%2) - + Close بستن - + Exit the first aid menu without initiating any action خروج از منوی کمک‌های اولیه بدون انجام اقدام - + Close first aid menu بستن منوی کمک‌های اولیه @@ -1922,12 +1957,12 @@ FirstAidButton - + Open first aid menu باز کردن منوی کمک‌های اولیه - + First Aid کمک‌های اولیه @@ -1935,7 +1970,7 @@ GonnectWindow - + Home خانه @@ -1943,7 +1978,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -1951,41 +1986,41 @@ HeadsetDeviceProxy - + Ringing در حال زنگ زدن - + Calling در حال تماس - - - - + + + + Call active تماس فعال - + Call waiting تماس در انتظار - + On Hold در انتظار - - + + Call ended تماس پایان یافت - + Phone conference کنفرانس تلفنی @@ -1993,43 +2028,43 @@ HistoryList - - + + No past calls هیچ تماس قبلی وجود ندارد - + History تاریخچه - + Searchable list of past calls and meetings فهرست قابل جستجوی تماس‌ها و جلسات گذشته - + History item section بخش آیتم تاریخچه - + Header for the currently selected day: %1 عنوان روز انتخاب‌شده: %1 - + History item آیتم تاریخچه - + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 آیتم انتخاب‌شده %1 - شرکت %2، مکان %3، شماره %4، زمان %5، مدت %6 - + hh:mm hh:mm @@ -2037,37 +2072,37 @@ HistoryListContextMenu - + Call تماس - + Copy number کپی شماره - + Remove favorite حذف از علاقه‌مندی‌ها - + Add favorite افزودن به علاقه‌مندی‌ها - + Remind when available یادآوری هنگام در دسترس بودن - + Unblock رفع انسداد - + Block for 8 hours مسدود کردن برای ۸ ساعت @@ -2075,78 +2110,78 @@ HistoryWidget - + History تاریخچه - - + + All همه - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker انتخاب نوع تماس در تاریخچه - + Select the call type to filter by نوع تماس برای فیلتر را انتخاب کنید - + Currently selected call type نوع تماس انتخاب‌شده - + Incoming ورودی - + Outgoing خروجی - + Missed از دست رفته - + History call origin picker انتخاب منشأ تماس در تاریخچه - + Select the call origin to filter by منشأ تماس برای فیلتر را انتخاب کنید - + Currently selected call origin منشأ تماس انتخاب‌شده - + Hide history search پنهان کردن جستجوی تاریخچه - + Show history search نمایش جستجوی تاریخچه @@ -2154,10 +2189,10 @@ IMHandler - - - - + + + + Ad hoc conference کنفرانس فوری @@ -2165,27 +2200,27 @@ IdentitySelector - + Default پیش‌فرض - + Auto خودکار - + Identity selection انتخاب هویت - + Select the preferred identity to be used in calls انتخاب هویت ترجیحی برای تماس‌ها - + Currently selected identity هویت انتخاب‌شده @@ -2193,7 +2228,7 @@ InfoDialog - + Ok تأیید @@ -2201,32 +2236,38 @@ JitsiConnector - + New chat message پیام جدید - + Unnamed participant شرکت‌کننده بی‌نام - + Active conference کنفرانس فعال - + Hang up قطع تماس - + %1 has joined the conference %1 به کنفرانس پیوست - + + + Failed to persist room password: %1 + ذخیرهٔ رمز عبور اتاق ناموفق بود: %1 + + + %1 has left the conference %1 کنفرانس را ترک کرد @@ -2234,22 +2275,22 @@ JitsiHistoryListContextMenu - + Start conference شروع کنفرانس - + Remove favorite حذف از علاقه‌مندی‌ها - + Add favorite افزودن به علاقه‌مندی‌ها - + Copy room name کپی نام اتاق @@ -2257,30 +2298,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - راه‌اندازی اتصال LDAP ناموفق بود - - - - - + + + LDAP error: %1 خطای LDAP: %1 - - + + Parse error: %1 خطای تجزیه: %1 - + LDAP timeout: %1 مهلت LDAP: %1 - + + Failed to initialize LDAP connection: %1 راه‌اندازی اتصال LDAP ناموفق بود: %1 @@ -2288,7 +2325,7 @@ LinuxDesktopSearchProvider - + Call تماس @@ -2296,62 +2333,78 @@ Main - + No system tray available سینی سیستم در دسترس نیست - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect از طریق سینی سیستم دسترسی سریع فراهم می‌کند. محیط کاری شما آن را ارائه نمی‌دهد. - + Information اطلاعات - - GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect برای سیستم‌عامل شما در حال آزمایش است و هنوز رسماً منتشر نشده. ممکن است خطاهایی وجود داشته باشد. آن‌ها را در GitHub گزارش دهید. آزمایش خوبی داشته باشید! - + There are still phone calls going on, do you really want to quit? هنوز تماس‌های تلفنی در جریان است. آیا واقعاً می‌خواهید خارج شوید؟ - + Please enter the password for %1: لطفاً رمز عبور %1 را وارد کنید: - + Please enter the recovery key for %1: لطفاً کلید بازیابی %1 را وارد کنید: - + Please enter the password for the SIP account: لطفاً رمز عبور حساب SIP را وارد کنید: - + End all calls پایان دادن به همه تماس‌ها - + + QT_LAYOUT_DIRECTION + QGuiApplication + RTL + + + Do you really want to close this window and terminate all ongoing calls? آیا می‌خواهید این پنجره بسته و همه تماس‌ها پایان یابد؟ - + + Registration failed + ثبت‌نام ناموفق بود + + + + Registration failed with with status %1: %2 + ثبت‌نام با وضعیت %1: %2 ناموفق بود + + + Error خطا - + Fatal Error خطای مهلک @@ -2359,114 +2412,114 @@ MainTabBar - - + + Home خانه - + Conference کنفرانس - + No active conference هیچ کنفرانس فعالی وجود ندارد - + No active call هیچ تماس فعالی وجود ندارد - + Move up انتقال به بالا - + Move tab up انتقال زبانه به بالا - + Moves the currently selected tab up by one زبانه انتخاب‌شده را یک موقعیت به بالا منتقل می‌کند - + Move down انتقال به پایین - + Move tab down انتقال زبانه به پایین - + Moves the currently selected tab down by one زبانه انتخاب‌شده را یک موقعیت به پایین منتقل می‌کند - + Edit ویرایش - + Edit page ویرایش صفحه - + Edit the currently selected dashboard page ویرایش صفحه داشبورد انتخاب‌شده - + Delete حذف - + Delete page حذف صفحه - + Delete the currently selected dashboard page حذف صفحه داشبورد انتخاب‌شده - + Call تماس - + Selected tab زبانه انتخاب‌شده - + The currently selected tab زبانه انتخاب‌شده فعلی - + Selected tab options گزینه‌های زبانه انتخاب‌شده - + The settings of the currently selected tab تنظیمات زبانه انتخاب‌شده فعلی - - + + Settings تنظیمات @@ -2474,17 +2527,17 @@ MenuContactInfo - + Commercial تجاری - + Mobile موبایل - + Home خانه @@ -2492,92 +2545,92 @@ PageCreationWindow - + Create new dashboard page ایجاد صفحه داشبورد جدید - + Edit dashboard page ویرایش صفحه داشبورد - + Name نام - + Page name نام صفحه - + Enter the page name نام صفحه را وارد کنید - + Icon نماد - + Page icon label برچسب نماد صفحه - + Page icon selection انتخاب نماد صفحه - + Select the page icon for the dashboard page انتخاب نماد صفحه داشبورد - + Currently selected page icon option گزینه نماد صفحه انتخاب‌شده - + Cancel لغو - + Cancel page modifcation لغو تغییر صفحه - + Cancel button to exit the page creation/update window دکمه لغو برای خروج از پنجره - + Create ایجاد - + Save ذخیره - + page صفحه - + Confirmation button to create the new dashboard page دکمه تأیید ایجاد صفحه جدید - + Confirmation button to apply changes to the dashboard page دکمه تأیید اعمال تغییرات @@ -2585,42 +2638,42 @@ ParticipantsList - + Participants list فهرست شرکت‌کنندگان - + List of all the participants of the current chat room فهرست همه شرکت‌کنندگان اتاق فعلی - + Chat participant شرکت‌کننده گفتگو - + Selected chat participant: %1 شرکت‌کننده انتخاب‌شده: %1 - + moderator مدیر - - it's you + + it's you این شمایید - + Kick اخراج - + Make moderator تعیین به عنوان مدیر @@ -2628,7 +2681,7 @@ PhoneNumberUtil - + Anonymous ناشناس @@ -2636,42 +2689,42 @@ PreferredIdentityEditWindow - + Phone Number Transmission انتقال شماره تلفن - + Name نام - + Prefix پیشوند - + Identity هویت - + Enabled فعال - + Automatic خودکار - + Delete حذف - + Save ذخیره @@ -2679,1247 +2732,1246 @@ QObject - + Andorra آندورا - + United Arab Emirates امارات متحده عربی - + Afghanistan افغانستان - + Antigua and Barbuda آنتیگوا و باربودا - + Anguilla آنگویلا - + Albania آلبانی - + Armenia ارمنستان - + Angola آنگولا - + Argentina آرژانتین - + American Samoa ساموای آمریکایی - + Austria اتریش - + Australia استرالیا - + Aruba آروبا - + Aland Islands جزایر آلاند - + Azerbaijan آذربایجان - + Bosnia and Herzegovina بوسنی و هرزگوین - + Barbados باربادوس - + Bangladesh بنگلادش - + Belgium بلژیک - + Burkina Faso بورکینافاسو - + Bulgaria بلغارستان - + Bahrain بحرین - + Burundi بوروندی - + Benin بنین - + Saint Barthelemy سن بارتلمی - + Bermuda برمودا - + Brunei برونئی - + Bolivia بولیوی - + Bonaire, Saint Eustatius and Saba بونایر، سینت استاتیوس و سابا - + Brazil برزیل - + Bahamas باهاما - + Bhutan بوتان - + Botswana بوتسوانا - + Belarus بلاروس - + Belize بلیز - + Canada کانادا - + Cocos Islands جزایر کوکوس - + Democratic Republic of the Congo جمهوری دموکراتیک کنگو - + Central African Republic جمهوری آفریقای مرکزی - + Republic of the Congo جمهوری کنگو - + Switzerland سوئیس - + Ivory Coast ساحل عاج - + Cook Islands جزایر کوک - + Chile شیلی - + Cameroon کامرون - + China چین - + Colombia کلمبیا - + Costa Rica کاستاریکا - + Cuba کوبا - + Cape Verde کیپ ورد - + Curacao کوراسائو - + Christmas Island جزیره کریسمس - + Cyprus قبرس - + Czech Republic جمهوری چک - + Germany آلمان - + Djibouti جیبوتی - + Denmark دانمارک - + Dominica دومینیکا - + Dominican Republic جمهوری دومینیکن - + Algeria الجزایر - + Ecuador اکوادور - + Estonia استونی - + Egypt مصر - + Western Sahara صحرای غربی - + Eritrea اریتره - + Spain اسپانیا - + Ethiopia اتیوپی - + Finland فنلاند - + Fiji فیجی - + Falkland Islands جزایر فالکلند - + Micronesia میکرونزی - + Faroe Islands جزایر فارو - + France فرانسه - + Gabon گابن - + United Kingdom بریتانیا - + Grenada گرانادا - + Georgia گرجستان - + French Guiana گویان فرانسوی - + Guernsey گرنزی - + Ghana غنا - + Gibraltar جبل‌الطارق - + Greenland گرینلند - + Gambia گامبیا - + Guinea گینه - + Guadeloupe گوادلوپ - + Equatorial Guinea گینه استوایی - + Greece یونان - + Guatemala گواتمالا - + Guam گوام - + Guinea-Bissau گینه بیسائو - + Guyana گویان - + Hong Kong هنگ‌کنگ - + Heard Island and McDonald Islands جزایر هرد و مک‌دونالد - + Honduras هندوراس - + Croatia کرواسی - + Haiti هائیتی - + Hungary مجارستان - + Indonesia اندونزی - + Ireland ایرلند - + Israel اسرائیل - + Isle of Man جزیره من - + India هند - + British Indian Ocean Territory قلمرو بریتانیا در اقیانوس هند - + Iraq عراق - + Iran ایران - + Iceland ایسلند - + Italy ایتالیا - + Jersey جرزی - + Jamaica جامائیکا - + Jordan اردن - + Japan ژاپن - + Kenya کنیا - + Kyrgyzstan قرقیزستان - + Cambodia کامبوج - + Kiribati کیریباتی - + Comoros کومور - + Saint Kitts and Nevis سنت کیتس و نویس - + North Korea کره شمالی - + South Korea کره جنوبی - + Kuwait کویت - + Cayman Islands جزایر کیمن - + Kazakhstan قزاقستان - + Laos لائوس - + Lebanon لبنان - + Saint Lucia سنت لوسیا - + Liechtenstein لیختن‌اشتاین - + Sri Lanka سری‌لانکا - + Liberia لیبریا - + Lesotho لسوتو - + Lithuania لیتوانی - + Luxembourg لوکزامبورگ - + Latvia لتونی - + Libya لیبی - + Morocco مراکش - + Monaco موناکو - + Moldova مولداوی - + Montenegro مونته‌نگرو - + Saint Martin سن مارتن - + Madagascar ماداگاسکار - + Marshall Islands جزایر مارشال - + Macedonia مقدونیه - + Mali مالی - + Myanmar میانمار - + Mongolia مغولستان - + Macao ماکائو - + Northern Mariana Islands جزایر ماریانای شمالی - + Martinique مارتینیک - + Mauritania موریتانی - + Montserrat مونتسرات - + Malta مالت - + Mauritius موریس - + Maldives مالدیو - + Malawi مالاوی - + Mexico مکزیک - + Malaysia مالزی - + Mozambique موزامبیک - + Namibia نامیبیا - + New Caledonia کالدونیای جدید - + Niger نیجر - + Norfolk Island جزیره نورفولک - + Nigeria نیجریه - + Nicaragua نیکاراگوئه - + Netherlands هلند - + Norway نروژ - + Nepal نپال - + Nauru نائورو - + Niue نیوئه - + New Zealand نیوزیلند - + Oman عمان - + Panama پاناما - + Peru پرو - + French Polynesia پلی‌نزی فرانسه - + Papua New Guinea پاپوا گینه نو - + Philippines فیلیپین - + Pakistan پاکستان - + Poland لهستان - + Saint Pierre and Miquelon سن پیر و میکلون - + Pitcairn پیتکرن - + Puerto Rico پورتوریکو - + Palestinian Territory سرزمین‌های فلسطینی - + Portugal پرتغال - + Palau پالائو - + Paraguay پاراگوئه - + Qatar قطر - + Reunion رئونیون - + Romania رومانی - + Serbia صربستان - + Russia روسیه - + Rwanda رواندا - + Saudi Arabia عربستان سعودی - + Solomon Islands جزایر سلیمان - + Seychelles سیشل - + Sudan سودان - + Sweden سوئد - + Singapore سنگاپور - + Saint Helena سنت هلنا - + Slovenia اسلوونی - + Svalbard and Jan Mayen اسوالبارد و یان ماین - + Slovakia اسلواکی - + Sierra Leone سیرالئون - + San Marino سن مارینو - + Senegal سنگال - + Somalia سومالی - + Suriname سورینام - + South Sudan سودان جنوبی - + Sao Tome and Principe سائوتومه و پرینسیپ - + El Salvador السالوادور - + Sint Maarten سینت مارتن - + Syria سوریه - + Swaziland سوازیلند - + Turks and Caicos Islands جزایر تورکس و کایکوس - + Chad چاد - + Togo توگو - + Thailand تایلند - + Tajikistan تاجیکستان - + Tokelau توکلائو - + East Timor تیمور شرقی - + Turkmenistan ترکمنستان - + Tunisia تونس - + Tonga تونگا - + Turkey ترکیه - + Trinidad and Tobago ترینیداد و توباگو - + Tuvalu تووالو - + Taiwan تایوان - + Tanzania تانزانیا - + Ukraine اوکراین - + Uganda اوگاندا - + United States Minor Outlying Islands جزایر دورافتاده کوچک ایالات متحده - + United States ایالات متحده - + Uruguay اروگوئه - + Uzbekistan ازبکستان - + Vatican واتیکان - + Saint Vincent and the Grenadines سنت وینسنت و گرنادین‌ها - + Venezuela ونزوئلا - + British Virgin Islands جزایر ویرجین بریتانیا - + U.S. Virgin Islands جزایر ویرجین آمریکا - + Vietnam ویتنام - + Vanuatu وانواتو - + Wallis and Futuna والیس و فوتونا - + Samoa ساموا - + Yemen یمن - + Mayotte مایوت - + South Africa آفریقای جنوبی - + Zambia زامبیا - + Zimbabwe زیمبابوه - + There are %n active call(s). calls %n تماس فعال وجود دارد. - %n تماس فعال وجود دارد. ResponseTreeModel - - - - + + + + Additional Information اطلاعات بیشتر @@ -3927,27 +3979,27 @@ RingToneFactory - + ringTone 425,0,1000,4000,0 - + busyTone 425,0,480,480,0 - + congestionTone 425,0,240,240,0 - + zip 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 - + endTone 425,0,200,200,200,200,200,200,-1 @@ -3955,40 +4007,45 @@ SIPAccount - - 'userUri' is no valid SIP URI: %1 - 'userUri' یک SIP URI معتبر نیست: %1 + + 'userUri' is no valid SIP URI: %1 + 'userUri' یک SIP URI معتبر نیست: %1 - - 'userUri' is required - 'userUri' الزامی است + + 'userUri' is required + 'userUri' الزامی است - - 'registrarUri' is no valid SIP URI: %1 - 'registrarUri' یک SIP URI معتبر نیست: %1 + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' یک SIP URI معتبر نیست: %1 - - 'registrarUri' is required - 'registrarUri' الزامی است + + 'registrarUri' is required + 'registrarUri' الزامی است - - 'proxies' contains invalid SIP URI entry: %1 - 'proxies' یک SIP URI نامعتبر دارد: %1 + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' یک SIP URI نامعتبر دارد: %1 - + Failed to create %1: %2 ایجاد %1 ناموفق بود: %2 + + + Failed to persist SIP credentials: %1 + ثبت نام کاربری SIP ناموفق بود: %1 + SIPBuddy - + %1 is now available %1 اکنون در دسترس است @@ -3996,12 +4053,12 @@ SIPCall - + Active call with %1 تماس فعال با %1 - + Hang up قطع تماس @@ -4009,34 +4066,34 @@ SIPCallManager - + %1 is calling %1 تماس می‌گیرد - + %1 (%2) is calling %1 (%2) تماس می‌گیرد - - + + Accept پذیرفتن - + Call back تماس مجدد - - + + Reject رد کردن - + Missed call from %1 تماس از دست رفته از %1 @@ -4044,7 +4101,7 @@ SIPManager - + New Identity هویت جدید @@ -4052,18 +4109,18 @@ SIPTemplate - - + + Failed to write to %1 نوشتن در %1 ناموفق بود - + Failed to copy %1 to the config space کپی %1 به فضای تنظیمات ناموفق بود - + Source file %1 does not exist فایل منبع %1 وجود ندارد @@ -4071,7 +4128,7 @@ SearchBox - + Search number جستجوی شماره @@ -4079,12 +4136,12 @@ SearchCategoryItem - + Search result category filter %1 فیلتر دسته نتایج جستجو %1 - + Filter for the individual search result items by category فیلتر نتایج جستجو بر اساس دسته @@ -4092,22 +4149,22 @@ SearchCategoryList - + Contacts مخاطبان - + Messages پیام‌ها - + Rooms and Teams اتاق‌ها و تیم‌ها - + Files فایل‌ها @@ -4115,42 +4172,42 @@ SearchDial - + Select number انتخاب شماره - + Activate search field فعال‌سازی فیلد جستجو - + Number or contact شماره یا مخاطب - + Clear search field پاک کردن فیلد جستجو - + Default پیش‌فرض - + Auto خودکار - + Preferred identity هویت ترجیحی - + Select the preferred identity for outgoing calls انتخاب هویت ترجیحی برای تماس‌های خروجی @@ -4158,12 +4215,12 @@ SearchField - + Search for contacts or room names... جستجوی مخاطبان یا نام اتاق‌ها... - + Clear search field پاک کردن فیلد جستجو @@ -4171,12 +4228,12 @@ SearchResultCategory - + Search result category %1 دسته نتایج جستجو %1 - + Divider for the individual search result items by category جداکننده نتایج جستجو بر اساس دسته @@ -4184,12 +4241,12 @@ SearchResultItem - + Search result نتیجه جستجو - + Currently selected search result نتیجه جستجوی انتخاب‌شده @@ -4197,17 +4254,17 @@ SearchResultNumberItem - + Phone number شماره تلفن - + Selected favorite number %1 شماره مورد علاقه انتخاب‌شده %1 - + Selected phone number %1 شماره تلفن انتخاب‌شده %1 @@ -4215,52 +4272,52 @@ SearchResultPopup - + Search filter and identity selection فیلتر جستجو و انتخاب هویت - + Select search filter to be applied, as well as the outgoing identity انتخاب فیلتر جستجو و هویت خروجی - + Outgoing identity هویت خروجی - + Search results نتایج جستجو - + All search results will be listed here in their respective categories همه نتایج جستجو اینجا بر اساس دسته فهرست می‌شوند - + Direct dial شماره‌گیری مستقیم - - Call "%1" + + Call "%1" تماس با «%1» - - Open room "%1" + + Open room "%1" باز کردن اتاق «%1» - + History تاریخچه - + Contacts مخاطبان @@ -4268,314 +4325,314 @@ SettingsPage - + Settings تنظیمات - + Show chat messages as desktop notifications نمایش پیام‌های گفتگو به عنوان اعلان - + Enable USB headset driver [%1] فعال‌سازی درایور هدست USB [%1] - + not detected شناسایی نشد - + Show dial window on USB headset pick up نمایش پنجره شماره‌گیری هنگام برداشتن هدست - + Color scheme طرح رنگی - + System default پیش‌فرض سیستم - + Light روشن - + Dark تیره - + Inverse Accept / Reject buttons معکوس کردن دکمه‌های پذیرش/رد - + Use dark mode tray icon استفاده از نماد سینی در حالت تاریک - + Disable USB headset mute state propagation غیرفعال‌سازی همگام‌سازی وضعیت بی‌صدا هدست - + Reload contacts from LDAP بارگذاری مجدد مخاطبان از LDAP - + Phoning تلفن - + Show main window on startup نمایش پنجره اصلی هنگام راه‌اندازی - + Disable synchronisation with the system mute state غیرفعال‌سازی همگام‌سازی با وضعیت بی‌صدای سیستم - - + + restart required راه‌اندازی مجدد لازم است - + Appearance ظاهر - + Use custom window decoration استفاده از تزئین سفارشی پنجره - + Theme selection box کادر انتخاب پوسته - + Select the UI theme انتخاب پوسته رابط کاربری - + Currently selected theme option گزینه پوسته انتخاب‌شده - + Signalling busy when a call is active اعلام اشغال هنگام فعال بودن تماس - + Rules for telephone number transmission قوانین انتقال شماره تلفن - + Standard preferred identity هویت ترجیحی استاندارد - - + + Default پیش‌فرض - - + + Auto خودکار - - + + Prefererred identity selection انتخاب هویت ترجیحی - - + + Select the preferred identity انتخاب هویت ترجیحی - + Currently selected identity option گزینه هویت انتخاب‌شده - + No preferred identities yet. هنوز هویت ترجیحی وجود ندارد. - + Currently highlighted preferred identity. Tap to edit. هویت ترجیحی فعلی. برای ویرایش ضربه بزنید. - + Standard استاندارد - + Add identity افزودن هویت - + Add a new preferred identity entry افزودن هویت ترجیحی جدید - + Audio settings تنظیمات صدا - + Input device دستگاه ورودی - + Input device selection انتخاب دستگاه ورودی - + Select the input device to be used انتخاب دستگاه ورودی - + Currently selected input option گزینه ورودی انتخاب‌شده - + Output device دستگاه خروجی - + Output device selection انتخاب دستگاه خروجی - + Select the output device to be used انتخاب دستگاه خروجی - + Currently selected output option گزینه خروجی انتخاب‌شده - + Output device for ring tone دستگاه خروجی برای زنگ - + Currently selected ring output option گزینه خروجی زنگ انتخاب‌شده - + Ring tone زنگ - + Prefer USB headset ring sound if available استفاده از زنگ هدست USB در صورت در دسترس بودن - + Reset ring tone بازنشانی زنگ - + Reset the ring tone to its default option بازنشانی زنگ به حالت پیش‌فرض - + Pick ring tone انتخاب زنگ - + Select the ring tone you want to use for incoming calls انتخاب زنگ برای تماس‌های ورودی - - + + Currently set to: مقدار فعلی: - + Ring tone volume صدای زنگ - - + + Adjust %1 تنظیم %1 - + %1 % %1 ٪ - + Pause between ring tones [s] مکث بین زنگ‌ها [ثانیه] - + Debugging اشکال‌زدایی - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. برای شروع اشکال‌زدایی این دکمه را فشار دهید. برنامه مجدداً راه‌اندازی شده و اطلاعات تشخیصی ثبت می‌کند. این حالت ۵ دقیقه است. - + Start debug run (restart app) شروع اشکال‌زدایی (راه‌اندازی مجدد برنامه) - + Download debug information دانلود اطلاعات اشکال‌زدایی @@ -4583,62 +4640,62 @@ ShortcutsWindow - + Shortcuts میانبرها - + Shortcut key: %1 کلید میانبر: %1 - + Local shortcuts (work only when app is focused) میانبرهای محلی (فقط هنگام فوکوس برنامه) - + Local shortcuts میانبرهای محلی - + Ctrl + F Ctrl + F - + Activates the global search field فیلد جستجوی سراسری را فعال می‌کند - + F11 F11 - + Toggles between fullsceen and normal window mode بین حالت تمام‌صفحه و معمولی جابجا می‌شود - + Ctrl + Shift + M Ctrl + Shift + M - + Toggles audio mute صدا را روشن/خاموش می‌کند - + Global shortcuts (work from anywhere) میانبرهای سراسری (از هر جایی) - + Global shortcuts میانبرهای سراسری @@ -4646,143 +4703,143 @@ SipTemplateWizard - + Initial configuration پیکربندی اولیه - + Error: %1 خطا: %1 - + SIP wizard notification اعلان راهنمای SIP - + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. GOnnect پیکربندی SIP پیدا نکرد. یک الگو انتخاب کنید و در صورت نیاز فایل را ویرایش کنید. - + Please pick: لطفاً انتخاب کنید: - + Select SIP template انتخاب الگوی SIP - + Select the SIP template to be used انتخاب الگوی SIP - + Currently selected SIP template الگوی SIP انتخاب‌شده - + Next بعدی - + Continue setup ادامه راه‌اندازی - + Confirmation button to continue the setup دکمه تأیید برای ادامه راه‌اندازی - + Template field list فهرست فیلدهای الگو - + List of all the available SIP template options فهرست همه گزینه‌های الگوی SIP - + SIP template option گزینه الگوی SIP - + Currently selected SIP template option گزینه الگوی SIP انتخاب‌شده - + Display name of the SIP template option نام نمایشی گزینه الگوی SIP - + Description of the SIP template option توضیح گزینه الگوی SIP - + Back برگشت - + Back button to return to the template selection menu دکمه برگشت به منوی انتخاب الگو - - + + Finish پایان - + Confirmation button to apply the changes to the SIP template دکمه تأیید اعمال تغییرات الگو - + Successful configuration file creation فایل پیکربندی با موفقیت ایجاد شد - + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. فایل پیکربندی ایجاد شد. بررسی کنید آیا تغییری لازم است و GOnnect را مجدداً راه‌اندازی کنید. - + The configuration has been saved to: پیکربندی ذخیره شد در: - + Copy to clipboard کپی در کلیپ‌بورد - + Copy the full path of the configuration file to the clipboard کپی مسیر کامل فایل پیکربندی - + Finish wizard پایان راهنما - + Finish the SIP configuration wizard پایان راهنمای پیکربندی SIP @@ -4790,27 +4847,27 @@ StateManager - + Show dial window and focus search field نمایش پنجره شماره‌گیری و فوکوس فیلد جستجو - + End all calls پایان دادن به همه تماس‌ها - + Redial last outgoing call شماره‌گیری مجدد آخرین تماس خروجی - + Toggle hold تغییر وضعیت نگه‌داری - + Phone calls are active تماس‌ها فعال هستند @@ -4818,48 +4875,48 @@ SystemTrayMenu - - + + Dial... شماره‌گیری... - + Not registered... ثبت‌نشده... - + Settings تنظیمات - + About درباره - + Quit خروج - + End conference پایان کنفرانس - + Call with %1 has ended تماس با %1 پایان یافت - + Hang up call with %1 قطع تماس با %1 - + Accept call with %1 پذیرفتن تماس از %1 @@ -4867,32 +4924,32 @@ TemplateFieldFile - + File path مسیر فایل - + Enter the file path for %1 مسیر فایل را برای %1 وارد کنید - + Choose... انتخاب... - + Open file picker باز کردن انتخاب‌گر فایل - + Select the file that should be used for %1 انتخاب فایل برای %1 - + Certificate files (%1) فایل‌های گواهینامه (%1) @@ -4900,12 +4957,12 @@ TemplateFieldText - + Text input ورودی متن - + Enter the desired value for %1 مقدار دلخواه را برای %1 وارد کنید @@ -4913,7 +4970,7 @@ Toggler - + Failed to toggle the state of %1. تغییر وضعیت %1 ناموفق بود. @@ -4921,17 +4978,17 @@ TogglerList - + Toggler list فهرست سوئیچ‌ها - + List of items that can be toggled فهرست موارد قابل سوئیچ - + Toggle %1 سوئیچ %1 @@ -4939,7 +4996,7 @@ VerticalLevelMeter - + Level meter نمایشگر سطح @@ -4947,7 +5004,7 @@ VideoDeviceMenu - + Virtual background پس‌زمینه مجازی @@ -4955,36 +5012,33 @@ ViewHelper - + Save File ذخیره فایل - + %n minute(s) %n دقیقه - %n دقیقه - + 1 hour and %n minute(s) ۱ ساعت و %n دقیقه - ۱ ساعت و %n دقیقه - + %n hour(s) %n ساعت - %n ساعت - + Audio Files (%1) فایل‌های صوتی (%1) @@ -4992,22 +5046,22 @@ VolumePopup - + Adjust the volume تنظیم صدا - + Unmute باز کردن صدا - + Mute بی‌صدا کردن - + Open audio settings باز کردن تنظیمات صدا @@ -5015,147 +5069,147 @@ WidgetSelectionWindow - + Add widget افزودن ابزارک - + Widget ابزارک - + Widget selection header عنوان انتخاب ابزارک - + Date Events رویدادها - + List of upcoming appointments فهرست قرارهای آینده - + Favorites علاقه‌مندی‌ها - + Quick dial for your favorite contacts and conferences شماره‌گیری سریع مخاطبان مورد علاقه - + History تاریخچه - + Web View نمای وب - + A web-based content display نمایش محتوای مبتنی بر وب - + Widget selection انتخاب ابزارک - + Select the widget that should be added to the current dashboard page انتخاب ابزارک برای افزودن به صفحه فعلی - + Currently selected widget option گزینه ابزارک انتخاب‌شده - + Accept all certificates پذیرفتن همه گواهینامه‌ها - + Confirm widget selection تأیید انتخاب ابزارک - + Confirmation button to create and add the selected widget to the current dashboard دکمه ایجاد و افزودن ابزارک - + Searchable call and conference history تاریخچه قابل جستجوی تماس‌ها و کنفرانس‌ها - + Title عنوان - + URL URL - + URL (dark mode) URL (حالت تاریک) - + Settings text input ورودی متن تنظیمات - + Input for widget setting %1 ورودی برای تنظیم ابزارک %1 - + Settings checkbox چک‌باکس تنظیمات - + Checkbox for widget setting %1 چک‌باکس برای تنظیم ابزارک %1 - + Widget setting %1 تنظیم ابزارک %1 - + Cancel لغو - + Cancel widget selection لغو انتخاب ابزارک - + Cancel button to exit widget selection selection without changes دکمه لغو برای خروج بدون تغییر - + Add افزودن @@ -5163,24 +5217,24 @@ WindowHeader - + GOnnect window header عنوان پنجره GOnnect - + Minimize کوچک کردن - + Maximize بزرگ کردن - + Close GOnnect window بستن پنجره GOnnect - \ No newline at end of file + diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts index b9cceaa4..115b9d6a 100644 --- a/i18n/gonnect_fr.ts +++ b/i18n/gonnect_fr.ts @@ -30,8 +30,8 @@ - Copy the currently used version of Gonnect to the clipboard - Copier la version actuelle de GOnnect dans le presse-papiers + Copy the currently used version number of GOnnect to the clipboard + Copiez le numéro de version actuellement utilisé par GOnnect dans le presse-papiers @@ -85,6 +85,14 @@ Section de réponse extensible + + AddressBookManager + + + Failed to persist address book credentials: %1 + Échec de la persistance des informations d'identification du carnet d'adresses : %1 + + AudioDeviceMenu @@ -222,16 +230,24 @@ AudioManager - + Default input Entrée par défaut - + Default output Sortie par défaut + + AuthManager + + + Failed to persist jitsi refresh token: %1 + Échec de la persistance du jeton d'actualisation Jitsi : %1 + + AvatarImage @@ -840,6 +856,19 @@ Liste des options configurables + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + Échec de la persistance du code de récupération du chat : %1 + + + + Failed to persist chat access token: %1 + Échec de la persistance du jeton d'accès au chat : %1 + + ChatMessageBox @@ -953,7 +982,7 @@ Sélectionner un emoji - + Enter chat message... Saisir un message... @@ -1288,14 +1317,14 @@ Credentials - - storing credentials failed: %1 - Échec de l'enregistrement des identifiants : %1 + + Storing credentials for %1 failed: %2 + Échec de l'enregistrement des informations d'identification pour %1 : %2 - - reading credentials failed: %1 - Échec de la lecture des identifiants : %1 + + reading credentials for %1 failed: %2 + Échec de la lecture des données de connexion pour %1 : %2 @@ -1344,6 +1373,14 @@ Fermer la fenêtre GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + Échec de la persistance des informations d'identification du calendrier : %1 + + DateEventManager @@ -1935,7 +1972,7 @@ GonnectWindow - + Home Accueil @@ -2201,32 +2238,38 @@ JitsiConnector - + New chat message Nouveau message de discussion - + Unnamed participant Participant sans nom - + Active conference Conférence active - + Hang up Raccrocher - + %1 has joined the conference %1 a rejoint la conférence - + + + Failed to persist room password: %1 + Échec de la persistance du mot de passe de la salle : %1 + + + %1 has left the conference %1 a quitté la conférence @@ -2257,30 +2300,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - Échec de l'initialisation de la connexion LDAP - - - - - + + + LDAP error: %1 Erreur LDAP : %1 - - + + Parse error: %1 Erreur d'analyse : %1 - + LDAP timeout: %1 Délai LDAP dépassé : %1 - + + Failed to initialize LDAP connection: %1 Échec de l'initialisation de la connexion LDAP : %1 @@ -2296,62 +2335,78 @@ Main - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + No system tray available Aucune zone de notification disponible - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect permet un accès rapide aux fonctionnalités via la zone de notification. Votre environnement de bureau n'en fournit pas. - + Information Information - + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect est en cours de test pour votre système d'exploitation et n'a pas encore été officiellement publié. Il n'y a pas encore de parité de fonctionnalités avec la version Linux, et il peut y avoir des bogues que nous n'avons pas encore trouvés. N'hésitez pas à signaler ces problèmes sur GitHub. Bonne exploration ! - + There are still phone calls going on, do you really want to quit? Des appels téléphoniques sont encore en cours. Voulez-vous vraiment quitter ? - + Please enter the password for %1: Veuillez saisir le mot de passe pour %1 : - + Please enter the recovery key for %1: Veuillez saisir la clé de récupération pour %1 : - + End all calls Terminer tous les appels - + Do you really want to close this window and terminate all ongoing calls? Voulez-vous vraiment fermer cette fenêtre et terminer tous les appels en cours ? - + Please enter the password for the SIP account: Veuillez saisir le mot de passe du compte SIP : - + + Registration failed + Échec de l'enregistrement + + + + Registration failed with with status %1: %2 + Échec de l'enregistrement avec le statut %1 : %2 + + + Error Erreur - + Fatal Error Erreur fatale @@ -3955,35 +4010,40 @@ SIPAccount - + 'userUri' is no valid SIP URI: %1 'userUri' n'est pas une URI SIP valide : %1 - + 'userUri' is required 'userUri' est obligatoire - + 'registrarUri' is no valid SIP URI: %1 'registrarUri' n'est pas une URI SIP valide : %1 - + 'registrarUri' is required 'registrarUri' est obligatoire - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contient une entrée URI SIP invalide : %1 - + Failed to create %1: %2 Échec de la création de %1 : %2 + + + Failed to persist SIP credentials: %1 + Échec de la persistance des informations d'identification SIP : %1 + SIPBuddy @@ -3996,12 +4056,12 @@ SIPCall - + Active call with %1 Appel actif avec %1 - + Hang up Raccrocher @@ -4044,7 +4104,7 @@ SIPManager - + New Identity Nouvelle identité diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts index 33bafc0e..7b65ad1d 100644 --- a/i18n/gonnect_it.ts +++ b/i18n/gonnect_it.ts @@ -30,8 +30,8 @@ - Copy the currently used version of Gonnect to the clipboard - Copia la versione attualmente utilizzata di GOnnect negli appunti + Copy the currently used version number of GOnnect to the clipboard + Copia il numero di versione attualmente in uso di GOnnect negli appunti @@ -85,6 +85,14 @@ Sezione di risposta espandibile + + AddressBookManager + + + Failed to persist address book credentials: %1 + Impossibile salvare le credenziali della rubrica: %1 + + AudioDeviceMenu @@ -222,16 +230,24 @@ AudioManager - + Default input Ingresso predefinito - + Default output Uscita predefinita + + AuthManager + + + Failed to persist jitsi refresh token: %1 + Impossibile mantenere il token di aggiornamento Jitsi: %1 + + AvatarImage @@ -840,6 +856,19 @@ Elenco delle opzioni configurabili + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + Impossibile salvare il codice di recupero della chat: %1 + + + + Failed to persist chat access token: %1 + Impossibile mantenere il token di accesso alla chat: %1 + + ChatMessageBox @@ -953,7 +982,7 @@ Seleziona emoji - + Enter chat message... Scrivi un messaggio... @@ -1288,14 +1317,14 @@ Credentials - - storing credentials failed: %1 - Salvataggio delle credenziali non riuscito: %1 + + Storing credentials for %1 failed: %2 + Impossibile salvare i dati di accesso per %1: %2 - - reading credentials failed: %1 - Lettura delle credenziali non riuscita: %1 + + reading credentials for %1 failed: %2 + Impossibile leggere i dati di accesso per %1: %2 @@ -1344,6 +1373,14 @@ Chiudi la finestra GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + Impossibile salvare le credenziali del calendario: %1 + + DateEventManager @@ -1935,7 +1972,7 @@ GonnectWindow - + Home Home @@ -2201,32 +2238,38 @@ JitsiConnector - + New chat message Nuovo messaggio chat - + Unnamed participant Partecipante senza nome - + Active conference Conferenza attiva - + Hang up Riaggancia - + %1 has joined the conference %1 ha partecipato alla conferenza - + + + Failed to persist room password: %1 + Impossibile memorizzare la password della stanza: %1 + + + %1 has left the conference %1 ha abbandonato la conferenza @@ -2257,30 +2300,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - Inizializzazione della connessione LDAP non riuscita - - - - - + + + LDAP error: %1 Errore LDAP: %1 - - + + Parse error: %1 Errore di analisi: %1 - + LDAP timeout: %1 Timeout LDAP: %1 - + + Failed to initialize LDAP connection: %1 Inizializzazione della connessione LDAP non riuscita: %1 @@ -2296,62 +2335,78 @@ Main - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + No system tray available Nessuna area di notifica disponibile - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect consente un accesso rapido alle funzionalità tramite l'area di notifica. Il tuo ambiente desktop non ne dispone. - + Information Informazioni - + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect è in fase di test per il tuo sistema operativo e non è ancora stato rilasciato ufficialmente. Al momento non c'è parità di funzionalità con la versione Linux, e potrebbero esserci bug non ancora trovati. Sei invitato a segnalare questi problemi su GitHub. Buon test! - + There are still phone calls going on, do you really want to quit? Ci sono ancora chiamate telefoniche in corso. Vuoi davvero uscire? - + Please enter the password for %1: Inserisci la password per %1: - + Please enter the recovery key for %1: Inserisci la chiave di ripristino per %1: - + End all calls Termina tutte le chiamate - + Do you really want to close this window and terminate all ongoing calls? Vuoi davvero chiudere questa finestra e terminare tutte le chiamate in corso? - + Please enter the password for the SIP account: Inserisci la password per l'account SIP: - + + Registration failed + Registrazione non riuscita + + + + Registration failed with with status %1: %2 + Registrazione non riuscita con stato %1: %2 + + + Error Errore - + Fatal Error Errore fatale @@ -3955,35 +4010,40 @@ SIPAccount - + 'userUri' is no valid SIP URI: %1 'userUri' non è un URI SIP valido: %1 - + 'userUri' is required 'userUri' è obbligatorio - + 'registrarUri' is no valid SIP URI: %1 'registrarUri' non è un URI SIP valido: %1 - + 'registrarUri' is required 'registrarUri' è obbligatorio - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contiene una voce URI SIP non valida: %1 - + Failed to create %1: %2 Impossibile creare %1: %2 + + + Failed to persist SIP credentials: %1 + Impossibile mantenere le credenziali SIP: %1 + SIPBuddy @@ -3996,12 +4056,12 @@ SIPCall - + Active call with %1 Chiamata attiva con %1 - + Hang up Riaggancia @@ -4044,7 +4104,7 @@ SIPManager - + New Identity Nuova identità diff --git a/i18n/gonnect_ru.ts b/i18n/gonnect_ru.ts index a5b4a933..aaffea25 100644 --- a/i18n/gonnect_ru.ts +++ b/i18n/gonnect_ru.ts @@ -4,65 +4,65 @@ AboutWindow - + About О программе - + GOnnect headline Заголовок GOnnect - + GOnnect version Версия GOnnect - + Version: v%1 Версия: v%1 - + Copy to clipboard Копировать в буфер обмена - - Copy the currently used version of Gonnect to the clipboard - Скопировать текущую версию GOnnect в буфер обмена + + Copy the currently used version number of GOnnect to the clipboard + Скопируйте номер текущей версии GOnnect в буфер обмена - - - - + + + + Homepage Домашняя страница - + Visit the project homepage Посетить домашнюю страницу проекта - + Bug Tracker Трекер ошибок - + Visit the project bug tracker Посетить трекер ошибок проекта - + Documentation Документация - + Visit the online project documentation Посетить онлайн-документацию проекта @@ -70,25 +70,33 @@ AdditionalInfo - + Additional caller related information Дополнительная информация об абоненте - + List of informational items regarding the caller, such as open support tickets Список информации об абоненте, например открытые тикеты поддержки - + Expandable response section Раскрываемый раздел ответа + + AddressBookManager + + + Failed to persist address book credentials: %1 + Не удалось сохранить учетные данные адресной книги: %1 + + AudioDeviceMenu - + Default По умолчанию @@ -96,117 +104,117 @@ AudioEnvWindow - + Unknown audio environment Неизвестная аудиосреда - + Audio environment error Ошибка аудиосреды - + No fitting audio environment could be found. Please select the desired audio devices. Подходящая аудиосреда не найдена. Выберите нужные аудиоустройства. - + Input device selection header Заголовок выбора устройства ввода - + Header for the input device selection below Заголовок для выбора устройства ввода ниже - + Input device Устройство ввода - + Input device selection box Поле выбора устройства ввода - + Select the input device that should be used Выбрать устройство ввода - + Currently selected input device Текущее устройство ввода - + Output device selection header Заголовок выбора устройства вывода - + Header for the output device selection below Заголовок для выбора устройства вывода ниже - + Output device Устройство вывода - + Output device selection box Поле выбора устройства вывода - + Select the output device that should be used Выбрать устройство вывода - + Currently selected output device Текущее устройство вывода - + Ring tone output device Устройство вывода мелодии звонка - + Output device for ring tone Устройство вывода для мелодии звонка - + Ring tone output device selection box Поле выбора устройства вывода мелодии - + Select the output device that should be used for playing the ring tone Выбрать устройство для воспроизведения мелодии звонка - + Currently selected ring tone output device Текущее устройство вывода мелодии - + Ok ОК - + Close audio environment selection Закрыть выбор аудиосреды - + Confirmation button to leave the audio environment selection window Кнопка подтверждения для выхода из окна выбора аудиосреды @@ -214,7 +222,7 @@ AudioLevelButton - + Change volume Изменить громкость @@ -222,20 +230,28 @@ AudioManager - + Default input Вход по умолчанию - + Default output Выход по умолчанию + + AuthManager + + + Failed to persist jitsi refresh token: %1 + Не удалось сохранить токен обновления Jitsi: %1 + + AvatarImage - + Initials of this contact Инициалы контакта @@ -243,7 +259,7 @@ BaseDialog - + Dialog Диалог @@ -251,17 +267,17 @@ BasePage - + Base dashboard page grid Базовая сетка страницы панели управления - + Canvas for editable dashboard pages Холст для редактируемых страниц панели управления - + Add widgets Добавить виджеты @@ -269,32 +285,32 @@ BaseWidget - + Drag widget Перетащить виджет - + Change the position of the widget Изменить положение виджета - + Remove widget Удалить виджет - + Remove the currently selected widget from the dashboard Удалить выбранный виджет с панели управления - + Resize widget Изменить размер виджета - + Resize the widget according to the mouse direction Изменить размер виджета по направлению мыши @@ -302,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Граница перетаскивания - + Top left drag border for window resize operations Граница перетаскивания вверху слева - + Top drag border for window resize operations Верхняя граница изменения размера окна - + Top right border for window resize operations Граница вверху справа - + Right drag border for window resize operations Правая граница изменения размера окна - + Bottom right drag border for window resize operations Граница внизу справа - + Bottom drag border for window resize operations Нижняя граница изменения размера окна - + Bottom left drag border for window resize operations Граница внизу слева - + Left drag border for window resize operations Левая граница изменения размера окна @@ -357,7 +373,7 @@ BottomStatusBar - + Status bar Строка состояния @@ -365,27 +381,27 @@ BurgerMenu - + Toggle fullscreen Полноэкранный режим - + Shortcuts... Горячие клавиши... - + Customize UI Настроить интерфейс - + About... О программе... - + Quit Выход @@ -393,12 +409,12 @@ Call - + Conference Конференция - + Drag bar Панель перетаскивания @@ -406,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 кГц - + Transmit Передача - + Call security level Уровень безопасности звонка - + Security level of the ongoing call Уровень безопасности текущего звонка - + Call security details Сведения о безопасности звонка - + Detailed call security status: %1 / %2 Подробный статус безопасности: %1 / %2 - + signaling encrypted сигнализация зашифрована - + signaling unencrypted сигнализация не зашифрована - + media encrypted медиа зашифровано - + media unencrypted медиа не зашифровано - + Call quality Качество звонка - + Quality of the ongoing call Качество текущего звонка - + Transmission statistics Статистика передачи - - + + Call quality metrics Метрики качества звонка - - + + MOS MOS - - + + Mean opinion score Средняя оценка качества (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Числовая метрика качества голосовой связи на стороне передачи: %1 - - + + Packet loss Потеря пакетов - + %1% of packets lost in transmission %1% пакетов потеряно при передаче - - + + Jitter Джиттер - + Amount of transmission side jitter: %1 Джиттер на стороне передачи: %1 - - + + Effective delay Эффективная задержка - + Effective transmission side call delay: %1 Эффективная задержка на стороне передачи: %1 - + Receiver statistics Статистика приёма - + Receive Приём - + Numerical metric assessing receiver-side voice/video call quality: %1 Числовая метрика качества на стороне приёма: %1 - + %1% of packets lost in receival %1% пакетов потеряно при приёме - + Amount of receiver side jitter: %1 Джиттер на стороне приёма: %1 - + Effective receiver side call delay: %1 Эффективная задержка на стороне приёма: %1 - + Codec Кодек - + Audio codec Аудиокодек - + The currently used audio codec and frequency: %1 Текущий аудиокодек и частота: %1 - + Elapsed call time Прошедшее время звонка - + The duration in seconds the call has been active for: %1 Продолжительность звонка в секундах: %1 - + Screen Экран - + Screensharing control Управление демонстрацией экрана - + Start sharing your screen Начать демонстрацию экрана - + Camera Камера - + Camera control Управление камерой - + Enable your camera Включить камеру - + Resume Продолжить - + Hold Удержание - + Resume call Продолжить звонок - + Hold call Удержать звонок - + Update the call hold state Обновить состояние удержания - + Micro Микрофон - + Input control Управление входом - + Set the mute state of the current input device Установить режим отключения звука устройства ввода - + Output Выход - + Output control Управление выходом - + Change the current output devices Изменить текущие устройства вывода - + Accept call Принять звонок - + Hangup call Завершить звонок @@ -680,47 +696,47 @@ CallDetails - + SIP call status code Код состояния SIP-звонка - + The current status code of the call: %1 Текущий код состояния звонка: %1 - + Jitsi Meet Jitsi Meet - + Switch to a Jitsi Meet session Перейти к сеансу Jitsi Meet - + Unhold Снять с удержания - + Hold Удержание - + Toggle the hold state to %1 Переключить удержание на %1 - + Accept call Принять звонок - + Hangup call Завершить звонок @@ -728,17 +744,17 @@ CallHistory - + Failed to create directory %1 to store the call history database. Не удалось создать каталог %1 для базы данных истории звонков. - + Failed to open call history database: %1 Не удалось открыть базу данных истории звонков: %1 - + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. База данных истории звонков несовместима. Удалите %1 и перезапустите приложение. @@ -746,22 +762,22 @@ CallItem - + Call Звонок - + Selected call %1 - contact %2, company %3, location %4/%5, number %6 Выбранный звонок %1 — контакт %2, компания %3, место %4/%5, номер %6 - + Hangup button Кнопка завершения - + Pressing this will end the call Нажатие завершит звонок @@ -769,18 +785,18 @@ CallList - + Drag callers onto each other to transfer call Перетащите абонентов друг на друга для переадресации - + List of active calls Список активных звонков - - + + Create conference Создать конференцию @@ -788,20 +804,21 @@ CallSideBar - + Chat Чат - + Person(s) %n участник - %n участников + %n участника + %n участники - + Info Сведения @@ -809,17 +826,17 @@ CallerBigAvatar - + Caller name Имя звонящего - + is calling... звонит... - + Calling... Вызов... @@ -827,7 +844,7 @@ CallsModel - + unknown number неизвестный номер @@ -835,25 +852,38 @@ CardList - + List of configurable options Список настраиваемых параметров + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + Не удалось сохранить код восстановления чата: %1 + + + + Failed to persist chat access token: %1 + Не удалось сохранить токен доступа к чату: %1 + + ChatMessageBox - + Message Сообщение - + Type message Введите сообщение - + Enter the chat text message Введите текстовое сообщение чата @@ -861,17 +891,17 @@ ChatMessageList - + Chat message list Список сообщений чата - + List of all chat messages of the current chat room Список всех сообщений текущей комнаты чата - + Auto scroll down Автопрокрутка вниз @@ -879,12 +909,12 @@ ChatMessageListItem - + Chat message Сообщение чата - + Selected chat message - from %1, at %2: %3 Выбранное сообщение — от %1, в %2: %3 @@ -892,12 +922,12 @@ ChatRoomList - + Chat room list Список комнат чата - + List of all chat rooms Список всех комнат чата @@ -905,12 +935,12 @@ ChatRoomListItem - + Chat room Комната чата - + Selected chat room %1: %2 unread messages Выбранная комната %1: непрочитанных %2 @@ -918,42 +948,42 @@ ChatSideBar - + Chat message list Список сообщений чата - + List of all the messages in the current chat Список всех сообщений текущего чата - + Chat message Сообщение чата - + Selected chat message from %1 at %2: %3 Выбранное сообщение от %1 в %2: %3 - + the server сервера - + you вас - + Select emoji Выбрать эмодзи - + Enter chat message... Введите сообщение... @@ -961,27 +991,27 @@ Chats - + Please enter your recovery key to decrypt messages: Введите ключ восстановления для расшифровки сообщений: - + Recovery key Ключ восстановления - + Enter recovery key Введите ключ восстановления - + Use key Использовать ключ - + Use recovery key Использовать ключ восстановления @@ -989,7 +1019,7 @@ ClipboardButton - + Copy to clipboard: %1 Скопировать в буфер: %1 @@ -997,138 +1027,138 @@ Conference - - + + Set room name Задать имя комнаты - - + + Room name: Имя комнаты: - - + + Enter the room name Введите имя комнаты - + Authenticate Аутентифицировать - + Please authenticate in the opened browser window... Выполните аутентификацию в открытом окне браузера... - + This conference is protected by a password. Please enter it to join the room. Конференция защищена паролем. Введите пароль для входа. - - + + Password Пароль - - + + Enter the password Введите пароль - + Remember password Запомнить пароль - - + + Cancel Отмена - + Join Room Войти в комнату - + Password required Требуется пароль - + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. Введите пароль для защиты конференции. Остальные участники должны ввести его перед входом. - + This password has been set for the conference room and must be entered by participants before taking part in the session. Этот пароль установлен для комнаты конференции и должен вводиться участниками перед входом. - + The room password has been set by someone else. Пароль комнаты установлен другим участником. - + Show password Показать пароль - + Remove Удалить - + Save Сохранить - + Video quality Качество видео - + Change the video quality of this meeting Изменить качество видео встречи - + No video (audio only) Без видео (только аудио) - + Lowest quality Наименьшее качество - + Standard quality Стандартное качество - + Highest quality Наилучшее качество - + Close Закрыть - + Drag bar Панель перетаскивания @@ -1136,135 +1166,136 @@ ConferenceButtonBar - + %n minutes left осталась %n минута - осталось %n минут + Осталось %n минуты + Осталось %n минут - + Conference room Комната конференции - + Share Поделиться - + Copy room name Скопировать имя комнаты - + Copy room link Скопировать ссылку на комнату - + Open in browser Открыть в браузере - + Show phone number Показать номер телефона - + Raise Поднять руку - + Resume Продолжить - + Hold Удержание - + View Вид - + Screen Экран - + Share window Поделиться окном - + Share screen Поделиться экраном - + Camera Камера - + Output Выход - + More Ещё - + Noise supression Шумоподавление - + Toggle subtitles Включить/выключить субтитры - + Toggle whiteboard Включить/выключить доску - + Video quality... Качество видео... - + Set room password... Установить пароль комнаты... - + Mute everyone Выключить звук у всех - + Leave conference Покинуть конференцию - + End conference for all Завершить конференцию для всех - + Micro Микрофон @@ -1272,7 +1303,7 @@ ConfirmDialog - + Cancel Отмена @@ -1280,7 +1311,7 @@ ControlBar - + App menu Меню приложения @@ -1288,40 +1319,40 @@ Credentials - - storing credentials failed: %1 - Ошибка сохранения учётных данных: %1 + + Storing credentials for %1 failed: %2 + Не удалось сохранить данные для входа для %1: %2 - - reading credentials failed: %1 - Ошибка чтения учётных данных: %1 + + reading credentials for %1 failed: %2 + Не удалось прочитать данные для входа для %1: %2 CredentialsDialog - + Authentication failed Ошибка аутентификации - + Please enter the password: Введите пароль: - + Password Пароль - + Enter the password Введите пароль - + Ok ОК @@ -1329,40 +1360,48 @@ CustomWindowHeader - + GOnnect window header Заголовок окна GOnnect - + App menu Меню приложения - + Close GOnnect window Закрыть окно GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + Не удалось сохранить учетные данные календаря: %1 + + DateEventManager - + Conference starting soon Конференция скоро начнётся - + Appointment starting soon Встреча скоро начнётся - + Join Присоединиться - + Open Открыть @@ -1370,114 +1409,114 @@ DateEventsList - + Date events События календаря - + List of all the currently active and upcoming date events Список всех активных и предстоящих событий - + Date event section Раздел событий - + Header for %1 Заголовок для %1 - + Today - %1 Сегодня — %1 - - + + yyyy/MM/dd dd.MM.yyyy - + Tomorrow - %1 Завтра — %1 - + dddd - yyyy/MM/dd dddd - dd.MM.yyyy - + Date event Событие календаря - + Currently selected date event: %1, starting time %2, remaining time %3 Выбранное событие: %1, начало %2, осталось %3 - - + + hh:mm hh:mm - + All day Весь день - + till %1 до %1 - + in %1 через %1 - + Join Присоединиться - + Open Открыть - + Join meeting Присоединиться к встрече - + Join the meeting associated with the currently selected event Присоединиться к встрече выбранного события - + Copy room link Скопировать ссылку на комнату - + Copy link Скопировать ссылку - + Copy meeting link Скопировать ссылку на встречу - + Copy the meeting link associated with the currently selected event Скопировать ссылку на встречу выбранного события @@ -1485,27 +1524,27 @@ DateEventsWidget - + Appointments Встречи - + Loading appointments... Загрузка встреч... - + No upcoming appointments Нет предстоящих встреч - + Date event widget status Статус виджета событий - + Displays the current status of the widget: %1 Отображает текущий статус виджета: %1 @@ -1513,12 +1552,12 @@ DialInInfo - + Call one of the phone numbers below and use this code for authentication: Позвоните по одному из номеров ниже и используйте этот код для аутентификации: - + Close Закрыть @@ -1526,12 +1565,12 @@ DtmfDialer - + Number pad Набор номера - + Character %1 Символ %1 @@ -1539,32 +1578,32 @@ EditModeOptions - + Add page Добавить страницу - + Add a new dashboard page Добавить новую страницу панели - + Add widget Добавить виджет - + Add a new widget to the current dashboard page Добавить новый виджет на текущую страницу - + Finished Готово - + Finish and save all dashboard and widget changes Завершить и сохранить все изменения @@ -1572,22 +1611,22 @@ EmergencyCallIncomingWindow - + Emergency Call Экстренный вызов - + Incoming emergency call from %1 Входящий экстренный вызов от %1 - + Answering the call will automatically terminate all other ongoing calls. Ответ автоматически завершит все остальные активные звонки. - + Answer Ответить @@ -1595,12 +1634,12 @@ EmojiButton - + Emoji Эмодзи - + Selected Emoji: %1 Выбранное эмодзи: %1 @@ -1608,12 +1647,12 @@ EmojiPicker - + Switch Emoji category Сменить категорию эмодзи - + Select Emoji Выбрать эмодзи @@ -1621,186 +1660,186 @@ EnumTranslation - + Trying Попытка - + Ringing Вызов - + Call being forwarded Звонок переадресован - + Queued В очереди - + Progress В процессе - + Ok ОК - + Accepted Принято - + Unauthorized Не авторизован - - + + Rejected Отклонено - + Not found Не найдено - + Proxy authentication required Требуется аутентификация прокси - + Request timeout Время запроса истекло - + Temporarily unavailable Временно недоступно - + Ambiguous Неоднозначно - + Busy here Занято - + Request terminated Запрос завершён - + Not acceptable here Неприемлемо - + Internal server error Внутренняя ошибка сервера - + Not implemented Не реализовано - + Bad gateway Неверный шлюз - + Service unavailable Сервис недоступен - + Server timeout Тайм-аут сервера - + Busy everywhere Занято везде - + Decline Отклонено - + Does not exist anywhere Не существует нигде - + Not acceptable anywhere Нигде неприемлемо - + Unwanted Нежелательный - - - - + + + + Unknown Неизвестно - + Commercial Рабочий - + Home Домашний - + Mobile Мобильный - + Incoming Входящий - + Outgoing Исходящий - + Blocked Заблокирован - + SIP SIP - + Jitsi Meet Jitsi Meet @@ -1808,12 +1847,12 @@ FavIcon - + Set favorite Добавить в избранное - + Unset favorite Удалить из избранного @@ -1821,22 +1860,22 @@ FavoriteListItemBig - + Favorite contact Избранный контакт - + Selected favorite %1: %2 Избранное %1: %2 - + tap to start meeting %1 нажмите, чтобы начать встречу %1 - + tap to call %1 нажмите, чтобы позвонить %1 @@ -1844,17 +1883,17 @@ FavoriteListItemSmall - + Favorite contact Избранный контакт - + Selected favorite %1: %2 Избранное %1: %2 - + tap to call %1 нажмите, чтобы позвонить %1 @@ -1862,13 +1901,13 @@ FavoritesList - - + + Favorites Избранное - + List of all contacts that have been marked as favorites Список всех контактов, отмеченных как избранные @@ -1876,12 +1915,12 @@ FavoritesWidget - + Favorites Избранное - + No favorites to display Нет избранных контактов @@ -1889,32 +1928,32 @@ FirstAid - + First Aid Экстренная помощь - + Clicking one of these buttons will end all current calls and start an emergency call. Нажатие кнопки завершит все текущие звонки и инициирует экстренный вызов. - + Tap to call emergency contact: %1 (%2) Нажмите, чтобы позвонить на экстренный контакт: %1 (%2) - + Close Закрыть - + Exit the first aid menu without initiating any action Выйти из меню экстренной помощи без действий - + Close first aid menu Закрыть меню экстренной помощи @@ -1922,12 +1961,12 @@ FirstAidButton - + Open first aid menu Открыть меню экстренной помощи - + First Aid Экстренная помощь @@ -1935,7 +1974,7 @@ GonnectWindow - + Home Домашний @@ -1943,7 +1982,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -1951,41 +1990,41 @@ HeadsetDeviceProxy - + Ringing Вызов - + Calling Вызов - - - - + + + + Call active Звонок активен - + Call waiting Звонок ожидает - + On Hold На удержании - - + + Call ended Звонок завершён - + Phone conference Телефонная конференция @@ -1993,43 +2032,43 @@ HistoryList - - + + No past calls Нет прошлых звонков - + History История - + Searchable list of past calls and meetings Список прошлых звонков и встреч - + History item section Раздел элемента истории - + Header for the currently selected day: %1 Заголовок выбранного дня: %1 - + History item Элемент истории - + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 Выбранный элемент %1 — компания %2, место %3, номер %4, время %5, длительность %6 - + hh:mm hh:mm @@ -2037,37 +2076,37 @@ HistoryListContextMenu - + Call Звонок - + Copy number Скопировать номер - + Remove favorite Удалить из избранного - + Add favorite Добавить в избранное - + Remind when available Напомнить при доступности - + Unblock Разблокировать - + Block for 8 hours Заблокировать на 8 часов @@ -2075,78 +2114,78 @@ HistoryWidget - + History История - - + + All Все - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Выбор типа звонка в истории - + Select the call type to filter by Выбрать тип звонка для фильтрации - + Currently selected call type Текущий тип звонка - + Incoming Входящий - + Outgoing Исходящий - + Missed Пропущенные - + History call origin picker Выбор направления звонка в истории - + Select the call origin to filter by Выбрать направление звонка для фильтрации - + Currently selected call origin Текущее направление звонка - + Hide history search Скрыть поиск в истории - + Show history search Показать поиск в истории @@ -2154,10 +2193,10 @@ IMHandler - - - - + + + + Ad hoc conference Спонтанная конференция @@ -2165,27 +2204,27 @@ IdentitySelector - + Default По умолчанию - + Auto Авто - + Identity selection Выбор идентификатора - + Select the preferred identity to be used in calls Выбрать предпочтительный идентификатор для звонков - + Currently selected identity Текущий идентификатор @@ -2193,7 +2232,7 @@ InfoDialog - + Ok ОК @@ -2201,32 +2240,38 @@ JitsiConnector - + New chat message Новое сообщение - + Unnamed participant Безымянный участник - + Active conference Активная конференция - + Hang up Завершить - + %1 has joined the conference %1 присоединился к конференции - + + + Failed to persist room password: %1 + Не удалось сохранить пароль комнаты: %1 + + + %1 has left the conference %1 покинул конференцию @@ -2234,22 +2279,22 @@ JitsiHistoryListContextMenu - + Start conference Начать конференцию - + Remove favorite Удалить из избранного - + Add favorite Добавить в избранное - + Copy room name Скопировать имя комнаты @@ -2257,30 +2302,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - Ошибка инициализации LDAP-соединения - - - - - + + + LDAP error: %1 Ошибка LDAP: %1 - - + + Parse error: %1 Ошибка синтаксического анализа: %1 - + LDAP timeout: %1 Тайм-аут LDAP: %1 - + + Failed to initialize LDAP connection: %1 Ошибка инициализации LDAP: %1 @@ -2288,7 +2329,7 @@ LinuxDesktopSearchProvider - + Call Звонок @@ -2296,62 +2337,78 @@ Main - + No system tray available Системный трей недоступен - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect обеспечивает быстрый доступ через системный трей. Ваша среда рабочего стола не поддерживает его. - + Information Информация - - GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect тестируется для вашей операционной системы и ещё не выпущен официально. Возможны ошибки — сообщайте о них на GitHub. Удачного тестирования! - + There are still phone calls going on, do you really want to quit? Ещё есть активные звонки. Вы действительно хотите выйти? - + Please enter the password for %1: Введите пароль для %1: - + Please enter the recovery key for %1: Введите ключ восстановления для %1: - + Please enter the password for the SIP account: Введите пароль для SIP-аккаунта: - + End all calls Завершить все звонки - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + Do you really want to close this window and terminate all ongoing calls? Закрыть окно и завершить все активные звонки? - + + Registration failed + Регистрация не удалась + + + + Registration failed with with status %1: %2 + Регистрация не удалась со статусом %1: %2 + + + Error Ошибка - + Fatal Error Критическая ошибка @@ -2359,114 +2416,114 @@ MainTabBar - - + + Home Домашний - + Conference Конференция - + No active conference Нет активной конференции - + No active call Нет активного звонка - + Move up Переместить вверх - + Move tab up Переместить вкладку вверх - + Moves the currently selected tab up by one Перемещает выбранную вкладку вверх на одну позицию - + Move down Переместить вниз - + Move tab down Переместить вкладку вниз - + Moves the currently selected tab down by one Перемещает выбранную вкладку вниз на одну позицию - + Edit Редактировать - + Edit page Редактировать страницу - + Edit the currently selected dashboard page Редактировать выбранную страницу панели - + Delete Удалить - + Delete page Удалить страницу - + Delete the currently selected dashboard page Удалить выбранную страницу панели - + Call Звонок - + Selected tab Выбранная вкладка - + The currently selected tab Текущая выбранная вкладка - + Selected tab options Параметры выбранной вкладки - + The settings of the currently selected tab Настройки текущей выбранной вкладки - - + + Settings Настройки @@ -2474,17 +2531,17 @@ MenuContactInfo - + Commercial Рабочий - + Mobile Мобильный - + Home Домашний @@ -2492,92 +2549,92 @@ PageCreationWindow - + Create new dashboard page Создать страницу панели - + Edit dashboard page Редактировать страницу панели - + Name Имя - + Page name Имя страницы - + Enter the page name Введите имя страницы - + Icon Значок - + Page icon label Метка значка страницы - + Page icon selection Выбор значка страницы - + Select the page icon for the dashboard page Выбрать значок страницы панели - + Currently selected page icon option Текущий параметр значка страницы - + Cancel Отмена - + Cancel page modifcation Отменить изменение страницы - + Cancel button to exit the page creation/update window Кнопка отмены для выхода из окна - + Create Создать - + Save Сохранить - + page страница - + Confirmation button to create the new dashboard page Кнопка подтверждения создания страницы - + Confirmation button to apply changes to the dashboard page Кнопка подтверждения применения изменений @@ -2585,42 +2642,42 @@ ParticipantsList - + Participants list Список участников - + List of all the participants of the current chat room Список всех участников текущей комнаты - + Chat participant Участник чата - + Selected chat participant: %1 Выбранный участник: %1 - + moderator модератор - - it's you + + it's you это вы - + Kick Исключить - + Make moderator Сделать модератором @@ -2628,7 +2685,7 @@ PhoneNumberUtil - + Anonymous Аноним @@ -2636,42 +2693,42 @@ PreferredIdentityEditWindow - + Phone Number Transmission Передача номера телефона - + Name Имя - + Prefix Префикс - + Identity Идентификатор - + Enabled Включено - + Automatic Автоматически - + Delete Удалить - + Save Сохранить @@ -2679,1236 +2736,1237 @@ QObject - + Andorra Андорра - + United Arab Emirates Объединённые Арабские Эмираты - + Afghanistan Афганистан - + Antigua and Barbuda Антигуа и Барбуда - + Anguilla Ангилья - + Albania Албания - + Armenia Армения - + Angola Ангола - + Argentina Аргентина - + American Samoa Американское Самоа - + Austria Австрия - + Australia Австралия - + Aruba Аруба - + Aland Islands Аландские острова - + Azerbaijan Азербайджан - + Bosnia and Herzegovina Босния и Герцеговина - + Barbados Барбадос - + Bangladesh Бангладеш - + Belgium Бельгия - + Burkina Faso Буркина-Фасо - + Bulgaria Болгария - + Bahrain Бахрейн - + Burundi Бурунди - + Benin Бенин - + Saint Barthelemy Сен-Бартелеми - + Bermuda Бермуды - + Brunei Бруней - + Bolivia Боливия - + Bonaire, Saint Eustatius and Saba Бонэйр, Синт-Эстатиус и Саба - + Brazil Бразилия - + Bahamas Багамы - + Bhutan Бутан - + Botswana Ботсвана - + Belarus Беларусь - + Belize Белиз - + Canada Канада - + Cocos Islands Кокосовые острова - + Democratic Republic of the Congo Демократическая Республика Конго - + Central African Republic Центральноафриканская Республика - + Republic of the Congo Республика Конго - + Switzerland Швейцария - + Ivory Coast - Кот-д'Ивуар + Кот-д'Ивуар - + Cook Islands Острова Кука - + Chile Чили - + Cameroon Камерун - + China Китай - + Colombia Колумбия - + Costa Rica Коста-Рика - + Cuba Куба - + Cape Verde Кабо-Верде - + Curacao Кюрасао - + Christmas Island Остров Рождества - + Cyprus Кипр - + Czech Republic Чехия - + Germany Германия - + Djibouti Джибути - + Denmark Дания - + Dominica Доминика - + Dominican Republic Доминиканская Республика - + Algeria Алжир - + Ecuador Эквадор - + Estonia Эстония - + Egypt Египет - + Western Sahara Западная Сахара - + Eritrea Эритрея - + Spain Испания - + Ethiopia Эфиопия - + Finland Финляндия - + Fiji Фиджи - + Falkland Islands Фолклендские острова - + Micronesia Микронезия - + Faroe Islands Фарерские острова - + France Франция - + Gabon Габон - + United Kingdom Великобритания - + Grenada Гренада - + Georgia Грузия - + French Guiana Французская Гвиана - + Guernsey Гернси - + Ghana Гана - + Gibraltar Гибралтар - + Greenland Гренландия - + Gambia Гамбия - + Guinea Гвинея - + Guadeloupe Гваделупа - + Equatorial Guinea Экваториальная Гвинея - + Greece Греция - + Guatemala Гватемала - + Guam Гуам - + Guinea-Bissau Гвинея-Бисау - + Guyana Гайана - + Hong Kong Гонконг - + Heard Island and McDonald Islands Острова Херд и Макдональд - + Honduras Гондурас - + Croatia Хорватия - + Haiti Гаити - + Hungary Венгрия - + Indonesia Индонезия - + Ireland Ирландия - + Israel Израиль - + Isle of Man Остров Мэн - + India Индия - + British Indian Ocean Territory Британская территория в Индийском океане - + Iraq Ирак - + Iran Иран - + Iceland Исландия - + Italy Италия - + Jersey Джерси - + Jamaica Ямайка - + Jordan Иордания - + Japan Япония - + Kenya Кения - + Kyrgyzstan Кыргызстан - + Cambodia Камбоджа - + Kiribati Кирибати - + Comoros Коморы - + Saint Kitts and Nevis Сент-Китс и Невис - + North Korea Северная Корея - + South Korea Южная Корея - + Kuwait Кувейт - + Cayman Islands Каймановы острова - + Kazakhstan Казахстан - + Laos Лаос - + Lebanon Ливан - + Saint Lucia Сент-Люсия - + Liechtenstein Лихтенштейн - + Sri Lanka Шри-Ланка - + Liberia Либерия - + Lesotho Лесото - + Lithuania Литва - + Luxembourg Люксембург - + Latvia Латвия - + Libya Ливия - + Morocco Марокко - + Monaco Монако - + Moldova Молдова - + Montenegro Черногория - + Saint Martin Сен-Мартен - + Madagascar Мадагаскар - + Marshall Islands Маршалловы острова - + Macedonia Македония - + Mali Мали - + Myanmar Мьянма - + Mongolia Монголия - + Macao Макао - + Northern Mariana Islands Северные Марианские острова - + Martinique Мартиника - + Mauritania Мавритания - + Montserrat Монтсеррат - + Malta Мальта - + Mauritius Маврикий - + Maldives Мальдивы - + Malawi Малави - + Mexico Мексика - + Malaysia Малайзия - + Mozambique Мозамбик - + Namibia Намибия - + New Caledonia Новая Каледония - + Niger Нигер - + Norfolk Island Остров Норфолк - + Nigeria Нигерия - + Nicaragua Никарагуа - + Netherlands Нидерланды - + Norway Норвегия - + Nepal Непал - + Nauru Науру - + Niue Ниуэ - + New Zealand Новая Зеландия - + Oman Оман - + Panama Панама - + Peru Перу - + French Polynesia Французская Полинезия - + Papua New Guinea Папуа — Новая Гвинея - + Philippines Филиппины - + Pakistan Пакистан - + Poland Польша - + Saint Pierre and Miquelon Сен-Пьер и Микелон - + Pitcairn Питкэрн - + Puerto Rico Пуэрто-Рико - + Palestinian Territory Палестинская территория - + Portugal Португалия - + Palau Палау - + Paraguay Парагвай - + Qatar Катар - + Reunion Реюньон - + Romania Румыния - + Serbia Сербия - + Russia Россия - + Rwanda Руанда - + Saudi Arabia Саудовская Аравия - + Solomon Islands Соломоновы острова - + Seychelles Сейшелы - + Sudan Судан - + Sweden Швеция - + Singapore Сингапур - + Saint Helena Остров Святой Елены - + Slovenia Словения - + Svalbard and Jan Mayen Шпицберген и Ян-Майен - + Slovakia Словакия - + Sierra Leone Сьерра-Леоне - + San Marino Сан-Марино - + Senegal Сенегал - + Somalia Сомали - + Suriname Суринам - + South Sudan Южный Судан - + Sao Tome and Principe Сан-Томе и Принсипи - + El Salvador Сальвадор - + Sint Maarten Синт-Мартен - + Syria Сирия - + Swaziland Свазиленд - + Turks and Caicos Islands Острова Тёркс и Кайкос - + Chad Чад - + Togo Того - + Thailand Таиланд - + Tajikistan Таджикистан - + Tokelau Токелау - + East Timor Восточный Тимор - + Turkmenistan Туркменистан - + Tunisia Тунис - + Tonga Тонга - + Turkey Турция - + Trinidad and Tobago Тринидад и Тобаго - + Tuvalu Тувалу - + Taiwan Тайвань - + Tanzania Танзания - + Ukraine Украина - + Uganda Уганда - + United States Minor Outlying Islands Внешние малые острова США - + United States Соединённые Штаты - + Uruguay Уругвай - + Uzbekistan Узбекистан - + Vatican Ватикан - + Saint Vincent and the Grenadines Сент-Винсент и Гренадины - + Venezuela Венесуэла - + British Virgin Islands Британские Виргинские острова - + U.S. Virgin Islands Американские Виргинские острова - + Vietnam Вьетнам - + Vanuatu Вануату - + Wallis and Futuna Уоллис и Футуна - + Samoa Самоа - + Yemen Йемен - + Mayotte Майотта - + South Africa Южная Африка - + Zambia Замбия - + Zimbabwe Зимбабве - + There are %n active call(s). calls Есть %n активный звонок. + Есть %n активных звонка. Есть %n активных звонков. @@ -3916,10 +3974,10 @@ ResponseTreeModel - - - - + + + + Additional Information Дополнительная информация @@ -3927,27 +3985,27 @@ RingToneFactory - + ringTone 425,0,1000,4000,0 - + busyTone 425,0,480,480,0 - + congestionTone 425,0,240,240,0 - + zip 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 - + endTone 425,0,200,200,200,200,200,200,-1 @@ -3955,40 +4013,45 @@ SIPAccount - - 'userUri' is no valid SIP URI: %1 - 'userUri' не является допустимым SIP URI: %1 + + 'userUri' is no valid SIP URI: %1 + 'userUri' не является допустимым SIP URI: %1 - - 'userUri' is required - 'userUri' обязателен + + 'userUri' is required + 'userUri' обязателен - - 'registrarUri' is no valid SIP URI: %1 - 'registrarUri' не является допустимым SIP URI: %1 + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' не является допустимым SIP URI: %1 - - 'registrarUri' is required - 'registrarUri' обязателен + + 'registrarUri' is required + 'registrarUri' обязателен - - 'proxies' contains invalid SIP URI entry: %1 - 'proxies' содержит недопустимый SIP URI: %1 + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' содержит недопустимый SIP URI: %1 - + Failed to create %1: %2 Не удалось создать %1: %2 + + + Failed to persist SIP credentials: %1 + Не удалось сохранить учетные данные SIP: %1 + SIPBuddy - + %1 is now available %1 теперь доступен @@ -3996,12 +4059,12 @@ SIPCall - + Active call with %1 Активный звонок с %1 - + Hang up Завершить @@ -4009,34 +4072,34 @@ SIPCallManager - + %1 is calling %1 звонит - + %1 (%2) is calling %1 (%2) звонит - - + + Accept Принять - + Call back Перезвонить - - + + Reject Отклонить - + Missed call from %1 Пропущенный звонок от %1 @@ -4044,7 +4107,7 @@ SIPManager - + New Identity Новый идентификатор @@ -4052,18 +4115,18 @@ SIPTemplate - - + + Failed to write to %1 Ошибка записи в %1 - + Failed to copy %1 to the config space Ошибка копирования %1 в каталог конфигурации - + Source file %1 does not exist Исходный файл %1 не существует @@ -4071,7 +4134,7 @@ SearchBox - + Search number Поиск номера @@ -4079,12 +4142,12 @@ SearchCategoryItem - + Search result category filter %1 Фильтр категории результатов поиска %1 - + Filter for the individual search result items by category Фильтр результатов поиска по категории @@ -4092,22 +4155,22 @@ SearchCategoryList - + Contacts Контакты - + Messages Сообщения - + Rooms and Teams Комнаты и команды - + Files Файлы @@ -4115,42 +4178,42 @@ SearchDial - + Select number Выбрать номер - + Activate search field Активировать поле поиска - + Number or contact Номер или контакт - + Clear search field Очистить поле поиска - + Default По умолчанию - + Auto Авто - + Preferred identity Предпочтительный идентификатор - + Select the preferred identity for outgoing calls Выбрать предпочтительный идентификатор для исходящих звонков @@ -4158,12 +4221,12 @@ SearchField - + Search for contacts or room names... Поиск контактов или комнат... - + Clear search field Очистить поле поиска @@ -4171,12 +4234,12 @@ SearchResultCategory - + Search result category %1 Категория результатов поиска %1 - + Divider for the individual search result items by category Разделитель результатов поиска по категории @@ -4184,12 +4247,12 @@ SearchResultItem - + Search result Результат поиска - + Currently selected search result Текущий результат поиска @@ -4197,17 +4260,17 @@ SearchResultNumberItem - + Phone number Номер телефона - + Selected favorite number %1 Выбранный избранный номер %1 - + Selected phone number %1 Выбранный номер телефона %1 @@ -4215,52 +4278,52 @@ SearchResultPopup - + Search filter and identity selection Фильтр поиска и выбор идентификатора - + Select search filter to be applied, as well as the outgoing identity Выбрать фильтр поиска и исходящий идентификатор - + Outgoing identity Исходящий идентификатор - + Search results Результаты поиска - + All search results will be listed here in their respective categories Все результаты поиска будут перечислены по категориям - + Direct dial Прямой набор - - Call "%1" + + Call "%1" Позвонить «%1» - - Open room "%1" + + Open room "%1" Открыть комнату «%1» - + History История - + Contacts Контакты @@ -4268,314 +4331,314 @@ SettingsPage - + Settings Настройки - + Show chat messages as desktop notifications Показывать сообщения чата как уведомления - + Enable USB headset driver [%1] Включить драйвер USB-гарнитуры [%1] - + not detected не обнаружено - + Show dial window on USB headset pick up Показывать окно набора при снятии USB-гарнитуры - + Color scheme Цветовая схема - + System default Системная по умолчанию - + Light Светлая - + Dark Тёмная - + Inverse Accept / Reject buttons Инвертировать кнопки принятия/отклонения - + Use dark mode tray icon Использовать значок трея для тёмного режима - + Disable USB headset mute state propagation Отключить синхронизацию состояния отключения USB-гарнитуры - + Reload contacts from LDAP Перезагрузить контакты из LDAP - + Phoning Телефония - + Show main window on startup Показывать главное окно при запуске - + Disable synchronisation with the system mute state Отключить синхронизацию с системным отключением звука - - + + restart required требуется перезапуск - + Appearance Внешний вид - + Use custom window decoration Использовать пользовательское оформление окна - + Theme selection box Поле выбора темы - + Select the UI theme Выбрать тему интерфейса - + Currently selected theme option Текущий параметр темы - + Signalling busy when a call is active Сигнализировать занято при активном звонке - + Rules for telephone number transmission Правила передачи номера телефона - + Standard preferred identity Стандартный предпочтительный идентификатор - - + + Default По умолчанию - - + + Auto Авто - - + + Prefererred identity selection Выбор предпочтительного идентификатора - - + + Select the preferred identity Выбрать предпочтительный идентификатор - + Currently selected identity option Текущий параметр идентификатора - + No preferred identities yet. Нет предпочтительных идентификаторов. - + Currently highlighted preferred identity. Tap to edit. Текущий предпочтительный идентификатор. Нажмите для редактирования. - + Standard Стандарт - + Add identity Добавить идентификатор - + Add a new preferred identity entry Добавить новый предпочтительный идентификатор - + Audio settings Настройки звука - + Input device Устройство ввода - + Input device selection Выбор устройства ввода - + Select the input device to be used Выбрать устройство ввода - + Currently selected input option Текущий параметр ввода - + Output device Устройство вывода - + Output device selection Выбор устройства вывода - + Select the output device to be used Выбрать устройство вывода - + Currently selected output option Текущий параметр вывода - + Output device for ring tone Устройство вывода для мелодии звонка - + Currently selected ring output option Текущий параметр вывода звонка - + Ring tone Мелодия звонка - + Prefer USB headset ring sound if available Использовать звонок USB-гарнитуры при наличии - + Reset ring tone Сбросить мелодию звонка - + Reset the ring tone to its default option Восстановить мелодию звонка по умолчанию - + Pick ring tone Выбрать мелодию звонка - + Select the ring tone you want to use for incoming calls Выбрать мелодию для входящих звонков - - + + Currently set to: Текущее значение: - + Ring tone volume Громкость мелодии звонка - - + + Adjust %1 Настроить %1 - + %1 % %1 % - + Pause between ring tones [s] Пауза между звонками [с] - + Debugging Отладка - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Нажмите для запуска сеанса отладки. Приложение перезапустится и начнёт запись дополнительной информации. Сеанс ограничен 5 минутами. - + Start debug run (restart app) Начать отладку (перезапустить приложение) - + Download debug information Загрузить отладочную информацию @@ -4583,62 +4646,62 @@ ShortcutsWindow - + Shortcuts Горячие клавиши - + Shortcut key: %1 Клавиша быстрого доступа: %1 - + Local shortcuts (work only when app is focused) Локальные сочетания (работают только при фокусе на приложении) - + Local shortcuts Локальные сочетания клавиш - + Ctrl + F Ctrl + F - + Activates the global search field Активирует глобальное поле поиска - + F11 F11 - + Toggles between fullsceen and normal window mode Переключает между полноэкранным и обычным режимом - + Ctrl + Shift + M Ctrl + Shift + M - + Toggles audio mute Включает/выключает звук - + Global shortcuts (work from anywhere) Глобальные сочетания (работают в любом месте) - + Global shortcuts Глобальные сочетания клавиш @@ -4646,143 +4709,143 @@ SipTemplateWizard - + Initial configuration Начальная конфигурация - + Error: %1 Ошибка: %1 - + SIP wizard notification Уведомление мастера SIP - + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. GOnnect не может найти конфигурацию SIP. Выберите шаблон ниже и при необходимости измените файл конфигурации. - + Please pick: Выберите: - + Select SIP template Выбрать шаблон SIP - + Select the SIP template to be used Выбрать используемый шаблон SIP - + Currently selected SIP template Текущий шаблон SIP - + Next Далее - + Continue setup Продолжить настройку - + Confirmation button to continue the setup Кнопка подтверждения для продолжения настройки - + Template field list Список полей шаблона - + List of all the available SIP template options Список всех доступных параметров шаблона SIP - + SIP template option Параметр шаблона SIP - + Currently selected SIP template option Текущий параметр шаблона SIP - + Display name of the SIP template option Отображаемое имя параметра шаблона SIP - + Description of the SIP template option Описание параметра шаблона SIP - + Back Назад - + Back button to return to the template selection menu Кнопка возврата к меню выбора шаблона - - + + Finish Завершить - + Confirmation button to apply the changes to the SIP template Кнопка подтверждения применения изменений шаблона - + Successful configuration file creation Файл конфигурации успешно создан - + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. Файл конфигурации создан. Проверьте, нужны ли изменения, и перезапустите GOnnect. - + The configuration has been saved to: Конфигурация сохранена в: - + Copy to clipboard Копировать в буфер обмена - + Copy the full path of the configuration file to the clipboard Скопировать полный путь к файлу конфигурации - + Finish wizard Завершить мастер - + Finish the SIP configuration wizard Завершить мастер настройки SIP @@ -4790,27 +4853,27 @@ StateManager - + Show dial window and focus search field Показать окно набора и перейти в поле поиска - + End all calls Завершить все звонки - + Redial last outgoing call Повторить последний исходящий звонок - + Toggle hold Переключить удержание - + Phone calls are active Звонки активны @@ -4818,48 +4881,48 @@ SystemTrayMenu - - + + Dial... Набор номера... - + Not registered... Не зарегистрирован... - + Settings Настройки - + About О программе - + Quit Выход - + End conference Завершить конференцию - + Call with %1 has ended Звонок с %1 завершён - + Hang up call with %1 Завершить звонок с %1 - + Accept call with %1 Принять звонок от %1 @@ -4867,32 +4930,32 @@ TemplateFieldFile - + File path Путь к файлу - + Enter the file path for %1 Введите путь к файлу для %1 - + Choose... Выбрать... - + Open file picker Открыть выбор файла - + Select the file that should be used for %1 Выбрать файл для %1 - + Certificate files (%1) Файлы сертификатов (%1) @@ -4900,12 +4963,12 @@ TemplateFieldText - + Text input Текстовый ввод - + Enter the desired value for %1 Введите нужное значение для %1 @@ -4913,7 +4976,7 @@ Toggler - + Failed to toggle the state of %1. Не удалось переключить состояние %1. @@ -4921,17 +4984,17 @@ TogglerList - + Toggler list Список переключателей - + List of items that can be toggled Список переключаемых элементов - + Toggle %1 Переключить %1 @@ -4939,7 +5002,7 @@ VerticalLevelMeter - + Level meter Индикатор уровня @@ -4947,7 +5010,7 @@ VideoDeviceMenu - + Virtual background Виртуальный фон @@ -4955,36 +5018,39 @@ ViewHelper - + Save File Сохранить файл - + %n minute(s) %n минута + %n минуты %n минут - + 1 hour and %n minute(s) 1 час и %n минута 1 час и %n минут + 1 час и %n минуты - + %n hour(s) %n час %n часов + %n часа - + Audio Files (%1) Аудиофайлы (%1) @@ -4992,22 +5058,22 @@ VolumePopup - + Adjust the volume Настроить громкость - + Unmute Включить звук - + Mute Выключить звук - + Open audio settings Открыть настройки звука @@ -5015,147 +5081,147 @@ WidgetSelectionWindow - + Add widget Добавить виджет - + Widget Виджет - + Widget selection header Заголовок выбора виджета - + Date Events События - + List of upcoming appointments Список предстоящих встреч - + Favorites Избранное - + Quick dial for your favorite contacts and conferences Быстрый набор избранных контактов и конференций - + History История - + Web View Веб-просмотр - + A web-based content display Отображение веб-содержимого - + Widget selection Выбор виджета - + Select the widget that should be added to the current dashboard page Выбрать виджет для добавления на текущую страницу - + Currently selected widget option Текущий параметр виджета - + Accept all certificates Принять все сертификаты - + Confirm widget selection Подтвердить выбор виджета - + Confirmation button to create and add the selected widget to the current dashboard Кнопка создания и добавления виджета на панель управления - + Searchable call and conference history История звонков и конференций - + Title Название - + URL URL - + URL (dark mode) URL (тёмный режим) - + Settings text input Поле настроек - + Input for widget setting %1 Ввод для параметра виджета %1 - + Settings checkbox Флажок настроек - + Checkbox for widget setting %1 Флажок для параметра виджета %1 - + Widget setting %1 Параметр виджета %1 - + Cancel Отмена - + Cancel widget selection Отменить выбор виджета - + Cancel button to exit widget selection selection without changes Кнопка отмены для выхода без изменений - + Add Добавить @@ -5163,24 +5229,24 @@ WindowHeader - + GOnnect window header Заголовок окна GOnnect - + Minimize Свернуть - + Maximize Развернуть - + Close GOnnect window Закрыть окно GOnnect - \ No newline at end of file + diff --git a/i18n/gonnect_uk.ts b/i18n/gonnect_uk.ts index d8969d0c..7d8de9b9 100644 --- a/i18n/gonnect_uk.ts +++ b/i18n/gonnect_uk.ts @@ -4,65 +4,65 @@ AboutWindow - + About Про програму - + GOnnect headline Заголовок GOnnect - + GOnnect version Версія GOnnect - + Version: v%1 Версія: v%1 - + Copy to clipboard Скопіювати до буфера обміну - - Copy the currently used version of Gonnect to the clipboard - Скопіювати поточну версію GOnnect до буфера обміну + + Copy the currently used version number of GOnnect to the clipboard + Скопіюйте номер поточної версії GOnnect в буфер обміну - - - - + + + + Homepage Домашня сторінка - + Visit the project homepage Відвідати домашню сторінку проєкту - + Bug Tracker Трекер помилок - + Visit the project bug tracker Відвідати трекер помилок проєкту - + Documentation Документація - + Visit the online project documentation Відвідати онлайн-документацію проєкту @@ -70,25 +70,33 @@ AdditionalInfo - + Additional caller related information Додаткова інформація про абонента - + List of informational items regarding the caller, such as open support tickets Список інформації про абонента, наприклад відкриті тікети підтримки - + Expandable response section Розгортуваний розділ відповіді + + AddressBookManager + + + Failed to persist address book credentials: %1 + Не вдалося зберегти облікові дані адресної книги: %1 + + AudioDeviceMenu - + Default За замовчуванням @@ -96,117 +104,117 @@ AudioEnvWindow - + Unknown audio environment Невідоме аудіосередовище - + Audio environment error Помилка аудіосередовища - + No fitting audio environment could be found. Please select the desired audio devices. Відповідне аудіосередовище не знайдено. Виберіть потрібні аудіопристрої. - + Input device selection header Заголовок вибору пристрою вводу - + Header for the input device selection below Заголовок для вибору пристрою вводу нижче - + Input device Пристрій вводу - + Input device selection box Поле вибору пристрою вводу - + Select the input device that should be used Вибрати пристрій вводу - + Currently selected input device Поточний пристрій вводу - + Output device selection header Заголовок вибору пристрою виводу - + Header for the output device selection below Заголовок для вибору пристрою виводу нижче - + Output device Пристрій виводу - + Output device selection box Поле вибору пристрою виводу - + Select the output device that should be used Вибрати пристрій виводу - + Currently selected output device Поточний пристрій виводу - + Ring tone output device Пристрій виводу мелодії дзвінка - + Output device for ring tone Пристрій виводу для мелодії дзвінка - + Ring tone output device selection box Поле вибору пристрою виводу мелодії - + Select the output device that should be used for playing the ring tone Вибрати пристрій для відтворення мелодії дзвінка - + Currently selected ring tone output device Поточний пристрій виводу мелодії - + Ok ОК - + Close audio environment selection Закрити вибір аудіосередовища - + Confirmation button to leave the audio environment selection window Кнопка підтвердження для виходу з вікна вибору аудіосередовища @@ -214,7 +222,7 @@ AudioLevelButton - + Change volume Змінити гучність @@ -222,20 +230,28 @@ AudioManager - + Default input Вхід за замовчуванням - + Default output Вихід за замовчуванням + + AuthManager + + + Failed to persist jitsi refresh token: %1 + Не вдалося зберегти токен оновлення jitsi: %1 + + AvatarImage - + Initials of this contact Ініціали цього контакту @@ -243,7 +259,7 @@ BaseDialog - + Dialog Діалог @@ -251,17 +267,17 @@ BasePage - + Base dashboard page grid Базова сітка сторінки панелі управління - + Canvas for editable dashboard pages Полотно для редагованих сторінок панелі - + Add widgets Додати віджети @@ -269,32 +285,32 @@ BaseWidget - + Drag widget Перетягнути віджет - + Change the position of the widget Змінити положення віджета - + Remove widget Видалити віджет - + Remove the currently selected widget from the dashboard Видалити вибраний віджет з панелі - + Resize widget Змінити розмір віджета - + Resize the widget according to the mouse direction Змінити розмір віджета за напрямком миші @@ -302,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Межа перетягування - + Top left drag border for window resize operations Ліва верхня межа для зміни розміру вікна - + Top drag border for window resize operations Верхня межа для зміни розміру вікна - + Top right border for window resize operations Права верхня межа для зміни розміру вікна - + Right drag border for window resize operations Права межа для зміни розміру вікна - + Bottom right drag border for window resize operations Права нижня межа для зміни розміру вікна - + Bottom drag border for window resize operations Нижня межа для зміни розміру вікна - + Bottom left drag border for window resize operations Ліва нижня межа для зміни розміру вікна - + Left drag border for window resize operations Ліва межа для зміни розміру вікна @@ -357,7 +373,7 @@ BottomStatusBar - + Status bar Рядок стану @@ -365,27 +381,27 @@ BurgerMenu - + Toggle fullscreen Перемкнути повноекранний режим - + Shortcuts... Гарячі клавіші... - + Customize UI Налаштувати інтерфейс - + About... Про програму... - + Quit Вийти @@ -393,12 +409,12 @@ Call - + Conference Конференція - + Drag bar Панель перетягування @@ -406,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 кГц - + Transmit Передача - + Call security level Рівень безпеки дзвінка - + Security level of the ongoing call Рівень безпеки поточного дзвінка - + Call security details Деталі безпеки дзвінка - + Detailed call security status: %1 / %2 Детальний статус безпеки: %1 / %2 - + signaling encrypted сигналізацію зашифровано - + signaling unencrypted сигналізацію не зашифровано - + media encrypted медіа зашифровано - + media unencrypted медіа не зашифровано - + Call quality Якість дзвінка - + Quality of the ongoing call Якість поточного дзвінка - + Transmission statistics Статистика передачі - - + + Call quality metrics Метрики якості дзвінка - - + + MOS MOS - - + + Mean opinion score Середня оцінка якості (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 - Числова метрика якості голосового зв'язку з боку передачі: %1 + Числова метрика якості голосового зв'язку з боку передачі: %1 - - + + Packet loss Втрата пакетів - + %1% of packets lost in transmission %1% пакетів втрачено при передачі - - + + Jitter Джитер - + Amount of transmission side jitter: %1 Джитер з боку передачі: %1 - - + + Effective delay Ефективна затримка - + Effective transmission side call delay: %1 Ефективна затримка з боку передачі: %1 - + Receiver statistics Статистика прийому - + Receive Прийом - + Numerical metric assessing receiver-side voice/video call quality: %1 Числова метрика якості з боку прийому: %1 - + %1% of packets lost in receival %1% пакетів втрачено при прийомі - + Amount of receiver side jitter: %1 Джитер з боку прийому: %1 - + Effective receiver side call delay: %1 Ефективна затримка з боку прийому: %1 - + Codec Кодек - + Audio codec Аудіокодек - + The currently used audio codec and frequency: %1 Поточний аудіокодек і частота: %1 - + Elapsed call time Тривалість дзвінка - + The duration in seconds the call has been active for: %1 Тривалість дзвінка у секундах: %1 - + Screen Екран - + Screensharing control Керування демонстрацією екрана - + Start sharing your screen Розпочати демонстрацію екрана - + Camera Камера - + Camera control Керування камерою - + Enable your camera Увімкнути камеру - + Resume Продовжити - + Hold Утримання - + Resume call Відновити дзвінок - + Hold call Утримати дзвінок - + Update the call hold state Оновити стан утримання - + Micro Мікрофон - + Input control Керування входом - + Set the mute state of the current input device Встановити стан вимкнення звуку пристрою вводу - + Output Вихід - + Output control Керування виходом - + Change the current output devices Змінити поточні пристрої виводу - + Accept call Прийняти дзвінок - + Hangup call Завершити дзвінок @@ -680,47 +696,47 @@ CallDetails - + SIP call status code Код стану SIP-дзвінка - + The current status code of the call: %1 Поточний код стану дзвінка: %1 - + Jitsi Meet Jitsi Meet - + Switch to a Jitsi Meet session Перейти до сеансу Jitsi Meet - + Unhold Зняти з утримання - + Hold Утримання - + Toggle the hold state to %1 Перемкнути стан утримання на %1 - + Accept call Прийняти дзвінок - + Hangup call Завершити дзвінок @@ -728,17 +744,17 @@ CallHistory - + Failed to create directory %1 to store the call history database. Не вдалося створити каталог %1 для бази даних історії дзвінків. - + Failed to open call history database: %1 Не вдалося відкрити базу даних історії дзвінків: %1 - + Call history database is inconsistent. Please remove %1 and restart the App to re-initialize the database. База даних пошкоджена. Видаліть %1 і перезапустіть додаток. @@ -746,22 +762,22 @@ CallItem - + Call Дзвінок - + Selected call %1 - contact %2, company %3, location %4/%5, number %6 Вибраний дзвінок %1 — контакт %2, компанія %3, місце %4/%5, номер %6 - + Hangup button Кнопка завершення - + Pressing this will end the call Натискання завершить дзвінок @@ -769,18 +785,18 @@ CallList - + Drag callers onto each other to transfer call Перетягніть абонентів один на одного для переадресації - + List of active calls Список активних дзвінків - - + + Create conference Створити конференцію @@ -788,20 +804,21 @@ CallSideBar - + Chat Чат - + Person(s) %n учасник + %n учасники %n учасників - + Info Відомості @@ -809,17 +826,17 @@ CallerBigAvatar - + Caller name - Ім'я абонента + Ім'я абонента - + is calling... дзвонить... - + Calling... Виклик... @@ -827,7 +844,7 @@ CallsModel - + unknown number невідомий номер @@ -835,25 +852,38 @@ CardList - + List of configurable options Список параметрів + + ChatConnectorManager + + + Failed to persist chat recovery code: %1 + Не вдалося зберегти код відновлення чату: %1 + + + + Failed to persist chat access token: %1 + Не вдалося зберегти токен доступу до чату: %1 + + ChatMessageBox - + Message Повідомлення - + Type message Введіть повідомлення - + Enter the chat text message Введіть текстове повідомлення чату @@ -861,17 +891,17 @@ ChatMessageList - + Chat message list Список повідомлень чату - + List of all chat messages of the current chat room Список усіх повідомлень поточної кімнати чату - + Auto scroll down Автопрокрутка вниз @@ -879,12 +909,12 @@ ChatMessageListItem - + Chat message Повідомлення чату - + Selected chat message - from %1, at %2: %3 Вибране повідомлення — від %1, о %2: %3 @@ -892,12 +922,12 @@ ChatRoomList - + Chat room list Список кімнат чату - + List of all chat rooms Список усіх кімнат чату @@ -905,12 +935,12 @@ ChatRoomListItem - + Chat room Кімната чату - + Selected chat room %1: %2 unread messages Вибрана кімната %1: непрочитаних %2 @@ -918,42 +948,42 @@ ChatSideBar - + Chat message list Список повідомлень чату - + List of all the messages in the current chat Список усіх повідомлень поточного чату - + Chat message Повідомлення чату - + Selected chat message from %1 at %2: %3 Вибране повідомлення від %1 о %2: %3 - + the server сервера - + you вас - + Select emoji Вибрати емодзі - + Enter chat message... Введіть повідомлення... @@ -961,27 +991,27 @@ Chats - + Please enter your recovery key to decrypt messages: Введіть ключ відновлення для розшифрування повідомлень: - + Recovery key Ключ відновлення - + Enter recovery key Введіть ключ відновлення - + Use key Використати ключ - + Use recovery key Використати ключ відновлення @@ -989,7 +1019,7 @@ ClipboardButton - + Copy to clipboard: %1 Скопіювати до буфера: %1 @@ -997,138 +1027,138 @@ Conference - - + + Set room name Задати назву кімнати - - + + Room name: Назва кімнати: - - + + Enter the room name Введіть назву кімнати - + Authenticate Автентифікувати - + Please authenticate in the opened browser window... Виконайте автентифікацію у відкритому вікні браузера... - + This conference is protected by a password. Please enter it to join the room. Конференція захищена паролем. Введіть пароль для входу. - - + + Password Пароль - - + + Enter the password Введіть пароль - + Remember password - Запам'ятати пароль + Запам'ятати пароль - - + + Cancel Скасувати - + Join Room Увійти до кімнати - + Password required Потрібен пароль - + Enter a password to protect this conference room. Other participants must enter it before taking part in the session. Введіть пароль для захисту конференції. Інші учасники мають ввести його перед входом. - + This password has been set for the conference room and must be entered by participants before taking part in the session. Цей пароль встановлено для кімнати і має вводитися учасниками перед входом. - + The room password has been set by someone else. Пароль кімнати встановлено іншим учасником. - + Show password Показати пароль - + Remove Видалити - + Save Зберегти - + Video quality Якість відео - + Change the video quality of this meeting Змінити якість відео зустрічі - + No video (audio only) Без відео (лише аудіо) - + Lowest quality Найнижча якість - + Standard quality Стандартна якість - + Highest quality Найвища якість - + Close Закрити - + Drag bar Панель перетягування @@ -1136,135 +1166,136 @@ ConferenceButtonBar - + %n minutes left залишилася %n хвилина - залишилося %n хвилин + Залишилися %n хвилини + Залишилося %n хвилин - + Conference room Кімната конференції - + Share Поділитися - + Copy room name Скопіювати назву кімнати - + Copy room link Скопіювати посилання на кімнату - + Open in browser Відкрити у браузері - + Show phone number Показати номер телефону - + Raise Підняти руку - + Resume Продовжити - + Hold Утримання - + View Вигляд - + Screen Екран - + Share window Поділитися вікном - + Share screen Поділитися екраном - + Camera Камера - + Output Вихід - + More Ще - + Noise supression Шумоподавлення - + Toggle subtitles Увімкнути/вимкнути субтитри - + Toggle whiteboard Увімкнути/вимкнути дошку - + Video quality... Якість відео... - + Set room password... Встановити пароль кімнати... - + Mute everyone Вимкнути звук усіх - + Leave conference Покинути конференцію - + End conference for all Завершити конференцію для всіх - + Micro Мікрофон @@ -1272,7 +1303,7 @@ ConfirmDialog - + Cancel Скасувати @@ -1280,7 +1311,7 @@ ControlBar - + App menu Меню застосунку @@ -1288,40 +1319,40 @@ Credentials - - storing credentials failed: %1 - Помилка збереження облікових даних: %1 + + Storing credentials for %1 failed: %2 + Не вдалося зберегти дані для входу для %1: %2 - - reading credentials failed: %1 - Помилка читання облікових даних: %1 + + reading credentials for %1 failed: %2 + Не вдалося прочитати дані для входу для %1: %2 CredentialsDialog - + Authentication failed Помилка автентифікації - + Please enter the password: Введіть пароль: - + Password Пароль - + Enter the password Введіть пароль - + Ok ОК @@ -1329,40 +1360,48 @@ CustomWindowHeader - + GOnnect window header Заголовок вікна GOnnect - + App menu Меню застосунку - + Close GOnnect window Закрити вікно GOnnect + + DateEventFeederManager + + + Failed to persist calendar credentials: %1 + Не вдалося зберегти облікові дані календаря: %1 + + DateEventManager - + Conference starting soon Конференція незабаром розпочнеться - + Appointment starting soon Зустріч незабаром розпочнеться - + Join Приєднатися - + Open Відкрити @@ -1370,114 +1409,114 @@ DateEventsList - + Date events Події календаря - + List of all the currently active and upcoming date events Список усіх активних та майбутніх подій - + Date event section Розділ подій - + Header for %1 Заголовок для %1 - + Today - %1 Сьогодні — %1 - - + + yyyy/MM/dd dd.MM.yyyy - + Tomorrow - %1 Завтра — %1 - + dddd - yyyy/MM/dd dddd - dd.MM.yyyy - + Date event Подія календаря - + Currently selected date event: %1, starting time %2, remaining time %3 Вибрана подія: %1, початок %2, залишилося %3 - - + + hh:mm hh:mm - + All day Весь день - + till %1 до %1 - + in %1 через %1 - + Join Приєднатися - + Open Відкрити - + Join meeting Приєднатися до зустрічі - + Join the meeting associated with the currently selected event Приєднатися до зустрічі вибраної події - + Copy room link Скопіювати посилання на кімнату - + Copy link Скопіювати посилання - + Copy meeting link Скопіювати посилання на зустріч - + Copy the meeting link associated with the currently selected event Скопіювати посилання на зустріч вибраної події @@ -1485,27 +1524,27 @@ DateEventsWidget - + Appointments Зустрічі - + Loading appointments... Завантаження зустрічей... - + No upcoming appointments Немає майбутніх зустрічей - + Date event widget status Статус віджета подій - + Displays the current status of the widget: %1 Відображає поточний статус віджета: %1 @@ -1513,12 +1552,12 @@ DialInInfo - + Call one of the phone numbers below and use this code for authentication: Зателефонуйте за одним із наведених номерів і використайте цей код для автентифікації: - + Close Закрити @@ -1526,12 +1565,12 @@ DtmfDialer - + Number pad Набір номера - + Character %1 Символ %1 @@ -1539,32 +1578,32 @@ EditModeOptions - + Add page Додати сторінку - + Add a new dashboard page Додати нову сторінку панелі - + Add widget Додати віджет - + Add a new widget to the current dashboard page Додати новий віджет на поточну сторінку - + Finished Готово - + Finish and save all dashboard and widget changes Завершити та зберегти всі зміни @@ -1572,22 +1611,22 @@ EmergencyCallIncomingWindow - + Emergency Call Екстрений виклик - + Incoming emergency call from %1 Вхідний екстрений виклик від %1 - + Answering the call will automatically terminate all other ongoing calls. Відповідь автоматично завершить усі інші активні дзвінки. - + Answer Відповісти @@ -1595,12 +1634,12 @@ EmojiButton - + Emoji Емодзі - + Selected Emoji: %1 Вибране емодзі: %1 @@ -1608,12 +1647,12 @@ EmojiPicker - + Switch Emoji category Змінити категорію емодзі - + Select Emoji Вибрати емодзі @@ -1621,186 +1660,186 @@ EnumTranslation - + Trying Спроба - + Ringing Виклик - + Call being forwarded Дзвінок переадресовано - + Queued У черзі - + Progress В процесі - + Ok ОК - + Accepted Прийнято - + Unauthorized Не авторизовано - - + + Rejected Відхилено - + Not found Не знайдено - + Proxy authentication required Потрібна автентифікація проксі - + Request timeout Час запиту вийшов - + Temporarily unavailable Тимчасово недоступно - + Ambiguous Неоднозначно - + Busy here Зайнято - + Request terminated Запит завершено - + Not acceptable here Неприйнятно - + Internal server error Внутрішня помилка сервера - + Not implemented Не реалізовано - + Bad gateway Неправильний шлюз - + Service unavailable Сервіс недоступний - + Server timeout Тайм-аут сервера - + Busy everywhere Зайнято всюди - + Decline Відхилено - + Does not exist anywhere Не існує ніде - + Not acceptable anywhere Ніде неприйнятно - + Unwanted Небажаний - - - - + + + + Unknown Невідомо - + Commercial Робочий - + Home Домашній - + Mobile Мобільний - + Incoming Вхідний - + Outgoing Вихідний - + Blocked Заблокований - + SIP SIP - + Jitsi Meet Jitsi Meet @@ -1808,12 +1847,12 @@ FavIcon - + Set favorite Додати до обраного - + Unset favorite Видалити з обраного @@ -1821,22 +1860,22 @@ FavoriteListItemBig - + Favorite contact Обраний контакт - + Selected favorite %1: %2 Обране %1: %2 - + tap to start meeting %1 натисніть, щоб розпочати зустріч %1 - + tap to call %1 натисніть, щоб зателефонувати %1 @@ -1844,17 +1883,17 @@ FavoriteListItemSmall - + Favorite contact Обраний контакт - + Selected favorite %1: %2 Обране %1: %2 - + tap to call %1 натисніть, щоб зателефонувати %1 @@ -1862,13 +1901,13 @@ FavoritesList - - + + Favorites Обране - + List of all contacts that have been marked as favorites Список усіх контактів, позначених як обрані @@ -1876,12 +1915,12 @@ FavoritesWidget - + Favorites Обране - + No favorites to display Немає обраних контактів @@ -1889,32 +1928,32 @@ FirstAid - + First Aid Перша допомога - + Clicking one of these buttons will end all current calls and start an emergency call. Натискання кнопки завершить усі поточні дзвінки та ініціює екстрений виклик. - + Tap to call emergency contact: %1 (%2) Натисніть, щоб зателефонувати на екстрений контакт: %1 (%2) - + Close Закрити - + Exit the first aid menu without initiating any action Вийти з меню першої допомоги без дій - + Close first aid menu Закрити меню першої допомоги @@ -1922,12 +1961,12 @@ FirstAidButton - + Open first aid menu Відкрити меню першої допомоги - + First Aid Перша допомога @@ -1935,7 +1974,7 @@ GonnectWindow - + Home Домашній @@ -1943,7 +1982,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -1951,41 +1990,41 @@ HeadsetDeviceProxy - + Ringing Виклик - + Calling Виклик - - - - + + + + Call active Дзвінок активний - + Call waiting Дзвінок очікує - + On Hold На утриманні - - + + Call ended Дзвінок завершено - + Phone conference Телефонна конференція @@ -1993,43 +2032,43 @@ HistoryList - - + + No past calls Немає минулих дзвінків - + History Історія - + Searchable list of past calls and meetings Список минулих дзвінків та зустрічей - + History item section Розділ елементів історії - + Header for the currently selected day: %1 Заголовок вибраного дня: %1 - + History item Елемент історії - + Selected history item %1 - company %2, location %3, number %4, time %5, duration %6 Вибраний елемент %1 — компанія %2, місце %3, номер %4, час %5, тривалість %6 - + hh:mm hh:mm @@ -2037,37 +2076,37 @@ HistoryListContextMenu - + Call Дзвінок - + Copy number Скопіювати номер - + Remove favorite Видалити з обраного - + Add favorite Додати до обраного - + Remind when available Нагадати при доступності - + Unblock Розблокувати - + Block for 8 hours Заблокувати на 8 годин @@ -2075,78 +2114,78 @@ HistoryWidget - + History Історія - - + + All Усі - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Вибір типу дзвінка в історії - + Select the call type to filter by Вибрати тип дзвінка для фільтрації - + Currently selected call type Поточний тип дзвінка - + Incoming Вхідний - + Outgoing Вихідний - + Missed Пропущені - + History call origin picker Вибір напрямку дзвінка в історії - + Select the call origin to filter by Вибрати напрямок дзвінка для фільтрації - + Currently selected call origin Поточний напрямок дзвінка - + Hide history search Приховати пошук в історії - + Show history search Показати пошук в історії @@ -2154,10 +2193,10 @@ IMHandler - - - - + + + + Ad hoc conference Спонтанна конференція @@ -2165,27 +2204,27 @@ IdentitySelector - + Default За замовчуванням - + Auto Авто - + Identity selection Вибір ідентифікатора - + Select the preferred identity to be used in calls Вибрати бажаний ідентифікатор для дзвінків - + Currently selected identity Поточний ідентифікатор @@ -2193,7 +2232,7 @@ InfoDialog - + Ok ОК @@ -2201,32 +2240,38 @@ JitsiConnector - + New chat message Нове повідомлення - + Unnamed participant Безіменний учасник - + Active conference Активна конференція - + Hang up Завершити - + %1 has joined the conference %1 приєднався до конференції - + + + Failed to persist room password: %1 + Не вдалося зберегти пароль кімнати: %1 + + + %1 has left the conference %1 покинув конференцію @@ -2234,22 +2279,22 @@ JitsiHistoryListContextMenu - + Start conference Розпочати конференцію - + Remove favorite Видалити з обраного - + Add favorite Додати до обраного - + Copy room name Скопіювати назву кімнати @@ -2257,30 +2302,26 @@ LDAPAddressBookFeeder - - Failed to initialize LDAP connection - Помилка ініціалізації LDAP-з'єднання - - - - - + + + LDAP error: %1 Помилка LDAP: %1 - - + + Parse error: %1 Помилка синтаксичного аналізу: %1 - + LDAP timeout: %1 Тайм-аут LDAP: %1 - + + Failed to initialize LDAP connection: %1 Помилка ініціалізації LDAP: %1 @@ -2288,7 +2329,7 @@ LinuxDesktopSearchProvider - + Call Дзвінок @@ -2296,62 +2337,78 @@ Main - + No system tray available Системний трей недоступний - + GOnnect provides quick access to functionality by providing a system tray. Your desktop environment does not provide one. GOnnect забезпечує швидкий доступ через системний трей. Ваше середовище його не підтримує. - + Information Інформація - - GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! + + GOnnect is under testing for your operating system and is not yet officially released. There is no feature parity with the Linux version yet, and there may be bugs that we didn't find yet. You're welcome with reporting these issues on github. Happy testing! GOnnect тестується для вашої операційної системи і ще не випущений офіційно. Можливі помилки — повідомляйте про них на GitHub. Вдалого тестування! - + There are still phone calls going on, do you really want to quit? Ще є активні дзвінки. Ви справді хочете вийти? - + Please enter the password for %1: Введіть пароль для %1: - + Please enter the recovery key for %1: Введіть ключ відновлення для %1: - + Please enter the password for the SIP account: Введіть пароль для SIP-акаунта: - + End all calls Завершити всі дзвінки - + + QT_LAYOUT_DIRECTION + QGuiApplication + LTR + + + Do you really want to close this window and terminate all ongoing calls? Закрити вікно та завершити всі активні дзвінки? - + + Registration failed + Реєстрація не вдалася + + + + Registration failed with with status %1: %2 + Реєстрація не вдалася зі статусом %1: %2 + + + Error Помилка - + Fatal Error Критична помилка @@ -2359,114 +2416,114 @@ MainTabBar - - + + Home Домашній - + Conference Конференція - + No active conference Немає активної конференції - + No active call Немає активного дзвінка - + Move up Перемістити вгору - + Move tab up Перемістити вкладку вгору - + Moves the currently selected tab up by one Переміщує вибрану вкладку вгору на одну позицію - + Move down Перемістити вниз - + Move tab down Перемістити вкладку вниз - + Moves the currently selected tab down by one Переміщує вибрану вкладку вниз на одну позицію - + Edit Редагувати - + Edit page Редагувати сторінку - + Edit the currently selected dashboard page Редагувати вибрану сторінку панелі - + Delete Видалити - + Delete page Видалити сторінку - + Delete the currently selected dashboard page Видалити вибрану сторінку панелі - + Call Дзвінок - + Selected tab Вибрана вкладка - + The currently selected tab Поточна вибрана вкладка - + Selected tab options Параметри вибраної вкладки - + The settings of the currently selected tab Налаштування поточної вибраної вкладки - - + + Settings Налаштування @@ -2474,17 +2531,17 @@ MenuContactInfo - + Commercial Робочий - + Mobile Мобільний - + Home Домашній @@ -2492,92 +2549,92 @@ PageCreationWindow - + Create new dashboard page Створити сторінку панелі - + Edit dashboard page Редагувати сторінку панелі - + Name - Ім'я + Ім'я - + Page name Назва сторінки - + Enter the page name Введіть назву сторінки - + Icon Значок - + Page icon label Мітка значка сторінки - + Page icon selection Вибір значка сторінки - + Select the page icon for the dashboard page Вибрати значок для сторінки панелі - + Currently selected page icon option Поточний параметр значка сторінки - + Cancel Скасувати - + Cancel page modifcation Скасувати зміну сторінки - + Cancel button to exit the page creation/update window Кнопка скасування для виходу з вікна - + Create Створити - + Save Зберегти - + page сторінка - + Confirmation button to create the new dashboard page Кнопка підтвердження створення нової сторінки - + Confirmation button to apply changes to the dashboard page Кнопка підтвердження застосування змін @@ -2585,42 +2642,42 @@ ParticipantsList - + Participants list Список учасників - + List of all the participants of the current chat room Список усіх учасників поточної кімнати - + Chat participant Учасник чату - + Selected chat participant: %1 Вибраний учасник: %1 - + moderator модератор - - it's you + + it's you це ви - + Kick Виключити - + Make moderator Зробити модератором @@ -2628,7 +2685,7 @@ PhoneNumberUtil - + Anonymous Анонім @@ -2636,42 +2693,42 @@ PreferredIdentityEditWindow - + Phone Number Transmission Передача номера телефону - + Name - Ім'я + Ім'я - + Prefix Префікс - + Identity Ідентифікатор - + Enabled Увімкнено - + Automatic Автоматично - + Delete Видалити - + Save Зберегти @@ -2679,1236 +2736,1237 @@ QObject - + Andorra Андорра - + United Arab Emirates - Об'єднані Арабські Емірати + Об'єднані Арабські Емірати - + Afghanistan Афганістан - + Antigua and Barbuda Антигуа і Барбуда - + Anguilla Ангілья - + Albania Албанія - + Armenia Вірменія - + Angola Ангола - + Argentina Аргентина - + American Samoa Американське Самоа - + Austria Австрія - + Australia Австралія - + Aruba Аруба - + Aland Islands Аландські острови - + Azerbaijan Азербайджан - + Bosnia and Herzegovina Боснія і Герцеговина - + Barbados Барбадос - + Bangladesh Бангладеш - + Belgium Бельгія - + Burkina Faso Буркіна-Фасо - + Bulgaria Болгарія - + Bahrain Бахрейн - + Burundi Бурунді - + Benin Бенін - + Saint Barthelemy Сен-Бартелемі - + Bermuda Бермуди - + Brunei Бруней - + Bolivia Болівія - + Bonaire, Saint Eustatius and Saba Бонайре, Сінт-Естатіус і Саба - + Brazil Бразилія - + Bahamas Багами - + Bhutan Бутан - + Botswana Ботсвана - + Belarus Білорусь - + Belize Беліз - + Canada Канада - + Cocos Islands Кокосові острови - + Democratic Republic of the Congo Демократична Республіка Конго - + Central African Republic Центральноафриканська Республіка - + Republic of the Congo Республіка Конго - + Switzerland Швейцарія - + Ivory Coast - Кот-д'Івуар + Кот-д'Івуар - + Cook Islands Острови Кука - + Chile Чилі - + Cameroon Камерун - + China Китай - + Colombia Колумбія - + Costa Rica Коста-Рика - + Cuba Куба - + Cape Verde Кабо-Верде - + Curacao Кюрасао - + Christmas Island Острів Різдва - + Cyprus Кіпр - + Czech Republic Чехія - + Germany Німеччина - + Djibouti Джибуті - + Denmark Данія - + Dominica Домініка - + Dominican Republic Домініканська Республіка - + Algeria Алжир - + Ecuador Еквадор - + Estonia Естонія - + Egypt Єгипет - + Western Sahara Західна Сахара - + Eritrea Еритрея - + Spain Іспанія - + Ethiopia Ефіопія - + Finland Фінляндія - + Fiji Фіджі - + Falkland Islands Фолклендські острови - + Micronesia Мікронезія - + Faroe Islands Фарерські острови - + France Франція - + Gabon Габон - + United Kingdom Велика Британія - + Grenada Гренада - + Georgia Грузія - + French Guiana Французька Гвіана - + Guernsey Гернсі - + Ghana Гана - + Gibraltar Гібралтар - + Greenland Гренландія - + Gambia Гамбія - + Guinea Гвінея - + Guadeloupe Гваделупа - + Equatorial Guinea Екваторіальна Гвінея - + Greece Греція - + Guatemala Гватемала - + Guam Гуам - + Guinea-Bissau Гвінея-Бісау - + Guyana Гайана - + Hong Kong Гонконг - + Heard Island and McDonald Islands Острови Херд і Макдональд - + Honduras Гондурас - + Croatia Хорватія - + Haiti Гаїті - + Hungary Угорщина - + Indonesia Індонезія - + Ireland Ірландія - + Israel Ізраїль - + Isle of Man Острів Мен - + India Індія - + British Indian Ocean Territory Британська територія в Індійському океані - + Iraq Ірак - + Iran Іран - + Iceland Ісландія - + Italy Італія - + Jersey Джерсі - + Jamaica Ямайка - + Jordan Йорданія - + Japan Японія - + Kenya Кенія - + Kyrgyzstan Киргизстан - + Cambodia Камбоджа - + Kiribati Кірібаті - + Comoros Комори - + Saint Kitts and Nevis Сент-Кітс і Невіс - + North Korea Північна Корея - + South Korea Південна Корея - + Kuwait Кувейт - + Cayman Islands Кайманові острови - + Kazakhstan Казахстан - + Laos Лаос - + Lebanon Ліван - + Saint Lucia Сент-Люсія - + Liechtenstein Ліхтенштейн - + Sri Lanka Шрі-Ланка - + Liberia Ліберія - + Lesotho Лесото - + Lithuania Литва - + Luxembourg Люксембург - + Latvia Латвія - + Libya Лівія - + Morocco Марокко - + Monaco Монако - + Moldova Молдова - + Montenegro Чорногорія - + Saint Martin Сен-Мартен - + Madagascar Мадагаскар - + Marshall Islands Маршаллові острови - + Macedonia Македонія - + Mali Малі - + Myanmar - М'янма + М'янма - + Mongolia Монголія - + Macao Макао - + Northern Mariana Islands Північні Маріанські острови - + Martinique Мартиніка - + Mauritania Мавританія - + Montserrat Монтсеррат - + Malta Мальта - + Mauritius Маврикій - + Maldives Мальдіви - + Malawi Малаві - + Mexico Мексика - + Malaysia Малайзія - + Mozambique Мозамбік - + Namibia Намібія - + New Caledonia Нова Каледонія - + Niger Нігер - + Norfolk Island Острів Норфолк - + Nigeria Нігерія - + Nicaragua Нікарагуа - + Netherlands Нідерланди - + Norway Норвегія - + Nepal Непал - + Nauru Науру - + Niue Ніуе - + New Zealand Нова Зеландія - + Oman Оман - + Panama Панама - + Peru Перу - + French Polynesia Французька Полінезія - + Papua New Guinea Папуа Нова Гвінея - + Philippines Філіппіни - + Pakistan Пакистан - + Poland Польща - + Saint Pierre and Miquelon - Сен-П'єр і Мікелон + Сен-П'єр і Мікелон - + Pitcairn Піткерн - + Puerto Rico Пуерто-Рико - + Palestinian Territory Палестинська територія - + Portugal Португалія - + Palau Палау - + Paraguay Парагвай - + Qatar Катар - + Reunion Реюньйон - + Romania Румунія - + Serbia Сербія - + Russia Росія - + Rwanda Руанда - + Saudi Arabia Саудівська Аравія - + Solomon Islands Соломонові острови - + Seychelles Сейшельські острови - + Sudan Судан - + Sweden Швеція - + Singapore Сингапур - + Saint Helena Острів Святої Єлени - + Slovenia Словенія - + Svalbard and Jan Mayen Шпіцберген і Ян-Маєн - + Slovakia Словаччина - + Sierra Leone Сьєрра-Леоне - + San Marino Сан-Маріно - + Senegal Сенегал - + Somalia Сомалі - + Suriname Суринам - + South Sudan Південний Судан - + Sao Tome and Principe Сан-Томе і Принсіпі - + El Salvador Сальвадор - + Sint Maarten Сінт-Мартен - + Syria Сирія - + Swaziland Свазіленд - + Turks and Caicos Islands Острови Теркс і Кайкос - + Chad Чад - + Togo Того - + Thailand Таїланд - + Tajikistan Таджикистан - + Tokelau Токелау - + East Timor Східний Тимор - + Turkmenistan Туркменістан - + Tunisia Туніс - + Tonga Тонга - + Turkey Туреччина - + Trinidad and Tobago Тринідад і Тобаго - + Tuvalu Тувалу - + Taiwan Тайвань - + Tanzania Танзанія - + Ukraine Україна - + Uganda Уганда - + United States Minor Outlying Islands Зовнішні малі острови США - + United States Сполучені Штати - + Uruguay Уругвай - + Uzbekistan Узбекистан - + Vatican Ватикан - + Saint Vincent and the Grenadines Сент-Вінсент і Гренадини - + Venezuela Венесуела - + British Virgin Islands Британські Віргінські острови - + U.S. Virgin Islands Американські Віргінські острови - + Vietnam - В'єтнам + В'єтнам - + Vanuatu Вануату - + Wallis and Futuna Волліс і Футуна - + Samoa Самоа - + Yemen Ємен - + Mayotte Майотта - + South Africa Південна Африка - + Zambia Замбія - + Zimbabwe Зімбабве - + There are %n active call(s). calls Є %n активний дзвінок. + Є %n активні дзвінки. Є %n активних дзвінків. @@ -3916,10 +3974,10 @@ ResponseTreeModel - - - - + + + + Additional Information Додаткова інформація @@ -3927,27 +3985,27 @@ RingToneFactory - + ringTone 425,0,1000,4000,0 - + busyTone 425,0,480,480,0 - + congestionTone 425,0,240,240,0 - + zip 425,0,200,200,200,1000,200,200,200,1000,200,200,200,5000,4 - + endTone 425,0,200,200,200,200,200,200,-1 @@ -3955,40 +4013,45 @@ SIPAccount - - 'userUri' is no valid SIP URI: %1 - 'userUri' не є допустимим SIP URI: %1 + + 'userUri' is no valid SIP URI: %1 + 'userUri' не є допустимим SIP URI: %1 - - 'userUri' is required - 'userUri' обов'язковий + + 'userUri' is required + 'userUri' обов'язковий - - 'registrarUri' is no valid SIP URI: %1 - 'registrarUri' не є допустимим SIP URI: %1 + + 'registrarUri' is no valid SIP URI: %1 + 'registrarUri' не є допустимим SIP URI: %1 - - 'registrarUri' is required - 'registrarUri' обов'язковий + + 'registrarUri' is required + 'registrarUri' обов'язковий - - 'proxies' contains invalid SIP URI entry: %1 - 'proxies' містить недопустимий SIP URI: %1 + + 'proxies' contains invalid SIP URI entry: %1 + 'proxies' містить недопустимий SIP URI: %1 - + Failed to create %1: %2 Не вдалося створити %1: %2 + + + Failed to persist SIP credentials: %1 + Не вдалося зберегти облікові дані SIP: %1 + SIPBuddy - + %1 is now available %1 тепер доступний @@ -3996,12 +4059,12 @@ SIPCall - + Active call with %1 Активний дзвінок з %1 - + Hang up Завершити @@ -4009,34 +4072,34 @@ SIPCallManager - + %1 is calling %1 дзвонить - + %1 (%2) is calling %1 (%2) дзвонить - - + + Accept Прийняти - + Call back Передзвонити - - + + Reject Відхилити - + Missed call from %1 Пропущений дзвінок від %1 @@ -4044,7 +4107,7 @@ SIPManager - + New Identity Новий ідентифікатор @@ -4052,18 +4115,18 @@ SIPTemplate - - + + Failed to write to %1 Помилка запису в %1 - + Failed to copy %1 to the config space Помилка копіювання %1 до каталогу конфігурації - + Source file %1 does not exist Вихідний файл %1 не існує @@ -4071,7 +4134,7 @@ SearchBox - + Search number Пошук номера @@ -4079,12 +4142,12 @@ SearchCategoryItem - + Search result category filter %1 Фільтр категорії результатів пошуку %1 - + Filter for the individual search result items by category Фільтр результатів пошуку за категорією @@ -4092,22 +4155,22 @@ SearchCategoryList - + Contacts Контакти - + Messages Повідомлення - + Rooms and Teams Кімнати та команди - + Files Файли @@ -4115,42 +4178,42 @@ SearchDial - + Select number Вибрати номер - + Activate search field Активувати поле пошуку - + Number or contact Номер або контакт - + Clear search field Очистити поле пошуку - + Default За замовчуванням - + Auto Авто - + Preferred identity Бажаний ідентифікатор - + Select the preferred identity for outgoing calls Вибрати бажаний ідентифікатор для вихідних дзвінків @@ -4158,12 +4221,12 @@ SearchField - + Search for contacts or room names... Пошук контактів або кімнат... - + Clear search field Очистити поле пошуку @@ -4171,12 +4234,12 @@ SearchResultCategory - + Search result category %1 Категорія результатів пошуку %1 - + Divider for the individual search result items by category Роздільник результатів пошуку за категорією @@ -4184,12 +4247,12 @@ SearchResultItem - + Search result Результат пошуку - + Currently selected search result Поточний результат пошуку @@ -4197,17 +4260,17 @@ SearchResultNumberItem - + Phone number Номер телефону - + Selected favorite number %1 Вибраний обраний номер %1 - + Selected phone number %1 Вибраний номер телефону %1 @@ -4215,52 +4278,52 @@ SearchResultPopup - + Search filter and identity selection Фільтр пошуку та вибір ідентифікатора - + Select search filter to be applied, as well as the outgoing identity Вибрати фільтр пошуку та вихідний ідентифікатор - + Outgoing identity Вихідний ідентифікатор - + Search results Результати пошуку - + All search results will be listed here in their respective categories Усі результати пошуку будуть перераховані за категоріями - + Direct dial Прямий набір - - Call "%1" + + Call "%1" Зателефонувати «%1» - - Open room "%1" + + Open room "%1" Відкрити кімнату «%1» - + History Історія - + Contacts Контакти @@ -4268,314 +4331,314 @@ SettingsPage - + Settings Налаштування - + Show chat messages as desktop notifications Показувати повідомлення чату як сповіщення - + Enable USB headset driver [%1] Увімкнути драйвер USB-гарнітури [%1] - + not detected не виявлено - + Show dial window on USB headset pick up Показувати вікно набору при знятті USB-гарнітури - + Color scheme Колірна схема - + System default Системна за замовчуванням - + Light Світла - + Dark Темна - + Inverse Accept / Reject buttons Інвертувати кнопки прийняття/відхилення - + Use dark mode tray icon Використовувати значок трея для темного режиму - + Disable USB headset mute state propagation Вимкнути синхронізацію стану вимкнення USB-гарнітури - + Reload contacts from LDAP Перезавантажити контакти з LDAP - + Phoning Телефонія - + Show main window on startup Показувати головне вікно при запуску - + Disable synchronisation with the system mute state Вимкнути синхронізацію із системним вимкненням звуку - - + + restart required потрібен перезапуск - + Appearance Зовнішній вигляд - + Use custom window decoration Використовувати власне оформлення вікна - + Theme selection box Поле вибору теми - + Select the UI theme Вибрати тему інтерфейсу - + Currently selected theme option Поточний параметр теми - + Signalling busy when a call is active Сигналізувати зайнятість при активному дзвінку - + Rules for telephone number transmission Правила передачі номера телефону - + Standard preferred identity Стандартний бажаний ідентифікатор - - + + Default За замовчуванням - - + + Auto Авто - - + + Prefererred identity selection Вибір бажаного ідентифікатора - - + + Select the preferred identity Вибрати бажаний ідентифікатор - + Currently selected identity option Поточний параметр ідентифікатора - + No preferred identities yet. Немає бажаних ідентифікаторів. - + Currently highlighted preferred identity. Tap to edit. Поточний бажаний ідентифікатор. Натисніть для редагування. - + Standard Стандарт - + Add identity Додати ідентифікатор - + Add a new preferred identity entry Додати новий бажаний ідентифікатор - + Audio settings Налаштування звуку - + Input device Пристрій вводу - + Input device selection Вибір пристрою вводу - + Select the input device to be used Вибрати пристрій вводу - + Currently selected input option Поточний параметр вводу - + Output device Пристрій виводу - + Output device selection Вибір пристрою виводу - + Select the output device to be used Вибрати пристрій виводу - + Currently selected output option Поточний параметр виводу - + Output device for ring tone Пристрій виводу для мелодії дзвінка - + Currently selected ring output option Поточний параметр виводу дзвінка - + Ring tone Мелодія дзвінка - + Prefer USB headset ring sound if available Використовувати дзвінок USB-гарнітури при наявності - + Reset ring tone Скинути мелодію дзвінка - + Reset the ring tone to its default option Відновити стандартну мелодію дзвінка - + Pick ring tone Вибрати мелодію - + Select the ring tone you want to use for incoming calls Вибрати мелодію для вхідних дзвінків - - + + Currently set to: Поточне значення: - + Ring tone volume Гучність мелодії дзвінка - - + + Adjust %1 Налаштувати %1 - + %1 % %1 % - + Pause between ring tones [s] Пауза між дзвінками [с] - + Debugging Налагодження - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Натисніть для запуску сеансу налагодження. Застосунок перезапуститься і почне записувати додаткову інформацію. Сеанс обмежено 5 хвилинами. - + Start debug run (restart app) Почати налагодження (перезапустити застосунок) - + Download debug information Завантажити інформацію налагодження @@ -4583,62 +4646,62 @@ ShortcutsWindow - + Shortcuts Гарячі клавіші - + Shortcut key: %1 Клавіша швидкого доступу: %1 - + Local shortcuts (work only when app is focused) Локальні сполучення (працюють лише при фокусі на застосунку) - + Local shortcuts Локальні сполучення - + Ctrl + F Ctrl + F - + Activates the global search field Активує глобальне поле пошуку - + F11 F11 - + Toggles between fullsceen and normal window mode Перемикає між повноекранним і звичайним режимом - + Ctrl + Shift + M Ctrl + Shift + M - + Toggles audio mute Вмикає/вимикає звук - + Global shortcuts (work from anywhere) Глобальні сполучення (працюють будь-де) - + Global shortcuts Глобальні сполучення @@ -4646,143 +4709,143 @@ SipTemplateWizard - + Initial configuration Початкова конфігурація - + Error: %1 Помилка: %1 - + SIP wizard notification Сповіщення майстра SIP - + GOnnect cannot find a SIP configuration. To get started, pick one of the templates below and modify the resulting configuration file if required. GOnnect не може знайти конфігурацію SIP. Виберіть шаблон нижче та за потреби змініть файл. - + Please pick: Будь ласка, виберіть: - + Select SIP template Вибрати шаблон SIP - + Select the SIP template to be used Вибрати шаблон SIP - + Currently selected SIP template Поточний шаблон SIP - + Next Далі - + Continue setup Продовжити налаштування - + Confirmation button to continue the setup Кнопка підтвердження для продовження - + Template field list Список полів шаблону - + List of all the available SIP template options Список усіх доступних параметрів шаблону SIP - + SIP template option Параметр шаблону SIP - + Currently selected SIP template option Поточний параметр шаблону SIP - + Display name of the SIP template option - Відображуване ім'я параметра шаблону SIP + Відображуване ім'я параметра шаблону SIP - + Description of the SIP template option Опис параметра шаблону SIP - + Back Назад - + Back button to return to the template selection menu Кнопка повернення до меню вибору шаблону - - + + Finish Завершити - + Confirmation button to apply the changes to the SIP template Кнопка підтвердження застосування змін - + Successful configuration file creation Файл конфігурації успішно створено - + We have created a configuration file for you. Please check if any changes are required to meet your needs and restart GOnnect to activate them. Файл конфігурації створено. Перевірте, чи потрібні зміни, та перезапустіть GOnnect. - + The configuration has been saved to: Конфігурацію збережено до: - + Copy to clipboard Скопіювати до буфера обміну - + Copy the full path of the configuration file to the clipboard Скопіювати повний шлях до файлу конфігурації - + Finish wizard Завершити майстер - + Finish the SIP configuration wizard Завершити майстер налаштування SIP @@ -4790,27 +4853,27 @@ StateManager - + Show dial window and focus search field Показати вікно набору та перейти до поля пошуку - + End all calls Завершити всі дзвінки - + Redial last outgoing call Повторити останній вихідний дзвінок - + Toggle hold Перемкнути утримання - + Phone calls are active Дзвінки активні @@ -4818,48 +4881,48 @@ SystemTrayMenu - - + + Dial... Набір номера... - + Not registered... Не зареєстровано... - + Settings Налаштування - + About Про програму - + Quit Вийти - + End conference Завершити конференцію - + Call with %1 has ended Дзвінок з %1 завершено - + Hang up call with %1 Завершити дзвінок з %1 - + Accept call with %1 Прийняти дзвінок від %1 @@ -4867,32 +4930,32 @@ TemplateFieldFile - + File path Шлях до файлу - + Enter the file path for %1 Введіть шлях до файлу для %1 - + Choose... Вибрати... - + Open file picker Відкрити вибір файлу - + Select the file that should be used for %1 Вибрати файл для %1 - + Certificate files (%1) Файли сертифікатів (%1) @@ -4900,12 +4963,12 @@ TemplateFieldText - + Text input Текстовий ввід - + Enter the desired value for %1 Введіть потрібне значення для %1 @@ -4913,7 +4976,7 @@ Toggler - + Failed to toggle the state of %1. Не вдалося перемкнути стан %1. @@ -4921,17 +4984,17 @@ TogglerList - + Toggler list Список перемикачів - + List of items that can be toggled Список перемикуваних елементів - + Toggle %1 Перемкнути %1 @@ -4939,7 +5002,7 @@ VerticalLevelMeter - + Level meter Індикатор рівня @@ -4947,7 +5010,7 @@ VideoDeviceMenu - + Virtual background Віртуальний фон @@ -4955,36 +5018,39 @@ ViewHelper - + Save File Зберегти файл - + %n minute(s) %n хвилина %n хвилин + %n хвилини - + 1 hour and %n minute(s) 1 година і %n хвилина + 1 година і %n хвилини 1 година і %n хвилин - + %n hour(s) %n година %n годин + %n години - + Audio Files (%1) Аудіофайли (%1) @@ -4992,22 +5058,22 @@ VolumePopup - + Adjust the volume Налаштувати гучність - + Unmute Увімкнути звук - + Mute Вимкнути звук - + Open audio settings Відкрити налаштування звуку @@ -5015,147 +5081,147 @@ WidgetSelectionWindow - + Add widget Додати віджет - + Widget Віджет - + Widget selection header Заголовок вибору віджета - + Date Events Події - + List of upcoming appointments Список майбутніх зустрічей - + Favorites Обране - + Quick dial for your favorite contacts and conferences Швидкий набір обраних контактів - + History Історія - + Web View Веб-перегляд - + A web-based content display Відображення веб-вмісту - + Widget selection Вибір віджета - + Select the widget that should be added to the current dashboard page Вибрати віджет для додавання на поточну сторінку - + Currently selected widget option Поточний параметр віджета - + Accept all certificates Прийняти всі сертифікати - + Confirm widget selection Підтвердити вибір віджета - + Confirmation button to create and add the selected widget to the current dashboard Кнопка створення та додавання віджета - + Searchable call and conference history Історія дзвінків та конференцій - + Title Назва - + URL URL - + URL (dark mode) URL (темний режим) - + Settings text input Поле налаштувань - + Input for widget setting %1 Ввід для параметра віджета %1 - + Settings checkbox Прапорець налаштувань - + Checkbox for widget setting %1 Прапорець для параметра віджета %1 - + Widget setting %1 Параметр віджета %1 - + Cancel Скасувати - + Cancel widget selection Скасувати вибір віджета - + Cancel button to exit widget selection selection without changes Кнопка скасування без змін - + Add Додати @@ -5163,24 +5229,24 @@ WindowHeader - + GOnnect window header Заголовок вікна GOnnect - + Minimize Згорнути - + Maximize Розгорнути - + Close GOnnect window Закрити вікно GOnnect - \ No newline at end of file + diff --git a/src/AuthManager.cpp b/src/AuthManager.cpp index 3452a266..ca06b7d7 100644 --- a/src/AuthManager.cpp +++ b/src/AuthManager.cpp @@ -122,7 +122,7 @@ void AuthManager::storeRefreshToken(const QString &token) const [](QKeychain::Error error, const QString &, const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist jitsi refresh token: %1").arg(message)); + tr("Failed to persist jitsi refresh token: %1").arg(message)); } }); } diff --git a/src/Main.qml b/src/Main.qml index 15232347..fc2926af 100644 --- a/src/Main.qml +++ b/src/Main.qml @@ -17,6 +17,8 @@ Item { } Component.onCompleted: () => { + qsTr("QT_LAYOUT_DIRECTION", "QGuiApplication"); + DialogFactory.rootItem = baseItem if (settings.showMainWindowOnStart) { @@ -131,6 +133,12 @@ Item { const dialog = DialogFactory.createDialog("CredentialsDialog.qml", { text: qsTr("Please enter the password for the SIP account:") }) dialog.onPasswordAccepted.connect(pw => SIPAccountManager.setAccountCredentials(accountId, pw)) } + function onConnectionError(code : int, message : string) { + DialogFactory.createInfoDialog({ + title: qsTr("Registration failed"), + text: qsTr("Registration failed with with status %1: %2").arg(code).arg(message) + }) + } } Component { diff --git a/src/calendar/DateEventFeederManager.cpp b/src/calendar/DateEventFeederManager.cpp index 14e24a2a..a2d32f4b 100644 --- a/src/calendar/DateEventFeederManager.cpp +++ b/src/calendar/DateEventFeederManager.cpp @@ -75,7 +75,7 @@ void DateEventFeederManager::acquireSecret(bool forcePrompt, const QString &conf const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist calendar " + tr("Failed to persist calendar " "credentials: %1") .arg(message)); } diff --git a/src/chat/ChatConnectorManager.cpp b/src/chat/ChatConnectorManager.cpp index 2f277337..aab3a044 100644 --- a/src/chat/ChatConnectorManager.cpp +++ b/src/chat/ChatConnectorManager.cpp @@ -39,7 +39,7 @@ void ChatConnectorManager::saveRecoveryKey(const QString &settingsGroup, const Q [](QKeychain::Error error, const QString &, const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist chat recovery code: %1").arg(message)); + tr("Failed to persist chat recovery code: %1").arg(message)); } }); } @@ -64,7 +64,7 @@ void ChatConnectorManager::saveAccessToken(const QString &settingsGroup, const Q [](QKeychain::Error error, const QString &, const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist chat access token: %1").arg(message)); + tr("Failed to persist chat access token: %1").arg(message)); } }); } diff --git a/src/contacts/AddressBookManager.cpp b/src/contacts/AddressBookManager.cpp index 78b0e94b..27f89433 100644 --- a/src/contacts/AddressBookManager.cpp +++ b/src/contacts/AddressBookManager.cpp @@ -174,7 +174,7 @@ void AddressBookManager::acquireSecret(bool forcePrompt, const QString &group, const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist address book " + tr("Failed to persist address book " "credentials: %1") .arg(message)); } diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index a3c08f16..98877c1c 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -9,6 +9,7 @@ #include "ErrorBus.h" #include "NetworkHelper.h" #include "Credentials.h" +#include "EnumTranslation.h" #include @@ -380,9 +381,10 @@ void SIPAccount::initialize() return; } - if (secret.isEmpty()) { + if (error == QKeychain::EntryNotFound) { qCWarning(lcSIPAccount) - << "no password available for auth group" << auth; + << "no password available for auth group" << auth + << "- using empty one to trigger authorization"; } pj::AuthCredInfo cred(scheme.toStdString(), realm.toStdString(), @@ -738,8 +740,15 @@ void SIPAccount::onRegState(pj::OnRegStateParam &prm) Q_EMIT isRegisteredChanged(); } - if (prm.code == PJSIP_SC_UNAUTHORIZED || prm.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED) { + if (prm.code == PJSIP_SC_UNAUTHORIZED || prm.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED + || prm.code == PJSIP_SC_FORBIDDEN) { Q_EMIT authorizationFailed(); + return; + } + + if (prm.code >= 400) { + Q_EMIT connectionError(prm.code, EnumTranslation::instance().sipStatusCode(prm.code)); + return; } if (!m_useInstantMessagingWithoutCheck && m_isRegistered && m_shallNegotiateCapabilities) { @@ -818,7 +827,7 @@ void SIPAccount::setCredentials(const QString &password) [authGroup](QKeychain::Error error, const QString &, const QString &message) { if (error != QKeychain::NoError) { ErrorBus::instance().error( - tr("Failed persist SIP credentials: %1").arg(message)); + tr("Failed to persist SIP credentials: %1").arg(message)); } }); } diff --git a/src/sip/SIPAccount.h b/src/sip/SIPAccount.h index 95808b6d..546ea89b 100644 --- a/src/sip/SIPAccount.h +++ b/src/sip/SIPAccount.h @@ -96,5 +96,6 @@ class SIPAccount : public QObject, public pj::Account void initialized(bool success); void isRegisteredChanged(); void authorizationFailed(); + void connectionError(int code, QString message); void messageReceived(const QString &sender, const QString &message, const QString &mimeType); }; diff --git a/src/ui/GonnectWindow.qml b/src/ui/GonnectWindow.qml index 523f97ae..ed1c45ee 100644 --- a/src/ui/GonnectWindow.qml +++ b/src/ui/GonnectWindow.qml @@ -15,6 +15,9 @@ BaseWindow { title: "GOnnect" resizable: true + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + readonly property LoggingCategory lc: LoggingCategory { id: category name: "gonnect.qml.GonnectWindow" diff --git a/src/ui/JitsiConnector.cpp b/src/ui/JitsiConnector.cpp index b674b660..ed12735f 100644 --- a/src/ui/JitsiConnector.cpp +++ b/src/ui/JitsiConnector.cpp @@ -601,7 +601,8 @@ void JitsiConnector::setRoomPassword(QString value) setIsPasswordRequired(!value.isEmpty()); } else { - ErrorBus::instance().error(tr("Failed persist room password: %1").arg(message)); + ErrorBus::instance().error( + tr("Failed to persist room password: %1").arg(message)); } }); } @@ -1308,7 +1309,7 @@ void JitsiConnector::enterPassword(const QString &password, bool rememberPasswor Q_EMIT executePasswordCommand(password); } else { ErrorBus::instance().error( - tr("Failed persist room password: %1").arg(message)); + tr("Failed to persist room password: %1").arg(message)); } }); } From c58ddab1df2445f8b5d5a2198026fee1115b4783 Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:02:31 +0100 Subject: [PATCH 010/122] chore: improved EDS integration and processing (#376) * chore: improved date event model handling (wip) * fix: use separate concrete source ids for caldav * fix: actually cancel async eds methods on timeout, refactoring/cleanup --------- Co-authored-by: Cajus Pollmeier --- src/calendar/DateEventManager.cpp | 8 +- src/calendar/caldav/CalDAVEventFeeder.cpp | 23 +++--- src/calendar/caldav/CalDAVEventFeeder.h | 2 +- src/calendar/eds/EDSEventFeeder.cpp | 91 +++++++++++------------ src/calendar/eds/EDSEventFeeder.h | 8 +- src/contacts/eds/EDSAddressBookFeeder.cpp | 77 +++++++++---------- src/contacts/eds/EDSAddressBookFeeder.h | 8 +- src/ui/DateEventsModel.cpp | 15 ++-- 8 files changed, 112 insertions(+), 120 deletions(-) diff --git a/src/calendar/DateEventManager.cpp b/src/calendar/DateEventManager.cpp index 00f775ef..5d90e0c5 100644 --- a/src/calendar/DateEventManager.cpp +++ b/src/calendar/DateEventManager.cpp @@ -195,7 +195,6 @@ void DateEventManager::removeDateEvent(const QString &id, const QDateTime &start qsizetype index = 0; QMutableListIterator it(m_dateEvents); while (it.hasNext()) { - index++; const auto item = it.next(); if (item && item->id() == dateEventId) { const auto eventHash = item->getHash(); @@ -210,6 +209,7 @@ void DateEventManager::removeDateEvent(const QString &id, const QDateTime &start Q_EMIT dateEventRemoved(index); } + index++; } } } @@ -237,19 +237,17 @@ void DateEventManager::resetDateEvents() void DateEventManager::removeDateEventsBySource(const QString &source) { - qsizetype i = 0; QMutexLocker lock(&m_feederMutex); QMutableListIterator it(m_dateEvents); while (it.hasNext()) { - i++; const auto item = it.next(); if (item && item->source() == source) { item->deleteLater(); it.remove(); - - Q_EMIT dateEventRemoved(i); } } + + Q_EMIT dateEventsCleared(); } bool DateEventManager::isAddedDateEvent(const QString &id) diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index 0496f997..509b78a0 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -67,7 +67,7 @@ void CalDAVEventFeeder::onParserFinished() QNetworkReply *reply = m_webdav.get(item.path()); connect( reply, &QNetworkReply::finished, this, - [reply, this]() { + [item, reply, this]() { if (!reply) { return; } @@ -78,10 +78,12 @@ void CalDAVEventFeeder::onParserFinished() QMimeType type = db.mimeTypeForData(data); if (type.name() == "text/calendar" && !data.isEmpty() && responseDataChanged(data)) { + QString concreteSource = QString("%1-%2").arg(m_config.source, item.name()); + DateEventManager &manager = DateEventManager::instance(); - manager.removeDateEventsBySource(m_config.source); + manager.removeDateEventsBySource(concreteSource); - processResponse(data); + processResponse(data, concreteSource); } }, Qt::ConnectionType::SingleShotConnection); @@ -100,7 +102,7 @@ void CalDAVEventFeeder::process(bool authFailed) }); } -void CalDAVEventFeeder::processResponse(const QByteArray &data) +void CalDAVEventFeeder::processResponse(const QByteArray &data, const QString &source) { DateEventManager &manager = DateEventManager::instance(); @@ -206,8 +208,8 @@ void CalDAVEventFeeder::processResponse(const QByteArray &data) && (recurStart >= m_config.timeRangeStart || recurMultiDay)) { QString recurId = QString("%1-%2").arg(id).arg(recurStart.toMSecsSinceEpoch()); - manager.addDateEvent(recurId, m_config.source, recurStart, recurEnd, - summary, location, description); + manager.addDateEvent(recurId, source, recurStart, recurEnd, summary, + location, description); } } @@ -220,16 +222,13 @@ void CalDAVEventFeeder::processResponse(const QByteArray &data) manager.removeDateEvent(id, start, end); } else if (manager.isAddedDateEvent(id)) { // Exists but modified - manager.modifyDateEvent(id, m_config.source, start, end, summary, location, - description); + manager.modifyDateEvent(id, source, start, end, summary, location, description); } else { // Does not exist, e.g. moved from past to future, different day - manager.addDateEvent(id, m_config.source, start, end, summary, location, - description); + manager.addDateEvent(id, source, start, end, summary, location, description); } } else { // Normal event, no recurrence, or update of a recurrent instance - manager.addDateEvent(id, m_config.source, start, end, summary, location, - description); + manager.addDateEvent(id, source, start, end, summary, location, description); } } } else { diff --git a/src/calendar/caldav/CalDAVEventFeeder.h b/src/calendar/caldav/CalDAVEventFeeder.h index 7661cb17..1bdc6921 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.h +++ b/src/calendar/caldav/CalDAVEventFeeder.h @@ -33,7 +33,7 @@ private Q_SLOTS: void onParserFinished(); private: - void processResponse(const QByteArray &data); + void processResponse(const QByteArray &data, const QString &source); bool responseDataChanged(const QByteArray &data); QDateTime createDateTimeFromTimeType(icaltimetype &datetime); diff --git a/src/calendar/eds/EDSEventFeeder.cpp b/src/calendar/eds/EDSEventFeeder.cpp index 6c34b88c..7775c86f 100644 --- a/src/calendar/eds/EDSEventFeeder.cpp +++ b/src/calendar/eds/EDSEventFeeder.cpp @@ -22,24 +22,19 @@ EDSEventFeeder::EDSEventFeeder(QObject *parent, const QString &source, const QDa EDSEventFeeder::~EDSEventFeeder() { - if (m_registry) { - g_object_unref(m_registry); - } + g_clear_object(&m_registry); if (m_sources) { g_list_free_full(m_sources, g_object_unref); } - if (m_searchExpr) { - g_free(m_searchExpr); - } + g_clear_pointer(&m_searchExpr, g_free); + g_clear_object(&m_cancellable); for (auto client : std::as_const(m_clients)) { - g_object_unref(client); - client = nullptr; + g_clear_object(&client); } for (auto clientView : std::as_const(m_clientViews)) { - g_object_unref(clientView); - clientView = nullptr; + g_clear_object(&clientView); } if (m_sourcePromise) { @@ -55,15 +50,16 @@ EDSEventFeeder::~EDSEventFeeder() void EDSEventFeeder::init() { - GError *error = nullptr; + m_cancellable = g_cancellable_new(); + + GError *error = NULL; // Create a source registry - m_registry = e_source_registry_new_sync(nullptr, &error); + m_registry = e_source_registry_new_sync(m_cancellable, &error); if (!m_registry) { if (error) { qCDebug(lcEDSEventFeeder) << "Can't create registry:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -85,13 +81,13 @@ void EDSEventFeeder::init() m_sourceFuture = m_sourcePromise->future(); m_futureWatcher = new QFutureWatcher(); - for (GList *iter = m_sources; iter != nullptr; iter = g_list_next(iter)) { + for (GList *iter = m_sources; iter != NULL; iter = g_list_next(iter)) { ESource *source = E_SOURCE(iter->data); qCDebug(lcEDSEventFeeder) << "Connecting to source" << e_source_get_display_name(source) << "(" << e_source_get_uid(source) << ")"; - e_cal_client_connect(source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, -1, nullptr, + e_cal_client_connect(source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, -1, m_cancellable, onEcalClientConnected, this); } @@ -107,6 +103,8 @@ void EDSEventFeeder::init() if (!m_futureWatcher->isFinished()) { qCDebug(lcEDSEventFeeder) << "Failed to process EDS sources"; + g_cancellable_cancel(m_cancellable); + m_sourceFuture.cancel(); m_futureWatcher->cancel(); } @@ -118,7 +116,8 @@ void EDSEventFeeder::init() void EDSEventFeeder::process() { for (auto client : std::as_const(m_clients)) { - e_cal_client_get_object_list(client, m_searchExpr, nullptr, onClientEventsRequested, this); + e_cal_client_get_object_list(client, m_searchExpr, m_cancellable, onClientEventsRequested, + this); } } @@ -152,8 +151,8 @@ void EDSEventFeeder::onEcalClientConnected(GObject *source_object, GAsyncResult { Q_UNUSED(source_object) - GError *error = nullptr; - ECalClient *client = nullptr; + GError *error = NULL; + ECalClient *client = NULL; EDSEventFeeder *feeder = static_cast(user_data); if (feeder) { @@ -161,12 +160,12 @@ void EDSEventFeeder::onEcalClientConnected(GObject *source_object, GAsyncResult if (error) { qCDebug(lcEDSEventFeeder) << "Can't retrieve finished client connection:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); return; } - e_cal_client_get_view(client, feeder->m_searchExpr, nullptr, onViewCreated, feeder); + e_cal_client_get_view(client, feeder->m_searchExpr, feeder->m_cancellable, onViewCreated, + feeder); } } @@ -183,8 +182,7 @@ void EDSEventFeeder::onViewComplete(ECalClientView *view, GError *error, gpointe Otherwise, future view updates would cause duplicate signal connections. */ guint signalId = g_signal_lookup("complete", G_OBJECT_TYPE(view)); - g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, nullptr, nullptr, - nullptr); + g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, NULL, NULL, NULL); if (error) { qCCritical(lcEDSEventFeeder) << "Failed to wait for view completion, unable to subscribe " @@ -244,19 +242,19 @@ void EDSEventFeeder::processEventsAdded(ECalClientView *view) if (client) { if (!m_clients.contains(client)) { - g_object_unref(client); + g_clear_object(&client); return; } // Use a class-managed reference instead const auto idx = m_clients.indexOf(client); - g_object_unref(client); + g_clear_object(&client); QString concreteSource = QString("%1-%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); - e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, nullptr, + e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, m_cancellable, onClientEventsRequested, this); } } @@ -268,18 +266,18 @@ void EDSEventFeeder::processEventsModified(ECalClientView *view) if (client) { if (!m_clients.contains(client)) { - g_object_unref(client); + g_clear_object(&client); return; } const auto idx = m_clients.indexOf(client); - g_object_unref(client); + g_clear_object(&client); QString concreteSource = QString("%1-%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); - e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, nullptr, + e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, m_cancellable, onClientEventsRequested, this); } } @@ -291,35 +289,34 @@ void EDSEventFeeder::processEventsRemoved(ECalClientView *view) if (client) { if (!m_clients.contains(client)) { - g_object_unref(client); + g_clear_object(&client); return; } const auto idx = m_clients.indexOf(client); - g_object_unref(client); + g_clear_object(&client); QString concreteSource = QString("%1-%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); - e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, nullptr, + e_cal_client_get_object_list(m_clients.at(idx), m_searchExpr, m_cancellable, onClientEventsRequested, this); } } void EDSEventFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, gpointer user_data) { - GError *error = nullptr; + GError *error = NULL; ECalClient *client = E_CAL_CLIENT(source_object); - ECalClientView *view = nullptr; + ECalClientView *view = NULL; EDSEventFeeder *feeder = static_cast(user_data); if (feeder && client) { if (!e_cal_client_get_view_finish(client, result, &view, &error)) { if (error) { qCCritical(lcEDSEventFeeder) << "Can't retrieve finished view:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -328,8 +325,7 @@ void EDSEventFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, e_cal_client_view_start(view, &error); if (error) { qCCritical(lcEDSEventFeeder) << "Can't start view:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); return; } feeder->m_clientViews.append(view); @@ -344,8 +340,8 @@ void EDSEventFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, void EDSEventFeeder::onClientEventsRequested(GObject *source_object, GAsyncResult *result, gpointer user_data) { - GError *error = nullptr; - GSList *components = nullptr; + GError *error = NULL; + GSList *components = NULL; EDSEventFeeder *feeder = static_cast(user_data); if (feeder) { @@ -353,8 +349,7 @@ void EDSEventFeeder::onClientEventsRequested(GObject *source_object, GAsyncResul &error)) { if (error) { qCCritical(lcEDSEventFeeder) << "Can't retrieve events:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -376,7 +371,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList QMap> exdatesById; - for (GSList *item = components; item != nullptr; item = g_slist_next(item)) { + for (GSList *item = components; item != NULL; item = g_slist_next(item)) { ICalComponent *component = I_CAL_COMPONENT(item->data); if (component && i_cal_component_isa(component) == I_CAL_VEVENT_COMPONENT) { QString id = i_cal_component_get_uid(component); @@ -391,7 +386,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList bool isRecurrent = false; ICalProperty *prop = i_cal_component_get_first_property(component, I_CAL_RRULE_PROPERTY); - ICalRecurrence *rrule = nullptr; + ICalRecurrence *rrule = NULL; if (prop) { isRecurrent = true; rrule = i_cal_property_get_rrule(prop); @@ -437,11 +432,11 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList if (isRecurrent) { // Recurrent origin event, parsed first // Get EXDATE's - ICalTime *exdate = nullptr; + ICalTime *exdate = NULL; QList exdates; for (ICalProperty *prop = i_cal_component_get_first_property(component, I_CAL_EXDATE_PROPERTY); - prop != nullptr; + prop != NULL; prop = i_cal_component_get_next_property(component, I_CAL_EXDATE_PROPERTY)) { exdate = i_cal_property_get_exdate(prop); exdates.append(createDateTimeFromTimeType(exdate)); @@ -505,7 +500,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList } g_slist_free_full(components, g_object_unref); - components = nullptr; + components = NULL; qCInfo(lcEDSEventFeeder) << "Loaded events of source" << clientName << "(" << clientUid << ")"; } diff --git a/src/calendar/eds/EDSEventFeeder.h b/src/calendar/eds/EDSEventFeeder.h index bdd23f1b..e244ea1a 100644 --- a/src/calendar/eds/EDSEventFeeder.h +++ b/src/calendar/eds/EDSEventFeeder.h @@ -60,12 +60,14 @@ class EDSEventFeeder : public QObject, public IDateEventFeeder QDateTime m_timeRangeStart; QDateTime m_timeRangeEnd; - ESourceRegistry *m_registry = nullptr; - GList *m_sources = nullptr; - gchar *m_searchExpr = nullptr; + ESourceRegistry *m_registry = NULL; + GList *m_sources = NULL; + gchar *m_searchExpr = NULL; QList m_clients; QList m_clientViews; + GCancellable *m_cancellable = NULL; + int m_sourceCount = 0; std::atomic m_clientCount = 0; QPromise *m_sourcePromise = nullptr; diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index 9f22031a..577f292e 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -20,24 +20,19 @@ EDSAddressBookFeeder::EDSAddressBookFeeder(const QString &group, AddressBookMana EDSAddressBookFeeder::~EDSAddressBookFeeder() { - if (m_registry) { - g_object_unref(m_registry); - } + g_clear_object(&m_registry); if (m_sources) { g_list_free_full(m_sources, g_object_unref); } - if (m_searchExpr) { - g_free(m_searchExpr); - } + g_clear_pointer(&m_searchExpr, g_free); + g_clear_object(&m_cancellable); for (auto client : std::as_const(m_clients)) { - g_object_unref(client); - client = nullptr; + g_clear_object(&client); } for (auto clientView : std::as_const(m_clientViews)) { - g_object_unref(clientView); - clientView = nullptr; + g_clear_object(&clientView); } if (m_sourcePromise) { @@ -53,15 +48,16 @@ EDSAddressBookFeeder::~EDSAddressBookFeeder() void EDSAddressBookFeeder::init() { - GError *error = nullptr; + m_cancellable = g_cancellable_new(); + + GError *error = NULL; // Create a source registry - m_registry = e_source_registry_new_sync(nullptr, &error); + m_registry = e_source_registry_new_sync(m_cancellable, &error); if (!m_registry) { if (error) { qCDebug(lcEDSAddressBookFeeder) << "Can't create registry:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -77,14 +73,14 @@ void EDSAddressBookFeeder::init() // Prepare the search query EBookQuery *query = e_book_query_any_field_contains(""); m_searchExpr = e_book_query_to_string(query); - e_book_query_unref(query); + g_clear_pointer(&query, e_book_query_unref); // Clients and signals m_sourcePromise = new QPromise(); m_sourceFuture = m_sourcePromise->future(); m_futureWatcher = new QFutureWatcher(); - for (GList *iter = m_sources; iter != nullptr; iter = g_list_next(iter)) { + for (GList *iter = m_sources; iter != NULL; iter = g_list_next(iter)) { ESource *source = E_SOURCE(iter->data); QString sourceInfo = @@ -92,7 +88,7 @@ void EDSAddressBookFeeder::init() qCDebug(lcEDSAddressBookFeeder) << "Connecting to source" << sourceInfo; - e_book_client_connect(source, -1, nullptr, onEbookClientConnected, this); + e_book_client_connect(source, -1, m_cancellable, onEbookClientConnected, this); } m_sourcePromise->start(); @@ -107,6 +103,8 @@ void EDSAddressBookFeeder::init() if (!m_futureWatcher->isFinished()) { qCDebug(lcEDSAddressBookFeeder) << "Failed to process EDS sources"; + g_cancellable_cancel(m_cancellable); + m_sourceFuture.cancel(); m_futureWatcher->cancel(); } @@ -166,7 +164,7 @@ QStringList EDSAddressBookFeeder::getList(EContact *contact, EContactField id) if (contact) { GList *items = static_cast(e_contact_get(contact, id)); - for (GList *item = items; item != nullptr; item = g_list_next(item)) { + for (GList *item = items; item != NULL; item = g_list_next(item)) { const gchar *result = static_cast(item->data); if (result) { results.append(QString::fromUtf8(result)); @@ -194,8 +192,7 @@ void EDSAddressBookFeeder::onViewComplete(EBookClientView *view, GError *error, Otherwise, future view updates would cause duplicate signal connections. */ guint signalId = g_signal_lookup("complete", G_OBJECT_TYPE(view)); - g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, nullptr, nullptr, - nullptr); + g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, NULL, NULL, NULL); if (error) { qCCritical(lcEDSAddressBookFeeder) @@ -257,7 +254,7 @@ void EDSAddressBookFeeder::processContactsAdded(GSList *contacts) { auto &addressbook = AddressBook::instance(); - for (GSList *item = contacts; item != nullptr; item = g_slist_next(item)) { + for (GSList *item = contacts; item != NULL; item = g_slist_next(item)) { if (EContact *eContact = E_CONTACT(item->data)) { QDateTime changed = QDateTime::fromString(getField(eContact, E_CONTACT_REV), Qt::ISODate); @@ -292,7 +289,7 @@ void EDSAddressBookFeeder::processContactsModified(GSList *contacts) { auto &addressbook = AddressBook::instance(); - for (GSList *item = contacts; item != nullptr; item = g_slist_next(item)) { + for (GSList *item = contacts; item != NULL; item = g_slist_next(item)) { if (EContact *eContact = E_CONTACT(item->data)) { QDateTime changed = QDateTime::fromString(getField(eContact, E_CONTACT_REV), Qt::ISODate); @@ -329,7 +326,7 @@ void EDSAddressBookFeeder::processContactsRemoved(GSList *uids) { auto &addressbook = AddressBook::instance(); - for (GSList *item = uids; item != nullptr; item = g_slist_next(item)) { + for (GSList *item = uids; item != NULL; item = g_slist_next(item)) { const gchar *uid = static_cast(item->data); if (uid) { // Do not keep images of deleted contacts @@ -346,8 +343,8 @@ void EDSAddressBookFeeder::onEbookClientConnected(GObject *source_object, GAsync { Q_UNUSED(source_object) - GError *error = nullptr; - EBookClient *client = nullptr; + GError *error = NULL; + EBookClient *client = NULL; EDSAddressBookFeeder *feeder = static_cast(user_data); if (feeder) { @@ -355,21 +352,21 @@ void EDSAddressBookFeeder::onEbookClientConnected(GObject *source_object, GAsync if (error) { qCDebug(lcEDSAddressBookFeeder) << "Can't retrieve finished client connection:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); return; } - e_book_client_get_view(client, feeder->m_searchExpr, nullptr, onViewCreated, feeder); + e_book_client_get_view(client, feeder->m_searchExpr, feeder->m_cancellable, onViewCreated, + feeder); } } void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, gpointer user_data) { - GError *error = nullptr; + GError *error = NULL; EBookClient *client = E_BOOK_CLIENT(source_object); - EBookClientView *view = nullptr; + EBookClientView *view = NULL; EDSAddressBookFeeder *feeder = static_cast(user_data); if (feeder && client) { @@ -377,8 +374,7 @@ void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *r if (error) { qCCritical(lcEDSAddressBookFeeder) << "Can't retrieve finished view:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -387,8 +383,7 @@ void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *r e_book_client_view_start(view, &error); if (error) { qCCritical(lcEDSAddressBookFeeder) << "Can't start view:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); return; } feeder->m_clientViews.append(view); @@ -404,8 +399,8 @@ void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *r void EDSAddressBookFeeder::onClientContactsRequested(GObject *source_object, GAsyncResult *result, gpointer user_data) { - GError *error = nullptr; - GSList *contacts = nullptr; + GError *error = NULL; + GSList *contacts = NULL; EDSAddressBookFeeder *feeder = static_cast(user_data); if (feeder) { @@ -413,8 +408,7 @@ void EDSAddressBookFeeder::onClientContactsRequested(GObject *source_object, GAs &error)) { if (error) { qCCritical(lcEDSAddressBookFeeder) << "Can't retrieve contacts:" << error->message; - g_error_free(error); - error = nullptr; + g_clear_error(&error); } return; } @@ -434,7 +428,7 @@ void EDSAddressBookFeeder::processContacts(QString clientInfo, GSList *contacts) unsigned contactCount = 0; auto &addressbook = AddressBook::instance(); - for (GSList *item = contacts; item != nullptr; item = g_slist_next(item)) { + for (GSList *item = contacts; item != NULL; item = g_slist_next(item)) { if (EContact *eContact = E_CONTACT(item->data)) { QDateTime changed = QDateTime::fromString(getField(eContact, E_CONTACT_REV), Qt::ISODate); @@ -467,7 +461,7 @@ void EDSAddressBookFeeder::processContacts(QString clientInfo, GSList *contacts) } g_slist_free_full(contacts, g_object_unref); - contacts = nullptr; + contacts = NULL; qCInfo(lcEDSAddressBookFeeder) << "Loaded" << contactCount << "contact(s) of source" << clientInfo; @@ -523,6 +517,7 @@ void EDSAddressBookFeeder::addAvatar(QString id, EContact *contact, QDateTime ch void EDSAddressBookFeeder::feedAddressBook() { for (auto client : std::as_const(m_clients)) { - e_book_client_get_contacts(client, m_searchExpr, nullptr, onClientContactsRequested, this); + e_book_client_get_contacts(client, m_searchExpr, m_cancellable, onClientContactsRequested, + this); } } diff --git a/src/contacts/eds/EDSAddressBookFeeder.h b/src/contacts/eds/EDSAddressBookFeeder.h index 812ec3c0..c216c142 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.h +++ b/src/contacts/eds/EDSAddressBookFeeder.h @@ -60,12 +60,14 @@ class EDSAddressBookFeeder : public QObject, public IAddressBookFeeder QString m_group; BlockInfo m_blockInfo; - ESourceRegistry *m_registry = nullptr; - GList *m_sources = nullptr; - gchar *m_searchExpr = nullptr; + ESourceRegistry *m_registry = NULL; + GList *m_sources = NULL; + gchar *m_searchExpr = NULL; QList m_clients; QList m_clientViews; + GCancellable *m_cancellable = NULL; + int m_sourceCount = 0; std::atomic m_clientCount = 0; QPromise *m_sourcePromise = nullptr; diff --git a/src/ui/DateEventsModel.cpp b/src/ui/DateEventsModel.cpp index a70dab6b..3bf99cf4 100644 --- a/src/ui/DateEventsModel.cpp +++ b/src/ui/DateEventsModel.cpp @@ -11,19 +11,20 @@ DateEventsModel::DateEventsModel(QObject *parent) : QAbstractListModel{ parent } endResetModel(); }); - connect(&manager, &DateEventManager::dateEventAdded, this, [this](qsizetype, DateEvent *) { - beginResetModel(); - endResetModel(); - }); + connect(&manager, &DateEventManager::dateEventAdded, this, + [this](qsizetype index, DateEvent *) { + beginInsertRows(QModelIndex(), index, index); + endInsertRows(); + }); connect(&manager, &DateEventManager::dateEventsModified, this, [this]() { beginResetModel(); endResetModel(); }); - connect(&manager, &DateEventManager::dateEventRemoved, this, [this](qsizetype) { - beginResetModel(); - endResetModel(); + connect(&manager, &DateEventManager::dateEventRemoved, this, [this](qsizetype index) { + beginRemoveRows(QModelIndex(), index, index); + endRemoveRows(); }); } From fbc4fb74d87afcaa89381f4d53cbc32f7f8aa9e1 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:55:15 +0100 Subject: [PATCH 011/122] chore(deps): update actions/download-artifact digest to 3e5f45b (#381) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 2384ec28..ef15dca1 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -279,7 +279,7 @@ jobs: conan: false - name: Download Test artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: gonnect-tests From 35f4546cdcf09365f41d960478b5358a07784b53 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:25:09 +0100 Subject: [PATCH 012/122] chore(deps): update jidicula/clang-format-action action to v4.17.0 (#382) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index ef15dca1..76feaf26 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -125,7 +125,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run clang-format style check for C/C++/Protobuf programs. - uses: jidicula/clang-format-action@6cd220de46c89139a0365edae93eee8eb30ca8fe # v4.16.0 + uses: jidicula/clang-format-action@3a18028048f01a66653b4e3bf8d968d2e7e2cb8b # v4.17.0 with: clang-format-version: '21' check-path: 'src' From b6d8819619edc0003a0ce3b837a2f84ef6a30b51 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 09:10:38 +0100 Subject: [PATCH 013/122] chore(deps): update actions/create-github-app-token digest to fee1f7d (#383) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03c7bf4f..c99d6672 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index fd64b32a..41c485fa 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_KEY }} From f9e6567a1bc89b5c688d7edb53fd4cc54899dcba Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Mon, 16 Mar 2026 07:21:36 +0100 Subject: [PATCH 014/122] feat: display sip registration status (#375) * feat: display sip registration status * Tune suspend resume behaviour * Show a different tray icon on registration problems * Remove blocking network connections * Optimization for RTL languages --------- Co-authored-by: Cajus Pollmeier Co-authored-by: Markus Bader --- resources/artwork/gonnect_noreg_dark.svg | 18 ++ resources/artwork/gonnect_noreg_light.svg | 18 ++ src/Application.cpp | 26 +- src/Application.h | 2 +- src/CMakeLists.txt | 2 - src/GlobalCallState.cpp | 2 +- src/GlobalMuteState.cpp | 1 - src/Main.qml | 2 + src/StateManager.cpp | 38 ++- src/StateManager.h | 1 + src/calendar/DateEventFeederManager.cpp | 20 +- src/calendar/caldav/CalDAVEventFeeder.cpp | 5 +- src/calendar/caldav/CalDAVEventFeeder.h | 2 +- src/calendar/eds/EDSEventFeeder.cpp | 2 +- src/contacts/AddressBookManager.cpp | 30 +-- .../carddav/CardDAVAddressBookFeeder.cpp | 2 +- .../carddav/CardDAVAddressBookFeeder.h | 2 +- src/contacts/eds/EDSAddressBookFeeder.cpp | 2 +- src/contacts/ldap/LDAPInitializer.cpp | 22 ++ src/icons.qrc.in | 2 + src/main.cpp | 3 + src/platform/NetworkHelper.cpp | 22 +- src/platform/NetworkHelper.h | 2 +- src/platform/flatpak/FlatpakNetworkHelper.cpp | 45 ++-- src/platform/flatpak/FlatpakNetworkHelper.h | 2 +- src/sip/SIPAccount.cpp | 18 +- src/sip/SIPAccountManager.cpp | 9 +- src/sip/SIPAccountManager.h | 4 +- src/sip/SIPManager.cpp | 47 ++++ src/sip/SIPManager.h | 3 + src/ui/BaseWindow.qml | 10 + src/ui/SystemTrayMenu.cpp | 19 +- src/ui/components/AdditionalSettings.qml | 2 +- src/ui/components/AvatarImage.qml | 1 + src/ui/components/BaseWidget.qml | 9 + src/ui/components/BuddyStatusIndicator.qml | 8 + src/ui/components/CallButtonBar.qml | 1 + src/ui/components/ConferenceButtonBar.qml | 14 +- src/ui/components/CustomWindowHeader.qml | 16 +- src/ui/components/HistoryWidget.qml | 6 +- src/ui/components/SearchBox.qml | 121 --------- src/ui/components/SettingsPage.qml | 19 +- src/ui/components/controls/ControlBar.qml | 16 +- src/ui/components/controls/FirstAidButton.qml | 3 +- src/ui/components/controls/SearchDial.qml | 252 ------------------ src/ui/components/controls/SearchField.qml | 4 +- src/ui/components/pages/Call.qml | 6 +- src/ui/components/pages/Conference.qml | 6 +- .../components/popups/SearchResultPopup.qml | 16 +- src/usb/HeadsetDevice.cpp | 9 +- src/usb/HeadsetDevice.h | 1 - src/usb/busylight/LitraBeamLX.cpp | 2 +- 52 files changed, 373 insertions(+), 522 deletions(-) create mode 100644 resources/artwork/gonnect_noreg_dark.svg create mode 100644 resources/artwork/gonnect_noreg_light.svg delete mode 100644 src/ui/components/SearchBox.qml delete mode 100644 src/ui/components/controls/SearchDial.qml diff --git a/resources/artwork/gonnect_noreg_dark.svg b/resources/artwork/gonnect_noreg_dark.svg new file mode 100644 index 00000000..9ac8a22b --- /dev/null +++ b/resources/artwork/gonnect_noreg_dark.svg @@ -0,0 +1,18 @@ + + diff --git a/resources/artwork/gonnect_noreg_light.svg b/resources/artwork/gonnect_noreg_light.svg new file mode 100644 index 00000000..0e69a108 --- /dev/null +++ b/resources/artwork/gonnect_noreg_light.svg @@ -0,0 +1,18 @@ + + diff --git a/src/Application.cpp b/src/Application.cpp index ffcbaa4a..7ad81276 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #ifdef Q_OS_MACOS # define LOGFAULT_WITH_OS_LOG @@ -62,20 +61,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) setApplicationVersion(QString::fromStdString(getVersion())); installTranslations(); - initLogging(); - - AddressBookManager::instance().initAddressBookConfigs(); - DateEventFeederManager::instance().initFeederConfigs(); - setQuitOnLastWindowClosed(false); - USBDevices::instance().initialize(); - - StateManager::instance().setParent(this); - SearchProvider::instance().setParent(this); - UISettings::instance().setParent(this); - #ifdef Q_OS_LINUX if (::socketpair(AF_UNIX, SOCK_STREAM, 0, s_sighupFd)) { qFatal("Couldn't create HUP socketpair"); @@ -91,13 +79,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) connect(m_termNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleSigTerm())); #endif - // Take care for running "initialize" after exec() is called - QTimer::singleShot(0, this, &Application::initialize); - #ifdef Q_OS_WINDOWS QObject::connect(this, &QGuiApplication::commitDataRequest, [](QSessionManager &manager) { manager.cancel(); }); #endif + + StateManager::instance().initialize(); } void Application::installTranslations() @@ -130,7 +117,14 @@ void Application::setRootWindow(QQuickWindow *win) void Application::initialize() { - StateManager::instance().initialize(); + StateManager::instance().initializeSip(); + + AddressBookManager::instance().initAddressBookConfigs(); + DateEventFeederManager::instance().initFeederConfigs(); + USBDevices::instance().initialize(); + StateManager::instance().setParent(this); + SearchProvider::instance().setParent(this); + UISettings::instance().setParent(this); AddressBookManager::instance().reloadAddressBook(); DateEventFeederManager::instance().reload(); diff --git a/src/Application.h b/src/Application.h index 010769df..d556cb77 100644 --- a/src/Application.h +++ b/src/Application.h @@ -43,6 +43,7 @@ public Q_SLOTS: void handleSigTerm(); #endif + void initialize(); void shutdown(); private: @@ -50,7 +51,6 @@ public Q_SLOTS: const QString &rawMsg); void initLogging(); - void initialize(); void initializeSIP(); void installTranslations(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afd00682..64180469 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -338,7 +338,6 @@ qt_add_qml_module(gonnect ui/components/controls/HeaderIconButton.qml ui/components/controls/Label.qml ui/components/controls/MenuContactInfo.qml - ui/components/controls/SearchDial.qml ui/components/controls/SearchField.qml ui/components/controls/Switch.qml ui/components/controls/TabBar.qml @@ -407,7 +406,6 @@ qt_add_qml_module(gonnect ui/components/HistoryList.qml ui/components/MainTabBar.qml ui/components/ParticipantsList.qml - ui/components/SearchBox.qml ui/components/SettingsPage.qml ui/components/TogglerList.qml ui/components/VerticalLevelMeter.qml diff --git a/src/GlobalCallState.cpp b/src/GlobalCallState.cpp index 6427d36c..5e51b554 100644 --- a/src/GlobalCallState.cpp +++ b/src/GlobalCallState.cpp @@ -176,8 +176,8 @@ void GlobalCallState::unholdOtherCall() const for (auto callObj : std::as_const(m_globalCallStateObjects)) { if (callObj->callState() & ICallState::State::OnHold) { callObj->toggleHold(); + break; } - break; } } } diff --git a/src/GlobalMuteState.cpp b/src/GlobalMuteState.cpp index ccfc4c36..c10af6d8 100644 --- a/src/GlobalMuteState.cpp +++ b/src/GlobalMuteState.cpp @@ -1,5 +1,4 @@ #include "GlobalMuteState.h" -#include "GlobalCallState.h" GlobalMuteState::GlobalMuteState(QObject *parent) : QObject{ parent } { } diff --git a/src/Main.qml b/src/Main.qml index fc2926af..5f73deb6 100644 --- a/src/Main.qml +++ b/src/Main.qml @@ -128,7 +128,9 @@ Item { } Connections { + id: sipAccountManagerConnections target: SIPAccountManager + function onAuthorizationFailed(accountId : string) { const dialog = DialogFactory.createDialog("CredentialsDialog.qml", { text: qsTr("Please enter the password for the SIP account:") }) dialog.onPasswordAccepted.connect(pw => SIPAccountManager.setAccountCredentials(accountId, pw)) diff --git a/src/StateManager.cpp b/src/StateManager.cpp index 7a614674..6e55e15a 100644 --- a/src/StateManager.cpp +++ b/src/StateManager.cpp @@ -14,6 +14,7 @@ #include "CallHistory.h" #include "GlobalCallState.h" #include "ExternalMediaManager.h" +#include "NetworkHelper.h" Q_LOGGING_CATEGORY(lcStateHandling, "gonnect.state") @@ -29,20 +30,6 @@ StateManager::StateManager(QObject *parent) : QObject(parent) con.registerObject(FLATPAK_APP_PATH, this) && con.registerService(FLATPAK_APP_ID); } #endif - - m_inhibitHelper = &InhibitHelper::instance(); - connect(m_inhibitHelper, &InhibitHelper::stateChanged, this, - &StateManager::sessionStateChanged); - - auto &cm = SIPCallManager::instance(); - connect(&cm, &SIPCallManager::activeCallsChanged, this, [this]() { - if (SIPCallManager::instance().activeCalls() == 0) { - m_inhibitHelper->release(); - } - }); - - connect(&GlobalCallState::instance(), &GlobalCallState::globalCallStateChanged, this, - &StateManager::updateInhibitState); } bool StateManager::globalShortcutsSupported() const @@ -66,6 +53,12 @@ QVariantMap StateManager::globalShortcuts() const void StateManager::initialize() { + m_inhibitHelper = &InhibitHelper::instance(); + connect(m_inhibitHelper, &InhibitHelper::stateChanged, this, + &StateManager::sessionStateChanged); + + connect(&GlobalCallState::instance(), &GlobalCallState::globalCallStateChanged, this, + &StateManager::updateInhibitState); auto &globalShortcuts = GlobalShortcuts::instance(); QList shortcuts = { @@ -100,6 +93,21 @@ void StateManager::initialize() cm.toggleHold(); } }); + connect(&NetworkHelper::instance(), &NetworkHelper::connectivityChanged, this, []() { + if (NetworkHelper::instance().hasConnectivity()) { + SIPManager::instance().resume(); + } + }); +} + +void StateManager::initializeSip() +{ + auto &cm = SIPCallManager::instance(); + connect(&cm, &SIPCallManager::activeCallsChanged, this, [this]() { + if (SIPCallManager::instance().activeCalls() == 0) { + m_inhibitHelper->release(); + } + }); } void StateManager::restart() @@ -152,6 +160,8 @@ void StateManager::sessionStateChanged(bool, InhibitHelper::InhibitState state) | InhibitHelper::InhibitFlag::SUSPEND | InhibitHelper::InhibitFlag::USER_SWITCH, QObject::tr("There are %n active call(s).", "calls", activeCalls)); + } else if (state == InhibitHelper::InhibitState::ENDING) { + SIPManager::instance().suspend(); } } } diff --git a/src/StateManager.h b/src/StateManager.h index 13db42ca..6f17bcaf 100644 --- a/src/StateManager.h +++ b/src/StateManager.h @@ -32,6 +32,7 @@ class StateManager : public QObject } void initialize(); + void initializeSip(); bool globalShortcutsSupported() const; QVariantMap globalShortcuts() const; diff --git a/src/calendar/DateEventFeederManager.cpp b/src/calendar/DateEventFeederManager.cpp index a2d32f4b..da3b1185 100644 --- a/src/calendar/DateEventFeederManager.cpp +++ b/src/calendar/DateEventFeederManager.cpp @@ -129,7 +129,6 @@ void DateEventFeederManager::processQueue() const auto &configId = it.next(); if (auto feeder = m_dateEventFeeders.value(configId, nullptr)) { - QUrl urlToCheck = feeder->networkCheckURL(); if (!urlToCheck.isEmpty()) { @@ -149,15 +148,20 @@ void DateEventFeederManager::processQueue() continue; } - if (!networkHelper.isReachable(urlToCheck)) { - qCWarning(lcDateEventFeederManager) - << "Feeder url" << urlToCheck << "is not reachable"; - setupReconnectSignal(); - continue; - } + networkHelper.isReachable(urlToCheck) + .then(this, [feeder, urlToCheck, this](bool isReachable) { + if (isReachable) { + QMutexLocker mutex(&m_queueMutex); + feeder->init(); + + } else { + qCWarning(lcDateEventFeederManager) + << "Feeder url" << urlToCheck << "is not reachable"; + setupReconnectSignal(); + } + }); } - feeder->init(); it.remove(); } } diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index 509b78a0..2d98820e 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -49,10 +49,11 @@ void CalDAVEventFeeder::init() '/caldav//Kalender'. A full calendar is generated on the fly once requested. */ m_calendarRefreshTimer.setInterval(m_config.interval); - connect(&m_calendarRefreshTimer, &QTimer::timeout, this, [this]() { process(false); }); + connect(&m_calendarRefreshTimer, &QTimer::timeout, this, [this]() { process(); }); + m_calendarRefreshTimer.start(); - process(false); + process(); } void CalDAVEventFeeder::onError(QString error) const diff --git a/src/calendar/caldav/CalDAVEventFeeder.h b/src/calendar/caldav/CalDAVEventFeeder.h index 1bdc6921..b61486a1 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.h +++ b/src/calendar/caldav/CalDAVEventFeeder.h @@ -26,7 +26,7 @@ class CalDAVEventFeeder : public QObject, public IDateEventFeeder void init() override; QUrl networkCheckURL() const override; - void process(bool authFailed); + void process(bool authFailed = false); private Q_SLOTS: void onError(QString error) const; diff --git a/src/calendar/eds/EDSEventFeeder.cpp b/src/calendar/eds/EDSEventFeeder.cpp index 7775c86f..4fb56d82 100644 --- a/src/calendar/eds/EDSEventFeeder.cpp +++ b/src/calendar/eds/EDSEventFeeder.cpp @@ -93,7 +93,7 @@ void EDSEventFeeder::init() m_sourcePromise->start(); - QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then([this]() { + QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then(this, [this]() { if (m_sourceFuture.isFinished()) { process(); } diff --git a/src/contacts/AddressBookManager.cpp b/src/contacts/AddressBookManager.cpp index 27f89433..57e4aafc 100644 --- a/src/contacts/AddressBookManager.cpp +++ b/src/contacts/AddressBookManager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace std::chrono_literals; using namespace Qt::Literals::StringLiterals; @@ -78,7 +79,6 @@ void AddressBookManager::reloadAddressBook() void AddressBookManager::processAddressBookQueue() { - bool changed = false; bool networkAvailable = true; auto &nh = NetworkHelper::instance(); @@ -120,26 +120,26 @@ void AddressBookManager::processAddressBookQueue() continue; } - if (!nh.isReachable(checkURL)) { - qCWarning(lcAddressBookManager) << checkURL << "is not reachable"; - connect( - &nh, &NetworkHelper::connectivityChanged, this, - [this]() { processAddressBookQueue(); }, - Qt::ConnectionType::SingleShotConnection); - continue; - } + nh.isReachable(checkURL).then(this, [feeder, checkURL, this](bool isReachable) { + if (isReachable) { + QMutexLocker mutex(&m_queueMutex); + + feeder->process(); + Q_EMIT AddressBook::instance().contactsReady(); + } else { + qCWarning(lcAddressBookManager) << checkURL << "is not reachable"; + connect( + &NetworkHelper::instance(), &NetworkHelper::connectivityChanged, + this, [this]() { processAddressBookQueue(); }, + Qt::ConnectionType::SingleShotConnection); + } + }); } - feeder->process(); it.remove(); - changed = true; } } - if (changed) { - Q_EMIT AddressBook::instance().contactsReady(); - } - m_queueMutex.unlock(); } diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index c5783550..d3f350e2 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -319,7 +319,7 @@ void CardDAVAddressBookFeeder::process() settings.value("port", useSSL ? 443 : 80).toInt(), useSSL }; init(); - feedAddressBook(false); + feedAddressBook(); settings.endGroup(); }; diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.h b/src/contacts/carddav/CardDAVAddressBookFeeder.h index 3deb1c4c..dfd75e82 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.h +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.h @@ -29,7 +29,7 @@ private Q_SLOTS: private: void init(); - void feedAddressBook(bool authFailed); + void feedAddressBook(bool authFailed = false); void processVcard(QByteArray data, const QString &uuid, const QDateTime &modifiedDate); void loadCachedData(const size_t hash); diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index 577f292e..f9679432 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -93,7 +93,7 @@ void EDSAddressBookFeeder::init() m_sourcePromise->start(); - QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then([this]() { + QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then(this, [this]() { if (m_sourceFuture.isFinished()) { feedAddressBook(); } diff --git a/src/contacts/ldap/LDAPInitializer.cpp b/src/contacts/ldap/LDAPInitializer.cpp index c3c0f221..71ad8142 100644 --- a/src/contacts/ldap/LDAPInitializer.cpp +++ b/src/contacts/ldap/LDAPInitializer.cpp @@ -1,8 +1,13 @@ #include "LDAPInitializer.h" +#include "ReadOnlyConfdSettings.h" #include #include +#ifdef WIN32 +# include +#endif + #ifdef HAVE_SASL # include #endif @@ -91,6 +96,23 @@ LDAP *LDAPInitializer::initialize(const LDAPInitializer::Config &config, int &re int version = 3; ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &version); + // Adjust timeouts + ReadOnlyConfdSettings settings; + + struct timeval tv; + bool ok = true; + auto networkTimeout = settings.value("generic/networkTimeout", 5).toUInt(&ok); + if (!ok) { + qCWarning(lcLDAPInitializer) + << "non numeric value for generic/networkTimeout - falling back to default"; + networkTimeout = 5; + } + tv.tv_sec = networkTimeout; + tv.tv_usec = 0; + ldap_set_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &tv); + qCDebug(lcLDAPInitializer) << "setting LDAP connection timeout to" << networkTimeout + << "seconds"; + // Use TLS if (config.useSSL) { result = ldap_start_tls_s(ldap, NULL, NULL); diff --git a/src/icons.qrc.in b/src/icons.qrc.in index 54196f6a..27049847 100644 --- a/src/icons.qrc.in +++ b/src/icons.qrc.in @@ -4,6 +4,8 @@ @PROJECT_SOURCE_DIR@/resources/artwork/gonnect.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_light.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_dark.svg + @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_noreg_light.svg + @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_noreg_dark.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_ring.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_line.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_dark_note.svg diff --git a/src/main.cpp b/src/main.cpp index 0657c72c..c32c341d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -125,6 +125,9 @@ int main(int argc, char *argv[]) app.setRootWindow(mainWindow); } + // Take care for running "initialize" after exec() is called + QTimer::singleShot(0, &app, &Application::initialize); + exitCode = app.exec(); if (exitCode == RESTART_CODE) { QProcess::startDetached(argv[0]); diff --git a/src/platform/NetworkHelper.cpp b/src/platform/NetworkHelper.cpp index a48bd318..f4541e86 100644 --- a/src/platform/NetworkHelper.cpp +++ b/src/platform/NetworkHelper.cpp @@ -78,20 +78,22 @@ NetworkHelper::NetworkHelper(QObject *parent) : QObject(parent) onReachabilityChanged(netInfo->reachability()); } -bool NetworkHelper::isReachable(const QUrl &url) +QFuture NetworkHelper::isReachable(const QUrl &url) { - QTcpSocket testSocket; - testSocket.connectToHost(url.host(), url.port()); - testSocket.waitForConnected(1000); + return QtConcurrent::run([url]() -> bool { + QTcpSocket testSocket; + testSocket.connectToHost(url.host(), url.port()); + testSocket.waitForConnected(1000); - bool state = testSocket.state() == QTcpSocket::UnconnectedState; - testSocket.close(); + bool state = testSocket.state() == QTcpSocket::UnconnectedState; + testSocket.close(); - if (!state) { - qCWarning(lcNetwork) << url << "is not reachable"; - } + if (!state) { + qCWarning(lcNetwork) << url << "is not reachable"; + } - return state; + return state; + }); } void NetworkHelper::onReachabilityChanged(QNetworkInformation::Reachability reachability) diff --git a/src/platform/NetworkHelper.h b/src/platform/NetworkHelper.h index fbc254bd..638a000d 100644 --- a/src/platform/NetworkHelper.h +++ b/src/platform/NetworkHelper.h @@ -18,7 +18,7 @@ class NetworkHelper : public QObject static NetworkHelper &instance(); virtual bool hasConnectivity() const { return m_connectivity; } - virtual bool isReachable(const QUrl &url); + virtual QFuture isReachable(const QUrl &url); virtual QStringList nameservers() const; diff --git a/src/platform/flatpak/FlatpakNetworkHelper.cpp b/src/platform/flatpak/FlatpakNetworkHelper.cpp index 8a952874..fbbd94ac 100644 --- a/src/platform/flatpak/FlatpakNetworkHelper.cpp +++ b/src/platform/flatpak/FlatpakNetworkHelper.cpp @@ -2,6 +2,7 @@ #include "NetworkMonitor.h" #include #include +#include #define NH_CALL_TIMEOUT 500 @@ -62,34 +63,36 @@ void FlatpakNetworkHelper::updateNetworkState() } } -bool FlatpakNetworkHelper::isReachable(const QUrl &url) +QFuture FlatpakNetworkHelper::isReachable(const QUrl &url) { - int port = url.port(); - if (port < 0) { + return QtConcurrent::run([url, this]() -> bool { + int port = url.port(); + if (port < 0) { + + QString scheme = url.scheme(); + struct servent *sptr = getservbyname(scheme.toStdString().c_str(), "tcp"); + if (!sptr) { + qCCritical(lcNetwork) << "cannot map scheme" << scheme << "to port"; + return false; + } - QString scheme = url.scheme(); - struct servent *sptr = getservbyname(scheme.toStdString().c_str(), "tcp"); - if (!sptr) { - qCCritical(lcNetwork) << "cannot map scheme" << scheme << "to port"; - return false; + port = ntohs(sptr->s_port); } - port = ntohs(sptr->s_port); - } - - auto reply = m_portal->CanReach(url.host(), port); - reply.waitForFinished(); + auto reply = m_portal->CanReach(url.host(), port); + reply.waitForFinished(); - if (reply.isError()) { - qCWarning(lcNetwork) << "failed to call CanReach:", qPrintable(reply.error().message()); - return false; - } + if (reply.isError()) { + qCWarning(lcNetwork) << "failed to call CanReach:", qPrintable(reply.error().message()); + return false; + } - if (!reply.value()) { - qCWarning(lcNetwork) << "unable to reach" << url.toString(); - } + if (!reply.value()) { + qCWarning(lcNetwork) << "unable to reach" << url.toString(); + } - return reply.value(); + return reply.value(); + }); } QStringList FlatpakNetworkHelper::nameservers() const diff --git a/src/platform/flatpak/FlatpakNetworkHelper.h b/src/platform/flatpak/FlatpakNetworkHelper.h index 48b7b0b1..6dd83249 100644 --- a/src/platform/flatpak/FlatpakNetworkHelper.h +++ b/src/platform/flatpak/FlatpakNetworkHelper.h @@ -13,7 +13,7 @@ class FlatpakNetworkHelper : public NetworkHelper explicit FlatpakNetworkHelper(); ~FlatpakNetworkHelper() = default; - bool isReachable(const QUrl &url) override; + QFuture isReachable(const QUrl &url) override; QStringList nameservers() const override; diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index 98877c1c..f120c4ae 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -421,18 +421,6 @@ void SIPAccount::finalizeInitialization() return; } - connect(&NetworkHelper::instance(), &NetworkHelper::connectivityChanged, this, []() { - if (NetworkHelper::instance().hasConnectivity() - && !SIPCallManager::instance().hasActiveCalls()) { - try { - pj::Endpoint::instance().handleIpChange(pj::IpChangeParam()); - } catch (pj::Error &err) { - qCCritical(lcSIPAccount) << "Ignoring pjsip error on handle ip change:" - << QString::fromLocal8Bit(err.info(false)); - } - } - }); - Q_EMIT initialized(true); } @@ -746,7 +734,11 @@ void SIPAccount::onRegState(pj::OnRegStateParam &prm) return; } - if (prm.code >= 400) { + if (prm.code == PJSIP_SC_SERVICE_UNAVAILABLE || prm.code == PJSIP_SC_SERVER_TIMEOUT) { + return; + } + + if (prm.code >= 400 && prm.code < 600) { Q_EMIT connectionError(prm.code, EnumTranslation::instance().sipStatusCode(prm.code)); return; } diff --git a/src/sip/SIPAccountManager.cpp b/src/sip/SIPAccountManager.cpp index c9d4257f..8fb84e53 100644 --- a/src/sip/SIPAccountManager.cpp +++ b/src/sip/SIPAccountManager.cpp @@ -19,7 +19,7 @@ void SIPAccountManager::setSipRegistered(bool value) value ? ReportDescriptorEnums::TeamsPresenceIcon::Online : ReportDescriptorEnums::TeamsPresenceIcon::Offline); - Q_EMIT sipRegisteredChanged(); + Q_EMIT sipRegisteredChanged(value); } } @@ -50,6 +50,13 @@ void SIPAccountManager::initialize() &SIPAccountManager::updateSipRegistered); connect(sipAccount, &SIPAccount::authorizationFailed, this, [this, sipAccount]() { Q_EMIT authorizationFailed(sipAccount->id()); }); + connect(sipAccount, &SIPAccount::connectionError, this, + [this](int code, QString message) { + if (code != m_lastErrorCode) { + m_lastErrorCode = code; + Q_EMIT connectionError(code, message); + } + }); updateSipRegistered(); connect(sipAccount, &SIPAccount::initialized, this, diff --git a/src/sip/SIPAccountManager.h b/src/sip/SIPAccountManager.h index 2a1b45ce..4241f3ca 100644 --- a/src/sip/SIPAccountManager.h +++ b/src/sip/SIPAccountManager.h @@ -41,8 +41,9 @@ class SIPAccountManager : public QObject Q_SIGNALS: void accountsChanged(); - void sipRegisteredChanged(); + void sipRegisteredChanged(bool status); void authorizationFailed(QString accountId); + void connectionError(int code, QString message); private: SIPAccountManager(QObject *parent = nullptr); @@ -50,6 +51,7 @@ class SIPAccountManager : public QObject void updateSipRegistered(); unsigned m_numberOfAccounts = 0; + int m_lastErrorCode = 0; QList m_accounts; bool m_sipRegistered = false; diff --git a/src/sip/SIPManager.cpp b/src/sip/SIPManager.cpp index 1aa71745..7cbebc02 100644 --- a/src/sip/SIPManager.cpp +++ b/src/sip/SIPManager.cpp @@ -341,6 +341,53 @@ SIPBuddy *SIPManager::getBuddy(const QString &var) return nullptr; } +void SIPManager::suspend() +{ + qCDebug(lcSIPManager) << "suspending SIP"; + + pj::CallOpParam prm; + prm.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE; + + // Hang up all calls + auto calls = SIPCallManager::instance().calls(); + for (auto call : std::as_const(calls)) { + call->hangup(prm); + } + + // Unregister account(s) + auto accounts = SIPAccountManager::instance().accounts(); + for (auto account : std::as_const(accounts)) { + account->setRegistration(false); + } + + // Shutdown transports + pj::IpChangeParam param; + param.shutdownTransport = true; + param.restartListener = false; + pj::Endpoint::instance().handleIpChange(param); +} + +void SIPManager::resume() +{ + // Since resume may be called in more network changed cases, only + // do this when we have no active calls going. + if (!!SIPCallManager::instance().hasActiveCalls()) { + // Restore transports + try { + pj::Endpoint::instance().handleIpChange(pj::IpChangeParam()); + } catch (pj::Error &err) { + qCCritical(lcSIPManager) + << "error handling IP change:" << QString::fromLocal8Bit(err.info(false)); + } + + // Re-activate account registration + auto accounts = SIPAccountManager::instance().accounts(); + for (auto account : std::as_const(accounts)) { + account->setRegistration(false); + } + } +} + void SIPManager::shutdown() { qCDebug(lcSIPManager) << "shutting down"; diff --git a/src/sip/SIPManager.h b/src/sip/SIPManager.h index 93dc1d5f..eb22c34f 100644 --- a/src/sip/SIPManager.h +++ b/src/sip/SIPManager.h @@ -47,6 +47,9 @@ class SIPManager : public QObject void initialize(); void shutdown(); + void suspend(); + void resume(); + void setPreferredCodecs(); void getPlaybackDevices(); diff --git a/src/ui/BaseWindow.qml b/src/ui/BaseWindow.qml index d3c7c123..370f9b8a 100644 --- a/src/ui/BaseWindow.qml +++ b/src/ui/BaseWindow.qml @@ -90,6 +90,8 @@ Window { left: parent.left } + LayoutMirroring.enabled: false + Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") Accessible.description: qsTr("Top left drag border for window resize operations") @@ -119,6 +121,8 @@ Window { rightMargin: dragHandlerContainer.borderWidth } + LayoutMirroring.enabled: false + Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") Accessible.description: qsTr("Top drag border for window resize operations") @@ -145,6 +149,7 @@ Window { top: parent.top right: parent.right } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") @@ -174,6 +179,7 @@ Window { topMargin: dragHandlerContainer.borderWidth bottomMargin: dragHandlerContainer.borderWidth } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") @@ -201,6 +207,7 @@ Window { bottom: parent.bottom right: parent.right } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") @@ -230,6 +237,7 @@ Window { leftMargin: dragHandlerContainer.borderWidth rightMargin: dragHandlerContainer.borderWidth } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") @@ -257,6 +265,7 @@ Window { bottom: parent.bottom left: parent.left } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") @@ -286,6 +295,7 @@ Window { topMargin: dragHandlerContainer.borderWidth bottomMargin: dragHandlerContainer.borderWidth } + LayoutMirroring.enabled: false Accessible.role: Accessible.Border Accessible.name: qsTr("Drag border") diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp index e5be9437..4eabe9fa 100644 --- a/src/ui/SystemTrayMenu.cpp +++ b/src/ui/SystemTrayMenu.cpp @@ -56,6 +56,8 @@ SystemTrayMenu::SystemTrayMenu(QObject *parent) : QObject{ parent } &SystemTrayMenu::updateBuddyState); connect(&SIPAccountManager::instance(), &SIPAccountManager::sipRegisteredChanged, this, &SystemTrayMenu::updateMenu); + connect(&SIPAccountManager::instance(), &SIPAccountManager::sipRegisteredChanged, this, + &SystemTrayMenu::resetTrayIcon); connect(&SIPCallManager::instance(), &SIPCallManager::activeCallsChanged, this, &SystemTrayMenu::updateCalls); connect(&SIPCallManager::instance(), &SIPCallManager::establishedCallsCountChanged, this, @@ -453,13 +455,22 @@ void SystemTrayMenu::resetTrayIcon() ThemeManager::instance().trayColorScheme() == ThemeManager::ColorScheme::DARK; QString noteDot = m_missedCallsCount ? "_note" : ""; - if (m_hasEstablishedCalls) { - m_trayIcon->setIcon(QIcon(":/icons/gonnect_line" + noteDot + ".svg")); + const auto sipReg = SIPAccountManager::instance().sipRegistered(); + if (sipReg) { + if (m_hasEstablishedCalls) { + m_trayIcon->setIcon(QIcon(":/icons/gonnect_line" + noteDot + ".svg")); + } else { + if (m_settings.value("generic/trayIconDark", darkIconDefault).toBool()) { + m_trayIcon->setIcon(QIcon(":/icons/gonnect_dark" + noteDot + ".svg")); + } else { + m_trayIcon->setIcon(QIcon(":/icons/gonnect_light" + noteDot + ".svg")); + } + } } else { if (m_settings.value("generic/trayIconDark", darkIconDefault).toBool()) { - m_trayIcon->setIcon(QIcon(":/icons/gonnect_dark" + noteDot + ".svg")); + m_trayIcon->setIcon(QIcon(":/icons/gonnect_noreg_dark.svg")); } else { - m_trayIcon->setIcon(QIcon(":/icons/gonnect_light" + noteDot + ".svg")); + m_trayIcon->setIcon(QIcon(":/icons/gonnect_noreg_light.svg")); } } } diff --git a/src/ui/components/AdditionalSettings.qml b/src/ui/components/AdditionalSettings.qml index 611ff166..95595158 100644 --- a/src/ui/components/AdditionalSettings.qml +++ b/src/ui/components/AdditionalSettings.qml @@ -35,7 +35,7 @@ Item { return Object.keys(control.parameters) } - function data() { + function items() { return control.parameters } } diff --git a/src/ui/components/AvatarImage.qml b/src/ui/components/AvatarImage.qml index 703f6f9a..eadffbd6 100644 --- a/src/ui/components/AvatarImage.qml +++ b/src/ui/components/AvatarImage.qml @@ -19,6 +19,7 @@ Item { property alias showBuddyStatus: buddyStatusIndicatorContainer.visible property alias buddyStatus: buddyStatusIndicator.status property alias isBlocked: buddyStatusIndicator.isBlocked + property alias isUnregistered: buddyStatusIndicator.isUnregistered states: [ State { diff --git a/src/ui/components/BaseWidget.qml b/src/ui/components/BaseWidget.qml index efd72896..19485ba1 100644 --- a/src/ui/components/BaseWidget.qml +++ b/src/ui/components/BaseWidget.qml @@ -20,6 +20,9 @@ Item { signal cleanupRequested() + LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft + LayoutMirroring.childrenInherit: true + Connections { target: control.config function onParametersUpdated() { @@ -272,6 +275,7 @@ Item { anchors.left: parent.left anchors.top: parent.top cursorShape: Qt.SizeFDiagCursor + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewX(mouse.x) @@ -299,6 +303,7 @@ Item { anchors.right: parent.right anchors.top: parent.top cursorShape: Qt.SizeBDiagCursor + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewY(mouse.y) @@ -315,6 +320,7 @@ Item { right: resizeTopRight.right bottom: resizeBottomRight.bottom } + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewWidth(mouse.x) @@ -326,6 +332,7 @@ Item { anchors.right: parent.right anchors.bottom: parent.bottom cursorShape: Qt.SizeFDiagCursor + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewWidth(mouse.x) @@ -353,6 +360,7 @@ Item { anchors.left: parent.left anchors.bottom: parent.bottom cursorShape: Qt.SizeBDiagCursor + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewX(mouse.x) @@ -369,6 +377,7 @@ Item { right: resizeTopLeft.right bottom: resizeBottomLeft.top } + LayoutMirroring.enabled: false onPositionChanged: mouse => { resizeHandleOverlay.setNewX(mouse.x) diff --git a/src/ui/components/BuddyStatusIndicator.qml b/src/ui/components/BuddyStatusIndicator.qml index 924107db..c5671037 100644 --- a/src/ui/components/BuddyStatusIndicator.qml +++ b/src/ui/components/BuddyStatusIndicator.qml @@ -19,6 +19,7 @@ Rectangle { property int status: SIPBuddyState.UNKNOWN property bool isBlocked: false + property bool isUnregistered: false Accessible.ignored: true @@ -59,6 +60,13 @@ Rectangle { } states: [ + State { + when: control.isUnregistered + + PropertyChanges { + control.color: Theme.borderColor + } + }, State { when: control.isBlocked diff --git a/src/ui/components/CallButtonBar.qml b/src/ui/components/CallButtonBar.qml index d25a4662..5f608b38 100644 --- a/src/ui/components/CallButtonBar.qml +++ b/src/ui/components/CallButtonBar.qml @@ -502,6 +502,7 @@ Item { Row { spacing: 5 rightPadding: 20 + leftPadding: 20 anchors { top: parent.top bottom: parent.bottom diff --git a/src/ui/components/ConferenceButtonBar.qml b/src/ui/components/ConferenceButtonBar.qml index 28241636..684a15f8 100644 --- a/src/ui/components/ConferenceButtonBar.qml +++ b/src/ui/components/ConferenceButtonBar.qml @@ -152,9 +152,20 @@ Item { } } + Item { + id: dummyFlickableWidthCalculator + height: 1 + anchors { + top: parent.top + right: rightStickyButtonRow.left + left: timeLabelContainer.right + leftMargin: 20 + } + } + Flickable { id: rowFlickable - width: Math.min(buttonRow.implicitWidth, control.width - (timeLabelContainer.x + timeLabelContainer.width) - rightStickyButtonRow.implicitWidth) + implicitWidth: Math.min(dummyFlickableWidthCalculator.width, buttonRow.implicitWidth) contentWidth: buttonRow.implicitWidth clip: true anchors { @@ -396,6 +407,7 @@ Item { Row { id: rightStickyButtonRow spacing: 5 + leftPadding: 20 rightPadding: 20 anchors { top: parent.top diff --git a/src/ui/components/CustomWindowHeader.qml b/src/ui/components/CustomWindowHeader.qml index 5402976f..603913e0 100644 --- a/src/ui/components/CustomWindowHeader.qml +++ b/src/ui/components/CustomWindowHeader.qml @@ -235,8 +235,11 @@ Rectangle { size: 28 initials: ViewHelper.initials(ViewHelper.currentUserName) source: ViewHelper.currentUser?.hasAvatar ? ("file://" + ViewHelper.currentUser.avatarPath) : "" - showBuddyStatus: ViewHelper.currentUser?.hasBuddyState ?? false + showBuddyStatus: avatarImage.hasBuddyState || avatarImage.isUnregistered buddyStatus: SIPBuddyState.UNKNOWN + isUnregistered: true + + property bool hasBuddyState: ViewHelper.currentUser?.hasBuddyState ?? false Component.onCompleted: () => { avatarImage.updateBuddyStatus() @@ -255,6 +258,17 @@ Rectangle { avatarImage.updateBuddyStatus() } } + + Connections { + target: SIPAccountManager + function onSipRegisteredChanged(status : bool) { + if (status) { + avatarImage.isUnregistered = false + } else { + avatarImage.isUnregistered = true + } + } + } } HeaderIconButton { diff --git a/src/ui/components/HistoryWidget.qml b/src/ui/components/HistoryWidget.qml index 5ae16f77..96cb248d 100644 --- a/src/ui/components/HistoryWidget.qml +++ b/src/ui/components/HistoryWidget.qml @@ -51,6 +51,7 @@ BaseWidget { height: 30 font.pixelSize: 13 padding: 0 + rightPadding: 10 valueRole: "value" textRole: "label" anchors { @@ -78,7 +79,7 @@ BaseWidget { delegate: ItemDelegate { id: historyFilterMediumSelectorDelg - width: parent.width + width: historyFilterMediumSelector.width text: historyFilterMediumSelectorDelg.label font.family: historyFilterMediumSelector.font.family @@ -99,6 +100,7 @@ BaseWidget { height: 30 font.pixelSize: 13 padding: 0 + rightPadding: 10 valueRole: "value" textRole: "label" anchors { @@ -129,7 +131,7 @@ BaseWidget { delegate: ItemDelegate { id: historyFilterTypeSelectorDelg - width: parent.width + width: historyFilterTypeSelector.width text: historyFilterTypeSelectorDelg.label font.family: historyFilterTypeSelector.font.family diff --git a/src/ui/components/SearchBox.qml b/src/ui/components/SearchBox.qml deleted file mode 100644 index 21266c96..00000000 --- a/src/ui/components/SearchBox.qml +++ /dev/null @@ -1,121 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import base - -Item { - id: control - implicitHeight: searchInputField.implicitHeight - - signal numberSelected(string number, string contactId) - signal escapePressed() - - property int highlightedIndex: -1 - readonly property string text: searchInputField.text.trim() - - function activate() { - searchInputField.forceActiveFocus() - searchInputField.selectAll() - } - - function clear() { - searchInputField.text = "" - } - - TextInput { - id: searchInputField - font.pixelSize: 18 - color: Theme.primaryTextColor - anchors { - left: parent.left - right: parent.right - } - - Accessible.role: Accessible.EditableText - Accessible.name: qsTr("Search number") - Accessible.searchEdit: true - Accessible.focusable: true - Accessible.onPressAction: () => internal.dialHighlightedNumber() - - onFocusChanged: (isFocused) => { - if (isFocused) { - searchInputField.selectAll() - } - } - - Keys.onEnterPressed: () => { - internal.dialHighlightedNumber() - } - - Keys.onReturnPressed: () => { - internal.dialHighlightedNumber() - } - - Keys.onEscapePressed: () => { - if (searchPopup.opened) { - searchPopup.close() - } else if (searchInputField.text.trim() !== "") { - searchInputField.text = "" - } else { - control.escapePressed() - } - } - - Keys.onDownPressed: () => { - if (control.highlightedIndex < internal.searchListModel.totalNumbersCount - 1) { - control.highlightedIndex++ - } - } - - Keys.onUpPressed: () => { - if (control.highlightedIndex > 0) { - control.highlightedIndex-- - } - } - } - - Connections { - target: SearchProvider - function onActivateSearch(query) { - searchInputField.text = query - } - } - - QtObject { - id: internal - - property SearchListModel searchListModel: null - - function dialHighlightedNumber() { - if (control.highlightedIndex >= 0) { - const tel = internal.searchListModel.phoneNumberByIndex(control.highlightedIndex) - if (tel === "") { - return; - } - - const id = internal.searchListModel.contactIdByIndex(control.highlightedIndex) - control.numberSelected(tel, id) - control.highlightedIndex = -1 - searchInputField.text = "" - } else { - control.numberSelected(searchInputField.text, ViewHelper.contactIdByNumber(searchInputField.text)) - searchInputField.text = "" - } - } - } - - SearchResultPopup { - id: searchPopup - y: searchInputField.height - width: searchInputField.width - height: Math.min(contentItem.implicitHeight + verticalPadding * 2, control.Window.height - control.y - control.height - topMargin - bottomMargin) - topMargin: 12 + searchInputField.height - searchText: searchInputField.text - - // onNumberSelected: (number, contactId) => { - // control.numberSelected(number, contactId) - // control.highlightedIndex = -1 - // searchInputField.text = "" - // } - } -} diff --git a/src/ui/components/SettingsPage.qml b/src/ui/components/SettingsPage.qml index 5a5e6bc2..ea597b31 100644 --- a/src/ui/components/SettingsPage.qml +++ b/src/ui/components/SettingsPage.qml @@ -341,6 +341,7 @@ Item { editable: false textRole: 'displayName' valueRole: 'value' + rightPadding: 10 anchors { left: parent.left right: parent.right @@ -367,7 +368,6 @@ Item { delegate: ItemDelegate { id: darkModeDelg - width: parent.width text: darkModeDelg.displayName font.family: darkModeComboBox.font.family @@ -540,6 +540,7 @@ Item { editable: false textRole: 'displayName' valueRole: 'id' + rightPadding: 10 anchors { left: parent.left right: parent.right @@ -560,7 +561,6 @@ Item { delegate: ItemDelegate { id: standardPreferredIdentityDelg - width: parent.width text: standardPreferredIdentityDelg.displayName font.family: standardPreferredIdentitySelector.font.family @@ -831,6 +831,7 @@ Item { editable: false textRole: 'name' valueRole: 'id' + rightPadding: 10 anchors { left: parent.left right: parent.right @@ -843,8 +844,8 @@ Item { delegate: ItemDelegate { id: inputAudioSelectorDelg - width: parent.width text: inputAudioSelectorDelg.name + width: inputAudioSelectorDelg.implicitWidth font.family: inputAudioSelector.font.family font.weight: inputAudioSelector.font.weight @@ -908,6 +909,7 @@ Item { editable: false textRole: 'name' valueRole: 'id' + rightPadding: 10 anchors { left: parent.left right: parent.right @@ -920,8 +922,8 @@ Item { delegate: ItemDelegate { id: outputAudioSelectorDelg - width: parent.width text: outputAudioSelectorDelg.name + width: outputAudioSelectorDelg.implicitWidth font.family: outputAudioSelector.font.family font.weight: outputAudioSelector.font.weight @@ -985,6 +987,7 @@ Item { editable: false textRole: 'name' valueRole: 'id' + rightPadding: 10 anchors { left: parent.left right: parent.right @@ -997,8 +1000,8 @@ Item { delegate: ItemDelegate { id: outputRingAudioSelectorDelg - width: parent.width text: outputRingAudioSelectorDelg.name + width: outputRingAudioSelectorDelg.implicitWidth font.family: outputRingToneAudioSelector.font.family font.weight: outputRingToneAudioSelector.font.weight @@ -1222,7 +1225,8 @@ Item { Label { id: ringToneVolumeSliderLabel - text: qsTr('%1 %').arg(ringToneVolumeSlider.value) + //: Label for showing percentage + text: qsTr('%1 %').arg(ringToneVolumeSlider.value.toLocaleString(Qt.locale(), "f", 0)) horizontalAlignment: Label.AlignRight width: 40 anchors { @@ -1293,7 +1297,8 @@ Item { Label { id: ringTonePauseValueLabel - text: (ringTonePauseSlider.value / 1000).toLocaleString(Qt.locale(), "f", 2) + " s" + //: Label for showing seconds + text: qsTr("%1 s").arg((ringTonePauseSlider.value / 1000).toLocaleString(Qt.locale(), "f", 2)) horizontalAlignment: Label.AlignRight width: 40 anchors { diff --git a/src/ui/components/controls/ControlBar.qml b/src/ui/components/controls/ControlBar.qml index 2f6e78c9..53b802e6 100644 --- a/src/ui/components/controls/ControlBar.qml +++ b/src/ui/components/controls/ControlBar.qml @@ -98,8 +98,11 @@ Item { size: 28 initials: ViewHelper.initials(ViewHelper.currentUserName) source: ViewHelper.currentUser?.hasAvatar ? ("file://" + ViewHelper.currentUser.avatarPath) : "" - showBuddyStatus: ViewHelper.currentUser?.hasBuddyState ?? false + showBuddyStatus: avatarImage.hasBuddyState || avatarImage.isUnregistered buddyStatus: SIPBuddyState.UNKNOWN + isUnregistered: true + + property bool hasBuddyState: ViewHelper.currentUser?.hasBuddyState ?? false Component.onCompleted: () => { avatarImage.updateBuddyStatus() @@ -118,6 +121,17 @@ Item { avatarImage.updateBuddyStatus() } } + + Connections { + target: SIPAccountManager + function onSipRegisteredChanged(status : bool) { + if (status) { + avatarImage.isUnregistered = false + } else { + avatarImage.isUnregistered = true + } + } + } } } } diff --git a/src/ui/components/controls/FirstAidButton.qml b/src/ui/components/controls/FirstAidButton.qml index 46652339..7b7c3ba0 100644 --- a/src/ui/components/controls/FirstAidButton.qml +++ b/src/ui/components/controls/FirstAidButton.qml @@ -6,7 +6,7 @@ import base Item { id: control implicitHeight: control.iconSize - implicitWidth: firstAidLabel.x + firstAidLabel.implicitWidth + implicitWidth: control.iconSize + 10 + firstAidLabel.implicitWidth visible: GlobalInfo.hasEmergencyNumbers property int iconSize: 24 @@ -23,6 +23,7 @@ Item { height: control.iconSize radius: control.iconSize / 2 color: Qt.rgba(1, 1, 1) + anchors.left: parent.left Rectangle { id: verticalBar diff --git a/src/ui/components/controls/SearchDial.qml b/src/ui/components/controls/SearchDial.qml deleted file mode 100644 index 8b389aab..00000000 --- a/src/ui/components/controls/SearchDial.qml +++ /dev/null @@ -1,252 +0,0 @@ -import QtQuick -import QtQuick.Controls.Material -import QtQuick.Controls.impl -import base - -/// Combined widget with contact search box, dial button and outgoing call type selector (inkognito, etc.) -Item { - id: control - height: 60 - - property int radius: 6 - - signal numberSelected(string number, string contactId, string preferredIdentity) - signal escapePressed() - - function activate() { - searchBox.activate() - } - - Rectangle { - id: leftWidgetBackground - width: 140 - color: leftHoverHandler.hovered ? Theme.backgroundOffsetHoveredColor : Theme.backgroundOffsetColor - topLeftRadius: control.radius - bottomLeftRadius: control.radius - anchors { - top: parent.top - bottom: parent.bottom - left: parent.left - } - - Behavior on color { ColorAnimation {} } - } - - Rectangle { - id: rightWidgetBackground - width: control.height - color: rightHoverHandler.hovered ? Theme.backgroundOffsetHoveredColor : Theme.backgroundOffsetColor - topRightRadius: control.radius - bottomRightRadius: control.radius - anchors { - top: parent.top - bottom: parent.bottom - right: parent.right - } - - Behavior on color { ColorAnimation {} } - - HoverHandler { - id: rightHoverHandler - } - - TapHandler { - onTapped: () => control.numberSelected(searchBox.text, "", modeSelector.currentValue) - } - - Accessible.role: Accessible.Button - Accessible.name: qsTr("Select number") - Accessible.focusable: true - Accessible.onPressAction: () => control.numberSelected(searchBox.text, "", modeSelector.currentValue) - } - - Rectangle { - id: background - color: 'transparent' - radius: control.radius - border.width: 1 - border.color: Theme.borderColor - anchors.fill: parent - } - - IconLabel { - id: searchIcon - icon { - source: Icons.systemSearch - width: 20 - height: 20 - } - anchors { - verticalCenter: parent.verticalCenter - left: leftWidgetBackground.right - leftMargin: 20 - } - } - - IconLabel { - id: phoneIcon - anchors.centerIn: rightWidgetBackground - icon { - source: Icons.callStart - width: 25 - height: 25 - } - } - - Item { - id: dummyItemForSearchBoxTapHandler - anchors { - top: parent.top - bottom: parent.bottom - left: leftWidgetBackground.right - right: rightWidgetBackground.left - } - - TapHandler { - onTapped: () => searchBox.activate() - } - - Accessible.role: Accessible.Button - Accessible.name: qsTr("Activate search field") - Accessible.focusable: true - Accessible.onPressAction: () => searchBox.activate() - } - - Label { - id: placeholderLabel - text: qsTr("Number or contact") - color: Theme.secondaryTextColor - visible: !searchBox.text - font.pixelSize: 18 - anchors { - left: searchBox.left - right: searchBox.left - verticalCenter: searchBox.verticalCenter - } - - Accessible.role: Accessible.StaticText - Accessible.name: placeholderLabel.displayText - } - - SearchBox { - id: searchBox - anchors { - verticalCenter: parent.verticalCenter - left: searchIcon.right - leftMargin: 20 - right: clearButtonContainer.left - } - - onNumberSelected: (number, contactId) => control.numberSelected(number, contactId, modeSelector.currentValue) - onEscapePressed: () => control.escapePressed() - } - - Item { - id: clearButtonContainer - width: 60 - anchors { - top: parent.top - bottom: parent.bottom - right: phoneIcon.left - rightMargin: 20 - } - - Accessible.role: Accessible.Button - Accessible.name: qsTr("Clear search field") - Accessible.focusable: true - Accessible.onPressAction: () => searchBox.clear() - - IconLabel { - id: clearButton - visible: searchBox.text !== "" - icon { - source: Icons.editClear - width: 20 - height: 20 - color: clearButtonHoveredHandler.hovered ? Theme.primaryTextColor : Theme.secondaryTextColor - } - anchors { - verticalCenter: parent.verticalCenter - right: parent.right - rightMargin: 20 - } - - Behavior on color { ColorAnimation {} } - - Accessible.ignored: true - } - - HoverHandler { - id: clearButtonHoveredHandler - } - - TapHandler { - onTapped: () => searchBox.clear() - } - } - - ComboBox { - id: modeSelector - flat: true - editable: false - anchors.fill: leftWidgetBackground - background: Item {} - textRole: 'displayName' - valueRole: 'id' - popup.width: control.width - model: [ - { - displayName: qsTr("Default"), - id: "default" - }, { - displayName: qsTr("Auto"), - id: "auto" - } - ].concat(SIPManager.preferredIdentities) - - Accessible.role: Accessible.ComboBox - Accessible.name: qsTr("Preferred identity") - Accessible.description: qsTr("Select the preferred identity for outgoing calls") - - contentItem: Label { - text: modeSelector.displayText - wrapMode: Label.WordWrap - font.pixelSize: 14 - maximumLineCount: 2 - elide: Label.ElideRight - verticalAlignment: Label.AlignVCenter - leftPadding: 10 - - Accessible.role: Accessible.ListItem - Accessible.name: modeSelector.displayText - Accessible.focusable: true - } - - function setDefaultIdentity() { - const selectedId = SIPManager.defaultPreferredIdentity - const model = modeSelector.model - - for (let i = 0; i < model.length; ++i) { - if (model[i].id === selectedId) { - modeSelector.currentIndex = i - return - } - } - - modeSelector.currentIndex = -1 - } - - Component.onCompleted: () => modeSelector.setDefaultIdentity() - - Connections { - target: SIPManager - function onPreferredIdentitiesChanged() { - modeSelector.setDefaultIdentity() - } - } - - HoverHandler { - id: leftHoverHandler - } - } -} diff --git a/src/ui/components/controls/SearchField.qml b/src/ui/components/controls/SearchField.qml index 115002a1..8a804840 100644 --- a/src/ui/components/controls/SearchField.qml +++ b/src/ui/components/controls/SearchField.qml @@ -80,8 +80,9 @@ Item { anchors { verticalCenter: parent.verticalCenter left: searchIcon.right + right: clearButton.visible ? clearButton.left : parent.right leftMargin: 10 - right: parent.right + rightMargin: 10 } Accessible.role: Accessible.EditableText @@ -108,6 +109,7 @@ Item { IconLabel { id: clearIcon anchors.centerIn: parent + rotation: LayoutMirroring.enabled ? 180 : 0 icon { source: Icons.editClear color: clearButtonHoverHandler.hovered ? Theme.primaryTextColor : Theme.secondaryInactiveTextColor diff --git a/src/ui/components/pages/Call.qml b/src/ui/components/pages/Call.qml index 88ed39e5..e556eb7e 100644 --- a/src/ui/components/pages/Call.qml +++ b/src/ui/components/pages/Call.qml @@ -48,7 +48,7 @@ Item { PropertyChanges { verticalDragbarDummyDragHandler.enabled: true verticalDragbarDummyHoverHandler.enabled: true - verticalDragbarDummy.x: 3/4 * control.width + verticalDragbarDummy.x: (control.LayoutMirroring.enabled ? 1/4 : 3/4) * control.width } AnchorChanges { @@ -265,8 +265,8 @@ Item { enabled: false yAxis.enabled: false xAxis { - minimum: 1/2 * control.width - maximum: control.width - 300 + minimum: control.LayoutMirroring.enabled ? 300 : (1/2 * control.width) + maximum: control.LayoutMirroring.enabled ? (1/2 * control.width) : (control.width - 300) } } } diff --git a/src/ui/components/pages/Conference.qml b/src/ui/components/pages/Conference.qml index 93672568..c4247d34 100644 --- a/src/ui/components/pages/Conference.qml +++ b/src/ui/components/pages/Conference.qml @@ -29,7 +29,7 @@ Item { PropertyChanges { verticalDragbarDummyDragHandler.enabled: true verticalDragbarDummyHoverHandler.enabled: true - verticalDragbarDummy.x: 3/4 * control.width + verticalDragbarDummy.x: control.LayoutMirroring.enabled ? (1/4 * control.width) : (3/4 * control.width) } AnchorChanges { @@ -797,8 +797,8 @@ Item { enabled: false yAxis.enabled: false xAxis { - minimum: 1/2 * control.width - maximum: control.width - callSideBar.optimalExtendedWidth - verticalDragbarDummy.width - callListCard.anchors.rightMargin + minimum: control.LayoutMirroring.enabled ? (1/5 * control.width) : (1/2 * control.width) + maximum: control.LayoutMirroring.enabled ? (1/2 * control.width) : (control.width - callSideBar.optimalExtendedWidth - verticalDragbarDummy.width - callListCard.anchors.rightMargin) } onActiveChanged: () => { diff --git a/src/ui/components/popups/SearchResultPopup.qml b/src/ui/components/popups/SearchResultPopup.qml index 6f001584..012e21ca 100644 --- a/src/ui/components/popups/SearchResultPopup.qml +++ b/src/ui/components/popups/SearchResultPopup.qml @@ -72,8 +72,20 @@ Popup { id: popupContainer focus: true - Keys.onLeftPressed: () => keyNavigator.keyLeft() - Keys.onRightPressed: () => keyNavigator.keyRight() + Keys.onLeftPressed: () => { + if (LayoutMirroring.enabled) { + keyNavigator.keyRight() + } else { + keyNavigator.keyLeft() + } + } + Keys.onRightPressed: () => { + if (LayoutMirroring.enabled) { + keyNavigator.keyLeft() + } else { + keyNavigator.keyRight() + } + } Keys.onDownPressed: () => keyNavigator.keyDown() Keys.onUpPressed: () => keyNavigator.keyUp() Keys.onEnterPressed: () => control.triggerPrimaryAction() diff --git a/src/usb/HeadsetDevice.cpp b/src/usb/HeadsetDevice.cpp index 4ed6e0b0..d749e90f 100644 --- a/src/usb/HeadsetDevice.cpp +++ b/src/usb/HeadsetDevice.cpp @@ -359,11 +359,6 @@ void HeadsetDevice::setTeamsUsageMapping(QHash teamsUsageMappi void HeadsetDevice::processEvents() { - if (m_ignoreNextMuteUpdate) { - m_ignoreNextMuteUpdate = false; - return; - } - unsigned char data[64]; memset(data, 0, sizeof(data)); @@ -404,11 +399,11 @@ void HeadsetDevice::processEvents() const auto &usage = m_hidUsages.value(UsageId::Telephony_PhoneMute); if (usage.reportId == reportId && (value & (1 << usage.bitPosition))) { m_muted = !m_muted; - Q_EMIT mute(); qCDebug(lcHeadset) << " Muted changed to" << m_muted; setMute(m_muted); - m_ignoreNextMuteUpdate = true; + + Q_EMIT mute(); } } diff --git a/src/usb/HeadsetDevice.h b/src/usb/HeadsetDevice.h index 9da2c07a..3d0cd515 100644 --- a/src/usb/HeadsetDevice.h +++ b/src/usb/HeadsetDevice.h @@ -107,6 +107,5 @@ class HeadsetDevice final : public IHeadsetDevice bool m_ringing = false; bool m_isOpen = false; - bool m_ignoreNextMuteUpdate = false; bool m_displaySupported = false; }; diff --git a/src/usb/busylight/LitraBeamLX.cpp b/src/usb/busylight/LitraBeamLX.cpp index 0c4164e7..685b651c 100644 --- a/src/usb/busylight/LitraBeamLX.cpp +++ b/src/usb/busylight/LitraBeamLX.cpp @@ -41,7 +41,7 @@ bool LitraBeamLX::hidppTransaction(unsigned char *buf, size_t len) } // Check for device error report - if (n >= 4 && resp[0] == 0x11 && resp[2] == 0xFF) { + if (n >= 4 && resp[0] == 0xFF) { return false; } From 01c64946a77c9cee40f566c9af2fecd51bfa7bcc Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:32:10 +0100 Subject: [PATCH 015/122] fix: restore functionality for feeders without network requirements (#387) * fix: restore functionality for feeders without network requirements --- src/calendar/DateEventFeederManager.cpp | 13 ++++++++----- src/contacts/AddressBookManager.cpp | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/calendar/DateEventFeederManager.cpp b/src/calendar/DateEventFeederManager.cpp index da3b1185..6ac261ae 100644 --- a/src/calendar/DateEventFeederManager.cpp +++ b/src/calendar/DateEventFeederManager.cpp @@ -131,18 +131,21 @@ void DateEventFeederManager::processQueue() if (auto feeder = m_dateEventFeeders.value(configId, nullptr)) { QUrl urlToCheck = feeder->networkCheckURL(); - if (!urlToCheck.isEmpty()) { + if (urlToCheck.isEmpty()) { + feeder->init(); + } else { if (!networkAvailable) { continue; } if (!urlToCheck.isValid()) { - qCCritical(lcDateEventFeederManager) << "Url is invalid:" << urlToCheck; + qCCritical(lcDateEventFeederManager) << "URL is invalid:" << urlToCheck; continue; } if (!networkHelper.hasConnectivity()) { - qCWarning(lcDateEventFeederManager) << "No network connectivity"; + qCWarning(lcDateEventFeederManager) + << "No connectivity state yet - trying later"; networkAvailable = false; setupReconnectSignal(); continue; @@ -152,11 +155,11 @@ void DateEventFeederManager::processQueue() .then(this, [feeder, urlToCheck, this](bool isReachable) { if (isReachable) { QMutexLocker mutex(&m_queueMutex); - feeder->init(); + feeder->init(); } else { qCWarning(lcDateEventFeederManager) - << "Feeder url" << urlToCheck << "is not reachable"; + << "Feeder URL" << urlToCheck << "is not reachable"; setupReconnectSignal(); } }); diff --git a/src/contacts/AddressBookManager.cpp b/src/contacts/AddressBookManager.cpp index 57e4aafc..0d02512d 100644 --- a/src/contacts/AddressBookManager.cpp +++ b/src/contacts/AddressBookManager.cpp @@ -103,13 +103,21 @@ void AddressBookManager::processAddressBookQueue() // the network helper / portal. If we've no connectivity, trigger on // connectivityChanged signal to recheck again. QUrl checkURL = feeder->networkCheckURL(); - if (!checkURL.isEmpty()) { + + if (checkURL.isEmpty()) { + feeder->process(); + } else { if (!networkAvailable) { continue; } + if (!checkURL.isValid()) { + qCCritical(lcAddressBookManager) << "URL is invalid:" << checkURL; + continue; + } + if (!nh.hasConnectivity()) { - qCWarning(lcAddressBookManager) << "no connectivity state yet - trying later"; + qCWarning(lcAddressBookManager) << "No connectivity state yet - trying later"; networkAvailable = false; connect( @@ -127,7 +135,8 @@ void AddressBookManager::processAddressBookQueue() feeder->process(); Q_EMIT AddressBook::instance().contactsReady(); } else { - qCWarning(lcAddressBookManager) << checkURL << "is not reachable"; + qCWarning(lcAddressBookManager) + << "Feeder URL" << checkURL << "is not reachable"; connect( &NetworkHelper::instance(), &NetworkHelper::connectivityChanged, this, [this]() { processAddressBookQueue(); }, From abf981c7349397b360129aa43550407d16ba09f8 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:32:37 +0100 Subject: [PATCH 016/122] chore(deps): update renovatebot/github-action action to v46.1.5 (#386) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 41c485fa..963dde56 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@0b17c4eb901eca44d018fb25744a50a74b2042df # v46.1.4 + uses: renovatebot/github-action@abd08c7549b2a864af5df4a2e369c43f035a6a9d # v46.1.5 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From d85afce46e12744966a6644b8e898c083482f611 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:40:04 +0100 Subject: [PATCH 017/122] chore(deps): update geekyeggo/delete-artifact action to v6 (#385) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 76feaf26..02355931 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -290,6 +290,6 @@ jobs: LD_LIBRARY_PATH=${QT_ROOT_DIR}/lib ./contactsTest - name: Cleanup - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5 + uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6 with: name: gonnect-tests From 7a177c7bcb6704ba727160f21bc850ce10977b2e Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:49:37 +0100 Subject: [PATCH 018/122] chore(deps): update actions/create-github-app-token action to v3 (#384) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c99d6672..3b98b213 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 963dde56..ba4905b8 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_KEY }} From 166a027aff4c9ff32588abc98bc153e0bedc3f71 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 07:37:14 +0100 Subject: [PATCH 019/122] chore(deps): update github/codeql-action digest to b1bff81 (#388) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 077a05b4..2301684f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 + uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4 with: category: "/language:${{matrix.language}}" From 04546376054152fa76db5f6320065d79e3831043 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 07:37:34 +0100 Subject: [PATCH 020/122] chore(deps): update svenstaro/upload-release-action digest to 29e53e9 (#389) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/release-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 3b6b122b..2cd9c75d 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -42,7 +42,7 @@ jobs: cache-key: flatpak-builder-${{ steps.var.outputs.gonnect_version }} - name: Upload binaries to release - uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: GOnnect.flatpak @@ -74,7 +74,7 @@ jobs: run: cd build && cpack ${{github.workspace}} - name: Upload binaries to release - uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/GOnnect*.exe @@ -105,7 +105,7 @@ jobs: run: cd build/Release && cpack ${{github.workspace}} - name: Upload binaries to release - uses: svenstaro/upload-release-action@b98a3b12e86552593f3e4e577ca8a62aa2f3f22b # v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/Release/GOnnect*.dmg From 346e03a713e59b83b3d2d9dbd844e62ba7b65667 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:02:45 +0100 Subject: [PATCH 021/122] chore(deps): update jidicula/clang-format-action action to v4.18.0 (#390) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 02355931..68eda102 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -125,7 +125,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run clang-format style check for C/C++/Protobuf programs. - uses: jidicula/clang-format-action@3a18028048f01a66653b4e3bf8d968d2e7e2cb8b # v4.17.0 + uses: jidicula/clang-format-action@654a770daa28443dd111d133e4083e21c1075674 # v4.18.0 with: clang-format-version: '21' check-path: 'src' From 518e3901ecbeffca8338f0e4f740d4ce3fd2f4f5 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:22:24 +0100 Subject: [PATCH 022/122] chore(deps): update jwlawson/actions-setup-cmake digest to 0d6a7d6 (#393) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/actions/prepare-linux/action.yml | 2 +- .github/actions/prepare-macos/action.yml | 2 +- .github/actions/prepare-windows/action.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare-linux/action.yml b/.github/actions/prepare-linux/action.yml index 19545aae..5aa2b824 100644 --- a/.github/actions/prepare-linux/action.yml +++ b/.github/actions/prepare-linux/action.yml @@ -20,7 +20,7 @@ runs: steps: - name: Setup cmake - uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2 + uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2 with: cmake-version: 'latest' diff --git a/.github/actions/prepare-macos/action.yml b/.github/actions/prepare-macos/action.yml index 29df8d27..cd3ad1f3 100644 --- a/.github/actions/prepare-macos/action.yml +++ b/.github/actions/prepare-macos/action.yml @@ -11,7 +11,7 @@ runs: steps: - name: Setup cmake - uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2 + uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2 with: cmake-version: 'latest' diff --git a/.github/actions/prepare-windows/action.yml b/.github/actions/prepare-windows/action.yml index cf76ac7a..da618b4a 100644 --- a/.github/actions/prepare-windows/action.yml +++ b/.github/actions/prepare-windows/action.yml @@ -10,7 +10,7 @@ runs: steps: - name: Setup cmake - uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2 + uses: jwlawson/actions-setup-cmake@0d6a7d60b009d01c9e7523be22153ff8f19460d3 # v2 with: cmake-version: 'latest' From 507b3008dc8d5df05937e2a03fc91549e34a525f Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:22:56 +0100 Subject: [PATCH 023/122] chore(deps): update actions/cache action to v5.0.4 (#394) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 68eda102..42b08cbe 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -207,7 +207,7 @@ jobs: conan profile detect || true - name: Restore cached Conan packages - uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 id: cache-conan-packages with: path: ${{ steps.conan-info.outputs.conan-home }}/p @@ -222,7 +222,7 @@ jobs: conan install . --build=missing -ctools.cmake.cmaketoolchain:generator=Ninja - name: Cached Conan packages - uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 if: always() && steps.cache-conan-packages.outputs.cache-hit != 'true' with: path: ${{ steps.conan-info.outputs.conan-home }}/p From a90a80cb4796c96f1626f82d3e59d49312c23585 Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Fri, 20 Mar 2026 14:18:07 +0100 Subject: [PATCH 024/122] fix: disable WMME in pjsip (#397) gonnect uses its own AudioPort and disables pjsip port/device on linux (by using "extern"). Likewise disable it on windows, to make sure the AudioPort is used (only). --- resources/conan/recipes/pjproject/all/conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/conan/recipes/pjproject/all/conanfile.py b/resources/conan/recipes/pjproject/all/conanfile.py index a58c23b6..5037ff0d 100644 --- a/resources/conan/recipes/pjproject/all/conanfile.py +++ b/resources/conan/recipes/pjproject/all/conanfile.py @@ -165,6 +165,7 @@ def makeSiteConfig(self): if self.settings.os == "Windows": file.write('\n\n#define PJ_HAS_SSL_SOCK 1\n') + file.write('\n\n#define PJMEDIA_AUDIO_DEV_HAS_WMME 0\n') def buildWindows(self): if self.options.shared: From b3805636d97a99e090eb4e61c066c819b4cd1dca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:29:18 +0100 Subject: [PATCH 025/122] chore(deps): bump undici in /.github/actions/get-qt-version (#400) Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 6.24.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/get-qt-version/package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/actions/get-qt-version/package-lock.json b/.github/actions/get-qt-version/package-lock.json index eacf20b5..127c740e 100644 --- a/.github/actions/get-qt-version/package-lock.json +++ b/.github/actions/get-qt-version/package-lock.json @@ -52,9 +52,10 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "license": "MIT", "engines": { "node": ">=18.17" } From 4f72dc6ad54731be70190d1495c00b15b301c14e Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Fri, 20 Mar 2026 16:28:14 +0100 Subject: [PATCH 026/122] fix: raise window after showing it (#398) Required on windows: After calling show(), also call raise(), otherwise the window shows up behind whatever window(s) is currently visible. This is required for the global shortcut on windows. --- src/StateManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StateManager.cpp b/src/StateManager.cpp index 6e55e15a..9e555c15 100644 --- a/src/StateManager.cpp +++ b/src/StateManager.cpp @@ -84,6 +84,7 @@ void StateManager::initialize() auto &cm = SIPCallManager::instance(); if (action == "dial") { qobject_cast(Application::instance())->rootWindow()->show(); + qobject_cast(Application::instance())->rootWindow()->raise(); } else if (action == "hangup") { cm.endAllCalls(); } else if (action == "redial") { From 8098ca633be176628b68f8ee14afb3734a3c3978 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 08:19:52 +0100 Subject: [PATCH 027/122] chore(deps): update github/codeql-action digest to 3869755 (#401) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2301684f..2cde1d62 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4 + uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4 + uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: category: "/language:${{matrix.language}}" From ebfb53a96038698ea806d1045a7a3f324806822b Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:10:20 +0100 Subject: [PATCH 028/122] chore(deps): update renovatebot/github-action action to v46.1.6 (#403) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index ba4905b8..bd88c405 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@abd08c7549b2a864af5df4a2e369c43f035a6a9d # v46.1.5 + uses: renovatebot/github-action@68a3ea99af6ad249940b5a9fdf44fc6d7f14378b # v46.1.6 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 5e5d78e0a6364d511a43558e59be5ab020bf2795 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Mon, 23 Mar 2026 11:53:28 +0100 Subject: [PATCH 029/122] fix(ui): give focus to call screen when startet via enter (#395) --- src/ui/components/popups/SearchResultPopup.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/components/popups/SearchResultPopup.qml b/src/ui/components/popups/SearchResultPopup.qml index 012e21ca..5c989d41 100644 --- a/src/ui/components/popups/SearchResultPopup.qml +++ b/src/ui/components/popups/SearchResultPopup.qml @@ -172,7 +172,11 @@ Popup { KeyNavigator { id: keyNavigator - onVerticallyOutOfBounds: () => control.returnFocus() + onVerticallyOutOfBounds: () => { + if (control.visible) { + control.returnFocus() + } + } readonly property Connections resetConnection: Connections { target: control From 0c4609ca355f760478a19bda2c618aa299ff86eb Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 23 Mar 2026 12:08:25 +0100 Subject: [PATCH 030/122] fix: handle direct pjsip mute again (#402) * fix: handle direct pjsip mute again --- src/media/AudioManager.cpp | 15 ++++++++------- src/media/AudioPort.cpp | 14 +++++++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/media/AudioManager.cpp b/src/media/AudioManager.cpp index d9eb1da2..1835fac8 100644 --- a/src/media/AudioManager.cpp +++ b/src/media/AudioManager.cpp @@ -48,19 +48,20 @@ AudioManager::AudioManager(QObject *parent) : QObject(parent) connect(this, &AudioManager::playbackDeviceIdChanged, this, &AudioManager::playbackAudioVolumeChanged); + connect(this, &AudioManager::isAudioCaptureMutedChanged, this, [this]() { + if (m_captureAudioPort) { #ifdef Q_OS_LINUX - if (!noSyncSystemMute()) { - connect(this, &AudioManager::isAudioCaptureMutedChanged, this, [this]() { - if (m_captureAudioPort) { + if (!noSyncSystemMute()) { m_paCallbackSuppress++; paMuteInputByName(m_captureAudioPort->getSystemDeviceID(), m_isAudioCaptureMuted); qCInfo(lcAudioManager) << "Sent mute state" << m_isAudioCaptureMuted << "to system"; - } else { - qCCritical(lcAudioManager) << "Missing capture audio port - cannot set muted flag"; } - }); - } #endif + m_captureAudioPort->setMuted(m_isAudioCaptureMuted); + } else { + qCCritical(lcAudioManager) << "Missing capture audio port - cannot set muted flag"; + } + }); connect(&GlobalMuteState::instance(), &GlobalMuteState::isMutedChangedWithTag, this, [this](bool value, const QString) { setProperty("isAudioCaptureMuted", value); }); diff --git a/src/media/AudioPort.cpp b/src/media/AudioPort.cpp index ecfb04b6..fe9ee661 100644 --- a/src/media/AudioPort.cpp +++ b/src/media/AudioPort.cpp @@ -6,6 +6,8 @@ #include "AudioPort.h" Q_LOGGING_CATEGORY(lcAudioPort, "gonnect.sip.audio") +#define NORMAL_AUDIO_LEVEL 2.0f + using namespace std::chrono_literals; AudioPort::AudioPort(QAudioDevice device) : m_device(device) @@ -31,12 +33,22 @@ bool AudioPort::initialize() createPort(m_device.id().toStdString(), m_pj_fmt); + if (m_device.mode() == QAudioDevice::Mode::Input) { + adjustTxLevel(NORMAL_AUDIO_LEVEL); + } + return true; } void AudioPort::setMuted(bool value) { - m_isMuted = value; + if (m_isMuted != value) { + if (m_device.mode() == QAudioDevice::Mode::Input) { + adjustTxLevel(value ? 0.0f : NORMAL_AUDIO_LEVEL); + } + + m_isMuted = value; + } } bool AudioPort::initFmt() From 8c3c91455b04a800a5c3e09c76b1f3e3b92ce023 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Wed, 25 Mar 2026 16:43:27 +0100 Subject: [PATCH 031/122] feat: basic MWI support (#392) * feat: basic MWI support * fix: only ring on 180 RINGING * fix: destruct busylight manager * feat: new culture circle compatible first aid button * feat: use tab red dot feature, customizable card heading, tab-page link * fix: flurry audio on call start with USB headsets * fix: potential race condition and initial silence in calls * fix: improve SIP suspend/resume --------- Co-authored-by: Leah Jennebach Co-authored-by: Markus Bader --- distrobox.ini | 4 +- docs/modules/ROOT/examples/sample.conf | 5 + i18n/gonnect_de.ts | 588 +++++++++-------- i18n/gonnect_en.ts | 21 +- i18n/gonnect_es.ts | 588 +++++++++-------- i18n/gonnect_fa.ts | 586 +++++++++-------- i18n/gonnect_fr.ts | 588 +++++++++-------- i18n/gonnect_it.ts | 588 +++++++++-------- i18n/gonnect_ru.ts | 590 +++++++++--------- i18n/gonnect_uk.ts | 590 +++++++++--------- resources/artwork/ISO_7010_E004.svg | 25 + resources/artwork/ISO_7010_E004_C.svg | 25 + resources/artwork/icons/brightness-high.svg | 24 + resources/artwork/icons/call-voicemail.svg | 12 + resources/artwork/icons/color-management.svg | 59 ++ resources/artwork/icons/videolight-off.svg | 20 + resources/artwork/icons/videolight-on.svg | 20 + resources/artwork/jitsi.svg | 1 - .../conan/recipes/pjproject/all/conanfile.py | 1 + resources/templates/sample.conf | 5 + src/Application.cpp | 7 - src/CMakeLists.txt | 3 +- src/ICallState.h | 3 +- src/icons.qrc.in | 4 +- src/main.cpp | 23 +- src/media/AudioManager.cpp | 5 +- src/media/AudioPort.cpp | 26 +- src/media/AudioPort.h | 2 + src/platform/NotificationIcon.cpp | 4 +- .../linux/LinuxNotificationManager.cpp | 4 + src/sip/RingTone.cpp | 3 +- src/sip/SIPAccount.cpp | 218 ++++++- src/sip/SIPAccount.h | 32 +- src/sip/SIPAccountManager.cpp | 28 + src/sip/SIPAccountManager.h | 8 + src/sip/SIPCall.cpp | 33 +- src/sip/SIPCall.h | 4 + src/sip/SIPCallManager.h | 2 +- src/sip/SIPManager.cpp | 17 +- src/ui/CallsModel.cpp | 6 + src/ui/CallsModel.h | 4 +- src/ui/GonnectWindow.qml | 18 +- src/ui/PageModel.qml | 21 +- src/ui/PageReader.qml | 16 +- src/ui/SystemTrayMenu.cpp | 2 + src/ui/SystemTrayMenu.h | 16 +- src/ui/Theme.cpp | 2 +- src/ui/Theme.h | 3 + src/ui/ViewHelper.cpp | 8 +- src/ui/ViewHelper.h | 3 + src/ui/WidgetModel.qml | 24 +- src/ui/components/BaseWidget.qml | 2 + src/ui/components/CallButtonBar.qml | 5 +- src/ui/components/CallerBigAvatar.qml | 3 +- src/ui/components/CardHeading.qml | 56 +- src/ui/components/HistoryWidget.qml | 18 + src/ui/components/MainTabBar.qml | 70 ++- src/ui/components/controls/FirstAidButton.qml | 79 +-- src/ui/components/controls/VoiceMailField.qml | 76 +++ src/ui/components/pages/BasePage.qml | 9 + src/ui/components/pages/Call.qml | 3 + ...olumePopup.qml => StreamingLightPopup.qml} | 0 src/usb/USBDevices.cpp | 2 + src/usb/busylight/BusylightDeviceManager.cpp | 8 + src/usb/busylight/BusylightDeviceManager.h | 2 + src/usb/busylight/LitraBeamLX.cpp | 3 +- src/usb/busylight/LitraBeamLX.h | 2 + 67 files changed, 2937 insertions(+), 2290 deletions(-) create mode 100644 resources/artwork/ISO_7010_E004.svg create mode 100644 resources/artwork/ISO_7010_E004_C.svg create mode 100644 resources/artwork/icons/brightness-high.svg create mode 100644 resources/artwork/icons/call-voicemail.svg create mode 100644 resources/artwork/icons/color-management.svg create mode 100644 resources/artwork/icons/videolight-off.svg create mode 100644 resources/artwork/icons/videolight-on.svg delete mode 100644 resources/artwork/jitsi.svg create mode 100644 src/ui/components/controls/VoiceMailField.qml rename src/ui/components/popups/{VolumePopup.qml => StreamingLightPopup.qml} (100%) diff --git a/distrobox.ini b/distrobox.ini index 66c5d3fb..b645be06 100644 --- a/distrobox.ini +++ b/distrobox.ini @@ -20,10 +20,12 @@ additional_packages=qt6-qt3d qt6-qtdatavis3d qt6-qtsvg qt6-qttools-devel qt6-qtm additional_packages=qt6-qtnetworkauth qt6-qtnetworkauth-devel qt6-qtwebengine qt6-qtwebengine-devtools qt6-qtwebengine-devel qt6-qthttpserver-devel additional_packages=qtkeychain-qt6-devel libical libical-devel extra-cmake-modules libnotify-devel bash-completion additional_packages=pip gobject-introspection-devel cairo-gobject-devel pkg-config python3-devel gtk4 perl-Time-Piece ImageMagick libicns-utils -additional_packages=poetry python3-tenacity python3-cairo python3-gobject qt6-qtgrpc-devel +additional_packages=poetry python3-tenacity python3-cairo python3-gobject qt6-qtgrpc-devel firefox # System DBus access additional_flags=--env DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/host/var/run/dbus/system_bus_socket additional_flags=--env PKG_CONFIG_PATH=/usr/lib64/pkgconfig +init_hooks=mkdir /etc/pipewire +init_hooks=echo Y29udGV4dC5tb2R1bGVzID0gWwogIHsgbmFtZSA9IGxpYnBpcGV3aXJlLW1vZHVsZS1ydAogICAgYXJncyA9IHsKICAgICAgbmljZS5sZXZlbCAgICA9IC0xMQogICAgICBydC5wcmlvICAgICAgID0gMAogICAgICBydC50aW1lLnNvZnQgID0gLTEKICAgICAgcnQudGltZS5oYXJkICA9IC0xCiAgICB9CiAgICBmbGFncyA9IFsgaWZleGlzdHMgbm9mYWlsIF0KICB9Cl0K | base64 -d | tee /etc/pipewire/client.conf; init_hooks=pip3 install aqtinstall; init_hooks=dnf clean all; diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index bd5f2c86..a706baf6 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -415,6 +415,11 @@ ## Multiplex RTCP information into existing RTP stream. #rtcpMuxEnabled=false +## SIP URI to call for the ordinary voice mail box. There is a fallback to the "Message-Account" field of the +## MWI info message of the SIP server. +## default: "" +#voiceMailUri= + #[auth0] ## The authentication scheme (e.g. “digest”). diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts index 6bf5f247..84e6823a 100644 --- a/i18n/gonnect_de.ts +++ b/i18n/gonnect_de.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 Speichern der Anmeldedaten für das Adressbuch fehlgeschlagen: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Standard-Eingang - + Default output Standard-Ausgang @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Initialen dieses Kontakts @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Dashboard-Seite Grundraster - + Canvas for editable dashboard pages Bearbeitungsfläche für Dashboard-Seiten - + Add widgets Widgets hinzufügen @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Widget verschieben - + Change the position of the widget Die Position des Widgets ändern - + Remove widget Widget entfernen - + Remove the currently selected widget from the dashboard Das aktuell ausgewählte Widget vom Dashboard entfernen - + Resize widget Widget-Größe ändern - + Resize the widget according to the mouse direction Die Größe des Widgets entsprechend der Mausrichtung ändern @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Aktiver Rand zum Ziehen - + Top left drag border for window resize operations Rand oben links zum Ändern der Fenstergröße - + Top drag border for window resize operations Rand oben zum Ändern der Fenstergröße - + Top right border for window resize operations Rand oben rechts zum Ändern der Fenstergröße - + Right drag border for window resize operations Rand rechts zum Ändern der Fenstergröße - + Bottom right drag border for window resize operations Rand unten rechts zum Ändern der Fenstergröße - + Bottom drag border for window resize operations Rand unten zum Ändern der Fenstergröße - + Bottom left drag border for window resize operations Rand unten links zum Ändern der Fenstergröße - + Left drag border for window resize operations Rand links zum Ändern der Fenstergröße @@ -409,12 +409,12 @@ Call - + Conference Konferenz - + Drag bar Ziehleiste @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 kHz - + Transmit Senden - + Call security level Anruf-Sicherheitsstufe - + Security level of the ongoing call Sicherheitsstufe des laufenden Anrufs - + Call security details Anruf-Sicherheitsdetails - + Detailed call security status: %1 / %2 Detaillierter Sicherheitsstatus des Anrufs: %1 / %2 - + signaling encrypted Signalisierung verschlüsselt - + signaling unencrypted Signalisierung unverschlüsselt - + media encrypted Medien verschlüsselt - + media unencrypted Medien unverschlüsselt - + Call quality Anrufqualität - + Quality of the ongoing call Qualität des laufenden Anrufs - + Transmission statistics Sendestatistiken - - + + Call quality metrics Metriken der Anrufqualität - - + + MOS MOS - - + + Mean opinion score Bewertungskennzahl (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Numerische Kennzahl zur Bewertung der sendeseitigen Sprachqualität: %1 - - + + Packet loss Paketverlust - + %1% of packets lost in transmission %1% der Pakete beim Senden verloren - - + + Jitter Jitter - + Amount of transmission side jitter: %1 Sendeseitiger Jitter: %1 - - + + Effective delay Effektive Verzögerung - + Effective transmission side call delay: %1 Effektive sendeseitige Anrufverzögerung: %1 - + Receiver statistics Empfangsstatistiken - + Receive Empfangen - + Numerical metric assessing receiver-side voice/video call quality: %1 Metriken zur Bewertung der empfangsseitigen Sprach-/Videoqualität: %1 - + %1% of packets lost in receival %1% der Pakete beim Empfang verloren - + Amount of receiver side jitter: %1 Empfangsseitiger Jitter: %1 - + Effective receiver side call delay: %1 Effektive empfangsseitige Anrufverzögerung: %1 - + Codec Codec - + Audio codec Audio-Codec - + The currently used audio codec and frequency: %1 Der aktuell verwendete Audio-Codec und die Frequenz: %1 - + Elapsed call time Verstrichene Anrufzeit - + The duration in seconds the call has been active for: %1 Die Dauer in Sekunden, seit der Anruf aktiv ist: %1 - + Screen Bildschirm - + Screensharing control Steuerung der Bildschirmfreigabe - + Start sharing your screen Bildschirmfreigabe starten - + Camera Kamera - + Camera control Kamera-Steuerung - + Enable your camera Kamera aktivieren - + Resume Fortsetzen - + Hold Halten - + Resume call Anruf fortsetzen - + Hold call Anruf halten - + Update the call hold state Den Haltezustand des Anrufs aktualisieren - + Micro Mikrofon - + Input control Eingangssteuerung - + Set the mute state of the current input device Den Stummschaltungszustand des aktuellen Eingangsgeräts ändern - + Output Ausgabe - + Output control Ausgangssteuerung - + Change the current output devices Die aktuellen Ausgangsgeräte ändern - + Accept call Anruf annehmen - + Hangup call Anruf beenden @@ -825,25 +825,30 @@ CallerBigAvatar - + Caller name Name des Anrufers - + is calling... ruft an... - + Calling... Rufe... + + + In progress... + Rufaufbau... + CallsModel - + unknown number unbekannte Nummer @@ -1178,122 +1183,122 @@ Konferenzraum - + Share Teilen - + Copy room name Raum-Name kopieren - + Copy room link Raum-Link kopieren - + Open in browser Im Browser öffnen - + Show phone number Zeige Telefonnummer - + Raise Hand heben - + Resume Fortsetzen - + Hold Halten - + View Ansicht - + Screen Bildschirm - + Share window Fenster teilen - + Share screen Bildschirm teilen - + Camera Kamera - + Output Ausgabe - + More Mehr - + Noise supression Geräuschunterdrückung - + Toggle subtitles Untertitel umschalten - + Toggle whiteboard Whiteboard umschalten - + Video quality... Videoqualität... - + Set room password... Raumpasswort setzen... - + Mute everyone Alle stummschalten - + Leave conference Konferenz verlassen - + End conference for all Konferenz für alle beenden - + Micro Mikrofon @@ -1368,7 +1373,7 @@ App Menü - + Close GOnnect window GOnnect-Fenster schließen @@ -1384,22 +1389,22 @@ DateEventManager - + Conference starting soon Konferenz startet bald - + Appointment starting soon Termin startet bald - + Join Teilnehmen - + Open Öffnen @@ -1959,12 +1964,12 @@ FirstAidButton - + Open first aid menu Erste-Hilfe-Menü öffnen - + First Aid Erste-Hilfe @@ -1972,7 +1977,7 @@ GonnectWindow - + Home Home @@ -1980,7 +1985,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2112,78 +2117,78 @@ HistoryWidget - + History Verlauf - - + + All Alle - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Anruftyp-Auswahl für den Verlauf - + Select the call type to filter by Anruftyp zum Filtern auswählen - + Currently selected call type Aktuell ausgewählter Anruftyp - + Incoming Eingehend - + Outgoing Ausgehend - + Missed Verpasst - + History call origin picker Anrufrichtung-Auswahl für den Verlauf - + Select the call origin to filter by Anrufrichtung zum Filtern auswählen - + Currently selected call origin Aktuell ausgewählte Anrufrichtung - + Hide history search Verlaufssuche ausblenden - + Show history search Verlaufssuche einblenden @@ -2370,7 +2375,7 @@ Bitte geben Sie den Wiederherstellungsschlüssel für %1 ein: - + Please enter the password for the SIP account: Bitte gib das Passwort für den SIP-Account ein: @@ -2391,22 +2396,22 @@ Möchtest Du das Fenster schliessen und alle aktiven Anrufe beenden? - + Registration failed Registrierung fehlgeschlagen - + Registration failed with with status %1: %2 Die Registrierung ist mit dem Status %1 fehlgeschlagen: %2 - + Error Fehler - + Fatal Error Fataler Fehler @@ -2414,114 +2419,114 @@ MainTabBar - - + + Home Home - + Conference Konferenz - + No active conference Keine aktive Konferenz - + No active call Kein aktiver Anruf - + Move up Nach oben bewegen - + Move tab up Tab nach oben verschieben - + Moves the currently selected tab up by one Verschiebt den aktuell ausgewählten Tab um eine Position nach oben - + Move down Nach unten bewegen - + Move tab down Tab nach unten verschieben - + Moves the currently selected tab down by one Verschiebt den aktuell ausgewählten Tab um eine Position nach unten - + Edit Bearbeiten - + Edit page Seite bearbeiten - + Edit the currently selected dashboard page Die aktuell ausgewählte Dashboard-Seite bearbeiten - + Delete Löschen - + Delete page Seite löschen - + Delete the currently selected dashboard page Die aktuell ausgewählte Dashboard-Seite löschen - + Call Anruf - + Selected tab Ausgewählter Tab - + The currently selected tab Der aktuell ausgewählte Tab - + Selected tab options Optionen des ausgewählten Tabs - + The settings of the currently selected tab Die Einstellungen des aktuell ausgewählten Tabs - - + + Settings Einstellungen @@ -3959,7 +3964,7 @@ Simbabwe - + There are %n active call(s). calls @@ -4020,27 +4025,32 @@ 'userUri' ist erforderlich - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' ist keine gültige SIP URI: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' ist keine gültige SIP URI: %1 - + 'registrarUri' is required 'registrarUri' ist erforderlich - + 'proxies' contains invalid SIP URI entry: %1 'proxies' enthält eine ungültige SIP URI: %1 - + Failed to create %1: %2 Fehler beim Erzeugen von %1: %2 - + Failed to persist SIP credentials: %1 SIP-Anmeldedaten konnten nicht gespeichert werden: %1 @@ -4056,12 +4066,12 @@ SIPCall - + Active call with %1 Aktiver Anruf mit %1 - + Hang up Auflegen @@ -4104,7 +4114,7 @@ SIPManager - + New Identity Neue Identität @@ -4128,14 +4138,6 @@ Quelldatei %1 existiert nicht - - SearchBox - - - Search number - Nummer suchen - - SearchCategoryItem @@ -4172,49 +4174,6 @@ Dateien - - SearchDial - - - Select number - Nummer auswählen - - - - Activate search field - Suchfeld aktivieren - - - - Number or contact - Nummer oder Kontakt - - - - Clear search field - Suchfeld leeren - - - - Default - Vorgabe - - - - Auto - Auto - - - - Preferred identity - Bevorzugte Identität - - - - Select the preferred identity for outgoing calls - Die bevorzugte Identität für ausgehende Anrufe auswählen - - SearchField @@ -4223,7 +4182,7 @@ Suche nach Kontakten oder Raumnamen... - + Clear search field Suchfeld leeren @@ -4275,52 +4234,52 @@ SearchResultPopup - + Search filter and identity selection Suchfilter- und Identitätsauswahl - + Select search filter to be applied, as well as the outgoing identity Anzuwendenden Suchfilter sowie die ausgehende Identität auswählen - + Outgoing identity Ausgehende Identität - + Search results Suchergebnisse - + All search results will be listed here in their respective categories Hier werden alle Suchergebnisse in den jeweiligen Kategorien aufgelistet - + Direct dial Direktwahl - + Call "%1" "%1" anrufen - + Open room "%1" Öffne Raum "%1" - + History Verlauf - + Contacts Kontakte @@ -4358,17 +4317,17 @@ Farbschema - + System default Systemvorgabe - + Light Hell - + Dark Dunkel @@ -4388,7 +4347,7 @@ Übertragung des Stummschaltungszustands für USB Headsets deaktivieren - + Reload contacts from LDAP LDAP Kontakte neu laden @@ -4424,12 +4383,12 @@ Eigene Fensterdekoration verwenden - + Theme selection box Auswahlfeld für das Farbschema - + Select the UI theme Das UI-Farbschema auswählen @@ -4454,26 +4413,26 @@ Bevorzugte Identität - - + + Default Vorgabe - + Auto Auto - - + + Prefererred identity selection Auswahl der bevorzugten Identität - - + + Select the preferred identity Die bevorzugte Identität auswählen @@ -4518,52 +4477,52 @@ Eingangsgerät - + Input device selection Eingangsgeräte-Auswahl - + Select the input device to be used Das zu verwendende Eingangsgerät auswählen - + Currently selected input option Aktuell ausgewählte Eingangsoption - + Output device Ausgangsgerät - + Output device selection Ausgangsgeräte-Auswahl - + Select the output device to be used Das zu verwendende Ausgangsgerät auswählen - + Currently selected output option Aktuell ausgewählte Ausgangsoption - + Output device for ring tone Ausgabegerät für Klingeltöne - + Currently selected ring output option Aktuell ausgewählte Klingelton-Ausgangsoption - + Ring tone Klingelton @@ -4573,69 +4532,76 @@ Nutze den Klingelton des USB Headsets wenn verfügbar - + Reset ring tone Klingelton zurücksetzen - + Reset the ring tone to its default option Den Klingelton auf die Standardeinstellung zurücksetzen - + Pick ring tone Klingelton auswählen - + Select the ring tone you want to use for incoming calls Den Klingelton auswählen, der für eingehende Anrufe verwendet werden soll - - + + Currently set to: Aktuell eingestellt auf: - + Ring tone volume Klingelton-Lautstärke - - + + Adjust %1 %1 einstellen - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Pause zwischen den Klingeltönen [s] - + + %1 s + Label for showing seconds + %1 s + + + Debugging Fehlersuche - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Verwende diese Schaltfläche, um einen Lauf zur Fehlersuche zu starten. Die App wird neu gestartet und beginnt dann, zusätzliche Informationen aufzuzeichnen, die für die Fehlersuche nützlich sein können. Während dieses Laufs kannst Du hierher zurückkehren, um die Informationen herunterzuladen. Ein Lauf zur Fehlersuche ist auf 5 Minuten begrenzt. Danach wird die App automatisch im normalen Modus neu gestartet. - + Start debug run (restart app) Lauf zur Fehlersuche starten (App neustarten) - + Download debug information Informationen zur Fehlersuche herunterladen @@ -4850,27 +4816,27 @@ StateManager - + Show dial window and focus search field Zeige das Wählfenster und fokussiere das Suchfeld - + End all calls Alle Anrufe beenden - + Redial last outgoing call Letzten ausgehenden Anruf wiederholen - + Toggle hold Halten umschalten - + Phone calls are active Anrufe sind aktiv @@ -4878,48 +4844,48 @@ SystemTrayMenu - - + + Dial... Wählen... - + Not registered... Nicht registriert... - + Settings Einstellungen - + About Über - + Quit Beenden - + End conference Konferenz beenden - + Call with %1 has ended Anruf mit %1 beendet - + Hang up call with %1 Ruf mit %1 beenden - + Accept call with %1 Ruf von %1 annehmen @@ -5048,6 +5014,36 @@ Audio Files (%1) Audio-Dateien (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Anrufbeantworter abhören + + + + %n new voice mail(s) + + %n neue Nachricht + %n neue Nachrichten + + + + + %n old voice mail(s) + + %n neue Nachricht + %n neue Nachrichten + + VolumePopup diff --git a/i18n/gonnect_en.ts b/i18n/gonnect_en.ts index 33afe06a..7aab4c04 100644 --- a/i18n/gonnect_en.ts +++ b/i18n/gonnect_en.ts @@ -26,7 +26,7 @@ QObject - + There are %n active call(s). calls @@ -62,4 +62,23 @@ + + VoiceMailField + + + %n new voice mail(s) + + %n new voice mail + %n new voice mails + + + + + %n old voice mail(s) + + %n old voice mail + %n old voice mails + + + diff --git a/i18n/gonnect_es.ts b/i18n/gonnect_es.ts index d96c130f..1c803697 100644 --- a/i18n/gonnect_es.ts +++ b/i18n/gonnect_es.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 No se pudieron conservar las credenciales de la libreta de direcciones: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Entrada predeterminada - + Default output Salida predeterminada @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Iniciales de este contacto @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Cuadrícula base de la página del panel - + Canvas for editable dashboard pages Lienzo para páginas del panel editables - + Add widgets Añadir widgets @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Arrastrar widget - + Change the position of the widget Cambiar la posición del widget - + Remove widget Eliminar widget - + Remove the currently selected widget from the dashboard Eliminar el widget seleccionado actualmente del panel - + Resize widget Redimensionar widget - + Resize the widget according to the mouse direction Redimensionar el widget según la dirección del ratón @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Borde de arrastre - + Top left drag border for window resize operations Borde de arrastre superior izquierdo para cambiar el tamaño de la ventana - + Top drag border for window resize operations Borde de arrastre superior para cambiar el tamaño de la ventana - + Top right border for window resize operations Borde superior derecho para cambiar el tamaño de la ventana - + Right drag border for window resize operations Borde de arrastre derecho para cambiar el tamaño de la ventana - + Bottom right drag border for window resize operations Borde de arrastre inferior derecho para cambiar el tamaño de la ventana - + Bottom drag border for window resize operations Borde de arrastre inferior para cambiar el tamaño de la ventana - + Bottom left drag border for window resize operations Borde de arrastre inferior izquierdo para cambiar el tamaño de la ventana - + Left drag border for window resize operations Borde de arrastre izquierdo para cambiar el tamaño de la ventana @@ -409,12 +409,12 @@ Call - + Conference Conferencia - + Drag bar Barra de arrastre @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 kHz - + Transmit Transmitir - + Call security level Nivel de seguridad de la llamada - + Security level of the ongoing call Nivel de seguridad de la llamada en curso - + Call security details Detalles de seguridad de la llamada - + Detailed call security status: %1 / %2 Estado detallado de seguridad de la llamada: %1 / %2 - + signaling encrypted señalización cifrada - + signaling unencrypted señalización sin cifrar - + media encrypted medios cifrados - + media unencrypted medios sin cifrar - + Call quality Calidad de la llamada - + Quality of the ongoing call Calidad de la llamada en curso - + Transmission statistics Estadísticas de transmisión - - + + Call quality metrics Métricas de calidad de la llamada - - + + MOS MOS - - + + Mean opinion score Puntuación de opinión media (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Métrica numérica que evalúa la calidad de voz en el lado de transmisión: %1 - - + + Packet loss Pérdida de paquetes - + %1% of packets lost in transmission %1% de paquetes perdidos en la transmisión - - + + Jitter Jitter - + Amount of transmission side jitter: %1 Jitter en el lado de transmisión: %1 - - + + Effective delay Retardo efectivo - + Effective transmission side call delay: %1 Retardo efectivo de llamada en el lado de transmisión: %1 - + Receiver statistics Estadísticas de recepción - + Receive Recibir - + Numerical metric assessing receiver-side voice/video call quality: %1 Métrica numérica que evalúa la calidad de voz/vídeo en el lado del receptor: %1 - + %1% of packets lost in receival %1% de paquetes perdidos en la recepción - + Amount of receiver side jitter: %1 Jitter en el lado del receptor: %1 - + Effective receiver side call delay: %1 Retardo efectivo de llamada en el lado del receptor: %1 - + Codec Códec - + Audio codec Códec de audio - + The currently used audio codec and frequency: %1 El códec de audio y la frecuencia utilizados actualmente: %1 - + Elapsed call time Tiempo transcurrido de la llamada - + The duration in seconds the call has been active for: %1 La duración en segundos que la llamada ha estado activa: %1 - + Screen Pantalla - + Screensharing control Control de compartición de pantalla - + Start sharing your screen Iniciar la compartición de pantalla - + Camera Cámera - + Camera control Control de la cámara - + Enable your camera Activar la cámara - + Resume Continuar - + Hold Mantener - + Resume call Reanudar la llamada - + Hold call Poner la llamada en espera - + Update the call hold state Actualizar el estado de espera de la llamada - + Micro Micrófono - + Input control Control de entrada - + Set the mute state of the current input device Establecer el estado de silencio del dispositivo de entrada actual - + Output Salida - + Output control Control de salida - + Change the current output devices Cambiar los dispositivos de salida actuales - + Accept call Aceptar llamada - + Hangup call Colgar llamada @@ -825,25 +825,30 @@ CallerBigAvatar - + Caller name Nombre del llamante - + is calling... está llamando... - + Calling... Llamando... + + + In progress... + Estableciendo conexión... + CallsModel - + unknown number número desconocido @@ -1178,122 +1183,122 @@ Sala de conferencias - + Share Compartir - + Copy room name Copiar nombre de sala - + Copy room link Copiar enlace de sala - + Open in browser Abrir en el navegador - + Show phone number Mostrar número de teléfono - + Raise Levantar la mano - + Resume Continuar - + Hold En espera - + View Vista - + Screen Pantalla - + Share window Compartir ventana - + Share screen Compartir pantalla - + Camera Cámera - + Micro Micrófono - + Output Salida - + More Más - + Noise supression Supresión de ruido - + Toggle subtitles Activar/desactivar subtítulos - + Toggle whiteboard Activar/desactivar pizarra - + Video quality... Calidad de vídeo... - + Set room password... Establecer contraseña de sala... - + Mute everyone Silenciar a todos - + Leave conference Abandonar la conferencia - + End conference for all Terminar la conferencia para todos @@ -1368,7 +1373,7 @@ Menú de la aplicación - + Close GOnnect window Cerrar la ventana de GOnnect @@ -1384,22 +1389,22 @@ DateEventManager - + Conference starting soon La conferencia comenzará pronto - + Appointment starting soon La cita comenzará pronto - + Join Unirse - + Open Abrir @@ -1959,12 +1964,12 @@ FirstAidButton - + Open first aid menu Abrir el menú de primeros auxilios - + First Aid Primeros auxilios @@ -1972,7 +1977,7 @@ GonnectWindow - + Home Inicio @@ -1980,7 +1985,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2112,78 +2117,78 @@ HistoryWidget - + History Historia - - + + All Todos/ todas - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Selector de tipo de llamada del historial - + Select the call type to filter by Seleccionar el tipo de llamada para filtrar - + Currently selected call type Tipo de llamada seleccionado actualmente - + Incoming Entrando - + Outgoing Saliendo - + Missed Perdido - + History call origin picker Selector de origen de llamada del historial - + Select the call origin to filter by Seleccionar el origen de llamada para filtrar - + Currently selected call origin Origen de llamada seleccionado actualmente - + Hide history search Ocultar búsqueda del historial - + Show history search Mostrar búsqueda del historial @@ -2386,27 +2391,27 @@ ¿Realmente desea cerrar esta ventana y terminar todas las llamadas en curso? - + Please enter the password for the SIP account: Por favor, introduzca la contraseña de la cuenta SIP: - + Registration failed El registro ha fallado - + Registration failed with with status %1: %2 El registro ha fallado con el estado %1: %2 - + Error Error - + Fatal Error Error fatal @@ -2414,114 +2419,114 @@ MainTabBar - - + + Home Casa - + Conference Conferencia - + No active conference No hay conferencia activa - + No active call No hay llamada activa - + Move up Mover arriba - + Move tab up Mover pestaña arriba - + Moves the currently selected tab up by one Mueve la pestaña seleccionada actualmente una posición hacia arriba - + Move down Mover abajo - + Move tab down Mover pestaña abajo - + Moves the currently selected tab down by one Mueve la pestaña seleccionada actualmente una posición hacia abajo - + Edit Editar - + Edit page Editar página - + Edit the currently selected dashboard page Editar la página del panel seleccionada actualmente - + Delete Borrar - + Delete page Eliminar página - + Delete the currently selected dashboard page Eliminar la página del panel seleccionada actualmente - + Call Llamar - + Selected tab Pestaña seleccionada - + The currently selected tab La pestaña seleccionada actualmente - + Selected tab options Opciones de la pestaña seleccionada - + The settings of the currently selected tab La configuración de la pestaña seleccionada actualmente - - + + Settings Ajustes @@ -2734,7 +2739,7 @@ QObject - + There are %n active call(s). calls @@ -4020,27 +4025,32 @@ 'userUri' es obligatorio - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' no es una URI SIP válida: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' no es una URI SIP válida: %1 - + 'registrarUri' is required 'registrarUri' es obligatorio - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contiene una entrada de URI SIP no válida: %1 - + Failed to create %1: %2 Error al crear %1: %2 - + Failed to persist SIP credentials: %1 No se pudieron conservar las credenciales SIP: %1 @@ -4056,12 +4066,12 @@ SIPCall - + Active call with %1 Llamada activa con %1 - + Hang up Colgar @@ -4104,7 +4114,7 @@ SIPManager - + New Identity Nueva identidad @@ -4128,14 +4138,6 @@ El archivo fuente %1 no existe - - SearchBox - - - Search number - Buscar número - - SearchCategoryItem @@ -4172,49 +4174,6 @@ Archivos - - SearchDial - - - Select number - Seleccionar número - - - - Activate search field - Activar campo de búsqueda - - - - Number or contact - Número o contacto - - - - Clear search field - Limpiar campo de búsqueda - - - - Default - Predeterminado - - - - Auto - Auto - - - - Preferred identity - Identidad preferida - - - - Select the preferred identity for outgoing calls - Seleccionar la identidad preferida para las llamadas salientes - - SearchField @@ -4223,7 +4182,7 @@ Buscar contactos o nombres de sala... - + Clear search field Limpiar campo de búsqueda @@ -4275,52 +4234,52 @@ SearchResultPopup - + Search filter and identity selection Filtro de búsqueda y selección de identidad - + Select search filter to be applied, as well as the outgoing identity Seleccionar el filtro de búsqueda a aplicar, así como la identidad saliente - + Outgoing identity Identidad saliente - + Search results Resultados de búsqueda - + All search results will be listed here in their respective categories Aquí se listarán todos los resultados de búsqueda en sus respectivas categorías - + Direct dial Marcación directa - + Call "%1" Llamar a "%1" - + Open room "%1" Abrir sala "%1" - + History Historia - + Contacts Contactos @@ -4373,22 +4332,22 @@ Esquema de color - + System default Predeterminado del sistema - + Light Claro - + Dark Oscuro - + Reload contacts from LDAP Recargar contactos desde LDAP @@ -4418,13 +4377,13 @@ Identidad preferida estándar - - + + Default Predeterminado - + Auto Auto @@ -4461,12 +4420,12 @@ Usar decoración de ventana personalizada - + Theme selection box Cuadro de selección de tema - + Select the UI theme Seleccionar el tema de la interfaz @@ -4476,14 +4435,14 @@ Opción de tema seleccionada actualmente - - + + Prefererred identity selection Selección de identidad preferida - - + + Select the preferred identity Seleccionar la identidad preferida @@ -4523,119 +4482,126 @@ Dispositivo de entrada - + Input device selection Selección del dispositivo de entrada - + Select the input device to be used Seleccionar el dispositivo de entrada que se debe usar - + Currently selected input option Opción de entrada seleccionada actualmente - + Output device Dispositivo de salida - + Output device selection Selección del dispositivo de salida - + Select the output device to be used Seleccionar el dispositivo de salida que se debe usar - + Currently selected output option Opción de salida seleccionada actualmente - + Output device for ring tone Dispositivo de salida para tono de llamada - + Currently selected ring output option Opción de salida del tono de llamada seleccionada actualmente - + Ring tone Tono de llamada - + Reset ring tone Restablecer tono de llamada - + Reset the ring tone to its default option Restablecer el tono de llamada a su opción predeterminada - + Pick ring tone Elegir tono de llamada - + Select the ring tone you want to use for incoming calls Seleccionar el tono de llamada que desea usar para las llamadas entrantes - - + + Currently set to: Configurado actualmente en: - + Ring tone volume Volumen del tono de llamada - - + + Adjust %1 Ajustar %1 - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Pausa entre tonos de llamada [s] - + + %1 s + Label for showing seconds + %1 s + + + Debugging Depuración - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Use este botón para iniciar una sesión de depuración. La aplicación se reiniciará y comenzará a registrar información adicional útil para la depuración. Durante esta sesión, regrese aquí para descargar la información. Una sesión de depuración está limitada a 5 minutos, tras los cuales la aplicación se reiniciará automáticamente en modo normal. - + Start debug run (restart app) Iniciar sesión de depuración (reiniciar aplicación) - + Download debug information Descargar información de depuración @@ -4850,27 +4816,27 @@ StateManager - + Show dial window and focus search field Mostrar la ventana de marcación y enfocar el campo de búsqueda - + End all calls Terminar todas las llamadas - + Redial last outgoing call Rellamar la última llamada realizada - + Toggle hold Alternar espera - + Phone calls are active Hay llamadas telefónicas activas @@ -4878,48 +4844,48 @@ SystemTrayMenu - - + + Dial... Marcar... - + Not registered... No registrado... - + Settings Ajustes - + About Sobre - + Quit Terminar - + End conference Terminar conferencia - + Call with %1 has ended La llamada con %1 ha finalizado - + Accept call with %1 Aceptar llamada de %1 - + Hang up call with %1 Colgar llamada con %1 @@ -5048,6 +5014,36 @@ Audio Files (%1) Archivos de audio (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Escuchar el contestador automático + + + + %n new voice mail(s) + + %n nuevo mensaje + %n mensajes nuevos + + + + + %n old voice mail(s) + + %n mensaje antiguo + %n noticias antiguas + + VolumePopup diff --git a/i18n/gonnect_fa.ts b/i18n/gonnect_fa.ts index 08429388..39980d6e 100644 --- a/i18n/gonnect_fa.ts +++ b/i18n/gonnect_fa.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 ذخیرهٔ اعتبارنامه‌های دفترچهٔ آدرس ناموفق بود: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input ورودی پیش‌فرض - + Default output خروجی پیش‌فرض @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact حروف اول نام مخاطب @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid شبکه پایه صفحه داشبورد - + Canvas for editable dashboard pages بوم صفحات داشبورد قابل ویرایش - + Add widgets افزودن ابزارک @@ -285,32 +285,32 @@ BaseWidget - + Drag widget کشیدن ابزارک - + Change the position of the widget تغییر موقعیت ابزارک - + Remove widget حذف ابزارک - + Remove the currently selected widget from the dashboard حذف ابزارک انتخاب‌شده از داشبورد - + Resize widget تغییر اندازه ابزارک - + Resize the widget according to the mouse direction تغییر اندازه ابزارک بر اساس جهت ماوس @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border لبه کشیدنی - + Top left drag border for window resize operations لبه کشیدنی بالا چپ برای تغییر اندازه پنجره - + Top drag border for window resize operations لبه کشیدنی بالا برای تغییر اندازه پنجره - + Top right border for window resize operations لبه بالا راست برای تغییر اندازه پنجره - + Right drag border for window resize operations لبه کشیدنی راست برای تغییر اندازه پنجره - + Bottom right drag border for window resize operations لبه کشیدنی پایین راست - + Bottom drag border for window resize operations لبه کشیدنی پایین برای تغییر اندازه پنجره - + Bottom left drag border for window resize operations لبه کشیدنی پایین چپ - + Left drag border for window resize operations لبه کشیدنی چپ برای تغییر اندازه پنجره @@ -409,12 +409,12 @@ Call - + Conference کنفرانس - + Drag bar نوار کشیدنی @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 کیلوهرتز - + Transmit ارسال - + Call security level سطح امنیت تماس - + Security level of the ongoing call سطح امنیت تماس جاری - + Call security details جزئیات امنیت تماس - + Detailed call security status: %1 / %2 وضعیت دقیق امنیت تماس: %1 / %2 - + signaling encrypted سیگنال‌دهی رمزنگاری‌شده - + signaling unencrypted سیگنال‌دهی رمزنگاری‌نشده - + media encrypted رسانه رمزنگاری‌شده - + media unencrypted رسانه رمزنگاری‌نشده - + Call quality کیفیت تماس - + Quality of the ongoing call کیفیت تماس جاری - + Transmission statistics آمار ارسال - - + + Call quality metrics معیارهای کیفیت تماس - - + + MOS MOS - - + + Mean opinion score امتیاز میانگین نظر (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 معیار عددی کیفیت صدا در سمت ارسال: %1 - - + + Packet loss اتلاف بسته - + %1% of packets lost in transmission %1٪ از بسته‌ها در ارسال از دست رفت - - + + Jitter جیتر - + Amount of transmission side jitter: %1 میزان جیتر در سمت ارسال: %1 - - + + Effective delay تأخیر مؤثر - + Effective transmission side call delay: %1 تأخیر مؤثر تماس در سمت ارسال: %1 - + Receiver statistics آمار دریافت - + Receive دریافت - + Numerical metric assessing receiver-side voice/video call quality: %1 معیار عددی کیفیت صدا/تصویر در سمت دریافت: %1 - + %1% of packets lost in receival %1٪ از بسته‌ها در دریافت از دست رفت - + Amount of receiver side jitter: %1 میزان جیتر در سمت دریافت: %1 - + Effective receiver side call delay: %1 تأخیر مؤثر در سمت دریافت: %1 - + Codec کدک - + Audio codec کدک صوتی - + The currently used audio codec and frequency: %1 کدک صوتی و فرکانس فعلی: %1 - + Elapsed call time زمان سپری‌شده تماس - + The duration in seconds the call has been active for: %1 مدت زمان فعال بودن تماس به ثانیه: %1 - + Screen صفحه‌نمایش - + Screensharing control کنترل اشتراک‌گذاری صفحه - + Start sharing your screen شروع اشتراک‌گذاری صفحه - + Camera دوربین - + Camera control کنترل دوربین - + Enable your camera فعال‌سازی دوربین - + Resume ادامه - + Hold نگه‌داشتن - + Resume call ادامه تماس - + Hold call نگه‌داشتن تماس - + Update the call hold state به‌روزرسانی وضعیت نگه‌داری تماس - + Micro میکروفون - + Input control کنترل ورودی - + Set the mute state of the current input device تنظیم وضعیت بی‌صدا کردن دستگاه ورودی - + Output خروجی - + Output control کنترل خروجی - + Change the current output devices تغییر دستگاه‌های خروجی فعلی - + Accept call پذیرفتن تماس - + Hangup call قطع تماس @@ -824,25 +824,30 @@ CallerBigAvatar - + Caller name نام تماس‌گیرنده - + is calling... در حال تماس... - + Calling... در حال برقراری تماس... + + + In progress... + در حال برقراری ارتباط... + CallsModel - + unknown number شماره ناشناس @@ -1176,122 +1181,122 @@ اتاق کنفرانس - + Share اشتراک‌گذاری - + Copy room name کپی نام اتاق - + Copy room link کپی لینک اتاق - + Open in browser باز کردن در مرورگر - + Show phone number نمایش شماره تلفن - + Raise بلند کردن دست - + Resume ادامه - + Hold نگه‌داشتن - + View نما - + Screen صفحه‌نمایش - + Share window اشتراک‌گذاری پنجره - + Share screen اشتراک‌گذاری صفحه - + Camera دوربین - + Output خروجی - + More بیشتر - + Noise supression حذف نویز - + Toggle subtitles روشن/خاموش کردن زیرنویس - + Toggle whiteboard روشن/خاموش کردن تخته سفید - + Video quality... کیفیت تصویر... - + Set room password... تنظیم رمز اتاق... - + Mute everyone بی‌صدا کردن همه - + Leave conference ترک کنفرانس - + End conference for all پایان کنفرانس برای همه - + Micro میکروفون @@ -1366,7 +1371,7 @@ منوی برنامه - + Close GOnnect window بستن پنجره GOnnect @@ -1382,22 +1387,22 @@ DateEventManager - + Conference starting soon کنفرانس به زودی شروع می‌شود - + Appointment starting soon قرار ملاقات به زودی شروع می‌شود - + Join پیوستن - + Open باز کردن @@ -1957,12 +1962,12 @@ FirstAidButton - + Open first aid menu باز کردن منوی کمک‌های اولیه - + First Aid کمک‌های اولیه @@ -1970,7 +1975,7 @@ GonnectWindow - + Home خانه @@ -1978,7 +1983,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2110,78 +2115,78 @@ HistoryWidget - + History تاریخچه - - + + All همه - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker انتخاب نوع تماس در تاریخچه - + Select the call type to filter by نوع تماس برای فیلتر را انتخاب کنید - + Currently selected call type نوع تماس انتخاب‌شده - + Incoming ورودی - + Outgoing خروجی - + Missed از دست رفته - + History call origin picker انتخاب منشأ تماس در تاریخچه - + Select the call origin to filter by منشأ تماس برای فیلتر را انتخاب کنید - + Currently selected call origin منشأ تماس انتخاب‌شده - + Hide history search پنهان کردن جستجوی تاریخچه - + Show history search نمایش جستجوی تاریخچه @@ -2368,7 +2373,7 @@ لطفاً کلید بازیابی %1 را وارد کنید: - + Please enter the password for the SIP account: لطفاً رمز عبور حساب SIP را وارد کنید: @@ -2389,22 +2394,22 @@ آیا می‌خواهید این پنجره بسته و همه تماس‌ها پایان یابد؟ - + Registration failed ثبت‌نام ناموفق بود - + Registration failed with with status %1: %2 ثبت‌نام با وضعیت %1: %2 ناموفق بود - + Error خطا - + Fatal Error خطای مهلک @@ -2412,114 +2417,114 @@ MainTabBar - - + + Home خانه - + Conference کنفرانس - + No active conference هیچ کنفرانس فعالی وجود ندارد - + No active call هیچ تماس فعالی وجود ندارد - + Move up انتقال به بالا - + Move tab up انتقال زبانه به بالا - + Moves the currently selected tab up by one زبانه انتخاب‌شده را یک موقعیت به بالا منتقل می‌کند - + Move down انتقال به پایین - + Move tab down انتقال زبانه به پایین - + Moves the currently selected tab down by one زبانه انتخاب‌شده را یک موقعیت به پایین منتقل می‌کند - + Edit ویرایش - + Edit page ویرایش صفحه - + Edit the currently selected dashboard page ویرایش صفحه داشبورد انتخاب‌شده - + Delete حذف - + Delete page حذف صفحه - + Delete the currently selected dashboard page حذف صفحه داشبورد انتخاب‌شده - + Call تماس - + Selected tab زبانه انتخاب‌شده - + The currently selected tab زبانه انتخاب‌شده فعلی - + Selected tab options گزینه‌های زبانه انتخاب‌شده - + The settings of the currently selected tab تنظیمات زبانه انتخاب‌شده فعلی - - + + Settings تنظیمات @@ -3957,7 +3962,7 @@ زیمبابوه - + There are %n active call(s). calls @@ -4017,27 +4022,32 @@ 'userUri' الزامی است - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' یک SIP URI معتبر نیست: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' یک SIP URI معتبر نیست: %1 - + 'registrarUri' is required 'registrarUri' الزامی است - + 'proxies' contains invalid SIP URI entry: %1 'proxies' یک SIP URI نامعتبر دارد: %1 - + Failed to create %1: %2 ایجاد %1 ناموفق بود: %2 - + Failed to persist SIP credentials: %1 ثبت نام کاربری SIP ناموفق بود: %1 @@ -4053,12 +4063,12 @@ SIPCall - + Active call with %1 تماس فعال با %1 - + Hang up قطع تماس @@ -4101,7 +4111,7 @@ SIPManager - + New Identity هویت جدید @@ -4125,14 +4135,6 @@ فایل منبع %1 وجود ندارد - - SearchBox - - - Search number - جستجوی شماره - - SearchCategoryItem @@ -4169,49 +4171,6 @@ فایل‌ها - - SearchDial - - - Select number - انتخاب شماره - - - - Activate search field - فعال‌سازی فیلد جستجو - - - - Number or contact - شماره یا مخاطب - - - - Clear search field - پاک کردن فیلد جستجو - - - - Default - پیش‌فرض - - - - Auto - خودکار - - - - Preferred identity - هویت ترجیحی - - - - Select the preferred identity for outgoing calls - انتخاب هویت ترجیحی برای تماس‌های خروجی - - SearchField @@ -4220,7 +4179,7 @@ جستجوی مخاطبان یا نام اتاق‌ها... - + Clear search field پاک کردن فیلد جستجو @@ -4272,52 +4231,52 @@ SearchResultPopup - + Search filter and identity selection فیلتر جستجو و انتخاب هویت - + Select search filter to be applied, as well as the outgoing identity انتخاب فیلتر جستجو و هویت خروجی - + Outgoing identity هویت خروجی - + Search results نتایج جستجو - + All search results will be listed here in their respective categories همه نتایج جستجو اینجا بر اساس دسته فهرست می‌شوند - + Direct dial شماره‌گیری مستقیم - + Call "%1" تماس با «%1» - + Open room "%1" باز کردن اتاق «%1» - + History تاریخچه - + Contacts مخاطبان @@ -4355,17 +4314,17 @@ طرح رنگی - + System default پیش‌فرض سیستم - + Light روشن - + Dark تیره @@ -4385,7 +4344,7 @@ غیرفعال‌سازی همگام‌سازی وضعیت بی‌صدا هدست - + Reload contacts from LDAP بارگذاری مجدد مخاطبان از LDAP @@ -4421,12 +4380,12 @@ استفاده از تزئین سفارشی پنجره - + Theme selection box کادر انتخاب پوسته - + Select the UI theme انتخاب پوسته رابط کاربری @@ -4451,26 +4410,26 @@ هویت ترجیحی استاندارد - - + + Default پیش‌فرض - + Auto خودکار - - + + Prefererred identity selection انتخاب هویت ترجیحی - - + + Select the preferred identity انتخاب هویت ترجیحی @@ -4515,52 +4474,52 @@ دستگاه ورودی - + Input device selection انتخاب دستگاه ورودی - + Select the input device to be used انتخاب دستگاه ورودی - + Currently selected input option گزینه ورودی انتخاب‌شده - + Output device دستگاه خروجی - + Output device selection انتخاب دستگاه خروجی - + Select the output device to be used انتخاب دستگاه خروجی - + Currently selected output option گزینه خروجی انتخاب‌شده - + Output device for ring tone دستگاه خروجی برای زنگ - + Currently selected ring output option گزینه خروجی زنگ انتخاب‌شده - + Ring tone زنگ @@ -4570,69 +4529,76 @@ استفاده از زنگ هدست USB در صورت در دسترس بودن - + Reset ring tone بازنشانی زنگ - + Reset the ring tone to its default option بازنشانی زنگ به حالت پیش‌فرض - + Pick ring tone انتخاب زنگ - + Select the ring tone you want to use for incoming calls انتخاب زنگ برای تماس‌های ورودی - - + + Currently set to: مقدار فعلی: - + Ring tone volume صدای زنگ - - + + Adjust %1 تنظیم %1 - + %1 % + Label for showing percentage %1 ٪ - + Pause between ring tones [s] مکث بین زنگ‌ها [ثانیه] - + + %1 s + Label for showing seconds + %1 ث + + + Debugging اشکال‌زدایی - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. برای شروع اشکال‌زدایی این دکمه را فشار دهید. برنامه مجدداً راه‌اندازی شده و اطلاعات تشخیصی ثبت می‌کند. این حالت ۵ دقیقه است. - + Start debug run (restart app) شروع اشکال‌زدایی (راه‌اندازی مجدد برنامه) - + Download debug information دانلود اطلاعات اشکال‌زدایی @@ -4847,27 +4813,27 @@ StateManager - + Show dial window and focus search field نمایش پنجره شماره‌گیری و فوکوس فیلد جستجو - + End all calls پایان دادن به همه تماس‌ها - + Redial last outgoing call شماره‌گیری مجدد آخرین تماس خروجی - + Toggle hold تغییر وضعیت نگه‌داری - + Phone calls are active تماس‌ها فعال هستند @@ -4875,48 +4841,48 @@ SystemTrayMenu - - + + Dial... شماره‌گیری... - + Not registered... ثبت‌نشده... - + Settings تنظیمات - + About درباره - + Quit خروج - + End conference پایان کنفرانس - + Call with %1 has ended تماس با %1 پایان یافت - + Hang up call with %1 قطع تماس با %1 - + Accept call with %1 پذیرفتن تماس از %1 @@ -5042,6 +5008,34 @@ Audio Files (%1) فایل‌های صوتی (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + C + + + + VoiceMailField + + + Listen to voicemail + پیام صوتی را گوش دهید + + + + %n new voice mail(s) + + %n پیام جدید + + + + + %n old voice mail(s) + + پیام قدیمی %n + + VolumePopup diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts index 115b9d6a..218e158b 100644 --- a/i18n/gonnect_fr.ts +++ b/i18n/gonnect_fr.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 Échec de la persistance des informations d'identification du carnet d'adresses : %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Entrée par défaut - + Default output Sortie par défaut @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Initiales de ce contact @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Grille de base de la page du tableau de bord - + Canvas for editable dashboard pages Zone de travail pour les pages du tableau de bord modifiables - + Add widgets Ajouter des widgets @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Déplacer le widget - + Change the position of the widget Modifier la position du widget - + Remove widget Supprimer le widget - + Remove the currently selected widget from the dashboard Supprimer le widget actuellement sélectionné du tableau de bord - + Resize widget Redimensionner le widget - + Resize the widget according to the mouse direction Redimensionner le widget selon la direction de la souris @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Bordure de déplacement - + Top left drag border for window resize operations Bordure de déplacement en haut à gauche pour le redimensionnement de la fenêtre - + Top drag border for window resize operations Bordure de déplacement en haut pour le redimensionnement de la fenêtre - + Top right border for window resize operations Bordure en haut à droite pour le redimensionnement de la fenêtre - + Right drag border for window resize operations Bordure de déplacement à droite pour le redimensionnement de la fenêtre - + Bottom right drag border for window resize operations Bordure de déplacement en bas à droite pour le redimensionnement de la fenêtre - + Bottom drag border for window resize operations Bordure de déplacement en bas pour le redimensionnement de la fenêtre - + Bottom left drag border for window resize operations Bordure de déplacement en bas à gauche pour le redimensionnement de la fenêtre - + Left drag border for window resize operations Bordure de déplacement à gauche pour le redimensionnement de la fenêtre @@ -409,12 +409,12 @@ Call - + Conference Conférence - + Drag bar Barre de déplacement @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 kHz - + Transmit Émettre - + Call security level Niveau de sécurité de l'appel - + Security level of the ongoing call Niveau de sécurité de l'appel en cours - + Call security details Détails de sécurité de l'appel - + Detailed call security status: %1 / %2 État détaillé de la sécurité de l'appel : %1 / %2 - + signaling encrypted signalisation chiffrée - + signaling unencrypted signalisation non chiffrée - + media encrypted médias chiffrés - + media unencrypted médias non chiffrés - + Call quality Qualité de l'appel - + Quality of the ongoing call Qualité de l'appel en cours - + Transmission statistics Statistiques d'émission - - + + Call quality metrics Métriques de qualité d'appel - - + + MOS MOS - - + + Mean opinion score Score d'opinion moyen (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Métrique numérique évaluant la qualité vocale côté émission : %1 - - + + Packet loss Perte de paquets - + %1% of packets lost in transmission %1 % de paquets perdus en transmission - - + + Jitter Gigue - + Amount of transmission side jitter: %1 Gigue côté émission : %1 - - + + Effective delay Délai effectif - + Effective transmission side call delay: %1 Délai d'appel effectif côté émission : %1 - + Receiver statistics Statistiques de réception - + Receive Réception - + Numerical metric assessing receiver-side voice/video call quality: %1 Métrique numérique évaluant la qualité voix/vidéo côté réception : %1 - + %1% of packets lost in receival %1 % de paquets perdus en réception - + Amount of receiver side jitter: %1 Gigue côté réception : %1 - + Effective receiver side call delay: %1 Délai d'appel effectif côté réception : %1 - + Codec Codec - + Audio codec Codec audio - + The currently used audio codec and frequency: %1 Le codec audio et la fréquence actuellement utilisés : %1 - + Elapsed call time Durée écoulée de l'appel - + The duration in seconds the call has been active for: %1 La durée en secondes pendant laquelle l'appel a été actif : %1 - + Screen Écran - + Screensharing control Contrôle du partage d'écran - + Start sharing your screen Commencer le partage d'écran - + Camera Caméra - + Camera control Contrôle de la caméra - + Enable your camera Activer la caméra - + Resume Reprendre - + Hold En attente - + Resume call Reprendre l'appel - + Hold call Mettre l'appel en attente - + Update the call hold state Mettre à jour l'état de mise en attente de l'appel - + Micro Micro - + Input control Contrôle d'entrée - + Set the mute state of the current input device Définir l'état de silence du périphérique d'entrée actuel - + Output Sortie - + Output control Contrôle de sortie - + Change the current output devices Modifier les périphériques de sortie actuels - + Accept call Accepter l'appel - + Hangup call Raccrocher @@ -825,25 +825,30 @@ CallerBigAvatar - + Caller name Nom de l'appelant - + is calling... appelle... - + Calling... Appel en cours... + + + In progress... + Établissement de la connexion... + CallsModel - + unknown number numéro inconnu @@ -1178,122 +1183,122 @@ Salle de conférence - + Share Partager - + Copy room name Copier le nom de la salle - + Copy room link Copier le lien de la salle - + Open in browser Ouvrir dans le navigateur - + Show phone number Afficher le numéro de téléphone - + Raise Lever la main - + Resume Reprendre - + Hold En attente - + View Vue - + Screen Écran - + Share window Partager la fenêtre - + Share screen Partager l'écran - + Camera Caméra - + Micro Micro - + Output Sortie - + More Plus - + Noise supression Suppression du bruit - + Toggle subtitles Activer/désactiver les sous-titres - + Toggle whiteboard Activer/désactiver le tableau blanc - + Video quality... Qualité vidéo... - + Set room password... Définir le mot de passe de la salle... - + Mute everyone Couper le son de tous - + Leave conference Quitter la conférence - + End conference for all Terminer la conférence pour tous @@ -1368,7 +1373,7 @@ Menu de l'application - + Close GOnnect window Fermer la fenêtre GOnnect @@ -1384,22 +1389,22 @@ DateEventManager - + Conference starting soon La conférence commence bientôt - + Appointment starting soon Le rendez-vous commence bientôt - + Join Rejoindre - + Open Ouvrir @@ -1959,12 +1964,12 @@ FirstAidButton - + Open first aid menu Ouvrir le menu premiers secours - + First Aid Premiers secours @@ -1972,7 +1977,7 @@ GonnectWindow - + Home Accueil @@ -1980,7 +1985,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2112,78 +2117,78 @@ HistoryWidget - + History Historique - - + + All Tous - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Sélecteur de type d'appel de l'historique - + Select the call type to filter by Sélectionner le type d'appel pour filtrer - + Currently selected call type Type d'appel actuellement sélectionné - + Incoming Entrant - + Outgoing Sortant - + Missed Manqués - + History call origin picker Sélecteur d'origine d'appel de l'historique - + Select the call origin to filter by Sélectionner l'origine de l'appel pour filtrer - + Currently selected call origin Origine de l'appel actuellement sélectionnée - + Hide history search Masquer la recherche dans l'historique - + Show history search Afficher la recherche dans l'historique @@ -2386,27 +2391,27 @@ Voulez-vous vraiment fermer cette fenêtre et terminer tous les appels en cours ? - + Please enter the password for the SIP account: Veuillez saisir le mot de passe du compte SIP : - + Registration failed Échec de l'enregistrement - + Registration failed with with status %1: %2 Échec de l'enregistrement avec le statut %1 : %2 - + Error Erreur - + Fatal Error Erreur fatale @@ -2414,114 +2419,114 @@ MainTabBar - - + + Home Domicile - + Conference Conférence - + No active conference Aucune conférence active - + No active call Aucun appel actif - + Move up Monter - + Move tab up Monter l'onglet - + Moves the currently selected tab up by one Déplace l'onglet actuellement sélectionné d'une position vers le haut - + Move down Descendre - + Move tab down Descendre l'onglet - + Moves the currently selected tab down by one Déplace l'onglet actuellement sélectionné d'une position vers le bas - + Edit Modifier - + Edit page Modifier la page - + Edit the currently selected dashboard page Modifier la page du tableau de bord actuellement sélectionnée - + Delete Supprimer - + Delete page Supprimer la page - + Delete the currently selected dashboard page Supprimer la page du tableau de bord actuellement sélectionnée - + Call Appel - + Selected tab Onglet sélectionné - + The currently selected tab L'onglet actuellement sélectionné - + Selected tab options Options de l'onglet sélectionné - + The settings of the currently selected tab Les paramètres de l'onglet actuellement sélectionné - - + + Settings Paramètres @@ -2734,7 +2739,7 @@ QObject - + There are %n active call(s). calls @@ -4020,27 +4025,32 @@ 'userUri' est obligatoire - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' n'est pas une URI SIP valide : %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' n'est pas une URI SIP valide : %1 - + 'registrarUri' is required 'registrarUri' est obligatoire - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contient une entrée URI SIP invalide : %1 - + Failed to create %1: %2 Échec de la création de %1 : %2 - + Failed to persist SIP credentials: %1 Échec de la persistance des informations d'identification SIP : %1 @@ -4056,12 +4066,12 @@ SIPCall - + Active call with %1 Appel actif avec %1 - + Hang up Raccrocher @@ -4104,7 +4114,7 @@ SIPManager - + New Identity Nouvelle identité @@ -4128,14 +4138,6 @@ Le fichier source %1 n'existe pas - - SearchBox - - - Search number - Rechercher un numéro - - SearchCategoryItem @@ -4172,49 +4174,6 @@ Fichiers - - SearchDial - - - Select number - Sélectionner un numéro - - - - Activate search field - Activer le champ de recherche - - - - Number or contact - Numéro ou contact - - - - Clear search field - Effacer le champ de recherche - - - - Default - Par défaut - - - - Auto - Auto - - - - Preferred identity - Identité préférée - - - - Select the preferred identity for outgoing calls - Sélectionner l'identité préférée pour les appels sortants - - SearchField @@ -4223,7 +4182,7 @@ Rechercher des contacts ou des noms de salle... - + Clear search field Effacer le champ de recherche @@ -4275,52 +4234,52 @@ SearchResultPopup - + Search filter and identity selection Filtre de recherche et sélection d'identité - + Select search filter to be applied, as well as the outgoing identity Sélectionner le filtre de recherche à appliquer, ainsi que l'identité sortante - + Outgoing identity Identité sortante - + Search results Résultats de recherche - + All search results will be listed here in their respective categories Tous les résultats de recherche seront répertoriés ici dans leurs catégories respectives - + Direct dial Numérotation directe - + Call "%1" Appeler « %1 » - + Open room "%1" Ouvrir la salle « %1 » - + History Historique - + Contacts Contacts @@ -4373,22 +4332,22 @@ Jeu de couleurs - + System default Valeur par défaut du système - + Light Clair - + Dark Sombre - + Reload contacts from LDAP Recharger les contacts depuis LDAP @@ -4418,13 +4377,13 @@ Identité préférée standard - - + + Default Par défaut - + Auto Auto @@ -4461,12 +4420,12 @@ Utiliser la décoration de fenêtre personnalisée - + Theme selection box Zone de sélection du thème - + Select the UI theme Sélectionner le thème de l'interface @@ -4476,14 +4435,14 @@ Option de thème actuellement sélectionnée - - + + Prefererred identity selection Sélection de l'identité préférée - - + + Select the preferred identity Sélectionner l'identité préférée @@ -4523,119 +4482,126 @@ Périphérique d'entrée - + Input device selection Sélection du périphérique d'entrée - + Select the input device to be used Sélectionner le périphérique d'entrée à utiliser - + Currently selected input option Option d'entrée actuellement sélectionnée - + Output device Périphérique de sortie - + Output device selection Sélection du périphérique de sortie - + Select the output device to be used Sélectionner le périphérique de sortie à utiliser - + Currently selected output option Option de sortie actuellement sélectionnée - + Output device for ring tone Périphérique de sortie pour la sonnerie - + Currently selected ring output option Option de sortie de sonnerie actuellement sélectionnée - + Ring tone Sonnerie - + Reset ring tone Réinitialiser la sonnerie - + Reset the ring tone to its default option Réinitialiser la sonnerie à son option par défaut - + Pick ring tone Choisir une sonnerie - + Select the ring tone you want to use for incoming calls Sélectionner la sonnerie à utiliser pour les appels entrants - - + + Currently set to: Actuellement défini sur : - + Ring tone volume Volume de la sonnerie - - + + Adjust %1 Régler %1 - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Pause entre les sonneries [s] - + + %1 s + Label for showing seconds + %1 s + + + Debugging Débogage - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Utilisez ce bouton pour démarrer une session de débogage. L'application redémarrera et commencera à enregistrer des informations supplémentaires utiles au débogage. Pendant cette session, revenez ici pour télécharger les informations. Une session de débogage est limitée à 5 minutes, après quoi l'application redémarrera automatiquement en mode normal. - + Start debug run (restart app) Démarrer la session de débogage (redémarrer l'application) - + Download debug information Télécharger les informations de débogage @@ -4850,27 +4816,27 @@ StateManager - + Show dial window and focus search field Afficher la fenêtre de numérotation et activer le champ de recherche - + End all calls Terminer tous les appels - + Redial last outgoing call Recomposer le dernier numéro appelé - + Toggle hold Basculer la mise en attente - + Phone calls are active Des appels téléphoniques sont actifs @@ -4878,48 +4844,48 @@ SystemTrayMenu - - + + Dial... Numéroter... - + Not registered... Non enregistré... - + Settings Paramètres - + About À propos - + Quit Quitter - + End conference Terminer la conférence - + Call with %1 has ended L'appel avec %1 est terminé - + Accept call with %1 Accepter l'appel de %1 - + Hang up call with %1 Raccrocher l'appel avec %1 @@ -5048,6 +5014,36 @@ Audio Files (%1) Fichiers audio (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Écouter les messages du répondeur + + + + %n new voice mail(s) + + %n nouveau message + %n nouveaux messages + + + + + %n old voice mail(s) + + %n ancien message + %n anciennes actualités + + VolumePopup diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts index 7b65ad1d..bc5b1524 100644 --- a/i18n/gonnect_it.ts +++ b/i18n/gonnect_it.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 Impossibile salvare le credenziali della rubrica: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Ingresso predefinito - + Default output Uscita predefinita @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Iniziali di questo contatto @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Griglia di base della pagina del pannello - + Canvas for editable dashboard pages Area di disegno per le pagine del pannello modificabili - + Add widgets Aggiungi widget @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Trascina widget - + Change the position of the widget Cambia la posizione del widget - + Remove widget Rimuovi widget - + Remove the currently selected widget from the dashboard Rimuovi il widget attualmente selezionato dal pannello - + Resize widget Ridimensiona widget - + Resize the widget according to the mouse direction Ridimensiona il widget in base alla direzione del mouse @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Bordo di trascinamento - + Top left drag border for window resize operations Bordo di trascinamento in alto a sinistra per ridimensionare la finestra - + Top drag border for window resize operations Bordo di trascinamento in alto per ridimensionare la finestra - + Top right border for window resize operations Bordo in alto a destra per ridimensionare la finestra - + Right drag border for window resize operations Bordo di trascinamento a destra per ridimensionare la finestra - + Bottom right drag border for window resize operations Bordo di trascinamento in basso a destra per ridimensionare la finestra - + Bottom drag border for window resize operations Bordo di trascinamento in basso per ridimensionare la finestra - + Bottom left drag border for window resize operations Bordo di trascinamento in basso a sinistra per ridimensionare la finestra - + Left drag border for window resize operations Bordo di trascinamento a sinistra per ridimensionare la finestra @@ -409,12 +409,12 @@ Call - + Conference Conferenza - + Drag bar Barra di trascinamento @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 kHz - + Transmit Trasmetti - + Call security level Livello di sicurezza della chiamata - + Security level of the ongoing call Livello di sicurezza della chiamata in corso - + Call security details Dettagli di sicurezza della chiamata - + Detailed call security status: %1 / %2 Stato dettagliato della sicurezza della chiamata: %1 / %2 - + signaling encrypted segnalazione cifrata - + signaling unencrypted segnalazione non cifrata - + media encrypted media cifrati - + media unencrypted media non cifrati - + Call quality Qualità della chiamata - + Quality of the ongoing call Qualità della chiamata in corso - + Transmission statistics Statistiche di trasmissione - - + + Call quality metrics Metriche di qualità della chiamata - - + + MOS MOS - - + + Mean opinion score Punteggio di opinione medio (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Metrica numerica che valuta la qualità vocale dal lato trasmissione: %1 - - + + Packet loss Perdita di pacchetti - + %1% of packets lost in transmission %1% dei pacchetti persi in trasmissione - - + + Jitter Jitter - + Amount of transmission side jitter: %1 Jitter dal lato trasmissione: %1 - - + + Effective delay Ritardo effettivo - + Effective transmission side call delay: %1 Ritardo effettivo della chiamata dal lato trasmissione: %1 - + Receiver statistics Statistiche di ricezione - + Receive Ricevi - + Numerical metric assessing receiver-side voice/video call quality: %1 Metrica numerica che valuta la qualità voce/video dal lato ricevitore: %1 - + %1% of packets lost in receival %1% dei pacchetti persi in ricezione - + Amount of receiver side jitter: %1 Jitter dal lato ricevitore: %1 - + Effective receiver side call delay: %1 Ritardo effettivo della chiamata dal lato ricevitore: %1 - + Codec Codec - + Audio codec Codec audio - + The currently used audio codec and frequency: %1 Il codec audio e la frequenza attualmente in uso: %1 - + Elapsed call time Durata trascorsa della chiamata - + The duration in seconds the call has been active for: %1 La durata in secondi in cui la chiamata è stata attiva: %1 - + Screen Schermo - + Screensharing control Controllo condivisione schermo - + Start sharing your screen Avvia la condivisione dello schermo - + Camera Fotocamera - + Camera control Controllo fotocamera - + Enable your camera Attiva la fotocamera - + Resume Riprendi - + Hold In attesa - + Resume call Riprendi la chiamata - + Hold call Metti in attesa la chiamata - + Update the call hold state Aggiorna lo stato di attesa della chiamata - + Micro Microfono - + Input control Controllo ingresso - + Set the mute state of the current input device Imposta lo stato di silenziamento del dispositivo di ingresso corrente - + Output Uscita - + Output control Controllo uscita - + Change the current output devices Cambia i dispositivi di uscita correnti - + Accept call Accetta chiamata - + Hangup call Termina chiamata @@ -825,25 +825,30 @@ CallerBigAvatar - + Caller name Nome del chiamante - + is calling... sta chiamando... - + Calling... Chiamata in corso... + + + In progress... + Connessione in corso... + CallsModel - + unknown number numero sconosciuto @@ -1178,122 +1183,122 @@ Stanza conferenza - + Share Condividi - + Copy room name Copia nome stanza - + Copy room link Copia link stanza - + Open in browser Apri nel browser - + Show phone number Mostra numero di telefono - + Raise Alza la mano - + Resume Riprendi - + Hold In attesa - + View Vista - + Screen Schermo - + Share window Condividi finestra - + Share screen Condividi schermo - + Camera Fotocamera - + Micro Microfono - + Output Uscita - + More Altro - + Noise supression Soppressione del rumore - + Toggle subtitles Attiva/disattiva sottotitoli - + Toggle whiteboard Attiva/disattiva lavagna - + Video quality... Qualità video... - + Set room password... Imposta password stanza... - + Mute everyone Silenzia tutti - + Leave conference Abbandona la conferenza - + End conference for all Termina la conferenza per tutti @@ -1368,7 +1373,7 @@ Menu applicazione - + Close GOnnect window Chiudi la finestra GOnnect @@ -1384,22 +1389,22 @@ DateEventManager - + Conference starting soon La conferenza inizia a breve - + Appointment starting soon L'appuntamento inizia a breve - + Join Partecipa - + Open Apri @@ -1959,12 +1964,12 @@ FirstAidButton - + Open first aid menu Apri il menu pronto soccorso - + First Aid Pronto soccorso @@ -1972,7 +1977,7 @@ GonnectWindow - + Home Home @@ -1980,7 +1985,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2112,78 +2117,78 @@ HistoryWidget - + History Cronologia - - + + All Tutti - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Selettore tipo di chiamata cronologia - + Select the call type to filter by Seleziona il tipo di chiamata per filtrare - + Currently selected call type Tipo di chiamata attualmente selezionato - + Incoming In entrata - + Outgoing In uscita - + Missed Perse - + History call origin picker Selettore origine chiamata cronologia - + Select the call origin to filter by Seleziona l'origine della chiamata per filtrare - + Currently selected call origin Origine della chiamata attualmente selezionata - + Hide history search Nascondi ricerca cronologia - + Show history search Mostra ricerca cronologia @@ -2386,27 +2391,27 @@ Vuoi davvero chiudere questa finestra e terminare tutte le chiamate in corso? - + Please enter the password for the SIP account: Inserisci la password per l'account SIP: - + Registration failed Registrazione non riuscita - + Registration failed with with status %1: %2 Registrazione non riuscita con stato %1: %2 - + Error Errore - + Fatal Error Errore fatale @@ -2414,114 +2419,114 @@ MainTabBar - + Selected tab Scheda selezionata - + The currently selected tab La scheda attualmente selezionata - + Selected tab options Opzioni scheda selezionata - + The settings of the currently selected tab Le impostazioni della scheda attualmente selezionata - - + + Home Casa - + Conference Conferenza - + No active conference Nessuna conferenza attiva - + Call Chiamata - + No active call Nessuna chiamata attiva - - + + Settings Impostazioni - + Move up Sposta su - + Move tab up Sposta scheda su - + Moves the currently selected tab up by one Sposta la scheda attualmente selezionata di una posizione verso l'alto - + Move down Sposta giù - + Move tab down Sposta scheda giù - + Moves the currently selected tab down by one Sposta la scheda attualmente selezionata di una posizione verso il basso - + Edit Modifica - + Edit page Modifica pagina - + Edit the currently selected dashboard page Modifica la pagina del pannello attualmente selezionata - + Delete Elimina - + Delete page Elimina pagina - + Delete the currently selected dashboard page Elimina la pagina del pannello attualmente selezionata @@ -2734,7 +2739,7 @@ QObject - + There are %n active call(s). calls @@ -4020,27 +4025,32 @@ 'userUri' è obbligatorio - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' non è un URI SIP valido: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' non è un URI SIP valido: %1 - + 'registrarUri' is required 'registrarUri' è obbligatorio - + 'proxies' contains invalid SIP URI entry: %1 'proxies' contiene una voce URI SIP non valida: %1 - + Failed to create %1: %2 Impossibile creare %1: %2 - + Failed to persist SIP credentials: %1 Impossibile mantenere le credenziali SIP: %1 @@ -4056,12 +4066,12 @@ SIPCall - + Active call with %1 Chiamata attiva con %1 - + Hang up Riaggancia @@ -4104,7 +4114,7 @@ SIPManager - + New Identity Nuova identità @@ -4128,14 +4138,6 @@ Il file sorgente %1 non esiste - - SearchBox - - - Search number - Cerca numero - - SearchCategoryItem @@ -4172,49 +4174,6 @@ File - - SearchDial - - - Select number - Seleziona numero - - - - Activate search field - Attiva campo di ricerca - - - - Number or contact - Numero o contatto - - - - Clear search field - Cancella campo di ricerca - - - - Default - Predefinito - - - - Auto - Auto - - - - Preferred identity - Identità preferita - - - - Select the preferred identity for outgoing calls - Seleziona l'identità preferita per le chiamate in uscita - - SearchField @@ -4223,7 +4182,7 @@ Cerca contatti o nomi stanza... - + Clear search field Cancella campo di ricerca @@ -4275,52 +4234,52 @@ SearchResultPopup - + Search filter and identity selection Filtro di ricerca e selezione identità - + Select search filter to be applied, as well as the outgoing identity Seleziona il filtro di ricerca da applicare e l'identità in uscita - + Outgoing identity Identità in uscita - + Search results Risultati di ricerca - + All search results will be listed here in their respective categories Tutti i risultati di ricerca saranno elencati qui nelle rispettive categorie - + Direct dial Composizione diretta - + Call "%1" Chiama "%1" - + Open room "%1" Apri stanza "%1" - + History Cronologia - + Contacts Contatti @@ -4383,27 +4342,27 @@ Schema colori - + System default Predefinito di sistema - + Light Chiaro - + Dark Scuro - + Theme selection box Casella di selezione tema - + Select the UI theme Seleziona il tema dell'interfaccia @@ -4433,26 +4392,26 @@ Identità preferita standard - - + + Default Predefinito - + Auto Auto - - + + Prefererred identity selection Selezione identità preferita - - + + Select the preferred identity Seleziona l'identità preferita @@ -4518,124 +4477,131 @@ Dispositivo di ingresso - + Input device selection Selezione dispositivo di ingresso - + Select the input device to be used Seleziona il dispositivo di ingresso da utilizzare - + Currently selected input option Opzione di ingresso attualmente selezionata - + Output device Dispositivo di uscita - + Output device selection Selezione dispositivo di uscita - + Select the output device to be used Seleziona il dispositivo di uscita da utilizzare - + Currently selected output option Opzione di uscita attualmente selezionata - + Output device for ring tone Dispositivo di uscita per la suoneria - + Currently selected ring output option Opzione di uscita suoneria attualmente selezionata - + Ring tone Suoneria - + Reset ring tone Reimposta suoneria - + Reset the ring tone to its default option Reimposta la suoneria alla sua opzione predefinita - + Pick ring tone Scegli suoneria - + Select the ring tone you want to use for incoming calls Seleziona la suoneria da utilizzare per le chiamate in entrata - - + + Currently set to: Attualmente impostata su: - + Ring tone volume Volume suoneria - - + + Adjust %1 Regola %1 - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Pausa tra le suonerie [s] - + + %1 s + Label for showing seconds + %1 s + + + Debugging Debug - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Usa questo pulsante per avviare una sessione di debug. L'app si riavvierà e inizierà a registrare informazioni aggiuntive utili per il debug. Durante questa sessione, torna qui per scaricare le informazioni. Una sessione di debug è limitata a 5 minuti, dopodiché l'app si riavvierà automaticamente in modalità normale. - + Start debug run (restart app) Avvia sessione di debug (riavvia app) - + Download debug information Scarica informazioni di debug - + Reload contacts from LDAP Ricarica contatti da LDAP @@ -4850,27 +4816,27 @@ StateManager - + Show dial window and focus search field Mostra la finestra di composizione e metti a fuoco il campo di ricerca - + End all calls Termina tutte le chiamate - + Redial last outgoing call Ripeti l'ultima chiamata effettuata - + Toggle hold Commuta attesa - + Phone calls are active Ci sono chiamate telefoniche attive @@ -4878,48 +4844,48 @@ SystemTrayMenu - - + + Dial... Componi... - + Not registered... Non registrato... - + Settings Impostazioni - + About Informazioni - + Quit Esci - + End conference Termina conferenza - + Call with %1 has ended La chiamata con %1 è terminata - + Accept call with %1 Accetta chiamata da %1 - + Hang up call with %1 Riaggancia chiamata con %1 @@ -5048,6 +5014,36 @@ Audio Files (%1) File audio (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Ascoltare la segreteria telefonica + + + + %n new voice mail(s) + + %n nuovo messaggio + %n nuovi messaggi + + + + + %n old voice mail(s) + + %n messaggio vecchio + %n notizie arretrate + + VolumePopup diff --git a/i18n/gonnect_ru.ts b/i18n/gonnect_ru.ts index aaffea25..2af7d0e0 100644 --- a/i18n/gonnect_ru.ts +++ b/i18n/gonnect_ru.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 Не удалось сохранить учетные данные адресной книги: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Вход по умолчанию - + Default output Выход по умолчанию @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Инициалы контакта @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Базовая сетка страницы панели управления - + Canvas for editable dashboard pages Холст для редактируемых страниц панели управления - + Add widgets Добавить виджеты @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Перетащить виджет - + Change the position of the widget Изменить положение виджета - + Remove widget Удалить виджет - + Remove the currently selected widget from the dashboard Удалить выбранный виджет с панели управления - + Resize widget Изменить размер виджета - + Resize the widget according to the mouse direction Изменить размер виджета по направлению мыши @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Граница перетаскивания - + Top left drag border for window resize operations Граница перетаскивания вверху слева - + Top drag border for window resize operations Верхняя граница изменения размера окна - + Top right border for window resize operations Граница вверху справа - + Right drag border for window resize operations Правая граница изменения размера окна - + Bottom right drag border for window resize operations Граница внизу справа - + Bottom drag border for window resize operations Нижняя граница изменения размера окна - + Bottom left drag border for window resize operations Граница внизу слева - + Left drag border for window resize operations Левая граница изменения размера окна @@ -409,12 +409,12 @@ Call - + Conference Конференция - + Drag bar Панель перетаскивания @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 кГц - + Transmit Передача - + Call security level Уровень безопасности звонка - + Security level of the ongoing call Уровень безопасности текущего звонка - + Call security details Сведения о безопасности звонка - + Detailed call security status: %1 / %2 Подробный статус безопасности: %1 / %2 - + signaling encrypted сигнализация зашифрована - + signaling unencrypted сигнализация не зашифрована - + media encrypted медиа зашифровано - + media unencrypted медиа не зашифровано - + Call quality Качество звонка - + Quality of the ongoing call Качество текущего звонка - + Transmission statistics Статистика передачи - - + + Call quality metrics Метрики качества звонка - - + + MOS MOS - - + + Mean opinion score Средняя оценка качества (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Числовая метрика качества голосовой связи на стороне передачи: %1 - - + + Packet loss Потеря пакетов - + %1% of packets lost in transmission %1% пакетов потеряно при передаче - - + + Jitter Джиттер - + Amount of transmission side jitter: %1 Джиттер на стороне передачи: %1 - - + + Effective delay Эффективная задержка - + Effective transmission side call delay: %1 Эффективная задержка на стороне передачи: %1 - + Receiver statistics Статистика приёма - + Receive Приём - + Numerical metric assessing receiver-side voice/video call quality: %1 Числовая метрика качества на стороне приёма: %1 - + %1% of packets lost in receival %1% пакетов потеряно при приёме - + Amount of receiver side jitter: %1 Джиттер на стороне приёма: %1 - + Effective receiver side call delay: %1 Эффективная задержка на стороне приёма: %1 - + Codec Кодек - + Audio codec Аудиокодек - + The currently used audio codec and frequency: %1 Текущий аудиокодек и частота: %1 - + Elapsed call time Прошедшее время звонка - + The duration in seconds the call has been active for: %1 Продолжительность звонка в секундах: %1 - + Screen Экран - + Screensharing control Управление демонстрацией экрана - + Start sharing your screen Начать демонстрацию экрана - + Camera Камера - + Camera control Управление камерой - + Enable your camera Включить камеру - + Resume Продолжить - + Hold Удержание - + Resume call Продолжить звонок - + Hold call Удержать звонок - + Update the call hold state Обновить состояние удержания - + Micro Микрофон - + Input control Управление входом - + Set the mute state of the current input device Установить режим отключения звука устройства ввода - + Output Выход - + Output control Управление выходом - + Change the current output devices Изменить текущие устройства вывода - + Accept call Принять звонок - + Hangup call Завершить звонок @@ -826,25 +826,30 @@ CallerBigAvatar - + Caller name Имя звонящего - + is calling... звонит... - + Calling... Вызов... + + + In progress... + Установка соединения... + CallsModel - + unknown number неизвестный номер @@ -1180,122 +1185,122 @@ Комната конференции - + Share Поделиться - + Copy room name Скопировать имя комнаты - + Copy room link Скопировать ссылку на комнату - + Open in browser Открыть в браузере - + Show phone number Показать номер телефона - + Raise Поднять руку - + Resume Продолжить - + Hold Удержание - + View Вид - + Screen Экран - + Share window Поделиться окном - + Share screen Поделиться экраном - + Camera Камера - + Output Выход - + More Ещё - + Noise supression Шумоподавление - + Toggle subtitles Включить/выключить субтитры - + Toggle whiteboard Включить/выключить доску - + Video quality... Качество видео... - + Set room password... Установить пароль комнаты... - + Mute everyone Выключить звук у всех - + Leave conference Покинуть конференцию - + End conference for all Завершить конференцию для всех - + Micro Микрофон @@ -1370,7 +1375,7 @@ Меню приложения - + Close GOnnect window Закрыть окно GOnnect @@ -1386,22 +1391,22 @@ DateEventManager - + Conference starting soon Конференция скоро начнётся - + Appointment starting soon Встреча скоро начнётся - + Join Присоединиться - + Open Открыть @@ -1961,12 +1966,12 @@ FirstAidButton - + Open first aid menu Открыть меню экстренной помощи - + First Aid Экстренная помощь @@ -1974,7 +1979,7 @@ GonnectWindow - + Home Домашний @@ -1982,7 +1987,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2114,78 +2119,78 @@ HistoryWidget - + History История - - + + All Все - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Выбор типа звонка в истории - + Select the call type to filter by Выбрать тип звонка для фильтрации - + Currently selected call type Текущий тип звонка - + Incoming Входящий - + Outgoing Исходящий - + Missed Пропущенные - + History call origin picker Выбор направления звонка в истории - + Select the call origin to filter by Выбрать направление звонка для фильтрации - + Currently selected call origin Текущее направление звонка - + Hide history search Скрыть поиск в истории - + Show history search Показать поиск в истории @@ -2372,7 +2377,7 @@ Введите ключ восстановления для %1: - + Please enter the password for the SIP account: Введите пароль для SIP-аккаунта: @@ -2393,22 +2398,22 @@ Закрыть окно и завершить все активные звонки? - + Registration failed Регистрация не удалась - + Registration failed with with status %1: %2 Регистрация не удалась со статусом %1: %2 - + Error Ошибка - + Fatal Error Критическая ошибка @@ -2416,114 +2421,114 @@ MainTabBar - - + + Home Домашний - + Conference Конференция - + No active conference Нет активной конференции - + No active call Нет активного звонка - + Move up Переместить вверх - + Move tab up Переместить вкладку вверх - + Moves the currently selected tab up by one Перемещает выбранную вкладку вверх на одну позицию - + Move down Переместить вниз - + Move tab down Переместить вкладку вниз - + Moves the currently selected tab down by one Перемещает выбранную вкладку вниз на одну позицию - + Edit Редактировать - + Edit page Редактировать страницу - + Edit the currently selected dashboard page Редактировать выбранную страницу панели - + Delete Удалить - + Delete page Удалить страницу - + Delete the currently selected dashboard page Удалить выбранную страницу панели - + Call Звонок - + Selected tab Выбранная вкладка - + The currently selected tab Текущая выбранная вкладка - + Selected tab options Параметры выбранной вкладки - + The settings of the currently selected tab Настройки текущей выбранной вкладки - - + + Settings Настройки @@ -3961,7 +3966,7 @@ Зимбабве - + There are %n active call(s). calls @@ -4023,27 +4028,32 @@ 'userUri' обязателен - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' не является допустимым SIP URI: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' не является допустимым SIP URI: %1 - + 'registrarUri' is required 'registrarUri' обязателен - + 'proxies' contains invalid SIP URI entry: %1 'proxies' содержит недопустимый SIP URI: %1 - + Failed to create %1: %2 Не удалось создать %1: %2 - + Failed to persist SIP credentials: %1 Не удалось сохранить учетные данные SIP: %1 @@ -4059,12 +4069,12 @@ SIPCall - + Active call with %1 Активный звонок с %1 - + Hang up Завершить @@ -4107,7 +4117,7 @@ SIPManager - + New Identity Новый идентификатор @@ -4131,14 +4141,6 @@ Исходный файл %1 не существует - - SearchBox - - - Search number - Поиск номера - - SearchCategoryItem @@ -4175,49 +4177,6 @@ Файлы - - SearchDial - - - Select number - Выбрать номер - - - - Activate search field - Активировать поле поиска - - - - Number or contact - Номер или контакт - - - - Clear search field - Очистить поле поиска - - - - Default - По умолчанию - - - - Auto - Авто - - - - Preferred identity - Предпочтительный идентификатор - - - - Select the preferred identity for outgoing calls - Выбрать предпочтительный идентификатор для исходящих звонков - - SearchField @@ -4226,7 +4185,7 @@ Поиск контактов или комнат... - + Clear search field Очистить поле поиска @@ -4278,52 +4237,52 @@ SearchResultPopup - + Search filter and identity selection Фильтр поиска и выбор идентификатора - + Select search filter to be applied, as well as the outgoing identity Выбрать фильтр поиска и исходящий идентификатор - + Outgoing identity Исходящий идентификатор - + Search results Результаты поиска - + All search results will be listed here in their respective categories Все результаты поиска будут перечислены по категориям - + Direct dial Прямой набор - + Call "%1" Позвонить «%1» - + Open room "%1" Открыть комнату «%1» - + History История - + Contacts Контакты @@ -4361,17 +4320,17 @@ Цветовая схема - + System default Системная по умолчанию - + Light Светлая - + Dark Тёмная @@ -4391,7 +4350,7 @@ Отключить синхронизацию состояния отключения USB-гарнитуры - + Reload contacts from LDAP Перезагрузить контакты из LDAP @@ -4427,12 +4386,12 @@ Использовать пользовательское оформление окна - + Theme selection box Поле выбора темы - + Select the UI theme Выбрать тему интерфейса @@ -4457,26 +4416,26 @@ Стандартный предпочтительный идентификатор - - + + Default По умолчанию - + Auto Авто - - + + Prefererred identity selection Выбор предпочтительного идентификатора - - + + Select the preferred identity Выбрать предпочтительный идентификатор @@ -4521,52 +4480,52 @@ Устройство ввода - + Input device selection Выбор устройства ввода - + Select the input device to be used Выбрать устройство ввода - + Currently selected input option Текущий параметр ввода - + Output device Устройство вывода - + Output device selection Выбор устройства вывода - + Select the output device to be used Выбрать устройство вывода - + Currently selected output option Текущий параметр вывода - + Output device for ring tone Устройство вывода для мелодии звонка - + Currently selected ring output option Текущий параметр вывода звонка - + Ring tone Мелодия звонка @@ -4576,69 +4535,76 @@ Использовать звонок USB-гарнитуры при наличии - + Reset ring tone Сбросить мелодию звонка - + Reset the ring tone to its default option Восстановить мелодию звонка по умолчанию - + Pick ring tone Выбрать мелодию звонка - + Select the ring tone you want to use for incoming calls Выбрать мелодию для входящих звонков - - + + Currently set to: Текущее значение: - + Ring tone volume Громкость мелодии звонка - - + + Adjust %1 Настроить %1 - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Пауза между звонками [с] - + + %1 s + Label for showing seconds + %1 c + + + Debugging Отладка - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Нажмите для запуска сеанса отладки. Приложение перезапустится и начнёт запись дополнительной информации. Сеанс ограничен 5 минутами. - + Start debug run (restart app) Начать отладку (перезапустить приложение) - + Download debug information Загрузить отладочную информацию @@ -4853,27 +4819,27 @@ StateManager - + Show dial window and focus search field Показать окно набора и перейти в поле поиска - + End all calls Завершить все звонки - + Redial last outgoing call Повторить последний исходящий звонок - + Toggle hold Переключить удержание - + Phone calls are active Звонки активны @@ -4881,48 +4847,48 @@ SystemTrayMenu - - + + Dial... Набор номера... - + Not registered... Не зарегистрирован... - + Settings Настройки - + About О программе - + Quit Выход - + End conference Завершить конференцию - + Call with %1 has ended Звонок с %1 завершён - + Hang up call with %1 Завершить звонок с %1 - + Accept call with %1 Принять звонок от %1 @@ -5054,6 +5020,38 @@ Audio Files (%1) Аудиофайлы (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Прослушивание автоответчика + + + + %n new voice mail(s) + + %n новое сообщение + %n новых сообщения + %n новых сообщений + + + + + %n old voice mail(s) + + %n старое сообщение + %n старых сообщения + %n старых сообщений + + VolumePopup diff --git a/i18n/gonnect_uk.ts b/i18n/gonnect_uk.ts index 7d8de9b9..aafcc047 100644 --- a/i18n/gonnect_uk.ts +++ b/i18n/gonnect_uk.ts @@ -88,7 +88,7 @@ AddressBookManager - + Failed to persist address book credentials: %1 Не вдалося зберегти облікові дані адресної книги: %1 @@ -230,12 +230,12 @@ AudioManager - + Default input Вхід за замовчуванням - + Default output Вихід за замовчуванням @@ -251,7 +251,7 @@ AvatarImage - + Initials of this contact Ініціали цього контакту @@ -267,17 +267,17 @@ BasePage - + Base dashboard page grid Базова сітка сторінки панелі управління - + Canvas for editable dashboard pages Полотно для редагованих сторінок панелі - + Add widgets Додати віджети @@ -285,32 +285,32 @@ BaseWidget - + Drag widget Перетягнути віджет - + Change the position of the widget Змінити положення віджета - + Remove widget Видалити віджет - + Remove the currently selected widget from the dashboard Видалити вибраний віджет з панелі - + Resize widget Змінити розмір віджета - + Resize the widget according to the mouse direction Змінити розмір віджета за напрямком миші @@ -318,54 +318,54 @@ BaseWindow - - - - - - - - + + + + + + + + Drag border Межа перетягування - + Top left drag border for window resize operations Ліва верхня межа для зміни розміру вікна - + Top drag border for window resize operations Верхня межа для зміни розміру вікна - + Top right border for window resize operations Права верхня межа для зміни розміру вікна - + Right drag border for window resize operations Права межа для зміни розміру вікна - + Bottom right drag border for window resize operations Права нижня межа для зміни розміру вікна - + Bottom drag border for window resize operations Нижня межа для зміни розміру вікна - + Bottom left drag border for window resize operations Ліва нижня межа для зміни розміру вікна - + Left drag border for window resize operations Ліва межа для зміни розміру вікна @@ -409,12 +409,12 @@ Call - + Conference Конференція - + Drag bar Панель перетягування @@ -422,273 +422,273 @@ CallButtonBar - + %1@%2 kHz %1@%2 кГц - + Transmit Передача - + Call security level Рівень безпеки дзвінка - + Security level of the ongoing call Рівень безпеки поточного дзвінка - + Call security details Деталі безпеки дзвінка - + Detailed call security status: %1 / %2 Детальний статус безпеки: %1 / %2 - + signaling encrypted сигналізацію зашифровано - + signaling unencrypted сигналізацію не зашифровано - + media encrypted медіа зашифровано - + media unencrypted медіа не зашифровано - + Call quality Якість дзвінка - + Quality of the ongoing call Якість поточного дзвінка - + Transmission statistics Статистика передачі - - + + Call quality metrics Метрики якості дзвінка - - + + MOS MOS - - + + Mean opinion score Середня оцінка якості (MOS) - + Numerical metric assessing transmission-side voice call quality: %1 Числова метрика якості голосового зв'язку з боку передачі: %1 - - + + Packet loss Втрата пакетів - + %1% of packets lost in transmission %1% пакетів втрачено при передачі - - + + Jitter Джитер - + Amount of transmission side jitter: %1 Джитер з боку передачі: %1 - - + + Effective delay Ефективна затримка - + Effective transmission side call delay: %1 Ефективна затримка з боку передачі: %1 - + Receiver statistics Статистика прийому - + Receive Прийом - + Numerical metric assessing receiver-side voice/video call quality: %1 Числова метрика якості з боку прийому: %1 - + %1% of packets lost in receival %1% пакетів втрачено при прийомі - + Amount of receiver side jitter: %1 Джитер з боку прийому: %1 - + Effective receiver side call delay: %1 Ефективна затримка з боку прийому: %1 - + Codec Кодек - + Audio codec Аудіокодек - + The currently used audio codec and frequency: %1 Поточний аудіокодек і частота: %1 - + Elapsed call time Тривалість дзвінка - + The duration in seconds the call has been active for: %1 Тривалість дзвінка у секундах: %1 - + Screen Екран - + Screensharing control Керування демонстрацією екрана - + Start sharing your screen Розпочати демонстрацію екрана - + Camera Камера - + Camera control Керування камерою - + Enable your camera Увімкнути камеру - + Resume Продовжити - + Hold Утримання - + Resume call Відновити дзвінок - + Hold call Утримати дзвінок - + Update the call hold state Оновити стан утримання - + Micro Мікрофон - + Input control Керування входом - + Set the mute state of the current input device Встановити стан вимкнення звуку пристрою вводу - + Output Вихід - + Output control Керування виходом - + Change the current output devices Змінити поточні пристрої виводу - + Accept call Прийняти дзвінок - + Hangup call Завершити дзвінок @@ -826,25 +826,30 @@ CallerBigAvatar - + Caller name Ім'я абонента - + is calling... дзвонить... - + Calling... Виклик... + + + In progress... + Встановлення з'єднання... + CallsModel - + unknown number невідомий номер @@ -1180,122 +1185,122 @@ Кімната конференції - + Share Поділитися - + Copy room name Скопіювати назву кімнати - + Copy room link Скопіювати посилання на кімнату - + Open in browser Відкрити у браузері - + Show phone number Показати номер телефону - + Raise Підняти руку - + Resume Продовжити - + Hold Утримання - + View Вигляд - + Screen Екран - + Share window Поділитися вікном - + Share screen Поділитися екраном - + Camera Камера - + Output Вихід - + More Ще - + Noise supression Шумоподавлення - + Toggle subtitles Увімкнути/вимкнути субтитри - + Toggle whiteboard Увімкнути/вимкнути дошку - + Video quality... Якість відео... - + Set room password... Встановити пароль кімнати... - + Mute everyone Вимкнути звук усіх - + Leave conference Покинути конференцію - + End conference for all Завершити конференцію для всіх - + Micro Мікрофон @@ -1370,7 +1375,7 @@ Меню застосунку - + Close GOnnect window Закрити вікно GOnnect @@ -1386,22 +1391,22 @@ DateEventManager - + Conference starting soon Конференція незабаром розпочнеться - + Appointment starting soon Зустріч незабаром розпочнеться - + Join Приєднатися - + Open Відкрити @@ -1961,12 +1966,12 @@ FirstAidButton - + Open first aid menu Відкрити меню першої допомоги - + First Aid Перша допомога @@ -1974,7 +1979,7 @@ GonnectWindow - + Home Домашній @@ -1982,7 +1987,7 @@ HeadsetDevice - + MMM dd dd MMM @@ -2114,78 +2119,78 @@ HistoryWidget - + History Історія - - + + All Усі - + SIP SIP - + Jitsi Meet Jitsi Meet - + History call type picker Вибір типу дзвінка в історії - + Select the call type to filter by Вибрати тип дзвінка для фільтрації - + Currently selected call type Поточний тип дзвінка - + Incoming Вхідний - + Outgoing Вихідний - + Missed Пропущені - + History call origin picker Вибір напрямку дзвінка в історії - + Select the call origin to filter by Вибрати напрямок дзвінка для фільтрації - + Currently selected call origin Поточний напрямок дзвінка - + Hide history search Приховати пошук в історії - + Show history search Показати пошук в історії @@ -2372,7 +2377,7 @@ Введіть ключ відновлення для %1: - + Please enter the password for the SIP account: Введіть пароль для SIP-акаунта: @@ -2393,22 +2398,22 @@ Закрити вікно та завершити всі активні дзвінки? - + Registration failed Реєстрація не вдалася - + Registration failed with with status %1: %2 Реєстрація не вдалася зі статусом %1: %2 - + Error Помилка - + Fatal Error Критична помилка @@ -2416,114 +2421,114 @@ MainTabBar - - + + Home Домашній - + Conference Конференція - + No active conference Немає активної конференції - + No active call Немає активного дзвінка - + Move up Перемістити вгору - + Move tab up Перемістити вкладку вгору - + Moves the currently selected tab up by one Переміщує вибрану вкладку вгору на одну позицію - + Move down Перемістити вниз - + Move tab down Перемістити вкладку вниз - + Moves the currently selected tab down by one Переміщує вибрану вкладку вниз на одну позицію - + Edit Редагувати - + Edit page Редагувати сторінку - + Edit the currently selected dashboard page Редагувати вибрану сторінку панелі - + Delete Видалити - + Delete page Видалити сторінку - + Delete the currently selected dashboard page Видалити вибрану сторінку панелі - + Call Дзвінок - + Selected tab Вибрана вкладка - + The currently selected tab Поточна вибрана вкладка - + Selected tab options Параметри вибраної вкладки - + The settings of the currently selected tab Налаштування поточної вибраної вкладки - - + + Settings Налаштування @@ -3961,7 +3966,7 @@ Зімбабве - + There are %n active call(s). calls @@ -4023,27 +4028,32 @@ 'userUri' обов'язковий - + + 'voiceMailUri' is no valid SIP URI: %1 + 'voiceMailUri' не є допустимим SIP URI: %1 + + + 'registrarUri' is no valid SIP URI: %1 'registrarUri' не є допустимим SIP URI: %1 - + 'registrarUri' is required 'registrarUri' обов'язковий - + 'proxies' contains invalid SIP URI entry: %1 'proxies' містить недопустимий SIP URI: %1 - + Failed to create %1: %2 Не вдалося створити %1: %2 - + Failed to persist SIP credentials: %1 Не вдалося зберегти облікові дані SIP: %1 @@ -4059,12 +4069,12 @@ SIPCall - + Active call with %1 Активний дзвінок з %1 - + Hang up Завершити @@ -4107,7 +4117,7 @@ SIPManager - + New Identity Новий ідентифікатор @@ -4131,14 +4141,6 @@ Вихідний файл %1 не існує - - SearchBox - - - Search number - Пошук номера - - SearchCategoryItem @@ -4175,49 +4177,6 @@ Файли - - SearchDial - - - Select number - Вибрати номер - - - - Activate search field - Активувати поле пошуку - - - - Number or contact - Номер або контакт - - - - Clear search field - Очистити поле пошуку - - - - Default - За замовчуванням - - - - Auto - Авто - - - - Preferred identity - Бажаний ідентифікатор - - - - Select the preferred identity for outgoing calls - Вибрати бажаний ідентифікатор для вихідних дзвінків - - SearchField @@ -4226,7 +4185,7 @@ Пошук контактів або кімнат... - + Clear search field Очистити поле пошуку @@ -4278,52 +4237,52 @@ SearchResultPopup - + Search filter and identity selection Фільтр пошуку та вибір ідентифікатора - + Select search filter to be applied, as well as the outgoing identity Вибрати фільтр пошуку та вихідний ідентифікатор - + Outgoing identity Вихідний ідентифікатор - + Search results Результати пошуку - + All search results will be listed here in their respective categories Усі результати пошуку будуть перераховані за категоріями - + Direct dial Прямий набір - + Call "%1" Зателефонувати «%1» - + Open room "%1" Відкрити кімнату «%1» - + History Історія - + Contacts Контакти @@ -4361,17 +4320,17 @@ Колірна схема - + System default Системна за замовчуванням - + Light Світла - + Dark Темна @@ -4391,7 +4350,7 @@ Вимкнути синхронізацію стану вимкнення USB-гарнітури - + Reload contacts from LDAP Перезавантажити контакти з LDAP @@ -4427,12 +4386,12 @@ Використовувати власне оформлення вікна - + Theme selection box Поле вибору теми - + Select the UI theme Вибрати тему інтерфейсу @@ -4457,26 +4416,26 @@ Стандартний бажаний ідентифікатор - - + + Default За замовчуванням - + Auto Авто - - + + Prefererred identity selection Вибір бажаного ідентифікатора - - + + Select the preferred identity Вибрати бажаний ідентифікатор @@ -4521,52 +4480,52 @@ Пристрій вводу - + Input device selection Вибір пристрою вводу - + Select the input device to be used Вибрати пристрій вводу - + Currently selected input option Поточний параметр вводу - + Output device Пристрій виводу - + Output device selection Вибір пристрою виводу - + Select the output device to be used Вибрати пристрій виводу - + Currently selected output option Поточний параметр виводу - + Output device for ring tone Пристрій виводу для мелодії дзвінка - + Currently selected ring output option Поточний параметр виводу дзвінка - + Ring tone Мелодія дзвінка @@ -4576,69 +4535,76 @@ Використовувати дзвінок USB-гарнітури при наявності - + Reset ring tone Скинути мелодію дзвінка - + Reset the ring tone to its default option Відновити стандартну мелодію дзвінка - + Pick ring tone Вибрати мелодію - + Select the ring tone you want to use for incoming calls Вибрати мелодію для вхідних дзвінків - - + + Currently set to: Поточне значення: - + Ring tone volume Гучність мелодії дзвінка - - + + Adjust %1 Налаштувати %1 - + %1 % + Label for showing percentage %1 % - + Pause between ring tones [s] Пауза між дзвінками [с] - + + %1 s + Label for showing seconds + %1 c + + + Debugging Налагодження - + Use this button to start a debug run. The App will restart and then begin to record additional information that can be useful for debugging purposes. During this run, come back here to download the information. A debug run is limited to 5 minutes, after which the App will automatically restart again in normal mode. Натисніть для запуску сеансу налагодження. Застосунок перезапуститься і почне записувати додаткову інформацію. Сеанс обмежено 5 хвилинами. - + Start debug run (restart app) Почати налагодження (перезапустити застосунок) - + Download debug information Завантажити інформацію налагодження @@ -4853,27 +4819,27 @@ StateManager - + Show dial window and focus search field Показати вікно набору та перейти до поля пошуку - + End all calls Завершити всі дзвінки - + Redial last outgoing call Повторити останній вихідний дзвінок - + Toggle hold Перемкнути утримання - + Phone calls are active Дзвінки активні @@ -4881,48 +4847,48 @@ SystemTrayMenu - - + + Dial... Набір номера... - + Not registered... Не зареєстровано... - + Settings Налаштування - + About Про програму - + Quit Вийти - + End conference Завершити конференцію - + Call with %1 has ended Дзвінок з %1 завершено - + Hang up call with %1 Завершити дзвінок з %1 - + Accept call with %1 Прийняти дзвінок від %1 @@ -5054,6 +5020,38 @@ Audio Files (%1) Аудіофайли (%1) + + + QT_CULTURAL_SPHERE + QGuiApplication + + + + + VoiceMailField + + + Listen to voicemail + Прослуховування автовідповідача + + + + %n new voice mail(s) + + %n нове повідомлення + %n нові повідомлення + %n нових повідомлень + + + + + %n old voice mail(s) + + %n старе повідомлення + %n старі повідомлення + %n старих повідомлень + + VolumePopup diff --git a/resources/artwork/ISO_7010_E004.svg b/resources/artwork/ISO_7010_E004.svg new file mode 100644 index 00000000..e475e642 --- /dev/null +++ b/resources/artwork/ISO_7010_E004.svg @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/resources/artwork/ISO_7010_E004_C.svg b/resources/artwork/ISO_7010_E004_C.svg new file mode 100644 index 00000000..45cbec83 --- /dev/null +++ b/resources/artwork/ISO_7010_E004_C.svg @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/resources/artwork/icons/brightness-high.svg b/resources/artwork/icons/brightness-high.svg new file mode 100644 index 00000000..fda97b84 --- /dev/null +++ b/resources/artwork/icons/brightness-high.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/artwork/icons/call-voicemail.svg b/resources/artwork/icons/call-voicemail.svg new file mode 100644 index 00000000..4249d898 --- /dev/null +++ b/resources/artwork/icons/call-voicemail.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/resources/artwork/icons/color-management.svg b/resources/artwork/icons/color-management.svg new file mode 100644 index 00000000..42b25fd2 --- /dev/null +++ b/resources/artwork/icons/color-management.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/artwork/icons/videolight-off.svg b/resources/artwork/icons/videolight-off.svg new file mode 100644 index 00000000..1a66c485 --- /dev/null +++ b/resources/artwork/icons/videolight-off.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/resources/artwork/icons/videolight-on.svg b/resources/artwork/icons/videolight-on.svg new file mode 100644 index 00000000..f1d1d8f9 --- /dev/null +++ b/resources/artwork/icons/videolight-on.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/resources/artwork/jitsi.svg b/resources/artwork/jitsi.svg deleted file mode 100644 index 55af2342..00000000 --- a/resources/artwork/jitsi.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/conan/recipes/pjproject/all/conanfile.py b/resources/conan/recipes/pjproject/all/conanfile.py index 5037ff0d..cfe7a4a4 100644 --- a/resources/conan/recipes/pjproject/all/conanfile.py +++ b/resources/conan/recipes/pjproject/all/conanfile.py @@ -158,6 +158,7 @@ def makeSiteConfig(self): with open(os.path.join(self.build_folder, 'pjlib/include/pj/config_site.h'), 'a') as file: file.write('\n\n#define PJ_HAS_IPV6 1\n') file.write('\n\n#define PJMEDIA_HAS_RTCP_XR 1\n') + file.write('\n\n#define PJSIP_MAX_PKT_LEN 8192\n') if self.settings.os == "Macos": file.write('\n\n#define PJ_HAS_SSL_SOCK 1\n') diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index 23a50fd2..3f99fa18 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -414,6 +414,11 @@ port=5061 ## Multiplex RTCP information into existing RTP stream. #rtcpMuxEnabled=false +## SIP URI to call for the ordinary voice mail box. There is a fallback to the "Message-Account" field of the +## MWI info message of the SIP server. +## default: "" +#voiceMailUri= + #[auth0] ## The authentication scheme (e.g. “digest”). diff --git a/src/Application.cpp b/src/Application.cpp index 7ad81276..7d687d62 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -145,13 +145,6 @@ void Application::initializeSIP() auto &sm = SIPManager::instance(); sm.initialize(); - auto &cm = SIPCallManager::instance(); - connect(&cm, &SIPCallManager::missedCallsChanged, this, [this]() { - auto count = SIPCallManager::instance().missedCalls(); - setBadgeNumber(count); - SystemTrayMenu::instance().setBadgeNumber(count); - }); - m_initialized = true; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 64180469..c04b4f9f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -343,6 +343,7 @@ qt_add_qml_module(gonnect ui/components/controls/TabBar.qml ui/components/controls/TabButton.qml ui/components/controls/TextField.qml + ui/components/controls/VoiceMailField.qml ui/components/dialogs/CredentialsDialog.qml ui/components/dialogs/BaseDialog.qml @@ -371,7 +372,7 @@ qt_add_qml_module(gonnect ui/components/popups/Menu.qml ui/components/popups/SearchResultPopup.qml ui/components/popups/VideoDeviceMenu.qml - ui/components/popups/VolumePopup.qml + ui/components/popups/StreamingLightPopup.qml ui/components/search/IdentitySelector.qml ui/components/search/KeyNavigator.qml diff --git a/src/ICallState.h b/src/ICallState.h index ad563d42..9dc581ee 100644 --- a/src/ICallState.h +++ b/src/ICallState.h @@ -24,7 +24,8 @@ class ICallState : public QObject OnHold = 1 << 5, SharingScreen = 1 << 6, Migrating = 1 << 7, - KnockingIncoming = 1 << 8 + KnockingIncoming = 1 << 8, + InProgress = 1 << 9, }; Q_ENUM(State) Q_DECLARE_FLAGS(States, State) diff --git a/src/icons.qrc.in b/src/icons.qrc.in index 27049847..267aa81e 100644 --- a/src/icons.qrc.in +++ b/src/icons.qrc.in @@ -12,12 +12,12 @@ @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_light_note.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_line_note.svg @PROJECT_SOURCE_DIR@/resources/artwork/gonnect_ring_note.svg - @PROJECT_SOURCE_DIR@/resources/artwork/jitsi.svg + @PROJECT_SOURCE_DIR@/resources/artwork/ISO_7010_E004.svg + @PROJECT_SOURCE_DIR@/resources/artwork/ISO_7010_E004_C.svg @ICONS@ @PROJECT_SOURCE_DIR@/resources/artwork/gonnect.svg - @PROJECT_SOURCE_DIR@/resources/artwork/jitsi.svg @ICONS_DARK@ diff --git a/src/main.cpp b/src/main.cpp index c32c341d..31bb8844 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,18 +60,23 @@ int main(int argc, char *argv[]) // Assemble the Qt web engine flags QStringList chromiumFlags; + auto webenginePresetEnv = qgetenv("GONNECT_QTWEBENGINE_CHROMIUM_FLAGS"); + if (webenginePresetEnv.isEmpty()) { #if QT_VERSION < QT_VERSION_CHECK(6, 10, 0) - chromiumFlags.push_back("--use-fake-ui-for-media-stream"); + chromiumFlags.push_back("--use-fake-ui-for-media-stream"); #endif - if (GlobalInfo::instance().isWorkaroundActive(GlobalInfo::WorkaroundId::GOW_002)) { - chromiumFlags.push_back("--disable-gpu"); - } - if (GlobalInfo::instance().isWorkaroundActive(GlobalInfo::WorkaroundId::GOW_003)) { - chromiumFlags.push_back("--disable-renderer-accessibility"); - } + if (GlobalInfo::instance().isWorkaroundActive(GlobalInfo::WorkaroundId::GOW_002)) { + chromiumFlags.push_back("--disable-gpu"); + } + if (GlobalInfo::instance().isWorkaroundActive(GlobalInfo::WorkaroundId::GOW_003)) { + chromiumFlags.push_back("--disable-renderer-accessibility"); + } - if (chromiumFlags.size() > 0) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.join(" ").toStdString().c_str()); + if (chromiumFlags.size() > 0) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.join(" ").toStdString().c_str()); + } + } else { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", webenginePresetEnv); } int exitCode = 0; diff --git a/src/media/AudioManager.cpp b/src/media/AudioManager.cpp index 1835fac8..7fb01118 100644 --- a/src/media/AudioManager.cpp +++ b/src/media/AudioManager.cpp @@ -193,7 +193,8 @@ void AudioManager::setPlaybackDeviceId(const QString &id) if (m_playbackHash != id) { newPlaybackHash = id; if (dev->isDefault()) { - m_settings.remove(QString("audio%1/playback").arg(m_currentAudioProfile)); + m_settings.setValue(QString("audio%1/playback").arg(m_currentAudioProfile), + "default"); } else { m_settings.setValue(QString("audio%1/playback").arg(m_currentAudioProfile), id); } @@ -317,7 +318,7 @@ void AudioManager::setExternalRinger(bool flag) bool AudioManager::isDeviceAvailable(const QString &hash) { // Default hash is empty - if (hash.isEmpty()) { + if (hash.isEmpty() || hash == "default") { return true; } diff --git a/src/media/AudioPort.cpp b/src/media/AudioPort.cpp index fe9ee661..1a713bbd 100644 --- a/src/media/AudioPort.cpp +++ b/src/media/AudioPort.cpp @@ -6,7 +6,8 @@ #include "AudioPort.h" Q_LOGGING_CATEGORY(lcAudioPort, "gonnect.sip.audio") -#define NORMAL_AUDIO_LEVEL 2.0f +#define NORMAL_AUDIO_LEVEL 1.6f +#define SILENCE_BUFFER_MS 1000 using namespace std::chrono_literals; @@ -138,14 +139,29 @@ void AudioPort::stopIO() } } +void AudioPort::writeSilenceMS(unsigned milliseconds) +{ + if (m_sink) { + if (!m_io.isNull()) { + pj::MediaFormatAudio fmt = getPortInfo().format; + unsigned byteCount = + (fmt.clockRate * fmt.channelCount * (fmt.bitsPerSample / 8) * milliseconds) + / 1000; + + // Write silence to allow USB headsets to switch audio mode without + // ugly crackling noise. + QByteArray silence(byteCount, 0); + m_io->write(silence); + } + } +} + void AudioPort::startSinkIO() { m_idleTimer.stop(); if (!m_sink.isNull()) { stopSinkIO(); - delete m_sink; - m_sink = nullptr; } qCInfo(lcAudioPort).noquote().nospace() @@ -159,6 +175,8 @@ void AudioPort::startSinkIO() m_sink = new QAudioSink(m_device, m_audioFormat); m_io = m_sink->start(); + writeSilenceMS(SILENCE_BUFFER_MS); + Q_EMIT audioSinkChanged(); } @@ -167,6 +185,8 @@ void AudioPort::stopSinkIO() m_idleTimer.stop(); if (m_sink) { + writeSilenceMS(SILENCE_BUFFER_MS); + m_sink->stop(); m_sink->deleteLater(); m_sink = nullptr; diff --git a/src/media/AudioPort.h b/src/media/AudioPort.h index bf2ab237..270dfb87 100644 --- a/src/media/AudioPort.h +++ b/src/media/AudioPort.h @@ -39,6 +39,8 @@ class AudioPort : public QObject, public pj::AudioMediaPort void sourceLevelChanged(qreal level); private: + void writeSilenceMS(unsigned milliseconds); + void startIO(); void stopIO(); diff --git a/src/platform/NotificationIcon.cpp b/src/platform/NotificationIcon.cpp index b1895422..18f9a837 100644 --- a/src/platform/NotificationIcon.cpp +++ b/src/platform/NotificationIcon.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -50,8 +49,7 @@ NotificationIcon::NotificationIcon(const QString &fileUri, const QString &emblem } } - QPixmap pixmap = QPixmap::fromImage(tmpImage); QBuffer buffer(&m_data); buffer.open(QIODevice::WriteOnly); - pixmap.save(&buffer, "PNG"); + tmpImage.save(&buffer, "PNG"); } diff --git a/src/platform/linux/LinuxNotificationManager.cpp b/src/platform/linux/LinuxNotificationManager.cpp index fc1cdfb9..ccc82642 100644 --- a/src/platform/linux/LinuxNotificationManager.cpp +++ b/src/platform/linux/LinuxNotificationManager.cpp @@ -14,6 +14,10 @@ NotificationManager &NotificationManager::instance() LinuxNotificationManager::LinuxNotificationManager() : NotificationManager() { notify_init(qAppName().toStdString().c_str()); + + if (GList *caps = notify_get_server_caps()) { + g_list_free_full(caps, g_free); + } } void LinuxNotificationManager::handleAction(QString id, QString action, QVariantList parameters) diff --git a/src/sip/RingTone.cpp b/src/sip/RingTone.cpp index 42d8d6e5..2a8c9835 100644 --- a/src/sip/RingTone.cpp +++ b/src/sip/RingTone.cpp @@ -1,5 +1,6 @@ #include "RingTone.h" #include "AudioManager.h" +#include "ReadOnlyConfdSettings.h" RingTone::RingTone(quint16 frequency1, quint16 frequency2, QList> intervals, qint8 loopIndex, QObject *parent) @@ -43,6 +44,7 @@ void RingTone::start() m_stopTimer.stop(); } + m_toneGen.startTransmit(m_mediaSink); playNextTone(); } @@ -81,7 +83,6 @@ void RingTone::playNextTone() tones.push_back(tone); m_toneGen.play(tones, m_loopIndex >= 0); - m_toneGen.startTransmit(m_mediaSink); // Restart timer ++m_currentIndex; diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index f120c4ae..517bd3b4 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -7,7 +7,6 @@ #include "PreferredIdentity.h" #include "PhoneNumberUtil.h" #include "ErrorBus.h" -#include "NetworkHelper.h" #include "Credentials.h" #include "EnumTranslation.h" @@ -77,6 +76,19 @@ void SIPAccount::initialize() return; } + QString voiceMailUri = m_settings.value("voiceMailUri", "").toString(); + if (!voiceMailUri.isEmpty()) { + if (!sipURI.match(voiceMailUri).hasMatch()) { + qCCritical(lcSIPAccount) << "'voiceMailUri' is no valid SIP URI:" << voiceMailUri; + ErrorBus::instance().addFatalError( + tr("'voiceMailUri' is no valid SIP URI: %1").arg(userUri)); + Q_EMIT initialized(false); + return; + } + + m_voiceMailUri = voiceMailUri; + } + QString registrarUri = m_settings.value("registrarUri", "").toString(); if (!registrarUri.isEmpty()) { m_domain = sipURI.match(registrarUri).captured(2); @@ -133,6 +145,9 @@ void SIPAccount::initialize() } m_accountConfig.mediaConfig.srtpUse = srtpUseValue; + m_accountConfig.mwiConfig.enabled = true; + m_accountConfig.mwiConfig.expirationSec = 3600; + int rtpPort = m_settings.value("rtpPort", 0).toInt(&ok); if (!ok) { qCCritical(lcSIPAccount) << "invalid value for 'rtpPort':" << rtpPort; @@ -219,33 +234,7 @@ void SIPAccount::initialize() m_transportConfig.tlsConfig.verifyServer = m_settings.value("verifyServer", false).toBool(); - try { - if (m_transportNet == TRANSPORT_NET::AUTO || m_transportNet == TRANSPORT_NET::IPv4) { - if (m_transportType == TRANSPORT_TYPE::TLS) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_TLS, - m_transportConfig); - } else if (m_transportType == TRANSPORT_TYPE::TCP) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_TCP, - m_transportConfig); - } else if (m_transportType == TRANSPORT_TYPE::UDP) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_UDP, - m_transportConfig); - } - } - if (m_transportNet == TRANSPORT_NET::AUTO || m_transportNet == TRANSPORT_NET::IPv6) { - if (m_transportType == TRANSPORT_TYPE::TLS) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_TLS6, - m_transportConfig); - } else if (m_transportType == TRANSPORT_TYPE::TCP) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_TCP6, - m_transportConfig); - } else if (m_transportType == TRANSPORT_TYPE::UDP) { - SIPManager::instance().endpoint().transportCreate(PJSIP_TRANSPORT_UDP6, - m_transportConfig); - } - } - } catch (pj::Error &err) { - qCCritical(lcSIPAccount) << "failed to create transport:" << err.info(false); + if (!activateTransports()) { Q_EMIT initialized(false); return; } @@ -408,6 +397,63 @@ void SIPAccount::initialize() finalizeInitialization(); } +bool SIPAccount::activateTransports() +{ + if (m_transportIds.length() != 0) { + qCCritical(lcSIPAccount) << "transports are already created - skipping"; + return false; + } + + try { + if (m_transportNet == TRANSPORT_NET::AUTO || m_transportNet == TRANSPORT_NET::IPv4) { + if (m_transportType == TRANSPORT_TYPE::TLS) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_TLS, m_transportConfig)); + } else if (m_transportType == TRANSPORT_TYPE::TCP) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_TCP, m_transportConfig)); + } else if (m_transportType == TRANSPORT_TYPE::UDP) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_UDP, m_transportConfig)); + } + } + if (m_transportNet == TRANSPORT_NET::AUTO || m_transportNet == TRANSPORT_NET::IPv6) { + if (m_transportType == TRANSPORT_TYPE::TLS) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_TLS6, m_transportConfig)); + } else if (m_transportType == TRANSPORT_TYPE::TCP) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_TCP6, m_transportConfig)); + } else if (m_transportType == TRANSPORT_TYPE::UDP) { + m_transportIds.push_back(SIPManager::instance().endpoint().transportCreate( + PJSIP_TRANSPORT_UDP6, m_transportConfig)); + } + } + } catch (pj::Error &err) { + qCCritical(lcSIPAccount) << "failed to create transport:" << err.info(false); + return false; + } + + return true; +} + +void SIPAccount::deactivateTransports() +{ + try { + setRegistration(false); + } catch (...) { + } + + for (auto tid : std::as_const(m_transportIds)) { + try { + SIPManager::instance().endpoint().transportClose(tid); + } catch (...) { + } + } + + m_transportIds.clear(); +} + void SIPAccount::finalizeInitialization() { try { @@ -483,15 +529,125 @@ long SIPAccount::sendMessage(const QString &recipient, const QString &message, return id; } +void SIPAccount::parseMessageCount(const QString &value, quint16 &newMessages, quint16 &oldMessages) +{ + static QRegularExpression messageMatcher("^(\\d+)/(\\d+)\\s+\\((\\d+)/(\\d+)\\)$"); + + auto match = messageMatcher.match(value); + if (match.hasMatch()) { + bool ok = false; + + newMessages = match.captured(1).toUInt(&ok); + if (!ok) { + qCWarning(lcSIPAccount) << "failed to parse 'new message count' from" << value; + } + + oldMessages = match.captured(2).toUInt(&ok); + if (!ok) { + qCWarning(lcSIPAccount) << "failed to parse 'old message count' from" << value; + } + } +} + +MwiInfo SIPAccount::parseMwiBody(const QString &body) +{ + MwiInfo info; + + QStringList lines = body.split("\r\n"); + for (auto line : std::as_const(lines)) { + line = line.trimmed().toLower(); + + auto colonPos = line.indexOf(":"); + if (colonPos < 0) { + continue; + } + + QString key = line.sliced(0, colonPos); + QString value = line.sliced(colonPos + 1).trimmed(); + + if (key == "messages-waiting") { + info.messagesWaiting = value == "yes"; + + } else if (key == "message-account") { + info.messageAccount = value; + + } else if (key == "voice-message") { + parseMessageCount(value, info.voiceNew, info.voiceOld); + } + } + + return info; +} + +void SIPAccount::onMwiInfo(pj::OnMwiInfoParam &prm) +{ + qCDebug(lcSIPAccount) << "received MWI info message - subscription status:" << prm.state; + + QString fullMsg = QString::fromStdString(prm.rdata.wholeMsg); + QStringList parts = fullMsg.split("\r\n\r\n"); + + if (parts.length() < 2 || parts[1].isEmpty()) { + qCDebug(lcSIPAccount) << "MWI message body has no payload"; + return; + } + + bool changed = false; + + auto mwiInfo = parseMwiBody(parts[1]); + + if (mwiInfo.messagesWaiting != m_messagesWaiting) { + qCInfo(lcSIPAccount) << "Messages waiting:" << mwiInfo.messagesWaiting; + m_messagesWaiting = mwiInfo.messagesWaiting; + changed = true; + } + + if (mwiInfo.voiceNew != m_newVoiceMessages) { + qCInfo(lcSIPAccount) << "New voice messages:" << mwiInfo.voiceNew; + m_newVoiceMessages = mwiInfo.voiceNew; + changed = true; + } + + if (mwiInfo.voiceOld != m_readVoiceMessages) { + qCInfo(lcSIPAccount) << "Read voice messages:" << mwiInfo.voiceOld; + m_readVoiceMessages = mwiInfo.voiceOld; + changed = true; + } + + m_messageAccount = mwiInfo.messageAccount; + + if (changed) { + Q_EMIT voiceMessagesWaitingChanged(); + } +} + +bool SIPAccount::callVoiceBox() +{ + if (!m_voiceMailUri.isEmpty()) { + qCDebug(lcSIPAccount) << "calling voice mail via" << m_voiceMailUri; + call(m_account, m_voiceMailUri, "", false); + return true; + + } else if (!m_messageAccount.isEmpty()) { + qCDebug(lcSIPAccount) << "calling voice mail via fallback from Message-Account" + << m_messageAccount; + call(m_account, m_messageAccount, "", false); + return true; + } + + qCCritical(lcSIPAccount) << "no voice mail account specified - use voiceMailUri or configure " + "MWI/Message-Account to contain one"; + return false; +} + void SIPAccount::onInstantMessageStatus(pj::OnInstantMessageStatusParam &prm) { - qCDebug(lcSIPAccount) << "sent message to " << prm.toUri << ", status: " << prm.code - << prm.reason << (intptr_t)prm.userData; + qCDebug(lcSIPAccount) << "sent message to" << prm.toUri << ", status:" << prm.code << prm.reason + << (intptr_t)prm.userData; } void SIPAccount::onInstantMessage(pj::OnInstantMessageParam &prm) { - qCDebug(lcSIPAccount) << "received message from " << prm.fromUri << ":" << prm.msgBody; + qCDebug(lcSIPAccount) << "received message from" << prm.fromUri << ":" << prm.msgBody; Q_EMIT messageReceived(prm.fromUri.c_str(), prm.msgBody.c_str(), prm.contentType.c_str()); } diff --git a/src/sip/SIPAccount.h b/src/sip/SIPAccount.h index 546ea89b..6d9c356a 100644 --- a/src/sip/SIPAccount.h +++ b/src/sip/SIPAccount.h @@ -8,6 +8,16 @@ #include "SIPBuddy.h" #include "ReadOnlyConfdSettings.h" +struct MwiInfo +{ + bool messagesWaiting = false; + QString messageAccount; + + // We're only interested in ordinary voice info w/o urgency + quint16 voiceNew = 0; + quint16 voiceOld = 0; +}; + class SIPAccount : public QObject, public pj::Account { Q_OBJECT @@ -23,12 +33,15 @@ class SIPAccount : public QObject, public pj::Account Q_ENUM(TRANSPORT_NET) void initialize(); + bool activateTransports(); + void deactivateTransports(); void onIncomingCall(pj::OnIncomingCallParam &prm) override; void onRegState(pj::OnRegStateParam &prm) override; void onSendRequest(pj::OnSendRequestParam &prm) override; void onInstantMessageStatus(pj::OnInstantMessageStatusParam &prm) override; void onInstantMessage(pj::OnInstantMessageParam &prm) override; + void onMwiInfo(pj::OnMwiInfoParam &prm) override; bool isRegistered() const { return m_isRegistered; } bool isInstantMessagingAllowed() const; @@ -50,6 +63,12 @@ class SIPAccount : public QObject, public pj::Account QString domain() const { return m_domain; } uint retryInterval() const; + QString voiceMessageAccount() const { return m_messageAccount; } + bool messagesWaiting() const { return m_messagesWaiting; } + quint16 newVoiceMessages() const { return m_newVoiceMessages; } + quint16 oldVoiceMessages() const { return m_readVoiceMessages; } + bool callVoiceBox(); + long sendMessage(const QString &recipient, const QString &message, const QString &mimeType = "text/plain"); @@ -72,13 +91,23 @@ class SIPAccount : public QObject, public pj::Account QString addTransport(const QString &uri); + MwiInfo parseMwiBody(const QString &body); + void parseMessageCount(const QString &value, quint16 &newMessages, quint16 &oldMessages); + QList m_calls; QList m_buddies; + QList m_transportIds; + + bool m_messagesWaiting = false; + quint16 m_newVoiceMessages = 0; + quint16 m_readVoiceMessages = 0; + QString m_messageAccount; + QString m_voiceMailUri; + bool m_isRegistered = false; bool m_isInstantMessagingAllowed = false; bool m_shallNegotiateCapabilities = true; bool m_useInstantMessagingWithoutCheck = true; - bool m_rttEnabled = true; QString m_account; @@ -95,6 +124,7 @@ class SIPAccount : public QObject, public pj::Account Q_SIGNALS: void initialized(bool success); void isRegisteredChanged(); + void voiceMessagesWaitingChanged(); void authorizationFailed(); void connectionError(int code, QString message); void messageReceived(const QString &sender, const QString &message, const QString &mimeType); diff --git a/src/sip/SIPAccountManager.cpp b/src/sip/SIPAccountManager.cpp index 8fb84e53..b640a8de 100644 --- a/src/sip/SIPAccountManager.cpp +++ b/src/sip/SIPAccountManager.cpp @@ -57,6 +57,9 @@ void SIPAccountManager::initialize() Q_EMIT connectionError(code, message); } }); + connect(sipAccount, &SIPAccount::voiceMessagesWaitingChanged, this, + &SIPAccountManager::voiceMessagesWaitingChanged); + updateSipRegistered(); connect(sipAccount, &SIPAccount::initialized, this, @@ -87,6 +90,15 @@ void SIPAccountManager::setAccountCredentials(const QString &accountId, const QS } } +void SIPAccountManager::callVoiceBox(const QString &accountId) +{ + if (auto account = getAccount(accountId)) { + account->callVoiceBox(); + } else { + qCCritical(lcSIPAccountManager) << "account" << accountId << "not found"; + } +} + uint SIPAccountManager::sipRegisterRetryInterval() const { for (const auto account : std::as_const(m_accounts)) { @@ -95,6 +107,22 @@ uint SIPAccountManager::sipRegisterRetryInterval() const return 30; } +qint16 SIPAccountManager::newVoiceMessageCount() const +{ + for (const auto account : std::as_const(m_accounts)) { + return account->newVoiceMessages(); + } + return 0; +} + +qint16 SIPAccountManager::oldVoiceMessageCount() const +{ + for (const auto account : std::as_const(m_accounts)) { + return account->oldVoiceMessages(); + } + return 0; +} + SIPAccount *SIPAccountManager::getAccount(const QString &accountId) { for (SIPAccount *ac : std::as_const(m_accounts)) { diff --git a/src/sip/SIPAccountManager.h b/src/sip/SIPAccountManager.h index 4241f3ca..6da4dda9 100644 --- a/src/sip/SIPAccountManager.h +++ b/src/sip/SIPAccountManager.h @@ -12,6 +12,10 @@ class SIPAccountManager : public QObject Q_PROPERTY(bool sipRegistered READ sipRegistered NOTIFY sipRegisteredChanged FINAL) Q_PROPERTY(uint sipRegisterRetryInterval READ sipRegisterRetryInterval CONSTANT FINAL) + Q_PROPERTY(qint16 newVoiceMessageCount READ newVoiceMessageCount NOTIFY + voiceMessagesWaitingChanged FINAL) + Q_PROPERTY(qint16 oldVoiceMessageCount READ oldVoiceMessageCount NOTIFY + voiceMessagesWaitingChanged FINAL) public: Q_REQUIRED_RESULT static SIPAccountManager &instance() @@ -27,11 +31,14 @@ class SIPAccountManager : public QObject bool sipRegistered() const { return m_sipRegistered; } uint sipRegisterRetryInterval() const; + qint16 newVoiceMessageCount() const; + qint16 oldVoiceMessageCount() const; SIPAccount *getAccount(const QString &accountId); QList accounts() const { return m_accounts; } Q_INVOKABLE void setAccountCredentials(const QString &accountId, const QString &password); + Q_INVOKABLE void callVoiceBox(const QString &accountId); ~SIPAccountManager() = default; @@ -42,6 +49,7 @@ class SIPAccountManager : public QObject Q_SIGNALS: void accountsChanged(); void sipRegisteredChanged(bool status); + void voiceMessagesWaitingChanged(); void authorizationFailed(QString accountId); void connectionError(int code, QString message); diff --git a/src/sip/SIPCall.cpp b/src/sip/SIPCall.cpp index 9b214e7c..5967d72d 100644 --- a/src/sip/SIPCall.cpp +++ b/src/sip/SIPCall.cpp @@ -159,6 +159,15 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm) << statusCode << ") " << " last reason " << ci.lastReason << " contactId " << m_contactId; + if (statusCode == PJSIP_SC_RINGING) { + ringToneFactory.ringingTone()->start(); + if (!m_isSilent && !m_incoming) { + removeCallState(ICallState::State::InProgress); + addCallState(ICallState::State::RingingOutgoing); + m_isInProgress = false; + } + } + switch (ci.state) { case PJSIP_INV_STATE_NULL: if (!m_isSilent) { @@ -171,8 +180,8 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm) case PJSIP_INV_STATE_CONNECTING: case PJSIP_INV_STATE_CALLING: if (!m_isSilent && !m_incoming) { - ringToneFactory.ringingTone()->start(); - addCallState(ICallState::State::RingingOutgoing); + m_isInProgress = true; + addCallState(ICallState::State::InProgress); } break; @@ -211,11 +220,9 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm) removeCallState(ICallState::State::RingingIncoming | ICallState::State::KnockingIncoming); removeCallState(ICallState::State::RingingOutgoing); + removeCallState(ICallState::State::InProgress); addCallState(ICallState::State::CallActive | ICallState::State::AudioActive); - ringToneFactory.ringingTone()->stop(); - ringToneFactory.zipTone()->stop(); - m_isEstablished = true; m_wasEstablished = true; m_establishedTime = QDateTime::currentDateTime(); @@ -352,13 +359,15 @@ void SIPCall::onCallMediaState(pj::OnCallMediaStateParam &prm) mic_media.startTransmit(aud_med); aud_med.startTransmit(speaker_media); - auto sniffer = new Sniffer(this); - sniffer->initialize(); - aud_med.startTransmit(dynamic_cast(*sniffer)); - connect(sniffer, &Sniffer::audioLevelChanged, this, [this, sniffer]() { - Q_EMIT SIPCallManager::instance().audioLevelChanged(this, - sniffer->audioLevel()); - }); + if (!m_sniffer) { + m_sniffer = new Sniffer(this); + m_sniffer->initialize(); + aud_med.startTransmit(dynamic_cast(*m_sniffer)); + connect(m_sniffer, &Sniffer::audioLevelChanged, this, [this]() { + Q_EMIT SIPCallManager::instance().audioLevelChanged( + this, m_sniffer->audioLevel()); + }); + } } break; diff --git a/src/sip/SIPCall.h b/src/sip/SIPCall.h index a7a1d0f6..8f5cd640 100644 --- a/src/sip/SIPCall.h +++ b/src/sip/SIPCall.h @@ -13,6 +13,7 @@ class SIPAccount; class CallHistoryItem; class IMHandler; class HeadsetDeviceProxy; +class Sniffer; class SIPCall : public ICallState, public pj::Call { @@ -63,6 +64,7 @@ class SIPCall : public ICallState, public pj::Call void accept(); void reject(); + bool isInProgress() const { return m_isInProgress; } bool isEstablished() const { return m_isEstablished; } /// The time when the call was established (i.e. answered); invalid QDateTime if not established QDateTime establishedTime() const { return m_establishedTime; } @@ -155,6 +157,7 @@ private Q_SLOTS: pj::AudioMedia *m_aud_med = NULL; IMHandler *m_imHandler = nullptr; + Sniffer *m_sniffer = nullptr; bool m_incoming = false; bool m_isEstablished = false; @@ -169,6 +172,7 @@ private Q_SLOTS: bool m_hasAccepted = false; bool m_hasRejected = false; bool m_hasRtt = false; + bool m_isInProgress = false; QString m_sipUrl; QString m_contactId; diff --git a/src/sip/SIPCallManager.h b/src/sip/SIPCallManager.h index fc6461bb..f97c806c 100644 --- a/src/sip/SIPCallManager.h +++ b/src/sip/SIPCallManager.h @@ -48,7 +48,7 @@ class SIPCallManager : public QObject bool isEarlyCallState() const { return m_earlyCallState; } - unsigned missedCalls() const { return m_missedCalls; } + Q_INVOKABLE unsigned missedCalls() const { return m_missedCalls; } QStringList callIds() const; Q_INVOKABLE QString call(const QString &number, bool silent = false); diff --git a/src/sip/SIPManager.cpp b/src/sip/SIPManager.cpp index 7cbebc02..e2e2e8cc 100644 --- a/src/sip/SIPManager.cpp +++ b/src/sip/SIPManager.cpp @@ -90,6 +90,8 @@ void SIPManager::initialize() "*.critical=true")); } + epConfig.uaConfig.mwiUnsolicitedEnabled = true; + m_mediaConfig = new SIPMediaConfig(this); m_mediaConfig->applyConfig(epConfig); @@ -357,7 +359,7 @@ void SIPManager::suspend() // Unregister account(s) auto accounts = SIPAccountManager::instance().accounts(); for (auto account : std::as_const(accounts)) { - account->setRegistration(false); + account->deactivateTransports(); } // Shutdown transports @@ -372,7 +374,15 @@ void SIPManager::resume() // Since resume may be called in more network changed cases, only // do this when we have no active calls going. if (!!SIPCallManager::instance().hasActiveCalls()) { - // Restore transports + qCDebug(lcSIPManager) << "resuming SIP"; + + // Activate transports again + auto accounts = SIPAccountManager::instance().accounts(); + for (auto account : std::as_const(accounts)) { + account->setRegistration(false); + account->activateTransports(); + } + try { pj::Endpoint::instance().handleIpChange(pj::IpChangeParam()); } catch (pj::Error &err) { @@ -381,9 +391,8 @@ void SIPManager::resume() } // Re-activate account registration - auto accounts = SIPAccountManager::instance().accounts(); for (auto account : std::as_const(accounts)) { - account->setRegistration(false); + account->setRegistration(true); } } } diff --git a/src/ui/CallsModel.cpp b/src/ui/CallsModel.cpp index a7cd0b0f..94fabb77 100644 --- a/src/ui/CallsModel.cpp +++ b/src/ui/CallsModel.cpp @@ -28,6 +28,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto idx = createIndex(index, 0); Q_EMIT dataChanged(idx, idx, { + static_cast(Roles::IsInProgress), static_cast(Roles::IsEstablished), static_cast(Roles::EstablishedTime), static_cast(Roles::HasCapabilityJitsi), @@ -231,6 +232,7 @@ QHash CallsModel::roleNames() const { static_cast(Roles::Country), "country" }, { static_cast(Roles::Company), "company" }, { static_cast(Roles::IsEstablished), "isEstablished" }, + { static_cast(Roles::IsInProgress), "isInProgress" }, { static_cast(Roles::EstablishedTime), "establishedTime" }, { static_cast(Roles::IsHolding), "isHolding" }, { static_cast(Roles::IsBlocked), "isBlocked" }, @@ -294,6 +296,7 @@ void CallsModel::updateCalls() callInfo->remoteUri = call->sipUrl(); callInfo->established = call->establishedTime(); callInfo->isEstablished = call->isEstablished(); + callInfo->isInProgress = call->isInProgress(); callInfo->isIncoming = call->isIncoming(); callInfo->isBlocked = call->isBlocked(); callInfo->isHolding = call->isHolding(); @@ -382,6 +385,9 @@ QVariant CallsModel::data(const QModelIndex &index, int role) const case static_cast(Roles::IsEstablished): return callInfo->isEstablished; + case static_cast(Roles::IsInProgress): + return callInfo->isInProgress; + case static_cast(Roles::IsFinished): return callInfo->isFinished; diff --git a/src/ui/CallsModel.h b/src/ui/CallsModel.h index 51a16da2..cce01c7e 100644 --- a/src/ui/CallsModel.h +++ b/src/ui/CallsModel.h @@ -5,7 +5,7 @@ #include #include #include "PhoneNumberUtil.h" -#include "SIPCall.h" +#include "SIPCallManager.h" class CallsModel : public QAbstractListModel { @@ -22,6 +22,7 @@ class CallsModel : public QAbstractListModel QString accountId; QString remoteUri; bool isIncoming = false; + bool isInProgress = false; bool isHolding = false; bool isBlocked = false; bool isEstablished = false; @@ -63,6 +64,7 @@ class CallsModel : public QAbstractListModel Country, Company, IsEstablished, + IsInProgress, EstablishedTime, IsHolding, IsBlocked, diff --git a/src/ui/GonnectWindow.qml b/src/ui/GonnectWindow.qml index ed1c45ee..26491dd5 100644 --- a/src/ui/GonnectWindow.qml +++ b/src/ui/GonnectWindow.qml @@ -161,12 +161,13 @@ BaseWindow { mainTabBar.saveTabList() } - function createPage(pageId : string, iconId : string, name : string) { + function createPage(pageId : string, name : string, iconId : string, tab : variant) { const page = pages.base.createObject(pageStack, { pageId: pageId, name: name, iconId: iconId, + tabButton: tab, editMode: true }) if (page === null) { @@ -190,6 +191,12 @@ BaseWindow { id: pages } + property int notifications: pageModel.notifications + + onNotificationsChanged: () => { + SystemTrayMenu.setBadgeNumber(control.notifications) + } + PageModel { id: pageModel } @@ -357,6 +364,7 @@ BaseWindow { pageId: control.homePageId name: qsTr("Home") iconId: "userHome" + tabButton: mainTabBar.getTabById(control.homePageId) } Call { @@ -388,7 +396,7 @@ BaseWindow { Item { id: bottomBar - visible: true // mainTabBar.selectedPageType === GonnectWindow.PageType.Base + visible: true height: 35 anchors { right: parent.right @@ -414,14 +422,12 @@ BaseWindow { anchors { right: parent.right bottom: parent.bottom - - topMargin: 6 - bottomMargin: 6 - rightMargin: 12 + rightMargin: 6 } FirstAidButton { id: firstAidButton + height: 42 z: 100000 } } diff --git a/src/ui/PageModel.qml b/src/ui/PageModel.qml index c8da64e1..76727f25 100644 --- a/src/ui/PageModel.qml +++ b/src/ui/PageModel.qml @@ -6,7 +6,15 @@ import base Item { id: control - property var model: [] + readonly property int notifications: internal.model.reduce((sum, el) => sum + el.notifications, 0) + + readonly property alias model: internal.model + + QtObject { + id: internal + + property list model: [] + } signal modelUpdated() @@ -19,16 +27,17 @@ Item { } function add(page: Item) { - model.push(page) + if (!internal.model.includes(page)) { + internal.model = internal.model.concat([page]) - control.modelUpdated() + control.modelUpdated() + } } function remove(page: Item) { - const index = model.indexOf(page) + if (internal.model.includes(page)) { + internal.model = internal.model.filter(item => item !== page) - if (index !== -1) { - model.splice(index, 1) UISettings.removeUISetting(page.pageId, "") control.modelUpdated() } diff --git a/src/ui/PageReader.qml b/src/ui/PageReader.qml index 0285727d..fff9c351 100644 --- a/src/ui/PageReader.qml +++ b/src/ui/PageReader.qml @@ -31,11 +31,18 @@ Item { for (const pageId of pageIds) { const pageName = UISettings.getUISetting(pageId, "name", "") const pageIconId = UISettings.getUISetting(pageId, "iconId", "") + + const tab = tabRoot.createTab(pageId, + GonnectWindow.PageType.Base, + pageIconId, + pageName) + const page = pages.base.createObject(control.pageRoot, { pageId: pageId, name: pageName, - iconId: pageIconId + iconId: pageIconId, + tabButton: tab }) if (!page) { console.error(category, "could not create page component", pageId) @@ -43,12 +50,6 @@ Item { } model.add(page) - - tabRoot.createTab(pageId, - GonnectWindow.PageType.Base, - pageIconId, - pageName) - pageRoot.pages[pageId] = page // Widgets @@ -61,6 +62,7 @@ Item { function loadHomePage(pageId : string) { const page = pageRoot.getPage(pageId) + model.add(page) const widgetIds = UISettings.getWidgetIds(pageId) if (widgetIds.length) { diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp index 4eabe9fa..6e4217e0 100644 --- a/src/ui/SystemTrayMenu.cpp +++ b/src/ui/SystemTrayMenu.cpp @@ -473,6 +473,8 @@ void SystemTrayMenu::resetTrayIcon() m_trayIcon->setIcon(QIcon(":/icons/gonnect_noreg_light.svg")); } } + + m_trayIcon->setVisible(true); } void SystemTrayMenu::setBadgeNumber(unsigned number) diff --git a/src/ui/SystemTrayMenu.h b/src/ui/SystemTrayMenu.h index c4017345..56ea6aa8 100644 --- a/src/ui/SystemTrayMenu.h +++ b/src/ui/SystemTrayMenu.h @@ -25,7 +25,7 @@ class SystemTrayMenu : public QObject return *_instance; } - void setBadgeNumber(unsigned number); + Q_INVOKABLE void setBadgeNumber(unsigned number); void resetTrayIcon(); void setRinging(bool flag); @@ -80,3 +80,17 @@ private Q_SLOTS: bool m_ringingState = false; bool m_hasEstablishedCalls = false; }; + +class SystemTrayMenuWrapper +{ + Q_GADGET + QML_FOREIGN(SystemTrayMenu) + QML_NAMED_ELEMENT(SystemTrayMenu) + QML_SINGLETON + +public: + static SystemTrayMenu *create(QQmlEngine *, QJSEngine *) { return &SystemTrayMenu::instance(); } + +private: + SystemTrayMenuWrapper() = default; +}; diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index ebe1713c..94ebd204 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -4,7 +4,6 @@ #include #include -#include Q_LOGGING_CATEGORY(lcTheme, "gonnect.app.theme") @@ -124,6 +123,7 @@ void Theme::updateColorPalette() m_backgroundInitials = QColor(214, 212, 233); m_shadowColor = QColor(0, 0, 0, 32); m_redColor = QColor(224, 27, 36); + m_emergencyColor = QColor(0, 136, 85); m_greenColor = QColor(36, 181, 27); m_darkGreenColor = QColor(128, 128, 0); m_activeIndicatorColor = QColor(255, 102, 0); diff --git a/src/ui/Theme.h b/src/ui/Theme.h index aba3528e..be027a16 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -48,6 +48,7 @@ class Theme : public QObject Q_PROPERTY(QColor backgroundInitials READ backgroundInitials NOTIFY colorPaletteChanged FINAL) Q_PROPERTY(QColor shadowColor READ shadowColor NOTIFY colorPaletteChanged FINAL) Q_PROPERTY(QColor redColor READ redColor NOTIFY colorPaletteChanged FINAL) + Q_PROPERTY(QColor emergencyColor READ emergencyColor NOTIFY colorPaletteChanged FINAL) Q_PROPERTY( QColor activeIndicatorColor READ activeIndicatorColor NOTIFY colorPaletteChanged FINAL) Q_PROPERTY(QColor greenColor READ greenColor NOTIFY colorPaletteChanged FINAL) @@ -130,6 +131,7 @@ class Theme : public QObject QColor backgroundInitials() const { return m_backgroundInitials; } QColor shadowColor() const { return m_shadowColor; } QColor redColor() const { return m_redColor; } + QColor emergencyColor() const { return m_emergencyColor; } QColor activeIndicatorColor() const { return m_activeIndicatorColor; } QColor greenColor() const { return m_greenColor; } QColor darkGreenColor() const { return m_darkGreenColor; } @@ -228,6 +230,7 @@ private Q_SLOTS: QColor m_backgroundInitials; QColor m_shadowColor; QColor m_redColor; + QColor m_emergencyColor; QColor m_greenColor; QColor m_darkGreenColor; QColor m_paneColor; diff --git a/src/ui/ViewHelper.cpp b/src/ui/ViewHelper.cpp index 4a4eefa7..3c65b419 100644 --- a/src/ui/ViewHelper.cpp +++ b/src/ui/ViewHelper.cpp @@ -289,6 +289,12 @@ void ViewHelper::updateIsActiveVideoCall() } } +QString ViewHelper::culturalSphereExtension() const +{ + auto sphere = tr("QT_CULTURAL_SPHERE", "QGuiApplication"); + return sphere == "QT_CULTURAL_SPHERE" ? "" : sphere; +} + void ViewHelper::quitApplicationNoConfirm() const { Application::instance()->quit(); @@ -301,7 +307,7 @@ void ViewHelper::resetTrayIcon() const bool ViewHelper::isUnsupportedPlatform() const { -#ifdef Q_OS_LINUX +#if defined(Q_OS_LINUX) || defined(Q_OS_WINDOWS) return false; #endif return true; diff --git a/src/ui/ViewHelper.h b/src/ui/ViewHelper.h index 91b9ceaa..a0772615 100644 --- a/src/ui/ViewHelper.h +++ b/src/ui/ViewHelper.h @@ -30,6 +30,7 @@ class ViewHelper : public QObject Q_PROPERTY(bool isActiveVideoCall READ isActiveVideoCall NOTIFY isActiveVideoCallChanged FINAL) Q_PROPERTY(bool unsupportedPlatform READ isUnsupportedPlatform CONSTANT FINAL) Q_PROPERTY(bool canSyncSystemMute READ canSyncSystemMute CONSTANT FINAL) + Q_PROPERTY(QString culturalSphereExtension READ culturalSphereExtension CONSTANT FINAL) public: static ViewHelper &instance() @@ -133,6 +134,8 @@ class ViewHelper : public QObject Q_INVOKABLE uint numberOfGridCells() const; + QString culturalSphereExtension() const; + public Q_SLOTS: Q_INVOKABLE void quitApplicationNoConfirm() const; Q_INVOKABLE void quitApplication(); diff --git a/src/ui/WidgetModel.qml b/src/ui/WidgetModel.qml index 16346fa4..4045a5d1 100644 --- a/src/ui/WidgetModel.qml +++ b/src/ui/WidgetModel.qml @@ -6,7 +6,15 @@ import base Item { id: control - property var model: [] + readonly property int notifications: internal.model.reduce((sum, el) => sum + el.notifications, 0) + + readonly property alias model: internal.model + + QtObject { + id: internal + + property list model: [] + } signal modelUpdated() @@ -19,18 +27,18 @@ Item { } function add(widget: Item) { - model.push(widget) + if (!internal.model.includes(widget)) { + internal.model = internal.model.concat([widget]) - control.modelUpdated() + control.modelUpdated() + } } function remove(widget: Item) { - const index = model.indexOf(widget) - if (index !== -1) { - model.splice(index, 1) + if (internal.model.includes(widget)) { + internal.model = internal.model.filter(item => item !== widget) UISettings.removeUISetting(widget.widgetId, "") - control.modelUpdated() } } @@ -40,7 +48,7 @@ Item { UISettings.removeUISetting(widget.widgetId, "") } - model.splice(0, model.length); + internal.model = [] control.modelUpdated() } diff --git a/src/ui/components/BaseWidget.qml b/src/ui/components/BaseWidget.qml index 19485ba1..b5927610 100644 --- a/src/ui/components/BaseWidget.qml +++ b/src/ui/components/BaseWidget.qml @@ -30,6 +30,8 @@ Item { } } + property int notifications: 0 + property real gridWidth property real gridHeight property real gridCellWidth diff --git a/src/ui/components/CallButtonBar.qml b/src/ui/components/CallButtonBar.qml index 5f608b38..ef695a63 100644 --- a/src/ui/components/CallButtonBar.qml +++ b/src/ui/components/CallButtonBar.qml @@ -19,6 +19,7 @@ Item { readonly property string accountId: control.callItem?.accountId ?? "" readonly property bool showHoldButton: control.callItem?.showHoldButton ?? true readonly property bool isEstablished: control.callItem?.isEstablished ?? false + readonly property bool isInProgress: control.callItem?.isInProgress ?? false readonly property bool isHolding: control.callItem?.isHolding ?? false readonly property bool isFinished: control.callItem?.isFinished ?? false readonly property bool isIncoming: control.callItem?.isIncoming ?? false @@ -464,7 +465,7 @@ Item { } Rectangle { - id: elabsedTimeSeparator + id: elapsedTimeSeparator height: 32 width: 1 color: Theme.borderColor @@ -483,7 +484,7 @@ Item { text: ViewHelper.secondsToNiceText(internal.elapsedSeconds) spacing: 4 anchors { - left: elabsedTimeSeparator.right + left: elapsedTimeSeparator.right leftMargin: 20 verticalCenter: parent.verticalCenter } diff --git a/src/ui/components/CallerBigAvatar.qml b/src/ui/components/CallerBigAvatar.qml index b45d4ec5..cf27a381 100644 --- a/src/ui/components/CallerBigAvatar.qml +++ b/src/ui/components/CallerBigAvatar.qml @@ -15,6 +15,7 @@ Item { property bool isIncoming property bool isEstablished + property bool isInProgress property bool isIncomingAudioLevel Accessible.role: Accessible.Heading @@ -76,7 +77,7 @@ Item { Label { id: callingLabel font.pixelSize: 22 - text: qsTr("Calling...") + text: control.isInProgress ? qsTr("In progress...") : qsTr("Calling...") color: Theme.secondaryTextColor visible: !control.isIncoming && !control.isEstablished anchors { diff --git a/src/ui/components/CardHeading.qml b/src/ui/components/CardHeading.qml index e3366aa6..8c013155 100644 --- a/src/ui/components/CardHeading.qml +++ b/src/ui/components/CardHeading.qml @@ -1,36 +1,74 @@ pragma ComponentBehavior: Bound import QtQuick +import QtQuick.Layouts import QtQuick.Controls.Material import base Item { id: control - implicitWidth: lbl.implicitWidth + lbl.anchors.leftMargin + lbl.anchors.rightMargin height: 46 - property alias text: lbl.text + property string text: "" + property bool showHeading: true + property bool showDivider: false + property alias headingMargin: headingLoaderWrapper.implicitWidth Accessible.role: Accessible.Heading Accessible.name: control.text - Label { - id: lbl - font.pixelSize: 16 - font.weight: Font.Medium - elide: Text.ElideRight - color: Theme.secondaryTextColor + Item { + id: headingLoaderWrapper anchors { verticalCenter: parent.verticalCenter left: parent.left - right: parent.right leftMargin: 20 rightMargin: 20 } + implicitWidth: headingLoader.item ? headingLoader.item.implicitWidth : 0 + implicitHeight: headingLoader.item ? headingLoader.item.implicitHeight : 0 + + Loader { + id: headingLoader + active: control.showHeading + sourceComponent: headingComponent + } + Accessible.ignored: true } + Component { + id: headingComponent + + RowLayout { + id: headingLayout + + Label { + id: headingText + text: control.text + font.pixelSize: 16 + font.weight: Font.Medium + elide: Text.ElideRight + color: Theme.secondaryTextColor + } + + Pane { + padding: 15 + background: Rectangle { + id: headingSeparator + visible: control.showDivider + height: 30 + width: 1 + color: Theme.borderColor + anchors.centerIn: parent + } + } + + Accessible.ignored: true + } + } + Rectangle { color: Theme.borderColor height: 1 diff --git a/src/ui/components/HistoryWidget.qml b/src/ui/components/HistoryWidget.qml index 96cb248d..37383fc7 100644 --- a/src/ui/components/HistoryWidget.qml +++ b/src/ui/components/HistoryWidget.qml @@ -10,6 +10,10 @@ BaseWidget { minCellWidth: 20 minCellHeight: 15 + notifications: voiceMailField.totalVoicemailCount + control.missedCallsCount + + property int missedCallsCount: SIPCallManager.missedCalls + Rectangle { id: historyWidget parent: control.root @@ -19,12 +23,26 @@ BaseWidget { CardHeading { id: historyHeading text: qsTr("History") + showDivider: historyHeading.voicemailVisible + showHeading: historyWidget.width > 650 || historyHeading.showDivider === false anchors { left: parent.left right: parent.right } property alias searchVisible: historySearchField.visible + property alias voicemailVisible: voiceMailField.hasVoicemail + + VoiceMailField { + id: voiceMailField + visible: !historyHeading.searchVisible && historyHeading.voicemailVisible + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 20 + historyHeading.headingMargin + rightMargin: 20 + } + } SearchField { id: historySearchField diff --git a/src/ui/components/MainTabBar.qml b/src/ui/components/MainTabBar.qml index 26553296..c965d328 100644 --- a/src/ui/components/MainTabBar.qml +++ b/src/ui/components/MainTabBar.qml @@ -53,8 +53,8 @@ Item { SM.uiHasActiveEditDialog = true item.accepted.connect((name, iconId) => { - control.createTab(id, GonnectWindow.PageType.Base, iconId, name) - control.mainWindow.createPage(id, iconId, name) + const tab = control.createTab(id, GonnectWindow.PageType.Base, iconId, name) + control.mainWindow.createPage(id, name, iconId, tab) }) item.show() } @@ -80,13 +80,9 @@ Item { page.name = name // Update tab button - const tabList = control.getTabList() - for (const tab of tabList) { - if (tab.pageId === id) { - tab.iconSource = Icons[iconId] - tab.labelText = name - break - } + if (page.tabButton) { + page.tabButton.iconSource = Icons[iconId] + page.tabButton.labelText = name } }) item.prefill(page.iconId, page.name) @@ -94,7 +90,7 @@ Item { } } - function createTab(id : string, type : int, iconId : string, name : string) { + function createTab(id : string, type : int, iconId : string, name : string) : variant { const iconPath = Icons[iconId] const tabButton = tabDelegate.createObject(topMenuCol, { @@ -104,16 +100,20 @@ Item { labelText: name, disabledTooltipText: "", isEnabled: true, - showRedDot: false, showActiveBorder: false, attachedData: null }) if (tabButton === null) { console.error(category, "could not create tab button component") - return + return tabButton } control.dynamicPageCount += 1 + return tabButton + } + + function getTabById(id : string) : variant { + return [...topMenuCol.children].find((button) => button.pageId === id); } function getTabList() { @@ -190,7 +190,6 @@ Item { required property string pageId required property int pageType required property bool isEnabled - required property bool showRedDot required property bool showActiveBorder required property string labelText required property string disabledTooltipText @@ -199,6 +198,9 @@ Item { readonly property bool isSelected: control.selectedPageId === delg.pageId + property int notifications: 0 + property bool showNotificationBubble: delg.notifications > 0 + Accessible.role: Accessible.Button Accessible.name: qsTr("Selected tab") Accessible.description: qsTr("The currently selected tab") @@ -318,31 +320,43 @@ Item { } Rectangle { - id: redDotBackground - visible: redDot.visible + id: notificationBubbleBackground + visible: notificationBubble.visible color: hoverBackground.visible ? hoverBackground.color : filler.color - anchors.centerIn: redDot - width: redDot.width + 4 - height: redDotBackground.width - radius: redDotBackground.width / 2 + anchors.centerIn: notificationBubble + width: notificationBubble.width + 4 + height: notificationBubbleBackground.width + radius: notificationBubbleBackground.width / 2 Accessible.ignored: true } Rectangle { - id: redDot - visible: delg.showRedDot + id: notificationBubble + visible: delg.showNotificationBubble color: Theme.redColor - width: 6 - height: redDot.width - radius: redDot.width / 2 + width: delg.notifications > notificationBubble.maxNotifications + ? 24 : 16 + height: 16 + radius: notificationBubble.height / 2 anchors { - verticalCenter: delgIcon.top + verticalCenter: delgIcon.bottom horizontalCenter: delgIcon.right - verticalCenterOffset: +5 + verticalCenterOffset: -5 horizontalCenterOffset: -5 } + property int maxNotifications: 99 + + Label { + id: notificationBubbleCount + color: Theme.foregroundWhiteColor + font.pixelSize: 12 + text: delg.notifications > notificationBubble.maxNotifications + ? "99+" : delg.notifications.toString() + anchors.centerIn: parent + } + Accessible.ignored: true } @@ -384,7 +398,6 @@ Item { labelText: qsTr("Home"), disabledTooltipText: qsTr("Home"), isEnabled: true, - showRedDot: false, showActiveBorder: false, attachedData: null }, { @@ -394,7 +407,6 @@ Item { labelText: qsTr("Conference"), disabledTooltipText: qsTr("No active conference"), isEnabled: control.hasActiveConference, - showRedDot: false, showActiveBorder: control.hasActiveConference && control.selectedPageId !== control.mainWindow.conferencePageId, attachedData: null }, { @@ -404,7 +416,6 @@ Item { labelText: qsTr("Call"), disabledTooltipText: qsTr("No active call"), isEnabled: control.hasActiveCall, - showRedDot: false, showActiveBorder: control.hasActiveUnfinishedCall && control.selectedPageId !== control.mainWindow.callPageId, attachedData: null } @@ -438,7 +449,6 @@ Item { labelText: qsTr("Settings"), disabledTooltipText: qsTr("Settings"), isEnabled: true, - showRedDot: false, showActiveBorder: false, attachedData: null } diff --git a/src/ui/components/controls/FirstAidButton.qml b/src/ui/components/controls/FirstAidButton.qml index 7b7c3ba0..31f7e438 100644 --- a/src/ui/components/controls/FirstAidButton.qml +++ b/src/ui/components/controls/FirstAidButton.qml @@ -3,79 +3,32 @@ import QtQuick.Controls.Material import QtQuick.Controls.impl import base -Item { +Button { id: control - implicitHeight: control.iconSize - implicitWidth: control.iconSize + 10 + firstAidLabel.implicitWidth visible: GlobalInfo.hasEmergencyNumbers + text: qsTr("First Aid") + icon.source: "qrc:/icons/ISO_7010_E004" + ViewHelper.culturalSphereExtension + ".svg" + highlighted: true - property int iconSize: 24 + padding: 0 + spacing: 6 + topPadding: 0 + leftPadding: 10 + rightPadding: 10 + bottomPadding: 0 Accessible.role: Accessible.Button - Accessible.name: firstAidLabel.text + Accessible.name: control.text Accessible.description: qsTr("Open first aid menu") Accessible.focusable: true Accessible.onPressAction: ViewHelper.showFirstAid() - Rectangle { - id: firstAidIcon - width: control.iconSize - height: control.iconSize - radius: control.iconSize / 2 - color: Qt.rgba(1, 1, 1) - anchors.left: parent.left + Material.accent: Theme.emergencyColor - Rectangle { - id: verticalBar - color: Qt.rgba(1, 0, 0) - width: 1/4 * control.iconSize - anchors { - horizontalCenter: parent.horizontalCenter - top: parent.top - bottom: parent.bottom - topMargin: 1/12 * control.iconSize - bottomMargin: 1/12 * control.iconSize - } - - Accessible.ignored: true - } - - Rectangle { - id: horizontalBar - color: Qt.rgba(1, 0, 0) - height: 1/4 * control.iconSize - anchors { - verticalCenter: parent.verticalCenter - left: parent.left - right: parent.right - leftMargin: 1/12 * control.iconSize - rightMargin: 1/12 * control.iconSize - } - - Accessible.ignored: true - } - - Accessible.ignored: true - } - - Label { - id: firstAidLabel - text: qsTr("First Aid") - color: firstAidHoverHandler.hovered ? Theme.primaryTextColor : Theme.inactiveTextColor - anchors { - verticalCenter: control.verticalCenter - left: firstAidIcon.right - leftMargin: 10 - } - - Accessible.ignored: true + Component.onCompleted: () => { + control.icon.width = 16 + control.icon.height = 16 } - HoverHandler { - id: firstAidHoverHandler - } - - TapHandler { - onTapped: () => ViewHelper.showFirstAid() - } + onClicked: () => ViewHelper.showFirstAid() } diff --git a/src/ui/components/controls/VoiceMailField.qml b/src/ui/components/controls/VoiceMailField.qml new file mode 100644 index 00000000..d0c616e3 --- /dev/null +++ b/src/ui/components/controls/VoiceMailField.qml @@ -0,0 +1,76 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Material +import QtQuick.Controls.impl +import base + +Item { + id: control + implicitWidth: voicemailRow.width + implicitHeight: 30 + + property int totalVoicemailCount: control.newVoicemailCount + control.oldVoicemailCount + property int newVoicemailCount: SIPAccountManager.newVoiceMessageCount + property int oldVoicemailCount: SIPAccountManager.oldVoiceMessageCount + + property bool hasVoicemail: control.totalVoicemailCount > 0 + property bool hasNewVoicemail: control.newVoicemailCount > 0 + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Listen to voicemail") + Accessible.onPressAction: () => SIPAccountManager.callVoiceBox("account0") + + RowLayout { + id: voicemailRow + spacing: 8 + anchors.verticalCenter: parent.verticalCenter + + IconLabel { + id: voicemailIcon + icon { + source: Icons.callVoicemail + width: 16 + height: 16 + } + + Rectangle { + id: redDot + visible: control.hasNewVoicemail + color: Theme.redColor + width: 6 + height: redDot.width + radius: redDot.width / 2 + anchors { + verticalCenter: voicemailIcon.bottom + horizontalCenter: voicemailIcon.right + } + } + + Accessible.ignored: true + } + + Label { + id: voicemailCount + text: control.hasNewVoicemail + ? qsTr("%n new voice mail(s)", "", control.newVoicemailCount) + : qsTr("%n old voice mail(s)", "", control.oldVoicemailCount) + font.pixelSize: 16 + font.weight: Font.Medium + elide: Text.ElideRight + color: Theme.secondaryTextColor + + Accessible.ignored: true + } + } + + HoverHandler { + id: clearButtonHoverHandler + cursorShape: Qt.PointingHandCursor + } + + TapHandler { + onTapped: () => SIPAccountManager.callVoiceBox("account0") + } +} diff --git a/src/ui/components/pages/BasePage.qml b/src/ui/components/pages/BasePage.qml index 5664f29e..e341a280 100644 --- a/src/ui/components/pages/BasePage.qml +++ b/src/ui/components/pages/BasePage.qml @@ -11,6 +11,7 @@ Item { required property string pageId required property string name required property string iconId + required property var tabButton readonly property alias grid: snapGrid readonly property alias gridWidth: snapGrid.width @@ -40,6 +41,14 @@ Item { } } + property int notifications: widgetModel.notifications + + onNotificationsChanged: () => { + if (control.tabButton) { + control.tabButton.notifications = control.notifications + } + } + readonly property WidgetModel model: WidgetModel { id: widgetModel } diff --git a/src/ui/components/pages/Call.qml b/src/ui/components/pages/Call.qml index e556eb7e..302f7a43 100644 --- a/src/ui/components/pages/Call.qml +++ b/src/ui/components/pages/Call.qml @@ -122,6 +122,7 @@ Item { avatarUrl: callSideBar.selectedCallItem?.hasAvatar ? ("file://" + callSideBar.selectedCallItem.avatarPath) : "" isIncoming: topBar.isIncoming isEstablished: topBar.isEstablished + isInProgress: topBar.isInProgress isIncomingAudioLevel: callSideBar.selectedCallItem?.hasIncomingAudioLevel ?? false anchors.horizontalCenter: parent.horizontalCenter } @@ -152,6 +153,7 @@ Item { required property string avatarPath required property bool hasIncomingAudioLevel required property bool isEstablished + required property bool isInProgress required property bool isIncoming CallerBigAvatar { @@ -161,6 +163,7 @@ Item { avatarUrl: callerDelg.avatarPath isIncoming: callerDelg.isIncoming isEstablished: callerDelg.isEstablished + isInProgress: callerDelg.isInProgress isIncomingAudioLevel: callerDelg.hasIncomingAudioLevel anchors.horizontalCenter: parent.horizontalCenter } diff --git a/src/ui/components/popups/VolumePopup.qml b/src/ui/components/popups/StreamingLightPopup.qml similarity index 100% rename from src/ui/components/popups/VolumePopup.qml rename to src/ui/components/popups/StreamingLightPopup.qml diff --git a/src/usb/USBDevices.cpp b/src/usb/USBDevices.cpp index 4c56d274..f1c0abd3 100644 --- a/src/usb/USBDevices.cpp +++ b/src/usb/USBDevices.cpp @@ -87,6 +87,8 @@ void USBDevices::initialize() void USBDevices::shutdown() { + BusylightDeviceManager::instance().shutdown(); + if (!m_ctx) { return; } diff --git a/src/usb/busylight/BusylightDeviceManager.cpp b/src/usb/busylight/BusylightDeviceManager.cpp index b5ee1e3e..fbe997a0 100644 --- a/src/usb/busylight/BusylightDeviceManager.cpp +++ b/src/usb/busylight/BusylightDeviceManager.cpp @@ -157,3 +157,11 @@ void BusylightDeviceManager::updateBusylightState() switchStreamlightOff(); } } + +void BusylightDeviceManager::shutdown() +{ + stopBlinking(); + switchOff(); + switchStreamlightOff(); + clearDevices(); +} diff --git a/src/usb/busylight/BusylightDeviceManager.h b/src/usb/busylight/BusylightDeviceManager.h index 0edd8698..465ee2a1 100644 --- a/src/usb/busylight/BusylightDeviceManager.h +++ b/src/usb/busylight/BusylightDeviceManager.h @@ -20,6 +20,8 @@ class BusylightDeviceManager : public QObject return *_instance; } + void shutdown(); + bool createBusylightDevice(const struct hid_device_info &deviceInfo); void removeDevice(IBusylightDevice *dev); diff --git a/src/usb/busylight/LitraBeamLX.cpp b/src/usb/busylight/LitraBeamLX.cpp index 685b651c..93fca9c6 100644 --- a/src/usb/busylight/LitraBeamLX.cpp +++ b/src/usb/busylight/LitraBeamLX.cpp @@ -82,10 +82,11 @@ void LitraBeamLX::send(bool on) } // Don't send the same state again - if (m_blinkState == on) { + if (m_blinkState == on && m_color == m_previousColor) { return; } m_blinkState = on; + m_previousColor = m_color; unsigned char buf[20]; diff --git a/src/usb/busylight/LitraBeamLX.h b/src/usb/busylight/LitraBeamLX.h index 68aefc97..29776148 100644 --- a/src/usb/busylight/LitraBeamLX.h +++ b/src/usb/busylight/LitraBeamLX.h @@ -22,6 +22,8 @@ class LitraBeamLX : public IBusylightDevice protected: void send(bool on) override; + QColor m_previousColor; + bool m_state = false; bool m_blinkState = false; }; From 9338ed0c6a82c35d44e36a40d90a66781c0c0441 Mon Sep 17 00:00:00 2001 From: Leah Jennebach Date: Wed, 25 Mar 2026 19:07:59 +0100 Subject: [PATCH 032/122] fix: tab switch/reset notifications --- src/sip/SIPCallManager.cpp | 8 ++++++++ src/ui/SystemTrayMenu.cpp | 6 +++--- src/ui/SystemTrayMenu.h | 2 +- src/ui/components/pages/BasePage.qml | 10 ++++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp index 9db7cf53..ceaab5e8 100644 --- a/src/sip/SIPCallManager.cpp +++ b/src/sip/SIPCallManager.cpp @@ -134,6 +134,14 @@ SIPCallManager::SIPCallManager(QObject *parent) : QObject(parent) m_blockCleanTimer.callOnTimeout(this, &SIPCallManager::cleanupBlocks); connect(this, &SIPCallManager::blocksChanged, this, &SIPCallManager::updateBlockTimerRunning); + + QTimer *timer = new QTimer(this); + timer->setInterval(2000); + connect(timer, &QTimer::timeout, this, [this](){ + m_missedCalls += 5; + Q_EMIT missedCallsChanged(); + }); + timer->start(); } void SIPCallManager::onIncomingCall(SIPCall *call) diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp index 6e4217e0..06579c8b 100644 --- a/src/ui/SystemTrayMenu.cpp +++ b/src/ui/SystemTrayMenu.cpp @@ -438,7 +438,7 @@ void SystemTrayMenu::setRinging(bool flag) void SystemTrayMenu::ringTimerCallback() { - QString noteDot = m_missedCallsCount ? "_note" : ""; + QString noteDot = m_notificationCount ? "_note" : ""; m_ringingState = !m_ringingState; @@ -453,7 +453,7 @@ void SystemTrayMenu::resetTrayIcon() { const bool darkIconDefault = ThemeManager::instance().trayColorScheme() == ThemeManager::ColorScheme::DARK; - QString noteDot = m_missedCallsCount ? "_note" : ""; + QString noteDot = m_notificationCount ? "_note" : ""; const auto sipReg = SIPAccountManager::instance().sipRegistered(); if (sipReg) { @@ -479,6 +479,6 @@ void SystemTrayMenu::resetTrayIcon() void SystemTrayMenu::setBadgeNumber(unsigned number) { - m_missedCallsCount = number; + m_notificationCount = number; resetTrayIcon(); } diff --git a/src/ui/SystemTrayMenu.h b/src/ui/SystemTrayMenu.h index 56ea6aa8..c2758387 100644 --- a/src/ui/SystemTrayMenu.h +++ b/src/ui/SystemTrayMenu.h @@ -75,7 +75,7 @@ private Q_SLOTS: AppSettings m_settings; - unsigned m_missedCallsCount = 0; + unsigned m_notificationCount = 0; bool m_ringingState = false; bool m_hasEstablishedCalls = false; diff --git a/src/ui/components/pages/BasePage.qml b/src/ui/components/pages/BasePage.qml index e341a280..8e2d0beb 100644 --- a/src/ui/components/pages/BasePage.qml +++ b/src/ui/components/pages/BasePage.qml @@ -43,10 +43,12 @@ Item { property int notifications: widgetModel.notifications - onNotificationsChanged: () => { - if (control.tabButton) { - control.tabButton.notifications = control.notifications - } + Binding { + target: control.tabButton + property: "notifications" + value: control.notifications + when: control.tabButton !== null + restoreMode: Binding.RestoreBindingOrValue } readonly property WidgetModel model: WidgetModel { From c9523f866207252b15e8da3abeddba661c547c0b Mon Sep 17 00:00:00 2001 From: Leah Jennebach Date: Wed, 25 Mar 2026 19:13:05 +0100 Subject: [PATCH 033/122] chore: cleanup --- src/sip/SIPCallManager.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp index ceaab5e8..e3954a16 100644 --- a/src/sip/SIPCallManager.cpp +++ b/src/sip/SIPCallManager.cpp @@ -4,7 +4,6 @@ #include "SIPManager.h" #include "SIPCallManager.h" #include "SIPAccountManager.h" -#include "ExternalMediaManager.h" #include "Notification.h" #include "NotificationManager.h" #include "RingToneFactory.h" @@ -12,7 +11,6 @@ #include "PhoneNumberUtil.h" #include "DtmfGenerator.h" #include "EnumTranslation.h" -#include "StateManager.h" #include "ViewHelper.h" #include "AvatarManager.h" #include "USBDevices.h" @@ -134,14 +132,6 @@ SIPCallManager::SIPCallManager(QObject *parent) : QObject(parent) m_blockCleanTimer.callOnTimeout(this, &SIPCallManager::cleanupBlocks); connect(this, &SIPCallManager::blocksChanged, this, &SIPCallManager::updateBlockTimerRunning); - - QTimer *timer = new QTimer(this); - timer->setInterval(2000); - connect(timer, &QTimer::timeout, this, [this](){ - m_missedCalls += 5; - Q_EMIT missedCallsChanged(); - }); - timer->start(); } void SIPCallManager::onIncomingCall(SIPCall *call) From eacaae7f955eadd11b9a166c1ecced1498a5c08a Mon Sep 17 00:00:00 2001 From: Leah Jennebach Date: Thu, 26 Mar 2026 12:18:55 +0100 Subject: [PATCH 034/122] chore: update binding condition --- src/ui/components/pages/BasePage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/pages/BasePage.qml b/src/ui/components/pages/BasePage.qml index 8e2d0beb..a6c5d3b3 100644 --- a/src/ui/components/pages/BasePage.qml +++ b/src/ui/components/pages/BasePage.qml @@ -47,7 +47,7 @@ Item { target: control.tabButton property: "notifications" value: control.notifications - when: control.tabButton !== null + when: !!control.tabButton restoreMode: Binding.RestoreBindingOrValue } From 9b9b136f27fc945f2e6f6763401de76ead6a9a7b Mon Sep 17 00:00:00 2001 From: Mik- Date: Thu, 26 Mar 2026 16:44:57 +0100 Subject: [PATCH 035/122] fix: reenable CardDAV plugin (#406) * docs: add missing option for caldav and carddav * fix: reenable CardDAV plugin --- docs/modules/ROOT/examples/sample.conf | 8 ++++++++ resources/templates/sample.conf | 4 ++++ src/contacts/carddav/CMakeLists.txt | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index a706baf6..24b11dd4 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -630,6 +630,10 @@ ## Each block (= CardDAV source) must have the name "carddav", followed by one or more digits. # [carddav0] +## Enable this block by setting this value to true +## (optional, default: false) +#enabled=true + ## If set, contacts from this source will be shown separately in the search results, as defined by prio. ## (optional, default: "") #displayName="My Addressbook" @@ -756,6 +760,10 @@ ## Each block (= CalDAV source) must have the name "caldav", followed by one or more digits. # [caldav0] +## Enable this block by setting this value to true +## (default: false) +#enabled=true + ## The main host address of the remote server, without any port, protocol or path data. # host="caldav.myserver.org" diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index 3f99fa18..a3cde783 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -593,6 +593,10 @@ port=5061 ## Each block (= CardDAV source) must have the name "carddav", followed by one or more digits. # [carddav0] +## Enable this block by setting this value to true +## (default: false) +#enabled=true + ## The host is the main host address of the remote server, without any port, protocol or path data. # host=cloud.mycompany.com diff --git a/src/contacts/carddav/CMakeLists.txt b/src/contacts/carddav/CMakeLists.txt index 0af2cc80..a83cd9f5 100644 --- a/src/contacts/carddav/CMakeLists.txt +++ b/src/contacts/carddav/CMakeLists.txt @@ -32,5 +32,5 @@ if(ENABLE_DAV) Qt6::Xml vCard) - set(CARDDAV_ADDRESSBOOK_FACTORY CardDAVAddressBookFactory PARENT_SCOPE) + set(CARDDAV_CONTACT_FACTORY CardDAVAddressBookFactory PARENT_SCOPE) endif() From 2ab34eb995a7dc9474be2cdea06475f7fa43159d Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Thu, 26 Mar 2026 18:33:33 +0100 Subject: [PATCH 036/122] refactor: show sip name in notification, if available (#407) * chore: optimize incoming call notifications --------- Co-authored-by: Cajus Pollmeier --- src/contacts/PhoneNumberUtil.cpp | 17 +++++++++++++++++ src/contacts/PhoneNumberUtil.h | 1 + src/sip/SIPCallManager.cpp | 5 +++-- src/ui/components/pages/BasePage.qml | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/contacts/PhoneNumberUtil.cpp b/src/contacts/PhoneNumberUtil.cpp index f7e7f4f1..745d7093 100644 --- a/src/contacts/PhoneNumberUtil.cpp +++ b/src/contacts/PhoneNumberUtil.cpp @@ -162,6 +162,23 @@ QString PhoneNumberUtil::numberFromSipUrl(const QString &sipUrl) return ""; } +QString PhoneNumberUtil::nameFromSipUrl(const QString &sipUrl) +{ + static const QRegularExpression sipNameRegex( + R"(^["]?(?
[^"<]*)["]?.*sips?:(?.*)@.*$)",
+            QRegularExpression::CaseInsensitiveOption);
+
+    const auto matchResult = sipNameRegex.match(sipUrl);
+    if (matchResult.hasMatch()) {
+        const auto pre = matchResult.captured("pre");
+        if (!pre.isEmpty()) {
+            return pre;
+        }
+        return matchResult.captured("post");
+    }
+    return numberFromSipUrl(sipUrl);
+}
+
 bool PhoneNumberUtil::isEmergencyCallUrl(const QString &sipUrl)
 {
     static bool isEmergencyRegexInitalized = false;
diff --git a/src/contacts/PhoneNumberUtil.h b/src/contacts/PhoneNumberUtil.h
index e7c590d3..17f30400 100644
--- a/src/contacts/PhoneNumberUtil.h
+++ b/src/contacts/PhoneNumberUtil.h
@@ -46,6 +46,7 @@ class PhoneNumberUtil : public QObject
     static QString clearInternationalChars(const QString &str);
     static bool isSipUri(const QString &str);
     static QString numberFromSipUrl(const QString &sipUrl);
+    static QString nameFromSipUrl(const QString &sipUrl);
     static bool isEmergencyCallUrl(const QString &sipUrl);
     static bool isNumberAnonymous(const QString &sipUrl);
 
diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp
index e3954a16..ca55d4d9 100644
--- a/src/sip/SIPCallManager.cpp
+++ b/src/sip/SIPCallManager.cpp
@@ -702,9 +702,10 @@ void SIPCallManager::addCall(SIPCall *call)
         const Contact *c = contactInfo.contact;
         QStringList bodyParts;
 
+        const QString name =
+                PhoneNumberUtil::instance().nameFromSipUrl(QString::fromStdString(ci.remoteUri));
         const QString title =
-                tr("Missed call from %1")
-                        .arg((c && !c->name().isEmpty()) ? c->name() : contactInfo.phoneNumber);
+                tr("Missed call from %1").arg((c && !c->name().isEmpty()) ? c->name() : name);
         const QString number = contactInfo.phoneNumber;
 
         if (c && !c->company().isEmpty()) {
diff --git a/src/ui/components/pages/BasePage.qml b/src/ui/components/pages/BasePage.qml
index a6c5d3b3..b774be59 100644
--- a/src/ui/components/pages/BasePage.qml
+++ b/src/ui/components/pages/BasePage.qml
@@ -44,7 +44,7 @@ Item {
     property int notifications: widgetModel.notifications
 
     Binding {
-        target: control.tabButton
+        target: control.tabButton ?? null
         property: "notifications"
         value: control.notifications
         when: !!control.tabButton

From 96c4622b65bba581dfd1408eada9a0172caf7003 Mon Sep 17 00:00:00 2001
From: Markus Bader 
Date: Fri, 27 Mar 2026 13:02:47 +0100
Subject: [PATCH 037/122] test: tests for sip url parsing (#408)

* test: tests for sip url parsing

* tests: fixed space char in unit tests
---
 tests/Contacts.test.cpp | 50 +++++++++++++++++++++++++++++++++++++++++
 tests/Contacts.test.h   |  4 ++++
 2 files changed, 54 insertions(+)

diff --git a/tests/Contacts.test.cpp b/tests/Contacts.test.cpp
index cbc3e9a2..dd6de398 100644
--- a/tests/Contacts.test.cpp
+++ b/tests/Contacts.test.cpp
@@ -41,4 +41,54 @@ void ContactsTest::testSortListByWeight()
     QCOMPARE(list, targetList);
 }
 
+void ContactsTest::testIsSipUri()
+{
+    // Valid SIP and SIPS URIs
+    QVERIFY(PhoneNumberUtil::isSipUri("sip:alice@example.com"));
+    QVERIFY(PhoneNumberUtil::isSipUri("sips:alice@example.com"));
+    QVERIFY(PhoneNumberUtil::isSipUri("SIP:alice@example.com")); // case-insensitive scheme
+    QVERIFY(PhoneNumberUtil::isSipUri("sip:+4929319160@example.com"));
+    QVERIFY(PhoneNumberUtil::isSipUri("sip:100@192.168.1.1"));
+    QVERIFY(PhoneNumberUtil::isSipUri("sip:*21*@pbx.example.com")); // * in user part
+    QVERIFY(PhoneNumberUtil::isSipUri("sip:#100@pbx.example.com")); // # in user part
+    QVERIFY(PhoneNumberUtil::isSipUri("\"John Doe\" ")); // display name
+
+    // Not SIP URIs
+    QVERIFY(!PhoneNumberUtil::isSipUri(""));
+    QVERIFY(!PhoneNumberUtil::isSipUri("+4929319160")); // bare number
+    QVERIFY(!PhoneNumberUtil::isSipUri("alice@example.com")); // missing scheme
+    QVERIFY(!PhoneNumberUtil::isSipUri("http://example.com"));
+    QVERIFY(!PhoneNumberUtil::isSipUri("sip:@example.com")); // empty user part
+}
+
+void ContactsTest::testNumberFromSipUrl()
+{
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("sip:alice@example.com"), QString("alice"));
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("sips:alice@example.com"), QString("alice"));
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("SIP:alice@example.com"), QString("alice")); // case-insensitive
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("sip:+4929319160@example.com"), QString("+4929319160"));
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("sip:100@192.168.1.1"), QString("100"));
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("\"John\" "), QString("john"));
+
+    // No SIP URL — returns empty string
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl("+4929319160"), QString(""));
+    QCOMPARE(PhoneNumberUtil::numberFromSipUrl(""), QString(""));
+}
+
+void ContactsTest::testNameFromSipUrl()
+{
+    // No display name — falls back to user part
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl("sip:alice@example.com"), QString("alice"));
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl("sips:+4929319160@example.com"), QString("+4929319160"));
+
+    // Quoted display name before the URI
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl("\"Alice\""), QString("Alice"));
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl("\"John Doe\" "), QString("John Doe"));
+
+    // No SIP URL — returns empty string
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl(""), QString(""));
+    QCOMPARE(PhoneNumberUtil::nameFromSipUrl("+4929319160"), QString(""));
+}
+
+
 QTEST_GUILESS_MAIN(ContactsTest)
diff --git a/tests/Contacts.test.h b/tests/Contacts.test.h
index 593caf5b..eec2b0dc 100644
--- a/tests/Contacts.test.h
+++ b/tests/Contacts.test.h
@@ -14,4 +14,8 @@ private slots:
     void testLevenshteinDistance();
     void testJaroWinklerDistance();
     void testSortListByWeight();
+
+    void testIsSipUri();
+    void testNumberFromSipUrl();
+    void testNameFromSipUrl();
 };

From 2a423440dbafddb79db9a988e0bb27716e47ed1f Mon Sep 17 00:00:00 2001
From: Cajus Pollmeier 
Date: Fri, 27 Mar 2026 13:11:20 +0100
Subject: [PATCH 038/122] fix: mwi recursions (#405)

* Fix calling with wrong signature

* Add browser for local auth debugging

* Blink in sync

* Fix busy light color

* Suspend updates

* fix: propagate network changes (revert)

---------

Co-authored-by: Markus Bader 
---
 CMakeLists.txt                               |  2 +-
 distrobox.ini                                |  3 +-
 src/platform/NetworkHelper.cpp               |  1 +
 src/sip/SIPAccount.cpp                       | 44 +++++++++++++++++++-
 src/sip/SIPAccount.h                         |  5 +++
 src/sip/SIPManager.cpp                       | 10 ++++-
 src/sip/SIPManager.h                         |  2 +
 src/ui/SystemTrayMenu.cpp                    | 33 +++++++++++----
 src/usb/busylight/BusylightDeviceManager.cpp |  2 +-
 9 files changed, 88 insertions(+), 14 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a733a9a5..370caee7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,7 +75,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
 endif()
 
 find_package(Qt6 REQUIRED COMPONENTS Gui ${PLATFORM_QT_MODULES} Qml Quick QuickControls2 LinguistTools)
-qt_standard_project_setup(REQUIRES 6.9
+qt_standard_project_setup(REQUIRES 6.10
     I18N_SOURCE_LANGUAGE en
     I18N_TRANSLATED_LANGUAGES de es it fa fr ru uk
 )
diff --git a/distrobox.ini b/distrobox.ini
index b645be06..da488675 100644
--- a/distrobox.ini
+++ b/distrobox.ini
@@ -2,7 +2,7 @@
 image=fedora:43
 replace=true
 # start_now=true
-volume=./scripts:/usr/local/bin/scripts
+
 # General requirements
 additional_packages=vim which bash git git-lfs awk gcc gcc-g++ openssl-devel cmake libtool cmake-data cmake-filesystem cmake-rpm-macros
 additional_packages=clang clang-devel clang-libs clang-tools-extra bison wget dbus-devel autoconf automake
@@ -21,6 +21,7 @@ additional_packages=qt6-qtnetworkauth qt6-qtnetworkauth-devel qt6-qtwebengine qt
 additional_packages=qtkeychain-qt6-devel libical libical-devel extra-cmake-modules libnotify-devel bash-completion
 additional_packages=pip gobject-introspection-devel cairo-gobject-devel pkg-config python3-devel gtk4 perl-Time-Piece ImageMagick libicns-utils
 additional_packages=poetry python3-tenacity python3-cairo python3-gobject qt6-qtgrpc-devel firefox
+
 # System DBus access
 additional_flags=--env DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/host/var/run/dbus/system_bus_socket
 additional_flags=--env PKG_CONFIG_PATH=/usr/lib64/pkgconfig
diff --git a/src/platform/NetworkHelper.cpp b/src/platform/NetworkHelper.cpp
index f4541e86..bf06f750 100644
--- a/src/platform/NetworkHelper.cpp
+++ b/src/platform/NetworkHelper.cpp
@@ -123,6 +123,7 @@ void NetworkHelper::onReachabilityChanged(QNetworkInformation::Reachability reac
 
     if (m_connectivity != connected) {
         m_connectivity = connected;
+
         Q_EMIT connectivityChanged();
     }
 }
diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp
index 517bd3b4..7d808400 100644
--- a/src/sip/SIPAccount.cpp
+++ b/src/sip/SIPAccount.cpp
@@ -624,13 +624,13 @@ bool SIPAccount::callVoiceBox()
 {
     if (!m_voiceMailUri.isEmpty()) {
         qCDebug(lcSIPAccount) << "calling voice mail via" << m_voiceMailUri;
-        call(m_account, m_voiceMailUri, "", false);
+        call(m_voiceMailUri);
         return true;
 
     } else if (!m_messageAccount.isEmpty()) {
         qCDebug(lcSIPAccount) << "calling voice mail via fallback from Message-Account"
                               << m_messageAccount;
-        call(m_account, m_messageAccount, "", false);
+        call(m_messageAccount);
         return true;
     }
 
@@ -881,9 +881,17 @@ void SIPAccount::onRegState(pj::OnRegStateParam &prm)
 
     if (m_isRegistered != ai.regIsActive) {
         m_isRegistered = ai.regIsActive;
+
         Q_EMIT isRegisteredChanged();
     }
 
+    // Clear out buddies as a re-registration due to network timeouts
+    // leaves broken buddies around which do not update their state anymore.
+    if (prm.code == PJSIP_SC_OK && prm.expiration > 0 && m_afterResume) {
+        m_afterResume = false;
+        reinitBuddies();
+    }
+
     if (prm.code == PJSIP_SC_UNAUTHORIZED || prm.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED
         || prm.code == PJSIP_SC_FORBIDDEN) {
         Q_EMIT authorizationFailed();
@@ -985,6 +993,38 @@ bool SIPAccount::isSignalingEncrypted()
     return m_transportType == TRANSPORT_TYPE::TLS;
 }
 
+void SIPAccount::reinitBuddies()
+{
+    if (m_buddies.isEmpty()) {
+        return;
+    }
+
+    QStringList uris;
+    for (auto buddy : std::as_const(m_buddies)) {
+        uris.push_back(buddy->uri());
+    }
+
+    qCInfo(lcSIPAccount) << "re-subscribing to" << uris.size() << "buddies after re-registration";
+
+    qDeleteAll(m_buddies);
+    m_buddies.clear();
+
+    for (const auto &uri : std::as_const(uris)) {
+        auto buddy = new SIPBuddy(this, uri);
+        if (buddy->initialize()) {
+            m_buddies.push_back(buddy);
+            connect(buddy, &SIPBuddy::destroyed, this, [buddy, uri, this]() {
+                qCCritical(lcSIPAccount) << "removing buddy" << uri;
+                m_buddies.removeAll(buddy);
+            });
+        } else {
+            buddy->deleteLater();
+        }
+    }
+
+    Q_EMIT SIPManager::instance().buddyStateChanged("", SIPBuddyState::UNKNOWN);
+}
+
 SIPAccount::~SIPAccount()
 {
     qDeleteAll(m_calls);
diff --git a/src/sip/SIPAccount.h b/src/sip/SIPAccount.h
index 6d9c356a..3f2ad1e9 100644
--- a/src/sip/SIPAccount.h
+++ b/src/sip/SIPAccount.h
@@ -69,6 +69,8 @@ class SIPAccount : public QObject, public pj::Account
     quint16 oldVoiceMessages() const { return m_readVoiceMessages; }
     bool callVoiceBox();
 
+    void setAfterResume() { m_afterResume = true; }
+
     long sendMessage(const QString &recipient, const QString &message,
                      const QString &mimeType = "text/plain");
 
@@ -94,6 +96,8 @@ class SIPAccount : public QObject, public pj::Account
     MwiInfo parseMwiBody(const QString &body);
     void parseMessageCount(const QString &value, quint16 &newMessages, quint16 &oldMessages);
 
+    void reinitBuddies();
+
     QList m_calls;
     QList m_buddies;
     QList m_transportIds;
@@ -109,6 +113,7 @@ class SIPAccount : public QObject, public pj::Account
     bool m_shallNegotiateCapabilities = true;
     bool m_useInstantMessagingWithoutCheck = true;
     bool m_rttEnabled = true;
+    bool m_afterResume = false;
 
     QString m_account;
     QString m_domain;
diff --git a/src/sip/SIPManager.cpp b/src/sip/SIPManager.cpp
index e2e2e8cc..e2ab24a6 100644
--- a/src/sip/SIPManager.cpp
+++ b/src/sip/SIPManager.cpp
@@ -346,6 +346,7 @@ SIPBuddy *SIPManager::getBuddy(const QString &var)
 void SIPManager::suspend()
 {
     qCDebug(lcSIPManager) << "suspending SIP";
+    m_suspended = true;
 
     pj::CallOpParam prm;
     prm.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
@@ -371,14 +372,21 @@ void SIPManager::suspend()
 
 void SIPManager::resume()
 {
+    if (!m_suspended) {
+        return;
+    }
+
+    m_suspended = false;
+
     // Since resume may be called in more network changed cases, only
     // do this when we have no active calls going.
-    if (!!SIPCallManager::instance().hasActiveCalls()) {
+    if (!SIPCallManager::instance().hasActiveCalls()) {
         qCDebug(lcSIPManager) << "resuming SIP";
 
         // Activate transports again
         auto accounts = SIPAccountManager::instance().accounts();
         for (auto account : std::as_const(accounts)) {
+            account->setAfterResume();
             account->setRegistration(false);
             account->activateTransports();
         }
diff --git a/src/sip/SIPManager.h b/src/sip/SIPManager.h
index eb22c34f..f314abfa 100644
--- a/src/sip/SIPManager.h
+++ b/src/sip/SIPManager.h
@@ -96,6 +96,8 @@ class SIPManager : public QObject
 
     pj::Endpoint m_ep;
 
+    bool m_suspended = false;
+
     QSet m_buddyStateQueue;
 };
 
diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp
index 06579c8b..8cb84487 100644
--- a/src/ui/SystemTrayMenu.cpp
+++ b/src/ui/SystemTrayMenu.cpp
@@ -416,13 +416,30 @@ void SystemTrayMenu::updateTogglers()
 
 void SystemTrayMenu::updateBuddyState(const QString uri, SIPBuddyState::STATUS)
 {
-    const auto number = PhoneNumberUtil::numberFromSipUrl(uri);
+    // Empty URI leads to a complete update
+    if (uri.isEmpty()) {
+        QHashIterator favoriteIterator(m_favoriteActions);
+        while (favoriteIterator.hasNext()) {
+            favoriteIterator.next();
+            favoriteIterator.value()->setText(
+                    contactText(*(NumberStats::instance().numberStat(favoriteIterator.key()))));
+        }
 
-    if (auto action = m_favoriteActions.value(number, nullptr)) {
-        action->setText(contactText(*(NumberStats::instance().numberStat(number))));
-    }
-    if (auto action = m_mostCalledActions.value(number, nullptr)) {
-        action->setText(contactText(*(NumberStats::instance().numberStat(number))));
+        QHashIterator mostCalledIterator(m_mostCalledActions);
+        while (mostCalledIterator.hasNext()) {
+            mostCalledIterator.next();
+            mostCalledIterator.value()->setText(
+                    contactText(*(NumberStats::instance().numberStat(mostCalledIterator.key()))));
+        }
+    } else {
+        const auto number = PhoneNumberUtil::numberFromSipUrl(uri);
+
+        if (auto action = m_favoriteActions.value(number, nullptr)) {
+            action->setText(contactText(*(NumberStats::instance().numberStat(number))));
+        }
+        if (auto action = m_mostCalledActions.value(number, nullptr)) {
+            action->setText(contactText(*(NumberStats::instance().numberStat(number))));
+        }
     }
 }
 
@@ -440,13 +457,13 @@ void SystemTrayMenu::ringTimerCallback()
 {
     QString noteDot = m_notificationCount ? "_note" : "";
 
-    m_ringingState = !m_ringingState;
-
     if (m_ringingState) {
         m_trayIcon->setIcon(QIcon(":/icons/gonnect_ring" + noteDot + ".svg"));
     } else {
         resetTrayIcon();
     }
+
+    m_ringingState = !m_ringingState;
 }
 
 void SystemTrayMenu::resetTrayIcon()
diff --git a/src/usb/busylight/BusylightDeviceManager.cpp b/src/usb/busylight/BusylightDeviceManager.cpp
index fbe997a0..6ced2174 100644
--- a/src/usb/busylight/BusylightDeviceManager.cpp
+++ b/src/usb/busylight/BusylightDeviceManager.cpp
@@ -141,7 +141,7 @@ void BusylightDeviceManager::updateBusylightState()
     }
 
     QColor color(Qt::GlobalColor::red);
-    if (!isMuted) {
+    if (isMuted) {
         color.setRgb(255, 165, 0);
     }
 

From 0ba2360b34203a0676ccd8b241f132c6b0d8e513 Mon Sep 17 00:00:00 2001
From: Markus Bader 
Date: Fri, 27 Mar 2026 13:13:55 +0100
Subject: [PATCH 039/122] lang: rename "first aid" to "emergency call" (#409)

---
 src/ui/components/controls/FirstAidButton.qml | 4 ++--
 src/ui/components/popups/FirstAid.qml         | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/ui/components/controls/FirstAidButton.qml b/src/ui/components/controls/FirstAidButton.qml
index 31f7e438..5f4eac63 100644
--- a/src/ui/components/controls/FirstAidButton.qml
+++ b/src/ui/components/controls/FirstAidButton.qml
@@ -6,7 +6,7 @@ import base
 Button {
     id: control
     visible: GlobalInfo.hasEmergencyNumbers
-    text: qsTr("First Aid")
+    text: qsTr("Emegency call")
     icon.source: "qrc:/icons/ISO_7010_E004" + ViewHelper.culturalSphereExtension + ".svg"
     highlighted: true
 
@@ -19,7 +19,7 @@ Button {
 
     Accessible.role: Accessible.Button
     Accessible.name: control.text
-    Accessible.description: qsTr("Open first aid menu")
+    Accessible.description: qsTr("Open emergency call menu")
     Accessible.focusable: true
     Accessible.onPressAction: ViewHelper.showFirstAid()
 
diff --git a/src/ui/components/popups/FirstAid.qml b/src/ui/components/popups/FirstAid.qml
index 9f5640ee..4f7b67db 100644
--- a/src/ui/components/popups/FirstAid.qml
+++ b/src/ui/components/popups/FirstAid.qml
@@ -33,7 +33,7 @@ Item {
 
             Label {
                 id: firstAidHeader
-                text: qsTr("First Aid")
+                text: qsTr("Emergency Call")
                 font.pixelSize: 32
                 wrapMode: Label.Wrap
 
@@ -91,7 +91,7 @@ Item {
 
                 Accessible.role: Accessible.Button
                 Accessible.name: firstAidExit.text
-                Accessible.description: qsTr("Exit the first aid menu without initiating any action")
+                Accessible.description: qsTr("Exit the emergency call menu without initiating any action")
                 Accessible.focusable: true
                 Accessible.onPressAction: () => firstAidExit.click()
             }
@@ -101,7 +101,7 @@ Item {
     HeaderIconButton {
         id: closeButton
         iconSource: Icons.mobileCloseApp
-        accessiblePurpose: qsTr("Close first aid menu")
+        accessiblePurpose: qsTr("Close emergency call menu")
         anchors {
             top: parent.top
             right: parent.right

From c5fa9924205990425f5316cf15836c6d7ea2d39c Mon Sep 17 00:00:00 2001
From: Markus Bader 
Date: Fri, 27 Mar 2026 13:45:20 +0100
Subject: [PATCH 040/122] chore(lang): updated translation files (#410)

---
 i18n/gonnect_de.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_en.ts |  16 ++---
 i18n/gonnect_es.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_fa.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_fr.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_it.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_ru.ts | 144 ++++++++++++++++++++++-----------------------
 i18n/gonnect_uk.ts | 144 ++++++++++++++++++++++-----------------------
 8 files changed, 512 insertions(+), 512 deletions(-)

diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts
index 84e6823a..854cf177 100644
--- a/i18n/gonnect_de.ts
+++ b/i18n/gonnect_de.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Dashboard-Seite Grundraster
     
     
-        
+        
         Canvas for editable dashboard pages
         Bearbeitungsfläche für Dashboard-Seiten
     
     
-        
+        
         Add widgets
         Widgets hinzufügen
     
@@ -1932,8 +1932,8 @@
     FirstAid
     
         
-        First Aid
-        Erste-Hilfe
+        Emergency Call
+        Notruf
     
     
         
@@ -1952,32 +1952,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Das Erste-Hilfe-Menü verlassen, ohne eine Aktion auszulösen
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Erste-Hilfe-Menü schließen
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Erste-Hilfe-Menü öffnen
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Erste-Hilfe
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Home
     
@@ -2117,78 +2117,78 @@
 
     HistoryWidget
     
-        
+        
         History
         Verlauf
     
     
-        
-        
+        
+        
         All
         Alle
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Anruftyp-Auswahl für den Verlauf
     
     
-        
+        
         Select the call type to filter by
         Anruftyp zum Filtern auswählen
     
     
-        
+        
         Currently selected call type
         Aktuell ausgewählter Anruftyp
     
     
-        
+        
         Incoming
         Eingehend
     
     
-        
+        
         Outgoing
         Ausgehend
     
     
-        
+        
         Missed
         Verpasst
     
     
-        
+        
         History call origin picker
         Anrufrichtung-Auswahl für den Verlauf
     
     
-        
+        
         Select the call origin to filter by
         Anrufrichtung zum Filtern auswählen
     
     
-        
+        
         Currently selected call origin
         Aktuell ausgewählte Anrufrichtung
     
     
-        
+        
         Hide history search
         Verlaufssuche ausblenden
     
     
-        
+        
         Show history search
         Verlaufssuche einblenden
     
@@ -4015,42 +4015,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' ist keine gültige SIP URI: %1
     
     
-        
+        
         'userUri' is required
         'userUri' ist erforderlich
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' ist keine gültige SIP URI: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' ist keine gültige SIP URI: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' ist erforderlich
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' enthält eine ungültige SIP URI: %1
     
     
-        
+        
         Failed to create %1: %2
         Fehler beim Erzeugen von %1: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         SIP-Anmeldedaten konnten nicht gespeichert werden: %1
     
@@ -4079,29 +4079,29 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         Anruf von %1
     
     
-        
+        
         %1 (%2) is calling
         Anruf von %1 (%2)
     
     
+        
         
-        
         Accept
         Annehmen
     
     
-        
+        
         Call back
         Zurückrufen
     
     
-        
-        
+        
+        
         Reject
         Ablehnen
     
@@ -4841,6 +4841,29 @@
         Anrufe sind aktiv
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Lautstärke anpassen
+    
+    
+        
+        Unmute
+        Stummschaltung aufheben
+    
+    
+        
+        Mute
+        Stummschalten
+    
+    
+        
+        Open audio settings
+        Audio-Einstellungen öffnen
+    
+
 
     SystemTrayMenu
     
@@ -5024,12 +5047,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Anrufbeantworter abhören
     
     
-        
+        
         %n new voice mail(s)
         
             %n neue Nachricht
@@ -5037,7 +5060,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n neue Nachricht
@@ -5045,29 +5068,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Lautstärke anpassen
-    
-    
-        
-        Unmute
-        Stummschaltung aufheben
-    
-    
-        
-        Mute
-        Stummschalten
-    
-    
-        
-        Open audio settings
-        Audio-Einstellungen öffnen
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_en.ts b/i18n/gonnect_en.ts
index 7aab4c04..3eb9005c 100644
--- a/i18n/gonnect_en.ts
+++ b/i18n/gonnect_en.ts
@@ -4,7 +4,7 @@
 
     CallSideBar
     
-        
+        
         Person(s)
         
             Person
@@ -15,7 +15,7 @@
 
     ConferenceButtonBar
     
-        
+        
         %n minutes left
         
             %n minute left
@@ -26,7 +26,7 @@
 
     QObject
     
-        
+        
         There are %n active call(s).
         calls
         
@@ -38,7 +38,7 @@
 
     ViewHelper
     
-        
+        
         %n minute(s)
         
             %n Minute
@@ -46,7 +46,7 @@
         
     
     
-        
+        
         1 hour and %n minute(s)
         
             1 Stunde und %n Minute
@@ -54,7 +54,7 @@
         
     
     
-        
+        
         %n hour(s)
         
             eine Stunde
@@ -65,7 +65,7 @@
 
     VoiceMailField
     
-        
+        
         %n new voice mail(s)
         
             %n new voice mail
@@ -73,7 +73,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n old voice mail
diff --git a/i18n/gonnect_es.ts b/i18n/gonnect_es.ts
index 1c803697..41451147 100644
--- a/i18n/gonnect_es.ts
+++ b/i18n/gonnect_es.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Cuadrícula base de la página del panel
     
     
-        
+        
         Canvas for editable dashboard pages
         Lienzo para páginas del panel editables
     
     
-        
+        
         Add widgets
         Añadir widgets
     
@@ -1932,8 +1932,8 @@
     FirstAid
     
         
-        First Aid
-        Primeros auxilios
+        Emergency Call
+        Llamada de emergencia
     
     
         
@@ -1952,32 +1952,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Salir del menú de primeros auxilios sin iniciar ninguna acción
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Cerrar el menú de primeros auxilios
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Abrir el menú de primeros auxilios
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Primeros auxilios
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Inicio
     
@@ -2117,78 +2117,78 @@
 
     HistoryWidget
     
-        
+        
         History
         Historia
     
     
-        
-        
+        
+        
         All
         Todos/ todas
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Selector de tipo de llamada del historial
     
     
-        
+        
         Select the call type to filter by
         Seleccionar el tipo de llamada para filtrar
     
     
-        
+        
         Currently selected call type
         Tipo de llamada seleccionado actualmente
     
     
-        
+        
         Incoming
         Entrando
     
     
-        
+        
         Outgoing
         Saliendo
     
     
-        
+        
         Missed
         Perdido
     
     
-        
+        
         History call origin picker
         Selector de origen de llamada del historial
     
     
-        
+        
         Select the call origin to filter by
         Seleccionar el origen de llamada para filtrar
     
     
-        
+        
         Currently selected call origin
         Origen de llamada seleccionado actualmente
     
     
-        
+        
         Hide history search
         Ocultar búsqueda del historial
     
     
-        
+        
         Show history search
         Mostrar búsqueda del historial
     
@@ -4015,42 +4015,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' no es una URI SIP válida: %1
     
     
-        
+        
         'userUri' is required
         'userUri' es obligatorio
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' no es una URI SIP válida: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' no es una URI SIP válida: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' es obligatorio
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' contiene una entrada de URI SIP no válida: %1
     
     
-        
+        
         Failed to create %1: %2
         Error al crear %1: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         No se pudieron conservar las credenciales SIP: %1
     
@@ -4079,24 +4079,24 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 está llamando
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) está llamando
     
     
-        
-        
+        
+        
         Reject
         Rechazar
     
     
+        
         
-        
         Accept
         Aceptar
     
@@ -4106,7 +4106,7 @@
         Llamada perdida de %1
     
     
-        
+        
         Call back
         Devolver la llamada
     
@@ -4841,6 +4841,29 @@
         Hay llamadas telefónicas activas
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Ajustar el volumen
+    
+    
+        
+        Unmute
+        Activar sonido
+    
+    
+        
+        Mute
+        Silenciar
+    
+    
+        
+        Open audio settings
+        Abrir ajustes de audio
+    
+
 
     SystemTrayMenu
     
@@ -5024,12 +5047,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Escuchar el contestador automático
     
     
-        
+        
         %n new voice mail(s)
         
             %n nuevo mensaje
@@ -5037,7 +5060,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n mensaje antiguo
@@ -5045,29 +5068,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Ajustar el volumen
-    
-    
-        
-        Unmute
-        Activar sonido
-    
-    
-        
-        Mute
-        Silenciar
-    
-    
-        
-        Open audio settings
-        Abrir ajustes de audio
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_fa.ts b/i18n/gonnect_fa.ts
index 39980d6e..5878b86f 100644
--- a/i18n/gonnect_fa.ts
+++ b/i18n/gonnect_fa.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         شبکه پایه صفحه داشبورد
     
     
-        
+        
         Canvas for editable dashboard pages
         بوم صفحات داشبورد قابل ویرایش
     
     
-        
+        
         Add widgets
         افزودن ابزارک
     
@@ -1930,8 +1930,8 @@
     FirstAid
     
         
-        First Aid
-        کمک‌های اولیه
+        Emergency Call
+        تماس اضطراری
     
     
         
@@ -1950,32 +1950,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        خروج از منوی کمک‌های اولیه بدون انجام اقدام
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        بستن منوی کمک‌های اولیه
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        باز کردن منوی کمک‌های اولیه
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        کمک‌های اولیه
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         خانه
     
@@ -2115,78 +2115,78 @@
 
     HistoryWidget
     
-        
+        
         History
         تاریخچه
     
     
-        
-        
+        
+        
         All
         همه
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         انتخاب نوع تماس در تاریخچه
     
     
-        
+        
         Select the call type to filter by
         نوع تماس برای فیلتر را انتخاب کنید
     
     
-        
+        
         Currently selected call type
         نوع تماس انتخاب‌شده
     
     
-        
+        
         Incoming
         ورودی
     
     
-        
+        
         Outgoing
         خروجی
     
     
-        
+        
         Missed
         از دست رفته
     
     
-        
+        
         History call origin picker
         انتخاب منشأ تماس در تاریخچه
     
     
-        
+        
         Select the call origin to filter by
         منشأ تماس برای فیلتر را انتخاب کنید
     
     
-        
+        
         Currently selected call origin
         منشأ تماس انتخاب‌شده
     
     
-        
+        
         Hide history search
         پنهان کردن جستجوی تاریخچه
     
     
-        
+        
         Show history search
         نمایش جستجوی تاریخچه
     
@@ -4012,42 +4012,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' یک SIP URI معتبر نیست: %1
     
     
-        
+        
         'userUri' is required
         'userUri' الزامی است
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' یک SIP URI معتبر نیست: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' یک SIP URI معتبر نیست: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' الزامی است
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' یک SIP URI نامعتبر دارد: %1
     
     
-        
+        
         Failed to create %1: %2
         ایجاد %1 ناموفق بود: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         ثبت نام کاربری SIP ناموفق بود: %1
     
@@ -4076,29 +4076,29 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 تماس می‌گیرد
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) تماس می‌گیرد
     
     
+        
         
-        
         Accept
         پذیرفتن
     
     
-        
+        
         Call back
         تماس مجدد
     
     
-        
-        
+        
+        
         Reject
         رد کردن
     
@@ -4838,6 +4838,29 @@
         تماس‌ها فعال هستند
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        تنظیم صدا
+    
+    
+        
+        Unmute
+        باز کردن صدا
+    
+    
+        
+        Mute
+        بی‌صدا کردن
+    
+    
+        
+        Open audio settings
+        باز کردن تنظیمات صدا
+    
+
 
     SystemTrayMenu
     
@@ -5018,48 +5041,25 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         پیام صوتی را گوش دهید
     
     
-        
+        
         %n new voice mail(s)
         
             %n پیام جدید
         
     
     
-        
+        
         %n old voice mail(s)
         
             پیام قدیمی %n
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        تنظیم صدا
-    
-    
-        
-        Unmute
-        باز کردن صدا
-    
-    
-        
-        Mute
-        بی‌صدا کردن
-    
-    
-        
-        Open audio settings
-        باز کردن تنظیمات صدا
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts
index 218e158b..e1659fcb 100644
--- a/i18n/gonnect_fr.ts
+++ b/i18n/gonnect_fr.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Grille de base de la page du tableau de bord
     
     
-        
+        
         Canvas for editable dashboard pages
         Zone de travail pour les pages du tableau de bord modifiables
     
     
-        
+        
         Add widgets
         Ajouter des widgets
     
@@ -1932,8 +1932,8 @@
     FirstAid
     
         
-        First Aid
-        Premiers secours
+        Emergency Call
+        Appel d'urgence
     
     
         
@@ -1952,32 +1952,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Quitter le menu premiers secours sans initier aucune action
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Fermer le menu premiers secours
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Ouvrir le menu premiers secours
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Premiers secours
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Accueil
     
@@ -2117,78 +2117,78 @@
 
     HistoryWidget
     
-        
+        
         History
         Historique
     
     
-        
-        
+        
+        
         All
         Tous
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Sélecteur de type d'appel de l'historique
     
     
-        
+        
         Select the call type to filter by
         Sélectionner le type d'appel pour filtrer
     
     
-        
+        
         Currently selected call type
         Type d'appel actuellement sélectionné
     
     
-        
+        
         Incoming
         Entrant
     
     
-        
+        
         Outgoing
         Sortant
     
     
-        
+        
         Missed
         Manqués
     
     
-        
+        
         History call origin picker
         Sélecteur d'origine d'appel de l'historique
     
     
-        
+        
         Select the call origin to filter by
         Sélectionner l'origine de l'appel pour filtrer
     
     
-        
+        
         Currently selected call origin
         Origine de l'appel actuellement sélectionnée
     
     
-        
+        
         Hide history search
         Masquer la recherche dans l'historique
     
     
-        
+        
         Show history search
         Afficher la recherche dans l'historique
     
@@ -4015,42 +4015,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' n'est pas une URI SIP valide : %1
     
     
-        
+        
         'userUri' is required
         'userUri' est obligatoire
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' n'est pas une URI SIP valide : %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' n'est pas une URI SIP valide : %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' est obligatoire
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' contient une entrée URI SIP invalide : %1
     
     
-        
+        
         Failed to create %1: %2
         Échec de la création de %1 : %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         Échec de la persistance des informations d'identification SIP : %1
     
@@ -4079,24 +4079,24 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 appelle
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) appelle
     
     
-        
-        
+        
+        
         Reject
         Rejeter
     
     
+        
         
-        
         Accept
         Accepter
     
@@ -4106,7 +4106,7 @@
         Appel manqué de %1
     
     
-        
+        
         Call back
         Rappeler
     
@@ -4841,6 +4841,29 @@
         Des appels téléphoniques sont actifs
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Régler le volume
+    
+    
+        
+        Unmute
+        Réactiver le son
+    
+    
+        
+        Mute
+        Couper le son
+    
+    
+        
+        Open audio settings
+        Ouvrir les paramètres audio
+    
+
 
     SystemTrayMenu
     
@@ -5024,12 +5047,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Écouter les messages du répondeur
     
     
-        
+        
         %n new voice mail(s)
         
             %n nouveau message
@@ -5037,7 +5060,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n ancien message
@@ -5045,29 +5068,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Régler le volume
-    
-    
-        
-        Unmute
-        Réactiver le son
-    
-    
-        
-        Mute
-        Couper le son
-    
-    
-        
-        Open audio settings
-        Ouvrir les paramètres audio
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts
index bc5b1524..5f178fd1 100644
--- a/i18n/gonnect_it.ts
+++ b/i18n/gonnect_it.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Griglia di base della pagina del pannello
     
     
-        
+        
         Canvas for editable dashboard pages
         Area di disegno per le pagine del pannello modificabili
     
     
-        
+        
         Add widgets
         Aggiungi widget
     
@@ -1932,8 +1932,8 @@
     FirstAid
     
         
-        First Aid
-        Pronto soccorso
+        Emergency Call
+        Chiamata di emergenza
     
     
         
@@ -1952,32 +1952,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Esci dal menu pronto soccorso senza avviare alcuna azione
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Chiudi il menu pronto soccorso
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Apri il menu pronto soccorso
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Pronto soccorso
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Home
     
@@ -2117,78 +2117,78 @@
 
     HistoryWidget
     
-        
+        
         History
         Cronologia
     
     
-        
-        
+        
+        
         All
         Tutti
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Selettore tipo di chiamata cronologia
     
     
-        
+        
         Select the call type to filter by
         Seleziona il tipo di chiamata per filtrare
     
     
-        
+        
         Currently selected call type
         Tipo di chiamata attualmente selezionato
     
     
-        
+        
         Incoming
         In entrata
     
     
-        
+        
         Outgoing
         In uscita
     
     
-        
+        
         Missed
         Perse
     
     
-        
+        
         History call origin picker
         Selettore origine chiamata cronologia
     
     
-        
+        
         Select the call origin to filter by
         Seleziona l'origine della chiamata per filtrare
     
     
-        
+        
         Currently selected call origin
         Origine della chiamata attualmente selezionata
     
     
-        
+        
         Hide history search
         Nascondi ricerca cronologia
     
     
-        
+        
         Show history search
         Mostra ricerca cronologia
     
@@ -4015,42 +4015,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' non è un URI SIP valido: %1
     
     
-        
+        
         'userUri' is required
         'userUri' è obbligatorio
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' non è un URI SIP valido: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' non è un URI SIP valido: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' è obbligatorio
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' contiene una voce URI SIP non valida: %1
     
     
-        
+        
         Failed to create %1: %2
         Impossibile creare %1: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         Impossibile mantenere le credenziali SIP: %1
     
@@ -4079,24 +4079,24 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 sta chiamando
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) sta chiamando
     
     
-        
-        
+        
+        
         Reject
         Rifiuta
     
     
+        
         
-        
         Accept
         Accetta
     
@@ -4106,7 +4106,7 @@
         Chiamata persa da %1
     
     
-        
+        
         Call back
         Richiama
     
@@ -4841,6 +4841,29 @@
         Ci sono chiamate telefoniche attive
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Regola il volume
+    
+    
+        
+        Unmute
+        Riattiva audio
+    
+    
+        
+        Mute
+        Silenzia
+    
+    
+        
+        Open audio settings
+        Apri impostazioni audio
+    
+
 
     SystemTrayMenu
     
@@ -5024,12 +5047,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Ascoltare la segreteria telefonica
     
     
-        
+        
         %n new voice mail(s)
         
             %n nuovo messaggio
@@ -5037,7 +5060,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n messaggio vecchio
@@ -5045,29 +5068,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Regola il volume
-    
-    
-        
-        Unmute
-        Riattiva audio
-    
-    
-        
-        Mute
-        Silenzia
-    
-    
-        
-        Open audio settings
-        Apri impostazioni audio
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_ru.ts b/i18n/gonnect_ru.ts
index 2af7d0e0..9a23445f 100644
--- a/i18n/gonnect_ru.ts
+++ b/i18n/gonnect_ru.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Базовая сетка страницы панели управления
     
     
-        
+        
         Canvas for editable dashboard pages
         Холст для редактируемых страниц панели управления
     
     
-        
+        
         Add widgets
         Добавить виджеты
     
@@ -1934,8 +1934,8 @@
     FirstAid
     
         
-        First Aid
-        Экстренная помощь
+        Emergency Call
+        Экстренный вызов
     
     
         
@@ -1954,32 +1954,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Выйти из меню экстренной помощи без действий
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Закрыть меню экстренной помощи
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Открыть меню экстренной помощи
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Экстренная помощь
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Домашний
     
@@ -2119,78 +2119,78 @@
 
     HistoryWidget
     
-        
+        
         History
         История
     
     
-        
-        
+        
+        
         All
         Все
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Выбор типа звонка в истории
     
     
-        
+        
         Select the call type to filter by
         Выбрать тип звонка для фильтрации
     
     
-        
+        
         Currently selected call type
         Текущий тип звонка
     
     
-        
+        
         Incoming
         Входящий
     
     
-        
+        
         Outgoing
         Исходящий
     
     
-        
+        
         Missed
         Пропущенные
     
     
-        
+        
         History call origin picker
         Выбор направления звонка в истории
     
     
-        
+        
         Select the call origin to filter by
         Выбрать направление звонка для фильтрации
     
     
-        
+        
         Currently selected call origin
         Текущее направление звонка
     
     
-        
+        
         Hide history search
         Скрыть поиск в истории
     
     
-        
+        
         Show history search
         Показать поиск в истории
     
@@ -4018,42 +4018,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' не является допустимым SIP URI: %1
     
     
-        
+        
         'userUri' is required
         'userUri' обязателен
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' не является допустимым SIP URI: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' не является допустимым SIP URI: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' обязателен
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' содержит недопустимый SIP URI: %1
     
     
-        
+        
         Failed to create %1: %2
         Не удалось создать %1: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         Не удалось сохранить учетные данные SIP: %1
     
@@ -4082,29 +4082,29 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 звонит
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) звонит
     
     
+        
         
-        
         Accept
         Принять
     
     
-        
+        
         Call back
         Перезвонить
     
     
-        
-        
+        
+        
         Reject
         Отклонить
     
@@ -4844,6 +4844,29 @@
         Звонки активны
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Настроить громкость
+    
+    
+        
+        Unmute
+        Включить звук
+    
+    
+        
+        Mute
+        Выключить звук
+    
+    
+        
+        Open audio settings
+        Открыть настройки звука
+    
+
 
     SystemTrayMenu
     
@@ -5030,12 +5053,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Прослушивание автоответчика
     
     
-        
+        
         %n new voice mail(s)
         
             %n новое сообщение
@@ -5044,7 +5067,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n старое сообщение
@@ -5053,29 +5076,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Настроить громкость
-    
-    
-        
-        Unmute
-        Включить звук
-    
-    
-        
-        Mute
-        Выключить звук
-    
-    
-        
-        Open audio settings
-        Открыть настройки звука
-    
-
 
     WidgetSelectionWindow
     
diff --git a/i18n/gonnect_uk.ts b/i18n/gonnect_uk.ts
index aafcc047..9d551ecb 100644
--- a/i18n/gonnect_uk.ts
+++ b/i18n/gonnect_uk.ts
@@ -267,17 +267,17 @@
 
     BasePage
     
-        
+        
         Base dashboard page grid
         Базова сітка сторінки панелі управління
     
     
-        
+        
         Canvas for editable dashboard pages
         Полотно для редагованих сторінок панелі
     
     
-        
+        
         Add widgets
         Додати віджети
     
@@ -1934,8 +1934,8 @@
     FirstAid
     
         
-        First Aid
-        Перша допомога
+        Emergency Call
+        Екстрений виклик
     
     
         
@@ -1954,32 +1954,32 @@
     
     
         
-        Exit the first aid menu without initiating any action
-        Вийти з меню першої допомоги без дій
+        Exit the emergency call menu without initiating any action
+        
     
     
         
-        Close first aid menu
-        Закрити меню першої допомоги
+        Close emergency call menu
+        
     
 
 
     FirstAidButton
     
-        
-        Open first aid menu
-        Відкрити меню першої допомоги
+        
+        Emegency call
+        
     
     
-        
-        First Aid
-        Перша допомога
+        
+        Open emergency call menu
+        
     
 
 
     GonnectWindow
     
-        
+        
         Home
         Домашній
     
@@ -2119,78 +2119,78 @@
 
     HistoryWidget
     
-        
+        
         History
         Історія
     
     
-        
-        
+        
+        
         All
         Усі
     
     
-        
+        
         SIP
         SIP
     
     
-        
+        
         Jitsi Meet
         Jitsi Meet
     
     
-        
+        
         History call type picker
         Вибір типу дзвінка в історії
     
     
-        
+        
         Select the call type to filter by
         Вибрати тип дзвінка для фільтрації
     
     
-        
+        
         Currently selected call type
         Поточний тип дзвінка
     
     
-        
+        
         Incoming
         Вхідний
     
     
-        
+        
         Outgoing
         Вихідний
     
     
-        
+        
         Missed
         Пропущені
     
     
-        
+        
         History call origin picker
         Вибір напрямку дзвінка в історії
     
     
-        
+        
         Select the call origin to filter by
         Вибрати напрямок дзвінка для фільтрації
     
     
-        
+        
         Currently selected call origin
         Поточний напрямок дзвінка
     
     
-        
+        
         Hide history search
         Приховати пошук в історії
     
     
-        
+        
         Show history search
         Показати пошук в історії
     
@@ -4018,42 +4018,42 @@
 
     SIPAccount
     
-        
+        
         'userUri' is no valid SIP URI: %1
         'userUri' не є допустимим SIP URI: %1
     
     
-        
+        
         'userUri' is required
         'userUri' обов'язковий
     
     
-        
+        
         'voiceMailUri' is no valid SIP URI: %1
         'voiceMailUri' не є допустимим SIP URI: %1
     
     
-        
+        
         'registrarUri' is no valid SIP URI: %1
         'registrarUri' не є допустимим SIP URI: %1
     
     
-        
+        
         'registrarUri' is required
         'registrarUri' обов'язковий
     
     
-        
+        
         'proxies' contains invalid SIP URI entry: %1
         'proxies' містить недопустимий SIP URI: %1
     
     
-        
+        
         Failed to create %1: %2
         Не вдалося створити %1: %2
     
     
-        
+        
         Failed to persist SIP credentials: %1
         Не вдалося зберегти облікові дані SIP: %1
     
@@ -4082,29 +4082,29 @@
 
     SIPCallManager
     
-        
+        
         %1 is calling
         %1 дзвонить
     
     
-        
+        
         %1 (%2) is calling
         %1 (%2) дзвонить
     
     
+        
         
-        
         Accept
         Прийняти
     
     
-        
+        
         Call back
         Передзвонити
     
     
-        
-        
+        
+        
         Reject
         Відхилити
     
@@ -4844,6 +4844,29 @@
         Дзвінки активні
     
 
+
+    StreamingLightPopup
+    
+        
+        Adjust the volume
+        Налаштувати гучність
+    
+    
+        
+        Unmute
+        Увімкнути звук
+    
+    
+        
+        Mute
+        Вимкнути звук
+    
+    
+        
+        Open audio settings
+        Відкрити налаштування звуку
+    
+
 
     SystemTrayMenu
     
@@ -5030,12 +5053,12 @@
 
     VoiceMailField
     
-        
+        
         Listen to voicemail
         Прослуховування автовідповідача
     
     
-        
+        
         %n new voice mail(s)
         
             %n нове повідомлення
@@ -5044,7 +5067,7 @@
         
     
     
-        
+        
         %n old voice mail(s)
         
             %n старе повідомлення
@@ -5053,29 +5076,6 @@
         
     
 
-
-    VolumePopup
-    
-        
-        Adjust the volume
-        Налаштувати гучність
-    
-    
-        
-        Unmute
-        Увімкнути звук
-    
-    
-        
-        Mute
-        Вимкнути звук
-    
-    
-        
-        Open audio settings
-        Відкрити налаштування звуку
-    
-
 
     WidgetSelectionWindow
     

From 9cb86c75fab7ada9f2e50aadde842ea9eaaca194 Mon Sep 17 00:00:00 2001
From: "Weblate (bot)" 
Date: Fri, 27 Mar 2026 14:06:25 +0100
Subject: [PATCH 041/122] chore(lang): translations update from Hosted Weblate
 (#411)

* chore(lang): translated using Weblate (French)

Currently translated at 100.0% (963 of 963 strings)

Translation: GOnnect/GOnnect
Translate-URL: https://hosted.weblate.org/projects/gonnect/gonnect/fr/

* chore(lang): translated using Weblate (Italian)

Currently translated at 100.0% (963 of 963 strings)

Translation: GOnnect/GOnnect
Translate-URL: https://hosted.weblate.org/projects/gonnect/gonnect/it/

* chore(lang): translated using Weblate (German)

Currently translated at 100.0% (963 of 963 strings)

Translation: GOnnect/GOnnect
Translate-URL: https://hosted.weblate.org/projects/gonnect/gonnect/de/

---------

Co-authored-by: Michael Neuendorf 
---
 i18n/gonnect_de.ts | 18 +++++++++---------
 i18n/gonnect_fr.ts | 18 +++++++++---------
 i18n/gonnect_it.ts | 18 +++++++++---------
 3 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts
index 854cf177..868cf458 100644
--- a/i18n/gonnect_de.ts
+++ b/i18n/gonnect_de.ts
@@ -1933,7 +1933,7 @@
     
         
         Emergency Call
-        Notruf
+        Notruf
     
     
         
@@ -1953,12 +1953,12 @@
     
         
         Exit the emergency call menu without initiating any action
-        
+        Verlassen des Notruf-Menüs ohne ein Aktion auszuführen
     
     
         
         Close emergency call menu
-        
+        Notruf-Menü schließen
     
 
 
@@ -1966,12 +1966,12 @@
     
         
         Emegency call
-        
+        Notruf
     
     
         
         Open emergency call menu
-        
+        Öffne das Notruf-Menü
     
 
 
@@ -4846,22 +4846,22 @@
     
         
         Adjust the volume
-        Lautstärke anpassen
+        Lautstärke anpassen
     
     
         
         Unmute
-        Stummschaltung aufheben
+        Stummschaltung aufheben
     
     
         
         Mute
-        Stummschalten
+        Stummschalten
     
     
         
         Open audio settings
-        Audio-Einstellungen öffnen
+        Audio-Einstellungen öffnen
     
 
 
diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts
index e1659fcb..7612ec26 100644
--- a/i18n/gonnect_fr.ts
+++ b/i18n/gonnect_fr.ts
@@ -1933,7 +1933,7 @@
     
         
         Emergency Call
-        Appel d'urgence
+        Appel d'urgence
     
     
         
@@ -1953,12 +1953,12 @@
     
         
         Exit the emergency call menu without initiating any action
-        
+        Quitter le menu d'appel d'urgence sans effectuer d'action
     
     
         
         Close emergency call menu
-        
+        Fermer le menu d'appel d'urgence
     
 
 
@@ -1966,12 +1966,12 @@
     
         
         Emegency call
-        
+        Appel d ' urgence
     
     
         
         Open emergency call menu
-        
+        Ouvre le menu d'appel d'urgence
     
 
 
@@ -4846,22 +4846,22 @@
     
         
         Adjust the volume
-        Régler le volume
+        Régler le volume
     
     
         
         Unmute
-        Réactiver le son
+        Réactiver le son
     
     
         
         Mute
-        Couper le son
+        Couper le son
     
     
         
         Open audio settings
-        Ouvrir les paramètres audio
+        Ouvrir les paramètres audio
     
 
 
diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts
index 5f178fd1..c1148660 100644
--- a/i18n/gonnect_it.ts
+++ b/i18n/gonnect_it.ts
@@ -1933,7 +1933,7 @@
     
         
         Emergency Call
-        Chiamata di emergenza
+        Chiamata di emergenza
     
     
         
@@ -1953,12 +1953,12 @@
     
         
         Exit the emergency call menu without initiating any action
-        
+        Esci dal menu delle chiamate di emergenza senza eseguire alcuna operazione
     
     
         
         Close emergency call menu
-        
+        Chiudi il menu delle chiamate di emergenza
     
 
 
@@ -1966,12 +1966,12 @@
     
         
         Emegency call
-        
+        Chiamata di emergenza
     
     
         
         Open emergency call menu
-        
+        Apri il menu delle chiamate di emergenza
     
 
 
@@ -4846,22 +4846,22 @@
     
         
         Adjust the volume
-        Regola il volume
+        Regola il volume
     
     
         
         Unmute
-        Riattiva audio
+        Riattiva audio
     
     
         
         Mute
-        Silenzia
+        Disattiva l'audio
     
     
         
         Open audio settings
-        Apri impostazioni audio
+        Apri impostazioni audio
     
 
 

From 5c2e99633b58896e4234819ccfad55306323aa1e Mon Sep 17 00:00:00 2001
From: "renovate-tokenizer[bot]"
 <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
Date: Tue, 31 Mar 2026 10:33:38 +0200
Subject: [PATCH 042/122] chore(deps): update github actions (#413)

Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
---
 .github/workflows/codeql.yml   | 4 ++--
 .github/workflows/renovate.yml | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 2cde1d62..ccc42eb8 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -92,7 +92,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
+      uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4
       with:
         languages: ${{ matrix.language }}
         build-mode: ${{ matrix.build-mode }}
@@ -119,6 +119,6 @@ jobs:
         cmake --build --preset conan-release --parallel $(nproc --all)
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
+      uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
index bd88c405..4180c11a 100644
--- a/.github/workflows/renovate.yml
+++ b/.github/workflows/renovate.yml
@@ -58,7 +58,7 @@ jobs:
             }
 
       - name: Self-hosted Renovate
-        uses: renovatebot/github-action@68a3ea99af6ad249940b5a9fdf44fc6d7f14378b # v46.1.6
+        uses: renovatebot/github-action@3633cede7d4d4598438e654eac4a695e46004420 # v46.1.7
         with:
           docker-cmd-file: .github/renovate-entrypoint.sh
           docker-user: root

From d74c53040e10f354cfbf86ef62662570af4bd3fc Mon Sep 17 00:00:00 2001
From: Mik- 
Date: Wed, 1 Apr 2026 08:50:55 +0200
Subject: [PATCH 043/122] fix: make MWI subscription configurable (#414)

check with local installation
---
 docs/modules/ROOT/examples/sample.conf |  4 ++++
 resources/templates/sample.conf        |  4 ++++
 src/sip/SIPAccount.cpp                 | 11 +++++++++--
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf
index 24b11dd4..22f6cd4f 100644
--- a/docs/modules/ROOT/examples/sample.conf
+++ b/docs/modules/ROOT/examples/sample.conf
@@ -415,6 +415,10 @@
 ## Multiplex RTCP information into existing RTP stream.
 #rtcpMuxEnabled=false
 
+## Enable the subscription to MWI events.
+## default: true
+#enableMwiConfig=true
+
 ## SIP URI to call for the ordinary voice mail box. There is a fallback to the "Message-Account" field of the
 ## MWI info message of the SIP server.
 ## default: ""
diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf
index a3cde783..b33915fe 100644
--- a/resources/templates/sample.conf
+++ b/resources/templates/sample.conf
@@ -414,6 +414,10 @@ port=5061
 ## Multiplex RTCP information into existing RTP stream.
 #rtcpMuxEnabled=false
 
+## Enable the subscription to MWI events.
+## default: true
+#enableMwiConfig=true
+
 ## SIP URI to call for the ordinary voice mail box. There is a fallback to the "Message-Account" field of the
 ## MWI info message of the SIP server.
 ## default: ""
diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp
index 7d808400..22d9a2d0 100644
--- a/src/sip/SIPAccount.cpp
+++ b/src/sip/SIPAccount.cpp
@@ -145,8 +145,11 @@ void SIPAccount::initialize()
     }
     m_accountConfig.mediaConfig.srtpUse = srtpUseValue;
 
-    m_accountConfig.mwiConfig.enabled = true;
-    m_accountConfig.mwiConfig.expirationSec = 3600;
+    bool enableMwiConfig = m_settings.value("enableMwiConfig", true).toBool();
+    if (enableMwiConfig) {
+        m_accountConfig.mwiConfig.enabled = true;
+        m_accountConfig.mwiConfig.expirationSec = 3600;
+    }
 
     int rtpPort = m_settings.value("rtpPort", 0).toInt(&ok);
     if (!ok) {
@@ -583,6 +586,10 @@ void SIPAccount::onMwiInfo(pj::OnMwiInfoParam &prm)
 {
     qCDebug(lcSIPAccount) << "received MWI info message - subscription status:" << prm.state;
 
+    if (prm.state == PJSIP_EVSUB_STATE_TERMINATED) {
+        return;
+    }
+
     QString fullMsg = QString::fromStdString(prm.rdata.wholeMsg);
     QStringList parts = fullMsg.split("\r\n\r\n");
 

From 7ae2fb01d1f32801bd9fd07ea9e83e40378f54ba Mon Sep 17 00:00:00 2001
From: Mik- 
Date: Wed, 1 Apr 2026 13:39:09 +0200
Subject: [PATCH 044/122] docs: add architecture documentation (#415)

* docs: initial arch docs

* fix: structure

* fix: remove table at the end

* fix: chapter indents

* fix: remove section numbering

* fix: plantuml diagrams

* fix: diagram

* docs: small fixes
---
 .../pages/architectural-scenarios.adoc        | 236 ++++++++++
 .../pages/building-block-view.adoc            | 360 ++++++++++++++++
 docs/modules/architecture/pages/concepts.adoc | 371 ++++++++++++++++
 .../architecture/pages/deployment-view.adoc   | 316 ++++++++++++++
 .../pages/introduction-and-goals.adoc         | 109 +++++
 .../pages/requirements-and-constraints.adoc   | 147 +++++++
 .../architecture/pages/runtime-view.adoc      | 306 +++++++++++++
 docs/modules/architecture/pages/security.adoc | 403 ++++++++++++++++++
 .../architecture/pages/solution-strategy.adoc | 204 +++++++++
 9 files changed, 2452 insertions(+)
 create mode 100644 docs/modules/architecture/pages/architectural-scenarios.adoc
 create mode 100644 docs/modules/architecture/pages/building-block-view.adoc
 create mode 100644 docs/modules/architecture/pages/concepts.adoc
 create mode 100644 docs/modules/architecture/pages/deployment-view.adoc
 create mode 100644 docs/modules/architecture/pages/introduction-and-goals.adoc
 create mode 100644 docs/modules/architecture/pages/requirements-and-constraints.adoc
 create mode 100644 docs/modules/architecture/pages/runtime-view.adoc
 create mode 100644 docs/modules/architecture/pages/security.adoc
 create mode 100644 docs/modules/architecture/pages/solution-strategy.adoc

diff --git a/docs/modules/architecture/pages/architectural-scenarios.adoc b/docs/modules/architecture/pages/architectural-scenarios.adoc
new file mode 100644
index 00000000..b7951e37
--- /dev/null
+++ b/docs/modules/architecture/pages/architectural-scenarios.adoc
@@ -0,0 +1,236 @@
+= Architectural Scenarios
+
+== Call Flow Scenario
+
+=== Outgoing Call Sequence
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor User
+participant "UI" as UI
+participant "StateManager" as SM
+participant "SIPManager" as SIPM
+participant "SIPCallManager" as CM
+participant "SIPCall" as Call
+participant "PJSIP" as PJSIP
+
+User -> UI : Dial number
+UI -> SM : Get preferred identity
+SM --> UI : Return identity
+UI -> SIPM : Create call request
+SIPM -> CM : Create call
+CM -> Call : Initialize call
+Call -> PJSIP : Send INVITE
+PJSIP --> Call : 100 Trying
+Call --> CM : State changed
+CM --> UI : Update UI
+PJSIP --> Call : 180 Ringing
+Call --> CM : State changed
+CM --> UI : Update UI
+PJSIP --> Call : 200 OK
+Call --> CM : State changed
+CM --> UI : Update UI
+User -> UI : Answer call
+UI -> Call : Accept
+Call -> PJSIP : ACK
+PJSIP --> Call : Media established
+Call --> CM : Call active
+CM --> UI : Update UI
+@enduml
+....
+
+=== Incoming Call Sequence
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor RemoteUser
+participant "PJSIP" as PJSIP
+participant "SIPCall" as Call
+participant "SIPCallManager" as CM
+participant "UI" as UI
+participant "User" as User
+
+RemoteUser -> PJSIP : INVITE
+PJSIP -> Call : Create call
+Call -> CM : Notify incoming
+CM -> UI : Show incoming call
+UI --> User : Display call
+User -> UI : Answer
+UI -> Call : Accept
+Call -> PJSIP : 200 OK
+PJSIP --> Call : ACK
+Call --> CM : Call established
+CM --> UI : Update UI
+@enduml
+....
+
+=== Call Termination
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "User" as User
+participant "UI" as UI
+participant "SIPCall" as Call
+participant "SIPCallManager" as CM
+participant "PJSIP" as PJSIP
+
+User -> UI : End call
+UI -> Call : Hangup
+Call -> PJSIP : Send BYE
+PJSIP --> Call : 200 OK
+Call --> CM : State changed
+CM --> UI : Update UI
+Call -> CM : Remove from active calls
+CM -> CM : Update call history
+@enduml
+....
+
+== Contact Synchronization Scenario
+
+=== Contact Source Loading
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "Application" as App
+participant "AddressBookManager" as ABM
+participant "LDAP Feeder" as LDAP
+participant "CardDAV Feeder" as CD
+participant "Akonadi Feeder" as AK
+participant "ContactModel" as CM
+
+App -> ABM : Initialize
+ABM -> LDAP : Load contacts
+ABM -> CD : Load contacts
+ABM -> AK : Load contacts
+LDAP --> ABM : Contacts list
+CD --> ABM : Contacts list
+AK --> ABM : Contacts list
+ABM -> CM : Update model
+CM --> ABM : Acknowledged
+ABM --> App : Ready
+@enduml
+....
+
+=== Contact Search
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "UI" as UI
+participant "ContactModel" as CM
+participant "Contact" as Contact
+
+User -> UI : Enter search term
+UI -> CM : Search query
+CM -> Contact : Filter contacts
+Contact --> CM : Matching contacts
+CM --> UI : Search results
+UI --> User : Display results
+@enduml
+....
+
+== Configuration Loading Scenario
+
+=== Application Startup
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "Application" as App
+participant "UISettings" as UIS
+participant "AppSettings" as AS
+participant "Keychain" as KC
+participant "StateManager" as SM
+
+User -> App : Start application
+App -> AS : Load settings
+AS --> App : Configuration
+App -> KC : Load credentials
+KC --> App : Decrypted secrets
+App -> UIS : Apply UI settings
+UIS --> App : UI configured
+App -> SM : Initialize state
+SM --> App : State ready
+@enduml
+....
+
+== Buddy State Subscription Scenario
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPManager" as SIPM
+participant "SIPBuddy" as Buddy
+participant "PJSIP" as PJSIP
+participant "UI" as UI
+
+SIPM -> Buddy : Subscribe to state
+Buddy -> PJSIP : SUBSCRIBE request
+PJSIP --> Buddy : 200 OK
+PJSIP --> Buddy : NOTIFY with state
+Buddy --> SIPM : State changed
+SIPM --> UI : Emit buddyStateChanged
+UI --> Buddy : Update display
+@enduml
+....
+
+== Emergency Call Scenario
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "UI" as UI
+participant "StateManager" as SM
+participant "SIPCallManager" as CM
+participant "SIPCall" as Call
+
+User -> UI : Click emergency number
+UI -> SM : Request emergency call
+SM --> UI : Authorization
+UI -> CM : Create emergency call
+CM -> Call : Initialize call
+Call -> Call : Mark as emergency
+Call -> CM : Send INVITE
+CM --> UI : Call in progress
+@enduml
+....
\ No newline at end of file
diff --git a/docs/modules/architecture/pages/building-block-view.adoc b/docs/modules/architecture/pages/building-block-view.adoc
new file mode 100644
index 00000000..1c835d06
--- /dev/null
+++ b/docs/modules/architecture/pages/building-block-view.adoc
@@ -0,0 +1,360 @@
+= Building Block View
+
+== High-Level Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect High-Level Architecture
+
+package "Main Application" as Main {
+  component "Application" as App
+  component "StateManager" as SM
+  component "ErrorBus" as EB
+}
+
+package "UI Layer" as UI {
+  component "GonnectWindow" as GW
+  component "CallWindow" as CW
+  component "HistoryWindow" as HW
+  component "SearchWindow" as SW
+  component "SettingsWindow" as SettW
+}
+
+package "SIP Module" as SIP {
+  component "SIPManager" as SIPM
+  component "SIPCallManager" as SCM
+  component "SIPCall" as SC
+  component "SIPAccount" as SA
+  component "SIPBuddy" as SB
+  component "PreferredIdentity" as PI
+}
+
+package "Contacts Module" as Contacts {
+  component "AddressBookManager" as ABM
+  component "Contact" as C
+  component "ContactModel" as CM
+  component "AvatarManager" as AM
+  component "ContactSourceInfoModel" as CSIM
+}
+
+package "Calendar Module" as Calendar {
+  component "DateEventFeederManager" as DEM
+  component "DateEvent" as DE
+  component "DateEventsModel" as DM
+}
+
+package "Media Module" as Media {
+  component "AudioManager" as AuM
+  component "VideoManager" as VM
+  component "AudioPort" as AP
+}
+
+package "Platform" as Platform {
+  component "SystemTrayMenu" as STM
+  component "NotificationManager" as NM
+  component "Credentials" as Creds
+}
+
+package "Contact Sources" as Sources {
+  component "LDAP Feeder" as LDAP
+  component "CardDAV Feeder" as CD
+  component "Akonadi Feeder" as AK
+  component "EDS Feeder" as EDS
+  component "CSV Feeder" as CSV
+}
+
+package "Calendar Sources" as CalSources {
+  component "CalDAV Feeder" as CDV
+  component "Akonadi Calendar" as AKC
+  component "EDS Calendar" as EDSV
+}
+
+App --> UI : QML Context
+App --> SIP : SIP Management
+App --> Contacts : Contact Management
+App --> Calendar : Calendar Management
+App --> Media : Media Management
+App --> Platform : Platform Services
+
+SIPM --> SA : Account management
+SIPM --> SB : Buddy tracking
+SCM --> SC : Call management
+SC --> SIP : PJSIP integration
+
+ABM --> Sources : Contact sources
+ABM --> CM : Model updates
+AM --> C : Avatar loading
+
+DEM --> CalSources : Calendar sources
+DEM --> DM : Model updates
+
+AuM --> Media : Audio devices
+VM --> Media : Video devices
+AP --> AuM : Port routing
+
+STM --> Platform : System tray
+NM --> Platform : Notifications
+Creds --> Platform : Keychain
+@enduml
+....
+
+== SIP Module Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect SIP Module
+
+package "SIP Core" as Core {
+  component "SIPManager" as SIPM
+  component "SIPEventLoop" as SEL
+  component "SIPMediaConfig" as SMC
+  component "SIPUserAgentConfig" as SUAC
+}
+
+package "Account Management" as Acc {
+  component "SIPAccountManager" as SAM
+  component "SIPAccount" as SA
+  component "PreferredIdentity" as PI
+  component "PreferredIdentityValidator" as PIV
+}
+
+package "Call Management" as CallM {
+  component "SIPCallManager" as SCM
+  component "SIPCall" as SC
+  component "ICallState" as ICS
+}
+
+package "Buddy Management" as Buddy {
+  component "SIPBuddy" as SB
+  component "SIPBuddyState" as SBS
+}
+
+package "Utilities" as Utils {
+  component "RingToneFactory" as RTF
+  component "RingTone" as RT
+  component "Ringer" as R
+  component "DtmfGenerator" as DTGF
+  component "TogglerManager" as TM
+  component "Toggler" as T
+}
+
+SIPM --> SEL : Event loop
+SIPM --> SMC : Media config
+SIPM --> SUAC : UA config
+SIPM --> SAM : Account manager
+
+SAM --> SA : Account instances
+SAM --> PI : Identity management
+SA --> PIV : Validation
+
+SCM --> SC : Call instances
+SC --> ICS : State interface
+
+RTF --> RT : Ring tone creation
+R --> RT : Ring playback
+TM --> T : Toggler instances
+DTGF --> SC : DTMF generation
+@enduml
+....
+
+== Contacts Module Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Contacts Module
+
+package "Core" as Core {
+  component "AddressBookManager" as ABM
+  component "Contact" as C
+  component "ContactModel" as CM
+  component "AvatarManager" as AM
+}
+
+package "Abstract Interfaces" as Abstract {
+  interface "IAddressBookFactory" as IABF
+  interface "IAddressBookFeeder" as IABFdr
+}
+
+package "LDAP Source" as LDAP {
+  component "LDAP Feeder" as LDAPF
+  component "LDAP Factory" as LDAPFact
+}
+
+package "CardDAV Source" as CD {
+  component "CardDAV Feeder" as CDF
+  component "CardDAV Factory" as CDFact
+}
+
+package "Akonadi Source" as AK {
+  component "Akonadi Feeder" as AKF
+  component "Akonadi Factory" as AKFact
+}
+
+package "EDS Source" as EDS {
+  component "EDS Feeder" as EDSF
+  component "EDS Factory" as EDSFact
+}
+
+package "CSV Source" as CSV {
+  component "CSV Feeder" as CSVF
+  component "CSV Factory" as CSVFact
+}
+
+package "Utilities" as Utils {
+  component "ContactSerializer" as CS
+  component "FuzzyCompare" as FC
+  component "PhoneNumberUtil" as PU
+  component "PhoneCodeLookup" as PCL
+  component "EmergencyContact" as EC
+  component "BlockInfo" as BI
+}
+
+ABM --> IABF : Factory interface
+ABM --> IABFdr : Feeder interface
+
+IABF --> LDAPFact : LDAP factory
+IABF --> CDFact : CardDAV factory
+IABF --> AKFact : Akonadi factory
+IABF --> EDSFact : EDS factory
+IABF --> CSVFact : CSV factory
+
+LDAPF --> IABFdr : Implements feeder
+CDF --> IABFdr : Implements feeder
+AKF --> IABFdr : Implements feeder
+EDSF --> IABFdr : Implements feeder
+CSVF --> IABFdr : Implements feeder
+
+ABM --> CM : Model updates
+ABM --> AM : Avatar requests
+AM --> C : Avatar loading
+
+CS --> C : Serialization
+FC --> C : Comparison
+PU --> C : Phone validation
+PCL --> C : Phone codes
+EC --> C : Emergency contacts
+BI --> C : Block info
+@enduml
+....
+
+== Calendar Module Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Calendar Module
+
+package "Core" as Core {
+  component "DateEventFeederManager" as DEM
+  component "DateEventManager" as DM
+  component "DateEvent" as DE
+  component "DateEventsModel" as DEMod
+}
+
+package "Abstract Interfaces" as Abstract {
+  interface "IDateEventFeederFactory" as IDEFF
+  interface "IDateEventFeeder" as IDEFFdr
+}
+
+package "CalDAV Source" as CDV {
+  component "CalDAV Feeder" as CDVF
+  component "CalDAV Factory" as CDVFct
+}
+
+package "Akonadi Source" as AK {
+  component "Akonadi Feeder" as AKF
+  component "Akonadi Factory" as AKFct
+}
+
+package "EDS Source" as EDS {
+  component "EDS Feeder" as EDSF
+  component "EDS Factory" as EDSFct
+}
+
+package "Utilities" as Utils {
+  component "RandomRoomNameGenerator" as RNG
+  component "EmojiResolver" as ER
+  component "EmojiModel" as EM
+}
+
+DEM --> IDEFF : Factory interface
+DEM --> IDEFFdr : Feeder interface
+
+IDEFF --> CDVFct : CalDAV factory
+IDEFF --> AKFct : Akonadi factory
+IDEFF --> EDSFct : EDS factory
+
+CDVF --> IDEFFdr : Implements feeder
+AKF --> IDEFFdr : Implements feeder
+EDSF --> IDEFFdr : Implements feeder
+
+DEM --> DEMod : Model updates
+DEM --> DE : Event instances
+
+RNG --> DE : Room name generation
+ER --> DE : Emoji resolution
+EM --> DE : Emoji model
+@enduml
+....
+
+== Media Module Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Media Module
+
+package "Audio" as Audio {
+  component "AudioManager" as AuM
+  component "SIPAudioDevice" as SAD
+  component "AudioPort" as AP
+}
+
+package "Video" as Video {
+  component "VideoManager" as VM
+  component "SIPVideoDevice" as SVD
+}
+
+package "Call Media" as CallMedia {
+  component "Sniffer" as Snif
+  component "HeadsetDeviceProxy" as HDP
+}
+
+package "Utilities" as Utils {
+  component "RingToneFactory" as RTF
+  component "RingTone" as RT
+}
+
+AuM --> SAD : Audio devices
+AuM --> AP : Port routing
+VM --> SVD : Video devices
+
+Snif --> Audio : Media sniffing
+HDP --> Audio : Headset proxy
+
+RTF --> RT : Ring tone creation
+@enduml
+....
\ No newline at end of file
diff --git a/docs/modules/architecture/pages/concepts.adoc b/docs/modules/architecture/pages/concepts.adoc
new file mode 100644
index 00000000..906826c1
--- /dev/null
+++ b/docs/modules/architecture/pages/concepts.adoc
@@ -0,0 +1,371 @@
+= Important Concepts
+
+== Preferred Identities
+
+GOnnect supports multiple preferred identities that allow callers to present different identities to callees based on the dialed number. This is useful for enterprises that want to route calls through different outbound numbers based on the destination.
+
+=== Identity Selection Algorithm
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "UI" as UI
+participant "SIPManager" as SIPM
+participant "PreferredIdentity" as PI
+
+User -> UI : Initiate call
+UI -> SIPM : Get preferred identity
+SIPM -> PI : Check identity list
+PI --> SIPM : Return matching identity
+SIPM --> UI : Identity selected
+UI -> SIPM : Use identity for call
+@enduml
+....
+
+=== Identity Configuration
+
+|===
+| Field | Description | Example
+
+| enabled | Whether the identity is active | true
+| automatic | Whether identity is auto-selected | true
+| name | Display name in UI | "Support Line"
+| identity | SIP URI for this identity | sip:support@example.org
+| prefix | Number prefix for auto-selection | +492913641
+|===
+
+== Buddy State Management
+
+GOnnect tracks the availability state of contacts through SIP SUBSCRIBE/NOTIFY mechanism. This enables presence awareness in the contact list.
+
+=== Buddy State Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "Contact" as Contact
+participant "SIPBuddy" as SB
+participant "SIPManager" as SIPM
+participant "PJSIP" as PJSIP
+
+Contact -> SB : Subscribe
+SB -> PJSIP : SUBSCRIBE request
+PJSIP --> SB : 200 OK
+PJSIP --> SB : NOTIFY: available
+SB --> Contact : State: Available
+PJSIP --> SB : NOTIFY: busy
+SB --> Contact : State: Busy
+@enduml
+....
+
+=== Buddy States
+
+|===
+| State | Description | Icon
+
+| Available | Contact is online and available | Green dot
+| Busy | Contact is on a call | Red dot
+| Away | Contact is away | Yellow dot
+| Offline | Contact is offline | Gray dot
+| Unknown | State not known | Question mark
+|===
+
+== Contact Source Federation
+
+GOnnect aggregates contacts from multiple sources into a unified view. Each source can have different priorities and display names.
+
+=== Contact Merging
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "User" as User
+participant "Search UI" as Search
+participant "AddressBookManager" as ABM
+participant "LDAP" as LDAP
+participant "CardDAV" as CD
+participant "Akonadi" as AK
+
+User -> Search : Search "John"
+Search -> ABM : Search query
+ABM -> LDAP : Search "John"
+ABM -> CD : Search "John"
+ABM -> AK : Search "John"
+LDAP --> ABM : 5 results
+CD --> ABM : 3 results
+AK --> ABM : 2 results
+ABM -> ABM : Merge and deduplicate
+ABM --> Search : 6 unique results
+Search --> User : Display results
+@enduml
+....
+
+=== Source Priority
+
+|===
+| Source | Default Priority | Configurable
+
+| LDAP | 50 | Yes
+| CardDAV | 50 | Yes
+| Akonadi | 25 | Yes
+| EDS | 25 | Yes
+| CSV | 10 | Yes
+|===
+
+== Call Quality Metrics
+
+GOnnect provides detailed call quality information using RTCP XR (Extended Reports) and PJSIP's built-in quality metrics.
+
+=== Quality Metrics
+
+|===
+| Metric | Description | Scale
+
+| MOS-TX | Mean Opinion Score - Transmission | 1.0 - 5.0
+| MOS-RX | Mean Opinion Score - Reception | 1.0 - 5.0
+| Loss-TX | Packet loss rate - Transmission | 0% - 100%
+| Loss-RX | Packet loss rate - Reception | 0% - 100%
+| Jitter-TX | Jitter - Transmission | 0 - 500ms
+| Jitter-RX | Jitter - Reception | 0 - 500ms
+| Delay-TX | One-way delay - Transmission | 0 - 500ms
+| Delay-RX | One-way delay - Reception | 0 - 500ms
+|===
+
+=== Quality Level Classification
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam stateStyle mode
+
+title Call Quality Levels
+
+state "Excellent" as Excellent
+state "Good" as Good
+state "Fair" as Fair
+state "Poor" as Poor
+state "Bad" as Bad
+
+Excellent : 4.5 - 5.0 MOS
+Good : 4.0 - 4.5 MOS
+Fair : 3.5 - 4.0 MOS
+Poor : 3.0 - 3.5 MOS
+Bad : < 3.0 MOS
+
+Excellent --> Good : MOS decreases
+Good --> Fair : MOS decreases
+Fair --> Poor : MOS decreases
+Poor --> Bad : MOS decreases
+@enduml
+....
+
+== Security Architecture
+
+=== Credential Storage
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPAccount" as SA
+participant "Credentials" as Creds
+participant "Keychain" as KC
+
+SA -> Creds : Request password
+Creds -> KC : Get credential
+KC --> Creds : Encrypted data
+Creds --> SA : Decrypted password
+SA -> SA : Use for authentication
+@enduml
+....
+
+=== Encryption Layers
+
+|===
+| Layer | Protocol | Purpose
+
+| Signaling | TLS 1.2+ | SIP message encryption
+| Media | SRTP | RTP/RTCP encryption
+| Credentials | Keychain | Password storage
+| Config | File permissions | Config file protection
+|===
+
+== Emergency Call Handling
+
+Emergency calls have special handling to ensure they are always prioritized and can override ongoing calls.
+
+=== Emergency Call Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "Emergency Button" as EB
+participant "SIPCallManager" as SCM
+participant "Active Call" as AC
+participant "Emergency Call" as EC
+
+User -> EB : Click emergency
+EB -> SCM : Request emergency call
+SCM -> AC : Terminate active calls
+AC --> SCM : Calls terminated
+SCM -> EC : Create emergency call
+EC --> SCM : Call established
+SCM --> EB : Emergency call in progress
+@enduml
+....
+
+=== Emergency Number Configuration
+
+|===
+| Field | Description | Example
+
+| number | Emergency number | 112
+| displayName | Display name | "Firefighters / Medical"
+| priority | Call priority | High
+|===
+
+== Jitsi Meet Integration
+
+GOnnect can upgrade SIP calls to Jitsi Meet video conferences when both parties support it.
+
+=== Jitsi Integration Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPCall" as SC
+participant "JitsiConnector" as JC
+participant "Jitsi Meet" as Jitsi
+
+SC -> JC : Check capabilities
+JC --> SC : Jitsi supported
+SC -> JC : Create conference
+JC -> Jitsi : Create room
+Jitsi --> JC : Conference URL
+JC --> SC : Upgrade to Jitsi
+SC --> JC : Send invite
+@enduml
+....
+
+=== Jitsi Configuration
+
+|===
+| Field | Description | Example
+
+| url | Jitsi server URL | https://meet.example.com
+| displayName | Display name in Jitsi | "GOnnect User"
+| preconfig | Show pre-join screen | false
+| authorizationMode | Authorization method | none
+| muc | Jitsi MUC for dial-in | conference.meet.jit.si
+|===
+
+== Plugin Architecture
+
+GOnnect uses a plugin-based architecture for contact sources and chat plugins.
+
+=== Contact Source Plugins
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title Contact Source Plugin Architecture
+
+interface "IAddressBookFeeder" as Feeder
+
+package "Core" as Core {
+  component "AddressBookManager" as ABM
+}
+
+package "Plugins" as Plugins {
+  component "LDAP Feeder" as LDAP
+  component "CardDAV Feeder" as CD
+  component "Akonadi Feeder" as AK
+  component "EDS Feeder" as EDS
+  component "CSV Feeder" as CSV
+}
+
+ABM --> Feeder : Factory creates
+LDAP --> Feeder : Implements
+CD --> Feeder : Implements
+AK --> Feeder : Implements
+EDS --> Feeder : Implements
+CSV --> Feeder : Implements
+@enduml
+....
+
+=== Plugin Lifecycle
+
+|===
+| Phase | Action | Description
+
+| Load | Initialize | Load plugin library
+| Configure | Apply settings | Read configuration
+| Start | Begin fetch | Start data synchronization
+| Stop | Cleanup | Release resources
+| Unload | Destroy | Unload library
+|===
+
+== State Management
+
+StateManager provides a centralized way to manage application state and coordinate between different components.
+
+=== State Properties
+
+|===
+| Property | Type | Description
+
+| globalShortcutsSupported | bool | System supports global shortcuts
+| uiEditMode | bool | UI is in edit mode
+| uiHasActiveEditDialog | bool | Edit dialog is open
+| globalShortcuts | QVariantMap | Custom shortcut mappings
+|===
+
+=== State Change Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "UI" as UI
+participant "StateManager" as SM
+participant "Other Component" as OC
+
+UI -> SM : Set uiEditMode=true
+SM --> UI : uiEditModeChanged signal
+SM --> OC : uiEditModeChanged signal
+OC -> UI : Update UI based on state
+@enduml
+....
diff --git a/docs/modules/architecture/pages/deployment-view.adoc b/docs/modules/architecture/pages/deployment-view.adoc
new file mode 100644
index 00000000..8a198a70
--- /dev/null
+++ b/docs/modules/architecture/pages/deployment-view.adoc
@@ -0,0 +1,316 @@
+= Deployment View
+
+== Deployment Architecture
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Deployment Architecture
+
+rectangle "User Desktop" as UserDesktop {
+  rectangle "GOnnect Application" as App {
+    component "Main Process" as MainProc
+    component "QML Engine" as QMLE
+    component "C++ Backend" as CPP
+  }
+  component "System Tray" as Tray
+  component "Notification Daemon" as ND
+}
+
+rectangle "Network" as Network {
+  rectangle "SIP Server / PBX" as SIPServer
+  rectangle "LDAP Server" as LDAPServer
+  rectangle "CardDAV Server" as CardDAVServer
+  rectangle "CalDAV Server" as CalDAVServer
+  rectangle "Jitsi Meet Server" as JitsiServer
+}
+
+rectangle "Local Services" as Local {
+  component "Akonadi Server" as Akonadi
+  component "EDS Server" as EDS
+  component "Keychain Service" as KC
+}
+
+MainProc --> SIPServer : SIP/TLS (5061)
+MainProc --> LDAPServer : LDAP/LDAPS
+MainProc --> CardDAVServer : CardDAV/HTTPS
+MainProc --> CalDAVServer : CalDAV/HTTPS
+MainProc --> JitsiServer : WebRTC/HTTPS
+
+MainProc --> Akonadi : D-Bus
+MainProc --> EDS : D-Bus
+MainProc --> KC : Secret Service API
+
+MainProc --> Tray : System tray
+Tray --> ND : Notifications
+@enduml
+....
+
+== Linux Deployment
+
+=== Native Linux Build
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title Linux Native Deployment
+
+rectangle "Build System" as Build {
+  component "CMake" as CMake
+  component "Conan" as Conan
+  component "clang-format" as CF
+}
+
+rectangle "Build Dependencies" as BuildDeps {
+  component "Qt6" as Qt6
+  component "PJSIP" as PJSIP
+  component "libsecret" as LS
+  component "logfault" as LF
+}
+
+rectangle "Target System" as Target {
+  component "GOnnect Binary" as Bin
+  component "QML Files" as QML
+  component "Translations" as Trans
+  component "Config Files" as CFG
+}
+
+rectangle "Runtime Dependencies" as Runtime {
+  component "Qt6 Runtime" as Qt6R
+  component "D-Bus" as DBus
+  component "Keychain Service" as KCS
+  component "Akonadi" as Akonadi
+  component "EDS" as EDS
+}
+
+CMake --> Conan : Dependency resolution
+Conan --> BuildDeps : Fetch packages
+CMake --> Target : Build application
+
+Target --> Runtime : Link libraries
+Target --> BuildDeps : Runtime libs
+@enduml
+....
+
+=== Flatpak Deployment
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title Flatpak Deployment
+
+rectangle "Flatpak Build" as FPBuild {
+  component "Flatpak Builder" as FPB
+  component "Manifest" as Manifest
+}
+
+rectangle "Flatpak Runtime" as FPR {
+  component "org.freedesktop.Platform" as FLP
+  component "org.kde.Platform" as KDP
+  component "Qt6 Runtime" as Qt6R
+}
+
+rectangle "Flatpak App" as FPA {
+  component "GOnnect App" as GP
+  component "Extensions" as Ext
+}
+
+rectangle "Host System" as Host {
+  component "D-Bus" as DBus
+  component "Keyring" as Keyring
+  component "File System" as FS
+}
+
+FPB --> FPR : Base runtime
+FPB --> FPA : Build app
+FPA --> FPR : Runtime dependencies
+FPA --> Host : D-Bus access
+FPA --> Host : Keyring access
+FPA --> Host : File system access
+@enduml
+....
+
+== macOS Deployment
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title macOS Deployment
+
+rectangle "Build System" as Build {
+  component "CMake" as CMake
+  component "Conan" as Conan
+}
+
+rectangle "Build Dependencies" as BuildDeps {
+  component "Qt6" as Qt6
+  component "PJSIP" as PJSIP
+  component "Keychain" as KC
+  component "OSLog" as OSLog
+}
+
+rectangle "App Bundle" as AppBundle {
+  component "GOnnect.app" as App
+  component "Contents/MacOS" as MacOS
+  component "Contents/Resources" as Res
+  component "Info.plist" as Info
+}
+
+rectangle "System Integration" as Sys {
+  component "LaunchAgents" as LA
+  component "MenuBar" as MB
+  component "Notifications" as N
+}
+
+CMake --> Conan : Dependency resolution
+Conan --> BuildDeps : Fetch packages
+CMake --> AppBundle : Build application
+
+AppBundle --> Sys : Register services
+AppBundle --> Sys : Launch agent
+@enduml
+....
+
+== Windows Deployment
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title Windows Deployment
+
+rectangle "Build System" as Build {
+  component "CMake" as CMake
+  component "Conan" as Conan
+  component "Visual Studio" as VS
+}
+
+rectangle "Build Dependencies" as BuildDeps {
+  component "Qt6" as Qt6
+  component "PJSIP" as PJSIP
+  component "Windows SDK" as WinSDK
+  component "WinCrypt" as WinCrypt
+}
+
+rectangle "Installer" as Inst {
+  component "NSIS Installer" as NSIS
+  component "GOnnect Setup.exe" as Setup
+}
+
+rectangle "System Integration" as Sys {
+  component "Start Menu" as SM
+  component "Taskbar" as TB
+  component "Event Viewer" as EV
+  component "Windows Keychain" as WK
+}
+
+CMake --> VS : Build configuration
+VS --> BuildDeps : Link libraries
+CMake --> Inst : Generate installer
+
+Inst --> Sys : Create shortcuts
+Inst --> Sys : Register file types
+Inst --> Sys : Event log source
+@enduml
+....
+
+== Configuration File Structure
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Configuration Structure
+
+package "User Configuration" as UserConf {
+  file "gonnect.conf" as CONF
+  file "keychain" as KC
+}
+
+package "System Configuration" as SysConf {
+  file "/etc/gonnect/gonnect.conf" as SYS_CONF
+  file "/etc/gonnect/keychain.d/" as SYS_KC
+}
+
+package "Application Resources" as Resources {
+  file ":/i18n/gonnect_de.qm" as QM1
+  file ":/i18n/gonnect_en.qm" as QM2
+  file ":/qml/GonnectWindow.qml" as QML1
+  file ":/qml/CallWindow.qml" as QML2
+}
+
+package "Runtime Data" as Runtime {
+  file "~/.local/share/gonnect/log/" as LOG
+  file "~/.local/share/gonnect/cache/" as CACHE
+  file "~/.local/share/gonnect/history/" as HIST
+}
+
+UserConf --> Resources : Load resources
+SysConf --> UserConf : Override settings
+Resources --> Runtime : Write logs
+Runtime --> UserConf : Persist data
+@enduml
+....
+
+== Network Ports and Protocols
+
+|===
+| Protocol | Port | Direction | Purpose
+
+| SIP/TLS | 5061 | Outbound | SIP signaling (encrypted)
+| SIP/UDP | 5060 | Outbound | SIP signaling (unencrypted)
+| SIP/TCP | 5060 | Outbound | SIP signaling (TCP)
+| RTP | 10000-20000 | Bidirectional | Media streams
+| STUN | 3478 | Outbound | NAT traversal
+| TURN | 3478 | Outbound | NAT relay
+| CardDAV | 443 | Outbound | Contact synchronization
+| CalDAV | 443 | Outbound | Calendar synchronization
+| Jitsi | 443 | Outbound | Video conference
+| D-Bus | N/A | Local | IPC
+|===
+
+== Resource Requirements
+
+=== Minimum Requirements
+
+|===
+| Resource | Minimum | Recommended
+
+| CPU | 1 Core @ 1GHz | 2 Cores @ 2GHz
+| RAM | 1GB | 4GB
+| Disk | 500MB | 1GB
+| Network | 128kbps | 1Mbps+
+|===
+
+== Environment Variables
+
+|===
+| Variable | Purpose | Default
+
+| GONNECT_DEBUG | Enable debug logging | false
+| QT_LOGGING_RULES | Qt logging configuration | 
+| PJSIP_LOG_LEVEL | PJSIP logging level | 0
+| KEYCHAIN_SERVICE | Keychain service name | gonnect
+|===
diff --git a/docs/modules/architecture/pages/introduction-and-goals.adoc b/docs/modules/architecture/pages/introduction-and-goals.adoc
new file mode 100644
index 00000000..3a2a32c7
--- /dev/null
+++ b/docs/modules/architecture/pages/introduction-and-goals.adoc
@@ -0,0 +1,109 @@
+= Introduction and Goals
+
+== Scope
+
+GOnnect is a cross-platform Unified Communications client built with Qt/QML that provides:
+
+* SIP voice calling capabilities
+* Contact aggregation from multiple sources (LDAP, CardDAV, Akonadi, EDS)
+* Calendar integration (CalDAV, Akonadi, EDS)
+* Instant messaging support
+* Jitsi Meet integration for video conferences
+* System tray integration with custom window decoration
+* Multi-platform support (Linux, Windows, macOS (experimental))
+
+The application targets enterprise and professional users requiring reliable communication tools with integration into corporate infrastructure.
+
+== System Context
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect System Context Diagram
+
+rectangle "End User" as User
+rectangle "GOnnect Client" as Client
+rectangle "SIP Server / PBX" as SIPServer
+rectangle "LDAP Server" as LDAPServer
+rectangle "CardDAV Server" as CardDAVServer
+rectangle "CalDAV Server" as CalDAVServer
+rectangle "Jitsi Meet Server" as JitsiServer
+rectangle "Akonadi / EDS" as LocalServices
+
+User --> Client : Uses application
+Client --> SIPServer : SIP signaling (TLS/TCP/UDP)
+Client --> SIPServer : SRTP media
+Client --> LDAPServer : LDAP queries
+Client --> CardDAVServer : CardDAV requests
+Client --> CalDAVServer : CalDAV requests
+Client --> JitsiServer : Jitsi Meet sessions
+Client --> LocalServices : Local contacts/calendar
+
+@enduml
+....
+
+== Stakeholders and Interests
+
+|===
+| Stakeholder | Interest
+
+| End Users | Reliable call quality, intuitive UI, seamless contact/calendar integration
+| System Administrators | Easy deployment, configuration management, monitoring
+| Security Team | Encrypted communications, secure credential storage, compliance
+| Developers | Maintainable codebase, clear architecture, testability
+| Product Managers | Feature flexibility, platform support, performance
+|===
+
+== Goals
+
+=== Technical Goals
+
+* Cross-platform compatibility (Linux, macOS, Windows)
+* High call quality with adaptive codec selection
+* Low resource consumption and fast startup
+* Reliable background operation (system tray, autostart)
+* Comprehensive logging and debugging capabilities
+
+=== Non-Technical Goals
+
+* User-friendly interface with minimal training required
+* Flexible configuration for diverse deployment scenarios
+* Extensibility through plugin architecture (contact sources, chat plugins)
+* Internationalization support (i18n)
+* Open source development model
+
+=== Out of Scope
+
+* PSTN gateway functionality
+* PBX server functionality
+* Mobile platform support (iOS, Android)
+
+== Definition of Terms
+
+|===
+| Term | Definition
+
+| SIP | Session Initiation Protocol (RFC 3261)
+| AOR | Address of Record - SIP URI identifying a user
+| SRTP | Secure Real-time Transport Protocol
+| ICE | Interactive Connectivity Establishment (RFC 5245)
+| STUN | Session Traversal Utilities for NAT
+| TURN | Traversal Using Relays around NAT
+| CardDAV | Contacts distributed via CalDAV protocol
+| CalDAV | Calendar distributed via CalDAV protocol
+| RTT | Real-time text (RFC 4103)
+|===
+
+== General Constraints
+
+* Must use Qt6 framework for cross-platform GUI
+* Must use PJSIP library for SIP stack implementation
+* Must support C++17 standard
+* Must follow Qt coding conventions
+* Must be buildable with CMake and Conan dependency management
+* Must support Flatpak packaging for Linux distribution
+* Must comply with DCO (Developer Certificate of Origin) for contributions
\ No newline at end of file
diff --git a/docs/modules/architecture/pages/requirements-and-constraints.adoc b/docs/modules/architecture/pages/requirements-and-constraints.adoc
new file mode 100644
index 00000000..0946d44d
--- /dev/null
+++ b/docs/modules/architecture/pages/requirements-and-constraints.adoc
@@ -0,0 +1,147 @@
+= Requirements and Constraints
+
+== Functional Requirements
+
+=== SIP Communication
+
+|===
+| ID | Requirement | Priority
+
+| FR-SIP-001 | Support SIP accounts with TLS/UDP/TCP transport | High
+| FR-SIP-002 | Support SRTP media encryption | High
+| FR-SIP-003 | Support multiple SIP accounts simultaneously | High
+| FR-SIP-004 | Support preferred identity selection | Medium
+| FR-SIP-005 | Support call forwarding and transfer | Medium
+| FR-SIP-006 | Support RTT (Real-time text) | Medium
+| FR-SIP-007 | Support DTMF tones in call | Medium
+|===
+
+=== Call Management
+
+|===
+| ID | Requirement | Priority
+
+| FR-CALL-001 | Support incoming call handling | High
+| FR-CALL-002 | Support outgoing call initiation | High
+| FR-CALL-003 | Support call hold/unhold | High
+| FR-CALL-004 | Support call mute/unmute | High
+| FR-CALL-005 | Support call history tracking | High
+| FR-CALL-006 | Support emergency call priority | High
+| FR-CALL-007 | Support call quality metrics display | Medium
+|===
+
+=== Contact Management
+
+|===
+| ID | Requirement | Priority
+
+| FR-CONTACT-001 | Support LDAP contact sources | High
+| FR-CONTACT-002 | Support CardDAV contact sources | High
+| FR-CONTACT-003 | Support Akonadi contact sources | Medium
+| FR-CONTACT-004 | Support EDS contact sources | Medium
+| FR-CONTACT-005 | Support CSV contact import | Medium
+| FR-CONTACT-006 | Support fuzzy contact search | High
+| FR-CONTACT-007 | Support contact avatar display | Medium
+| FR-CONTACT-008 | Support buddy state subscription | Medium
+|===
+
+=== Calendar Integration
+
+|===
+| ID | Requirement | Priority
+
+| FR-CAL-001 | Support CalDAV calendar sources | High
+| FR-CAL-002 | Support Akonadi calendar sources | Medium
+| FR-CAL-003 | Support EDS calendar sources | Medium
+| FR-CAL-004 | Support event-based call initiation | Medium
+| FR-CAL-005 | Support room booking integration | Low
+|===
+
+=== User Interface
+
+|===
+| ID | Requirement | Priority
+
+| FR-UI-001 | Support QML-based user interface | High
+| FR-UI-002 | Support custom window decoration | High
+| FR-UI-003 | Support system tray integration | High
+| FR-UI-004 | Support internationalization (i18n) | High
+| FR-UI-005 | Support accessibility features | Medium
+| FR-UI-006 | Support keyboard shortcuts | Medium
+|===
+
+== Non-Functional Requirements
+
+=== Performance
+
+|===
+| ID | Requirement | Target
+
+| NFR-PERF-001 | Application startup time | < 3 seconds
+| NFR-PERF-002 | Call establishment time | < 2 seconds
+| NFR-PERF-003 | Contact search response time | < 500ms
+| NFR-PERF-004 | Memory usage (idle) | < 200MB
+| NFR-PERF-005 | CPU usage (idle) | < 5%
+|===
+
+=== Quality
+
+|===
+| ID | Requirement | Target
+
+| NFR-QUAL-001 | Call quality MOS score | > 3.5
+| NFR-QUAL-002 | Audio latency | < 150ms
+| NFR-QUAL-003 | Video frame rate | >= 25 fps
+| NFR-QUAL-004 | Jitter buffer adaptation | < 100ms
+|===
+
+=== Reliability
+
+|===
+| ID | Requirement | Target
+
+| NFR-REL-001 | Call drop rate | < 1%
+| NFR-REL-002 | Uptime (background) | 24/7
+| NFR-REL-003 | Auto-reconnect on network failure | Yes
+| NFR-REL-004 | Config persistence | 100%
+|===
+
+=== Security
+
+|===
+| ID | Requirement | Target
+
+| NFR-SEC-001 | SIP signaling encryption | TLS 1.2+
+| NFR-SEC-002 | Media encryption | SRTP
+| NFR-SEC-003 | Credential storage | Encrypted keychain
+| NFR-SEC-004 | Certificate validation | Strict
+|===
+
+== Architectural Constraints
+
+=== Technology Constraints
+
+* Qt6 framework (minimum version 6.5)
+* C++17 standard
+* PJSIP library (v2.14+)
+* CMake build system (v3.20+)
+* Conan dependency manager
+* Qt Quick Controls 2
+* Qt WebEngine (for Jitsi integration)
+
+=== Platform Constraints
+
+|===
+| Platform | Minimum Version | Notes
+
+| Linux | Ubuntu 20.04+ | Flatpak and native builds
+| macOS | macOS 12.0+ | Apple Silicon and Intel
+| Windows | Windows 10 21H2+ | MSVC 2022
+|===
+
+=== Dependency Constraints
+
+* Must use Qt's parent-child memory management
+* Must avoid raw new/delete in favor of Qt containers
+* Must use QML for UI, C++ for business logic
+* Must follow Qt coding style guidelines
diff --git a/docs/modules/architecture/pages/runtime-view.adoc b/docs/modules/architecture/pages/runtime-view.adoc
new file mode 100644
index 00000000..afc5a375
--- /dev/null
+++ b/docs/modules/architecture/pages/runtime-view.adoc
@@ -0,0 +1,306 @@
+= Runtime View
+
+== Application Startup Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "main.cpp" as Main
+participant "Application" as App
+participant "StateManager" as SM
+participant "SIPManager" as SIPM
+participant "AddressBookManager" as ABM
+participant "DateEventFeederManager" as DEM
+participant "Credentials" as Creds
+participant "UISettings" as UIS
+participant "SystemTrayMenu" as STM
+participant "BackgroundManager" as BG
+
+User -> Main : Launch application
+Main -> App : Construct Application
+App -> SM : Initialize state
+SM --> App : State ready
+App -> UIS : Load settings
+UIS --> App : Settings loaded
+App -> ABM : Initialize address books
+App -> DEM : Initialize calendar feeders
+App -> STM : Create tray menu
+App -> Creds : Initialize credentials
+Creds --> App : Credentials loaded
+App -> BG : Request autostart
+BG --> App : Autostart configured
+App -> SIPM : Initialize SIP (after creds)
+SIPM --> App : SIP ready
+App --> Main : Application ready
+Main --> User : UI displayed
+@enduml
+....
+
+== SIP Initialization Sequence
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPManager" as SIPM
+participant "SIPEventLoop" as SEL
+participant "SIPMediaConfig" as SMC
+participant "SIPUserAgentConfig" as SUAC
+participant "SIPAccountManager" as SAM
+participant "PJSIP" as PJSIP
+
+SIPM -> SEL : Create event loop
+SIPM -> SMC : Create media config
+SIPM -> SUAC : Create UA config
+SIPM -> SAM : Create account manager
+SAM -> PJSIP : Create endpoint
+PJSIP --> SAM : Endpoint created
+SAM --> SIPM : Accounts ready
+SIPM -> SEL : Start event loop
+SEL --> SIPM : Event loop running
+@enduml
+....
+
+== Call State Machine
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam stateStyle mode
+
+title SIPCall State Machine
+
+state "Idle" as Idle
+state "Dialing" as Dialing
+state "Ringing" as Ringing
+state "EarlyMedia" as EarlyMedia
+state "Established" as Established
+state "Hold" as Hold
+state "Terminating" as Terminating
+state "Terminated" as Terminated
+
+Idle --> Dialing : INVITE sent
+Dialing --> Ringing : 180/183 received
+Dialing --> Idle : Cancel/Timeout
+Ringing --> EarlyMedia : 183/200 OK
+Ringing --> Terminated : Reject
+EarlyMedia --> Established : ACK sent
+EarlyMedia --> Terminated : BYE sent
+Established --> Hold : Hold requested
+Established --> Terminating : BYE sent
+Hold --> Established : Unhold requested
+Hold --> Terminating : BYE sent
+Terminating --> Terminated : 200 OK
+@enduml
+....
+
+== Contact Synchronization Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "Application" as App
+participant "AddressBookManager" as ABM
+participant "ContactSource1" as CS1
+participant "ContactSource2" as CS2
+participant "ContactSource3" as CS3
+participant "ContactModel" as CM
+participant "AvatarManager" as AM
+
+App -> ABM : Initialize
+ABM -> CS1 : Start loading
+ABM -> CS2 : Start loading
+ABM -> CS3 : Start loading
+CS1 --> ABM : Contacts batch 1
+CS2 --> ABM : Contacts batch 1
+CS3 --> ABM : Contacts batch 1
+CS1 --> ABM : Contacts batch 2
+CS2 --> ABM : Contacts batch 2
+CS3 --> ABM : Contacts batch 2
+ABM -> CM : Update model
+CM --> ABM : Acknowledged
+ABM --> App : Ready
+App --> User : Contact search available
+AM -> CS1 : Request avatar
+AM -> CS2 : Request avatar
+CS1 --> AM : Avatar data
+CS2 --> AM : Avatar data
+AM --> CM : Update avatars
+@enduml
+....
+
+== Buddy State Update Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "PJSIP" as PJSIP
+participant "SIPManager" as SIPM
+participant "SIPBuddy" as SB
+participant "ContactModel" as CM
+participant "UI" as UI
+
+PJSIP --> SB : NOTIFY: presence state
+SB -> SIPM : State changed
+SIPM --> CM : Emit buddyStateChanged
+CM --> UI : Update buddy icon
+UI --> SB : Acknowledged
+@enduml
+....
+
+== Call Quality Monitoring
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPCall" as SC
+participant "PJSIP" as PJSIP
+participant "SIPCallManager" as SCM
+participant "UI" as UI
+
+SC -> PJSIP : Get RTCP stats
+PJSIP --> SC : RTCP statistics
+SC -> SC : Calculate MOS
+SC -> SCM : Notify quality
+SCM --> UI : Update quality display
+SC -> PJSIP : Get jitter buffer stats
+PJSIP --> SC : Jitter buffer info
+SC -> SC : Update metrics
+SC --> SCM : Quality info changed
+@enduml
+....
+
+== Emergency Call Flow
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "UI" as UI
+participant "StateManager" as SM
+participant "SIPCallManager" as SCM
+participant "SIPCall" as SC
+participant "PJSIP" as PJSIP
+
+User -> UI : Click emergency button
+UI -> SM : Request emergency call
+SM --> UI : Authorization granted
+UI -> SCM : Create emergency call
+SCM -> SC : Initialize call
+SC -> SC : Mark as emergency
+SC -> PJSIP : Send INVITE
+PJSIP --> SC : 100 Trying
+SC --> SCM : State changed
+SCM --> UI : Call in progress
+PJSIP --> SC : 180 Ringing
+SC --> SCM : State changed
+SCM --> UI : Update UI
+PJSIP --> SC : 200 OK
+SC --> SCM : Call established
+SCM --> UI : Update UI
+@enduml
+....
+
+== Configuration Save/Load Cycle
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "SettingsWindow" as SW
+participant "AppSettings" as AS
+participant "Keychain" as KC
+participant "SIPManager" as SIPM
+participant "UISettings" as UIS
+
+User -> SW : Modify settings
+SW -> AS : Save settings
+AS --> SW : Saved
+SW -> KC : Save credentials
+KC --> SW : Encrypted
+SW -> SIPM : Apply SIP settings
+SIPM --> SW : Settings applied
+SW -> UIS : Apply UI settings
+UIS --> SW : UI updated
+@enduml
+....
+
+== System Tray Menu Activation
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "SystemTray" as ST
+participant "SystemTrayMenu" as STM
+participant "Application" as App
+participant "StateManager" as SM
+
+User -> ST : Click tray icon
+ST --> STM : Show menu
+STM --> App : Request window
+App --> STM : Window reference
+STM --> SM : Get call state
+SM --> STM : Current state
+STM --> User : Menu displayed
+User -> STM : Select action
+STM --> App : Execute action
+App --> STM : Action completed
+@enduml
+....
+
+== D-Bus Activation
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "External Process" as Ext
+participant "D-Bus" as DBUS
+participant "StateManager" as SM
+participant "Application" as App
+
+Ext -> DBUS : Activate signal
+DBUS --> SM : Activate called
+SM -> App : Bring to front
+App --> SM : Window shown
+SM --> Ext : Activation complete
+@enduml
+....
diff --git a/docs/modules/architecture/pages/security.adoc b/docs/modules/architecture/pages/security.adoc
new file mode 100644
index 00000000..18669e5b
--- /dev/null
+++ b/docs/modules/architecture/pages/security.adoc
@@ -0,0 +1,403 @@
+= Security Considerations
+
+== Security Architecture Overview
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Security Architecture
+
+package "Security Boundaries" as Sec {
+  component "Application Sandbox" as Sandbox
+  component "Credential Store" as Creds
+  component "Network Layer" as Net
+}
+
+package "Authentication" as Auth {
+  component "SIP Digest Auth" as SIPAuth
+  component "LDAP Bind" as LDAPBind
+  component "CardDAV Auth" as CDAuth
+  component "CalDAV Auth" as CDVAuth
+}
+
+package "Encryption" as Enc {
+  component "TLS/SSL" as TLS
+  component "SRTP" as SRTP
+  component "Keychain" as KC
+}
+
+package "Access Control" as AC {
+  component "Contact Blocking" as CB
+  component "Emergency Override" as EO
+  component "Permission Checks" as PC
+}
+
+Sandbox --> Creds : Secure access
+Sandbox --> Net : Encrypted comms
+Net --> TLS : Transport security
+Net --> SRTP : Media security
+Auth --> SIPAuth : SIP authentication
+Auth --> LDAPBind : LDAP authentication
+Auth --> CDAuth : CardDAV authentication
+Auth --> CDVAuth : CalDAV authentication
+AC --> CB : Contact filtering
+AC --> EO : Emergency priority
+AC --> PC : Permission validation
+@enduml
+....
+
+== Authentication Mechanisms
+
+=== SIP Authentication
+
+GOnnect supports SIP digest authentication as defined in RFC 2617.
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "SIPAccount" as SA
+participant "Credentials" as Creds
+participant "SIP Server" as SIPServer
+
+User -> SA : Configure account
+SA -> Creds : Store credentials
+Creds --> SA : Encrypted storage
+
+User -> SA : Initiate call
+SA -> Creds : Retrieve credentials
+Creds --> SA : Decrypted credentials
+SA -> SIPServer : REGISTER with auth
+SIPServer --> SA : 401 Unauthorized
+SA -> SIPServer : REGISTER with digest
+SIPServer --> SA : 200 OK
+@enduml
+....
+
+=== Authentication Methods
+
+|===
+| Method | Protocol | Security Level | Description
+
+| Digest | SIP | High | RFC 2617 digest authentication
+| Basic | SIP | Low | Base64 encoded credentials (not recommended)
+| GSSAPI | LDAP | High | Kerberos-based authentication
+| Plain | LDAP | Medium | Simple bind with password
+| OAuth 2.0 | CardDAV/CalDAV | High | Token-based authentication
+| Basic | CardDAV/CalDAV | Medium | HTTP Basic auth
+|===
+
+== Encryption Implementation
+
+=== Transport Security
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title Transport Security Layers
+
+rectangle "Application" as App {
+  component "SIP Manager" as SIPM
+  component "Network Layer" as Net
+}
+
+rectangle "Transport Layer" as Transport {
+  component "TLS 1.2+" as TLS
+  component "DTLS-SRTP" as DTLS
+}
+
+rectangle "Network" as Network {
+  component "Firewall" as FW
+  component "Internet" as Internet
+}
+
+App --> Transport : Secure channel
+Transport --> Network : Encrypted packets
+Net --> TLS : SIP over TLS
+Net --> DTLS : Media over DTLS
+@enduml
+....
+
+=== Encryption Configuration
+
+|===
+| Parameter | Value | Description
+
+| TLS Version | 1.2+ | Minimum TLS version
+| Cipher Suites | AES-256-GCM | Preferred cipher suites
+| Certificate Validation | Strict | Enforce certificate validation
+| SRTP Profile | AES_CM_128_HMAC_SHA1_80 | Default SRTP profile
+|===
+
+=== Certificate Handling
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+participant "SIPManager" as SIPM
+participant "Certificate Store" as CertStore
+participant "CA Bundle" as CABundle
+participant "SIP Server" as SIPServer
+
+SIPM -> CertStore : Load CA certificates
+CertStore --> SIPM : CA bundle
+SIPM -> SIPServer : TLS handshake
+SIPServer --> SIPM : Server certificate
+SIPM -> CABundle : Validate certificate
+CABundle --> SIPM : Validation result
+SIPM --> SIPServer : Handshake complete
+@enduml
+....
+
+== Credential Storage
+
+=== Keychain Integration
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "User" as User
+participant "Credentials" as Creds
+participant "Keychain Service" as KC
+participant "SIPAccount" as SA
+
+User -> SA : Enter password
+SA -> Creds : Store credential
+Creds -> KC : Save to keychain
+KC --> Creds : Encrypted storage
+
+SA -> Creds : Request password
+Creds -> KC : Get credential
+KC --> Creds : Prompt user
+User -> KC : Enter password
+KC --> Creds : Decrypted password
+Creds --> SA : Password for auth
+@enduml
+....
+
+=== Keychain Service Configuration
+
+|===
+| Platform | Keychain Service | Service Name | Label
+
+| Linux | Secret Service API | gonnect | GOnnect credentials
+| macOS | Keychain | gonnect | GOnnect credentials
+| Windows | Windows Credential Manager | gonnect | GOnnect credentials
+|===
+
+=== Credential Security
+
+|===
+| Aspect | Implementation | Notes
+
+| Storage | Platform keychain | Encrypted at rest
+| Access | Runtime only | Never written to disk
+| Transmission | In-memory only | No network transmission
+| Logging | Never logged | Credentials excluded from logs
+|===
+
+== Contact Privacy
+
+=== Contact Blocking
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam sequenceMessageAlign center
+skinparam participantStyle rectangle
+
+actor "Caller" as Caller
+participant "SIPCall" as SC
+participant "BlockInfo" as BI
+participant "SIPManager" as SIPM
+
+Caller -> SIPM : INVITE
+SIPM -> BI : Check block list
+BI --> SIPM : Contact blocked
+SIPM -> SC : Reject call
+SC --> Caller : 603 Declined
+@enduml
+....
+
+=== Blocking Configuration
+
+|===
+| Field | Description | Default
+
+| blocked | Contact is blocked | false
+| blockSipCode | SIP response code | 603 (Declined)
+| blockReason | Reason for blocking | Configurable
+|===
+
+== Network Security
+
+=== Firewall Configuration
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Firewall Requirements
+
+rectangle "GOnnect" as App {
+  component "SIP Signaling" as SIP
+  component "Media Streams" as Media
+  component "External Services" as Ext
+}
+
+rectangle "Firewall" as FW {
+  component "Outbound Rules" as Out
+  component "Inbound Rules" as In
+}
+
+rectangle "Network" as Network {
+  component "SIP Server" as SIPServer
+  component "External Servers" as ExtServers
+}
+
+App --> FW : Request access
+FW --> Out : Allow outbound
+FW --> In : Allow inbound (RTP)
+Out --> SIPServer : SIP/TLS (5061)
+Out --> ExtServers : CardDAV/CalDAV
+In --> Media : RTP/RTCP (10000-20000)
+@enduml
+....
+
+=== Required Ports
+
+|===
+| Port | Protocol | Direction | Purpose
+
+| 5061 | TCP/TLS | Outbound | SIP signaling
+| 5060 | TCP/UDP | Outbound | SIP signaling
+| 10000-20000 | UDP | Bidirectional | RTP media
+| 3478 | UDP/TCP | Outbound | STUN/TURN
+| 443 | TCP/TLS | Outbound | CardDAV/CalDAV/Jitsi
+|===
+
+== Security Best Practices
+
+=== Configuration Security
+
+|===
+| Practice | Description | Priority
+
+| Use strong passwords | Minimum 12 characters with complexity | High
+| Enable TLS | Always use TLS for SIP signaling | High
+| Validate certificates | Enable strict certificate validation | High
+| Regular updates | Keep application updated | High
+| Secure keychain | Protect system keychain access | High
+|===
+
+=== Operational Security
+
+|===
+| Practice | Description | Frequency
+
+| Audit logs | Review application logs | Weekly
+| Check certificates | Verify certificate validity | Monthly
+| Update credentials | Rotate passwords periodically | Quarterly
+| Review access | Audit who has access | Monthly
+|===
+
+== Threat Model
+
+=== Identified Threats
+
+|===
+| Threat | Impact | Mitigation
+
+| Eavesdropping | Confidentiality | TLS/SRTP encryption
+| Man-in-the-Middle | Integrity | Certificate validation
+| Credential Theft | Confidentiality | Keychain storage
+| Unauthorized Access | Availability | Authentication
+| Denial of Service | Availability | Rate limiting
+|===
+
+=== Attack Surface
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Attack Surface
+
+rectangle "External Interfaces" as Ext {
+  component "SIP Interface" as SIP
+  component "Network Services" as Net
+  component "File System" as FS
+}
+
+rectangle "Internal Components" as Int {
+  component "SIP Stack" as SIPStack
+  component "Contact Manager" as CM
+  component "Credential Handler" as CH
+}
+
+rectangle "Data Stores" as Data {
+  component "Keychain" as KC
+  component "Config Files" as CFG
+  component "Cache" as Cache
+}
+
+Ext --> Int : Interface exposure
+Int --> Data : Data access
+SIP --> SIPStack : SIP processing
+Net --> Net : Network services
+FS --> Cache : File operations
+CH --> KC : Credential access
+@enduml
+....
+
+== Security Compliance
+
+=== Protocol Compliance
+
+|===
+| Standard | Compliance | Notes
+
+| RFC 3261 (SIP) | Full | Complete SIP implementation
+| RFC 3264 (SDP) | Full | Offer/answer model
+| RFC 3711 (SRTP) | Full | Media encryption
+| RFC 5245 (ICE) | Full | NAT traversal
+| RFC 5389 (STUN) | Full | NAT discovery
+| RFC 5626 (SIP Outbound) | Full | Connection reuse
+|===
+
+=== Security Standards
+
+|===
+| Standard | Status | Notes
+
+| OWASP | Compliant | Follows OWASP guidelines
+| NIST | Compliant | Meets NIST recommendations
+| GDPR | Compliant | Privacy by design
+|===
diff --git a/docs/modules/architecture/pages/solution-strategy.adoc b/docs/modules/architecture/pages/solution-strategy.adoc
new file mode 100644
index 00000000..8a2414b1
--- /dev/null
+++ b/docs/modules/architecture/pages/solution-strategy.adoc
@@ -0,0 +1,204 @@
+= Solution Strategy
+
+== Overall Architecture
+
+GOnnect follows a layered architecture pattern with clear separation of concerns:
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Layered Architecture
+
+rectangle "Presentation Layer (QML)" as Presentation {
+  rectangle "Main Window" as MainWindow
+  rectangle "Call Window" as CallWindow
+  rectangle "Settings Window" as SettingsWindow
+  rectangle "Search Window" as SearchWindow
+}
+
+rectangle "Application Layer (C++)" as Application {
+  rectangle "Application" as App
+  rectangle "StateManager" as StateMgr
+  rectangle "UISettings" as UISettings
+  rectangle "ErrorBus" as ErrorBus
+}
+
+rectangle "Business Logic Layer (C++)" as Business {
+  rectangle "SIP Module" as SIP
+  rectangle "Contacts Module" as Contacts
+  rectangle "Calendar Module" as Calendar
+  rectangle "Media Module" as Media
+}
+
+rectangle "Infrastructure Layer (C++/External)" as Infrastructure {
+  rectangle "PJSIP" as PJSIP
+  rectangle "Qt Multimedia" as QtMedia
+  rectangle "Keychain" as Keychain
+  rectangle "D-Bus" as DBus
+}
+
+Presentation --> Application : QML Context
+Application --> Business : C++ Backend
+Business --> Infrastructure : External Libraries
+@enduml
+....
+
+== Design Patterns
+
+=== Singleton Pattern
+
+Used for global managers that should have exactly one instance:
+
+* `SIPManager` - SIP stack management
+* `StateManager` - Global application state
+* `AddressBookManager` - Contact source management
+* `UISettings` - UI configuration
+
+=== Factory Pattern
+
+Used for creating contact and calendar sources:
+
+* `IAddressBookFactory` - Contact source factory
+* `IDateEventFeederFactory` - Calendar source factory
+
+=== Observer Pattern
+
+Qt's signal/slot mechanism for event notification:
+
+* `ErrorBus` - Error event broadcasting
+* `StateManager` - State change notifications
+* `SIPManager` - Buddy state changes
+
+=== MVC Pattern
+
+For QML data models:
+
+* `CallsModel` - Call list model
+* `HistoryModel` - Call history model
+* `ContactModel` - Contact list model
+
+== Technology Decisions
+
+=== Why Qt/QML?
+
+* Cross-platform GUI framework
+* Declarative UI with QML
+* Strong C++ backend integration
+* Rich ecosystem of Qt modules
+* Active community and long-term support
+
+=== Why PJSIP?
+
+* Mature SIP stack (15+ years)
+* Feature-complete implementation
+* C++ wrapper (PJSUA2) available
+* Active development and maintenance
+* Industry-standard compliance
+
+=== Why CMake + Conan?
+
+* Modern build system with presets
+* Cross-platform compatibility
+* Conan for dependency management
+* Better dependency versioning than Qt's qmake
+
+== Module Responsibilities
+
+=== SIP Module
+
+|===
+| Component | Responsibility
+
+| SIPManager | Central SIP stack management, account handling
+| SIPCall | Individual call state and media handling
+| SIPCallManager | Call lifecycle management
+| SIPBuddy | Contact buddy state tracking
+| SIPAccount | Account configuration and credentials
+|===
+
+=== Contacts Module
+
+|===
+| Component | Responsibility
+
+| AddressBookManager | Contact source orchestration
+| Contact | Contact data model
+| ContactModel | QML contact list model
+| AvatarManager | Contact avatar handling
+|===
+
+=== Calendar Module
+
+|===
+| Component | Responsibility
+
+| DateEventFeederManager | Calendar source orchestration
+| DateEvent | Calendar event data model
+| DateEventsModel | QML calendar events model
+|===
+
+=== Media Module
+
+|===
+| Component | Responsibility
+
+| AudioManager | Audio device and stream management
+| VideoManager | Video device and stream management
+| AudioPort | Audio port routing
+|===
+
+== Component Interaction
+
+[plantuml]
+....
+@startuml
+skinparam backgroundColor #FFFFFF
+skinparam componentStyle uml2
+skinparam shadowing false
+
+title GOnnect Component Interaction
+
+package "UI Layer" as UI {
+  component "GonnectWindow" as GW
+  component "CallWindow" as CW
+  component "SettingsWindow" as SW
+}
+
+package "Application Layer" as Applayer {
+  component "Application" as App
+  component "StateManager" as SM
+  component "UISettings" as UIS
+}
+
+package "Business Layer" as Biz {
+  component "SIPManager" as SIPM
+  component "SIPCallManager" as SCM
+  component "AddressBookManager" as ABM
+  component "DateEventFeederManager" as DEM
+}
+
+package "Infrastructure" as Infra {
+  component "PJSIP" as PJS
+  component "Keychain" as KC
+  component "D-Bus" as DBUS
+}
+
+GW --> App : QML Context
+CW --> App : QML Context
+SW --> App : QML Context
+
+App --> SM : Singleton
+App --> SIPM : Singleton
+App --> ABM : Singleton
+App --> DEM : Singleton
+
+SM --> DBUS : Activation
+SIPM --> PJS : SIP Stack
+SIPM --> KC : Credentials
+ABM --> PJS : Buddy State
+@enduml
+....

From 2242f60c72cc82aa7b9e52752a6949819e3a4d23 Mon Sep 17 00:00:00 2001
From: "renovate-tokenizer[bot]"
 <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
Date: Tue, 7 Apr 2026 08:54:48 +0200
Subject: [PATCH 045/122] chore(deps): update flatpak/flatpak-github-actions
 action to v6.7 (#417)

Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
---
 .github/workflows/gonnect.yml       | 2 +-
 .github/workflows/release-build.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml
index 42b08cbe..dfec9019 100644
--- a/.github/workflows/gonnect.yml
+++ b/.github/workflows/gonnect.yml
@@ -256,7 +256,7 @@ jobs:
         run: |
           echo "gonnect_version=$(grep -oP 'GOnnect VERSION ([0-9.]+)' CMakeLists.txt | awk '{ print $3 }')" >> $GITHUB_OUTPUT
 
-      - uses: flatpak/flatpak-github-actions/flatpak-builder@92ae9851ad316786193b1fd3f40c4b51eb5cb101 # v6.6
+      - uses: flatpak/flatpak-github-actions/flatpak-builder@401fe28a8384095fc1531b9d320b292f0ee45adb # v6.7
         with:
           bundle: gonnect
           build-bundle: 'true'
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 2cd9c75d..99032a7c 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -33,7 +33,7 @@ jobs:
         run: |
           echo "gonnect_version=$(grep -oP 'GOnnect VERSION ([0-9.]+)' CMakeLists.txt | awk '{ print $3 }')" >> $GITHUB_OUTPUT
 
-      - uses: flatpak/flatpak-github-actions/flatpak-builder@92ae9851ad316786193b1fd3f40c4b51eb5cb101 # v6.6
+      - uses: flatpak/flatpak-github-actions/flatpak-builder@401fe28a8384095fc1531b9d320b292f0ee45adb # v6.7
         with:
           bundle: GOnnect.flatpak
           build-bundle: 'true'

From a30381c7d36ad4fadb4eee7ce5d51f17a744d1d3 Mon Sep 17 00:00:00 2001
From: "renovate-tokenizer[bot]"
 <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
Date: Tue, 7 Apr 2026 08:55:33 +0200
Subject: [PATCH 046/122] chore(deps): update renovatebot/github-action action
 to v46.1.8 (#416)

Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com>
---
 .github/workflows/renovate.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
index 4180c11a..31608fef 100644
--- a/.github/workflows/renovate.yml
+++ b/.github/workflows/renovate.yml
@@ -58,7 +58,7 @@ jobs:
             }
 
       - name: Self-hosted Renovate
-        uses: renovatebot/github-action@3633cede7d4d4598438e654eac4a695e46004420 # v46.1.7
+        uses: renovatebot/github-action@b67590ea780158ccd13192c22a3655a5231f869d # v46.1.8
         with:
           docker-cmd-file: .github/renovate-entrypoint.sh
           docker-user: root

From d82cf254b58f330d202b86212c42c3fc6e07d6f8 Mon Sep 17 00:00:00 2001
From: Michael Neuendorf 
Date: Fri, 10 Apr 2026 07:56:51 +0000
Subject: [PATCH 047/122] docs: fix link to docs

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 40d9d5d7..cb20ffcd 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 

- Documentation | + Documentation | Issues | Install

From 7a40d4beb0d07ccdfd928b2ea4ff1840cf46dd78 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Fri, 10 Apr 2026 11:09:23 +0000 Subject: [PATCH 048/122] ci: re-enable format job as the upstream issue is fixed --- .github/workflows/gonnect.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index e9ace3a8..dfec9019 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -120,8 +120,6 @@ jobs: path: build/Release/GOnnect*.dmg format: - # disable temporarily due to defect action: https://github.com/jidicula/clang-format-action/issues/267 - if: false runs-on: ubuntu-24.04 name: "Code Formatting" steps: From 2297e4998a87c7c98de98464c447c5e6ae2ea51b Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:50:47 +0200 Subject: [PATCH 049/122] chore(deps): update github actions (#421) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 8 ++++---- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index dfec9019..cf449062 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -52,7 +52,7 @@ jobs: run: cmake --build --preset conan-release --parallel $(nproc --all) - name: Upload Test artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: gonnect-tests path: build/Release/tests/contactsTest @@ -84,7 +84,7 @@ jobs: run: cd build && cpack ${{github.workspace}} - name: Save installer - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-installer.zip compression-level: 0 @@ -113,7 +113,7 @@ jobs: run: cd build/Release && cpack ${{github.workspace}} - name: Save installer - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: mac-installer.dmg compression-level: 0 @@ -157,7 +157,7 @@ jobs: scripts/static-clang-analyzer.sh - name: Save Report - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: clang-report path: /tmp/build.log diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 260b5ddb..995ad2de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 31608fef..f4c74a2c 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_KEY }} @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@b67590ea780158ccd13192c22a3655a5231f869d # v46.1.8 + uses: renovatebot/github-action@eb932558ad942cccfd8211cf535f17ff183a9f74 # v46.1.9 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 9e8171ba4ef5336e988d6938fa62793432d89ae5 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 13 Apr 2026 12:00:27 +0200 Subject: [PATCH 050/122] fix: c++ standard and guarding updates (#422) * C++ 23 updates * fix: emergency button label --------- Co-authored-by: Leah Jennebach --- CMakeLists.txt | 2 +- .../hid-rp/hid-rp/hid/rdf/descriptor_view.hpp | 4 +- i18n/gonnect_de.ts | 2 +- i18n/gonnect_es.ts | 2 +- i18n/gonnect_fa.ts | 2 +- i18n/gonnect_fr.ts | 2 +- i18n/gonnect_it.ts | 2 +- i18n/gonnect_ru.ts | 2 +- i18n/gonnect_uk.ts | 2 +- src/StateManager.cpp | 12 +++--- src/calendar/akonadi/AkonadiEventFeeder.cpp | 7 ++-- src/contacts/FuzzyCompare.cpp | 5 +-- src/contacts/FuzzyCompare.h | 8 ++-- src/contacts/eds/EDSAddressBookFeeder.cpp | 11 +++--- src/dbus/portal/AbstractPortal.cpp | 2 +- src/platform/NotificationIcon.cpp | 5 ++- src/platform/windows/WindowsInhibitHelper.cpp | 4 +- src/sip/NumberStats.cpp | 3 +- src/sip/SIPAccount.cpp | 2 +- src/sip/SIPCall.h | 2 +- src/sip/SIPCallManager.cpp | 2 +- src/sip/SIPManager.cpp | 6 +-- src/sip/SIPManager.h | 2 +- src/ui/CallsModel.cpp | 18 +++++---- src/ui/components/controls/FirstAidButton.qml | 2 +- src/usb/HeadsetDevice.cpp | 39 +++++++++++++------ src/usb/ReportDescriptorEnums.cpp | 11 +++--- src/usb/busylight/KuandoOmega.cpp | 8 +++- src/usb/busylight/LitraGlow.cpp | 11 ++++-- src/usb/busylight/LuxaforFlag.cpp | 4 +- 30 files changed, 110 insertions(+), 74 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 370caee7..bdbc2c8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ set(APPLICATION_URL "https://www.gonnect-project.org") set(CMAKE_COMPILE_WARNING_AS_ERROR ON) set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_UNITY_BUILD_BATCH_SIZE 10) diff --git a/external/hid-rp/hid-rp/hid/rdf/descriptor_view.hpp b/external/hid-rp/hid-rp/hid/rdf/descriptor_view.hpp index bf46c3dc..68d1226a 100644 --- a/external/hid-rp/hid-rp/hid/rdf/descriptor_view.hpp +++ b/external/hid-rp/hid-rp/hid/rdf/descriptor_view.hpp @@ -254,8 +254,8 @@ class descriptor_view_base : public items_view_base {} template constexpr descriptor_view_base(const TIter begin, const TIter end) - : descriptor_view_base(std::addressof(*begin), - std::addressof(*begin) + std::distance(begin, end)) + : descriptor_view_base(begin == end ? nullptr : std::addressof(*begin), + begin == end ? nullptr : std::addressof(*begin) + std::distance(begin, end)) {} private: diff --git a/i18n/gonnect_de.ts b/i18n/gonnect_de.ts index 868cf458..4db0d4f8 100644 --- a/i18n/gonnect_de.ts +++ b/i18n/gonnect_de.ts @@ -1965,7 +1965,7 @@ FirstAidButton - Emegency call + Emergency call Notruf diff --git a/i18n/gonnect_es.ts b/i18n/gonnect_es.ts index 41451147..04134ed4 100644 --- a/i18n/gonnect_es.ts +++ b/i18n/gonnect_es.ts @@ -1965,7 +1965,7 @@ FirstAidButton - Emegency call + Emergency call diff --git a/i18n/gonnect_fa.ts b/i18n/gonnect_fa.ts index 5878b86f..51c5c579 100644 --- a/i18n/gonnect_fa.ts +++ b/i18n/gonnect_fa.ts @@ -1963,7 +1963,7 @@ FirstAidButton - Emegency call + Emergency call diff --git a/i18n/gonnect_fr.ts b/i18n/gonnect_fr.ts index 7612ec26..e8e4b54d 100644 --- a/i18n/gonnect_fr.ts +++ b/i18n/gonnect_fr.ts @@ -1965,7 +1965,7 @@ FirstAidButton - Emegency call + Emergency call Appel d ' urgence diff --git a/i18n/gonnect_it.ts b/i18n/gonnect_it.ts index c1148660..a068789a 100644 --- a/i18n/gonnect_it.ts +++ b/i18n/gonnect_it.ts @@ -1965,7 +1965,7 @@ FirstAidButton - Emegency call + Emergency call Chiamata di emergenza diff --git a/i18n/gonnect_ru.ts b/i18n/gonnect_ru.ts index 9a23445f..166195d1 100644 --- a/i18n/gonnect_ru.ts +++ b/i18n/gonnect_ru.ts @@ -1967,7 +1967,7 @@ FirstAidButton - Emegency call + Emergency call diff --git a/i18n/gonnect_uk.ts b/i18n/gonnect_uk.ts index 9d551ecb..cc837f72 100644 --- a/i18n/gonnect_uk.ts +++ b/i18n/gonnect_uk.ts @@ -1967,7 +1967,7 @@ FirstAidButton - Emegency call + Emergency call diff --git a/src/StateManager.cpp b/src/StateManager.cpp index 9e555c15..359000a6 100644 --- a/src/StateManager.cpp +++ b/src/StateManager.cpp @@ -83,8 +83,8 @@ void StateManager::initialize() connect(&globalShortcuts, &GlobalShortcuts::activated, this, [](const QString &action) { auto &cm = SIPCallManager::instance(); if (action == "dial") { - qobject_cast(Application::instance())->rootWindow()->show(); - qobject_cast(Application::instance())->rootWindow()->raise(); + static_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->raise(); } else if (action == "hangup") { cm.endAllCalls(); } else if (action == "redial") { @@ -189,7 +189,7 @@ void StateManager::sendArguments(const QStringList &args) void StateManager::Activate(const QVariantMap &) { - qobject_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->show(); } void StateManager::ActivateAction(const QString &action_name, const QVariantList ¶meter, @@ -203,7 +203,7 @@ void StateManager::ActivateAction(const QString &action_name, const QVariantList QString value = v.toString(); if (value == "--show") { - qobject_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->show(); } else if (value == "--hangup") { SIPCallManager::instance().endAllCalls(); } else { @@ -212,7 +212,7 @@ void StateManager::ActivateAction(const QString &action_name, const QVariantList } } } else if (action_name == "Show") { - qobject_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->show(); } else if (action_name == "Hangup") { SIPCallManager::instance().endAllCalls(); } else if (action_name == "refreshIdentities") { @@ -266,6 +266,6 @@ void StateManager::Open(const QStringList &args, const QVariantMap &) } ActivateAction("invoke", vArgs, {}); } else { - qobject_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->show(); } } diff --git a/src/calendar/akonadi/AkonadiEventFeeder.cpp b/src/calendar/akonadi/AkonadiEventFeeder.cpp index 1f005344..f5e6aca9 100644 --- a/src/calendar/akonadi/AkonadiEventFeeder.cpp +++ b/src/calendar/akonadi/AkonadiEventFeeder.cpp @@ -16,7 +16,7 @@ AkonadiEventFeeder::AkonadiEventFeeder(QObject *parent, const QString &source, m_timeRangeStart(timeRangeStart), m_timeRangeEnd(timeRangeEnd), m_session(new Akonadi::Session("GOnnect::CalendarSession")), - m_monitor(new Akonadi::Monitor(parent)) + m_monitor(new Akonadi::Monitor()) { } @@ -122,7 +122,8 @@ void AkonadiEventFeeder::processCollections(KJob *job) // RRULE bool isRecurrent = event->recurs(); KCalendarCore::Recurrence *recurrence = event->recurrence(); - KCalendarCore::RecurrenceRule *rrule = recurrence->defaultRRule(); + KCalendarCore::RecurrenceRule *rrule = + isRecurrent ? recurrence->defaultRRule() : nullptr; // RID: The first ever recorded time of a recurrent event instance. We'll // use 'UID-UNIX_TIMESTAMP' as ID. @@ -161,7 +162,7 @@ void AkonadiEventFeeder::processCollections(KJob *job) QString location = event->location(); QString description = event->description(); - if (isRecurrent) { // Recurrent origin event, parsed first + if (isRecurrent && rrule) { // Recurrent origin event, parsed first // Get EXDATE's QList exdates; for (auto &exdate : recurrence->exDateTimes()) { diff --git a/src/contacts/FuzzyCompare.cpp b/src/contacts/FuzzyCompare.cpp index dc85ffa0..794c6aed 100644 --- a/src/contacts/FuzzyCompare.cpp +++ b/src/contacts/FuzzyCompare.cpp @@ -1,4 +1,5 @@ #include +#include #include "FuzzyCompare.h" quint8 FuzzyCompare::levenshteinDistance(const QString &a, const QString &b) @@ -7,9 +8,7 @@ quint8 FuzzyCompare::levenshteinDistance(const QString &a, const QString &b) QList v0(n + 1); QList v1(n + 1); - for (int i = 0; i <= n; ++i) { - v0[i] = i; - } + std::iota(v0.begin(), v0.end(), 0); for (int i = 0; i < a.size(); ++i) { v1[0] = i + 1; diff --git a/src/contacts/FuzzyCompare.h b/src/contacts/FuzzyCompare.h index 69d33278..4eea0eab 100644 --- a/src/contacts/FuzzyCompare.h +++ b/src/contacts/FuzzyCompare.h @@ -2,6 +2,8 @@ #include #include +#include +#include class FuzzyCompare { @@ -19,11 +21,11 @@ class FuzzyCompare QList idx(list.size()); std::iota(idx.begin(), idx.end(), 0); - std::sort(idx.begin(), idx.end(), [&weights](const qsizetype left, const qsizetype right) { + std::ranges::sort(idx, [&weights](const qsizetype left, const qsizetype right) { return weights.at(left) < weights.at(right); }); - std::transform(idx.begin(), idx.end(), list.begin(), - [&listCopy](const qsizetype i) { return listCopy[i]; }); + std::ranges::transform(idx, list.begin(), + [&listCopy](const qsizetype i) { return listCopy[i]; }); } }; diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index f9679432..3461f887 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -494,11 +494,12 @@ void EDSAddressBookFeeder::addAvatar(QString id, EContact *contact, QDateTime ch if (!image.isNull()) { QByteArray avatar; QBuffer buffer(&avatar); - buffer.open(QIODevice::WriteOnly); - // INFO: EDS stores contact photos as PNG ("*.image-2Fpng") - image.save(&buffer, "PNG"); - if (avatar.size()) { - AvatarManager::instance().addExternalImage(id, avatar, changed); + if (buffer.open(QIODevice::WriteOnly)) { + // INFO: EDS stores contact photos as PNG ("*.image-2Fpng") + image.save(&buffer, "PNG"); + if (avatar.size()) { + AvatarManager::instance().addExternalImage(id, avatar, changed); + } } } } diff --git a/src/dbus/portal/AbstractPortal.cpp b/src/dbus/portal/AbstractPortal.cpp index dbd71ed4..d7ef2c11 100644 --- a/src/dbus/portal/AbstractPortal.cpp +++ b/src/dbus/portal/AbstractPortal.cpp @@ -114,7 +114,7 @@ QString AbstractPortal::parentId() const auto unixServices = dynamic_cast( QGuiApplicationPrivate::platformIntegration()->services()); #endif - auto rootWindow = qobject_cast(qGuiApp)->rootWindow(); + auto rootWindow = static_cast(qGuiApp)->rootWindow(); if (rootWindow && unixServices) { parentId = unixServices->portalWindowIdentifier(rootWindow); } diff --git a/src/platform/NotificationIcon.cpp b/src/platform/NotificationIcon.cpp index 18f9a837..6b3a9560 100644 --- a/src/platform/NotificationIcon.cpp +++ b/src/platform/NotificationIcon.cpp @@ -50,6 +50,9 @@ NotificationIcon::NotificationIcon(const QString &fileUri, const QString &emblem } QBuffer buffer(&m_data); - buffer.open(QIODevice::WriteOnly); + if (!buffer.open(QIODevice::WriteOnly)) { + qCWarning(lcNotifications) << "unable to open buffer for PNG encoding"; + return; + } tmpImage.save(&buffer, "PNG"); } diff --git a/src/platform/windows/WindowsInhibitHelper.cpp b/src/platform/windows/WindowsInhibitHelper.cpp index 7c2455b3..1464960c 100644 --- a/src/platform/windows/WindowsInhibitHelper.cpp +++ b/src/platform/windows/WindowsInhibitHelper.cpp @@ -52,7 +52,7 @@ void WindowsInhibitHelper::inhibit(unsigned int flags, const QString &reason) Q_UNUSED(reason) if (!m_inhibit) { - auto app = qobject_cast(Application::instance()); + auto app = static_cast(Application::instance()); auto hwnd = app->rootWindow()->winId(); QString msg = QObject::tr("There are still phone calls going on"); ShutdownBlockReasonCreate(reinterpret_cast(hwnd), @@ -66,7 +66,7 @@ void WindowsInhibitHelper::inhibit(unsigned int flags, const QString &reason) void WindowsInhibitHelper::release() { if (m_inhibit) { - auto app = qobject_cast(Application::instance()); + auto app = static_cast(Application::instance()); auto hwnd = app->rootWindow()->winId(); ShutdownBlockReasonDestroy(reinterpret_cast(hwnd)); diff --git a/src/sip/NumberStats.cpp b/src/sip/NumberStats.cpp index be1e9768..3abe4267 100644 --- a/src/sip/NumberStats.cpp +++ b/src/sip/NumberStats.cpp @@ -7,6 +7,7 @@ #include #include #include +#include Q_LOGGING_CATEGORY(lcNumberStats, "gonnect.app.NumberStats") @@ -233,7 +234,7 @@ bool NumberStats::ensureFlaggedNumberExists(const QString &phoneNumber, "INSERT INTO contactflags (phonenumber, callcount, isFavorite, isBlocked, type) " "VALUES (:phoneNumber, 0, 0, 0, :type);"); query.bindValue(":phoneNumber", phoneNumber); - query.bindValue(":type", static_cast(contactType)); + query.bindValue(":type", std::to_underlying(contactType)); if (!query.exec()) { qCCritical(lcNumberStats) diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index 22d9a2d0..4372c383 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -81,7 +81,7 @@ void SIPAccount::initialize() if (!sipURI.match(voiceMailUri).hasMatch()) { qCCritical(lcSIPAccount) << "'voiceMailUri' is no valid SIP URI:" << voiceMailUri; ErrorBus::instance().addFatalError( - tr("'voiceMailUri' is no valid SIP URI: %1").arg(userUri)); + tr("'voiceMailUri' is no valid SIP URI: %1").arg(voiceMailUri)); Q_EMIT initialized(false); return; } diff --git a/src/sip/SIPCall.h b/src/sip/SIPCall.h index 8f5cd640..722eca2a 100644 --- a/src/sip/SIPCall.h +++ b/src/sip/SIPCall.h @@ -155,7 +155,7 @@ private Q_SLOTS: QList m_metadata; - pj::AudioMedia *m_aud_med = NULL; + pj::AudioMedia *m_aud_med = nullptr; IMHandler *m_imHandler = nullptr; Sniffer *m_sniffer = nullptr; diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp index ca55d4d9..e0c4907f 100644 --- a/src/sip/SIPCallManager.cpp +++ b/src/sip/SIPCallManager.cpp @@ -123,7 +123,7 @@ SIPCallManager::SIPCallManager(QObject *parent) : QObject(parent) connect(dev, &HeadsetDeviceProxy::dial, this, [this](const QString &number) { if (m_calls.count() == 0) { - qobject_cast(Application::instance())->rootWindow()->show(); + static_cast(Application::instance())->rootWindow()->show(); call(number); } }); diff --git a/src/sip/SIPManager.cpp b/src/sip/SIPManager.cpp index e2ab24a6..a441d06d 100644 --- a/src/sip/SIPManager.cpp +++ b/src/sip/SIPManager.cpp @@ -99,10 +99,10 @@ void SIPManager::initialize() m_uaConfig->applyConfig(epConfig); // Setup log writer - m_logWriter = new SIPLogWriter(); + m_logWriter = std::make_unique(); pj::LogConfig *log_cfg = &epConfig.logConfig; - log_cfg->writer = m_logWriter; + log_cfg->writer = m_logWriter.get(); log_cfg->decor = log_cfg->decor & ~(::pj_log_decoration::PJ_LOG_HAS_CR | ::pj_log_decoration::PJ_LOG_HAS_NEWLINE | ::pj_log_decoration::PJ_LOG_HAS_TIME | ::pj_log_decoration::PJ_LOG_HAS_MICRO_SEC); @@ -165,7 +165,7 @@ void SIPManager::setPreferredCodecs() int invalidCodecs = 0; for (const auto &pC : preferredCodecs) { try { - pj::CodecParam param = m_ep.codecGetParam(pC.toStdString()); + m_ep.codecGetParam(pC.toStdString()); // throws if codec is unknown } catch (pj::Error &err) { invalidCodecs++; } diff --git a/src/sip/SIPManager.h b/src/sip/SIPManager.h index f314abfa..99bbf6f5 100644 --- a/src/sip/SIPManager.h +++ b/src/sip/SIPManager.h @@ -91,7 +91,7 @@ class SIPManager : public QObject SIPMediaConfig *m_mediaConfig = nullptr; SIPUserAgentConfig *m_uaConfig = nullptr; - SIPLogWriter *m_logWriter = nullptr; + std::unique_ptr m_logWriter; SIPEventLoop *m_ev = nullptr; pj::Endpoint m_ep; diff --git a/src/ui/CallsModel.cpp b/src/ui/CallsModel.cpp index 94fabb77..00c16709 100644 --- a/src/ui/CallsModel.cpp +++ b/src/ui/CallsModel.cpp @@ -20,7 +20,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } connect(&callManager, &SIPCallManager::establishedChanged, this, [this](SIPCall *call) { auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->isEstablished = call->isEstablished(); callInfo->established = call->establishedTime(); callInfo->hasCapabilityJitsi = call->hasCapability("jitsi") && callInfo->isEstablished; @@ -39,7 +39,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } connect(&callManager, &SIPCallManager::metadataChanged, this, [this](SIPCall *call) { auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->hasMetadata = call->hasMetadata(); auto idx = createIndex(index, 0); @@ -51,7 +51,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->isHolding = call->isHolding(); auto idx = createIndex(index, 0); @@ -63,7 +63,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->isBlocked = call->isBlocked(); auto idx = createIndex(index, 0); @@ -76,7 +76,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->incomingAudioLevel = QtAudio::convertVolume( level, QtAudio::LinearVolumeScale, QtAudio::LogarithmicVolumeScale); @@ -175,7 +175,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto callInfo = m_callsHash.value(callId); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->statusCode = static_cast(statusCode); auto idx = createIndex(index, 0); @@ -187,7 +187,7 @@ CallsModel::CallsModel(QObject *parent) : QAbstractListModel{ parent } auto callInfo = m_callsHash.value(call->getId()); const auto index = m_calls.indexOf(callInfo); - if (index >= 0) { + if (callInfo && index >= 0) { callInfo->hasCapabilityJitsi = call->hasCapability("jitsi") && callInfo->isEstablished; auto idx = createIndex(index, 0); @@ -292,7 +292,9 @@ void CallsModel::updateCalls() } callInfo->callId = callId; - callInfo->accountId = qobject_cast(call->parent())->id(); + if (auto *account = qobject_cast(call->parent())) { + callInfo->accountId = account->id(); + } callInfo->remoteUri = call->sipUrl(); callInfo->established = call->establishedTime(); callInfo->isEstablished = call->isEstablished(); diff --git a/src/ui/components/controls/FirstAidButton.qml b/src/ui/components/controls/FirstAidButton.qml index 5f4eac63..69c1b749 100644 --- a/src/ui/components/controls/FirstAidButton.qml +++ b/src/ui/components/controls/FirstAidButton.qml @@ -6,7 +6,7 @@ import base Button { id: control visible: GlobalInfo.hasEmergencyNumbers - text: qsTr("Emegency call") + text: qsTr("Emergency call") icon.source: "qrc:/icons/ISO_7010_E004" + ViewHelper.culturalSphereExtension + ".svg" highlighted: true diff --git a/src/usb/HeadsetDevice.cpp b/src/usb/HeadsetDevice.cpp index d749e90f..df3ef023 100644 --- a/src/usb/HeadsetDevice.cpp +++ b/src/usb/HeadsetDevice.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "HeadsetDevice.h" #include "GlobalInfo.h" @@ -62,7 +63,7 @@ bool HeadsetDevice::open() // Read Teams info if available if (m_teamsUsageMapping.contains(UsageId::Teams_VendorExtension)) { unsigned char buf[256]; - memset(buf, 0, sizeof(buf)); + std::ranges::fill(buf, 0); // Read vendor extension from device buf[0] = m_teamsUsageMapping[UsageId::Teams_VendorExtension]; @@ -76,7 +77,7 @@ bool HeadsetDevice::open() // If we've display support, read the display configuration from device if (m_displaySupported && m_teamsUsageMapping.contains(UsageId::Teams_DisplayAttributes)) { - memset(buf, 0, sizeof(buf)); + std::ranges::fill(buf, 0); buf[0] = m_teamsUsageMapping[UsageId::Teams_DisplayAttributes]; auto res = hid_get_feature_report(device, buf, sizeof(buf)); if (res >= 5) { @@ -127,7 +128,9 @@ void HeadsetDevice::send(quint8 reportId, unsigned data) qCDebug(lcHeadset) << "Sending report" << QString::asprintf("0x%02X", reportId) << "with data" << QString::asprintf("0x%08X", data); - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write to headset device"; + } } void HeadsetDevice::setIdle() @@ -232,7 +235,9 @@ void HeadsetDevice::setMute(bool flag) qCInfo(lcHeadset) << "Sending 'mute' state with value" << flag << "to headset with display specification usage" << UsageId::Teams_IconsControl; - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write mute state to headset device"; + } } else { const auto &usage = m_hidUsages.value(UsageId::LED_Mute); const unsigned bitValue = 1 << usage.bitPosition; @@ -360,7 +365,7 @@ void HeadsetDevice::setTeamsUsageMapping(QHash teamsUsageMappi void HeadsetDevice::processEvents() { unsigned char data[64]; - memset(data, 0, sizeof(data)); + std::ranges::fill(data, 0); if (auto len = hid_read_timeout(m_device, data, sizeof(data), 10)) { quint8 reportId = data[0]; @@ -508,11 +513,13 @@ void HeadsetDevice::setDisplayField(ReportDescriptorEnums::TeamsDisplayFieldSupp buf[0] = m_teamsUsageMapping[UsageId::Teams_CharacterAttributes]; buf[1] = (quint8)field; buf[2] = text.length() ? 0x80 : 0; - hid_write(m_device, buf, 3); + if (hid_write(m_device, buf, 3) < 0) { + qCWarning(lcHeadset) << "failed to write display field header to headset device"; + } auto chunks = makeChunks(text, 8); for (auto i = 0; i < chunks.length(); i++) { - memset(buf, 0, sizeof(buf)); + std::ranges::fill(buf, 0); buf[0] = m_teamsUsageMapping[UsageId::Teams_CharacterReport]; buf[1] = i == chunks.length() - 1 ? 0x80 : 0; @@ -526,7 +533,9 @@ void HeadsetDevice::setDisplayField(ReportDescriptorEnums::TeamsDisplayFieldSupp buf[dataIndex++] = ch >> 8; } - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write display field chunk to headset device"; + } } } } @@ -610,7 +619,9 @@ void HeadsetDevice::selectScreen(ReportDescriptorEnums::TeamsScreenSelect screen buf[0] = m_teamsUsageMapping[UsageId::Teams_DisplayControl]; buf[1] = (((quint8)screen & 0b1111) << 3) + (backlight ? 4 : 0) + (clear ? 2 : 0) + 1; // 1 for enable - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write screen selection to headset device"; + } } } @@ -628,7 +639,9 @@ void HeadsetDevice::setPresenceIcon(ReportDescriptorEnums::TeamsPresenceIcon ico buf[0] = m_teamsUsageMapping[UsageId::Teams_IconsControl]; buf[1] = (quint8)m_presenceIcon & 0b1111; buf[2] = 0; - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write presence icon to headset device"; + } } } @@ -641,10 +654,12 @@ void HeadsetDevice::sendASP(quint8 cmd) if (m_teamsUsageMapping.contains(UsageId::Teams_ASPNotification)) { unsigned char buf[64]; - memset(buf, 0, sizeof(buf)); + std::ranges::fill(buf, 0); buf[0] = m_teamsUsageMapping[UsageId::Teams_ASPNotification]; buf[1] = cmd; // 0x00, 0x10 and 0x40 have been captured - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcHeadset) << "failed to write ASP notification to headset device"; + } } } diff --git a/src/usb/ReportDescriptorEnums.cpp b/src/usb/ReportDescriptorEnums.cpp index a5df1956..c383e073 100644 --- a/src/usb/ReportDescriptorEnums.cpp +++ b/src/usb/ReportDescriptorEnums.cpp @@ -1,24 +1,25 @@ #include "ReportDescriptorEnums.h" #include +#include QString ReportDescriptorEnums::toString(const UsageId id) { - return QMetaEnum::fromType().valueToKey(static_cast(id)); + return QMetaEnum::fromType().valueToKey(std::to_underlying(id)); } QString ReportDescriptorEnums::toString(const TeamsDisplayFieldSupport id) { - return QMetaEnum::fromType().valueToKey(static_cast(id)); + return QMetaEnum::fromType().valueToKey(std::to_underlying(id)); } QString ReportDescriptorEnums::toString(const TeamsScreenSelect id) { - return QMetaEnum::fromType().valueToKey(static_cast(id)); + return QMetaEnum::fromType().valueToKey(std::to_underlying(id)); } QString ReportDescriptorEnums::toString(const TeamsPresenceIcon id) { - return QMetaEnum::fromType().valueToKey(static_cast(id)); + return QMetaEnum::fromType().valueToKey(std::to_underlying(id)); } ReportDescriptorEnums::UsageId ReportDescriptorEnums::intToUsageId(const quint32 id) @@ -35,5 +36,5 @@ ReportDescriptorEnums::UsageId ReportDescriptorEnums::intToUsageId(const quint32 QString ReportDescriptorEnums::toString(const UsageType id) { - return QMetaEnum::fromType().valueToKey(static_cast(id)); + return QMetaEnum::fromType().valueToKey(std::to_underlying(id)); } diff --git a/src/usb/busylight/KuandoOmega.cpp b/src/usb/busylight/KuandoOmega.cpp index ce0bb941..43856f11 100644 --- a/src/usb/busylight/KuandoOmega.cpp +++ b/src/usb/busylight/KuandoOmega.cpp @@ -60,7 +60,9 @@ void KuandoOmega::send(bool on) buf[62] = checksum >> 8; buf[63] = checksum & 0xFF; - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcKuandoOmega) << "failed to write to Kuando Omega device"; + } } void KuandoOmega::sendKeepAlive() const @@ -89,5 +91,7 @@ void KuandoOmega::sendKeepAlive() const buf[62] = checksum >> 8; buf[63] = checksum & 0xFF; - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcKuandoOmega) << "failed to write keepalive to Kuando Omega device"; + } } diff --git a/src/usb/busylight/LitraGlow.cpp b/src/usb/busylight/LitraGlow.cpp index df5eb0b1..53e0d7ae 100644 --- a/src/usb/busylight/LitraGlow.cpp +++ b/src/usb/busylight/LitraGlow.cpp @@ -1,5 +1,6 @@ #include "LitraGlow.h" #include +#include #define REPORT_ID 0x11 #define DEVICE_ID 0xFF @@ -33,15 +34,19 @@ void LitraGlow::switchStreamlight(bool on) unsigned char buf[20]; // Switch on or off - memset(buf, 0, sizeof(buf)); + std::ranges::fill(buf, 0); buf[0] = REPORT_ID; buf[1] = DEVICE_ID; buf[2] = FEATURE_ILLUMINATION; buf[3] = SET_ILLUMINATION | SOFTWARE_ID; buf[4] = on ? 1 : 0; - hid_write(m_device, buf, sizeof(buf)); - hid_read_timeout(m_device, buf, sizeof(buf), 200); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcLitraGlow) << "failed to write to Litra Glow device"; + } + if (hid_read_timeout(m_device, buf, sizeof(buf), 200) < 0) { + qCWarning(lcLitraGlow) << "failed to read from Litra Glow device"; + } } void LitraGlow::send(bool on) diff --git a/src/usb/busylight/LuxaforFlag.cpp b/src/usb/busylight/LuxaforFlag.cpp index c51507c8..a541a6b9 100644 --- a/src/usb/busylight/LuxaforFlag.cpp +++ b/src/usb/busylight/LuxaforFlag.cpp @@ -31,5 +31,7 @@ void LuxaforFlag::send(bool on) buf[6] = 0x00; // Pad buf[7] = 0x00; // Pad - hid_write(m_device, buf, sizeof(buf)); + if (hid_write(m_device, buf, sizeof(buf)) < 0) { + qCWarning(lcLuxaforFlag) << "failed to write to Luxafor Flag device"; + } } From 5435feeebf2d721d7b4bd696383c879a13e2030f Mon Sep 17 00:00:00 2001 From: Mik- Date: Mon, 13 Apr 2026 12:45:31 +0200 Subject: [PATCH 051/122] chore(deps): update EDS to 3.60.1 (#420) * chore(deps): update EDS to 3.60 --------- Co-authored-by: Cajus Pollmeier --- resources/flatpak/de.gonicus.gonnect.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index 96c2802c..fa993c1d 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -8,7 +8,7 @@ command: gonnect add-extensions: de.gonicus.gonnect.plugin: - version: "2" + version: '2' directory: plugins subdirectories: true no-autodownload: true @@ -142,11 +142,11 @@ modules: sources: - type: git url: https://github.com/pjsip/pjproject.git - tag: "2.16" + tag: 2.16 commit: 6cab30cec44af36ba7c8a45ff7af2f74a8ed288c x-checker-data: type: git - tag-pattern: ^([0-9]+\.[0-9]+\.[0-9]+)$ + tag-pattern: ^([0-9]+\.[0-9]+(\.[0-9]+)?)$ - type: patch path: patches/pjproject-site-config.patch @@ -169,7 +169,7 @@ modules: type: anitya stable-only: true project-id: 2551 - url-template: "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${major}.${minor}.${patch}.tgz" + url-template: https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${major}.${minor}.${patch}.tgz - name: qtwebdav buildsystem: cmake-ninja @@ -249,8 +249,8 @@ modules: buildsystem: cmake-ninja sources: - type: archive - url: https://download.gnome.org/sources/evolution-data-server/3.59/evolution-data-server-3.59.2.tar.xz - sha256: 15bd798c85dee923462bebe880761b4668b6c5065584e840900988389add83bf + url: https://download.gnome.org/sources/evolution-data-server/3.60/evolution-data-server-3.60.1.tar.xz + sha256: 33f92d3b8822eba04c313796c0778cbb65a1a38472e857edc5f98854cca9b34c x-checker-data: type: gnome name: evolution-data-server @@ -282,8 +282,8 @@ modules: sources: - type: git url: https://github.com/jgaa/logfault.git - tag: v0.8.1 - commit: 9851bceb50e53de21cbbf27fef0b79c19667d3ec + tag: v0.8.5 + commit: 846e8a11abd88a6780591864fb37d182e33793b7 x-checker-data: type: git tag-pattern: ^(v[0-9.]+)$ From 48328e235cf888f00b95b4b6792b727138aaf35c Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Mon, 13 Apr 2026 15:01:39 +0200 Subject: [PATCH 052/122] fix: make ReadOnlyConfdSettings treate string lists correctly (#423) Co-authored-by: Cajus Pollmeier --- src/ReadOnlyConfdSettings.cpp | 50 +++++++++++++++++++++++++---------- src/ReadOnlyConfdSettings.h | 2 ++ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/ReadOnlyConfdSettings.cpp b/src/ReadOnlyConfdSettings.cpp index 6c6d9e58..d4f73bd9 100644 --- a/src/ReadOnlyConfdSettings.cpp +++ b/src/ReadOnlyConfdSettings.cpp @@ -61,8 +61,6 @@ QStringList ReadOnlyConfdSettings::getUserGroups() void ReadOnlyConfdSettings::readConfd() { static const QRegularExpression configFileName("^\\d+-[a-zA-Z0-9_-]+.conf$"); - static const QRegularExpression envPlaceholder("%ENV\\[([a-zA-Z0-9][A-Za-z0-9_]*)\\]%"); - static const QRegularExpression cfgPlaceholder("%CFG\\[([A_Za-z_/]+)\\]%"); const QString basePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/gonnect"; @@ -102,24 +100,48 @@ void ReadOnlyConfdSettings::readConfd() continue; } - QString settingsValue = tmpSettings.value(key).toString(); - auto envMatch = envPlaceholder.match(settingsValue); - if (envMatch.hasMatch()) { - settingsValue.replace( - envPlaceholder, - qEnvironmentVariable(envMatch.captured(1).toStdString().c_str())); - } - auto cfgMatch = cfgPlaceholder.match(settingsValue); - if (cfgMatch.hasMatch()) { - settingsValue.replace(cfgPlaceholder, value(cfgMatch.captured(1)).toString()); - } + QVariant settingsValue = tmpSettings.value(key); + + if (settingsValue.userType() == QMetaType::QStringList) { + QStringList newList; + const QStringList strings = settingsValue.toStringList(); + newList.reserve(strings.length()); + std::ranges::transform( + strings, std::back_inserter(newList), + [this](const QString &s) { return replacePlaceholders(s); }); - setValue(key, settingsValue); + setValue(key, newList); + + } else if (settingsValue.userType() == QMetaType::QString) { + setValue(key, replacePlaceholders(settingsValue.toString())); + } else { + setValue(key, settingsValue); + } } } } } +QString ReadOnlyConfdSettings::replacePlaceholders(const QString &settingsStringValue) const +{ + static const QRegularExpression envPlaceholder("%ENV\\[([a-zA-Z0-9][A-Za-z0-9_]*)\\]%"); + static const QRegularExpression cfgPlaceholder("%CFG\\[([A_Za-z_/]+)\\]%"); + + QString str = settingsStringValue; + + auto envMatch = envPlaceholder.match(str); + if (envMatch.hasMatch()) { + str.replace(envPlaceholder, + qEnvironmentVariable(envMatch.captured(1).toStdString().c_str())); + } + auto cfgMatch = cfgPlaceholder.match(str); + if (cfgMatch.hasMatch()) { + str.replace(cfgPlaceholder, value(cfgMatch.captured(1)).toString()); + } + + return str; +} + QString ReadOnlyConfdSettings::hashForSettingsGroup(const QString &group) { beginGroup(group); diff --git a/src/ReadOnlyConfdSettings.h b/src/ReadOnlyConfdSettings.h index 37cbc911..544afc54 100644 --- a/src/ReadOnlyConfdSettings.h +++ b/src/ReadOnlyConfdSettings.h @@ -18,4 +18,6 @@ class ReadOnlyConfdSettings : public QSettings #endif void readConfd(); + + QString replacePlaceholders(const QString &value) const; }; From be2eb60249c37576bc767b30524cd72d2f2f310e Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 14 Apr 2026 11:24:39 +0200 Subject: [PATCH 053/122] chore: remove QCA as it is deprecated (#425) * Remove QCA as it is deprecated --- conanfile.py | 1 - resources/conan/recipes/qca/all/conandata.yml | 7 - resources/conan/recipes/qca/all/conanfile.py | 83 --------- .../patches/fix-windows-static-build.patch | 15 -- resources/conan/recipes/qca/config.yml | 8 - src/CMakeLists.txt | 2 - src/dbus/portal/SecretPortal.cpp | 173 +++++++++++++----- src/dbus/portal/SecretPortal.h | 2 +- src/pch.h | 1 - 9 files changed, 131 insertions(+), 161 deletions(-) delete mode 100644 resources/conan/recipes/qca/all/conandata.yml delete mode 100644 resources/conan/recipes/qca/all/conanfile.py delete mode 100644 resources/conan/recipes/qca/all/patches/fix-windows-static-build.patch delete mode 100644 resources/conan/recipes/qca/config.yml diff --git a/conanfile.py b/conanfile.py index 9aed2237..b96ad09c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -83,7 +83,6 @@ def requirements(self): self.requires("wayland/1.24.0", override=True) self.requires("openssl/3.5.5", override=True) - self.requires("qca/2.3.10") self.requires("qtwebdav/2025-03-16") self.requires("qtkeychain/0.15.0") self.requires("libusb/1.0.29") diff --git a/resources/conan/recipes/qca/all/conandata.yml b/resources/conan/recipes/qca/all/conandata.yml deleted file mode 100644 index 49249077..00000000 --- a/resources/conan/recipes/qca/all/conandata.yml +++ /dev/null @@ -1,7 +0,0 @@ -sources: - "2.3.10": - url: "https://github.com/KDE/qca/archive/refs/tags/v2.3.10.zip" - sha256: "2d95f384255ad96832f93c14ae51d24828d6fea373a9045dada40007a7319659" -patches: - "2.3.10": - - patch_file: "patches/fix-windows-static-build.patch" diff --git a/resources/conan/recipes/qca/all/conanfile.py b/resources/conan/recipes/qca/all/conanfile.py deleted file mode 100644 index ba982964..00000000 --- a/resources/conan/recipes/qca/all/conanfile.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -from conan import ConanFile -from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps -from conan.tools.files import apply_conandata_patches, export_conandata_patches, get, collect_libs -from conan.tools.env import Environment, VirtualBuildEnv, VirtualRunEnv -from conan.tools.build import cross_building - -class QCA(ConanFile): - name = "qca" - license = "GPL" - homepage = "https://github.com/KDE/qca" - package_type = "library" - - settings = "os", "arch", "compiler", "build_type" - options = { - "shared": [True, False], - "fPIC": [True, False], - "with_conan_qt": [True, False], - } - default_options = { - "shared": False, - "fPIC": True, - "with_conan_qt": False, - } - - def export_sources(self): - export_conandata_patches(self) - - def requirements(self): - if self.options.with_conan_qt: - self.requires("qt/6.10.1") - - self.requires("openssl/3.5.4") - - def source(self): - get(self, **self.conan_data["sources"][self.version], strip_root=True) - - def config_options(self): - if self.settings.os == "Windows": - del self.options.fPIC - - def layout(self): - cmake_layout(self) - - def generate(self): - vbe = VirtualBuildEnv(self) - vbe.generate() - if not cross_building(self): - vre = VirtualRunEnv(self) - vre.generate(scope="build") - - # TODO: to remove when properly handled by conan (see https://github.com/conan-io/conan/issues/11962) - env = Environment() - env.unset("VCPKG_ROOT") - env.prepend_path("PKG_CONFIG_PATH", self.generators_folder) - env.vars(self).save_script("conanbuildenv_pkg_config_path") - - deps = CMakeDeps(self) - deps.generate() - tc = CMakeToolchain(self) - tc.variables['BUILD_WITH_QT6'] = True - tc.variables['BUILD_TESTS'] = False - tc.variables['BUILD_TOOLS'] = False - tc.variables['BUILD_PLUGINS'] = "ossl" - tc.variables['OSX_FRAMEWORK'] = False - tc.generate() - - def build(self): - apply_conandata_patches(self) - cmake = CMake(self) - cmake.configure() - cmake.build() - - def package(self): - cmake = CMake(self) - cmake.install() - - def package_info(self): - self.cpp_info.set_property("cmake_file_name", "Qca-qt6") - self.cpp_info.set_property("cmake_target_name", "qca-qt6") - self.cpp_info.libs = collect_libs(self) - self.cpp_info.includedirs.append(os.path.join("include", "Qca-qt6")) - self.cpp_info.includedirs.append(os.path.join("include", "Qca-qt6", "QtCrypto")) diff --git a/resources/conan/recipes/qca/all/patches/fix-windows-static-build.patch b/resources/conan/recipes/qca/all/patches/fix-windows-static-build.patch deleted file mode 100644 index 625bc51e..00000000 --- a/resources/conan/recipes/qca/all/patches/fix-windows-static-build.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/cmake/modules/QcaMacro.cmake b/cmake/modules/QcaMacro.cmake -index 94f2562d..64dff0ff 100644 ---- a/cmake/modules/QcaMacro.cmake -+++ b/cmake/modules/QcaMacro.cmake -@@ -66,8 +66,8 @@ endmacro(add_qca_test) - - macro(install_pdb TARGET INSTALL_PATH) - if(MSVC) -- install(FILES $ DESTINATION ${INSTALL_PATH} CONFIGURATIONS Debug) -- install(FILES $ DESTINATION ${INSTALL_PATH} CONFIGURATIONS RelWithDebInfo) -+ #install(FILES $ DESTINATION ${INSTALL_PATH} CONFIGURATIONS Debug) -+ #install(FILES $ DESTINATION ${INSTALL_PATH} CONFIGURATIONS RelWithDebInfo) - endif() - endmacro(install_pdb) - diff --git a/resources/conan/recipes/qca/config.yml b/resources/conan/recipes/qca/config.yml deleted file mode 100644 index 1c1d2520..00000000 --- a/resources/conan/recipes/qca/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -versions: - "2.3.10": - folder: all - -updater: - source: anitya - id: 13606 - url-pattern: "https://github.com/KDE/qca/archive/refs/tags/v${version}.zip" \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c04b4f9f..0f9b9590 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,6 @@ find_package(Qt6 REQUIRED COMPONENTS WebEngineQuick Xml) -find_package(Qca-qt6 REQUIRED) find_package(logfault CONFIG REQUIRED) if(UNIX) @@ -741,7 +740,6 @@ target_link_libraries(gonnect Qt6::Core5Compat GOnnectVersion ${PLATFORM_LIBRARIES} - qca-qt6 qt6keychain hid-rp ${AKONADI_CONTACT_FACTORY} diff --git a/src/dbus/portal/SecretPortal.cpp b/src/dbus/portal/SecretPortal.cpp index 7e35333e..fb3bb049 100644 --- a/src/dbus/portal/SecretPortal.cpp +++ b/src/dbus/portal/SecretPortal.cpp @@ -2,32 +2,109 @@ #include #include #include -#include + +#include +#include +#include #include "SecretPortal.h" #include "AppSettings.h" Q_LOGGING_CATEGORY(lcSecretPortal, "gonnect.secrets") -SecretPortal::SecretPortal(QObject *parent) - : AbstractPortal{ FREEDESKTOP_DBUS_PORTAL_SERVICE, FREEDESKTOP_DBUS_PORTAL_PATH, - SECRET_PORTAL_INTERFACE, parent } +static constexpr int AES_BLOCK_SIZE = 16; +static constexpr int AES_256_KEY_LEN = 32; + +struct EvpCtxDeleter { - const QString chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - const auto charsSize = chars.length(); + void operator()(EVP_CIPHER_CTX *ctx) const { EVP_CIPHER_CTX_free(ctx); } +}; +using EvpCtxPtr = std::unique_ptr; - AppSettings settings; - if (!settings.contains("keychain/iv")) { - QString randomString; - for (int i = 0; i < 16; i++) { - auto r = QRandomGenerator::global()->generate(); - randomString.append(chars.at(r % charsSize)); - } +static QByteArray opensslEncrypt(const QByteArray &plainText, const QByteArray &key, + const QByteArray &iv) +{ + if (key.size() != AES_256_KEY_LEN || iv.size() != AES_BLOCK_SIZE) { + return {}; + } - settings.setValue("keychain/iv", randomString); + EvpCtxPtr ctx(EVP_CIPHER_CTX_new()); + if (!ctx) { + return {}; } - m_iva = settings.value("keychain/iv").toByteArray(); + if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, + reinterpret_cast(key.constData()), + reinterpret_cast(iv.constData())) + != 1) { + return {}; + } + + QByteArray cipherText(plainText.size() + AES_BLOCK_SIZE, '\0'); + int outLen1 = 0; + int outLen2 = 0; + + if (EVP_EncryptUpdate(ctx.get(), reinterpret_cast(cipherText.data()), &outLen1, + reinterpret_cast(plainText.constData()), + plainText.size()) + != 1) { + return {}; + } + + if (EVP_EncryptFinal_ex( + ctx.get(), reinterpret_cast(cipherText.data()) + outLen1, &outLen2) + != 1) { + return {}; + } + + cipherText.resize(outLen1 + outLen2); + return cipherText; +} + +static QByteArray opensslDecrypt(const QByteArray &cipherText, const QByteArray &key, + const QByteArray &iv) +{ + if (key.size() != AES_256_KEY_LEN || iv.size() != AES_BLOCK_SIZE || cipherText.isEmpty()) { + return {}; + } + + EvpCtxPtr ctx(EVP_CIPHER_CTX_new()); + if (!ctx) { + return {}; + } + + if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, + reinterpret_cast(key.constData()), + reinterpret_cast(iv.constData())) + != 1) { + return {}; + } + + QByteArray plainText(cipherText.size() + AES_BLOCK_SIZE, '\0'); + int outLen1 = 0; + int outLen2 = 0; + + if (EVP_DecryptUpdate(ctx.get(), reinterpret_cast(plainText.data()), &outLen1, + reinterpret_cast(cipherText.constData()), + cipherText.size()) + != 1) { + return {}; + } + + if (EVP_DecryptFinal_ex(ctx.get(), + reinterpret_cast(plainText.data()) + outLen1, &outLen2) + != 1) { + return {}; + } + + plainText.resize(outLen1 + outLen2); + return plainText; +} + +SecretPortal::SecretPortal(QObject *parent) + : AbstractPortal{ FREEDESKTOP_DBUS_PORTAL_SERVICE, FREEDESKTOP_DBUS_PORTAL_PATH, + SECRET_PORTAL_INTERFACE, parent } +{ } void SecretPortal::initialize() @@ -37,12 +114,13 @@ void SecretPortal::initialize() return; } - QCA::Initializer init; - m_supported = QCA::isSupported("aes256-cbc-pkcs7"); + // Verify that AES-256-CBC is available in the linked OpenSSL build. + // EVP_aes_256_cbc() returns a non-null pointer when supported. + m_supported = (EVP_aes_256_cbc() != nullptr); if (!m_supported) { m_hasTriedInitialization = true; - qCFatal(lcSecretPortal) << "QCA does not support aes256-cbc-pkcs7!"; + qCFatal(lcSecretPortal) << "OpenSSL does not support AES-256-CBC!"; return; } @@ -71,8 +149,6 @@ void SecretPortal::initialize() void SecretPortal::RetrieveSecret(PortalResponse callback) { - QCA::Initializer init; - QDBusMessage message = QDBusMessage::createMethodCall( FREEDESKTOP_DBUS_PORTAL_SERVICE, FREEDESKTOP_DBUS_PORTAL_PATH, SECRET_PORTAL_INTERFACE, "RetrieveSecret"); @@ -83,7 +159,6 @@ void SecretPortal::RetrieveSecret(PortalResponse callback) if (pipe(m_fds) < 0) { callback(1, { { "error", "failed to open secret pipe" } }); - return; } @@ -112,7 +187,6 @@ void SecretPortal::RetrieveSecret(PortalResponse callback) if (count < SECRET_MAX_LEN) { QVariantMap reply = response; reply.insert("secret", QByteArray(secret, count)); - callback(code, reply); } else { callback(2, { { "error", "secret size too large" } }); @@ -131,42 +205,55 @@ QString SecretPortal::encrypt(const QString &plainText) return ""; } - QCA::Initializer init; - QCA::SecureArray arg = plainText.toUtf8(); - QCA::InitializationVector iv(m_iva); + AppSettings settings; + + // For backwards compatibility, fall back to the stored iv value that is + // stored in the configuration. This is not ideal, as iv should be stored + // with the secret instead, but changing it would break with old versions. + // Also note that the secret portal is a fallback for old 1.x versions + // anyway. + if (!settings.contains("keychain/iv")) { + unsigned char ivBuf[AES_BLOCK_SIZE]; + if (RAND_bytes(ivBuf, AES_BLOCK_SIZE) != 1) { + qCFatal(lcSecretPortal) << "RAND_bytes failed while generating IV"; + } - QCA::Cipher cipher(QStringLiteral("aes256"), QCA::Cipher::CBC, QCA::Cipher::DefaultPadding, - QCA::Encode, m_instanceSecret, iv); + settings.setValue( + "keychain/iv", + QString::fromLatin1( + QByteArray(reinterpret_cast(ivBuf), AES_BLOCK_SIZE).toHex())); - QCA::SecureArray u = cipher.update(arg); - if (!cipher.ok()) { - qCCritical(lcSecretPortal) << "cipher update failed"; - return ""; + m_iv = QByteArray::fromHex(settings.value("keychain/iv").toByteArray()); } - QCA::SecureArray f = cipher.final(); - if (!cipher.ok()) { - qCCritical(lcSecretPortal) << "cipher finalization failed"; + QByteArray key = m_instanceSecret.leftJustified(AES_256_KEY_LEN, '\0').left(AES_256_KEY_LEN); + QByteArray iv = m_iv.leftJustified(AES_BLOCK_SIZE, '\0').left(AES_BLOCK_SIZE); + + const QByteArray cipherBytes = opensslEncrypt(plainText.toUtf8(), key, iv); + if (cipherBytes.isEmpty()) { + qCCritical(lcSecretPortal) << "encryption failed"; return ""; } - QCA::SecureArray cipherText = u.append(f); - return QCA::arrayToHex(cipherText.toByteArray()); + return QString::fromLatin1(cipherBytes.toHex()); } QString SecretPortal::decrypt(const QString &cipherText) { - if (!m_supported || !m_initialized) { + if (!m_supported || !m_initialized || m_iv.isEmpty()) { qCCritical(lcSecretPortal) << "secret portal is not available"; return ""; } - QCA::Initializer init; - QCA::SecureArray arg = QCA::hexToArray(cipherText); - QCA::InitializationVector iv(m_iva); + QByteArray key = m_instanceSecret.leftJustified(AES_256_KEY_LEN, '\0').left(AES_256_KEY_LEN); + QByteArray iv = m_iv.leftJustified(AES_BLOCK_SIZE, '\0').left(AES_BLOCK_SIZE); + QByteArray cipherBytes = QByteArray::fromHex(cipherText.toLatin1()); - QCA::Cipher cipher(QStringLiteral("aes256"), QCA::Cipher::CBC, QCA::Cipher::DefaultPadding, - QCA::Decode, m_instanceSecret, iv); + const QByteArray plainBytes = opensslDecrypt(cipherBytes, key, iv); + if (plainBytes.isEmpty()) { + qCCritical(lcSecretPortal) << "decryption failed"; + return ""; + } - return QCA::SecureArray(cipher.process(arg)).data(); + return QString::fromUtf8(plainBytes); } diff --git a/src/dbus/portal/SecretPortal.h b/src/dbus/portal/SecretPortal.h index 3c72475e..a1d7f1c0 100644 --- a/src/dbus/portal/SecretPortal.h +++ b/src/dbus/portal/SecretPortal.h @@ -41,7 +41,7 @@ class SecretPortal : public AbstractPortal void RetrieveSecret(PortalResponse callback); QByteArray m_instanceSecret; - QByteArray m_iva; + QByteArray m_iv; int m_fds[2]; diff --git a/src/pch.h b/src/pch.h index 648367eb..83a8ef5f 100644 --- a/src/pch.h +++ b/src/pch.h @@ -109,7 +109,6 @@ #include #include #include -#include #include #include #include From cac2abea887986ca4bab6a3987d82593fa4afd4a Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:50:23 +0200 Subject: [PATCH 054/122] chore(deps): update actions/cache action to v5.0.5 (#424) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index cf449062..5a6db61a 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -207,7 +207,7 @@ jobs: conan profile detect || true - name: Restore cached Conan packages - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 id: cache-conan-packages with: path: ${{ steps.conan-info.outputs.conan-home }}/p @@ -222,7 +222,7 @@ jobs: conan install . --build=missing -ctools.cmake.cmaketoolchain:generator=Ninja - name: Cached Conan packages - uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 if: always() && steps.cache-conan-packages.outputs.cache-hit != 'true' with: path: ${{ steps.conan-info.outputs.conan-home }}/p From ca091c15686ab5a76523b2c2c12222932d519727 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:44:14 +0200 Subject: [PATCH 055/122] chore(deps): update github/codeql-action digest to 95e58e9 (#426) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ccc42eb8..d620a850 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: category: "/language:${{matrix.language}}" From e7e8cd96bffc42e63dfb488894adb836c5ad4ff5 Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Mon, 20 Apr 2026 10:10:31 +0200 Subject: [PATCH 056/122] feat: plugins for microsoft calendars and contacts (#428) * feat: plugins for microsoft calendars and contacts Both plugins use the msgraph API over OAuth. The existing contacts plugin is improved and extended, the calendar plugin is newly added with a similar approach to the contacts plugin. The OAuth code for the microsoft login is refactored out of the plugins, so that we can use the same login for both plugins. A basic UI is introduced for the OAuth login, that can be re-used for other OAuths, if needed. This is in particular needed to inform the user *why* a browser window is being opened that requests a login from the user (and also to tell the user when an error occurred). --------- Co-authored-by: Cajus Pollmeier --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 6 + src/MSOAuthManager.cpp | 318 ++++++++++++++++++ src/MSOAuthManager.h | 55 +++ src/Main.qml | 27 ++ src/calendar/DateEventFeederManager.h | 2 +- src/calendar/msgraph/CMakeLists.txt | 28 ++ src/calendar/msgraph/MSGraphEventFeeder.cpp | 238 +++++++++++++ src/calendar/msgraph/MSGraphEventFeeder.h | 42 +++ .../msgraph/MSGraphEventFeederFactory.cpp | 34 ++ .../msgraph/MSGraphEventFeederFactory.h | 22 ++ src/contacts/msgraph/CMakeLists.txt | 2 - .../msgraph/MSGraphAddressBookFactory.cpp | 20 +- .../msgraph/MSGraphAddressBookFactory.h | 2 +- .../msgraph/MSGraphAddressBookFeeder.cpp | 136 ++++---- .../msgraph/MSGraphAddressBookFeeder.h | 19 +- src/ui/ViewHelper.cpp | 20 ++ src/ui/ViewHelper.h | 10 + .../components/dialogs/OauthLoginDialog.qml | 115 +++++++ 19 files changed, 1002 insertions(+), 96 deletions(-) create mode 100644 src/MSOAuthManager.cpp create mode 100644 src/MSOAuthManager.h create mode 100644 src/calendar/msgraph/CMakeLists.txt create mode 100644 src/calendar/msgraph/MSGraphEventFeeder.cpp create mode 100644 src/calendar/msgraph/MSGraphEventFeeder.h create mode 100644 src/calendar/msgraph/MSGraphEventFeederFactory.cpp create mode 100644 src/calendar/msgraph/MSGraphEventFeederFactory.h create mode 100644 src/ui/components/dialogs/OauthLoginDialog.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index bdbc2c8d..41b82d39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ option(ENABLE_EDS "Enable EDS plugins" ON) option(ENABLE_LDAP "Enable LDAP plugin" ON) option(ENABLE_DAV "Enable DAV plugin" ON) option(ENABLE_CSV "Enable CSV plugin" ON) -option(ENABLE_MSGRAPH "Enable MS Graph plugin" OFF) +option(ENABLE_MSGRAPH "Enable MS Graph plugins" OFF) set(APP_ID "de.gonicus.gonnect") add_definitions(-DAPP_ID="${APP_ID}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0f9b9590..872a05d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -167,6 +167,7 @@ add_subdirectory(calendar/eds) add_subdirectory(contacts/akonadi) add_subdirectory(calendar/akonadi) add_subdirectory(contacts/msgraph) +add_subdirectory(calendar/msgraph) find_package(Qt6Keychain REQUIRED) @@ -348,6 +349,7 @@ qt_add_qml_module(gonnect ui/components/dialogs/BaseDialog.qml ui/components/dialogs/ConfirmDialog.qml ui/components/dialogs/InfoDialog.qml + ui/components/dialogs/OauthLoginDialog.qml # Dynamic UI ui/components/BaseWidget.qml ui/components/DateEventsWidget.qml @@ -461,6 +463,8 @@ qt_add_qml_module(gonnect StateManager.h UISettings.cpp UISettings.h + MSOAuthManager.cpp + MSOAuthManager.h sip/CallHistory.h sip/CallHistory.cpp @@ -751,6 +755,7 @@ target_link_libraries(gonnect ${EDS_CALENDAR_LIBRARIES} ${LDAP_ADDRESSBOOK_FACTORY} ${MSGRAPH_ADDRESSBOOK_FACTORY} + ${MSGRAPH_CALENDAR_FACTORY} ) qt_import_plugins(gonnect INCLUDE @@ -760,6 +765,7 @@ qt_import_plugins(gonnect INCLUDE ${CSV_ADDRESSBOOK_FACTORY} ${CALDAV_CALENDAR_FACTORY} ${MSGRAPH_ADDRESSBOOK_FACTORY} + ${MSGRAPH_CALENDAR_FACTORY} ${EDS_ADDRESSBOOK_FACTORY} ${EDS_CALENDAR_FACTORY} ${LDAP_ADDRESSBOOK_FACTORY}) diff --git a/src/MSOAuthManager.cpp b/src/MSOAuthManager.cpp new file mode 100644 index 00000000..7bd3c579 --- /dev/null +++ b/src/MSOAuthManager.cpp @@ -0,0 +1,318 @@ +#include "MSOAuthManager.h" +#include "ReadOnlyConfdSettings.h" +#include "Credentials.h" +#include "ViewHelper.h" +#include "ErrorBus.h" +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(msOAuthManager, "gonnect.app.MSOAuthManager") + +MSOAuthManager::MSOAuthManager(QObject *parent) + : QObject(parent), m_group(QStringLiteral("msoauth")) +{ +} + +/// @brief Start OAuth login with Microsoft server, or refresh a previously successful login +/// +/// This function attempts to load a refresh token from a previous login and use it with the +/// Microsoft servers, or (on failure) starts displaying a UI to the user indicating that a login +/// is required and requests user interaction. +/// +/// @param configId The settings group of the plugin that started the login. This is used to +/// identify the response signal from the UI. +/// @param reason A user visible reason for why the user should login to their Microsoft account. +/// @param scopes The scopes to request in the OAuth request if login is needed. +/// +void MSOAuthManager::refreshOrRequestOauthLogin(const QString &configId, const QString &reason, + const QSet &scopes) +{ + if (m_readRefreshTokenIsOngoing) { + qCDebug(msOAuthManager) << "OAuth flow already ongoing. Ignoring new request."; + return; + } + if (m_authCodeFlow) { + // Check if flow is already ongoing - may happen if two plugins request login, + // e.g. on startup. + switch (m_authCodeFlow->status()) { + case QAbstractOAuth::Status::TemporaryCredentialsReceived: + case QAbstractOAuth::Status::RefreshingToken: + qCDebug(msOAuthManager) << "OAuth flow already ongoing. Ignoring new request."; + return; + default: + break; + } + } + m_readRefreshTokenIsOngoing = true; + acquireRefreshToken([this, configId, reason, scopes](const QString &token) { + m_readRefreshTokenIsOngoing = false; + if (!token.isEmpty()) { + initAuthCodeFlow(); + m_authCodeFlow->setRefreshToken(token); + m_authCodeFlow->refreshTokens(); + } else { + qCDebug(msOAuthManager) << "refreshToken not available. Require login."; + requestLoginInUi(configId, reason, scopes); + } + }); +} + +void MSOAuthManager::clearRefreshToken() +{ + Credentials::instance().set( + m_group + "/refreshToken", QString(), + [](QKeychain::Error error, const QString &, const QString &message) { + if (error != QKeychain::NoError) { + ErrorBus::instance().error( + tr("Failed to clear refresh token for Microsoft login: %2") + .arg(message)); + } + }); +} + +void MSOAuthManager::requestLoginInUi(const QString &configId, const QString &reason, + const QSet &scopes) +{ + auto &viewHelper = ViewHelper::instance(); + + // Remove any potentially existing connections + QObject::disconnect(&viewHelper, nullptr, this, nullptr); + + QObject::connect(&viewHelper, &ViewHelper::oauthLoginStartResponded, this, + [configId, scopes, this](const QString &id) { + if (id == configId) { + authorize(scopes); + } + }); + QObject::connect(&viewHelper, &ViewHelper::oauthLoginCloseResponded, this, + [configId, this](const QString &id) { + if (id == configId) { + QObject::disconnect(&ViewHelper::instance(), nullptr, this, nullptr); + } + }); + m_requestConfigId = configId; + viewHelper.requestOauthLogin(m_requestConfigId, reason); +} + +void MSOAuthManager::authStatusChanged(QAbstractOAuth::Status status) +{ + qCDebug(msOAuthManager) << "Microsoft auth status changed:" << static_cast(status); + if (status == QAbstractOAuth::Status::Granted) { + m_replyHandler->close(); + showOauthLoginStatus(tr("Login successful."), false); + Q_EMIT loginSuccessful(); + } +} + +void MSOAuthManager::initAuthCodeFlow() +{ + if (m_authCodeFlow) { + return; + } + + ReadOnlyConfdSettings settings; + settings.beginGroup(m_group); + // The identifier of the "GOnnect" app in the microsoft backend. + // This should be a Gonicus registered app (see the Microsoft Entra admin center), with the + // redirect urls (see below) and the supported scopes configured. + // (A different identifier, registered by someone else, could also be used, if desired) + const QString clientIdentifier = settings.value("clientIdentifier").toString(); + const QString authorizationUrl = + settings.value("authorizationUrl", + QStringLiteral("https://login.microsoftonline.com/common/oauth2/v2.0/" + "authorize")) + .toString(); + const QString tokenUrl = + settings.value("authorizationUrl", + QStringLiteral( + "https://login.microsoftonline.com/common/oauth2/v2.0/token")) + .toString(); + + // For each port a redirect url must be registered in the microsoft portal for this app. + // http://localhost:33221/Gonnect, ... + const std::array availablePorts = { 33221, 34221, 33521 }; + + if (!m_replyHandler) { + // NOTE: Use QHostAddress::Any here. + // Qt transforms "QHostAddress::Any" (and AnyIPv4 and AnyIPv6) to "localhost", + // which is the only valid string atm. + // NOTE: Address mutch match the address in the microsoft portal exactly. + m_replyHandler = + new QOAuthHttpServerReplyHandler(QHostAddress::Any, availablePorts[0], this); + //: This is text is displayed in the web browser after the user successfully logged in to the microsoft account. + m_replyHandler->setCallbackText( + tr("Login to the Microsoft account has been received by GOnnect. GOnnect will " + "continue the authorization process now. You can close this page now.")); + m_replyHandler->setCallbackPath(QStringLiteral("/Gonnect")); + } + + if (!m_replyHandler->isListening()) { + // try and hope that one of the available ports is available for listening. + // pick the first available port. + for (auto port : availablePorts) { + if (m_replyHandler->listen(QHostAddress::Any, port)) { + break; + } + } + + if (!m_replyHandler->isListening()) { + qCCritical(msOAuthManager) + << "Failed to start QOAuthHttpServerReplyHandler. No port available?"; + showOauthLoginStatus( + tr("Failed to start login, the local system could not be set up to receive a " + "response. Try again later.\nIf the problem persists, try to close running " + "applications that reserve/block network ports."), + true); + return; + } + } + + m_authCodeFlow = new QOAuth2AuthorizationCodeFlow(this); + m_authCodeFlow->setReplyHandler(m_replyHandler); + m_authCodeFlow->setAuthorizationUrl(authorizationUrl); + m_authCodeFlow->setTokenUrl(tokenUrl); + m_authCodeFlow->setClientIdentifier(clientIdentifier); + m_authCodeFlow->setAutoRefresh(true); + + connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::statusChanged, this, + &MSOAuthManager::authStatusChanged); + connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, + &QDesktopServices::openUrl); + connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::serverReportedErrorOccurred, this, + [&](const QString &error, const QString &errorDescription, const QUrl &uri) { + qCInfo(msOAuthManager) << "Microsoft auth serverReportedErrorOccurred:" << error + << "description:" << errorDescription << "uri:" << uri; + showOauthLoginStatus(tr("Login failed, the server reported an error:\n%1\nWith " + "error description: %2") + .arg(error, errorDescription), + true); + }); + connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::requestFailed, this, + [&](const QAbstractOAuth::Error error) { + qCInfo(msOAuthManager) + << "Microsoft auth requestFailed:" << static_cast(error); + switch (error) { + case QAbstractOAuth::Error::NoError: + break; + case QAbstractOAuth::Error::NetworkError: + showOauthLoginStatus(tr("Login failed due to a network error. Check your " + "internet connectivity and try again."), + true); + break; + case QAbstractOAuth::Error::ServerError: + showOauthLoginStatus( + tr("Login failed because the Microsoft server returned an unexpected " + "or incorrect response. Please try again later."), + true); + break; + case QAbstractOAuth::Error::OAuthTokenNotFoundError: // fall-through intended + case QAbstractOAuth::Error::OAuthTokenSecretNotFoundError: + showOauthLoginStatus(tr("Login failed, no token has been received."), true); + break; + case QAbstractOAuth::Error::OAuthCallbackNotVerified: + showOauthLoginStatus( + tr("Login failed, possibly due to a server configuration error."), + true); + break; + case QAbstractOAuth::Error::ClientError: + showOauthLoginStatus(tr("Login failed, possibly due to a GOnnect configuration " + "error."), + true); + break; + case QAbstractOAuth::Error::ExpiredError: + showOauthLoginStatus(tr("Login failed, token expired. Please try again."), + true); + break; + default: + showOauthLoginStatus(tr("Login failed, unknown error. Please try again."), + true); + break; + } + }); + connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::refreshTokenChanged, this, + [this](const QString &refreshToken) { + if (!refreshToken.isEmpty()) { + storeRefreshToken(refreshToken); + } + }); + + settings.endGroup(); +} + +void MSOAuthManager::authorize(const QSet &scopes) +{ + initAuthCodeFlow(); + if (!m_authCodeFlow) { + return; + } + QString scopesString; + for (const auto &s : scopes) { + if (!scopesString.isEmpty()) { + scopesString += ","; + } + scopesString += s; + } + qCDebug(msOAuthManager) << "Starting microsoft authorization for scopes " << scopesString; + m_authCodeFlow->setRequestedScopeTokens(scopes); + m_authCodeFlow->grant(); +} + +bool MSOAuthManager::isGranted() const +{ + if (!m_authCodeFlow) { + return false; + } + return (m_authCodeFlow->status() == QAbstractOAuth::Status::Granted); +} + +QString MSOAuthManager::token() const +{ + if (!m_authCodeFlow) { + return QString(); + } + return m_authCodeFlow->token(); +} + +void MSOAuthManager::acquireRefreshToken(std::function callback) +{ + Credentials::instance().get( + m_group + "/refreshToken", + [this, callback](QKeychain::Error error, const QString &secret, const QString &) { + if (error == QKeychain::NoError) { + if (!secret.isEmpty()) { + qCDebug(msOAuthManager) << "refreshToken available for" << m_group; + } else { + qCDebug(msOAuthManager) << "refreshToken not available for" << m_group; + } + } else { + if (error == QKeychain::EntryNotFound) { + qCDebug(msOAuthManager) << "No refreshToken found for" << m_group; + } else { + qCDebug(msOAuthManager) + << "Error reading refreshToken for" << m_group << ":" << error; + } + } + callback(secret); + }); +} + +void MSOAuthManager::storeRefreshToken(const QString &token) const +{ + Credentials::instance().set( + m_group + "/refreshToken", token, + [](QKeychain::Error error, const QString &, const QString &message) { + if (error != QKeychain::NoError) { + ErrorBus::instance().error( + tr("Failed to persist refresh token for Microsoft login: %2") + .arg(message)); + } + }); +} + +void MSOAuthManager::showOauthLoginStatus(const QString &status, bool canRetry) +{ + auto &viewHelper = ViewHelper::instance(); + viewHelper.showOauthLoginStatus(m_requestConfigId, status, canRetry); +} diff --git a/src/MSOAuthManager.h b/src/MSOAuthManager.h new file mode 100644 index 00000000..fca03171 --- /dev/null +++ b/src/MSOAuthManager.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +class QOAuth2AuthorizationCodeFlow; +class QOAuthHttpServerReplyHandler; + +/// Helper class to perform an OAuth2 login for the user to the user's Microsoft account. +/// The login can then be used to use Microsoft APIs such as Microsoft Graph to access contacts +/// and calendars. +/// +/// See also https://learn.microsoft.com/en-us/entra/identity-platform/ and +/// https://learn.microsoft.com/en-us/graph/auth/ +class MSOAuthManager : public QObject +{ + Q_OBJECT + +public: + static MSOAuthManager &instance() + { + static MSOAuthManager *_instance = nullptr; + if (!_instance) { + _instance = new MSOAuthManager; + } + return *_instance; + } + + void refreshOrRequestOauthLogin(const QString &configId, const QString &reason, + const QSet &scopes); + void clearRefreshToken(); + + bool isGranted() const; + QString token() const; + +Q_SIGNALS: + void loginSuccessful(); + +private: + explicit MSOAuthManager(QObject *parent = nullptr); + void acquireRefreshToken(std::function callback); + void initAuthCodeFlow(); + void requestLoginInUi(const QString &configId, const QString &reason, + const QSet &scopes); + void authStatusChanged(QAbstractOAuth::Status status); + void showOauthLoginStatus(const QString &status, bool canRetry); + void storeRefreshToken(const QString &token) const; + void authorize(const QSet &scopes); + + const QString m_group; + QOAuthHttpServerReplyHandler *m_replyHandler = nullptr; + QOAuth2AuthorizationCodeFlow *m_authCodeFlow = nullptr; + QString m_requestConfigId; + bool m_readRefreshTokenIsOngoing = false; +}; diff --git a/src/Main.qml b/src/Main.qml index 5f73deb6..06055dcd 100644 --- a/src/Main.qml +++ b/src/Main.qml @@ -47,6 +47,7 @@ Item { property EmergencyCallIncomingWindow emergencyWindow: null property var passwordDialogs: ({}) + property var oauthLoginDialogs: ({}) function onActivateSearch() { gonnectWindow.ensureVisible() @@ -95,6 +96,32 @@ Item { dialog.Component.destruction.connect(() => delete viewHelperConnections.passwordDialogs[id]) } else { existingDialogs[id].show() + existingDialogs[id].raise() + } + } + + function onOauthLoginRequested(id : string, reason : string) { + const existingDialogs = viewHelperConnections.oauthLoginDialogs + + if (!existingDialogs[id]) { + const dialog = DialogFactory.createDialog("OauthLoginDialog.qml", { text: reason }) + dialog.onStartOauthLogin.connect(() => ViewHelper.respondStartOauthLogin(id)) + dialog.onCloseDialog.connect(() => ViewHelper.respondOauthLoginClosed(id)) + + viewHelperConnections.oauthLoginDialogs[id] = dialog + dialog.Component.destruction.connect(() => delete viewHelperConnections.oauthLoginDialogs[id]) + } else { + existingDialogs[id].show() + existingDialogs[id].raise() + } + } + + function onOauthLoginStatus(id : string, status : string, canRetry : bool) { + const dialog = viewHelperConnections.oauthLoginDialogs[id] + if (dialog) { + dialog.setStatus(status, canRetry) + dialog.show() + dialog.raise() } } diff --git a/src/calendar/DateEventFeederManager.h b/src/calendar/DateEventFeederManager.h index e4f127da..27546094 100644 --- a/src/calendar/DateEventFeederManager.h +++ b/src/calendar/DateEventFeederManager.h @@ -25,7 +25,7 @@ class DateEventFeederManager : public QObject void initFeederConfigs(); void reload(); - void acquireSecret(bool override, const QString &configId, + void acquireSecret(bool forcePrompt, const QString &configId, std::function callback); private: diff --git a/src/calendar/msgraph/CMakeLists.txt b/src/calendar/msgraph/CMakeLists.txt new file mode 100644 index 00000000..c06138bf --- /dev/null +++ b/src/calendar/msgraph/CMakeLists.txt @@ -0,0 +1,28 @@ +if(ENABLE_MSGRAPH) + qt_add_plugin(MSGraphEventFeederFactory STATIC + CLASS_NAME MSGraphEventFeederFactory + + MSGraphEventFeederFactory.cpp + MSGraphEventFeederFactory.h + MSGraphEventFeeder.cpp + MSGraphEventFeeder.h + ) + + target_include_directories(MSGraphEventFeederFactory + PRIVATE + ${PROJECT_SOURCE_DIR}/src/calendar + ${PROJECT_SOURCE_DIR}/src/ui + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}/src + ) + + target_link_libraries(MSGraphEventFeederFactory PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Quick + Qt6::Xml + Qt6::NetworkAuth) + + set(MSGRAPH_CALENDAR_FACTORY MSGraphEventFeederFactory PARENT_SCOPE) +endif() diff --git a/src/calendar/msgraph/MSGraphEventFeeder.cpp b/src/calendar/msgraph/MSGraphEventFeeder.cpp new file mode 100644 index 00000000..48e8eb1c --- /dev/null +++ b/src/calendar/msgraph/MSGraphEventFeeder.cpp @@ -0,0 +1,238 @@ +#include "MSGraphEventFeeder.h" +#include "DateEventManager.h" +#include "DateEventFeederManager.h" +#include "ReadOnlyConfdSettings.h" +#include "MSOAuthManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(lcMSGraphEventFeeder, "gonnect.app.dateevents.feeder.MSGraphEventFeeder") + +MSGraphEventFeeder::MSGraphEventFeeder(const QString &group, const QDateTime &timeRangeStart, + const QDateTime &timeRangeEnd, + DateEventFeederManager *parent) + : QObject(parent), + m_group(group), + m_manager(parent), + m_timeRangeStart(timeRangeStart), + m_timeRangeEnd(timeRangeEnd) +{ + m_networkAccessManager = new QNetworkAccessManager(this); + m_calendarRefreshTimer.setSingleShot(true); + + connect(&MSOAuthManager::instance(), &MSOAuthManager::loginSuccessful, this, + &MSGraphEventFeeder::requestEvents); + connect(&m_calendarRefreshTimer, &QTimer::timeout, this, &MSGraphEventFeeder::requestEvents); +} + +MSGraphEventFeeder::~MSGraphEventFeeder() { } + +QUrl MSGraphEventFeeder::networkCheckURL() const +{ + return {}; +} + +void MSGraphEventFeeder::init() +{ + if (MSOAuthManager::instance().isGranted()) { + requestEvents(); + } else { + refreshOrRequestLogin(); + } +} + +void MSGraphEventFeeder::refreshOrRequestLogin() +{ + ReadOnlyConfdSettings settings; + QSet scopes = { + { "offline_access" }, +#if QT_VERSION < QT_VERSION_CHECK(6, 11, 1) + { "openid" }, // See QTBUG-145561. Required for Qt <= 6.11 only +#endif + { "Calendars.Read" }, + { "Calendars.Read.Shared" }, + }; + + // If contacts plugin is enabled as well, request calendar permissions as well at + // the same time + const auto contactsGroup = QStringLiteral("msgraphcontacts"); + if (settings.childGroups().contains(contactsGroup) + && settings.value(contactsGroup + QStringLiteral("/enabled"), true).toBool()) { + scopes.insert({ "Contacts.Read" }); + scopes.insert({ "Contacts.Read.Shared" }); + } + + MSOAuthManager::instance().refreshOrRequestOauthLogin( + m_group, + tr("Login to your Microsoft account is required to access your contacts or calendars " + "from GOnnect."), + scopes); +} + +void MSGraphEventFeeder::eventsReceived(QNetworkReply *reply) +{ + if (!reply) { + return; + } + reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + return; + } + + auto jsonText = reply->readAll(); + auto doc = QJsonDocument::fromJson(jsonText); + if (doc.isNull()) { + return; + } + + const QString eventSource = m_group; + + DateEventManager &manager = DateEventManager::instance(); + if (m_isFirstPage) { + manager.removeDateEventsBySource(eventSource); + } + + auto value = doc.object()["value"]; + auto events = value.toArray(); + for (const auto &event : events) { + const auto obj = event.toObject(); + if (!obj.contains("id") || !obj.contains("start") || !obj.contains("end") + || !obj.contains("subject")) { + qCWarning(lcMSGraphEventFeeder) << "Invalid event format encountered"; + continue; + } + if (obj.contains("isCancelled") && obj["isCancelled"].toBool()) { + continue; + } + const auto eventId = obj["id"].toString(); + const QDateTime start = parseDateTime(obj["start"]); + const QDateTime end = parseDateTime(obj["end"]); + if (!start.isValid() || !end.isValid()) { + qCCritical(lcMSGraphEventFeeder) + << "Error parsing start or end datetime for event" << eventId + << "start:" << obj["start"].toString() << "end:" << obj["end"].toString(); + continue; + } + const auto subject = obj["subject"].toString(); + QString location; + if (obj.contains("location") && obj["location"].isObject()) { + location = obj["location"].toObject()["displayName"].toString(); + } + // Alternatively,we could use obj["body"].toObject()["content"].toString(), however this + // is likely in HTML (see obj["body"].toObject()["contentType"].toString()) + const auto bodyPreview = obj["bodyPreview"].toString(); + + manager.addDateEvent(eventId, eventSource, start, end, subject, location, bodyPreview); + } + + m_isFirstPage = false; + if (doc.object().contains("@odata.nextLink")) { + QString nextLink = doc.object()["@odata.nextLink"].toString(); + + using namespace Qt::StringLiterals; + QNetworkRequest request(nextLink); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Authorization, + u"Bearer "_s + MSOAuthManager::instance().token()); + request.setHeaders(headers); + auto *reply = m_networkAccessManager->get(request); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { eventsReceived(reply); }); + connect(reply, &QNetworkReply::errorOccurred, this, + [this, reply](QNetworkReply::NetworkError code) { errorOccurred(reply, code); }); + } else { + ReadOnlyConfdSettings settings; + m_calendarRefreshTimer.setInterval(settings.value("interval", 900000).toInt()); + m_calendarRefreshTimer.start(); + } +} + +void MSGraphEventFeeder::requestEvents() +{ + m_calendarRefreshTimer.stop(); + if (!MSOAuthManager::instance().isGranted()) { + qCWarning(lcMSGraphEventFeeder) << "Cannot request events - not logged in"; + return; + } + if (!m_timeRangeStart.isValid() || !m_timeRangeEnd.isValid()) { + qCWarning(lcMSGraphEventFeeder) << "Cannot request events - timerange not valid"; + return; + } + + qCDebug(lcMSGraphEventFeeder) << "Requesting events with microsoft graph api"; + + QNetworkRequestFactory requestFactory({ "https://graph.microsoft.com/v1.0" }); + requestFactory.setBearerToken(MSOAuthManager::instance().token().toLatin1()); + + // NOTE: timerange is required for calendarview requests. Must be in ISO-8601 format + // ("2026-04-21T14:21:51.565Z") + auto request = requestFactory.createRequest(QStringLiteral("me/calendarview?startdatetime=") + + m_timeRangeStart.toString(Qt::ISODate) + + QStringLiteral("&enddatetime=") + + m_timeRangeEnd.toString(Qt::ISODate)); + auto *reply = m_networkAccessManager->get(request); + if (!reply) { + qCCritical(lcMSGraphEventFeeder) << "Failed to create contacts request"; + return; + } + + m_isFirstPage = true; + connect(reply, &QNetworkReply::finished, this, [this, reply]() { eventsReceived(reply); }); + connect(reply, &QNetworkReply::errorOccurred, this, + [this, reply](QNetworkReply::NetworkError code) { errorOccurred(reply, code); }); +} + +void MSGraphEventFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code) +{ + if (!reply) { + return; + } + m_calendarRefreshTimer.stop(); + switch (code) { + case QNetworkReply::NoError: + // Should never happen here + break; + // See msgraph API documentation: HTTP status code 401 (Unauthorized) and 403 (Forbidden) + // may indicate issues with the login. + case QNetworkReply::AuthenticationRequiredError: // Corresponds to HTTP 401 + case QNetworkReply::ContentAccessDenied: // Corresponds to HTTP 403 + qCCritical(lcMSGraphEventFeeder) + << "Network error for msgraph request:" << code << "require new login to account."; + MSOAuthManager::instance() + .clearRefreshToken(); // Make sure we don't try to refresh the token again + refreshOrRequestLogin(); + break; + default: + qCCritical(lcMSGraphEventFeeder) << "Network error for msgraph request:" << code; + break; + } + reply->deleteLater(); +} + +QDateTime MSGraphEventFeeder::parseDateTime(const QJsonValue &dateTimeContainer) +{ + if (!dateTimeContainer.isObject()) { + return QDateTime(); + } + const auto obj = dateTimeContainer.toObject(); + // Container have a "dateTime" and a "timeZone" key. + // We only need dateTime: timeZone is UTC in responses, if nothing else was requested. + if (!obj.contains("dateTime")) { + return QDateTime(); + } + auto s = obj["dateTime"].toString(); + // Epected format: "2026-03-20T17:00:00.0000000" + // we only need up to 3 digits after the decimal point (=ms precision) and we need a 'Z' + // afterwards, for Qt to recognize UTC. + if (s.length() < 23) { + return QDateTime(); + } + s = s.left(23) + QStringLiteral("Z"); + return QDateTime::fromString(s, QStringLiteral("yyyy-MM-ddTHH:mm:ss.zzzt")); +} diff --git a/src/calendar/msgraph/MSGraphEventFeeder.h b/src/calendar/msgraph/MSGraphEventFeeder.h new file mode 100644 index 00000000..7889d3b5 --- /dev/null +++ b/src/calendar/msgraph/MSGraphEventFeeder.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +#include "IDateEventFeeder.h" + +class QJsonValue; +class DateEventFeederManager; + +class MSGraphEventFeeder : public QObject, public IDateEventFeeder +{ + Q_OBJECT + +public: + explicit MSGraphEventFeeder(const QString &group, const QDateTime &timeRangeStart, + const QDateTime &timeRangeEnd, + DateEventFeederManager *parent = nullptr); + ~MSGraphEventFeeder(); + + void init() override; + QUrl networkCheckURL() const override; + +private Q_SLOTS: + void requestEvents(); + +private: + void refreshOrRequestLogin(); + void eventsReceived(QNetworkReply *reply); + void errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code); + static QDateTime parseDateTime(const QJsonValue &dateTimeContainer); + + const QString m_group; + const QDateTime m_timeRangeStart; + const QDateTime m_timeRangeEnd; + DateEventFeederManager *m_manager = nullptr; + QNetworkAccessManager *m_networkAccessManager = nullptr; + QTimer m_calendarRefreshTimer; + bool m_isFirstPage = true; +}; diff --git a/src/calendar/msgraph/MSGraphEventFeederFactory.cpp b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp new file mode 100644 index 00000000..b325aa57 --- /dev/null +++ b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp @@ -0,0 +1,34 @@ +#include "MSGraphEventFeederFactory.h" +#include "ReadOnlyConfdSettings.h" +#include "MSGraphEventFeeder.h" +#include "DateEventFeederManager.h" + +MSGraphEventFeederFactory::MSGraphEventFeederFactory(QObject *parent) : QObject{ parent } { } + +QStringList MSGraphEventFeederFactory::configurations() const +{ + QStringList res; + + ReadOnlyConfdSettings settings; + const auto msOAuthGroup = QStringLiteral("msoauth"); + const auto group = QStringLiteral("msgraphcalendar"); + if (settings.childGroups().contains(msOAuthGroup) && settings.childGroups().contains(group)) { + const auto &clientIdentifier = + settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier")).toString(); + const bool enabled = settings.value(group + QStringLiteral("enabled"), true).toBool(); + + if (enabled && !clientIdentifier.isEmpty()) { + res.push_back(group); + } + } + + return res; +} + +IDateEventFeeder *MSGraphEventFeederFactory::createFeeder( + const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, + const QDateTime &timeRangeEnd, DateEventFeederManager *feederManager) const +{ + Q_UNUSED(currentTime); + return new MSGraphEventFeeder(settingsGroup, timeRangeStart, timeRangeEnd, feederManager); +} diff --git a/src/calendar/msgraph/MSGraphEventFeederFactory.h b/src/calendar/msgraph/MSGraphEventFeederFactory.h new file mode 100644 index 00000000..fb4c553a --- /dev/null +++ b/src/calendar/msgraph/MSGraphEventFeederFactory.h @@ -0,0 +1,22 @@ +#pragma once + +#include "IDateEventFeederFactory.h" + +#include + +class MSGraphEventFeederFactory : public QObject, IDateEventFeederFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "MSGraphEventFeederFactory" URI + "de.gonicus.gonnect.MSGraphEventFeederFactory") + Q_INTERFACES(IDateEventFeederFactory) + +public: + MSGraphEventFeederFactory(QObject *parent = nullptr); + + QString name() const override { return "MSGraphCalendar"; }; + QStringList configurations() const override; + IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, + const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + DateEventFeederManager *feederManager) const override; +}; diff --git a/src/contacts/msgraph/CMakeLists.txt b/src/contacts/msgraph/CMakeLists.txt index 744c2cc2..41f1eb09 100644 --- a/src/contacts/msgraph/CMakeLists.txt +++ b/src/contacts/msgraph/CMakeLists.txt @@ -11,9 +11,7 @@ if(ENABLE_MSGRAPH) target_include_directories(MSGraphAddressBookFactory PRIVATE ${PROJECT_SOURCE_DIR}/src/contacts - ${PROJECT_SOURCE_DIR}/src/dbus/portal ${PROJECT_SOURCE_DIR}/src/ui - ${PROJECT_SOURCE_DIR}/src/sip ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}/src diff --git a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp index 00a302f9..70bfcf88 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp +++ b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp @@ -6,20 +6,16 @@ QStringList MSGraphAddressBookFactory::configurations() const { QStringList res; - static QRegularExpression groupRegex = QRegularExpression("^msgraph[0-9]+$"); - ReadOnlyConfdSettings settings; - const QStringList groups = settings.childGroups(); - - for (const auto &group : groups) { - if (groupRegex.match(group).hasMatch()) { - settings.beginGroup(group); - const bool enabled = settings.value("enabled", true).toBool(); - settings.endGroup(); + const auto msOAuthGroup = QStringLiteral("msoauth"); + const auto group = QStringLiteral("msgraphcontacts"); + if (settings.childGroups().contains(msOAuthGroup) && settings.childGroups().contains(group)) { + const auto &clientIdentifier = + settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier")).toString(); + const bool enabled = settings.value(group + QStringLiteral("enabled"), true).toBool(); - if (enabled) { - res.push_back(group); - } + if (enabled && !clientIdentifier.isEmpty()) { + res.push_back(group); } } diff --git a/src/contacts/msgraph/MSGraphAddressBookFactory.h b/src/contacts/msgraph/MSGraphAddressBookFactory.h index dd4097fd..882685a1 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFactory.h +++ b/src/contacts/msgraph/MSGraphAddressBookFactory.h @@ -14,7 +14,7 @@ class MSGraphAddressBookFactory : public QObject, IAddressBookFactory public: MSGraphAddressBookFactory() { }; - QString name() const override { return "MSGraph"; } + QString name() const override { return "MSGraphContacts"; } QStringList configurations() const override; diff --git a/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp b/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp index e6ef887f..9a73fe67 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp +++ b/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp @@ -1,15 +1,13 @@ #include "MSGraphAddressBookFeeder.h" #include "AddressBook.h" #include "AddressBookManager.h" -#include "AvatarManager.h" #include "ReadOnlyConfdSettings.h" -#include -#include -#include +#include "MSOAuthManager.h" #include #include #include #include +#include #include #include #include @@ -20,6 +18,9 @@ MSGraphAddressBookFeeder::MSGraphAddressBookFeeder(const QString &group, Address : QObject(parent), m_manager(parent), m_group(group) { m_networkAccessManager = new QNetworkAccessManager(this); + + connect(&MSOAuthManager::instance(), &MSOAuthManager::loginSuccessful, this, + &MSGraphAddressBookFeeder::requestContacts); } QUrl MSGraphAddressBookFeeder::networkCheckURL() const @@ -27,16 +28,12 @@ QUrl MSGraphAddressBookFeeder::networkCheckURL() const return {}; } -void MSGraphAddressBookFeeder::authStatusChanged(QAbstractOAuth::Status status) -{ - if (status == QAbstractOAuth::Status::Granted) { - m_replyHandler->close(); - QTimer::singleShot(std::chrono::seconds(0), this, [this]() { requestContacts(); }); - } -} - void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) { + if (!reply) { + return; + } + reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { return; } @@ -50,7 +47,7 @@ void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) auto value = doc.object()["value"]; auto contacts = value.toArray(); auto &addressBook = AddressBook::instance(); - for (auto contact : contacts) { + for (const auto &contact : contacts) { const auto obj = contact.toObject(); const auto contactId = obj["id"].toString(); @@ -117,17 +114,45 @@ void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) QNetworkRequest request(nextLink); QHttpHeaders headers; headers.append(QHttpHeaders::WellKnownHeader::Authorization, - u"Bearer "_s + m_authCodeFlow->token()); + u"Bearer "_s + MSOAuthManager::instance().token()); request.setHeaders(headers); auto *reply = m_networkAccessManager->get(request); connect(reply, &QNetworkReply::finished, reply, [this, reply]() { contactsReceived(reply); }); + connect(reply, &QNetworkReply::errorOccurred, this, + [this, reply](QNetworkReply::NetworkError code) { errorOccurred(reply, code); }); } } +void MSGraphAddressBookFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code) +{ + if (!reply) { + return; + } + switch (code) { + case QNetworkReply::NoError: + // Should never happen here + break; + // See msgraph API documentation: HTTP status code 401 (Unauthorized) and 403 (Forbidden) + // may indicate issues with the login. + case QNetworkReply::AuthenticationRequiredError: // Corresponds to HTTP 401 + case QNetworkReply::ContentAccessDenied: // Corresponds to HTTP 403 + qCCritical(msGraphAddressBookFeeder) + << "Network error for msgraph request:" << code << "require new login to account."; + MSOAuthManager::instance() + .clearRefreshToken(); // Make sure we don't try to refresh the token again + refreshOrRequestLogin(); + break; + default: + qCCritical(msGraphAddressBookFeeder) << "Network error for msgraph request:" << code; + break; + } + reply->deleteLater(); +} + void MSGraphAddressBookFeeder::requestContacts() { - if (!m_authCodeFlow || m_authCodeFlow->status() != QAbstractOAuth::Status::Granted) { + if (!MSOAuthManager::instance().isGranted()) { qCWarning(msGraphAddressBookFeeder) << "Cannot request contacts - not logged in"; return; } @@ -135,7 +160,7 @@ void MSGraphAddressBookFeeder::requestContacts() qCDebug(msGraphAddressBookFeeder) << "Requesting contacts with microsoft graph api"; QNetworkRequestFactory requestFactory({ "https://graph.microsoft.com/v1.0" }); - requestFactory.setBearerToken(m_authCodeFlow->token().toLatin1()); + requestFactory.setBearerToken(MSOAuthManager::instance().token().toLatin1()); auto request = requestFactory.createRequest("me/contacts"); auto *reply = m_networkAccessManager->get(request); @@ -145,60 +170,43 @@ void MSGraphAddressBookFeeder::requestContacts() } connect(reply, &QNetworkReply::finished, reply, [this, reply]() { contactsReceived(reply); }); + connect(reply, &QNetworkReply::errorOccurred, this, + [this, reply](QNetworkReply::NetworkError code) { errorOccurred(reply, code); }); } -void MSGraphAddressBookFeeder::authorize() +void MSGraphAddressBookFeeder::process() { - qCDebug(msGraphAddressBookFeeder) << "Starting microsoft authorization"; - if (!m_replyHandler) { - m_replyHandler = new QOAuthHttpServerReplyHandler(this); - } - - if (!m_replyHandler->isListening()) { - // For each port a redirect url must be registered in the azure portal for this app. - // http://127.0.0.1:33221 ... - std::array availablePorts = { 33221, 34221, 33521 }; - - // try and hope that one of the available ports is available for listening - for (auto port : availablePorts) { - if (m_replyHandler->listen(QHostAddress::Any, port)) { - break; - } - } - - if (!m_replyHandler->isListening()) { - qCCritical(msGraphAddressBookFeeder) - << "Failed to start QOAuthHttpServerReplyHandler. No port available?"; - return; - } - } - - if (!m_authCodeFlow) { - const QSet tokens = { { "Contacts.Read" } }; - m_authCodeFlow = new QOAuth2AuthorizationCodeFlow(this); - m_authCodeFlow->setReplyHandler(m_replyHandler); - m_authCodeFlow->setAuthorizationUrl( - QStringLiteral("https://login.microsoftonline.com/common/oauth2/v2.0/authorize")); - m_authCodeFlow->setTokenUrl( - { "https://login.microsoftonline.com/common/oauth2/v2.0/token" }); - m_authCodeFlow->setRequestedScopeTokens(tokens); - m_authCodeFlow->setClientIdentifier(QStringLiteral("040bd189-3f48-414d-acf8-076bf1983326")); - - connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::statusChanged, this, - &MSGraphAddressBookFeeder::authStatusChanged); - - connect(m_authCodeFlow, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, - &QDesktopServices::openUrl); + if (MSOAuthManager::instance().isGranted()) { + requestContacts(); + } else { + refreshOrRequestLogin(); } - - m_authCodeFlow->grant(); } -void MSGraphAddressBookFeeder::process() +void MSGraphAddressBookFeeder::refreshOrRequestLogin() { - if (m_authCodeFlow && m_authCodeFlow->status() == QAbstractOAuth::Status::Granted) { - requestContacts(); - } else { - authorize(); + ReadOnlyConfdSettings settings; + QSet scopes = { + { "offline_access" }, +#if QT_VERSION < QT_VERSION_CHECK(6, 11, 1) + { "openid" }, // See QTBUG-145561. Required for Qt <= 6.11 only +#endif + { "Contacts.Read" }, + { "Contacts.Read.Shared" }, + }; + + // If calendar plugin is enabled as well, request calendar permissions as well at + // the same time + const auto calendarGroup = QStringLiteral("msgraphcalendar"); + if (settings.childGroups().contains(calendarGroup) + && settings.value(calendarGroup + QStringLiteral("/enabled"), true).toBool()) { + scopes.insert({ "Calendars.Read" }); + scopes.insert({ "Calendars.Read.Shared" }); } + + MSOAuthManager::instance().refreshOrRequestOauthLogin( + m_group, + tr("Login to your Microsoft account is required to access your contacts or calendars " + "from GOnnect."), + scopes); } diff --git a/src/contacts/msgraph/MSGraphAddressBookFeeder.h b/src/contacts/msgraph/MSGraphAddressBookFeeder.h index fb648472..472261db 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFeeder.h +++ b/src/contacts/msgraph/MSGraphAddressBookFeeder.h @@ -1,14 +1,12 @@ #pragma once #include +#include #include -#include -#include #include "Contact.h" class AddressBook; class AddressBookManager; -class QOAuthHttpServerReplyHandler; class MSGraphAddressBookFeeder : public QObject, public IAddressBookFeeder { @@ -21,21 +19,12 @@ class MSGraphAddressBookFeeder : public QObject, public IAddressBookFeeder QUrl networkCheckURL() const override; private: - void authStatusChanged(QAbstractOAuth::Status status); - + void refreshOrRequestLogin(); void contactsReceived(QNetworkReply *reply); - + void errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code); void requestContacts(); - void authorize(); - + const QString m_group; AddressBookManager *m_manager = nullptr; - - QOAuthHttpServerReplyHandler *m_replyHandler = nullptr; - - QString m_group; - - QOAuth2AuthorizationCodeFlow *m_authCodeFlow = nullptr; - QNetworkAccessManager *m_networkAccessManager = nullptr; }; diff --git a/src/ui/ViewHelper.cpp b/src/ui/ViewHelper.cpp index 3c65b419..a36c7577 100644 --- a/src/ui/ViewHelper.cpp +++ b/src/ui/ViewHelper.cpp @@ -359,6 +359,26 @@ void ViewHelper::respondPassword(const QString &id, const QString password) Q_EMIT passwordResponded(id, password); } +void ViewHelper::requestOauthLogin(const QString &id, const QString &reason) +{ + Q_EMIT oauthLoginRequested(id, reason); +} + +void ViewHelper::respondStartOauthLogin(const QString &id) +{ + Q_EMIT oauthLoginStartResponded(id); +} + +void ViewHelper::showOauthLoginStatus(const QString &id, const QString &status, bool canRetry) +{ + Q_EMIT oauthLoginStatus(id, status, canRetry); +} + +void ViewHelper::respondOauthLoginClosed(const QString &id) +{ + Q_EMIT oauthLoginCloseResponded(id); +} + void ViewHelper::respondRecoveryKey(const QString &id, const QString &key) { Q_EMIT recoveryKeyResponded(id, key); diff --git a/src/ui/ViewHelper.h b/src/ui/ViewHelper.h index a0772615..e5410642 100644 --- a/src/ui/ViewHelper.h +++ b/src/ui/ViewHelper.h @@ -99,6 +99,11 @@ class ViewHelper : public QObject void requestPassword(const QString &id, const QString &host); Q_INVOKABLE void respondPassword(const QString &id, const QString password); + void requestOauthLogin(const QString &id, const QString &reason); + Q_INVOKABLE void respondStartOauthLogin(const QString &id); + void showOauthLoginStatus(const QString &id, const QString &status, bool canRetry); + Q_INVOKABLE void respondOauthLoginClosed(const QString &id); + void requestRecoveryKey(const QString &id, const QString &displayName); Q_INVOKABLE void respondRecoveryKey(const QString &id, const QString &key); @@ -187,6 +192,11 @@ private Q_SLOTS: void passwordRequested(QString id, QString host); void passwordResponded(QString id, QString password); + void oauthLoginRequested(QString id, QString reason); + void oauthLoginStartResponded(QString id); + void oauthLoginStatus(QString id, QString status, bool canRetry); + void oauthLoginCloseResponded(QString id); + void recoveryKeyRequested(QString id, QString displayName); void recoveryKeyResponded(QString id, QString key); diff --git a/src/ui/components/dialogs/OauthLoginDialog.qml b/src/ui/components/dialogs/OauthLoginDialog.qml new file mode 100644 index 00000000..62461524 --- /dev/null +++ b/src/ui/components/dialogs/OauthLoginDialog.qml @@ -0,0 +1,115 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Window +import QtQuick.Controls.Material +import base + +BaseDialog { + id: control + title: qsTr("Login required") + + signal startOauthLogin() + signal closeDialog() + + property alias text: contentLabel.text + property alias statusText: statusLabel.text + height: 454 + + Label { + id: contentLabel + text: "" + elide: Label.ElideRight + wrapMode: Label.WordWrap + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 20 + } + + Accessible.role: Accessible.StaticText + Accessible.name: control.title + ", " + contentLabel.text + } + + Label { + id: flowLabel + text: qsTr("To begin the login, press 'Authenticate'. A browser window will open asking you to login to your account and share the required data with GOnnect.") + elide: Label.ElideRight + wrapMode: Label.WordWrap + anchors { + top: contentLabel.bottom + left: parent.left + right: parent.right + margins: 20 + } + + Accessible.role: Accessible.StaticText + Accessible.name: control.title + ", " + flowLabel.text + } + + Button { + id: startButton + text: qsTr("Authenticate") + highlighted: true + anchors { + top: flowLabel.bottom + horizontalCenter: parent.horizontalCenter + margins: 20 + } + + onClicked: () => { + startButton.enabled = false + control.startOauthLogin() + } + + Accessible.role: Accessible.Button + Accessible.name: startButton.text + Accessible.focusable: true + Accessible.onPressAction: () => startButton.click() + } + + Label { + id: statusLabel + text: "" + visible: false + elide: Label.ElideRight + wrapMode: Label.WordWrap + anchors { + top: startButton.bottom + left: parent.left + right: parent.right + margins: 20 + } + } + + Button { + id: closeButton + text: qsTr("Close") + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: 5 + rightMargin: 10 + } + + onClicked: () => { + closeButton.enabled = false + control.closeDialog() + control.close() + } + + Accessible.role: Accessible.Button + Accessible.name: closeButton.text + Accessible.focusable: true + Accessible.onPressAction: () => closeButton.click() + } + + function setStatus(status : string, canRetry: bool) { + startButton.enabled = true + statusLabel.text = status + statusLabel.visible = true + startButton.visible = canRetry + flowLabel.visible = canRetry + } +} From 3068cb437920ed5e013d495dbe278aa284db9204 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:11:42 +0200 Subject: [PATCH 057/122] chore(deps): update eun/http-server-action digest to dadebd2 (#430) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index f4c74a2c..eed8fd92 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -46,7 +46,7 @@ jobs: resources/conan/updater.py --renovate=tmp/renovate/ -v - name: Serve Files - uses: Eun/http-server-action@f71cec1321f665652a46c40b6852f8e5a68bfcd4 # v1 + uses: Eun/http-server-action@dadebd209d4a902eeefceb524c24c38b364c46a7 # v1 with: directory: ${{ github.workspace }}/tmp/renovate/ port: 8080 From f34d71a1fd59f15551c28b8f055a6c07c34ba243 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:31:52 +0200 Subject: [PATCH 058/122] chore(deps): update github actions (#431) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 995ad2de..ec54e398 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 token: ${{ steps.get_token.outputs.token }} - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "lts/*" - name: Install dependencies diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index eed8fd92..d3199592 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@eb932558ad942cccfd8211cf535f17ff183a9f74 # v46.1.9 + uses: renovatebot/github-action@83ec54fee49ab67d9cd201084c1ff325b4b462e4 # v46.1.10 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 8e8ba1f9897457a4582ffdb059985569ac957984 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 21 Apr 2026 09:13:17 +0200 Subject: [PATCH 059/122] fix: build with certificate patch (#433) * feat: feed configured CAs into QWebdav --------- Co-authored-by: Markus Bader --- .../conan/recipes/qtwebdav/all/conandata.yml | 1 + .../all/patches/ssl-certificates.patch | 56 +++++++++++++++++++ resources/flatpak/de.gonicus.gonnect.yml | 2 + .../patches/qtwebdav-ssl-certificates.patch | 56 +++++++++++++++++++ src/AuthManager.cpp | 5 ++ src/calendar/caldav/CalDAVEventFeeder.cpp | 2 + .../carddav/CardDAVAddressBookFeeder.cpp | 3 + 7 files changed, 125 insertions(+) create mode 100644 resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch create mode 100644 resources/flatpak/patches/qtwebdav-ssl-certificates.patch diff --git a/resources/conan/recipes/qtwebdav/all/conandata.yml b/resources/conan/recipes/qtwebdav/all/conandata.yml index 5eddbcae..cb35149e 100644 --- a/resources/conan/recipes/qtwebdav/all/conandata.yml +++ b/resources/conan/recipes/qtwebdav/all/conandata.yml @@ -5,3 +5,4 @@ sources: patches: - patch_file: patches/fixcmake.patch - patch_file: patches/remove-deprecations.patch +- patch_file: patches/ssl-certificates.patch diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch new file mode 100644 index 00000000..e2d382c0 --- /dev/null +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -0,0 +1,56 @@ +diff --git a/qwebdav.cpp b/qwebdav.cpp +index 091500d..6619a48 100644 +--- a/qwebdav.cpp ++++ b/qwebdav.cpp +@@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, + m_sslCertDigestSha1 = hexToDigest(sslCertDigestSha1); + } + ++void QWebdav::addSslCa(QSslCertificate certificate) ++{ ++ m_sslCa += certificate; ++} ++ ++void QWebdav::addSslCa(const QList &certificates) ++{ ++ m_sslCa += certificates; ++} ++ + void QWebdav::replyReadyRead() + { + auto reply = qobject_cast(QObject::sender()); +@@ -695,6 +705,12 @@ QNetworkReply* QWebdav::remove(const QString& path) + QNetworkRequest QWebdav::buildRequest() { + QNetworkRequest req; + if (isSSL()) { ++ if (!m_sslCa.isEmpty()) { ++ QSslConfiguration sslConfig; ++ sslConfig.addCaCertificates(m_sslCa); ++ req.setSslConfiguration(sslConfig); ++ } ++ + QString concatenated = m_username + ":" + m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; +diff --git a/qwebdav.h b/qwebdav.h +index 7111f05..bc3b5f5 100644 +--- a/qwebdav.h ++++ b/qwebdav.h +@@ -97,6 +97,9 @@ public: + void acceptSslCertificate(const QString &sslCertDigestMd5 = "", + const QString &sslCertDigestSha1 = ""); + ++ void addSslCa(QSslCertificate certificate); ++ void addSslCa(const QList& certificates); ++ + QNetworkReply* list(const QString& path); + QNetworkReply* list(const QString& path, int depth); + +@@ -156,6 +159,7 @@ private: + private: + QMap m_outDataDevices; + QMap m_inDataDevices; ++ QList m_sslCa; + + QString m_rootPath; + QString m_username; diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index fa993c1d..d55b17dc 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -181,6 +181,8 @@ modules: commit: ac39687b8ae16118d385d3694e900becd108a0f2 - type: patch path: patches/qtwebdav-cmake.patch + - type: patch + path: patches/qtwebdav-ssl-certificates.patch - name: qtkeychain buildsystem: cmake-ninja diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch new file mode 100644 index 00000000..e2d382c0 --- /dev/null +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -0,0 +1,56 @@ +diff --git a/qwebdav.cpp b/qwebdav.cpp +index 091500d..6619a48 100644 +--- a/qwebdav.cpp ++++ b/qwebdav.cpp +@@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, + m_sslCertDigestSha1 = hexToDigest(sslCertDigestSha1); + } + ++void QWebdav::addSslCa(QSslCertificate certificate) ++{ ++ m_sslCa += certificate; ++} ++ ++void QWebdav::addSslCa(const QList &certificates) ++{ ++ m_sslCa += certificates; ++} ++ + void QWebdav::replyReadyRead() + { + auto reply = qobject_cast(QObject::sender()); +@@ -695,6 +705,12 @@ QNetworkReply* QWebdav::remove(const QString& path) + QNetworkRequest QWebdav::buildRequest() { + QNetworkRequest req; + if (isSSL()) { ++ if (!m_sslCa.isEmpty()) { ++ QSslConfiguration sslConfig; ++ sslConfig.addCaCertificates(m_sslCa); ++ req.setSslConfiguration(sslConfig); ++ } ++ + QString concatenated = m_username + ":" + m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; +diff --git a/qwebdav.h b/qwebdav.h +index 7111f05..bc3b5f5 100644 +--- a/qwebdav.h ++++ b/qwebdav.h +@@ -97,6 +97,9 @@ public: + void acceptSslCertificate(const QString &sslCertDigestMd5 = "", + const QString &sslCertDigestSha1 = ""); + ++ void addSslCa(QSslCertificate certificate); ++ void addSslCa(const QList& certificates); ++ + QNetworkReply* list(const QString& path); + QNetworkReply* list(const QString& path, int depth); + +@@ -156,6 +159,7 @@ private: + private: + QMap m_outDataDevices; + QMap m_inDataDevices; ++ QList m_sslCa; + + QString m_rootPath; + QString m_username; diff --git a/src/AuthManager.cpp b/src/AuthManager.cpp index ca06b7d7..9fd569ce 100644 --- a/src/AuthManager.cpp +++ b/src/AuthManager.cpp @@ -25,6 +25,11 @@ void AuthManager::init() m_isAuthManagerInitialized = true; ReadOnlyConfdSettings settings; + + if (!settings.childGroups().contains("jitsi")) { + return; + } + settings.beginGroup("jitsi"); // Use this when other auth types for Jitsi Meet are implemented diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index 2d98820e..5a0585c2 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -4,6 +4,7 @@ #include "CalDAVEventFeeder.h" #include "DateEventManager.h" +#include "AuthManager.h" Q_LOGGING_CATEGORY(lcCalDAVEventFeeder, "gonnect.app.dateevents.feeder.caldav") @@ -12,6 +13,7 @@ using namespace std::chrono_literals; CalDAVEventFeeder::CalDAVEventFeeder(QObject *parent, const CalDAVEventFeederConfig &config) : QObject(parent), m_config(config) { + m_webdav.addSslCa(AuthManager::instance().sslCAs()); } CalDAVEventFeeder::~CalDAVEventFeeder() diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index d3f350e2..505357a3 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -3,6 +3,7 @@ #include "AddressBookManager.h" #include "AvatarManager.h" #include "ReadOnlyConfdSettings.h" +#include "AuthManager.h" #include #include @@ -30,6 +31,8 @@ void CardDAVAddressBookFeeder::init() m_cacheWriteTimer.setInterval(3s); m_cacheWriteTimer.callOnTimeout(this, &CardDAVAddressBookFeeder::flushCacheImpl); + m_webdav.addSslCa(AuthManager::instance().sslCAs()); + loadCachedData(m_settingsHash); connect(&m_webdavParser, &QWebdavDirParser::finished, this, From 0ee826d3384151954c559e3c417967f8b3fc872f Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Tue, 21 Apr 2026 10:03:04 +0200 Subject: [PATCH 060/122] feat: delete old history entries (#432) * feat: remove older history items (with settings) --------- Co-authored-by: Cajus Pollmeier --- migrate/callhistory/009.sql | 1 + migrate/migrationscripts.qrc | 1 + src/CMakeLists.txt | 3 + src/Ticker.cpp | 21 +++++ src/Ticker.h | 30 +++++++ src/sip/CallHistory.cpp | 39 +++++++++ src/sip/CallHistory.h | 1 + src/sip/NumberStat.h | 1 - src/sip/NumberStats.cpp | 134 +++++++++++++++++++++-------- src/sip/NumberStats.h | 16 ++-- src/sip/PhoneNumberCallCount.h | 14 +++ src/ui/MostCalledModel.cpp | 2 + src/ui/NumberStatsModel.cpp | 14 +-- src/ui/NumberStatsModel.h | 2 +- src/ui/NumberStatsProxyModel.cpp | 17 ++-- src/ui/components/SettingsPage.qml | 53 ++++++++++++ 16 files changed, 279 insertions(+), 70 deletions(-) create mode 100644 migrate/callhistory/009.sql create mode 100644 src/Ticker.cpp create mode 100644 src/Ticker.h create mode 100644 src/sip/PhoneNumberCallCount.h diff --git a/migrate/callhistory/009.sql b/migrate/callhistory/009.sql new file mode 100644 index 00000000..3243dc5b --- /dev/null +++ b/migrate/callhistory/009.sql @@ -0,0 +1 @@ +ALTER TABLE contactflags DROP COLUMN callcount; diff --git a/migrate/migrationscripts.qrc b/migrate/migrationscripts.qrc index 726821a6..5630b83c 100644 --- a/migrate/migrationscripts.qrc +++ b/migrate/migrationscripts.qrc @@ -8,5 +8,6 @@ callhistory/006.sql callhistory/007.sql callhistory/008.sql + callhistory/009.sql diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 872a05d1..ef2c4c20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -461,6 +461,8 @@ qt_add_qml_module(gonnect KeychainSettings.h StateManager.cpp StateManager.h + Ticker.h + Ticker.cpp UISettings.cpp UISettings.h MSOAuthManager.cpp @@ -477,6 +479,7 @@ qt_add_qml_module(gonnect sip/NumberStat.h sip/NumberStats.h sip/NumberStats.cpp + sip/PhoneNumberCallCount.h sip/SIPAccount.h sip/SIPAccount.cpp sip/SIPAccountManager.h diff --git a/src/Ticker.cpp b/src/Ticker.cpp new file mode 100644 index 00000000..f1921b35 --- /dev/null +++ b/src/Ticker.cpp @@ -0,0 +1,21 @@ +#include "Ticker.h" + +#include + +Ticker::Ticker(QObject *parent) : QObject{ parent } +{ + m_dayTicker.setSingleShot(true); + m_dayTicker.callOnTimeout(this, [this]() { + Q_EMIT newDay(); + restartDailyTimer(); + }); + restartDailyTimer(); +} + +void Ticker::restartDailyTimer() +{ + const auto now = QDateTime::currentDateTime(); + const QDateTime next(now.addDays(1).date(), QTime(1, 0)); + const qint64 ms = now.msecsTo(next); + m_dayTicker.start(ms); +} diff --git a/src/Ticker.h b/src/Ticker.h new file mode 100644 index 00000000..61dfb553 --- /dev/null +++ b/src/Ticker.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +/// Singleton class that fires signals in regular intervals (e.g. once every new day). +class Ticker : public QObject +{ + Q_OBJECT + +public: + static Ticker &instance() + { + static Ticker *_instance = nullptr; + if (!_instance) { + _instance = new Ticker; + } + return *_instance; + } + +private Q_SLOTS: + void restartDailyTimer(); + +private: + explicit Ticker(QObject *parent = nullptr); + + QTimer m_dayTicker; + +Q_SIGNALS: + void newDay(); +}; diff --git a/src/sip/CallHistory.cpp b/src/sip/CallHistory.cpp index 01909841..3c163108 100644 --- a/src/sip/CallHistory.cpp +++ b/src/sip/CallHistory.cpp @@ -1,5 +1,7 @@ #include "CallHistory.h" #include "ErrorBus.h" +#include "ReadOnlyConfdSettings.h" +#include "Ticker.h" #include #include @@ -37,7 +39,10 @@ CallHistory::CallHistory(QObject *parent) : QObject{ parent } auto db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(m_databasePath); + connect(&Ticker::instance(), &Ticker::newDay, this, &CallHistory::removeOldHistory); + ensureDatabaseVersion(); + removeOldHistory(); readFromDatabase(); } @@ -175,6 +180,40 @@ void CallHistory::ensureDatabaseVersion() } } +void CallHistory::removeOldHistory() +{ + ReadOnlyConfdSettings settings; + bool ok = false; + const auto days = settings.value("generic/keepHistoryDays", 90).toUInt(&ok); + + if (!ok) { + qCCritical(lcCallHistory) + << "Unable to parse settings value generic/keepHistoryDays as number"; + return; + } + + auto db = QSqlDatabase::database(); + + if (!db.open()) { + qCCritical(lcCallHistory) << "Unable to open call history database:" + << db.lastError().text(); + } else { + qCInfo(lcCallHistory) + << "Successfully opened history database to remove old history entries"; + + QSqlQuery query(db); + query.prepare(QString("DELETE FROM history WHERE time <= unixepoch('now', '-%1 days');") + .arg(days)); + + if (query.exec()) { + qInfo() << query.numRowsAffected() << "old history entries deleted"; + } else { + qCCritical(lcCallHistory) + << "Error on executing SQL query:" << query.lastError().text(); + } + } +} + void CallHistory::writeToDatabase(CallHistoryItem &item) { auto db = QSqlDatabase::database(); diff --git a/src/sip/CallHistory.h b/src/sip/CallHistory.h index 58f3b623..c68dc3a1 100644 --- a/src/sip/CallHistory.h +++ b/src/sip/CallHistory.h @@ -38,6 +38,7 @@ class CallHistory : public QObject explicit CallHistory(QObject *parent = nullptr); qsizetype insertItemAtCorrectPosition(CallHistoryItem *item); void ensureDatabaseVersion(); + void removeOldHistory(); QList m_historyItems; QString m_databasePath; diff --git a/src/sip/NumberStat.h b/src/sip/NumberStat.h index 4aadd1ce..528c103f 100644 --- a/src/sip/NumberStat.h +++ b/src/sip/NumberStat.h @@ -12,7 +12,6 @@ struct NumberStat : public QObject explicit NumberStat(QObject *parent = nullptr) : QObject{ parent } { } QString phoneNumber; - quint32 callCount = 0; bool isFavorite = false; bool isBlocked = false; NumberStats::ContactType contactType = NumberStats::ContactType::PhoneNumber; diff --git a/src/sip/NumberStats.cpp b/src/sip/NumberStats.cpp index 3abe4267..a890c626 100644 --- a/src/sip/NumberStats.cpp +++ b/src/sip/NumberStats.cpp @@ -2,6 +2,7 @@ #include "CallHistory.h" #include "AddressBook.h" #include "NumberStat.h" +#include "PhoneNumberCallCount.h" #include #include @@ -19,7 +20,10 @@ NumberStats::NumberStats(QObject *parent) : QObject{ parent } m_debounceAddressBookUpdateTimer.setSingleShot(true); m_debounceAddressBookUpdateTimer.setInterval(5ms); - m_debounceAddressBookUpdateTimer.callOnTimeout(this, &NumberStats::initialRead); + m_debounceAddressBookUpdateTimer.callOnTimeout(this, [this]() { + initialRead(); + readNumberOfCalls(); + }); connect(&AddressBook::instance(), &AddressBook::contactsReady, this, [this]() { m_debounceAddressBookUpdateTimer.start(); }); @@ -29,6 +33,7 @@ NumberStats::NumberStats(QObject *parent) : QObject{ parent } [this]() { m_debounceAddressBookUpdateTimer.start(); }); initialRead(); + readNumberOfCalls(); } void NumberStats::initialRead() @@ -58,7 +63,6 @@ void NumberStats::initialRead() auto item = new NumberStat; item->phoneNumber = query.value("phoneNumber").toString(); - item->callCount = query.value("callcount").toUInt(); item->isBlocked = query.value("isBlocked").toBool(); item->isFavorite = query.value("isFavorite").toBool(); item->contactType = @@ -81,6 +85,45 @@ void NumberStats::initialRead() Q_EMIT modelReset(); } +void NumberStats::readNumberOfCalls() +{ + auto db = QSqlDatabase::database(); + + if (!db.open()) { + qCCritical(lcNumberStats) << "Unable to open call history database:" + << db.lastError().text(); + } else { + qCInfo(lcNumberStats) << "Successfully opened history database"; + + const auto sipCallValue = std::to_underlying(CallHistoryItem::Type::SIPCall); + QSqlQuery query(db); + query.prepare("SELECT remoteUrl, COUNT(*) as numberOfCalls FROM HISTORY WHERE (type & " + ":typeValue) != 0 GROUP BY remoteUrl, account ORDER BY numberOfCalls DESC;"); + query.bindValue(":typeValue", sipCallValue); + + if (!query.exec()) { + qCCritical(lcNumberStats) + << "Error on executing SQL query:" << query.lastError().text(); + } else { + while (query.next()) { + const auto phoneNumber = PhoneNumberUtil::cleanPhoneNumber( + PhoneNumberUtil::numberFromSipUrl(query.value("remoteUrl").toString())); + + if (!phoneNumber.isEmpty()) { + const auto count = query.value("numberOfCalls").toUInt(); + + // Because remoteUrls are not + if (auto *item = m_callCountLookup.value(phoneNumber, nullptr)) { + item->count += count; + } else { + createAndAddCountObject(phoneNumber, count); + } + } + } + } + } +} + NumberStats::~NumberStats() { m_statItemsLookup.clear(); @@ -99,24 +142,21 @@ void NumberStats::incrementCallCount(const QString &phoneNumber) } else { qCInfo(lcNumberStats) << "Successfully opened history database"; - QSqlQuery query(db); - if (ensureFlaggedNumberExists(phoneNumber)) { - auto statItem = m_statItemsLookup.value(phoneNumber); - statItem->callCount++; - Q_EMIT countChanged(m_statItems.indexOf(statItem)); - - // Update DB entry - qCInfo(lcNumberStats) << "Updating call count for number" << phoneNumber - << "in database"; - query.prepare("UPDATE contactflags SET callcount = callcount + 1 WHERE phoneNumber = " - ":phoneNumber;"); - query.bindValue(":phoneNumber", phoneNumber); - - if (!query.exec()) { - qCCritical(lcNumberStats) - << "Error on executing SQL query:" << query.lastError().text(); + auto *countObj = m_callCountLookup.value(phoneNumber, nullptr); + if (countObj) { + countObj->count++; + + std::ranges::sort(m_callCounts, + [](const PhoneNumberCallCount *left, + const PhoneNumberCallCount *right) -> bool { + return left->count > right->count; + }); + } else { + countObj = createAndAddCountObject(phoneNumber, 1); } + + Q_EMIT countChanged(m_callCounts.indexOf(countObj)); } } } @@ -150,27 +190,34 @@ QList NumberStats::favorites() const QStringList NumberStats::mostCalled(quint8 limit, bool includeFavorites) const { - auto copy = m_statItems; - - std::sort(copy.begin(), copy.end(), - [](const NumberStat *left, const NumberStat *right) -> bool { - return left->callCount > right->callCount; - }); - QStringList result; result.reserve(limit); - quint8 count = 0; - for (qsizetype i = 0, s = copy.size(); count < limit && i < s; ++i) { - const auto item = copy.at(i); - if (includeFavorites || !item->isFavorite) { - result.append(item->phoneNumber); - ++count; + if (includeFavorites) { + const auto l = std::min(static_cast(limit), m_callCounts.size()); + for (qsizetype i = 0; i < l; ++i) { + result.append(m_callCounts.at(i)->phoneNumber); + } + } else { + for (const auto *countObj : std::as_const(m_callCounts)) { + if (m_favoriteLookup.contains(countObj->phoneNumber)) { + result.append(countObj->phoneNumber); + } + + if (result.size() == limit) { + break; + } } } + return result; } +bool NumberStats::isFavorite(const QString &phoneNumber) const +{ + return m_favoriteLookup.contains(phoneNumber); +} + void NumberStats::toggleFavorite(const QString &phoneNumber, const NumberStats::ContactType contactType) { @@ -212,6 +259,11 @@ void NumberStats::toggleFavorite(const QString &phoneNumber, } } +const NumberStat *NumberStats::numberStat(const QString &phoneNumber) const +{ + return m_statItemsLookup.value(phoneNumber, nullptr); +} + bool NumberStats::ensureFlaggedNumberExists(const QString &phoneNumber, const NumberStats::ContactType contactType) { @@ -230,9 +282,8 @@ bool NumberStats::ensureFlaggedNumberExists(const QString &phoneNumber, qCInfo(lcNumberStats) << "Successfully opened history database"; QSqlQuery query(db); - query.prepare( - "INSERT INTO contactflags (phonenumber, callcount, isFavorite, isBlocked, type) " - "VALUES (:phoneNumber, 0, 0, 0, :type);"); + query.prepare("INSERT INTO contactflags (phonenumber, isFavorite, isBlocked, type) " + "VALUES (:phoneNumber, 0, 0, :type);"); query.bindValue(":phoneNumber", phoneNumber); query.bindValue(":type", std::to_underlying(contactType)); @@ -255,6 +306,19 @@ bool NumberStats::ensureFlaggedNumberExists(const QString &phoneNumber, return false; } +PhoneNumberCallCount *NumberStats::createAndAddCountObject(const QString &phoneNumber, + quint32 count) +{ + auto *countObj = new PhoneNumberCallCount(this); + countObj->phoneNumber = phoneNumber; + countObj->count = count; + + m_callCounts.append(countObj); + m_callCountLookup.insert(phoneNumber, countObj); + + return countObj; +} + QDebug operator<<(QDebug debug, const NumberStats &stats) { QDebugStateSaver saver(debug); @@ -265,7 +329,7 @@ QDebug operator<<(QDebug debug, const NumberStats &stats) QDebug operator<<(QDebug debug, const NumberStat &statItem) { QDebugStateSaver saver(debug); - debug.nospace() << "NumberStat(" << statItem.phoneNumber << ", count: " << statItem.callCount + debug.nospace() << "NumberStat(" << statItem.phoneNumber << ", is favorite: " << statItem.isFavorite << ", is blocked: " << statItem.isBlocked << ")"; return debug; diff --git a/src/sip/NumberStats.h b/src/sip/NumberStats.h index e588f257..5c308fb4 100644 --- a/src/sip/NumberStats.h +++ b/src/sip/NumberStats.h @@ -6,6 +6,7 @@ #include struct NumberStat; +struct PhoneNumberCallCount; class NumberStats : public QObject { @@ -36,20 +37,15 @@ class NumberStats : public QObject QStringList mostCalled(quint8 limit, bool includeFavorites = true) const; - bool isFavorite(const QString &phoneNumber) const - { - return m_favoriteLookup.contains(phoneNumber); - } + bool isFavorite(const QString &phoneNumber) const; void toggleFavorite(const QString &phoneNumber, const NumberStats::ContactType contactType); - const NumberStat *numberStat(const QString &phoneNumber) const - { - return m_statItemsLookup.value(phoneNumber, nullptr); - } + const NumberStat *numberStat(const QString &phoneNumber) const; private: explicit NumberStats(QObject *parent = nullptr); void initialRead(); + void readNumberOfCalls(); /** * @brief ensureFlaggedNumberExists creates a new entry in flagged table in database, if it does @@ -62,9 +58,13 @@ class NumberStats : public QObject const QString &phoneNumber, const NumberStats::ContactType contactType = NumberStats::ContactType::PhoneNumber); + PhoneNumberCallCount *createAndAddCountObject(const QString &phoneNumber, quint32 count); + QHash m_statItemsLookup; QHash m_favoriteLookup; QList m_statItems; + QList m_callCounts; + QHash m_callCountLookup; QTimer m_debounceAddressBookUpdateTimer; Q_SIGNALS: diff --git a/src/sip/PhoneNumberCallCount.h b/src/sip/PhoneNumberCallCount.h new file mode 100644 index 00000000..0ecc5dcd --- /dev/null +++ b/src/sip/PhoneNumberCallCount.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +struct PhoneNumberCallCount : public QObject +{ + Q_OBJECT + +public: + explicit PhoneNumberCallCount(QObject *parent = nullptr) : QObject{ parent } { } + + QString phoneNumber; + quint32 count = 0; +}; diff --git a/src/ui/MostCalledModel.cpp b/src/ui/MostCalledModel.cpp index be512553..682a16e0 100644 --- a/src/ui/MostCalledModel.cpp +++ b/src/ui/MostCalledModel.cpp @@ -8,6 +8,8 @@ MostCalledModel::MostCalledModel(QObject *parent) : QAbstractListModel{ parent } connect(&numStats, &NumberStats::numberStatAdded, this, &MostCalledModel::updateModel); connect(&numStats, &NumberStats::countChanged, this, &MostCalledModel::updateModel); + connect(&numStats, &NumberStats::favoriteAdded, this, &MostCalledModel::updateModel); + connect(&numStats, &NumberStats::favoriteRemoved, this, &MostCalledModel::updateModel); updateModel(); } diff --git a/src/ui/NumberStatsModel.cpp b/src/ui/NumberStatsModel.cpp index f8d9676d..78a8888e 100644 --- a/src/ui/NumberStatsModel.cpp +++ b/src/ui/NumberStatsModel.cpp @@ -4,14 +4,8 @@ NumberStatsModel::NumberStatsModel(QObject *parent) : QAbstractListModel{ parent } { - auto &numStats = NumberStats::instance(); - connect(&numStats, &NumberStats::countChanged, this, [this](const qsizetype index) { - const auto modelIndex = createIndex(index, 0); - Q_EMIT dataChanged(modelIndex, modelIndex, { static_cast(Roles::Count) }); - }); - connect(&numStats, &NumberStats::numberStatAdded, this, [this](const qsizetype index) { beginInsertRows(QModelIndex(), index, index); endInsertRows(); @@ -22,7 +16,6 @@ QHash NumberStatsModel::roleNames() const { return { { static_cast(Roles::PhoneNumber), "phoneNumber" }, - { static_cast(Roles::Count), "count" }, }; } @@ -34,13 +27,8 @@ int NumberStatsModel::rowCount(const QModelIndex &parent) const QVariant NumberStatsModel::data(const QModelIndex &index, int role) const { + Q_UNUSED(role); const auto item = NumberStats::instance().statsAndFlags().at(index.row()); - - switch (role) { - case static_cast(Roles::Count): - return item->callCount; - } - return item->phoneNumber; } diff --git a/src/ui/NumberStatsModel.h b/src/ui/NumberStatsModel.h index e41a7882..25d9ca85 100644 --- a/src/ui/NumberStatsModel.h +++ b/src/ui/NumberStatsModel.h @@ -10,7 +10,7 @@ class NumberStatsModel : public QAbstractListModel QML_ELEMENT public: - enum class Roles { PhoneNumber = Qt::UserRole + 1, Count }; + enum class Roles { PhoneNumber = Qt::UserRole + 1 }; explicit NumberStatsModel(QObject *parent = nullptr); diff --git a/src/ui/NumberStatsProxyModel.cpp b/src/ui/NumberStatsProxyModel.cpp index 1b489439..50a413d8 100644 --- a/src/ui/NumberStatsProxyModel.cpp +++ b/src/ui/NumberStatsProxyModel.cpp @@ -17,16 +17,9 @@ bool NumberStatsProxyModel::lessThan(const QModelIndex &sourceLeft, typedef NumberStatsModel::Roles Roles; - const auto leftCount = model->data(sourceLeft, static_cast(Roles::Count)).toUInt(); - const auto rightCount = model->data(sourceRight, static_cast(Roles::Count)).toUInt(); - - if (leftCount == rightCount) { - const auto leftNumber = - model->data(sourceLeft, static_cast(Roles::PhoneNumber)).toString(); - const auto rightNumber = - model->data(sourceRight, static_cast(Roles::PhoneNumber)).toString(); - return leftNumber < rightNumber; - } - - return leftCount > rightCount; + const auto leftNumber = + model->data(sourceLeft, static_cast(Roles::PhoneNumber)).toString(); + const auto rightNumber = + model->data(sourceRight, static_cast(Roles::PhoneNumber)).toString(); + return leftNumber < rightNumber; } diff --git a/src/ui/components/SettingsPage.qml b/src/ui/components/SettingsPage.qml index ea597b31..4317a9d1 100644 --- a/src/ui/components/SettingsPage.qml +++ b/src/ui/components/SettingsPage.qml @@ -43,6 +43,7 @@ Item { property alias useHeadset: headsetCheckBox.checked property alias noSyncSystemMute: disableSystemMutePropagationCheckBox.checked property alias jitsiChatAsNotifications: jitsiChatAsNotificationsCheckBox.checked + property alias keepHistoryDays: historyDaysToKeepInputField.text } Settings { @@ -248,6 +249,58 @@ Item { } } + CardList { + title: qsTr('History') + spacing: 20 + anchors { + left: parent.left + right: parent.right + margins: 20 + } + + Item { + implicitHeight: historyDaysToKeepInputField.implicitHeight + anchors { + left: parent.left + right: parent.right + } + + TextField { + id: historyDaysToKeepInputField + text: "90" + anchors.left: parent.left + topPadding: 0 + bottomPadding: 0 + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator { + bottom: 1 + top: 999 + } + } + + Label { + text: qsTr("day(s) of history", "", parseInt(historyDaysToKeepInputField.text, 10)) + anchors { + left: historyDaysToKeepInputField.right + right: parent.right + verticalCenter: historyDaysToKeepInputField.verticalCenter + leftMargin: 12 + } + } + } + + Label { + id: historyExplanationLabel + color: Theme.secondaryTextColor + wrapMode: Label.Wrap + anchors { + left: parent.left + right: parent.right + } + text: qsTr("Keep a call history for this number of days (from 1 to 999). Any row before this time span is automatically deleted. Changing this setting has an effect on the next day or a reboot of GOnnect.") + } + } + CardList { title: qsTr('Appearance') anchors { From e262f3f75ef70ed54b1aca17c7169e06c7ebd94a Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:06:13 +0200 Subject: [PATCH 061/122] feat: feeder plugin retry mechanism (#391) * feat: initial retry mechanism for calendar/contact plugins --------- Co-authored-by: Cajus Pollmeier --- docs/modules/ROOT/examples/sample.conf | 10 + resources/templates/sample.conf | 10 + src/Application.cpp | 2 +- src/calendar/DateEventFeederManager.cpp | 11 +- src/calendar/DateEventFeederManager.h | 2 +- src/calendar/IDateEventFeederFactory.h | 3 +- src/calendar/akonadi/AkonadiEventFeeder.cpp | 5 +- src/calendar/akonadi/AkonadiEventFeeder.h | 3 +- .../akonadi/AkonadiEventFeederFactory.cpp | 9 +- .../akonadi/AkonadiEventFeederFactory.h | 1 + src/calendar/caldav/CalDAVEventFeeder.cpp | 145 +++++++++++--- src/calendar/caldav/CalDAVEventFeeder.h | 15 +- src/calendar/caldav/CalDAVEventFeederConfig.h | 3 +- .../caldav/CalDAVEventFeederFactory.cpp | 12 +- .../caldav/CalDAVEventFeederFactory.h | 1 + src/calendar/eds/EDSEventFeeder.cpp | 178 +++++++++++++----- src/calendar/eds/EDSEventFeeder.h | 17 +- src/calendar/eds/EDSEventFeederFactory.cpp | 7 +- src/calendar/eds/EDSEventFeederFactory.h | 1 + src/calendar/msgraph/MSGraphEventFeeder.cpp | 64 +++++-- src/calendar/msgraph/MSGraphEventFeeder.h | 14 +- .../msgraph/MSGraphEventFeederFactory.cpp | 6 +- .../msgraph/MSGraphEventFeederFactory.h | 1 + src/contacts/AddressBook.cpp | 33 +++- src/contacts/AddressBook.h | 8 +- src/contacts/AddressBookManager.cpp | 7 +- src/contacts/AddressBookManager.h | 1 + src/contacts/Contact.cpp | 11 +- src/contacts/Contact.h | 1 + src/contacts/ContactSerializer.cpp | 6 +- src/contacts/IAddressBookFactory.h | 3 +- .../akonadi/AkonadiAddressBookFactory.cpp | 4 +- .../akonadi/AkonadiAddressBookFactory.h | 3 +- .../akonadi/AkonadiAddressBookFeeder.cpp | 13 +- .../akonadi/AkonadiAddressBookFeeder.h | 4 +- .../carddav/CardDAVAddressBookFactory.cpp | 5 +- .../carddav/CardDAVAddressBookFactory.h | 3 +- .../carddav/CardDAVAddressBookFeeder.cpp | 76 ++++++-- .../carddav/CardDAVAddressBookFeeder.h | 17 +- src/contacts/csv/CSVAddressBookFactory.cpp | 5 +- src/contacts/csv/CSVAddressBookFactory.h | 3 +- src/contacts/csv/CSVFileAddressBookFeeder.cpp | 12 +- src/contacts/csv/CSVFileAddressBookFeeder.h | 4 +- src/contacts/eds/EDSAddressBookFactory.cpp | 5 +- src/contacts/eds/EDSAddressBookFactory.h | 3 +- src/contacts/eds/EDSAddressBookFeeder.cpp | 161 +++++++++++----- src/contacts/eds/EDSAddressBookFeeder.h | 17 +- src/contacts/ldap/LDAPAddressBookFactory.cpp | 5 +- src/contacts/ldap/LDAPAddressBookFactory.h | 3 +- src/contacts/ldap/LDAPAddressBookFeeder.cpp | 92 ++++++--- src/contacts/ldap/LDAPAddressBookFeeder.h | 11 +- .../msgraph/MSGraphAddressBookFactory.cpp | 5 +- .../msgraph/MSGraphAddressBookFactory.h | 3 +- .../msgraph/MSGraphAddressBookFeeder.cpp | 59 +++++- .../msgraph/MSGraphAddressBookFeeder.h | 13 +- src/ui/components/SettingsPage.qml | 8 +- 56 files changed, 853 insertions(+), 271 deletions(-) diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index a28c8303..aabffdda 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -62,6 +62,16 @@ ## default: false #disableGlobalShortcuts=false +## Defines the maximum number of times a calendar/contact plugin will attempt +## to fetch data before stopping and reporting a permanent failure. +## (optional, default: 5) +#feederPluginRetryCount=5 + +## Specifies the delay (in milliseconds) before calendar/contact plugins attempt +## to fetch and process data again after a previously failed attempt. +## (optional, default: 10000) +#feederPluginRetryInterval=10000 + [sip] ## Encode SIP headers in their short forms to reduce size. By default, SIP headers in outgoing messages will ## be encoded in their full names. If this option is enabled, then SIP headers for outgoing messages will be diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index 7589e53d..46f42a77 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -55,6 +55,16 @@ busyOnBusy=true ## default: false #disableGlobalShortcuts=false +## Defines the maximum number of times a calendar/contact plugin will attempt +## to fetch data before stopping and reporting a permanent failure. +## (optional, default: 5) +#feederPluginRetryCount=5 + +## Specifies the delay (in milliseconds) before calendar/contact plugins attempt +## to fetch and process data again after a previously failed attempt. +## (optional, default: 10000) +#feederPluginRetryInterval=10000 + [sip] ## Encode SIP headers in their short forms to reduce size. By default, SIP headers in outgoing messages will ## be encoded in their full names. If this option is enabled, then SIP headers for outgoing messages will be diff --git a/src/Application.cpp b/src/Application.cpp index 7d687d62..c9ee33d1 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -127,7 +127,7 @@ void Application::initialize() UISettings::instance().setParent(this); AddressBookManager::instance().reloadAddressBook(); - DateEventFeederManager::instance().reload(); + DateEventFeederManager::instance().reloadCalendar(); SystemTrayMenu::instance(); // Ensure singleton is created AppSettings settings; diff --git a/src/calendar/DateEventFeederManager.cpp b/src/calendar/DateEventFeederManager.cpp index 6ac261ae..326e1479 100644 --- a/src/calendar/DateEventFeederManager.cpp +++ b/src/calendar/DateEventFeederManager.cpp @@ -21,7 +21,7 @@ DateEventFeederManager::DateEventFeederManager(QObject *parent) : QObject{ paren connect(&m_nextDayRefreshTimer, &QTimer::timeout, this, [this]() { setTimeData(); initFeederConfigs(); - reload(); + reloadCalendar(); }); m_nextDayRefreshTimer.start(); } @@ -38,7 +38,7 @@ void DateEventFeederManager::setTimeData() m_nextDayRefreshTimer.setInterval(m_nextDayDuration); } -void DateEventFeederManager::reload() +void DateEventFeederManager::reloadCalendar() { DateEventManager::instance().resetDateEvents(); m_feederConfigIds = m_dateEventFeeders.keys(); @@ -96,6 +96,10 @@ void DateEventFeederManager::acquireSecret(bool forcePrompt, const QString &conf void DateEventFeederManager::initFeederConfigs() { + ReadOnlyConfdSettings settings; + int retryCount = settings.value("generic/feederPluginRetryCount", 5).toInt(); + int retryInterval = settings.value("generic/feederPluginRetryInterval", 10000).toInt(); + const QObjectList &staticPlugins = QPluginLoader::staticInstances(); for (QObject *obj : std::as_const(staticPlugins)) { @@ -108,7 +112,8 @@ void DateEventFeederManager::initFeederConfigs() for (auto &cfg : std::as_const(configs)) { m_dateEventFeeders.insert(cfg, plugin->createFeeder(cfg, m_currentTime, m_timeRangeStart, - m_timeRangeEnd, this)); + m_timeRangeEnd, retryCount, + retryInterval, this)); } } } diff --git a/src/calendar/DateEventFeederManager.h b/src/calendar/DateEventFeederManager.h index 27546094..e9f81c75 100644 --- a/src/calendar/DateEventFeederManager.h +++ b/src/calendar/DateEventFeederManager.h @@ -24,7 +24,7 @@ class DateEventFeederManager : public QObject } void initFeederConfigs(); - void reload(); + void reloadCalendar(); void acquireSecret(bool forcePrompt, const QString &configId, std::function callback); diff --git a/src/calendar/IDateEventFeederFactory.h b/src/calendar/IDateEventFeederFactory.h index 7ac98414..97c6f8ff 100644 --- a/src/calendar/IDateEventFeederFactory.h +++ b/src/calendar/IDateEventFeederFactory.h @@ -16,7 +16,8 @@ class IDateEventFeederFactory virtual IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, + const QDateTime &timeRangeEnd, const int retryCount, + const int retryInterval, DateEventFeederManager *feederManager) const = 0; }; diff --git a/src/calendar/akonadi/AkonadiEventFeeder.cpp b/src/calendar/akonadi/AkonadiEventFeeder.cpp index f5e6aca9..7ecde6c0 100644 --- a/src/calendar/akonadi/AkonadiEventFeeder.cpp +++ b/src/calendar/akonadi/AkonadiEventFeeder.cpp @@ -9,7 +9,8 @@ Q_LOGGING_CATEGORY(lcAkonadiEventFeeder, "gonnect.app.dateevents.feeder.akonadi" AkonadiEventFeeder::AkonadiEventFeeder(QObject *parent, const QString &source, const QDateTime ¤tTime, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd) + const QDateTime &timeRangeEnd, const int retryCount, + const int retryInterval) : QObject(parent), m_source(source), m_currentTime(currentTime), @@ -18,6 +19,8 @@ AkonadiEventFeeder::AkonadiEventFeeder(QObject *parent, const QString &source, m_session(new Akonadi::Session("GOnnect::CalendarSession")), m_monitor(new Akonadi::Monitor()) { + Q_UNUSED(retryCount) + Q_UNUSED(retryInterval) } AkonadiEventFeeder::~AkonadiEventFeeder() diff --git a/src/calendar/akonadi/AkonadiEventFeeder.h b/src/calendar/akonadi/AkonadiEventFeeder.h index 13bb18f3..7c11d772 100644 --- a/src/calendar/akonadi/AkonadiEventFeeder.h +++ b/src/calendar/akonadi/AkonadiEventFeeder.h @@ -27,7 +27,8 @@ class AkonadiEventFeeder : public QObject, public IDateEventFeeder explicit AkonadiEventFeeder(QObject *parent = nullptr, const QString &source = "", const QDateTime ¤tTime = QDateTime(), const QDateTime &timeRangeStart = QDateTime(), - const QDateTime &timeRangeEnd = QDateTime()); + const QDateTime &timeRangeEnd = QDateTime(), + const int retryCount = 0, const int retryInterval = 0); ~AkonadiEventFeeder(); diff --git a/src/calendar/akonadi/AkonadiEventFeederFactory.cpp b/src/calendar/akonadi/AkonadiEventFeederFactory.cpp index fa805794..676baf30 100644 --- a/src/calendar/akonadi/AkonadiEventFeederFactory.cpp +++ b/src/calendar/akonadi/AkonadiEventFeederFactory.cpp @@ -27,10 +27,9 @@ QStringList AkonadiEventFeederFactory::configurations() const IDateEventFeeder *AkonadiEventFeederFactory::createFeeder( const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, DateEventFeederManager *feederManager) const + const QDateTime &timeRangeEnd, const int retryCount, const int retryInterval, + DateEventFeederManager *feederManager) const { - ReadOnlyConfdSettings settings; - settings.beginGroup(settingsGroup); - - return new AkonadiEventFeeder(feederManager, name(), currentTime, timeRangeStart, timeRangeEnd); + return new AkonadiEventFeeder(feederManager, settingsGroup, currentTime, timeRangeStart, + retryCount, retryInterval, timeRangeEnd); } diff --git a/src/calendar/akonadi/AkonadiEventFeederFactory.h b/src/calendar/akonadi/AkonadiEventFeederFactory.h index 66122310..a0293059 100644 --- a/src/calendar/akonadi/AkonadiEventFeederFactory.h +++ b/src/calendar/akonadi/AkonadiEventFeederFactory.h @@ -18,5 +18,6 @@ class AkonadiEventFeederFactory : public QObject, IDateEventFeederFactory QStringList configurations() const override; IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval, DateEventFeederManager *feederManager) const override; }; diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index 5a0585c2..aa892f69 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -38,8 +38,8 @@ void CalDAVEventFeeder::init() connect(&m_webdav, &QWebdav::errorChanged, this, &CalDAVEventFeeder::onError); connect(&m_webdav, &QWebdav::authenticationRequired, this, [this]() { - // Previous run failed due to auth, we'll prompt the user again - process(true); + m_pendingAuth = true; + checkErrorStatus(); }); /* @@ -58,45 +58,122 @@ void CalDAVEventFeeder::init() process(); } -void CalDAVEventFeeder::onError(QString error) const +void CalDAVEventFeeder::checkErrorStatus() { - qCCritical(lcCalDAVEventFeeder) << error; + QMetaObject::invokeMethod( + this, + [this]() { + // Prepare feeder for re-run + resetCalendar(); + m_checksums.clear(); + if (m_calendarRefreshTimer.isActive()) { + m_calendarRefreshTimer.stop(); + } + + if (m_pendingAuth && m_pendingError) { + // Previous run failed due to auth, we'll prompt the user again immediately + qCWarning(lcCalDAVEventFeeder) + << "Failed to process CalDAV sources - invalid password"; + + m_calendarRefreshTimer.start(); + + process(true); + } else if (m_pendingError) { + // Some other error has occurred, wait and try again + if (m_config.retryCount > 0) { + m_config.retryCount--; + + qCWarning(lcCalDAVEventFeeder) + << "Failed to process CalDAV sources - trying later"; + + QTimer::singleShot(m_config.retryInterval, this, [this]() { + m_calendarRefreshTimer.start(); + + process(); + }); + } + } + + m_pendingAuth = false; + m_pendingError = false; + }, + Qt::QueuedConnection); } -void CalDAVEventFeeder::onParserFinished() +void CalDAVEventFeeder::resetCalendar() { - const auto list = m_webdavParser.getList(); - for (const auto &item : list) { - QNetworkReply *reply = m_webdav.get(item.path()); - connect( - reply, &QNetworkReply::finished, this, - [item, reply, this]() { - if (!reply) { - return; - } - QByteArray data = reply->readAll(); - reply->deleteLater(); + DateEventManager &manager = DateEventManager::instance(); - QMimeDatabase db; - QMimeType type = db.mimeTypeForData(data); - if (type.name() == "text/calendar" && !data.isEmpty() - && responseDataChanged(data)) { - QString concreteSource = QString("%1-%2").arg(m_config.source, item.name()); + for (auto &concreteSource : std::as_const(m_concreteSources)) { + manager.removeDateEventsBySource(concreteSource); + } +} - DateEventManager &manager = DateEventManager::instance(); - manager.removeDateEventsBySource(concreteSource); +void CalDAVEventFeeder::onError(QString error) +{ + qCCritical(lcCalDAVEventFeeder) << "Error:" << error; - processResponse(data, concreteSource); - } - }, - Qt::ConnectionType::SingleShotConnection); + m_pendingError = true; + checkErrorStatus(); +} + +void CalDAVEventFeeder::onParserFinished() +{ + m_concreteSources.clear(); + m_items = m_webdavParser.getList(); + + getNextItem(); +} + +void CalDAVEventFeeder::getNextItem() +{ + if (m_items.isEmpty()) { + return; } + + auto item = m_items.takeFirst(); + QNetworkReply *reply = m_webdav.get(item.path()); + + connect( + reply, &QNetworkReply::finished, this, + [item, reply, this]() { + if (!reply) { + return; + } + + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QMimeDatabase db; + QMimeType type = db.mimeTypeForData(data); + + bool success = true; + + if (type.name() == "text/calendar" && !data.isEmpty() + && responseDataChanged(data)) { + QString concreteSource = QString("%1_%2").arg(m_config.source, item.name()); + m_concreteSources.append(concreteSource); + + DateEventManager &manager = DateEventManager::instance(); + manager.removeDateEventsBySource(concreteSource); + + success = processResponse(data, concreteSource); + } + + // If iCal parsing fails at any point, we want to start over entirely + if (success) { + getNextItem(); + } else { + m_items.clear(); + } + }, + Qt::SingleShotConnection); } void CalDAVEventFeeder::process(bool authFailed) { auto manager = q_check_ptr(qobject_cast(parent())); - manager->acquireSecret(authFailed, m_config.settingsGroupId, [this](const QString &password) { + manager->acquireSecret(authFailed, m_config.source, [this](const QString &password) { m_webdav.setConnectionSettings(m_config.useSSL ? QWebdav::HTTPS : QWebdav::HTTP, m_config.host, m_config.path, m_config.user, password, m_config.port); @@ -105,7 +182,7 @@ void CalDAVEventFeeder::process(bool authFailed) }); } -void CalDAVEventFeeder::processResponse(const QByteArray &data, const QString &source) +bool CalDAVEventFeeder::processResponse(const QByteArray &data, const QString &source) { DateEventManager &manager = DateEventManager::instance(); @@ -234,9 +311,15 @@ void CalDAVEventFeeder::processResponse(const QByteArray &data, const QString &s manager.addDateEvent(id, source, start, end, summary, location, description); } } - } else { - qCCritical(lcCalDAVEventFeeder) << icalerror_strerror(icalerrno); + + icalcomponent_free(calendar); } + + if (icalerrno != ICAL_NO_ERROR) { + onError(icalerror_strerror(icalerrno)); + return false; + } + return true; } bool CalDAVEventFeeder::responseDataChanged(const QByteArray &data) diff --git a/src/calendar/caldav/CalDAVEventFeeder.h b/src/calendar/caldav/CalDAVEventFeeder.h index b61486a1..07b2a494 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.h +++ b/src/calendar/caldav/CalDAVEventFeeder.h @@ -29,15 +29,20 @@ class CalDAVEventFeeder : public QObject, public IDateEventFeeder void process(bool authFailed = false); private Q_SLOTS: - void onError(QString error) const; + void onError(QString error); void onParserFinished(); private: - void processResponse(const QByteArray &data, const QString &source); + void getNextItem(); + + bool processResponse(const QByteArray &data, const QString &source); bool responseDataChanged(const QByteArray &data); QDateTime createDateTimeFromTimeType(icaltimetype &datetime); + void checkErrorStatus(); + void resetCalendar(); + QList m_checksums; CalDAVEventFeederConfig m_config; @@ -46,4 +51,10 @@ private Q_SLOTS: QWebdav m_webdav; QWebdavDirParser m_webdavParser; + QList m_items; + + QStringList m_concreteSources; + + bool m_pendingError = false; + bool m_pendingAuth = false; }; diff --git a/src/calendar/caldav/CalDAVEventFeederConfig.h b/src/calendar/caldav/CalDAVEventFeederConfig.h index 50e1bd36..36bba54c 100644 --- a/src/calendar/caldav/CalDAVEventFeederConfig.h +++ b/src/calendar/caldav/CalDAVEventFeederConfig.h @@ -6,7 +6,6 @@ struct CalDAVEventFeederConfig { - QString settingsGroupId; QString source; QString host; QString path; @@ -17,4 +16,6 @@ struct CalDAVEventFeederConfig QDateTime currentTime; QDateTime timeRangeStart; QDateTime timeRangeEnd; + int retryCount; + int retryInterval; }; diff --git a/src/calendar/caldav/CalDAVEventFeederFactory.cpp b/src/calendar/caldav/CalDAVEventFeederFactory.cpp index ca7a8f0d..c2e4e872 100644 --- a/src/calendar/caldav/CalDAVEventFeederFactory.cpp +++ b/src/calendar/caldav/CalDAVEventFeederFactory.cpp @@ -31,16 +31,16 @@ QStringList CalDAVEventFeederFactory::configurations() const IDateEventFeeder *CalDAVEventFeederFactory::createFeeder( const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, DateEventFeederManager *feederManager) const + const QDateTime &timeRangeEnd, const int retryCount, const int retryInterval, + DateEventFeederManager *feederManager) const { ReadOnlyConfdSettings settings; settings.beginGroup(settingsGroup); return new CalDAVEventFeeder( feederManager, - { settingsGroup, name(), settings.value("host").toString(), - settings.value("path").toString(), settings.value("user").toString(), - settings.value("port", 0).toInt(), settings.value("useSSL", true).toBool(), - settings.value("interval", 300000).toInt(), currentTime, timeRangeStart, - timeRangeEnd }); + { settingsGroup, settings.value("host").toString(), settings.value("path").toString(), + settings.value("user").toString(), settings.value("port", 0).toInt(), + settings.value("useSSL", true).toBool(), settings.value("interval", 300000).toInt(), + currentTime, timeRangeStart, timeRangeEnd, retryCount, retryInterval }); } diff --git a/src/calendar/caldav/CalDAVEventFeederFactory.h b/src/calendar/caldav/CalDAVEventFeederFactory.h index 9f9a2bb4..b9e4a8c6 100644 --- a/src/calendar/caldav/CalDAVEventFeederFactory.h +++ b/src/calendar/caldav/CalDAVEventFeederFactory.h @@ -18,5 +18,6 @@ class CalDAVEventFeederFactory : public QObject, IDateEventFeederFactory QStringList configurations() const override; IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval, DateEventFeederManager *feederManager) const override; }; diff --git a/src/calendar/eds/EDSEventFeeder.cpp b/src/calendar/eds/EDSEventFeeder.cpp index 4fb56d82..62bf21a3 100644 --- a/src/calendar/eds/EDSEventFeeder.cpp +++ b/src/calendar/eds/EDSEventFeeder.cpp @@ -11,45 +11,43 @@ using namespace std::chrono_literals; Q_LOGGING_CATEGORY(lcEDSEventFeeder, "gonnect.app.dateevents.feeder.eds") EDSEventFeeder::EDSEventFeeder(QObject *parent, const QString &source, const QDateTime ¤tTime, - const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd) + const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval) : QObject(parent), m_source(source), m_currentTime(currentTime), m_timeRangeStart(timeRangeStart), - m_timeRangeEnd(timeRangeEnd) + m_timeRangeEnd(timeRangeEnd), + m_retryCount(retryCount), + m_retryInterval(retryInterval) { } EDSEventFeeder::~EDSEventFeeder() { - g_clear_object(&m_registry); - if (m_sources) { - g_list_free_full(m_sources, g_object_unref); - } - g_clear_pointer(&m_searchExpr, g_free); - g_clear_object(&m_cancellable); + resetFeeder(); +} - for (auto client : std::as_const(m_clients)) { - g_clear_object(&client); - } +void EDSEventFeeder::init() +{ + connect( + this, &EDSEventFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + resetCalendar(); + resetFeeder(); - for (auto clientView : std::as_const(m_clientViews)) { - g_clear_object(&clientView); - } + if (m_retryCount > 0) { + m_retryCount--; - if (m_sourcePromise) { - delete m_sourcePromise; - m_sourcePromise = nullptr; - } + qCWarning(lcEDSEventFeeder) << "Failed to process EDS sources - trying later"; - if (m_futureWatcher) { - m_futureWatcher->deleteLater(); - m_futureWatcher = nullptr; - } -} + // Retry + QTimer::singleShot(m_retryInterval, this, [this]() { init(); }); + } + }, + Qt::SingleShotConnection); -void EDSEventFeeder::init() -{ m_cancellable = g_cancellable_new(); GError *error = NULL; @@ -60,6 +58,8 @@ void EDSEventFeeder::init() if (error) { qCDebug(lcEDSEventFeeder) << "Can't create registry:" << error->message; g_clear_error(&error); + + Q_EMIT feederFailed(); } return; } @@ -93,24 +93,93 @@ void EDSEventFeeder::init() m_sourcePromise->start(); - QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then(this, [this]() { - if (m_sourceFuture.isFinished()) { - process(); + m_chainFuture = QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished) + .then(this, [this]() { + if (!m_chainFuture.isCanceled() && m_sourceFuture.isFinished()) { + process(); + } + }); + + m_sourceTimeout.setSingleShot(true); + m_sourceTimeout.setInterval(5s); + connect(&m_sourceTimeout, &QTimer::timeout, this, [this]() { + if (m_futureWatcher && !m_futureWatcher->isFinished()) { + Q_EMIT feederFailed(); } }); + m_sourceTimeout.start(); - QTimer::singleShot(5s, this, [this]() { - if (!m_futureWatcher->isFinished()) { - qCDebug(lcEDSEventFeeder) << "Failed to process EDS sources"; + m_futureWatcher->setFuture(m_sourceFuture); +} - g_cancellable_cancel(m_cancellable); +void EDSEventFeeder::resetCalendar() +{ + DateEventManager &manager = DateEventManager::instance(); - m_sourceFuture.cancel(); - m_futureWatcher->cancel(); + for (auto client : std::as_const(m_clients)) { + if (client) { + QString concreteSource = QString("%1_%2").arg( + m_source, e_source_get_uid(e_client_get_source(E_CLIENT(client)))); + manager.removeDateEventsBySource(concreteSource); } - }); + } +} - m_futureWatcher->setFuture(m_sourceFuture); +void EDSEventFeeder::resetFeeder() +{ + m_sourceCount = 0; + m_clientCount = 0; + + if (m_sourceTimeout.isActive()) { + m_sourceTimeout.stop(); + } + + g_clear_object(&m_registry); + if (m_sources) { + g_clear_list(&m_sources, g_object_unref); + } + g_clear_pointer(&m_searchExpr, g_free); + + // Cancel all active EDS async methods + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + for (auto client : std::as_const(m_clients)) { + g_clear_object(&client); + } + m_clients.clear(); + + // Disconnect all EDS signal handlers + disconnectCalendarSignals(); + for (auto clientView : std::as_const(m_clientViews)) { + g_clear_object(&clientView); + } + m_clientViews.clear(); + + // Future/Promise + if (m_chainFuture.isRunning()) { + m_chainFuture.cancel(); + } + m_chainFuture = QFuture(); + + if (m_futureWatcher) { + m_futureWatcher->cancel(); + + m_futureWatcher->deleteLater(); + m_futureWatcher = nullptr; + } + + if (m_sourceFuture.isRunning()) { + m_sourceFuture.cancel(); + } + m_sourceFuture = QFuture(); + + if (m_sourcePromise) { + m_sourcePromise->finish(); + + delete m_sourcePromise; + m_sourcePromise = nullptr; + } } void EDSEventFeeder::process() @@ -161,6 +230,8 @@ void EDSEventFeeder::onEcalClientConnected(GObject *source_object, GAsyncResult qCDebug(lcEDSEventFeeder) << "Can't retrieve finished client connection:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); return; } @@ -184,14 +255,18 @@ void EDSEventFeeder::onViewComplete(ECalClientView *view, GError *error, gpointe guint signalId = g_signal_lookup("complete", G_OBJECT_TYPE(view)); g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, NULL, NULL, NULL); + EDSEventFeeder *feeder = static_cast(user_data); + if (error) { qCCritical(lcEDSEventFeeder) << "Failed to wait for view completion, unable to subscribe " "to live calendar updates:" << error->message; + if (feeder) { + Q_EMIT feeder->feederFailed(); + } return; } - EDSEventFeeder *feeder = static_cast(user_data); if (feeder) { feeder->connectCalendarSignals(view); } @@ -204,6 +279,14 @@ void EDSEventFeeder::connectCalendarSignals(ECalClientView *view) g_signal_connect(view, "objects-removed", G_CALLBACK(onEventsRemoved), this); } +void EDSEventFeeder::disconnectCalendarSignals() +{ + for (auto view : std::as_const(m_clientViews)) { + // Match all signals with the same gpointer user_data + g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this); + } +} + void EDSEventFeeder::onEventsAdded(ECalClientView *view, GSList *components, gpointer user_data) { // INFO: We want *all* events to account for recursive updates @@ -250,7 +333,7 @@ void EDSEventFeeder::processEventsAdded(ECalClientView *view) const auto idx = m_clients.indexOf(client); g_clear_object(&client); - QString concreteSource = QString("%1-%2").arg( + QString concreteSource = QString("%1_%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); @@ -273,7 +356,7 @@ void EDSEventFeeder::processEventsModified(ECalClientView *view) const auto idx = m_clients.indexOf(client); g_clear_object(&client); - QString concreteSource = QString("%1-%2").arg( + QString concreteSource = QString("%1_%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); @@ -296,7 +379,7 @@ void EDSEventFeeder::processEventsRemoved(ECalClientView *view) const auto idx = m_clients.indexOf(client); g_clear_object(&client); - QString concreteSource = QString("%1-%2").arg( + QString concreteSource = QString("%1_%2").arg( m_source, e_source_get_uid(e_client_get_source(E_CLIENT(m_clients.at(idx))))); manager.removeDateEventsBySource(concreteSource); @@ -317,6 +400,8 @@ void EDSEventFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, if (error) { qCCritical(lcEDSEventFeeder) << "Can't retrieve finished view:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); } return; } @@ -326,6 +411,8 @@ void EDSEventFeeder::onViewCreated(GObject *source_object, GAsyncResult *result, if (error) { qCCritical(lcEDSEventFeeder) << "Can't start view:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); return; } feeder->m_clientViews.append(view); @@ -345,11 +432,17 @@ void EDSEventFeeder::onClientEventsRequested(GObject *source_object, GAsyncResul EDSEventFeeder *feeder = static_cast(user_data); if (feeder) { + /* + INFO: The function below may return false, but no error - it seems that this + happens if the components GSList is empty or NULL (?), this shouldn't happen + */ if (!e_cal_client_get_object_list_finish(E_CAL_CLIENT(source_object), result, &components, &error)) { if (error) { qCCritical(lcEDSEventFeeder) << "Can't retrieve events:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); } return; } @@ -367,7 +460,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList { DateEventManager &manager = DateEventManager::instance(); - QString concreteSource = QString("%1-%2").arg(m_source, clientUid); + QString concreteSource = QString("%1_%2").arg(m_source, clientUid); QMap> exdatesById; @@ -499,8 +592,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList } } - g_slist_free_full(components, g_object_unref); - components = NULL; + g_clear_slist(&components, g_object_unref); qCInfo(lcEDSEventFeeder) << "Loaded events of source" << clientName << "(" << clientUid << ")"; } diff --git a/src/calendar/eds/EDSEventFeeder.h b/src/calendar/eds/EDSEventFeeder.h index e244ea1a..64ce7052 100644 --- a/src/calendar/eds/EDSEventFeeder.h +++ b/src/calendar/eds/EDSEventFeeder.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "IDateEventFeeder.h" @@ -20,7 +21,8 @@ class EDSEventFeeder : public QObject, public IDateEventFeeder explicit EDSEventFeeder(QObject *parent = nullptr, const QString &source = "", const QDateTime ¤tTime = QDateTime(), const QDateTime &timeRangeStart = QDateTime(), - const QDateTime &timeRangeEnd = QDateTime()); + const QDateTime &timeRangeEnd = QDateTime(), const int retryCount = 0, + const int retryInterval = 0); ~EDSEventFeeder(); void init() override; @@ -28,12 +30,17 @@ class EDSEventFeeder : public QObject, public IDateEventFeeder void process(); +Q_SIGNALS: + void feederFailed(); + private: QDateTime createDateTimeFromTimeType(ICalTime *datetime); static void onEcalClientConnected(GObject *source_object, GAsyncResult *result, gpointer user_data); + void disconnectCalendarSignals(); + void connectViewCompleteSignal(ECalClientView *view); static void onViewComplete(ECalClientView *view, GError *error, gpointer user_data); @@ -55,6 +62,9 @@ class EDSEventFeeder : public QObject, public IDateEventFeeder void processEvents(QString clientName, QString clientUid, GSList *components); + void resetCalendar(); + void resetFeeder(); + QString m_source; QDateTime m_currentTime; QDateTime m_timeRangeStart; @@ -70,7 +80,12 @@ class EDSEventFeeder : public QObject, public IDateEventFeeder int m_sourceCount = 0; std::atomic m_clientCount = 0; + QFuture m_chainFuture; + QTimer m_sourceTimeout; QPromise *m_sourcePromise = nullptr; QFuture m_sourceFuture; QFutureWatcher *m_futureWatcher = nullptr; + + int m_retryCount = 0; + int m_retryInterval = 0; }; diff --git a/src/calendar/eds/EDSEventFeederFactory.cpp b/src/calendar/eds/EDSEventFeederFactory.cpp index 93e23ae5..8b3bacfe 100644 --- a/src/calendar/eds/EDSEventFeederFactory.cpp +++ b/src/calendar/eds/EDSEventFeederFactory.cpp @@ -29,10 +29,9 @@ IDateEventFeeder *EDSEventFeederFactory::createFeeder(const QString &settingsGro const QDateTime ¤tTime, const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval, DateEventFeederManager *feederManager) const { - ReadOnlyConfdSettings settings; - settings.beginGroup(settingsGroup); - - return new EDSEventFeeder(feederManager, name(), currentTime, timeRangeStart, timeRangeEnd); + return new EDSEventFeeder(feederManager, settingsGroup, currentTime, timeRangeStart, + timeRangeEnd, retryCount, retryInterval); } diff --git a/src/calendar/eds/EDSEventFeederFactory.h b/src/calendar/eds/EDSEventFeederFactory.h index 7dd77c24..bf983893 100644 --- a/src/calendar/eds/EDSEventFeederFactory.h +++ b/src/calendar/eds/EDSEventFeederFactory.h @@ -17,5 +17,6 @@ class EDSEventFeederFactory : public QObject, IDateEventFeederFactory QStringList configurations() const override; IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval, DateEventFeederManager *feederManager) const override; }; diff --git a/src/calendar/msgraph/MSGraphEventFeeder.cpp b/src/calendar/msgraph/MSGraphEventFeeder.cpp index 48e8eb1c..55d7c3c8 100644 --- a/src/calendar/msgraph/MSGraphEventFeeder.cpp +++ b/src/calendar/msgraph/MSGraphEventFeeder.cpp @@ -15,14 +15,16 @@ Q_LOGGING_CATEGORY(lcMSGraphEventFeeder, "gonnect.app.dateevents.feeder.MSGraphEventFeeder") -MSGraphEventFeeder::MSGraphEventFeeder(const QString &group, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, - DateEventFeederManager *parent) +MSGraphEventFeeder::MSGraphEventFeeder(const QString &source, const QDateTime &timeRangeStart, + const QDateTime &timeRangeEnd, const int retryCount, + const int retryInterval, DateEventFeederManager *parent) : QObject(parent), - m_group(group), - m_manager(parent), + m_source(source), m_timeRangeStart(timeRangeStart), - m_timeRangeEnd(timeRangeEnd) + m_timeRangeEnd(timeRangeEnd), + m_manager(parent), + m_retryCount(retryCount), + m_retryInterval(retryInterval) { m_networkAccessManager = new QNetworkAccessManager(this); m_calendarRefreshTimer.setSingleShot(true); @@ -41,6 +43,26 @@ QUrl MSGraphEventFeeder::networkCheckURL() const void MSGraphEventFeeder::init() { + connect( + this, &MSGraphEventFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + m_calendarRefreshTimer.stop(); + m_isFirstPage = false; + DateEventManager::instance().removeDateEventsBySource(m_source); + + if (m_retryCount > 0) { + m_retryCount--; + + qCWarning(lcMSGraphEventFeeder) + << "Failed to process MSGraph sources - trying later"; + + // Retry + QTimer::singleShot(m_retryInterval, this, [this]() { init(); }); + } + }, + Qt::SingleShotConnection); + if (MSOAuthManager::instance().isGranted()) { requestEvents(); } else { @@ -70,7 +92,7 @@ void MSGraphEventFeeder::refreshOrRequestLogin() } MSOAuthManager::instance().refreshOrRequestOauthLogin( - m_group, + m_source, tr("Login to your Microsoft account is required to access your contacts or calendars " "from GOnnect."), scopes); @@ -81,22 +103,25 @@ void MSGraphEventFeeder::eventsReceived(QNetworkReply *reply) if (!reply) { return; } - reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qCWarning(lcMSGraphEventFeeder) << "Error:" << reply->errorString(); + reply->deleteLater(); + + Q_EMIT feederFailed(); return; } auto jsonText = reply->readAll(); + reply->deleteLater(); auto doc = QJsonDocument::fromJson(jsonText); if (doc.isNull()) { return; } - const QString eventSource = m_group; - DateEventManager &manager = DateEventManager::instance(); if (m_isFirstPage) { - manager.removeDateEventsBySource(eventSource); + manager.removeDateEventsBySource(m_source); } auto value = doc.object()["value"]; @@ -129,7 +154,7 @@ void MSGraphEventFeeder::eventsReceived(QNetworkReply *reply) // is likely in HTML (see obj["body"].toObject()["contentType"].toString()) const auto bodyPreview = obj["bodyPreview"].toString(); - manager.addDateEvent(eventId, eventSource, start, end, subject, location, bodyPreview); + manager.addDateEvent(eventId, m_source, start, end, subject, location, bodyPreview); } m_isFirstPage = false; @@ -158,8 +183,11 @@ void MSGraphEventFeeder::requestEvents() m_calendarRefreshTimer.stop(); if (!MSOAuthManager::instance().isGranted()) { qCWarning(lcMSGraphEventFeeder) << "Cannot request events - not logged in"; + + Q_EMIT feederFailed(); return; } + if (!m_timeRangeStart.isValid() || !m_timeRangeEnd.isValid()) { qCWarning(lcMSGraphEventFeeder) << "Cannot request events - timerange not valid"; return; @@ -179,6 +207,8 @@ void MSGraphEventFeeder::requestEvents() auto *reply = m_networkAccessManager->get(request); if (!reply) { qCCritical(lcMSGraphEventFeeder) << "Failed to create contacts request"; + + Q_EMIT feederFailed(); return; } @@ -193,7 +223,7 @@ void MSGraphEventFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply::Netw if (!reply) { return; } - m_calendarRefreshTimer.stop(); + switch (code) { case QNetworkReply::NoError: // Should never happen here @@ -202,17 +232,19 @@ void MSGraphEventFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply::Netw // may indicate issues with the login. case QNetworkReply::AuthenticationRequiredError: // Corresponds to HTTP 401 case QNetworkReply::ContentAccessDenied: // Corresponds to HTTP 403 - qCCritical(lcMSGraphEventFeeder) - << "Network error for msgraph request:" << code << "require new login to account."; + qCCritical(lcMSGraphEventFeeder) << "Network error for msgraph request:" << code + << "requires a new login to account."; MSOAuthManager::instance() .clearRefreshToken(); // Make sure we don't try to refresh the token again - refreshOrRequestLogin(); break; default: qCCritical(lcMSGraphEventFeeder) << "Network error for msgraph request:" << code; break; } + reply->deleteLater(); + + Q_EMIT feederFailed(); } QDateTime MSGraphEventFeeder::parseDateTime(const QJsonValue &dateTimeContainer) diff --git a/src/calendar/msgraph/MSGraphEventFeeder.h b/src/calendar/msgraph/MSGraphEventFeeder.h index 7889d3b5..6a9ba1f6 100644 --- a/src/calendar/msgraph/MSGraphEventFeeder.h +++ b/src/calendar/msgraph/MSGraphEventFeeder.h @@ -15,14 +15,17 @@ class MSGraphEventFeeder : public QObject, public IDateEventFeeder Q_OBJECT public: - explicit MSGraphEventFeeder(const QString &group, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, - DateEventFeederManager *parent = nullptr); + explicit MSGraphEventFeeder(const QString &source, const QDateTime &timeRangeStart, + const QDateTime &timeRangeEnd, const int retryCount, + const int retryInterval, DateEventFeederManager *parent = nullptr); ~MSGraphEventFeeder(); void init() override; QUrl networkCheckURL() const override; +Q_SIGNALS: + void feederFailed(); + private Q_SLOTS: void requestEvents(); @@ -32,11 +35,14 @@ private Q_SLOTS: void errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code); static QDateTime parseDateTime(const QJsonValue &dateTimeContainer); - const QString m_group; + const QString m_source; const QDateTime m_timeRangeStart; const QDateTime m_timeRangeEnd; DateEventFeederManager *m_manager = nullptr; QNetworkAccessManager *m_networkAccessManager = nullptr; QTimer m_calendarRefreshTimer; bool m_isFirstPage = true; + + int m_retryCount = 0; + int m_retryInterval = 0; }; diff --git a/src/calendar/msgraph/MSGraphEventFeederFactory.cpp b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp index b325aa57..e36e940c 100644 --- a/src/calendar/msgraph/MSGraphEventFeederFactory.cpp +++ b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp @@ -27,8 +27,10 @@ QStringList MSGraphEventFeederFactory::configurations() const IDateEventFeeder *MSGraphEventFeederFactory::createFeeder( const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, - const QDateTime &timeRangeEnd, DateEventFeederManager *feederManager) const + const QDateTime &timeRangeEnd, const int retryCount, const int retryInterval, + DateEventFeederManager *feederManager) const { Q_UNUSED(currentTime); - return new MSGraphEventFeeder(settingsGroup, timeRangeStart, timeRangeEnd, feederManager); + return new MSGraphEventFeeder(settingsGroup, timeRangeStart, timeRangeEnd, retryCount, + retryInterval, feederManager); } diff --git a/src/calendar/msgraph/MSGraphEventFeederFactory.h b/src/calendar/msgraph/MSGraphEventFeederFactory.h index fb4c553a..cee634bd 100644 --- a/src/calendar/msgraph/MSGraphEventFeederFactory.h +++ b/src/calendar/msgraph/MSGraphEventFeederFactory.h @@ -18,5 +18,6 @@ class MSGraphEventFeederFactory : public QObject, IDateEventFeederFactory QStringList configurations() const override; IDateEventFeeder *createFeeder(const QString &settingsGroup, const QDateTime ¤tTime, const QDateTime &timeRangeStart, const QDateTime &timeRangeEnd, + const int retryCount, const int retryInterval, DateEventFeederManager *feederManager) const override; }; diff --git a/src/contacts/AddressBook.cpp b/src/contacts/AddressBook.cpp index f36f68df..51bb65bd 100644 --- a/src/contacts/AddressBook.cpp +++ b/src/contacts/AddressBook.cpp @@ -142,6 +142,30 @@ void AddressBook::removeContact(const QString &sourceUid) } } +void AddressBook::resetContacts() +{ + QMutexLocker lock(&m_feederMutex); + + qDeleteAll(m_contacts); + m_contacts.clear(); + m_contactsBySourceId.clear(); + Q_EMIT contactsCleared(); +} + +void AddressBook::removeContactsBySource(const QString &source) +{ + QMutexLocker lock(&m_feederMutex); + + for (auto contact : std::as_const(m_contacts)) { + if (contact->contactSourceInfo().configId == source) { + m_contacts.remove(contact->id()); + m_contactsBySourceId.remove(contact->sourceUid()); + } + } + + Q_EMIT contactsCleared(); +} + QHash AddressBook::contacts() const { return m_contacts; @@ -223,12 +247,3 @@ Contact *AddressBook::lookupBySourceUid(const QString &sourceUid) const { return m_contactsBySourceId.value(sourceUid, nullptr); } - -void AddressBook::clear() -{ - QMutexLocker lock(&m_feederMutex); - - qDeleteAll(m_contacts); - m_contacts.clear(); - Q_EMIT contactsCleared(); -} diff --git a/src/contacts/AddressBook.h b/src/contacts/AddressBook.h index 33634342..281f5fde 100644 --- a/src/contacts/AddressBook.h +++ b/src/contacts/AddressBook.h @@ -34,7 +34,14 @@ class AddressBook : public QObject void removeContact(const QString &sourceUid); + /// Delete all date events + void resetContacts(); + + /// Delete contacts of a specifc source + void removeContactsBySource(const QString &source); + QHash contacts() const; + void reserve(qsizetype size); QList search(const QString &searchString, bool includeBlocked = false) const; @@ -43,7 +50,6 @@ class AddressBook : public QObject Contact *lookupByContactId(const QString &contactId) const; Contact *lookupBySourceUid(const QString &sourceUid) const; - void clear(); QString hashifyCn(const QString &cn) const; const QList &sortedSourceInfos() const diff --git a/src/contacts/AddressBookManager.cpp b/src/contacts/AddressBookManager.cpp index 0d02512d..a8f409ce 100644 --- a/src/contacts/AddressBookManager.cpp +++ b/src/contacts/AddressBookManager.cpp @@ -52,6 +52,8 @@ QString AddressBookManager::hashForSettingsGroup(const QString &group) void AddressBookManager::initAddressBookConfigs() { ReadOnlyConfdSettings settings; + int retryCount = settings.value("generic/feederPluginRetryCount", 5).toInt(); + int retryInterval = settings.value("generic/feederPluginRetryInterval", 10000).toInt(); const QObjectList &staticPlugins = QPluginLoader::staticInstances(); @@ -63,7 +65,8 @@ void AddressBookManager::initAddressBookConfigs() << "active configurations for address book plugin" << addrPlugin->name(); for (auto &cfg : std::as_const(configs)) { - m_addressBookFeeders.insert(cfg, addrPlugin->createFeeder(cfg, this)); + m_addressBookFeeders.insert( + cfg, addrPlugin->createFeeder(cfg, retryCount, retryInterval, this)); m_addressBookConfigs.push_back(cfg); } } @@ -72,7 +75,7 @@ void AddressBookManager::initAddressBookConfigs() void AddressBookManager::reloadAddressBook() { - AddressBook::instance().clear(); + AddressBook::instance().resetContacts(); m_addressBookQueue = m_addressBookConfigs; processAddressBookQueue(); } diff --git a/src/contacts/AddressBookManager.h b/src/contacts/AddressBookManager.h index 5cacadd8..a3627d47 100644 --- a/src/contacts/AddressBookManager.h +++ b/src/contacts/AddressBookManager.h @@ -3,6 +3,7 @@ #include #include #include +#include class IAddressBookFeeder; diff --git a/src/contacts/Contact.cpp b/src/contacts/Contact.cpp index 43aa3cc6..0f452ad7 100644 --- a/src/contacts/Contact.cpp +++ b/src/contacts/Contact.cpp @@ -316,9 +316,9 @@ QDataStream &operator<<(QDataStream &out, const Contact &contact) const auto &contactSourceInfo = contact.contactSourceInfo(); out << contact.id() << contact.dn() << contact.sourceUid() << contact.name() - << contactSourceInfo.prio << contactSourceInfo.displayName << contact.company() - << contact.mail() << contact.lastModified() << contact.sipStatusSubscriptable() - << contact.phoneNumbers() << contact.blockInfo(); + << contactSourceInfo.prio << contactSourceInfo.displayName << contactSourceInfo.configId + << contact.company() << contact.mail() << contact.lastModified() + << contact.sipStatusSubscriptable() << contact.phoneNumbers() << contact.blockInfo(); return out; } @@ -330,6 +330,7 @@ QDataStream &operator>>(QDataStream &in, Contact &contact) QString name; unsigned prio; QString displayName; + QString configId; QString company; QString mail; QDateTime lastModified; @@ -339,8 +340,8 @@ QDataStream &operator>>(QDataStream &in, Contact &contact) in >> id >> dn >> sourceUid >> name >> prio >> displayName >> company >> mail >> lastModified >> sipStatusSubscriptable >> phoneNumbers >> blockInfo; - contact = Contact(id, dn, sourceUid, { prio, displayName }, name, company, mail, lastModified, - phoneNumbers, blockInfo); + contact = Contact(id, dn, sourceUid, { prio, displayName, configId }, name, company, mail, + lastModified, phoneNumbers, blockInfo); return in; } diff --git a/src/contacts/Contact.h b/src/contacts/Contact.h index 99d1560c..2619c992 100644 --- a/src/contacts/Contact.h +++ b/src/contacts/Contact.h @@ -38,6 +38,7 @@ class Contact : public QObject { unsigned prio = 0; QString displayName; + QString configId; bool operator==(const ContactSourceInfo &other) const; bool operator!=(const ContactSourceInfo &other) const; diff --git a/src/contacts/ContactSerializer.cpp b/src/contacts/ContactSerializer.cpp index 9ec29eb7..aa09f2b0 100644 --- a/src/contacts/ContactSerializer.cpp +++ b/src/contacts/ContactSerializer.cpp @@ -80,6 +80,7 @@ void ContactSerializer::loadAddressBook(AddressBook &addressBook) QString name; unsigned prio; QString displayName; + QString configId; QString company; QString mail; QDateTime lastModified; @@ -92,6 +93,7 @@ void ContactSerializer::loadAddressBook(AddressBook &addressBook) in >> sourceUid; in >> prio; in >> displayName; + in >> configId; in >> name; in >> company; in >> mail; @@ -101,8 +103,8 @@ void ContactSerializer::loadAddressBook(AddressBook &addressBook) in >> blockInfo; Contact *contact = - new Contact(id, dn, sourceUid, { prio, displayName }, name, company, mail, - lastModified, phoneNumbers, blockInfo, &addressBook); + new Contact(id, dn, sourceUid, { prio, displayName, configId }, name, company, + mail, lastModified, phoneNumbers, blockInfo, &addressBook); addressBook.addContact(contact); } diff --git a/src/contacts/IAddressBookFactory.h b/src/contacts/IAddressBookFactory.h index 4879d5f1..74c780e3 100644 --- a/src/contacts/IAddressBookFactory.h +++ b/src/contacts/IAddressBookFactory.h @@ -13,7 +13,8 @@ class IAddressBookFactory virtual QString name() const = 0; virtual QStringList configurations() const = 0; - virtual IAddressBookFeeder *createFeeder(const QString &id, + virtual IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const = 0; virtual ~IAddressBookFactory() = default; diff --git a/src/contacts/akonadi/AkonadiAddressBookFactory.cpp b/src/contacts/akonadi/AkonadiAddressBookFactory.cpp index e86fa3d8..1688598e 100644 --- a/src/contacts/akonadi/AkonadiAddressBookFactory.cpp +++ b/src/contacts/akonadi/AkonadiAddressBookFactory.cpp @@ -23,7 +23,9 @@ QStringList AkonadiAddressBookFactory::configurations() const } IAddressBookFeeder *AkonadiAddressBookFactory::createFeeder(const QString &group, + const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new AkonadiAddressBookFeeder(group, parent); + return new AkonadiAddressBookFeeder(group, retryCount, retryInterval, parent); } diff --git a/src/contacts/akonadi/AkonadiAddressBookFactory.h b/src/contacts/akonadi/AkonadiAddressBookFactory.h index 69a948ea..d72ef15c 100644 --- a/src/contacts/akonadi/AkonadiAddressBookFactory.h +++ b/src/contacts/akonadi/AkonadiAddressBookFactory.h @@ -18,6 +18,7 @@ class AkonadiAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp b/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp index 4e124206..04a54ba2 100644 --- a/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp +++ b/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp @@ -14,12 +14,17 @@ Q_LOGGING_CATEGORY(lcAkonadiAddressBookFeeder, "gonnect.app.feeder.AkonadiAddressBookFeeder") -AkonadiAddressBookFeeder::AkonadiAddressBookFeeder(const QString &group, AddressBookManager *parent) +AkonadiAddressBookFeeder::AkonadiAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent) : QObject(parent), m_group(group), m_session(new Akonadi::Session("GOnnect::ContactsSession")), m_monitor(new Akonadi::Monitor(parent)) { + Q_UNUSED(retryCount) + Q_UNUSED(retryInterval) + ReadOnlyConfdSettings settings; settings.beginGroup(m_group); @@ -93,7 +98,7 @@ AkonadiAddressBookFeeder::AkonadiAddressBookFeeder(const QString &group, Address Contact *contact = addressbook.addContact( addressee.assembledName() + addressee.organization(), addressee.uid(), - { m_priority, m_displayName }, addressee.assembledName(), + { m_priority, m_displayName, m_group }, addressee.assembledName(), addressee.organization(), email, changed, phoneNumbers); if (!addressee.photo().isEmpty()) { @@ -239,8 +244,8 @@ void AkonadiAddressBookFeeder::processSearchResult(KJob *job) Contact *contact = addressbook.addContact( addressee.assembledName() + addressee.organization(), addressee.uid(), - { m_priority, m_displayName }, addressee.assembledName(), addressee.organization(), - email, changed, phoneNumbers); + { m_priority, m_displayName, m_group }, addressee.assembledName(), + addressee.organization(), email, changed, phoneNumbers); if (!addressee.photo().isEmpty()) { AvatarManager::instance().addExternalImage(contact->id(), addressee.photo().rawData(), diff --git a/src/contacts/akonadi/AkonadiAddressBookFeeder.h b/src/contacts/akonadi/AkonadiAddressBookFeeder.h index 276b5c7e..37259997 100644 --- a/src/contacts/akonadi/AkonadiAddressBookFeeder.h +++ b/src/contacts/akonadi/AkonadiAddressBookFeeder.h @@ -12,7 +12,9 @@ class AkonadiAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit AkonadiAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit AkonadiAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent = nullptr); void process() override; ~AkonadiAddressBookFeeder(); diff --git a/src/contacts/carddav/CardDAVAddressBookFactory.cpp b/src/contacts/carddav/CardDAVAddressBookFactory.cpp index 415669c6..d7711a90 100644 --- a/src/contacts/carddav/CardDAVAddressBookFactory.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFactory.cpp @@ -26,8 +26,9 @@ QStringList CardDAVAddressBookFactory::configurations() const return res; } -IAddressBookFeeder *CardDAVAddressBookFactory::createFeeder(const QString &id, +IAddressBookFeeder *CardDAVAddressBookFactory::createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new CardDAVAddressBookFeeder(id, parent); + return new CardDAVAddressBookFeeder(id, retryCount, retryInterval, parent); } diff --git a/src/contacts/carddav/CardDAVAddressBookFactory.h b/src/contacts/carddav/CardDAVAddressBookFactory.h index 4d8d2feb..163dbe72 100644 --- a/src/contacts/carddav/CardDAVAddressBookFactory.h +++ b/src/contacts/carddav/CardDAVAddressBookFactory.h @@ -18,6 +18,7 @@ class CardDAVAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index 505357a3..709fc3b2 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -19,8 +19,10 @@ Q_LOGGING_CATEGORY(lcCardDAVAddressBookFeeder, "gonnect.app.feeder.CardDAVAddres using namespace std::chrono_literals; -CardDAVAddressBookFeeder::CardDAVAddressBookFeeder(const QString &group, AddressBookManager *parent) - : QObject(parent), m_group(group) +CardDAVAddressBookFeeder::CardDAVAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent) + : QObject(parent), m_group(group), m_retryCount(retryCount), m_retryInterval(retryInterval) { m_manager = qobject_cast(parent); } @@ -37,16 +39,65 @@ void CardDAVAddressBookFeeder::init() connect(&m_webdavParser, &QWebdavDirParser::finished, this, &CardDAVAddressBookFeeder::onParserFinished); + connect(&m_webdavParser, &QWebdavDirParser::errorChanged, this, &CardDAVAddressBookFeeder::onError); connect(&m_webdav, &QWebdav::errorChanged, this, &CardDAVAddressBookFeeder::onError); connect(&m_webdav, &QWebdav::authenticationRequired, this, [this]() { - // Previous run failed due to auth, we'll prompt the user again - feedAddressBook(true); + m_pendingAuth = true; + checkErrorStatus(); }); } +void CardDAVAddressBookFeeder::checkErrorStatus() +{ + QMetaObject::invokeMethod( + this, + [this]() { + // Prepare feeder for re-run + resetContacts(); + if (m_cacheWriteTimer.isActive()) { + m_cacheWriteTimer.stop(); + } + + if (m_pendingAuth && m_pendingError) { + // Previous run failed due to auth, we'll prompt the user again immediately + qCWarning(lcCardDAVAddressBookFeeder) + << "Failed to process CardDAV sources - invalid password"; + + feedAddressBook(true); + } else if (m_pendingError) { + // Some other error has occurred, wait and try again + if (m_retryCount > 0) { + m_retryCount--; + + qCWarning(lcCardDAVAddressBookFeeder) + << "Failed to process CardDAV sources - trying later"; + + QTimer::singleShot(m_retryInterval, this, [this]() { feedAddressBook(); }); + } + } + + m_pendingAuth = false; + m_pendingError = false; + }, + Qt::QueuedConnection); +} + +void CardDAVAddressBookFeeder::resetContacts() +{ + AddressBook::instance().removeContactsBySource(m_group); +} + +void CardDAVAddressBookFeeder::onError(QString error) +{ + qCCritical(lcCardDAVAddressBookFeeder) << "Error:" << error; + + m_pendingError = true; + checkErrorStatus(); +} + void CardDAVAddressBookFeeder::feedAddressBook(bool authFailed) { m_manager->acquireSecret(authFailed, m_group, [this](const QString &password) { @@ -130,8 +181,8 @@ void CardDAVAddressBookFeeder::processVcard(QByteArray data, const QString &uuid if (!uuid.isEmpty() && !name.isEmpty() && !phoneNumbers.isEmpty()) { Contact *contact = AddressBook::instance().addContact( - uuid, remoteUid, { m_priority, m_displayName }, name, org, email, modifiedDate, - phoneNumbers, m_blockInfo); + uuid, remoteUid, { m_priority, m_displayName, m_group }, name, org, email, + modifiedDate, phoneNumbers, m_blockInfo); m_cachedContacts.insert(uuid, contact); processPhotoProperty(contact->id(), photoData, modifiedDate); @@ -177,7 +228,7 @@ void CardDAVAddressBookFeeder::loadCachedData(const size_t hash) if (magic != CARDDAV_MAGIC || version != CARDDAV_VERSION) { qCInfo(lcCardDAVAddressBookFeeder) << "CardDAV cache file at" << filePath - << "in invalid and will therefore be removed."; + << "is invalid and will therefore be removed."; cacheFile.remove(); return; } @@ -220,11 +271,6 @@ void CardDAVAddressBookFeeder::processPhotoProperty(const QString &id, const QBy } } -void CardDAVAddressBookFeeder::onError(QString error) const -{ - qCCritical(lcCardDAVAddressBookFeeder) << "Error:" << error; -} - void CardDAVAddressBookFeeder::onParserFinished() { const auto list = m_webdavParser.getList(); @@ -234,9 +280,9 @@ void CardDAVAddressBookFeeder::onParserFinished() if (m_ignoredIds.contains(cacheId) && m_ignoredIds.value(cacheId) >= modifiedDate) { continue; - } else if (Contact *cashedContact = m_cachedContacts.value(cacheId, nullptr)) { - if (cashedContact->lastModified() >= modifiedDate) { - AddressBook::instance().addContact(cashedContact); + } else if (Contact *cachedContact = m_cachedContacts.value(cacheId, nullptr)) { + if (cachedContact->lastModified() >= modifiedDate) { + AddressBook::instance().addContact(cachedContact); continue; } } diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.h b/src/contacts/carddav/CardDAVAddressBookFeeder.h index dfd75e82..99531d93 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.h +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.h @@ -17,13 +17,17 @@ class CardDAVAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit CardDAVAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit CardDAVAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent = nullptr); + + ~CardDAVAddressBookFeeder() = default; void process() override; QUrl networkCheckURL() const override; private Q_SLOTS: - void onError(QString error) const; + void onError(QString error); void onParserFinished(); void flushCacheImpl(); @@ -37,6 +41,9 @@ private Q_SLOTS: void processPhotoProperty(const QString &id, const QByteArray &data, const QDateTime &modifiedDate) const; + void checkErrorStatus(); + void resetContacts(); + AddressBookManager *m_manager = nullptr; size_t m_settingsHash = 0; @@ -50,4 +57,10 @@ private Q_SLOTS: BlockInfo m_blockInfo; CardDAVAddressBookFeederConfig m_config; + + int m_retryCount = 0; + int m_retryInterval = 0; + + bool m_pendingError = false; + bool m_pendingAuth = false; }; diff --git a/src/contacts/csv/CSVAddressBookFactory.cpp b/src/contacts/csv/CSVAddressBookFactory.cpp index ab357c6b..66d708ea 100644 --- a/src/contacts/csv/CSVAddressBookFactory.cpp +++ b/src/contacts/csv/CSVAddressBookFactory.cpp @@ -26,8 +26,9 @@ QStringList CSVAddressBookFactory::configurations() const return res; } -IAddressBookFeeder *CSVAddressBookFactory::createFeeder(const QString &id, +IAddressBookFeeder *CSVAddressBookFactory::createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new CsvFileAddressBookFeeder(id, parent); + return new CsvFileAddressBookFeeder(id, retryCount, retryInterval, parent); } diff --git a/src/contacts/csv/CSVAddressBookFactory.h b/src/contacts/csv/CSVAddressBookFactory.h index 4a4bb347..63b97b58 100644 --- a/src/contacts/csv/CSVAddressBookFactory.h +++ b/src/contacts/csv/CSVAddressBookFactory.h @@ -17,6 +17,7 @@ class CSVAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/csv/CSVFileAddressBookFeeder.cpp b/src/contacts/csv/CSVFileAddressBookFeeder.cpp index 4a191a4d..c1c99787 100644 --- a/src/contacts/csv/CSVFileAddressBookFeeder.cpp +++ b/src/contacts/csv/CSVFileAddressBookFeeder.cpp @@ -10,9 +10,13 @@ Q_LOGGING_CATEGORY(lcCsvAddressBookFeeder, "gonnect.app.feeder.CsvAddressBookFeeder") -CsvFileAddressBookFeeder::CsvFileAddressBookFeeder(const QString &group, AddressBookManager *parent) +CsvFileAddressBookFeeder::CsvFileAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent) : QObject(parent), m_group(group) { + Q_UNUSED(retryCount) + Q_UNUSED(retryInterval) } void CsvFileAddressBookFeeder::process() @@ -88,9 +92,9 @@ void CsvFileAddressBookFeeder::feedAddressBook() { Contact::NumberType::Home, splitted.at(6), splitted.at(7) == "true" }); } - addressbook.addContact(splitted.at(0) + splitted.at(1), "", { m_priority, m_displayName }, - splitted.at(0), splitted.at(1), "", QDateTime(), phoneNumbers, - m_blockInfo); + addressbook.addContact(splitted.at(0) + splitted.at(1), "", + { m_priority, m_displayName, m_group }, splitted.at(0), + splitted.at(1), "", QDateTime(), phoneNumbers, m_blockInfo); ++contactCount; } diff --git a/src/contacts/csv/CSVFileAddressBookFeeder.h b/src/contacts/csv/CSVFileAddressBookFeeder.h index fba4a153..374f16f4 100644 --- a/src/contacts/csv/CSVFileAddressBookFeeder.h +++ b/src/contacts/csv/CSVFileAddressBookFeeder.h @@ -11,7 +11,9 @@ class CsvFileAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit CsvFileAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit CsvFileAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent = nullptr); void process() override; diff --git a/src/contacts/eds/EDSAddressBookFactory.cpp b/src/contacts/eds/EDSAddressBookFactory.cpp index 22864979..159ee06a 100644 --- a/src/contacts/eds/EDSAddressBookFactory.cpp +++ b/src/contacts/eds/EDSAddressBookFactory.cpp @@ -21,8 +21,9 @@ QStringList EDSAddressBookFactory::configurations() const return { groupName }; } -IAddressBookFeeder *EDSAddressBookFactory::createFeeder(const QString &id, +IAddressBookFeeder *EDSAddressBookFactory::createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new EDSAddressBookFeeder(id, parent); + return new EDSAddressBookFeeder(id, retryCount, retryInterval, parent); } diff --git a/src/contacts/eds/EDSAddressBookFactory.h b/src/contacts/eds/EDSAddressBookFactory.h index 65362587..ad663585 100644 --- a/src/contacts/eds/EDSAddressBookFactory.h +++ b/src/contacts/eds/EDSAddressBookFactory.h @@ -20,6 +20,7 @@ class EDSAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index 3461f887..f562e6d2 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -13,41 +13,38 @@ using namespace std::chrono_literals; Q_LOGGING_CATEGORY(lcEDSAddressBookFeeder, "gonnect.app.feeder.EDSAddressBookFeeder") -EDSAddressBookFeeder::EDSAddressBookFeeder(const QString &group, AddressBookManager *parent) - : QObject(parent), m_group(group) +EDSAddressBookFeeder::EDSAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, AddressBookManager *parent) + : QObject(parent), m_group(group), m_retryCount(retryCount), m_retryInterval(retryInterval) { } EDSAddressBookFeeder::~EDSAddressBookFeeder() { - g_clear_object(&m_registry); - if (m_sources) { - g_list_free_full(m_sources, g_object_unref); - } - g_clear_pointer(&m_searchExpr, g_free); - g_clear_object(&m_cancellable); + resetFeeder(); +} - for (auto client : std::as_const(m_clients)) { - g_clear_object(&client); - } +void EDSAddressBookFeeder::init() +{ + connect( + this, &EDSAddressBookFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + resetContacts(); + resetFeeder(); - for (auto clientView : std::as_const(m_clientViews)) { - g_clear_object(&clientView); - } + if (m_retryCount > 0) { + m_retryCount--; - if (m_sourcePromise) { - delete m_sourcePromise; - m_sourcePromise = nullptr; - } + qCWarning(lcEDSAddressBookFeeder) + << "Failed to process EDS sources - trying later"; - if (m_futureWatcher) { - m_futureWatcher->deleteLater(); - m_futureWatcher = nullptr; - } -} + // Retry + QTimer::singleShot(m_retryInterval, this, [this]() { process(); }); + } + }, + Qt::SingleShotConnection); -void EDSAddressBookFeeder::init() -{ m_cancellable = g_cancellable_new(); GError *error = NULL; @@ -58,6 +55,8 @@ void EDSAddressBookFeeder::init() if (error) { qCDebug(lcEDSAddressBookFeeder) << "Can't create registry:" << error->message; g_clear_error(&error); + + Q_EMIT feederFailed(); } return; } @@ -93,24 +92,85 @@ void EDSAddressBookFeeder::init() m_sourcePromise->start(); - QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished).then(this, [this]() { - if (m_sourceFuture.isFinished()) { - feedAddressBook(); + m_chainFuture = QtFuture::connect(m_futureWatcher, &QFutureWatcher::finished) + .then(this, [this]() { + if (!m_chainFuture.isCanceled() && m_sourceFuture.isFinished()) { + feedAddressBook(); + } + }); + + m_sourceTimeout.setSingleShot(true); + m_sourceTimeout.setInterval(5s); + connect(&m_sourceTimeout, &QTimer::timeout, this, [this]() { + if (m_futureWatcher && !m_futureWatcher->isFinished()) { + Q_EMIT feederFailed(); } }); + m_sourceTimeout.start(); - QTimer::singleShot(5s, this, [this]() { - if (!m_futureWatcher->isFinished()) { - qCDebug(lcEDSAddressBookFeeder) << "Failed to process EDS sources"; + m_futureWatcher->setFuture(m_sourceFuture); +} - g_cancellable_cancel(m_cancellable); +void EDSAddressBookFeeder::resetContacts() +{ + AddressBook::instance().removeContactsBySource(m_group); +} - m_sourceFuture.cancel(); - m_futureWatcher->cancel(); - } - }); +void EDSAddressBookFeeder::resetFeeder() +{ + m_sourceCount = 0; + m_clientCount = 0; - m_futureWatcher->setFuture(m_sourceFuture); + if (m_sourceTimeout.isActive()) { + m_sourceTimeout.stop(); + } + + g_clear_object(&m_registry); + if (m_sources) { + g_clear_list(&m_sources, g_object_unref); + } + g_clear_pointer(&m_searchExpr, g_free); + + // Cancel all active EDS async methods + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + for (auto client : std::as_const(m_clients)) { + g_clear_object(&client); + } + m_clients.clear(); + + // Disconnect all EDS signal handlers + disconnectContactSignals(); + for (auto clientView : std::as_const(m_clientViews)) { + g_clear_object(&clientView); + } + m_clientViews.clear(); + + // Future/Promise + if (m_chainFuture.isRunning()) { + m_chainFuture.cancel(); + } + m_chainFuture = QFuture(); + + if (m_futureWatcher) { + m_futureWatcher->cancel(); + + m_futureWatcher->deleteLater(); + m_futureWatcher = nullptr; + } + + if (m_sourceFuture.isRunning()) { + m_sourceFuture.cancel(); + } + m_sourceFuture = QFuture(); + + if (m_sourcePromise) { + m_sourcePromise->finish(); + + delete m_sourcePromise; + m_sourcePromise = nullptr; + } } void EDSAddressBookFeeder::process() @@ -194,15 +254,19 @@ void EDSAddressBookFeeder::onViewComplete(EBookClientView *view, GError *error, guint signalId = g_signal_lookup("complete", G_OBJECT_TYPE(view)); g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_ID, signalId, 0, NULL, NULL, NULL); + EDSAddressBookFeeder *feeder = static_cast(user_data); + if (error) { qCCritical(lcEDSAddressBookFeeder) << "Failed to wait for view completion, unable to subscribe " "to live contact updates:" << error->message; + if (feeder) { + Q_EMIT feeder->feederFailed(); + } return; } - EDSAddressBookFeeder *feeder = static_cast(user_data); if (feeder) { feeder->connectContactSignals(view); } @@ -217,6 +281,14 @@ void EDSAddressBookFeeder::connectContactSignals(EBookClientView *view) g_signal_connect(view, "objects-removed", G_CALLBACK(onContactsRemoved), this); } +void EDSAddressBookFeeder::disconnectContactSignals() +{ + for (auto view : std::as_const(m_clientViews)) { + // Match all signals with the same gpointer user_data + g_signal_handlers_disconnect_matched(view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this); + } +} + void EDSAddressBookFeeder::onContactsAdded(EBookClientView *view, GSList *contacts, gpointer user_data) { @@ -276,7 +348,7 @@ void EDSAddressBookFeeder::processContactsAdded(GSList *contacts) Contact *contact = addressbook.addContact( getField(eContact, E_CONTACT_FULL_NAME) + getField(eContact, E_CONTACT_ORG), - getField(eContact, E_CONTACT_UID), { m_priority, m_displayName }, + getField(eContact, E_CONTACT_UID), { m_priority, m_displayName, m_group }, getField(eContact, E_CONTACT_FULL_NAME), getField(eContact, E_CONTACT_ORG), getField(eContact, E_CONTACT_EMAIL_1), changed, phoneNumbers, m_blockInfo); @@ -353,6 +425,7 @@ void EDSAddressBookFeeder::onEbookClientConnected(GObject *source_object, GAsync qCDebug(lcEDSAddressBookFeeder) << "Can't retrieve finished client connection:" << error->message; g_clear_error(&error); + Q_EMIT feeder->feederFailed(); return; } @@ -375,6 +448,8 @@ void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *r qCCritical(lcEDSAddressBookFeeder) << "Can't retrieve finished view:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); } return; } @@ -384,6 +459,7 @@ void EDSAddressBookFeeder::onViewCreated(GObject *source_object, GAsyncResult *r if (error) { qCCritical(lcEDSAddressBookFeeder) << "Can't start view:" << error->message; g_clear_error(&error); + Q_EMIT feeder->feederFailed(); return; } feeder->m_clientViews.append(view); @@ -409,6 +485,8 @@ void EDSAddressBookFeeder::onClientContactsRequested(GObject *source_object, GAs if (error) { qCCritical(lcEDSAddressBookFeeder) << "Can't retrieve contacts:" << error->message; g_clear_error(&error); + + Q_EMIT feeder->feederFailed(); } return; } @@ -450,7 +528,7 @@ void EDSAddressBookFeeder::processContacts(QString clientInfo, GSList *contacts) Contact *contact = addressbook.addContact( getField(eContact, E_CONTACT_FULL_NAME) + getField(eContact, E_CONTACT_ORG), - getField(eContact, E_CONTACT_UID), { m_priority, m_displayName }, + getField(eContact, E_CONTACT_UID), { m_priority, m_displayName, m_group }, getField(eContact, E_CONTACT_FULL_NAME), getField(eContact, E_CONTACT_ORG), getField(eContact, E_CONTACT_EMAIL_1), changed, phoneNumbers, m_blockInfo); @@ -460,8 +538,7 @@ void EDSAddressBookFeeder::processContacts(QString clientInfo, GSList *contacts) } } - g_slist_free_full(contacts, g_object_unref); - contacts = NULL; + g_clear_slist(&contacts, g_object_unref); qCInfo(lcEDSAddressBookFeeder) << "Loaded" << contactCount << "contact(s) of source" << clientInfo; diff --git a/src/contacts/eds/EDSAddressBookFeeder.h b/src/contacts/eds/EDSAddressBookFeeder.h index c216c142..7e2afc2b 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.h +++ b/src/contacts/eds/EDSAddressBookFeeder.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "IAddressBookFeeder.h" #include "BlockInfo.h" @@ -19,11 +20,15 @@ class EDSAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit EDSAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit EDSAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr); ~EDSAddressBookFeeder(); void process() override; +Q_SIGNALS: + void feederFailed(); + private: QString getField(EContact *contact, EContactField id); QString getFieldMerge(EContact *contact, EContactField pId, EContactField sId); @@ -36,6 +41,8 @@ class EDSAddressBookFeeder : public QObject, public IAddressBookFeeder static void onEbookClientConnected(GObject *source_object, GAsyncResult *result, gpointer user_data); + void disconnectContactSignals(); + void connectViewCompleteSignal(EBookClientView *view); static void onViewComplete(EBookClientView *view, GError *error, gpointer user_data); @@ -57,6 +64,9 @@ class EDSAddressBookFeeder : public QObject, public IAddressBookFeeder void processContacts(QString clientInfo, GSList *contacts); + void resetContacts(); + void resetFeeder(); + QString m_group; BlockInfo m_blockInfo; @@ -70,7 +80,12 @@ class EDSAddressBookFeeder : public QObject, public IAddressBookFeeder int m_sourceCount = 0; std::atomic m_clientCount = 0; + QFuture m_chainFuture; + QTimer m_sourceTimeout; QPromise *m_sourcePromise = nullptr; QFuture m_sourceFuture; QFutureWatcher *m_futureWatcher = nullptr; + + int m_retryCount = 0; + int m_retryInterval = 0; }; diff --git a/src/contacts/ldap/LDAPAddressBookFactory.cpp b/src/contacts/ldap/LDAPAddressBookFactory.cpp index df067ccb..c4dfdd38 100644 --- a/src/contacts/ldap/LDAPAddressBookFactory.cpp +++ b/src/contacts/ldap/LDAPAddressBookFactory.cpp @@ -26,8 +26,9 @@ QStringList LDAPAddressBookFactory::configurations() const return res; } -IAddressBookFeeder *LDAPAddressBookFactory::createFeeder(const QString &id, +IAddressBookFeeder *LDAPAddressBookFactory::createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new LDAPAddressBookFeeder(id, parent); + return new LDAPAddressBookFeeder(id, retryCount, retryInterval, parent); } diff --git a/src/contacts/ldap/LDAPAddressBookFactory.h b/src/contacts/ldap/LDAPAddressBookFactory.h index 634a8ae8..4e51d2e4 100644 --- a/src/contacts/ldap/LDAPAddressBookFactory.h +++ b/src/contacts/ldap/LDAPAddressBookFactory.h @@ -17,6 +17,7 @@ class LDAPAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.cpp b/src/contacts/ldap/LDAPAddressBookFeeder.cpp index 7eef2781..b163d83d 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.cpp +++ b/src/contacts/ldap/LDAPAddressBookFeeder.cpp @@ -17,8 +17,9 @@ Q_LOGGING_CATEGORY(lcLDAPAddressBookFeeder, "gonnect.app.feeder.LDAPAddressBookFeeder") -LDAPAddressBookFeeder::LDAPAddressBookFeeder(const QString &group, AddressBookManager *parent) - : QObject(parent), m_group(group) +LDAPAddressBookFeeder::LDAPAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, AddressBookManager *parent) + : QObject(parent), m_group(group), m_retryCount(retryCount), m_retryInterval(retryInterval) { m_manager = qobject_cast(parent); @@ -34,22 +35,6 @@ LDAPAddressBookFeeder::LDAPAddressBookFeeder(const QString &group, AddressBookMa connect(this, &LDAPAddressBookFeeder::newExternalImageAdded, this, [](const QString &id, const QByteArray &data, const QDateTime &modified, QPrivateSignal) { AvatarManager::instance().addExternalImage(id, data, modified); }); - - connect(this, &LDAPAddressBookFeeder::invalidCredentials, this, [this]() { - // TODO: Check for bind method again? - m_manager->acquireSecret(true, m_group, [this](const QString &password) { - m_ldapConfig.bindPassword = password; - - feedAddressBook(); - - const auto dirtyContacts = AvatarManager::instance().initialLoad(); - if (dirtyContacts.isEmpty()) { - loadAllAvatars(m_ldapConfig); - } else { - loadAvatars(dirtyContacts); - } - }); - }); } void LDAPAddressBookFeeder::init(const LDAPInitializer::Config &ldapConfig, @@ -61,8 +46,50 @@ void LDAPAddressBookFeeder::init(const LDAPInitializer::Config &ldapConfig, m_sipStatusSubscriptableAttributes = sipStatusSubscriptableAttributes; } +void LDAPAddressBookFeeder::resetFeeder() +{ + m_ldapConfig = {}; + + if (m_ldap) { + LDAPInitializer::freeLDAPHandle(m_ldap); + m_ldap = nullptr; + } + + m_ldapSearchMessageId = -1; + m_baseNumber = ""; + m_sipStatusSubscriptableAttributes.clear(); + m_blockInfo = {}; +} + void LDAPAddressBookFeeder::process() { + connect( + this, &LDAPAddressBookFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + resetFeeder(); + AddressBook::instance().removeContactsBySource(m_group); + + if (m_authFailed) { + // Previous run failed due to auth, we'll prompt the user again immediately + qCCritical(lcLDAPAddressBookFeeder) + << "Failed to process LDAP sources - invalid password"; + + process(); + } else { + // Some other error has occurred, wait and try again + if (m_retryCount > 0) { + m_retryCount--; + + qCCritical(lcLDAPAddressBookFeeder) + << "Failed to process LDAP sources - trying later"; + + QTimer::singleShot(m_retryInterval, this, [this]() { process(); }); + } + } + }, + Qt::SingleShotConnection); + ReadOnlyConfdSettings settings; settings.beginGroup(m_group); @@ -81,8 +108,11 @@ void LDAPAddressBookFeeder::process() const auto bindMethodStr = settings.value("bindMethod", "none").toString(); if (bindMethodStr == "simple" || bindMethodStr == "gssapi") { - m_manager->acquireSecret(false, m_group, - [this](const QString &password) { processImpl(password); }); + m_manager->acquireSecret(m_authFailed, m_group, [this](const QString &password) { + m_authFailed = false; + + processImpl(password); + }); } else { // "none" processImpl(""); } @@ -110,6 +140,7 @@ void LDAPAddressBookFeeder::processImpl(const QString &password) if (s_bindMethods.contains(bindMethodStr)) { bindMethod = s_bindMethods.value(bindMethodStr); } else { + // INFO: This would point towards a config issue - not going to retry automatically qCCritical(lcLDAPAddressBookFeeder).nospace() << "Unknown LDAP bind method '" << bindMethodStr << "' - initialization of LDAP account will be aborted."; @@ -180,8 +211,9 @@ void LDAPAddressBookFeeder::feedAddressBook() clearCStringlist(attrs); if (result == LDAP_INVALID_CREDENTIALS) { - Q_EMIT invalidCredentials(); + m_authFailed = true; } + Q_EMIT feederFailed(); return; } @@ -201,6 +233,8 @@ void LDAPAddressBookFeeder::feedAddressBook() qCCritical(lcLDAPAddressBookFeeder) << "Error on search request: " << ldap_err2string(result); ErrorBus::instance().addError(tr("LDAP error: %1").arg(ldap_err2string(result))); + + Q_EMIT feederFailed(); return; } } @@ -265,8 +299,9 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo m_isProcessing = false; if (result == LDAP_INVALID_CREDENTIALS) { - Q_EMIT invalidCredentials(); + m_authFailed = true; } + Q_EMIT feederFailed(); return; } @@ -280,7 +315,9 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo qCCritical(lcLDAPAddressBookFeeder) << "Error on search request: " << ldap_err2string(result); ErrorBus::instance().addError(tr("LDAP error: %1").arg(ldap_err2string(result))); + m_isProcessing = false; + Q_EMIT feederFailed(); return; } @@ -354,7 +391,9 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo << "LDAP parse error:" << ldap_err2string(parseResultCode); ErrorBus::instance().addError( tr("Parse error: %1").arg(ldap_err2string(parseResultCode))); + m_isProcessing = false; + Q_EMIT feederFailed(); return; } break; @@ -362,7 +401,9 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo default: qCCritical(lcLDAPAddressBookFeeder) << "Unknown message type:" << msgType; + m_isProcessing = false; + Q_EMIT feederFailed(); return; } } @@ -493,7 +534,8 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) } } - Q_EMIT newContactReady(dn, sourceUid, { m_priority, m_displayName }, cn, company, mail, + Q_EMIT newContactReady(dn, sourceUid, { m_priority, m_displayName, m_group }, cn, + company, mail, QDateTime::fromString(modifyTimestamp, "yyyyMMddhhmmsst"), phoneNumbers, QPrivateSignal()); @@ -513,6 +555,8 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) << "LDAP parse error:" << ldap_err2string(parseResultCode); ErrorBus::instance().addError( tr("Parse error: %1").arg(ldap_err2string(parseResultCode))); + + Q_EMIT feederFailed(); return; } break; @@ -520,6 +564,8 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) default: qCCritical(lcLDAPAddressBookFeeder) << "Unknown message type:" << msgType; + + Q_EMIT feederFailed(); return; } } diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.h b/src/contacts/ldap/LDAPAddressBookFeeder.h index a90be2e3..2280476d 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.h +++ b/src/contacts/ldap/LDAPAddressBookFeeder.h @@ -14,7 +14,8 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit LDAPAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit LDAPAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr); void process() override; QUrl networkCheckURL() const override; @@ -30,7 +31,7 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder void newExternalImageAdded(const QString &id, const QByteArray &data, const QDateTime &modified, QPrivateSignal); - void invalidCredentials(); + void feederFailed(); private: void clearCStringlist(char **attrs) const; @@ -43,6 +44,8 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder void processResult(LDAPMessage *ldapMessage); void startContactQuery(); + void resetFeeder(); + LDAPInitializer::Config m_ldapConfig; AddressBookManager *m_manager = nullptr; @@ -53,4 +56,8 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder QString m_baseNumber; QStringList m_sipStatusSubscriptableAttributes; BlockInfo m_blockInfo; + + bool m_authFailed = false; + int m_retryCount = 0; + int m_retryInterval = 0; }; diff --git a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp index 70bfcf88..ec8b43df 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp +++ b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp @@ -22,8 +22,9 @@ QStringList MSGraphAddressBookFactory::configurations() const return res; } -IAddressBookFeeder *MSGraphAddressBookFactory::createFeeder(const QString &id, +IAddressBookFeeder *MSGraphAddressBookFactory::createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent) const { - return new MSGraphAddressBookFeeder(id, parent); + return new MSGraphAddressBookFeeder(id, retryCount, retryInterval, parent); } diff --git a/src/contacts/msgraph/MSGraphAddressBookFactory.h b/src/contacts/msgraph/MSGraphAddressBookFactory.h index 882685a1..d296b0a8 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFactory.h +++ b/src/contacts/msgraph/MSGraphAddressBookFactory.h @@ -18,6 +18,7 @@ class MSGraphAddressBookFactory : public QObject, IAddressBookFactory QStringList configurations() const override; - IAddressBookFeeder *createFeeder(const QString &id, + IAddressBookFeeder *createFeeder(const QString &id, const int retryCount, + const int retryInterval, AddressBookManager *parent = nullptr) const override; }; diff --git a/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp b/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp index 9a73fe67..9984f637 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp +++ b/src/contacts/msgraph/MSGraphAddressBookFeeder.cpp @@ -14,8 +14,15 @@ Q_LOGGING_CATEGORY(msGraphAddressBookFeeder, "gonnect.app.feeder.MSGraphAddressBookFeeder") -MSGraphAddressBookFeeder::MSGraphAddressBookFeeder(const QString &group, AddressBookManager *parent) - : QObject(parent), m_manager(parent), m_group(group) +MSGraphAddressBookFeeder::MSGraphAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent) + : QObject(parent), + m_manager(parent), + m_group(group), + m_retryCount(retryCount), + m_retryInterval(retryInterval) + { m_networkAccessManager = new QNetworkAccessManager(this); @@ -33,12 +40,17 @@ void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) if (!reply) { return; } - reply->deleteLater(); + if (reply->error() != QNetworkReply::NoError) { + qCWarning(msGraphAddressBookFeeder) << "Error:" << reply->errorString(); + reply->deleteLater(); + + Q_EMIT feederFailed(); return; } auto jsonText = reply->readAll(); + reply->deleteLater(); auto doc = QJsonDocument::fromJson(jsonText); if (doc.isNull()) { return; @@ -102,12 +114,11 @@ void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) } } - addressBook.addContact(contactId, "", { 1, "Outlook" }, name, company, email, QDateTime(), - phoneNumbers, BlockInfo()); + addressBook.addContact(contactId, "", { 1, "Outlook", m_group }, name, company, email, + QDateTime(), phoneNumbers, BlockInfo()); } if (doc.object().contains("@odata.nextLink")) { - QString nextLink = doc.object()["@odata.nextLink"].toString(); using namespace Qt::StringLiterals; @@ -117,6 +128,11 @@ void MSGraphAddressBookFeeder::contactsReceived(QNetworkReply *reply) u"Bearer "_s + MSOAuthManager::instance().token()); request.setHeaders(headers); auto *reply = m_networkAccessManager->get(request); + if (!reply) { + Q_EMIT feederFailed(); + return; + } + connect(reply, &QNetworkReply::finished, reply, [this, reply]() { contactsReceived(reply); }); connect(reply, &QNetworkReply::errorOccurred, this, @@ -129,6 +145,7 @@ void MSGraphAddressBookFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply if (!reply) { return; } + switch (code) { case QNetworkReply::NoError: // Should never happen here @@ -137,23 +154,27 @@ void MSGraphAddressBookFeeder::errorOccurred(QNetworkReply *reply, QNetworkReply // may indicate issues with the login. case QNetworkReply::AuthenticationRequiredError: // Corresponds to HTTP 401 case QNetworkReply::ContentAccessDenied: // Corresponds to HTTP 403 - qCCritical(msGraphAddressBookFeeder) - << "Network error for msgraph request:" << code << "require new login to account."; + qCCritical(msGraphAddressBookFeeder) << "Network error for msgraph request:" << code + << "requires a new login to account."; MSOAuthManager::instance() .clearRefreshToken(); // Make sure we don't try to refresh the token again - refreshOrRequestLogin(); break; default: qCCritical(msGraphAddressBookFeeder) << "Network error for msgraph request:" << code; break; } + reply->deleteLater(); + + Q_EMIT feederFailed(); } void MSGraphAddressBookFeeder::requestContacts() { if (!MSOAuthManager::instance().isGranted()) { qCWarning(msGraphAddressBookFeeder) << "Cannot request contacts - not logged in"; + + Q_EMIT feederFailed(); return; } @@ -166,6 +187,8 @@ void MSGraphAddressBookFeeder::requestContacts() auto *reply = m_networkAccessManager->get(request); if (!reply) { qCCritical(msGraphAddressBookFeeder) << "Failed to create contacts request"; + + Q_EMIT feederFailed(); return; } @@ -176,6 +199,24 @@ void MSGraphAddressBookFeeder::requestContacts() void MSGraphAddressBookFeeder::process() { + connect( + this, &MSGraphAddressBookFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + AddressBook::instance().removeContactsBySource(m_group); + + // Some other error has occurred, wait and try again + if (m_retryCount > 0) { + m_retryCount--; + + qCCritical(msGraphAddressBookFeeder) + << "Failed to process MSGraph sources - trying later"; + + QTimer::singleShot(m_retryInterval, this, [this]() { process(); }); + } + }, + Qt::SingleShotConnection); + if (MSOAuthManager::instance().isGranted()) { requestContacts(); } else { diff --git a/src/contacts/msgraph/MSGraphAddressBookFeeder.h b/src/contacts/msgraph/MSGraphAddressBookFeeder.h index 472261db..d6d6e869 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFeeder.h +++ b/src/contacts/msgraph/MSGraphAddressBookFeeder.h @@ -3,7 +3,6 @@ #include #include #include -#include "Contact.h" class AddressBook; class AddressBookManager; @@ -13,18 +12,26 @@ class MSGraphAddressBookFeeder : public QObject, public IAddressBookFeeder Q_OBJECT public: - explicit MSGraphAddressBookFeeder(const QString &group, AddressBookManager *parent = nullptr); + explicit MSGraphAddressBookFeeder(const QString &group, const int retryCount, + const int retryInterval, + AddressBookManager *parent = nullptr); void process() override; QUrl networkCheckURL() const override; +Q_SIGNALS: + void feederFailed(); + private: void refreshOrRequestLogin(); void contactsReceived(QNetworkReply *reply); void errorOccurred(QNetworkReply *reply, QNetworkReply::NetworkError code); void requestContacts(); - const QString m_group; AddressBookManager *m_manager = nullptr; QNetworkAccessManager *m_networkAccessManager = nullptr; + + const QString m_group; + int m_retryCount = 0; + int m_retryInterval = 0; }; diff --git a/src/ui/components/SettingsPage.qml b/src/ui/components/SettingsPage.qml index 4317a9d1..b64cd1e3 100644 --- a/src/ui/components/SettingsPage.qml +++ b/src/ui/components/SettingsPage.qml @@ -1434,15 +1434,15 @@ Item { } Button { - id: ldapReloadButton + id: contactReloadButton anchors.horizontalCenter: parent.horizontalCenter - text: qsTr('Reload contacts from LDAP') + text: qsTr('Reload contacts') onClicked: () => ViewHelper.reloadAddressBook() Accessible.role: Accessible.Button - Accessible.name: ldapReloadButton.text + Accessible.name: contactReloadButton.text Accessible.focusable: true - Accessible.onPressAction: () => ldapReloadButton.click() + Accessible.onPressAction: () => contactReloadButton.click() } } } From eb5237ed507135d99f5d27b304eec25e0352df2a Mon Sep 17 00:00:00 2001 From: Andreas Beckermann Date: Wed, 22 Apr 2026 11:37:40 +0200 Subject: [PATCH 062/122] feat: adding global shortcuts support for windows (#399) * feat: Adding global shortcuts support for windows Adding dependency on QHotkey, which provides Qt API for windows hotkeys. Used on windows only - other platforms are supported by QHotkey, but currently not needed here. Also adding settings for global shortcuts on windows, since they can otherwise not be configured in a similar manner as with the flatpack portal. --------- Co-authored-by: Cajus Pollmeier Co-authored-by: Cajus Pollmeier --- conanfile.py | 1 + docs/modules/ROOT/examples/sample.conf | 14 ++++ .../conan/recipes/qhotkey/all/conandata.yml | 4 ++ .../conan/recipes/qhotkey/all/conanfile.py | 60 ++++++++++++++++ resources/conan/recipes/qhotkey/config.yml | 8 +++ resources/templates/sample.conf | 13 ++++ src/CMakeLists.txt | 6 +- .../windows/WindowsGlobalShortcuts.cpp | 71 +++++++++++++++++++ src/platform/windows/WindowsGlobalShortcuts.h | 24 +++++++ 9 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 resources/conan/recipes/qhotkey/all/conandata.yml create mode 100644 resources/conan/recipes/qhotkey/all/conanfile.py create mode 100644 resources/conan/recipes/qhotkey/config.yml create mode 100644 src/platform/windows/WindowsGlobalShortcuts.cpp create mode 100644 src/platform/windows/WindowsGlobalShortcuts.h diff --git a/conanfile.py b/conanfile.py index b96ad09c..591e9428 100644 --- a/conanfile.py +++ b/conanfile.py @@ -72,6 +72,7 @@ def requirements(self): if self.settings.os == "Windows": self.requires("wintoast/1.3.2") + self.requires("qhotkey/2026-03-19") if self.settings.os == "Linux": self.requires("libnotify/system") diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index aabffdda..2b698a84 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -871,3 +871,17 @@ ## statusbar - The toggler is shown in the status bar of the main window ## settings-phoning - the toggler is shown on the settings page in the phoning section # display=statusbar + + +## Pre-configure global keyboard shortcuts on windows using the keys +## * dial - shows the dialing window +## * redial - dials the last number again +## * hangup - hangs up the current call +## * toggle-hold - toggles the hold state for the call +## and the values as standard key sequences, detailed in the Qt documentation: +## https://doc.qt.io/qt-6/qkeysequence.html#details +#[windows_shortcuts] +#dial=Ctrl+Shift+D +#redial=Ctrl+Shift+R +#hangup=Ctrl+Shift+H +#toggle-hold=Ctrl+Shift+M diff --git a/resources/conan/recipes/qhotkey/all/conandata.yml b/resources/conan/recipes/qhotkey/all/conandata.yml new file mode 100644 index 00000000..2caa89fb --- /dev/null +++ b/resources/conan/recipes/qhotkey/all/conandata.yml @@ -0,0 +1,4 @@ +sources: + '2026-03-19': + url: "https://github.com/Skycoder42/QHotkey/archive/4e3a244d87f1f7e741e1395f2ffe825f3a8ada45.zip" + sha256: "6055b2b91b955b8e1b24674c4250a9da87fc7e754a8001ef867e7edab934e854" diff --git a/resources/conan/recipes/qhotkey/all/conanfile.py b/resources/conan/recipes/qhotkey/all/conanfile.py new file mode 100644 index 00000000..5930f582 --- /dev/null +++ b/resources/conan/recipes/qhotkey/all/conanfile.py @@ -0,0 +1,60 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.files import apply_conandata_patches, export_conandata_patches, get +import os + +class QHotkey(ConanFile): + name = "qhotkey" + description = "A global shortcut/hotkey for Desktop Qt-Applications." + license = "BSD-3-clause" + homepage = "https://github.com/Skycoder42/QHotkey" + package_type = "library" + + settings = "os", "arch", "compiler", "build_type" + options = { + "shared": [True, False], + "fPIC": [True, False], + "with_conan_qt": [True, False], + } + default_options = { + "shared": False, + "fPIC": True, + "with_conan_qt": False, + } + + def export_sources(self): + export_conandata_patches(self) + + def requirements(self): + if self.options.with_conan_qt: + self.requires("qt/6.10.1") + + def source(self): + get(self, **self.conan_data["sources"][self.version], strip_root=True) + apply_conandata_patches(self) + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.variables['QT_DEFAULT_MAJOR_VERSION '] = 6 + tc.generate() + + def build(self): + apply_conandata_patches(self) + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.set_property("cmake_file_name", "QHotkey") + self.cpp_info.set_property("cmake_target_name", "QHotkey") + self.cpp_info.libs = ["QHotkey"] + self.cpp_info.includedirs.append(os.path.join("include")) diff --git a/resources/conan/recipes/qhotkey/config.yml b/resources/conan/recipes/qhotkey/config.yml new file mode 100644 index 00000000..18b6757e --- /dev/null +++ b/resources/conan/recipes/qhotkey/config.yml @@ -0,0 +1,8 @@ +versions: + '2026-03-19': + folder: all +updater: + source: github + id: Skycoder42/QHotkey + branch: master + url-pattern: https://github.com/Skycoder42/QHotkey/archive/${sha}.zip diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index 46f42a77..73a82c74 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -670,3 +670,16 @@ port=5061 ## (mandatory if 'authorizationMode' includes 'jwt', igonred otherwise) # baseUrl=https://meet.gonicus.de/auth/token + +## Pre-configure global keyboard shortcuts on windows using the keys +## * dial - shows the dialing window +## * redial - dials the last number again +## * hangup - hangs up the current call +## * toggle-hold - toggles the hold state for the call +## and the values as standard key sequences, detailed in the Qt documentation: +## https://doc.qt.io/qt-6/qkeysequence.html#details +#[windows_shortcuts] +#dial=Ctrl+Shift+D +#redial=Ctrl+Shift+R +#hangup=Ctrl+Shift+H +#toggle-hold=Ctrl+Shift+M diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef2c4c20..6610cb43 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,12 +38,14 @@ else() find_package(hidapi REQUIRED) find_package(libusb REQUIRED) find_package(wintoast REQUIRED) + find_package(QHotkey REQUIRED) list(APPEND PLATFORM_LIBRARIES pjproject::pjproject hidapi::hidapi libusb::libusb wintoast::wintoast + QHotkey runtimeobject) endif() @@ -281,8 +283,8 @@ elseif(WIN32) platform/windows/SmtcExternalMediaManager.h platform/windows/SmtcExternalMediaManager.cpp platform/windows/WindowsInhibitHelper.cpp - platform/dummy/DummyGlobalShortcuts.h - platform/dummy/DummyGlobalShortcuts.cpp + platform/windows/WindowsGlobalShortcuts.h + platform/windows/WindowsGlobalShortcuts.cpp platform/dummy/DummySearchProvider.h platform/dummy/DummySearchProvider.cpp ) diff --git a/src/platform/windows/WindowsGlobalShortcuts.cpp b/src/platform/windows/WindowsGlobalShortcuts.cpp new file mode 100644 index 00000000..ad4e3070 --- /dev/null +++ b/src/platform/windows/WindowsGlobalShortcuts.cpp @@ -0,0 +1,71 @@ +#include "WindowsGlobalShortcuts.h" +#include "ReadOnlyConfdSettings.h" +#include + +Q_LOGGING_CATEGORY(lcShortcuts, "gonnect.session.shortcuts") + +GlobalShortcuts &GlobalShortcuts::instance() +{ + static GlobalShortcuts *_instance = nullptr; + if (!_instance) { + _instance = new WindowsGlobalShortcuts; + } + return *_instance; +} + +WindowsGlobalShortcuts::WindowsGlobalShortcuts() : GlobalShortcuts{} +{ + ReadOnlyConfdSettings settings; + m_enabled = !settings.value("generic/disableGlobalShortcuts", false).toBool(); +} + +WindowsGlobalShortcuts::~WindowsGlobalShortcuts() +{ + qDeleteAll(m_currentShortcuts); + m_currentShortcuts.clear(); + qDeleteAll(m_hotkeys); + m_hotkeys.clear(); +} + +void WindowsGlobalShortcuts::setShortcuts(Shortcuts &shortcuts) +{ + qDeleteAll(m_currentShortcuts); + m_currentShortcuts.clear(); + qDeleteAll(m_hotkeys); + m_hotkeys.clear(); + if (m_enabled) { + ReadOnlyConfdSettings settings; + for (auto &sc : std::as_const(shortcuts)) { + auto trigger = sc.second.value("preferred_trigger", QString()).toString(); + QString id = sc.first; + if (trigger.isEmpty()) { + qCWarning(lcShortcuts) << "invalid preferred_trigger for shortcut" << id; + continue; + } + trigger = settings.value(QString("windows_shortcuts/%1").arg(id), trigger).toString(); + + QKeySequence ks = QKeySequence::fromString(trigger); + if (ks.isEmpty()) { + qCInfo(lcShortcuts) << "no trigger for shortcut" << id; + } else { + auto *h = new QHotkey(ks, true, this); + if (h->isRegistered()) { + qCInfo(lcShortcuts) << "registered shortcut" << id << "with" << trigger; + connect(h, &QHotkey::activated, this, [this, id]() { Q_EMIT activated(id); }); + m_hotkeys.append(h); + } else { + qCInfo(lcShortcuts) << "failed to register shortcut" << id << "with" << trigger; + delete h; + } + } + + auto sci = new ShortcutItem(); + sci->id = id; + sci->description = sc.second.value("description", "Error: no description").toString(); + sci->triggerDescription = trigger; + m_currentShortcuts.push_back(sci); + } + } + + Q_EMIT initialized(); +} diff --git a/src/platform/windows/WindowsGlobalShortcuts.h b/src/platform/windows/WindowsGlobalShortcuts.h new file mode 100644 index 00000000..53cca6f5 --- /dev/null +++ b/src/platform/windows/WindowsGlobalShortcuts.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include "../GlobalShortcuts.h" + +class QHotkey; + +class WindowsGlobalShortcuts : public GlobalShortcuts +{ + Q_OBJECT + +public: + explicit WindowsGlobalShortcuts(); + ~WindowsGlobalShortcuts(); + + bool isSupported() const override { return true; } + void setShortcuts(Shortcuts &shortcuts) override; + QList shortcuts() const override { return m_currentShortcuts; } + +private: + bool m_enabled = false; + QList m_currentShortcuts; + QLinkedList m_hotkeys; +}; From 98c719971d48f696f312a21dde469c745c4ba220 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Wed, 22 Apr 2026 14:04:30 +0200 Subject: [PATCH 063/122] feat: update pjproject to 2.17 (#435) * Updated pjproject to 2.17 --- conanfile.py | 2 +- resources/conan/recipes/pjproject/all/conandata.yml | 9 +++------ resources/conan/recipes/pjproject/config.yml | 2 +- resources/flatpak/de.gonicus.gonnect.yml | 5 +++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/conanfile.py b/conanfile.py index 591e9428..04dd38fb 100644 --- a/conanfile.py +++ b/conanfile.py @@ -61,7 +61,7 @@ def package(self): def requirements(self): self.requires("hidapi/0.15.0") - self.requires("pjproject/2.16") + self.requires("pjproject/2.17") self.requires("openldap/2.6.13") self.requires("libical/3.0.20") self.requires("vcard/cci.20250408") diff --git a/resources/conan/recipes/pjproject/all/conandata.yml b/resources/conan/recipes/pjproject/all/conandata.yml index fc4c6b1d..d588dba4 100644 --- a/resources/conan/recipes/pjproject/all/conandata.yml +++ b/resources/conan/recipes/pjproject/all/conandata.yml @@ -1,7 +1,4 @@ sources: - '2.16': - url: https://github.com/pjsip/pjproject/archive/refs/tags/2.16.tar.gz - sha256: 3af2e481d51aaa095897820fa2ee26c30e530590c6ca56d23e4133bbdad369eb -patches: - '2.16': - - patch_file: patches/01-cmake-archive-location.patch + '2.17': + url: https://github.com/pjsip/pjproject/archive/refs/tags/2.17.tar.gz + sha256: 065fe06c06788d97c35f563796d59f00ce52fe9558a52d7b490a042a966facce diff --git a/resources/conan/recipes/pjproject/config.yml b/resources/conan/recipes/pjproject/config.yml index 5ddf541c..ca9856db 100644 --- a/resources/conan/recipes/pjproject/config.yml +++ b/resources/conan/recipes/pjproject/config.yml @@ -1,5 +1,5 @@ versions: - '2.16': + '2.17': folder: all updater: source: anitya diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index d55b17dc..925a16c6 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -133,6 +133,7 @@ modules: - name: pjsip buildsystem: autotools + no-parallel-make: true config-opts: - --disable-video - --enable-ext-sound @@ -142,8 +143,8 @@ modules: sources: - type: git url: https://github.com/pjsip/pjproject.git - tag: 2.16 - commit: 6cab30cec44af36ba7c8a45ff7af2f74a8ed288c + tag: 2.17 + commit: 5a457451fa2712ba18e12b01738e8ff3af2b26fd x-checker-data: type: git tag-pattern: ^([0-9]+\.[0-9]+(\.[0-9]+)?)$ From 9a8d6048e871262fda444a24a5091096940274f2 Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:33:49 +0200 Subject: [PATCH 064/122] fix: resolve feeder manager deadlock (#427) * fix: deadlocks --------- Co-authored-by: Cajus Pollmeier --- src/calendar/DateEventFeederManager.cpp | 19 +++++--- src/calendar/DateEventManager.cpp | 29 ++++++++++-- src/calendar/caldav/CalDAVEventFeeder.cpp | 6 +++ src/calendar/msgraph/MSGraphEventFeeder.cpp | 2 +- src/contacts/AddressBookManager.cpp | 45 ++++++++++--------- .../carddav/CardDAVAddressBookFeeder.cpp | 8 ++++ src/contacts/eds/EDSAddressBookFeeder.cpp | 38 ++++++++-------- src/contacts/ldap/LDAPAddressBookFeeder.cpp | 2 + src/platform/linux/LinuxUserInfo.cpp | 1 + src/sip/SIPCall.h | 1 - src/ui/ViewHelper.h | 2 +- src/usb/USBDevices.h | 4 +- 12 files changed, 101 insertions(+), 56 deletions(-) diff --git a/src/calendar/DateEventFeederManager.cpp b/src/calendar/DateEventFeederManager.cpp index 326e1479..80fc608c 100644 --- a/src/calendar/DateEventFeederManager.cpp +++ b/src/calendar/DateEventFeederManager.cpp @@ -125,10 +125,12 @@ void DateEventFeederManager::processQueue() auto &networkHelper = NetworkHelper::instance(); if (!m_queueMutex.tryLock()) { - QTimer::singleShot(100, this, &DateEventFeederManager::processQueue); + qCFatal(lcDateEventFeederManager) << "Failed to acquire lock for the feeder queue"; return; } + bool reconnectRequired = false; + QMutableStringListIterator it(m_feederConfigIds); while (it.hasNext()) { const auto &configId = it.next(); @@ -145,27 +147,28 @@ void DateEventFeederManager::processQueue() if (!urlToCheck.isValid()) { qCCritical(lcDateEventFeederManager) << "URL is invalid:" << urlToCheck; + continue; } if (!networkHelper.hasConnectivity()) { qCWarning(lcDateEventFeederManager) << "No connectivity state yet - trying later"; + networkAvailable = false; - setupReconnectSignal(); + reconnectRequired = true; continue; } networkHelper.isReachable(urlToCheck) - .then(this, [feeder, urlToCheck, this](bool isReachable) { + .then(this, [feeder, urlToCheck, &reconnectRequired](bool isReachable) { if (isReachable) { - QMutexLocker mutex(&m_queueMutex); - feeder->init(); } else { qCWarning(lcDateEventFeederManager) << "Feeder URL" << urlToCheck << "is not reachable"; - setupReconnectSignal(); + + reconnectRequired = true; } }); } @@ -175,6 +178,10 @@ void DateEventFeederManager::processQueue() } m_queueMutex.unlock(); + + if (reconnectRequired) { + setupReconnectSignal(); + } } void DateEventFeederManager::setupReconnectSignal() diff --git a/src/calendar/DateEventManager.cpp b/src/calendar/DateEventManager.cpp index 5d90e0c5..d3dd105a 100644 --- a/src/calendar/DateEventManager.cpp +++ b/src/calendar/DateEventManager.cpp @@ -25,6 +25,14 @@ QList> DateEventManager::createDaysFromRange(const Q { QList> days; + // INFO: Events can have a duration of 0 (start == end) + // And since one could manually edit an ICS/iCal file and, for some reason, + // set an end time < start time, we'll check for that as well + if (end <= start) { + days.append(qMakePair(start, start)); + return days; + } + QDateTime currentStart = start; while (currentStart < end) { QDateTime endOfDay = currentStart.addDays(1); @@ -81,10 +89,15 @@ void DateEventManager::addDateEvent(const QString &id, const QString &source, // Multi-day events will share the same base ID with a counter added to it dateEventIds.append(QString("%1-%2").arg(id).arg(i)); } - } else { + } else if (days.count() == 1) { dateEventIds.append(id); } + if (days.count() != dateEventIds.count()) { + qCWarning(lcDateEventManager) << "DateEvent will be ignored due to an invalid duration"; + return; + } + QMutexLocker lock(&m_feederMutex); for (int i = 0; i < dateEventIds.count(); i++) { @@ -141,10 +154,15 @@ void DateEventManager::modifyDateEvent(const QString &id, const QString &source, for (int i = 0; i < days.count(); i++) { dateEventIds.append(QString("%1-%2").arg(id).arg(i)); } - } else { + } else if (days.count() == 1) { dateEventIds.append(id); } + if (days.count() != dateEventIds.count()) { + qCWarning(lcDateEventManager) << "DateEvent will be ignored due to an invalid duration"; + return; + } + QMutexLocker lock(&m_feederMutex); for (int i = 0; i < dateEventIds.count(); i++) { @@ -185,10 +203,15 @@ void DateEventManager::removeDateEvent(const QString &id, const QDateTime &start for (int i = 0; i < days.count(); i++) { dateEventIds.append(QString("%1-%2").arg(id).arg(i)); } - } else { + } else if (days.count() == 1) { dateEventIds.append(id); } + if (days.count() != dateEventIds.count()) { + qCWarning(lcDateEventManager) << "DateEvent will be ignored due to an invalid duration"; + return; + } + QMutexLocker lock(&m_feederMutex); for (auto &dateEventId : dateEventIds) { diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index aa892f69..d842c933 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -141,6 +141,12 @@ void CalDAVEventFeeder::getNextItem() return; } + if (reply->error() != QNetworkReply::NoError) { + qCDebug(lcCalDAVEventFeeder) << "WebDAV reply error:" << reply->error(); + reply->deleteLater(); + return; + } + QByteArray data = reply->readAll(); reply->deleteLater(); diff --git a/src/calendar/msgraph/MSGraphEventFeeder.cpp b/src/calendar/msgraph/MSGraphEventFeeder.cpp index 55d7c3c8..38a1f98d 100644 --- a/src/calendar/msgraph/MSGraphEventFeeder.cpp +++ b/src/calendar/msgraph/MSGraphEventFeeder.cpp @@ -47,9 +47,9 @@ void MSGraphEventFeeder::init() this, &MSGraphEventFeeder::feederFailed, this, [this]() { // Prepare feeder for re-run + DateEventManager::instance().removeDateEventsBySource(m_source); m_calendarRefreshTimer.stop(); m_isFirstPage = false; - DateEventManager::instance().removeDateEventsBySource(m_source); if (m_retryCount > 0) { m_retryCount--; diff --git a/src/contacts/AddressBookManager.cpp b/src/contacts/AddressBookManager.cpp index a8f409ce..25eb40c3 100644 --- a/src/contacts/AddressBookManager.cpp +++ b/src/contacts/AddressBookManager.cpp @@ -86,16 +86,17 @@ void AddressBookManager::processAddressBookQueue() auto &nh = NetworkHelper::instance(); if (!m_queueMutex.tryLock()) { - QTimer::singleShot(100, this, &AddressBookManager::processAddressBookQueue); + qCFatal(lcAddressBookManager) << "Failed to acquire lock for the feeder queue"; return; } + bool reconnectRequired = false; + QMutableStringListIterator it(m_addressBookQueue); while (it.hasNext()) { QString group = it.next(); if (auto feeder = m_addressBookFeeders.value(group, nullptr)) { - if (feeder->isProcessing()) { // A currently active feeder must not be invoked again to prevent double runs and // threading issues. @@ -116,6 +117,7 @@ void AddressBookManager::processAddressBookQueue() if (!checkURL.isValid()) { qCCritical(lcAddressBookManager) << "URL is invalid:" << checkURL; + continue; } @@ -123,29 +125,22 @@ void AddressBookManager::processAddressBookQueue() qCWarning(lcAddressBookManager) << "No connectivity state yet - trying later"; networkAvailable = false; - connect( - &nh, &NetworkHelper::connectivityChanged, this, - [this]() { processAddressBookQueue(); }, - Qt::ConnectionType::SingleShotConnection); - + reconnectRequired = true; continue; } - nh.isReachable(checkURL).then(this, [feeder, checkURL, this](bool isReachable) { - if (isReachable) { - QMutexLocker mutex(&m_queueMutex); - - feeder->process(); - Q_EMIT AddressBook::instance().contactsReady(); - } else { - qCWarning(lcAddressBookManager) - << "Feeder URL" << checkURL << "is not reachable"; - connect( - &NetworkHelper::instance(), &NetworkHelper::connectivityChanged, - this, [this]() { processAddressBookQueue(); }, - Qt::ConnectionType::SingleShotConnection); - } - }); + nh.isReachable(checkURL).then( + this, [feeder, checkURL, &reconnectRequired](bool isReachable) { + if (isReachable) { + feeder->process(); + Q_EMIT AddressBook::instance().contactsReady(); + } else { + qCWarning(lcAddressBookManager) + << "Feeder URL" << checkURL << "is not reachable"; + + reconnectRequired = true; + } + }); } it.remove(); @@ -153,6 +148,12 @@ void AddressBookManager::processAddressBookQueue() } m_queueMutex.unlock(); + + if (reconnectRequired) { + connect( + &nh, &NetworkHelper::connectivityChanged, this, + [this]() { processAddressBookQueue(); }, Qt::ConnectionType::SingleShotConnection); + } } void AddressBookManager::acquireSecret(bool forcePrompt, const QString &group, diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index 709fc3b2..941f0026 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -294,6 +294,14 @@ void CardDAVAddressBookFeeder::onParserFinished() if (!reply) { return; } + + if (reply->error() != QNetworkReply::NoError) { + qCDebug(lcCardDAVAddressBookFeeder) + << "WebDAV reply error:" << reply->error(); + reply->deleteLater(); + return; + } + QByteArray data = reply->readAll(); reply->deleteLater(); diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index f562e6d2..27338fd4 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -26,25 +26,6 @@ EDSAddressBookFeeder::~EDSAddressBookFeeder() void EDSAddressBookFeeder::init() { - connect( - this, &EDSAddressBookFeeder::feederFailed, this, - [this]() { - // Prepare feeder for re-run - resetContacts(); - resetFeeder(); - - if (m_retryCount > 0) { - m_retryCount--; - - qCWarning(lcEDSAddressBookFeeder) - << "Failed to process EDS sources - trying later"; - - // Retry - QTimer::singleShot(m_retryInterval, this, [this]() { process(); }); - } - }, - Qt::SingleShotConnection); - m_cancellable = g_cancellable_new(); GError *error = NULL; @@ -175,6 +156,25 @@ void EDSAddressBookFeeder::resetFeeder() void EDSAddressBookFeeder::process() { + connect( + this, &EDSAddressBookFeeder::feederFailed, this, + [this]() { + // Prepare feeder for re-run + resetContacts(); + resetFeeder(); + + if (m_retryCount > 0) { + m_retryCount--; + + qCWarning(lcEDSAddressBookFeeder) + << "Failed to process EDS sources - trying later"; + + // Retry + QTimer::singleShot(m_retryInterval, this, [this]() { process(); }); + } + }, + Qt::SingleShotConnection); + ReadOnlyConfdSettings settings; settings.beginGroup(m_group); diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.cpp b/src/contacts/ldap/LDAPAddressBookFeeder.cpp index b163d83d..8317b924 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.cpp +++ b/src/contacts/ldap/LDAPAddressBookFeeder.cpp @@ -48,6 +48,8 @@ void LDAPAddressBookFeeder::init(const LDAPInitializer::Config &ldapConfig, void LDAPAddressBookFeeder::resetFeeder() { + m_isProcessing = false; + m_ldapConfig = {}; if (m_ldap) { diff --git a/src/platform/linux/LinuxUserInfo.cpp b/src/platform/linux/LinuxUserInfo.cpp index ce9bf2dd..99e8b916 100644 --- a/src/platform/linux/LinuxUserInfo.cpp +++ b/src/platform/linux/LinuxUserInfo.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "LinuxUserInfo.h" #include "UserInfo.h" diff --git a/src/sip/SIPCall.h b/src/sip/SIPCall.h index 722eca2a..a0cd2205 100644 --- a/src/sip/SIPCall.h +++ b/src/sip/SIPCall.h @@ -12,7 +12,6 @@ class SIPAccount; class CallHistoryItem; class IMHandler; -class HeadsetDeviceProxy; class Sniffer; class SIPCall : public ICallState, public pj::Call diff --git a/src/ui/ViewHelper.h b/src/ui/ViewHelper.h index e5410642..250e0a65 100644 --- a/src/ui/ViewHelper.h +++ b/src/ui/ViewHelper.h @@ -8,10 +8,10 @@ #include "Application.h" #include "NumberStats.h" #include "JitsiConnector.h" +#include "HeadsetDeviceProxy.h" class Ringer; class Contact; -class HeadsetDeviceProxy; class CallHistoryItem; class ViewHelper : public QObject diff --git a/src/usb/USBDevices.h b/src/usb/USBDevices.h index 1ee06494..14489412 100644 --- a/src/usb/USBDevices.h +++ b/src/usb/USBDevices.h @@ -7,9 +7,7 @@ #include #include #include - -class HeadsetDevice; -class HeadsetDeviceProxy; +#include "HeadsetDeviceProxy.h" class USBDevices : public QObject { From a9637431c22f6b6c3d0b44106c86603c0e22b96d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 22 Apr 2026 16:29:26 +0000 Subject: [PATCH 065/122] chore(release): 2.2.0-beta.1 [skip ci] # [2.2.0-beta.1](https://github.com/gonicus/gonnect/compare/v2.1.0...v2.2.0-beta.1) (2026-04-22) ### Bug Fixes * build with certificate patch ([#433](https://github.com/gonicus/gonnect/issues/433)) ([8e8ba1f](https://github.com/gonicus/gonnect/commit/8e8ba1f9897457a4582ffdb059985569ac957984)) * c++ standard and guarding updates ([#422](https://github.com/gonicus/gonnect/issues/422)) ([9e8171b](https://github.com/gonicus/gonnect/commit/9e8171ba4ef5336e988d6938fa62793432d89ae5)) * code review loop ([#374](https://github.com/gonicus/gonnect/issues/374)) ([f1c609d](https://github.com/gonicus/gonnect/commit/f1c609da8dc0214e2f0c4909ef7b645b08352c11)) * disable WMME in pjsip ([#397](https://github.com/gonicus/gonnect/issues/397)) ([a90a80c](https://github.com/gonicus/gonnect/commit/a90a80cb4796c96f1626f82d3e59d49312c23585)) * handle direct pjsip mute again ([#402](https://github.com/gonicus/gonnect/issues/402)) ([0c4609c](https://github.com/gonicus/gonnect/commit/0c4609ca355f760478a19bda2c618aa299ff86eb)) * home/call switch tab notification reset ([#404](https://github.com/gonicus/gonnect/issues/404)) ([95e0a3f](https://github.com/gonicus/gonnect/commit/95e0a3ff849fda5a6fbb7e2294a99349edef77f8)) * make MWI subscription configurable ([#414](https://github.com/gonicus/gonnect/issues/414)) ([d74c530](https://github.com/gonicus/gonnect/commit/d74c53040e10f354cfbf86ef62662570af4bd3fc)) * make ReadOnlyConfdSettings treate string lists correctly ([#423](https://github.com/gonicus/gonnect/issues/423)) ([48328e2](https://github.com/gonicus/gonnect/commit/48328e235cf888f00b95b4b6792b727138aaf35c)) * more sip codes lead to auth request ([#379](https://github.com/gonicus/gonnect/issues/379)) ([31ee0cd](https://github.com/gonicus/gonnect/commit/31ee0cd138b21d34c3945274a4f6f08e344eae96)) * mwi recursions ([#405](https://github.com/gonicus/gonnect/issues/405)) ([2a42344](https://github.com/gonicus/gonnect/commit/2a423440dbafddb79db9a988e0bb27716e47ed1f)) * potential crash on HeadsetDevice destroyed ([#371](https://github.com/gonicus/gonnect/issues/371)) ([65900ab](https://github.com/gonicus/gonnect/commit/65900abcc5030f33687ebfab95e03f05c1591c51)) * raise window after showing it ([#398](https://github.com/gonicus/gonnect/issues/398)) ([4f72dc6](https://github.com/gonicus/gonnect/commit/4f72dc6ad54731be70190d1495c00b15b301c14e)) * reenable CardDAV plugin ([#406](https://github.com/gonicus/gonnect/issues/406)) ([9b9b136](https://github.com/gonicus/gonnect/commit/9b9b136f27fc945f2e6f6763401de76ead6a9a7b)) * resolve feeder manager deadlock ([#427](https://github.com/gonicus/gonnect/issues/427)) ([9a8d604](https://github.com/gonicus/gonnect/commit/9a8d6048e871262fda444a24a5091096940274f2)) * restore functionality for feeders without network requirements ([#387](https://github.com/gonicus/gonnect/issues/387)) ([01c6494](https://github.com/gonicus/gonnect/commit/01c64946a77c9cee40f566c9af2fecd51bfa7bcc)) * tab switch/reset notifications ([9338ed0](https://github.com/gonicus/gonnect/commit/9338ed0c6a82c35d44e36a40d90a66781c0c0441)) * **ui:** give focus to call screen when startet via enter ([#395](https://github.com/gonicus/gonnect/issues/395)) ([5e5d78e](https://github.com/gonicus/gonnect/commit/5e5d78e0a6364d511a43558e59be5ab020bf2795)) ### Features * adding global shortcuts support for windows ([#399](https://github.com/gonicus/gonnect/issues/399)) ([eb5237e](https://github.com/gonicus/gonnect/commit/eb5237ed507135d99f5d27b304eec25e0352df2a)) * basic MWI support ([#392](https://github.com/gonicus/gonnect/issues/392)) ([8c3c914](https://github.com/gonicus/gonnect/commit/8c3c91455b04a800a5c3e09c76b1f3e3b92ce023)) * delete old history entries ([#432](https://github.com/gonicus/gonnect/issues/432)) ([0ee826d](https://github.com/gonicus/gonnect/commit/0ee826d3384151954c559e3c417967f8b3fc872f)) * display sip registration status ([#375](https://github.com/gonicus/gonnect/issues/375)) ([f9e6567](https://github.com/gonicus/gonnect/commit/f9e6567a1bc89b5c688d7edb53fd4cc54899dcba)) * feeder plugin retry mechanism ([#391](https://github.com/gonicus/gonnect/issues/391)) ([e262f3f](https://github.com/gonicus/gonnect/commit/e262f3f75ef70ed54b1aca17c7169e06c7ebd94a)) * pause/resume media on windows ([#370](https://github.com/gonicus/gonnect/issues/370)) ([61f3407](https://github.com/gonicus/gonnect/commit/61f34075a0f8dae316bd2a7ed6f6cf171917ad2b)) * plugins for microsoft calendars and contacts ([#428](https://github.com/gonicus/gonnect/issues/428)) ([e7e8cd9](https://github.com/gonicus/gonnect/commit/e7e8cd96bffc42e63dfb488894adb836c5ad4ff5)) * pre-fill missing it/es translations ([#377](https://github.com/gonicus/gonnect/issues/377)) ([4428e4e](https://github.com/gonicus/gonnect/commit/4428e4e659af0c708fa3738dc521977b7b11d1c2)) * update pjproject to 2.17 ([#435](https://github.com/gonicus/gonnect/issues/435)) ([98c7199](https://github.com/gonicus/gonnect/commit/98c719971d48f696f312a21dde469c745c4ba220)) --- CHANGELOG.md | 36 +++++++++++++++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- .../flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7748f29f..068ad298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# [2.2.0-beta.1](https://github.com/gonicus/gonnect/compare/v2.1.0...v2.2.0-beta.1) (2026-04-22) + + +### Bug Fixes + +* build with certificate patch ([#433](https://github.com/gonicus/gonnect/issues/433)) ([8e8ba1f](https://github.com/gonicus/gonnect/commit/8e8ba1f9897457a4582ffdb059985569ac957984)) +* c++ standard and guarding updates ([#422](https://github.com/gonicus/gonnect/issues/422)) ([9e8171b](https://github.com/gonicus/gonnect/commit/9e8171ba4ef5336e988d6938fa62793432d89ae5)) +* code review loop ([#374](https://github.com/gonicus/gonnect/issues/374)) ([f1c609d](https://github.com/gonicus/gonnect/commit/f1c609da8dc0214e2f0c4909ef7b645b08352c11)) +* disable WMME in pjsip ([#397](https://github.com/gonicus/gonnect/issues/397)) ([a90a80c](https://github.com/gonicus/gonnect/commit/a90a80cb4796c96f1626f82d3e59d49312c23585)) +* handle direct pjsip mute again ([#402](https://github.com/gonicus/gonnect/issues/402)) ([0c4609c](https://github.com/gonicus/gonnect/commit/0c4609ca355f760478a19bda2c618aa299ff86eb)) +* home/call switch tab notification reset ([#404](https://github.com/gonicus/gonnect/issues/404)) ([95e0a3f](https://github.com/gonicus/gonnect/commit/95e0a3ff849fda5a6fbb7e2294a99349edef77f8)) +* make MWI subscription configurable ([#414](https://github.com/gonicus/gonnect/issues/414)) ([d74c530](https://github.com/gonicus/gonnect/commit/d74c53040e10f354cfbf86ef62662570af4bd3fc)) +* make ReadOnlyConfdSettings treate string lists correctly ([#423](https://github.com/gonicus/gonnect/issues/423)) ([48328e2](https://github.com/gonicus/gonnect/commit/48328e235cf888f00b95b4b6792b727138aaf35c)) +* more sip codes lead to auth request ([#379](https://github.com/gonicus/gonnect/issues/379)) ([31ee0cd](https://github.com/gonicus/gonnect/commit/31ee0cd138b21d34c3945274a4f6f08e344eae96)) +* mwi recursions ([#405](https://github.com/gonicus/gonnect/issues/405)) ([2a42344](https://github.com/gonicus/gonnect/commit/2a423440dbafddb79db9a988e0bb27716e47ed1f)) +* potential crash on HeadsetDevice destroyed ([#371](https://github.com/gonicus/gonnect/issues/371)) ([65900ab](https://github.com/gonicus/gonnect/commit/65900abcc5030f33687ebfab95e03f05c1591c51)) +* raise window after showing it ([#398](https://github.com/gonicus/gonnect/issues/398)) ([4f72dc6](https://github.com/gonicus/gonnect/commit/4f72dc6ad54731be70190d1495c00b15b301c14e)) +* reenable CardDAV plugin ([#406](https://github.com/gonicus/gonnect/issues/406)) ([9b9b136](https://github.com/gonicus/gonnect/commit/9b9b136f27fc945f2e6f6763401de76ead6a9a7b)) +* resolve feeder manager deadlock ([#427](https://github.com/gonicus/gonnect/issues/427)) ([9a8d604](https://github.com/gonicus/gonnect/commit/9a8d6048e871262fda444a24a5091096940274f2)) +* restore functionality for feeders without network requirements ([#387](https://github.com/gonicus/gonnect/issues/387)) ([01c6494](https://github.com/gonicus/gonnect/commit/01c64946a77c9cee40f566c9af2fecd51bfa7bcc)) +* tab switch/reset notifications ([9338ed0](https://github.com/gonicus/gonnect/commit/9338ed0c6a82c35d44e36a40d90a66781c0c0441)) +* **ui:** give focus to call screen when startet via enter ([#395](https://github.com/gonicus/gonnect/issues/395)) ([5e5d78e](https://github.com/gonicus/gonnect/commit/5e5d78e0a6364d511a43558e59be5ab020bf2795)) + + +### Features + +* adding global shortcuts support for windows ([#399](https://github.com/gonicus/gonnect/issues/399)) ([eb5237e](https://github.com/gonicus/gonnect/commit/eb5237ed507135d99f5d27b304eec25e0352df2a)) +* basic MWI support ([#392](https://github.com/gonicus/gonnect/issues/392)) ([8c3c914](https://github.com/gonicus/gonnect/commit/8c3c91455b04a800a5c3e09c76b1f3e3b92ce023)) +* delete old history entries ([#432](https://github.com/gonicus/gonnect/issues/432)) ([0ee826d](https://github.com/gonicus/gonnect/commit/0ee826d3384151954c559e3c417967f8b3fc872f)) +* display sip registration status ([#375](https://github.com/gonicus/gonnect/issues/375)) ([f9e6567](https://github.com/gonicus/gonnect/commit/f9e6567a1bc89b5c688d7edb53fd4cc54899dcba)) +* feeder plugin retry mechanism ([#391](https://github.com/gonicus/gonnect/issues/391)) ([e262f3f](https://github.com/gonicus/gonnect/commit/e262f3f75ef70ed54b1aca17c7169e06c7ebd94a)) +* pause/resume media on windows ([#370](https://github.com/gonicus/gonnect/issues/370)) ([61f3407](https://github.com/gonicus/gonnect/commit/61f34075a0f8dae316bd2a7ed6f6cf171917ad2b)) +* plugins for microsoft calendars and contacts ([#428](https://github.com/gonicus/gonnect/issues/428)) ([e7e8cd9](https://github.com/gonicus/gonnect/commit/e7e8cd96bffc42e63dfb488894adb836c5ad4ff5)) +* pre-fill missing it/es translations ([#377](https://github.com/gonicus/gonnect/issues/377)) ([4428e4e](https://github.com/gonicus/gonnect/commit/4428e4e659af0c708fa3738dc521977b7b11d1c2)) +* update pjproject to 2.17 ([#435](https://github.com/gonicus/gonnect/issues/435)) ([98c7199](https://github.com/gonicus/gonnect/commit/98c719971d48f696f312a21dde469c745c4ba220)) + # [2.1.0](https://github.com/gonicus/gonnect/compare/v2.0.9...v2.1.0) (2026-04-10) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41b82d39..8e7d9482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.1 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 271381cb..7fe7aa68 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.1.0 +version: 2.2.0-beta.1 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 875afdc4..b2734af6 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ +

Changes:

From c1ab98fe1294fc6fa62c204007f714f55d6eafe2 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Thu, 23 Apr 2026 10:02:50 +0200 Subject: [PATCH 066/122] fix: use verifyServer flag for CA verification with webdav (#437) --- .../all/patches/ssl-certificates.patch | 28 ++++++++++++++++--- .../patches/qtwebdav-ssl-certificates.patch | 28 ++++++++++++++++--- src/AuthManager.cpp | 10 +++++-- src/calendar/caldav/CalDAVEventFeeder.cpp | 3 ++ .../carddav/CardDAVAddressBookFeeder.cpp | 2 ++ src/sip/SIPAccount.cpp | 2 +- 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch index e2d382c0..b0d4c2e0 100644 --- a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..6619a48 100644 +index 091500d..f6f347f 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,12 +19,22 @@ index 091500d..6619a48 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -695,6 +705,12 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,22 @@ QNetworkReply* QWebdav::remove(const QString& path) + return createRequest("DELETE", req); + } + ++void QWebdav::setVerifyCa(const bool value) { ++ m_verifyCa = value; ++} ++ QNetworkRequest QWebdav::buildRequest() { QNetworkRequest req; if (isSSL()) { + if (!m_sslCa.isEmpty()) { + QSslConfiguration sslConfig; ++ if (!m_verifyCa) { ++ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); ++ } + sslConfig.addCaCertificates(m_sslCa); + req.setSslConfiguration(sslConfig); + } @@ -33,7 +43,7 @@ index 091500d..6619a48 100644 QByteArray data = concatenated.toLocal8Bit().toBase64(); QString headerData = "Basic " + data; diff --git a/qwebdav.h b/qwebdav.h -index 7111f05..bc3b5f5 100644 +index 7111f05..3e8cbcb 100644 --- a/qwebdav.h +++ b/qwebdav.h @@ -97,6 +97,9 @@ public: @@ -46,11 +56,21 @@ index 7111f05..bc3b5f5 100644 QNetworkReply* list(const QString& path); QNetworkReply* list(const QString& path, int depth); -@@ -156,6 +159,7 @@ private: +@@ -125,6 +128,8 @@ public: + QNetworkReply* lock(const QString& path, qint64 secs, const QString& token = QString(), QWebdavLockScope scope = LOCK_SCOPE_EXCLUSIVE, QWebdavLockDepth depth = LOCK_DEPTH_ZERO, const QString& owner = QString()); + QNetworkReply* unlock(const QString& path, const QString& token); + ++ void setVerifyCa(const bool value); ++ + //! converts a digest from QByteArray to hexadecimal format ( XX:XX:XX:... with X in [0-9,A-F] ) + static QString digestToHex(const QByteArray &input); + //! converts a digest from hexadecimal format ( XX:XX:XX:... with X in [0-9,A-F] ) to QByteArray +@@ -156,6 +161,8 @@ private: private: QMap m_outDataDevices; QMap m_inDataDevices; + QList m_sslCa; ++ bool m_verifyCa = true; QString m_rootPath; QString m_username; diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch index e2d382c0..b0d4c2e0 100644 --- a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..6619a48 100644 +index 091500d..f6f347f 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,12 +19,22 @@ index 091500d..6619a48 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -695,6 +705,12 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,22 @@ QNetworkReply* QWebdav::remove(const QString& path) + return createRequest("DELETE", req); + } + ++void QWebdav::setVerifyCa(const bool value) { ++ m_verifyCa = value; ++} ++ QNetworkRequest QWebdav::buildRequest() { QNetworkRequest req; if (isSSL()) { + if (!m_sslCa.isEmpty()) { + QSslConfiguration sslConfig; ++ if (!m_verifyCa) { ++ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); ++ } + sslConfig.addCaCertificates(m_sslCa); + req.setSslConfiguration(sslConfig); + } @@ -33,7 +43,7 @@ index 091500d..6619a48 100644 QByteArray data = concatenated.toLocal8Bit().toBase64(); QString headerData = "Basic " + data; diff --git a/qwebdav.h b/qwebdav.h -index 7111f05..bc3b5f5 100644 +index 7111f05..3e8cbcb 100644 --- a/qwebdav.h +++ b/qwebdav.h @@ -97,6 +97,9 @@ public: @@ -46,11 +56,21 @@ index 7111f05..bc3b5f5 100644 QNetworkReply* list(const QString& path); QNetworkReply* list(const QString& path, int depth); -@@ -156,6 +159,7 @@ private: +@@ -125,6 +128,8 @@ public: + QNetworkReply* lock(const QString& path, qint64 secs, const QString& token = QString(), QWebdavLockScope scope = LOCK_SCOPE_EXCLUSIVE, QWebdavLockDepth depth = LOCK_DEPTH_ZERO, const QString& owner = QString()); + QNetworkReply* unlock(const QString& path, const QString& token); + ++ void setVerifyCa(const bool value); ++ + //! converts a digest from QByteArray to hexadecimal format ( XX:XX:XX:... with X in [0-9,A-F] ) + static QString digestToHex(const QByteArray &input); + //! converts a digest from hexadecimal format ( XX:XX:XX:... with X in [0-9,A-F] ) to QByteArray +@@ -156,6 +161,8 @@ private: private: QMap m_outDataDevices; QMap m_inDataDevices; + QList m_sslCa; ++ bool m_verifyCa = true; QString m_rootPath; QString m_username; diff --git a/src/AuthManager.cpp b/src/AuthManager.cpp index 9fd569ce..827f53f3 100644 --- a/src/AuthManager.cpp +++ b/src/AuthManager.cpp @@ -40,7 +40,9 @@ void AuthManager::init() } QSslConfiguration sslConfig; - sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); + if (!settings.value("verifyServer", true).toBool()) { + sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); + } sslConfig.addCaCertificates(sslCAs()); m_reqFactory.setSslConfiguration(sslConfig); @@ -369,7 +371,11 @@ void AuthManager::authenticateJitsiImpl(const QString &roomName) auto request = m_reqFactory.createRequest(QUrlQuery(QString("room=%1").arg(roomName))); QSslConfiguration sslConfig; - sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); + + ReadOnlyConfdSettings settings; + if (!settings.value("verifyServer", true).toBool()) { + sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); + } sslConfig.addCaCertificates(sslCAs()); request.setSslConfiguration(sslConfig); diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index d842c933..c0f08064 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -5,6 +5,7 @@ #include "CalDAVEventFeeder.h" #include "DateEventManager.h" #include "AuthManager.h" +#include "ReadOnlyConfdSettings.h" Q_LOGGING_CATEGORY(lcCalDAVEventFeeder, "gonnect.app.dateevents.feeder.caldav") @@ -13,6 +14,8 @@ using namespace std::chrono_literals; CalDAVEventFeeder::CalDAVEventFeeder(QObject *parent, const CalDAVEventFeederConfig &config) : QObject(parent), m_config(config) { + ReadOnlyConfdSettings settings; + m_webdav.setVerifyCa(settings.value("verifyServer", true).toBool()); m_webdav.addSslCa(AuthManager::instance().sslCAs()); } diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index 941f0026..cf003b13 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -33,6 +33,8 @@ void CardDAVAddressBookFeeder::init() m_cacheWriteTimer.setInterval(3s); m_cacheWriteTimer.callOnTimeout(this, &CardDAVAddressBookFeeder::flushCacheImpl); + ReadOnlyConfdSettings settings; + m_webdav.setVerifyCa(settings.value("verifyServer", true).toBool()); m_webdav.addSslCa(AuthManager::instance().sslCAs()); loadCachedData(m_settingsHash); diff --git a/src/sip/SIPAccount.cpp b/src/sip/SIPAccount.cpp index 4372c383..c8bde695 100644 --- a/src/sip/SIPAccount.cpp +++ b/src/sip/SIPAccount.cpp @@ -235,7 +235,7 @@ void SIPAccount::initialize() m_transportConfig.tlsConfig.privKeyFile = privKeyFile.toStdString(); } - m_transportConfig.tlsConfig.verifyServer = m_settings.value("verifyServer", false).toBool(); + m_transportConfig.tlsConfig.verifyServer = m_settings.value("verifyServer", true).toBool(); if (!activateTransports()) { Q_EMIT initialized(false); From 50eec8eb1619bd15277dd383cb0a400ca8913afa Mon Sep 17 00:00:00 2001 From: Mik- Date: Thu, 23 Apr 2026 13:53:36 +0200 Subject: [PATCH 067/122] docs: updates (#419) * docs: remove unused config in sample * docs: fix docs URL --- README.md | 2 +- docs/modules/ROOT/examples/sample.conf | 20 ------------------- .../flatpak/de.gonicus.gonnect.metainfo.xml | 2 +- src/ui/AboutWindow.qml | 2 +- 4 files changed, 3 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index cb20ffcd..aa0941a6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- Documentation | + Documentation | Issues | Install

diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index 2b698a84..0cc85329 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -751,26 +751,6 @@ # enabled=true -## A jschat block defines a Javascript chat plugin account for chatting. -## It must be "jschat" followed by a unique number for each account. -# [jschat0] - -## The display name that shall be shown in the UI for this chat account. -## (optional) -# displayName=My Company's Internal Chat - -## The name of this device to be identified by the chat server. -## (optional, default: "GOnnect") -# deviceId="My fancy device" - -## The absolute file path of the folder where to find the Javascript chat plugin. -# url=/home/me/chatplugin/dist - -## The chat id. The server url is automatically deducted from the id. -## (mandatory) -# id=@username:homeserver.tld - - ## A CalDAV block defines a CalDAV source for dates. ## The password for the authentication will be requested in a UI dialog on next start of the app. It will then be saved encrypted for further usage in ## the config file named "keychain". Delete the appropriate part there to enforce a new password request. diff --git a/resources/flatpak/de.gonicus.gonnect.metainfo.xml b/resources/flatpak/de.gonicus.gonnect.metainfo.xml index d41a5ae6..aaab7d3f 100644 --- a/resources/flatpak/de.gonicus.gonnect.metainfo.xml +++ b/resources/flatpak/de.gonicus.gonnect.metainfo.xml @@ -126,7 +126,7 @@ https://github.com/gonicus/gonnect https://github.com/gonicus/gonnect/issues - https://docs.gonicus.de/gonnect/v2.1.x + https://docs.gonicus.de/gonnect https://gonnect-project.org https://hosted.weblate.org/engage/gonnect/ https://github.com/gonicus/gonnect#contributing-ov-file diff --git a/src/ui/AboutWindow.qml b/src/ui/AboutWindow.qml index 20474942..0be8a155 100644 --- a/src/ui/AboutWindow.qml +++ b/src/ui/AboutWindow.qml @@ -42,7 +42,7 @@ BaseWindow { property string homePageURL: "https://gonnect-project.org" property string issueTrackerURL: "https://github.com/gonicus/gonnect/issues" - property string documentationURL: "https://docs.gonicus.de/gonnect/v2.1.x/" + property string documentationURL: "https://docs.gonicus.de/gonnect" } Image { From 63eae6b82dc30966cf4ef059bab40830c2a8463b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 23 Apr 2026 12:10:59 +0000 Subject: [PATCH 068/122] chore(release): 2.2.0-beta.2 [skip ci] # [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) ### Bug Fixes * use verifyServer flag for CA verification with webdav ([#437](https://github.com/gonicus/gonnect/issues/437)) ([c1ab98f](https://github.com/gonicus/gonnect/commit/c1ab98fe1294fc6fa62c204007f714f55d6eafe2)) --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 068ad298..daf61841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) + + +### Bug Fixes + +* use verifyServer flag for CA verification with webdav ([#437](https://github.com/gonicus/gonnect/issues/437)) ([c1ab98f](https://github.com/gonicus/gonnect/commit/c1ab98fe1294fc6fa62c204007f714f55d6eafe2)) + # [2.2.0-beta.1](https://github.com/gonicus/gonnect/compare/v2.1.0...v2.2.0-beta.1) (2026-04-22) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e7d9482..fe5f11db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.1 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.2 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 7fe7aa68..9fb8b658 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.1 +version: 2.2.0-beta.2 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index b2734af6..e95e1d3a 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From b973bb63c7798bea7fc6bf0c249e053c5d331fef Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Thu, 23 Apr 2026 14:34:47 +0200 Subject: [PATCH 069/122] feat(ui): leave conference via systray menu (#438) * fix: use verifyServer setting from correct ini group * feat(ui): leave conference via systray menu --- src/AuthManager.cpp | 6 ++-- src/calendar/caldav/CalDAVEventFeeder.cpp | 2 +- .../carddav/CardDAVAddressBookFeeder.cpp | 2 +- src/ui/SystemTrayMenu.cpp | 35 +++++++++++++++++++ src/ui/SystemTrayMenu.h | 3 ++ 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/AuthManager.cpp b/src/AuthManager.cpp index 827f53f3..deed46be 100644 --- a/src/AuthManager.cpp +++ b/src/AuthManager.cpp @@ -39,13 +39,15 @@ void AuthManager::init() return; } + settings.endGroup(); QSslConfiguration sslConfig; - if (!settings.value("verifyServer", true).toBool()) { + if (!settings.value("generic/verifyServer", true).toBool()) { sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); } sslConfig.addCaCertificates(sslCAs()); m_reqFactory.setSslConfiguration(sslConfig); + settings.beginGroup("jitsi"); m_reqFactory.setBaseUrl(settings.value("baseUrl", "").toUrl()); m_authFlow = new QOAuth2AuthorizationCodeFlow(this); @@ -373,7 +375,7 @@ void AuthManager::authenticateJitsiImpl(const QString &roomName) QSslConfiguration sslConfig; ReadOnlyConfdSettings settings; - if (!settings.value("verifyServer", true).toBool()) { + if (!settings.value("generic/verifyServer", true).toBool()) { sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); } sslConfig.addCaCertificates(sslCAs()); diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index c0f08064..0e7f50a1 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -15,7 +15,7 @@ CalDAVEventFeeder::CalDAVEventFeeder(QObject *parent, const CalDAVEventFeederCon : QObject(parent), m_config(config) { ReadOnlyConfdSettings settings; - m_webdav.setVerifyCa(settings.value("verifyServer", true).toBool()); + m_webdav.setVerifyCa(settings.value("generic/verifyServer", true).toBool()); m_webdav.addSslCa(AuthManager::instance().sslCAs()); } diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index cf003b13..c21cb33e 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -34,7 +34,7 @@ void CardDAVAddressBookFeeder::init() m_cacheWriteTimer.callOnTimeout(this, &CardDAVAddressBookFeeder::flushCacheImpl); ReadOnlyConfdSettings settings; - m_webdav.setVerifyCa(settings.value("verifyServer", true).toBool()); + m_webdav.setVerifyCa(settings.value("generic/verifyServer", true).toBool()); m_webdav.addSslCa(AuthManager::instance().sslCAs()); loadCachedData(m_settingsHash); diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp index 8cb84487..2f77585f 100644 --- a/src/ui/SystemTrayMenu.cpp +++ b/src/ui/SystemTrayMenu.cpp @@ -1,4 +1,6 @@ #include "SystemTrayMenu.h" +#include "GlobalCallState.h" +#include "IConferenceConnector.h" #include "NumberStat.h" #include "ViewHelper.h" #include "NumberStats.h" @@ -17,6 +19,7 @@ using namespace std::chrono_literals; SystemTrayMenu::SystemTrayMenu(QObject *parent) : QObject{ parent } { initMenu(); + updateConferences(); updateCalls(); updateFavorites(); updateMostCalled(); @@ -74,6 +77,8 @@ SystemTrayMenu::SystemTrayMenu(QObject *parent) : QObject{ parent } &SystemTrayMenu::updateTogglers); connect(&TogglerManager::instance(), &TogglerManager::togglerBusyChanged, this, &SystemTrayMenu::updateTogglers); + connect(&GlobalCallState::instance(), &GlobalCallState::globalCallStateChanged, this, + &SystemTrayMenu::updateConferences); resetTrayIcon(); } @@ -92,11 +97,40 @@ void SystemTrayMenu::updateMenu() m_mainWindowAction->setIcon(sipReg ? QIcon::fromTheme("call-start-symbolic") : QIcon::fromTheme("view-refresh-symbolic")); + updateConferences(); updateCalls(); updateFavorites(); updateMostCalled(); } +void SystemTrayMenu::updateConferences() +{ + for (QAction *action : std::as_const(m_activeConferencesActions)) { + m_trayIconMenu->removeAction(action); + } + m_activeConferencesActions.clear(); + + const auto globalCallStateObject = GlobalCallState::instance().globalCallStateObjects(); + + for (const auto globalCallObj : globalCallStateObject) { + auto conferenceObj = qobject_cast(globalCallObj); + if (conferenceObj && (conferenceObj->callState() & ICallState::State::CallActive)) { + + auto action = + new QAction(QIcon::fromTheme("call-stop-symbolic"), + tr("Leave conference '%1'").arg(conferenceObj->conferenceName()), + m_trayIconMenu); + m_trayIconMenu->insertAction(m_activeConferencesSeparator, action); + m_activeConferencesActions.append(action); + + connect(action, &QAction::triggered, this, + [conferenceObj]() { conferenceObj->leaveConference(); }); + } + } + + m_activeConferencesSeparator->setVisible(m_activeConferencesActions.size()); +} + void SystemTrayMenu::initMenu() { m_trayIconMenu = new QMenu; @@ -107,6 +141,7 @@ void SystemTrayMenu::initMenu() connect(action, &QAction::triggered, &ViewHelper::instance(), &ViewHelper::activateSearch); m_trayIconMenu->addSeparator(); + m_activeConferencesSeparator = m_trayIconMenu->addSeparator(); m_activeCallsSeparator = m_trayIconMenu->addSeparator(); m_favoritesSeparator = m_trayIconMenu->addSeparator(); m_mostCalledSeparator = m_trayIconMenu->addSeparator(); diff --git a/src/ui/SystemTrayMenu.h b/src/ui/SystemTrayMenu.h index c2758387..c5da151e 100644 --- a/src/ui/SystemTrayMenu.h +++ b/src/ui/SystemTrayMenu.h @@ -31,6 +31,7 @@ class SystemTrayMenu : public QObject private Q_SLOTS: void updateMenu(); + void updateConferences(); void updateCalls(); void updateFavorites(); void updateMostCalled(); @@ -62,6 +63,7 @@ private Q_SLOTS: QAction *m_mainWindowAction = nullptr; QAction *m_settingsWindowAction = nullptr; + QAction *m_activeConferencesSeparator = nullptr; QAction *m_activeCallsSeparator = nullptr; QAction *m_mostCalledSeparator = nullptr; QAction *m_favoritesSeparator = nullptr; @@ -69,6 +71,7 @@ private Q_SLOTS: QList m_callEntries; QList m_activeCallsActions; + QList m_activeConferencesActions; QList m_togglerActions; QHash m_favoriteActions; QHash m_mostCalledActions; From 6c15000c4baea28a227fb99ef8901b27be29252c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 23 Apr 2026 12:41:07 +0000 Subject: [PATCH 070/122] chore(release): 2.2.0-beta.2 [skip ci] # [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) ### Bug Fixes * use verifyServer flag for CA verification with webdav ([#437](https://github.com/gonicus/gonnect/issues/437)) ([c1ab98f](https://github.com/gonicus/gonnect/commit/c1ab98fe1294fc6fa62c204007f714f55d6eafe2)) ### Features * **ui:** leave conference via systray menu ([#438](https://github.com/gonicus/gonnect/issues/438)) ([b973bb6](https://github.com/gonicus/gonnect/commit/b973bb63c7798bea7fc6bf0c249e053c5d331fef)) --- CHANGELOG.md | 12 ++++++++++++ resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index daf61841..2c62b164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) +### Bug Fixes + +* use verifyServer flag for CA verification with webdav ([#437](https://github.com/gonicus/gonnect/issues/437)) ([c1ab98f](https://github.com/gonicus/gonnect/commit/c1ab98fe1294fc6fa62c204007f714f55d6eafe2)) + + +### Features + +* **ui:** leave conference via systray menu ([#438](https://github.com/gonicus/gonnect/issues/438)) ([b973bb6](https://github.com/gonicus/gonnect/commit/b973bb63c7798bea7fc6bf0c249e053c5d331fef)) + +# [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) + + ### Bug Fixes * use verifyServer flag for CA verification with webdav ([#437](https://github.com/gonicus/gonnect/issues/437)) ([c1ab98f](https://github.com/gonicus/gonnect/commit/c1ab98fe1294fc6fa62c204007f714f55d6eafe2)) diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index e95e1d3a..2a1d1013 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From f1a05f59746755ebfb4d89def1b1bd1c8ff7a73a Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Mon, 27 Apr 2026 11:12:55 +0200 Subject: [PATCH 071/122] fix: prevent unhold on currently changing call (#439) --- src/sip/SIPCall.cpp | 5 ++++- src/sip/SIPCallManager.cpp | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/sip/SIPCall.cpp b/src/sip/SIPCall.cpp index 5967d72d..8b30f53c 100644 --- a/src/sip/SIPCall.cpp +++ b/src/sip/SIPCall.cpp @@ -115,7 +115,10 @@ SIPCall::~SIPCall() SIPCallManager::instance().removeCall(this); Q_EMIT GlobalCallState::instance().callEnded(false); - GlobalCallState::instance().unholdOtherCall(); + + // Prevent unhold in the same event loop tick to prevent confused pjsip segfault + QTimer::singleShot(0, &GlobalCallState::instance(), + []() { GlobalCallState::instance().unholdOtherCall(); }); } void SIPCall::call(const QString &dst_uri, const pj::CallOpParam &prm) diff --git a/src/sip/SIPCallManager.cpp b/src/sip/SIPCallManager.cpp index e0c4907f..71c9ff89 100644 --- a/src/sip/SIPCallManager.cpp +++ b/src/sip/SIPCallManager.cpp @@ -765,7 +765,25 @@ void SIPCallManager::removeCall(SIPCall *call) // Automatically unhold last remaining call if (oldCount > 1 && m_calls.size() == 1 && m_calls.at(0)->isHolding()) { - m_calls.at(0)->unhold(); + auto *remainingCall = m_calls.at(0); + + // Make sure that unhold will be called in the next event loop iteration, because pjsip + // might still be doing stuff in this one which can cause a segfault. + // And the call might be in termination, hence the info.state check. + QTimer::singleShot(0, remainingCall, [remainingCall]() { + if (remainingCall->isHolding()) { + try { + pj::CallInfo info = remainingCall->getInfo(); + + if (info.state == PJSIP_INV_STATE_CONFIRMED) { + remainingCall->unhold(); + } + } catch (pj::Error &) { + // This means the call is terminated or currently in termination. That is fine, + // but ignore the error. + } + } + }); } updateCallCount(); From db0f78b34ea8830a85567ed0474ac756f26abef9 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:13:49 +0200 Subject: [PATCH 072/122] chore(deps): update renovatebot/github-action action to v46.1.11 (#441) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index d3199592..cbae96cb 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@83ec54fee49ab67d9cd201084c1ff325b4b462e4 # v46.1.10 + uses: renovatebot/github-action@6a9df9227eeb83af9a5abef6890bbb0c9068f436 # v46.1.11 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From a049615a09a2619ca1ef367cf93620dce3378c3a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 27 Apr 2026 09:47:26 +0000 Subject: [PATCH 073/122] chore(release): 2.2.0-beta.3 [skip ci] # [2.2.0-beta.3](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.2...v2.2.0-beta.3) (2026-04-27) ### Bug Fixes * prevent unhold on currently changing call ([#439](https://github.com/gonicus/gonnect/issues/439)) ([f1a05f5](https://github.com/gonicus/gonnect/commit/f1a05f59746755ebfb4d89def1b1bd1c8ff7a73a)) --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c62b164..1b74f4cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.2.0-beta.3](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.2...v2.2.0-beta.3) (2026-04-27) + + +### Bug Fixes + +* prevent unhold on currently changing call ([#439](https://github.com/gonicus/gonnect/issues/439)) ([f1a05f5](https://github.com/gonicus/gonnect/commit/f1a05f59746755ebfb4d89def1b1bd1c8ff7a73a)) + # [2.2.0-beta.2](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.1...v2.2.0-beta.2) (2026-04-23) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe5f11db..92d9f189 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.2 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.3 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 9fb8b658..67936dd8 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.2 +version: 2.2.0-beta.3 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 2a1d1013..64eab369 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From cdd370a1db5ea007cba1776f2f9e443bacbdd162 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 13:00:54 +0200 Subject: [PATCH 074/122] chore(deps): update github actions (#442) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/renovate.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d620a850..1318c213 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 + uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 + uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index cbae96cb..8984876d 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@6a9df9227eeb83af9a5abef6890bbb0c9068f436 # v46.1.11 + uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 05f58e3f19ab412af621c0a183ee8b397ab6f8c5 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 15:04:44 +0200 Subject: [PATCH 075/122] chore(deps): update negrutiu/nsis-install action to v3 (#448) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- .github/actions/prepare-windows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/prepare-windows/action.yml b/.github/actions/prepare-windows/action.yml index da618b4a..cba71464 100644 --- a/.github/actions/prepare-windows/action.yml +++ b/.github/actions/prepare-windows/action.yml @@ -16,7 +16,7 @@ runs: - name: Install NSIS id: nsis - uses: negrutiu/nsis-install@f3339c88dba6fd08910d5275a943f8f746d94876 # v2 + uses: negrutiu/nsis-install@9c575d44c2938a43e38a305caeb403de90303c60 # v3 - name: Install `ExecDos` NSIS-plugin uses: negrutiu/nsis-install-plugin@c7f666810808b77249537bec2f7110e7ad9340b1 # v1 From b8bbb72d2d013802abce8be55a966509ebf6c237 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 17:12:01 +0200 Subject: [PATCH 076/122] chore(deps): update dependency qtkeychain to v0.16.0 (#444) * chore(deps): update dependency qtkeychain to v0.16.0 --------- Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- conanfile.py | 2 +- .../recipes/qtkeychain/all/conandata.yml | 9 +++------ .../all/patches/fix-apple-keychain.patch | 19 ------------------- resources/conan/recipes/qtkeychain/config.yml | 4 ++-- resources/flatpak/de.gonicus.gonnect.yml | 4 ++-- 5 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 resources/conan/recipes/qtkeychain/all/patches/fix-apple-keychain.patch diff --git a/conanfile.py b/conanfile.py index 04dd38fb..11dbdfde 100644 --- a/conanfile.py +++ b/conanfile.py @@ -85,7 +85,7 @@ def requirements(self): self.requires("openssl/3.5.5", override=True) self.requires("qtwebdav/2025-03-16") - self.requires("qtkeychain/0.15.0") + self.requires("qtkeychain/0.16.0") self.requires("libusb/1.0.29") def build_requirements(self): diff --git a/resources/conan/recipes/qtkeychain/all/conandata.yml b/resources/conan/recipes/qtkeychain/all/conandata.yml index 8b3865d4..c77e015e 100644 --- a/resources/conan/recipes/qtkeychain/all/conandata.yml +++ b/resources/conan/recipes/qtkeychain/all/conandata.yml @@ -1,7 +1,4 @@ sources: - "0.15.0": - url: "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.15.0.zip" - sha256: "2aca3845e18366f450ce4d0ef94411d9417aa9d86c4444dad8ebf06e3b565743" -patches: - "0.15.0": - - patch_file: patches/fix-apple-keychain.patch + "0.16.0": + url: "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.16.0.zip" + sha256: "5d06fd509e1e483bae0e5778c2cb71e8f91736c361f8028c304f5fe4b165faad" diff --git a/resources/conan/recipes/qtkeychain/all/patches/fix-apple-keychain.patch b/resources/conan/recipes/qtkeychain/all/patches/fix-apple-keychain.patch deleted file mode 100644 index f0f0b5da..00000000 --- a/resources/conan/recipes/qtkeychain/all/patches/fix-apple-keychain.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff -Naur a/qtkeychain/keychain_apple.mm b/qtkeychain/keychain_apple.mm ---- a/qtkeychain/keychain_apple.mm 2025-01-16 12:53:01 -+++ b/qtkeychain/keychain_apple.mm 2025-11-13 15:44:41 -@@ -106,11 +106,10 @@ - - - (void)keychainReadTaskFinished:(NSData *)retrievedData - { -- _privateJob->data.clear(); -- _privateJob->mode = JobPrivate::Binary; -- -- if (retrievedData != nil) { -- if (_privateJob) { -+ if (_privateJob) { -+ _privateJob->data.clear(); -+ _privateJob->mode = JobPrivate::Binary; -+ if (retrievedData != nil) { - _privateJob->data = QByteArray::fromNSData(retrievedData); - } - } diff --git a/resources/conan/recipes/qtkeychain/config.yml b/resources/conan/recipes/qtkeychain/config.yml index e0f0f130..c51bf08d 100644 --- a/resources/conan/recipes/qtkeychain/config.yml +++ b/resources/conan/recipes/qtkeychain/config.yml @@ -1,8 +1,8 @@ versions: - "0.15.0": + "0.16.0": folder: all updater: source: anitya id: 381807 - url-pattern: https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/${version}.zip \ No newline at end of file + url-pattern: https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/${version}.zip diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index 925a16c6..b706c07f 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -193,8 +193,8 @@ modules: sources: - type: git url: https://github.com/frankosterfeld/qtkeychain.git - tag: 0.15.0 - commit: ad7344c45a86a4f66cbafc4b081b5f7b876cb0b7 + tag: 0.16.0 + commit: aa6da344e1a20b9194e12bace3665caeea6b6304 x-checker-data: type: git tag-pattern: ^([0-9]+\.[0-9]+\.[0-9]+)$ From 428860a320785b9a52888a824d28990f59a6beb6 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Mon, 4 May 2026 15:35:52 +0200 Subject: [PATCH 077/122] fix: ignore certain ssl errors when verifyServer is false (webdav) (#451) * fix: ignore certain ssl errors when verifyServer is false (webdav) * chore: user error type out of error object --- .../all/patches/ssl-certificates.patch | 35 +++++++++++++++++-- .../patches/qtwebdav-ssl-certificates.patch | 35 +++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch index b0d4c2e0..8fa831c0 100644 --- a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..f6f347f 100644 +index 091500d..98d7b59 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,38 @@ index 091500d..f6f347f 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -692,9 +702,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -263,6 +273,30 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) + { + // user accepted this SSL certifcate already ==> ignore SSL errors + reply->ignoreSslErrors(); ++ ++ } else if (!m_verifyCa) { ++ ++ // Ignore these errors ++ static const QList verifyErrors = { ++ QSslError::SelfSignedCertificate, ++ QSslError::SelfSignedCertificateInChain, ++ QSslError::UnableToGetIssuerCertificate, ++ QSslError::UnableToGetLocalIssuerCertificate, ++ QSslError::UnableToVerifyFirstCertificate, ++ QSslError::CertificateUntrusted, ++ QSslError::CertificateExpired, ++ QSslError::CertificateNotYetValid, ++ QSslError::HostNameMismatch, ++ }; ++ ++ for (const auto& error : errors) { ++ if (!verifyErrors.contains(error.error())) { ++ emit checkSslCertifcate(errors); ++ reply->abort(); ++ return; ++ } ++ } ++ + } else { + // user has to check the SSL certificate and has to accept manually + emit checkSslCertifcate(errors); +@@ -692,9 +726,22 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch index b0d4c2e0..8fa831c0 100644 --- a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..f6f347f 100644 +index 091500d..98d7b59 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,38 @@ index 091500d..f6f347f 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -692,9 +702,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -263,6 +273,30 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) + { + // user accepted this SSL certifcate already ==> ignore SSL errors + reply->ignoreSslErrors(); ++ ++ } else if (!m_verifyCa) { ++ ++ // Ignore these errors ++ static const QList verifyErrors = { ++ QSslError::SelfSignedCertificate, ++ QSslError::SelfSignedCertificateInChain, ++ QSslError::UnableToGetIssuerCertificate, ++ QSslError::UnableToGetLocalIssuerCertificate, ++ QSslError::UnableToVerifyFirstCertificate, ++ QSslError::CertificateUntrusted, ++ QSslError::CertificateExpired, ++ QSslError::CertificateNotYetValid, ++ QSslError::HostNameMismatch, ++ }; ++ ++ for (const auto& error : errors) { ++ if (!verifyErrors.contains(error.error())) { ++ emit checkSslCertifcate(errors); ++ reply->abort(); ++ return; ++ } ++ } ++ + } else { + // user has to check the SSL certificate and has to accept manually + emit checkSslCertifcate(errors); +@@ -692,9 +726,22 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } From f89882f9be298c1f90de881239b44a50b749492b Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Mon, 4 May 2026 15:54:50 +0200 Subject: [PATCH 078/122] fix: do not wipe all contact sources on by-source removals (#450) --- src/contacts/AddressBook.cpp | 18 +++++++++++++++--- src/contacts/Contact.cpp | 3 ++- .../akonadi/AkonadiAddressBookFeeder.cpp | 2 +- .../carddav/CardDAVAddressBookFeeder.cpp | 2 +- src/contacts/csv/CSVFileAddressBookFeeder.cpp | 2 +- src/contacts/eds/EDSAddressBookFeeder.cpp | 2 +- src/contacts/ldap/LDAPAddressBookFeeder.cpp | 2 +- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/contacts/AddressBook.cpp b/src/contacts/AddressBook.cpp index 51bb65bd..00909e70 100644 --- a/src/contacts/AddressBook.cpp +++ b/src/contacts/AddressBook.cpp @@ -156,14 +156,26 @@ void AddressBook::removeContactsBySource(const QString &source) { QMutexLocker lock(&m_feederMutex); + QString sourceUid; + bool sourceInfoCleared = false; for (auto contact : std::as_const(m_contacts)) { if (contact->contactSourceInfo().configId == source) { + sourceUid = contact->sourceUid(); + + // Remove the ContactSourceInfo of the contact source + if (!sourceInfoCleared) { + m_contactSourceInfos.removeAll(contact->contactSourceInfo()); + sourceInfoCleared = true; + + Q_EMIT contactSourceInfosChanged(); + } + m_contacts.remove(contact->id()); - m_contactsBySourceId.remove(contact->sourceUid()); + m_contactsBySourceId.remove(sourceUid); + + Q_EMIT contactRemoved(sourceUid); } } - - Q_EMIT contactsCleared(); } QHash AddressBook::contacts() const diff --git a/src/contacts/Contact.cpp b/src/contacts/Contact.cpp index 0f452ad7..084e7f72 100644 --- a/src/contacts/Contact.cpp +++ b/src/contacts/Contact.cpp @@ -406,5 +406,6 @@ bool Contact::ContactSourceInfo::operator==(const ContactSourceInfo &other) cons bool Contact::ContactSourceInfo::operator!=(const ContactSourceInfo &other) const { - return this->prio != other.prio || this->displayName != other.displayName; + return this->prio != other.prio || this->displayName != other.displayName + || this->configId != other.configId; } diff --git a/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp b/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp index 04a54ba2..7c7b8090 100644 --- a/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp +++ b/src/contacts/akonadi/AkonadiAddressBookFeeder.cpp @@ -28,7 +28,7 @@ AkonadiAddressBookFeeder::AkonadiAddressBookFeeder(const QString &group, const i ReadOnlyConfdSettings settings; settings.beginGroup(m_group); - m_displayName = settings.value("displayName", "").toString(); + m_displayName = settings.value("displayName", m_group).toString(); bool ok = true; m_priority = settings.value("prio", 0).toUInt(&ok); if (!ok) { diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index c21cb33e..74635e92 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -365,7 +365,7 @@ void CardDAVAddressBookFeeder::process() m_blockInfo.responseCode = settings.value("blockSipCode", GONNECT_DEFAULT_BLOCK_SIP_CODE).toUInt(); - m_displayName = settings.value("displayName", "").toString(); + m_displayName = settings.value("displayName", m_group).toString(); bool ok = true; m_priority = settings.value("prio", 0).toUInt(&ok); if (!ok) { diff --git a/src/contacts/csv/CSVFileAddressBookFeeder.cpp b/src/contacts/csv/CSVFileAddressBookFeeder.cpp index c1c99787..71424d9f 100644 --- a/src/contacts/csv/CSVFileAddressBookFeeder.cpp +++ b/src/contacts/csv/CSVFileAddressBookFeeder.cpp @@ -25,7 +25,7 @@ void CsvFileAddressBookFeeder::process() settings.beginGroup(m_group); m_filePath = settings.value("path", "").toString(); - m_displayName = settings.value("displayName", "").toString(); + m_displayName = settings.value("displayName", m_group).toString(); m_blockInfo.isBlocking = settings.value("block", false).toBool(); m_blockInfo.responseCode = diff --git a/src/contacts/eds/EDSAddressBookFeeder.cpp b/src/contacts/eds/EDSAddressBookFeeder.cpp index 27338fd4..e6824c9e 100644 --- a/src/contacts/eds/EDSAddressBookFeeder.cpp +++ b/src/contacts/eds/EDSAddressBookFeeder.cpp @@ -178,7 +178,7 @@ void EDSAddressBookFeeder::process() ReadOnlyConfdSettings settings; settings.beginGroup(m_group); - m_displayName = settings.value("displayName", "").toString(); + m_displayName = settings.value("displayName", m_group).toString(); m_blockInfo.isBlocking = settings.value("block", false).toBool(); m_blockInfo.responseCode = diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.cpp b/src/contacts/ldap/LDAPAddressBookFeeder.cpp index 8317b924..a4ca0ed0 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.cpp +++ b/src/contacts/ldap/LDAPAddressBookFeeder.cpp @@ -95,7 +95,7 @@ void LDAPAddressBookFeeder::process() ReadOnlyConfdSettings settings; settings.beginGroup(m_group); - m_displayName = settings.value("displayName", "").toString(); + m_displayName = settings.value("displayName", m_group).toString(); bool ok = true; m_priority = settings.value("prio", 0).toUInt(&ok); if (!ok) { From 0e742b873a053b24c4fb2acf7c862a0a81418272 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 15:55:48 +0200 Subject: [PATCH 079/122] chore(deps): update renovatebot/github-action action to v46.1.13 (#449) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 8984876d..36b82321 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12 + uses: renovatebot/github-action@79dc0ba74dc3de28db0a7aeb1d0b95d5bf5fde2a # v46.1.13 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From 41133841f2c00c72229658a159abfae59330d7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20K=C3=B6hler?= <126574988+dko-strd@users.noreply.github.com> Date: Mon, 4 May 2026 16:17:57 +0200 Subject: [PATCH 080/122] feat(ldap): configurable contact attribute mapping (#445) * feat(ldap): support configurable contact attribute mapping * feat(ldap): support configurable avatar attribute * docs: document LDAP attribute mapping options --------- Signed-off-by: Daniel Koehler Co-authored-by: Markus Bader Co-authored-by: Cajus Pollmeier --- docs/modules/ROOT/examples/sample.conf | 44 ++++++- src/contacts/ldap/LDAPAddressBookFeeder.cpp | 131 +++++++++++++------- src/contacts/ldap/LDAPAddressBookFeeder.h | 17 +++ 3 files changed, 147 insertions(+), 45 deletions(-) diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index 0cc85329..1d1a6590 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -607,12 +607,52 @@ ## If the SIP environment the client is used in does support subscribing to buddy states (i.e. if a contact is available, busy, etc.), the phone number ## found for this LDAP attribute will be used for that subscription. -## This is a performance optimization: when numbers saved under a specific attribute can and will never be subscriptable for SIP states (e.g. "mobile"), +## This is a performance optimization: when numbers saved under a specific attribute can and will never be subscriptable for SIP states (e.g. "mobile"), ## the client does not need to try. While not an excessive overhead, it might affect performance on slow systems and a large number of subscriptions. -## Multiple values must be comma-seperated, e.g. "telephoneNumber,mobile" +## Multiple values must be comma-seperated, e.g. "telephoneNumber,mobile". When custom attribute names are configured below, the values listed here +## must match those configured names exactly. ## (optional) #sipStatusSubscriptableAttributes="telephoneNumber" +## Map the semantic contact roles to the LDAP attribute names actually used by +## the directory. Defaults match the standard inetOrgPerson schema and need only +## be overridden for servers that publish phonebook entries under custom names +## (e.g. Grandstream UCM uses CallerIDName / AccountNumber / MobileNumber / ...). +## Setting a key to the empty string disables that role for this source. +## Comparison against the directory is ASCII-case-insensitive (per RFC 4512). + +## Attribute holding the contact's display name. +## (optional, default: "cn") +#attrName="cn" + +## Attribute holding a stable per-contact identifier (used for change detection). +## (optional, default: "uid") +#attrUid="uid" + +## Attribute holding the contact's organisation / company. +## (optional, default: "o") +#attrCompany="o" + +## Attribute holding the contact's primary email address. +## (optional, default: "mail") +#attrEmail="mail" + +## Attribute holding the commercial / office phone number. +## (optional, default: "telephoneNumber") +#attrCommercial="telephoneNumber" + +## Attribute holding the mobile phone number. +## (optional, default: "mobile") +#attrMobile="mobile" + +## Attribute holding the home phone number. +## (optional, default: "homePhone") +#attrHome="homePhone" + +## Attribute holding the binary contact photo. Set empty to skip avatar lookup. +## (optional, default: "jpegPhoto") +#attrAvatar="jpegPhoto" + ## Realm as it might be required for SASL LDAP bind ## default: "" ## (optional) diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.cpp b/src/contacts/ldap/LDAPAddressBookFeeder.cpp index a4ca0ed0..f8c1d21d 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.cpp +++ b/src/contacts/ldap/LDAPAddressBookFeeder.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "LDAPAddressBookFeeder.h" @@ -61,6 +62,7 @@ void LDAPAddressBookFeeder::resetFeeder() m_baseNumber = ""; m_sipStatusSubscriptableAttributes.clear(); m_blockInfo = {}; + m_attrs = {}; } void LDAPAddressBookFeeder::process() @@ -162,6 +164,15 @@ void LDAPAddressBookFeeder::processImpl(const QString &password) ldapConfig.saslAuthcid = settings.value("authcid", "").toString(); ldapConfig.saslAuthzid = settings.value("authzid", "").toString(); + m_attrs.name = settings.value("attrName", "cn").toByteArray(); + m_attrs.uid = settings.value("attrUid", "uid").toByteArray(); + m_attrs.company = settings.value("attrCompany", "o").toByteArray(); + m_attrs.email = settings.value("attrEmail", "mail").toByteArray(); + m_attrs.commercial = settings.value("attrCommercial", "telephoneNumber").toByteArray(); + m_attrs.mobile = settings.value("attrMobile", "mobile").toByteArray(); + m_attrs.home = settings.value("attrHome", "homePhone").toByteArray(); + m_attrs.avatar = settings.value("attrAvatar", "jpegPhoto").toByteArray(); + init(ldapConfig, scriptableAttributes.isEmpty() ? QStringList() : scriptableAttributes.split(QChar(',')), settings.value("baseNumber", "").toString()); @@ -191,15 +202,25 @@ void LDAPAddressBookFeeder::clearCStringlist(char **attrs) const void LDAPAddressBookFeeder::feedAddressBook() { - QStringList attributes = { "uid", "cn", "o", "telephoneNumber", - "mobile", "homePhone", "mail", "modifyTimestamp" }; + // modifyTimestamp is an operational attribute we always need for change + // detection. The remaining names come from the user-configurable map; a + // single attribute may serve multiple roles (e.g. AccountNumber as both + // uid and primary number on a Grandstream UCM), hence the QSet dedup. + QSet requested = { QByteArrayLiteral("modifyTimestamp") }; + for (const QByteArray *role : { &m_attrs.name, &m_attrs.uid, &m_attrs.company, &m_attrs.email, + &m_attrs.commercial, &m_attrs.mobile, &m_attrs.home }) { + if (!role->isEmpty()) { + requested.insert(*role); + } + } + int result = 0; size_t i = 0; - char **attrs = (char **)malloc((attributes.count() + 1) * sizeof(char *)); - for (auto &attr : std::as_const(attributes)) { + char **attrs = (char **)malloc((requested.size() + 1) * sizeof(char *)); + for (const QByteArray &attr : std::as_const(requested)) { size_t sz = attr.size() + 1; char *p = (char *)malloc(sz); - strncpy(p, attr.toLocal8Bit().toStdString().c_str(), sz); + strncpy(p, attr.constData(), sz); attrs[i++] = p; } attrs[i] = NULL; @@ -243,11 +264,16 @@ void LDAPAddressBookFeeder::feedAddressBook() void LDAPAddressBookFeeder::loadAvatars(const QList &contacts) { + if (m_attrs.name.isEmpty() || m_attrs.avatar.isEmpty()) { + return; + } + + const auto nameAttr = QString::fromLatin1(m_attrs.name); QStringList filterList; filterList.reserve(contacts.size()); for (const Contact *contact : contacts) { - filterList.append(QString("(cn=%1)").arg(contact->name())); + filterList.append(QString("(%1=%2)").arg(nameAttr, contact->name())); } LDAPInitializer::Config newConfig(m_ldapConfig); @@ -261,7 +287,11 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo { m_isProcessing = true; - QThread::create([this, ldapConfig]() { + // Snapshot the configured avatar attribute so the worker thread does not + // race with the main thread's reset/replay of m_attrs. + const QByteArray avatarAttr = m_attrs.avatar; + + QThread::create([this, ldapConfig, avatarAttr]() { char *a = nullptr; char *dnTemp = nullptr; BerElement *ber = nullptr; @@ -272,14 +302,19 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo LDAP *ldap = nullptr; LDAPMessage *msg = nullptr; - QStringList attributes = { "cn", "jpegPhoto", "modifyTimestamp" }; + // Honour the configurable avatar attribute. modifyTimestamp stays + // hardcoded as an operational LDAP attribute. + QList attributes = { QByteArrayLiteral("modifyTimestamp") }; + if (!avatarAttr.isEmpty()) { + attributes.append(avatarAttr); + } size_t i = 0; char **attrs = (char **)malloc((attributes.count() + 1) * sizeof(char *)); - for (auto &attr : std::as_const(attributes)) { + for (const QByteArray &attr : std::as_const(attributes)) { size_t sz = attr.size() + 1; char *p = (char *)malloc(sz); - strncpy(p, attr.toLocal8Bit().toStdString().c_str(), sz); + strncpy(p, attr.constData(), sz); attrs[i++] = p; } attrs[i] = NULL; @@ -354,12 +389,12 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo char *val = (**vals).bv_val; - if (strcmp(a, "jpegPhoto") == 0) { + if (!avatarAttr.isEmpty() && qstricmp(a, avatarAttr.constData()) == 0) { for (uint i = 0; i < (**vals).bv_len; ++i) { jpegPhoto.append(*val); val += sizeof(char); } - } else if (strcmp(a, "modifyTimestamp") == 0) { + } else if (qstricmp(a, "modifyTimestamp") == 0) { modifyTimestamp = QDateTime::fromString(val, "yyyyMMddhhmmsst"); } @@ -460,9 +495,16 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) char *errorMsg = nullptr; int numEntries = 0, numRefs = 0, result = 0, msgType = 0, parseResultCode = 0; LDAPMessage *msg = ldapMessage; - QString dn, cn, sourceUid, company, number, mail, modifyTimestamp; + QString dn, cn, sourceUid, company, mail, modifyTimestamp; QList phoneNumbers; + auto stripBaseNumber = [this](QString num) { + if (num.startsWith(m_baseNumber)) { + num = num.sliced(m_baseNumber.size()); + } + return num; + }; + numEntries = ldap_count_entries(m_ldap, msg); numRefs = ldap_count_references(m_ldap, msg); @@ -477,7 +519,6 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) dn = ""; cn = ""; company = ""; - number = ""; mail = ""; modifyTimestamp = ""; phoneNumbers.clear(); @@ -496,40 +537,44 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) if ((vals = ldap_get_values_len(m_ldap, msg, a)) != NULL) { const auto val = (**vals).bv_val; - if (strcmp(a, "cn") == 0) { + // Independent matches: a single LDAP attribute may serve + // more than one role at once when the user maps several + // roles to the same source attribute. ASCII-case-insensitive + // per RFC 4512, which treats attribute descriptions as + // case-insensitive. + auto matches = [a](const QByteArray &name) { + return !name.isEmpty() && qstricmp(a, name.constData()) == 0; + }; + + if (matches(m_attrs.name)) { cn = val; - } else if (strcmp(a, "o") == 0) { + } + if (matches(m_attrs.company)) { company = val; - } else if (strcmp(a, "uid") == 0) { + } + if (matches(m_attrs.uid)) { sourceUid = val; - } else if (strcmp(a, "mail") == 0) { + } + if (matches(m_attrs.email)) { mail = val; - } else if (strcmp(a, "modifyTimestamp") == 0) { + } + if (qstricmp(a, "modifyTimestamp") == 0) { modifyTimestamp = val; - } else if (strcmp(a, "telephoneNumber") == 0) { - number = val; - if (number.startsWith(m_baseNumber)) { - number = number.sliced(QString(m_baseNumber).size()); - } - phoneNumbers.append( - { Contact::NumberType::Commercial, number, - m_sipStatusSubscriptableAttributes.contains("telephoneNumber") }); - } else if (strcmp(a, "mobile") == 0) { - number = val; - if (number.startsWith(m_baseNumber)) { - number = number.sliced(QString(m_baseNumber).size()); - } - phoneNumbers.append( - { Contact::NumberType::Mobile, number, - m_sipStatusSubscriptableAttributes.contains("mobile") }); - } else if (strcmp(a, "homePhone") == 0) { - number = val; - if (number.startsWith(m_baseNumber)) { - number = number.sliced(QString(m_baseNumber).size()); - } - phoneNumbers.append( - { Contact::NumberType::Home, number, - m_sipStatusSubscriptableAttributes.contains("homePhone") }); + } + if (matches(m_attrs.commercial)) { + phoneNumbers.append({ Contact::NumberType::Commercial, stripBaseNumber(val), + m_sipStatusSubscriptableAttributes.contains( + QString::fromLatin1(m_attrs.commercial)) }); + } + if (matches(m_attrs.mobile)) { + phoneNumbers.append({ Contact::NumberType::Mobile, stripBaseNumber(val), + m_sipStatusSubscriptableAttributes.contains( + QString::fromLatin1(m_attrs.mobile)) }); + } + if (matches(m_attrs.home)) { + phoneNumbers.append({ Contact::NumberType::Home, stripBaseNumber(val), + m_sipStatusSubscriptableAttributes.contains( + QString::fromLatin1(m_attrs.home)) }); } ldap_value_free_len(vals); diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.h b/src/contacts/ldap/LDAPAddressBookFeeder.h index 2280476d..27d863ce 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.h +++ b/src/contacts/ldap/LDAPAddressBookFeeder.h @@ -2,6 +2,7 @@ #include #include +#include #include "IAddressBookFeeder.h" #include "LDAPInitializer.h" #include "Contact.h" @@ -46,6 +47,21 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder void resetFeeder(); + // Per-account mapping from semantic contact roles to the LDAP attribute + // names actually published by the directory. Empty entries disable that + // role for the current source. Defaults match standard inetOrgPerson. + struct AttributeMap + { + QByteArray name; + QByteArray uid; + QByteArray company; + QByteArray email; + QByteArray commercial; + QByteArray mobile; + QByteArray home; + QByteArray avatar; + }; + LDAPInitializer::Config m_ldapConfig; AddressBookManager *m_manager = nullptr; @@ -56,6 +72,7 @@ class LDAPAddressBookFeeder : public QObject, public IAddressBookFeeder QString m_baseNumber; QStringList m_sipStatusSubscriptableAttributes; BlockInfo m_blockInfo; + AttributeMap m_attrs; bool m_authFailed = false; int m_retryCount = 0; From 7b9a8b498b8e07e3b63e3195c960dc9be2d4c226 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 May 2026 14:45:38 +0000 Subject: [PATCH 081/122] chore(release): 2.2.0-beta.4 [skip ci] # [2.2.0-beta.4](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.3...v2.2.0-beta.4) (2026-05-04) ### Bug Fixes * do not wipe all contact sources on by-source removals ([#450](https://github.com/gonicus/gonnect/issues/450)) ([f89882f](https://github.com/gonicus/gonnect/commit/f89882f9be298c1f90de881239b44a50b749492b)) * ignore certain ssl errors when verifyServer is false (webdav) ([#451](https://github.com/gonicus/gonnect/issues/451)) ([428860a](https://github.com/gonicus/gonnect/commit/428860a320785b9a52888a824d28990f59a6beb6)) ### Features * **ldap:** configurable contact attribute mapping ([#445](https://github.com/gonicus/gonnect/issues/445)) ([4113384](https://github.com/gonicus/gonnect/commit/41133841f2c00c72229658a159abfae59330d7c9)) --- CHANGELOG.md | 13 +++++++++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b74f4cf..9b33aad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [2.2.0-beta.4](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.3...v2.2.0-beta.4) (2026-05-04) + + +### Bug Fixes + +* do not wipe all contact sources on by-source removals ([#450](https://github.com/gonicus/gonnect/issues/450)) ([f89882f](https://github.com/gonicus/gonnect/commit/f89882f9be298c1f90de881239b44a50b749492b)) +* ignore certain ssl errors when verifyServer is false (webdav) ([#451](https://github.com/gonicus/gonnect/issues/451)) ([428860a](https://github.com/gonicus/gonnect/commit/428860a320785b9a52888a824d28990f59a6beb6)) + + +### Features + +* **ldap:** configurable contact attribute mapping ([#445](https://github.com/gonicus/gonnect/issues/445)) ([4113384](https://github.com/gonicus/gonnect/commit/41133841f2c00c72229658a159abfae59330d7c9)) + # [2.2.0-beta.3](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.2...v2.2.0-beta.3) (2026-04-27) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d9f189..2bd41a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.3 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.4 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 67936dd8..2a945752 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.3 +version: 2.2.0-beta.4 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 64eab369..d5b76067 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From 526445bd4486fb932b1457da931bd91d97435b09 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 5 May 2026 16:37:48 +0200 Subject: [PATCH 082/122] chore: sync documentation (#452) * chore: sync documentation --------- Co-authored-by: Markus Bader --- resources/templates/sample.conf | 50 ++++++++++++++++++++- src/contacts/ldap/LDAPAddressBookFeeder.cpp | 16 +------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index 73a82c74..b6ffe3a0 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -33,6 +33,14 @@ autostart=false ## default: false #showCallWindowOnStartup=false +## Whether an own window decoration or the default decoration of the system shall be used. +## possible values: +## true: use the custom window decoration +## false: use the system window decoration +## auto: try to auto detect what is best (e.g. custom decoration on Gnome and system decoration on KDE) +## (optional, default: "auto") +#useOwnWindowDecoration=auto + ## signal 486 Busy here when already on an active call ## default: false busyOnBusy=true @@ -582,10 +590,50 @@ port=5061 ## found for this LDAP attribute will be used for that subscription. ## This is a performance optimization: when numbers saved under a specific attribute can and will never be subscriptable for SIP states (e.g. "mobile"), ## the client does not need to try. While not an excessive overhead, it might affect performance on slow systems and a large number of subscriptions. -## Multiple values must be comma-seperated, e.g. "telephoneNumber,mobile" +## Multiple values must be comma-seperated, e.g. "telephoneNumber,mobile". When custom attribute names are configured below, the values listed here +## must match those configured names exactly. ## (optional) #sipStatusSubscriptableAttributes="telephoneNumber" +## Map the semantic contact roles to the LDAP attribute names actually used by +## the directory. Defaults match the standard inetOrgPerson schema and need only +## be overridden for servers that publish phonebook entries under custom names +## (e.g. Grandstream UCM uses CallerIDName / AccountNumber / MobileNumber / ...). +## Setting a key to the empty string disables that role for this source. +## Comparison against the directory is ASCII-case-insensitive (per RFC 4512). + +## Attribute holding the contact's display name. +## (optional, default: "cn") +#attrName="cn" + +## Attribute holding a stable per-contact identifier (used for change detection). +## (optional, default: "uid") +#attrUid="uid" + +## Attribute holding the contact's organisation / company. +## (optional, default: "o") +#attrCompany="o" + +## Attribute holding the contact's primary email address. +## (optional, default: "mail") +#attrEmail="mail" + +## Attribute holding the commercial / office phone number. +## (optional, default: "telephoneNumber") +#attrCommercial="telephoneNumber" + +## Attribute holding the mobile phone number. +## (optional, default: "mobile") +#attrMobile="mobile" + +## Attribute holding the home phone number. +## (optional, default: "homePhone") +#attrHome="homePhone" + +## Attribute holding the binary contact photo. Set empty to skip avatar lookup. +## (optional, default: "jpegPhoto") +#attrAvatar="jpegPhoto" + ## Realm as it might be required for SASL LDAP bind ## default: "" ## (optional) diff --git a/src/contacts/ldap/LDAPAddressBookFeeder.cpp b/src/contacts/ldap/LDAPAddressBookFeeder.cpp index f8c1d21d..3eb801b2 100644 --- a/src/contacts/ldap/LDAPAddressBookFeeder.cpp +++ b/src/contacts/ldap/LDAPAddressBookFeeder.cpp @@ -202,10 +202,6 @@ void LDAPAddressBookFeeder::clearCStringlist(char **attrs) const void LDAPAddressBookFeeder::feedAddressBook() { - // modifyTimestamp is an operational attribute we always need for change - // detection. The remaining names come from the user-configurable map; a - // single attribute may serve multiple roles (e.g. AccountNumber as both - // uid and primary number on a Grandstream UCM), hence the QSet dedup. QSet requested = { QByteArrayLiteral("modifyTimestamp") }; for (const QByteArray *role : { &m_attrs.name, &m_attrs.uid, &m_attrs.company, &m_attrs.email, &m_attrs.commercial, &m_attrs.mobile, &m_attrs.home }) { @@ -285,11 +281,8 @@ void LDAPAddressBookFeeder::loadAvatars(const QList &contacts) void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapConfig) { - m_isProcessing = true; - - // Snapshot the configured avatar attribute so the worker thread does not - // race with the main thread's reset/replay of m_attrs. const QByteArray avatarAttr = m_attrs.avatar; + m_isProcessing = true; QThread::create([this, ldapConfig, avatarAttr]() { char *a = nullptr; @@ -302,8 +295,6 @@ void LDAPAddressBookFeeder::loadAllAvatars(const LDAPInitializer::Config &ldapCo LDAP *ldap = nullptr; LDAPMessage *msg = nullptr; - // Honour the configurable avatar attribute. modifyTimestamp stays - // hardcoded as an operational LDAP attribute. QList attributes = { QByteArrayLiteral("modifyTimestamp") }; if (!avatarAttr.isEmpty()) { attributes.append(avatarAttr); @@ -537,11 +528,6 @@ void LDAPAddressBookFeeder::processResult(LDAPMessage *ldapMessage) if ((vals = ldap_get_values_len(m_ldap, msg, a)) != NULL) { const auto val = (**vals).bv_val; - // Independent matches: a single LDAP attribute may serve - // more than one role at once when the user maps several - // roles to the same source attribute. ASCII-case-insensitive - // per RFC 4512, which treats attribute descriptions as - // case-insensitive. auto matches = [a](const QByteArray &name) { return !name.isEmpty() && qstricmp(a, name.constData()) == 0; }; From be3d71c419b252e4a103d5107ab9a9598b474742 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Tue, 5 May 2026 17:05:53 +0200 Subject: [PATCH 083/122] fix: ignore verification ssl errors if verifyServer=false (#453) --- .../recipes/qtwebdav/all/patches/ssl-certificates.patch | 8 +++++--- resources/flatpak/patches/qtwebdav-ssl-certificates.patch | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch index 8fa831c0..dccef181 100644 --- a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..98d7b59 100644 +index 091500d..558c475 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,7 @@ index 091500d..98d7b59 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -263,6 +273,30 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) +@@ -263,6 +273,32 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) { // user accepted this SSL certifcate already ==> ignore SSL errors reply->ignoreSslErrors(); @@ -46,11 +46,13 @@ index 091500d..98d7b59 100644 + return; + } + } ++ ++ reply->ignoreSslErrors(); + } else { // user has to check the SSL certificate and has to accept manually emit checkSslCertifcate(errors); -@@ -692,9 +726,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +728,22 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch index 8fa831c0..dccef181 100644 --- a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..98d7b59 100644 +index 091500d..558c475 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,7 @@ index 091500d..98d7b59 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -263,6 +273,30 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) +@@ -263,6 +273,32 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) { // user accepted this SSL certifcate already ==> ignore SSL errors reply->ignoreSslErrors(); @@ -46,11 +46,13 @@ index 091500d..98d7b59 100644 + return; + } + } ++ ++ reply->ignoreSslErrors(); + } else { // user has to check the SSL certificate and has to accept manually emit checkSslCertifcate(errors); -@@ -692,9 +726,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +728,22 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } From 1d1ca9683bd2afc74d4106aca7607b79c103df51 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 5 May 2026 15:17:07 +0000 Subject: [PATCH 084/122] chore(release): 2.2.0-beta.5 [skip ci] # [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-05) ### Bug Fixes * ignore verification ssl errors if verifyServer=false ([#453](https://github.com/gonicus/gonnect/issues/453)) ([be3d71c](https://github.com/gonicus/gonnect/commit/be3d71c419b252e4a103d5107ab9a9598b474742)) --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b33aad7..6a68ae49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-05) + + +### Bug Fixes + +* ignore verification ssl errors if verifyServer=false ([#453](https://github.com/gonicus/gonnect/issues/453)) ([be3d71c](https://github.com/gonicus/gonnect/commit/be3d71c419b252e4a103d5107ab9a9598b474742)) + # [2.2.0-beta.4](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.3...v2.2.0-beta.4) (2026-05-04) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bd41a13..129b3d44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.4 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.5 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 2a945752..6277102b 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.4 +version: 2.2.0-beta.5 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index d5b76067..2a6126fb 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From a0ac5fc10a9db5651490f5a551952ceba2d2715e Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Wed, 6 May 2026 08:09:49 +0200 Subject: [PATCH 085/122] Fix crash on exit (#454) --- src/sip/SIPManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sip/SIPManager.cpp b/src/sip/SIPManager.cpp index a441d06d..d444f251 100644 --- a/src/sip/SIPManager.cpp +++ b/src/sip/SIPManager.cpp @@ -411,6 +411,11 @@ void SIPManager::shutdown() m_ep.hangupAllCalls(); + // Release ownership of the log writer to pjsua2, which will delete it + // during libDestroy(). Without this, libDestroy() logs internally and + // calls into the already-deleted writer, or double-deletes it. + m_logWriter.release(); + try { delete m_ev; m_ep.libDestroy(); From f45a1d81f152bb8d2024dad675206302255e2126 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Wed, 6 May 2026 13:58:35 +0200 Subject: [PATCH 086/122] fix: if so configured, do not verify CA even if they are state (#455) --- .../all/patches/ssl-certificates.patch | 47 ++++--------------- .../patches/qtwebdav-ssl-certificates.patch | 47 ++++--------------- 2 files changed, 16 insertions(+), 78 deletions(-) diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch index dccef181..065e4d53 100644 --- a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..558c475 100644 +index 091500d..5483888 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,40 +19,7 @@ index 091500d..558c475 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -263,6 +273,32 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) - { - // user accepted this SSL certifcate already ==> ignore SSL errors - reply->ignoreSslErrors(); -+ -+ } else if (!m_verifyCa) { -+ -+ // Ignore these errors -+ static const QList verifyErrors = { -+ QSslError::SelfSignedCertificate, -+ QSslError::SelfSignedCertificateInChain, -+ QSslError::UnableToGetIssuerCertificate, -+ QSslError::UnableToGetLocalIssuerCertificate, -+ QSslError::UnableToVerifyFirstCertificate, -+ QSslError::CertificateUntrusted, -+ QSslError::CertificateExpired, -+ QSslError::CertificateNotYetValid, -+ QSslError::HostNameMismatch, -+ }; -+ -+ for (const auto& error : errors) { -+ if (!verifyErrors.contains(error.error())) { -+ emit checkSslCertifcate(errors); -+ reply->abort(); -+ return; -+ } -+ } -+ -+ reply->ignoreSslErrors(); -+ - } else { - // user has to check the SSL certificate and has to accept manually - emit checkSslCertifcate(errors); -@@ -692,9 +728,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,24 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } @@ -63,11 +30,13 @@ index 091500d..558c475 100644 QNetworkRequest QWebdav::buildRequest() { QNetworkRequest req; if (isSSL()) { -+ if (!m_sslCa.isEmpty()) { ++ ++ if (!m_verifyCa) { ++ QSslConfiguration sslConfig; ++ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); ++ req.setSslConfiguration(sslConfig); ++ } else if (!m_sslCa.isEmpty()) { + QSslConfiguration sslConfig; -+ if (!m_verifyCa) { -+ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); -+ } + sslConfig.addCaCertificates(m_sslCa); + req.setSslConfiguration(sslConfig); + } diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch index dccef181..065e4d53 100644 --- a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..558c475 100644 +index 091500d..5483888 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,40 +19,7 @@ index 091500d..558c475 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -263,6 +273,32 @@ void QWebdav::sslErrors(QNetworkReply *reply, const QList &errors) - { - // user accepted this SSL certifcate already ==> ignore SSL errors - reply->ignoreSslErrors(); -+ -+ } else if (!m_verifyCa) { -+ -+ // Ignore these errors -+ static const QList verifyErrors = { -+ QSslError::SelfSignedCertificate, -+ QSslError::SelfSignedCertificateInChain, -+ QSslError::UnableToGetIssuerCertificate, -+ QSslError::UnableToGetLocalIssuerCertificate, -+ QSslError::UnableToVerifyFirstCertificate, -+ QSslError::CertificateUntrusted, -+ QSslError::CertificateExpired, -+ QSslError::CertificateNotYetValid, -+ QSslError::HostNameMismatch, -+ }; -+ -+ for (const auto& error : errors) { -+ if (!verifyErrors.contains(error.error())) { -+ emit checkSslCertifcate(errors); -+ reply->abort(); -+ return; -+ } -+ } -+ -+ reply->ignoreSslErrors(); -+ - } else { - // user has to check the SSL certificate and has to accept manually - emit checkSslCertifcate(errors); -@@ -692,9 +728,22 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,24 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } @@ -63,11 +30,13 @@ index 091500d..558c475 100644 QNetworkRequest QWebdav::buildRequest() { QNetworkRequest req; if (isSSL()) { -+ if (!m_sslCa.isEmpty()) { ++ ++ if (!m_verifyCa) { ++ QSslConfiguration sslConfig; ++ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); ++ req.setSslConfiguration(sslConfig); ++ } else if (!m_sslCa.isEmpty()) { + QSslConfiguration sslConfig; -+ if (!m_verifyCa) { -+ sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); -+ } + sslConfig.addCaCertificates(m_sslCa); + req.setSslConfiguration(sslConfig); + } From 73d5c557ef6ee7d456fb656b23e765be3bc8cec6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 6 May 2026 14:58:34 +0000 Subject: [PATCH 087/122] chore(release): 2.2.0-beta.5 [skip ci] # [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-06) ### Bug Fixes * if so configured, do not verify CA even if they are state ([#455](https://github.com/gonicus/gonnect/issues/455)) ([f45a1d8](https://github.com/gonicus/gonnect/commit/f45a1d81f152bb8d2024dad675206302255e2126)) * ignore verification ssl errors if verifyServer=false ([#453](https://github.com/gonicus/gonnect/issues/453)) ([be3d71c](https://github.com/gonicus/gonnect/commit/be3d71c419b252e4a103d5107ab9a9598b474742)) --- CHANGELOG.md | 8 ++++++++ resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a68ae49..b07f957b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-06) + + +### Bug Fixes + +* if so configured, do not verify CA even if they are state ([#455](https://github.com/gonicus/gonnect/issues/455)) ([f45a1d8](https://github.com/gonicus/gonnect/commit/f45a1d81f152bb8d2024dad675206302255e2126)) +* ignore verification ssl errors if verifyServer=false ([#453](https://github.com/gonicus/gonnect/issues/453)) ([be3d71c](https://github.com/gonicus/gonnect/commit/be3d71c419b252e4a103d5107ab9a9598b474742)) + # [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-05) diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 2a6126fb..9d9f37b8 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From 6dcbf78a5b1b470ec966d8704b68e7d3e21da490 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Mon, 11 May 2026 08:51:25 +0200 Subject: [PATCH 088/122] fix: base edits on SSL default configuration (#459) --- .../qtwebdav/all/patches/ssl-certificates.patch | 13 ++++++------- .../flatpak/patches/qtwebdav-ssl-certificates.patch | 13 ++++++------- src/AuthManager.cpp | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch index 065e4d53..9e9b0afc 100644 --- a/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch +++ b/resources/conan/recipes/qtwebdav/all/patches/ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..5483888 100644 +index 091500d..f3fa4b8 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,7 @@ index 091500d..5483888 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -692,9 +702,24 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,23 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } @@ -31,15 +31,14 @@ index 091500d..5483888 100644 QNetworkRequest req; if (isSSL()) { + ++ QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); + if (!m_verifyCa) { -+ QSslConfiguration sslConfig; + sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); -+ req.setSslConfiguration(sslConfig); -+ } else if (!m_sslCa.isEmpty()) { -+ QSslConfiguration sslConfig; ++ } ++ if (!m_sslCa.isEmpty()) { + sslConfig.addCaCertificates(m_sslCa); -+ req.setSslConfiguration(sslConfig); + } ++ req.setSslConfiguration(sslConfig); + QString concatenated = m_username + ":" + m_password; QByteArray data = concatenated.toLocal8Bit().toBase64(); diff --git a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch index 065e4d53..9e9b0afc 100644 --- a/resources/flatpak/patches/qtwebdav-ssl-certificates.patch +++ b/resources/flatpak/patches/qtwebdav-ssl-certificates.patch @@ -1,5 +1,5 @@ diff --git a/qwebdav.cpp b/qwebdav.cpp -index 091500d..5483888 100644 +index 091500d..f3fa4b8 100644 --- a/qwebdav.cpp +++ b/qwebdav.cpp @@ -160,6 +160,16 @@ void QWebdav::acceptSslCertificate(const QString &sslCertDigestMd5, @@ -19,7 +19,7 @@ index 091500d..5483888 100644 void QWebdav::replyReadyRead() { auto reply = qobject_cast(QObject::sender()); -@@ -692,9 +702,24 @@ QNetworkReply* QWebdav::remove(const QString& path) +@@ -692,9 +702,23 @@ QNetworkReply* QWebdav::remove(const QString& path) return createRequest("DELETE", req); } @@ -31,15 +31,14 @@ index 091500d..5483888 100644 QNetworkRequest req; if (isSSL()) { + ++ QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); + if (!m_verifyCa) { -+ QSslConfiguration sslConfig; + sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); -+ req.setSslConfiguration(sslConfig); -+ } else if (!m_sslCa.isEmpty()) { -+ QSslConfiguration sslConfig; ++ } ++ if (!m_sslCa.isEmpty()) { + sslConfig.addCaCertificates(m_sslCa); -+ req.setSslConfiguration(sslConfig); + } ++ req.setSslConfiguration(sslConfig); + QString concatenated = m_username + ":" + m_password; QByteArray data = concatenated.toLocal8Bit().toBase64(); diff --git a/src/AuthManager.cpp b/src/AuthManager.cpp index deed46be..29c02e1f 100644 --- a/src/AuthManager.cpp +++ b/src/AuthManager.cpp @@ -40,7 +40,7 @@ void AuthManager::init() } settings.endGroup(); - QSslConfiguration sslConfig; + QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); if (!settings.value("generic/verifyServer", true).toBool()) { sslConfig.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); } @@ -372,7 +372,7 @@ void AuthManager::authenticateJitsiImpl(const QString &roomName) auto request = m_reqFactory.createRequest(QUrlQuery(QString("room=%1").arg(roomName))); - QSslConfiguration sslConfig; + QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); ReadOnlyConfdSettings settings; if (!settings.value("generic/verifyServer", true).toBool()) { From 22f2204dee55704edd60834052d256d19ba4d934 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 May 2026 07:03:21 +0000 Subject: [PATCH 089/122] chore(release): 2.2.0-beta.6 [skip ci] # [2.2.0-beta.6](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.5...v2.2.0-beta.6) (2026-05-11) ### Bug Fixes * base edits on SSL default configuration ([#459](https://github.com/gonicus/gonnect/issues/459)) ([6dcbf78](https://github.com/gonicus/gonnect/commit/6dcbf78a5b1b470ec966d8704b68e7d3e21da490)) --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b07f957b..a0844199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.2.0-beta.6](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.5...v2.2.0-beta.6) (2026-05-11) + + +### Bug Fixes + +* base edits on SSL default configuration ([#459](https://github.com/gonicus/gonnect/issues/459)) ([6dcbf78](https://github.com/gonicus/gonnect/commit/6dcbf78a5b1b470ec966d8704b68e7d3e21da490)) + # [2.2.0-beta.5](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.4...v2.2.0-beta.5) (2026-05-06) diff --git a/CMakeLists.txt b/CMakeLists.txt index 129b3d44..e7e8a500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.5 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.6 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 6277102b..070c8e08 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.5 +version: 2.2.0-beta.6 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 9d9f37b8..b7898127 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From 27057fab2949cbd1ebd3bb9fb9512b1ca53be23a Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 12:29:21 +0200 Subject: [PATCH 090/122] chore(deps): update github actions (#458) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Cajus Pollmeier --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/renovate.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1318c213..6c441151 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4 + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 36b82321..8a099e1a 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -58,7 +58,7 @@ jobs: } - name: Self-hosted Renovate - uses: renovatebot/github-action@79dc0ba74dc3de28db0a7aeb1d0b95d5bf5fde2a # v46.1.13 + uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14 with: docker-cmd-file: .github/renovate-entrypoint.sh docker-user: root From c46f066fd5f4f1aee410676f6e502ac682f4b265 Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Tue, 12 May 2026 09:58:07 +0200 Subject: [PATCH 091/122] feat: ical rrule parsing optimization (#457) * feat: add initial ical rrule parsing tweaks --- src/calendar/caldav/CalDAVEventFeeder.cpp | 32 +++++++++++++++++ src/calendar/eds/EDSEventFeeder.cpp | 44 ++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/calendar/caldav/CalDAVEventFeeder.cpp b/src/calendar/caldav/CalDAVEventFeeder.cpp index 0e7f50a1..ebd09701 100644 --- a/src/calendar/caldav/CalDAVEventFeeder.cpp +++ b/src/calendar/caldav/CalDAVEventFeeder.cpp @@ -273,6 +273,38 @@ bool CalDAVEventFeeder::processResponse(const QByteArray &data, const QString &s icalrecur_iterator *recurrenceIter = icalrecur_iterator_new(rrule, dtstart); if (recurrenceIter) { + // INFO: Since libical v3.0, a start time limit can be specified for recurrence + // iterators in order to reduce parsing overhead, i.e. for old events that are + // irrelevant to us. This only works for RRULE's that do not contain COUNT. + // https://github.com/libical/libical/blob/3.0/src/libical/icalrecur.h#L291 + if (rrule.count == 0) { + QDateTime timeRangeStart = m_config.timeRangeStart.toUTC(); + + struct icaltimetype recurStartCap = icaltime_null_time(); + recurStartCap.year = timeRangeStart.date().year(); + recurStartCap.month = timeRangeStart.date().month(); + recurStartCap.day = timeRangeStart.date().day(); + recurStartCap.hour = timeRangeStart.time().hour(); + recurStartCap.minute = timeRangeStart.time().minute(); + recurStartCap.second = timeRangeStart.time().second(); + recurStartCap.is_date = false; + + if (icaltime_is_valid_time(recurStartCap)) { + if (!icalrecur_iterator_set_start(recurrenceIter, recurStartCap)) { + onError(QString("Failed to set RRULE iterator starting date: %1") + .arg(icalerror_strerror(icalerrno))); + + icalrecur_iterator_free(recurrenceIter); + + return false; + } + } else { + qCDebug(lcCalDAVEventFeeder) + << "Invalid RRULE iterator starting date - skipping:" + << icalerror_strerror(icalerrno); + } + } + qint64 duration = start.secsTo(end); for (icaltimetype next = icalrecur_iterator_next(recurrenceIter); diff --git a/src/calendar/eds/EDSEventFeeder.cpp b/src/calendar/eds/EDSEventFeeder.cpp index 62bf21a3..d94274f8 100644 --- a/src/calendar/eds/EDSEventFeeder.cpp +++ b/src/calendar/eds/EDSEventFeeder.cpp @@ -523,7 +523,7 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList QString location = i_cal_component_get_location(component); QString description = i_cal_component_get_description(component); - if (isRecurrent) { // Recurrent origin event, parsed first + if (isRecurrent && rrule) { // Recurrent origin event, parsed first // Get EXDATE's ICalTime *exdate = NULL; QList exdates; @@ -536,8 +536,47 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList } exdatesById[id] = exdates; + ICalTime *recurStartCap = NULL; ICalRecurIterator *recurrenceIter = i_cal_recur_iterator_new(rrule, dtstart); if (recurrenceIter) { + // INFO: Since libical-glib v3.0, a start time limit can be specified for + // recurrence iterators in order to reduce parsing overhead, i.e. for old + // events that are irrelevant to us. This only works for RRULE's that + // do not contain COUNT. + // https://github.com/libical/libical/blob/3.0/src/libical/icalrecur.h#L291 + if (i_cal_recurrence_get_count(rrule) == 0) { + QDateTime timeRangeStart = m_timeRangeStart.toUTC(); + + recurStartCap = i_cal_time_new(); + if (recurStartCap) { + i_cal_time_set_date(recurStartCap, timeRangeStart.date().year(), + timeRangeStart.date().month(), + timeRangeStart.date().day()); + i_cal_time_set_time(recurStartCap, timeRangeStart.time().hour(), + timeRangeStart.time().minute(), + timeRangeStart.time().second()); + i_cal_time_set_is_date(recurStartCap, 0); + } + + if (recurStartCap && i_cal_time_is_valid_time(recurStartCap)) { + if (!i_cal_recur_iterator_set_start(recurrenceIter, recurStartCap)) { + qCCritical(lcEDSEventFeeder) + << "Failed to set RRULE iterator starting date:" + << i_cal_error_strerror(i_cal_errno_return()); + + g_clear_object(&recurStartCap); + i_cal_recur_iterator_free(recurrenceIter); + + Q_EMIT feederFailed(); + return; + } + } else { + qCDebug(lcEDSEventFeeder) + << "Invalid RRULE iterator starting date - skipping:" + << i_cal_error_strerror(i_cal_errno_return()); + } + } + qint64 duration = start.secsTo(end); for (ICalTime *next = i_cal_recur_iterator_next(recurrenceIter); @@ -569,6 +608,9 @@ void EDSEventFeeder::processEvents(QString clientName, QString clientUid, GSList } } + if (recurStartCap) { + g_clear_object(&recurStartCap); + } i_cal_recur_iterator_free(recurrenceIter); } } else if (isUpdatedRecurrence) { // Updates of a recurrent event instance From 8ee676d08a6e79eba5c31749fb62531dfa6c7069 Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Tue, 12 May 2026 14:27:37 +0200 Subject: [PATCH 092/122] fix: do not show favorites in "most called" list (#456) Co-authored-by: Cajus Pollmeier --- src/sip/NumberStats.cpp | 2 +- src/ui/SystemTrayMenu.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/sip/NumberStats.cpp b/src/sip/NumberStats.cpp index a890c626..ca05d657 100644 --- a/src/sip/NumberStats.cpp +++ b/src/sip/NumberStats.cpp @@ -200,7 +200,7 @@ QStringList NumberStats::mostCalled(quint8 limit, bool includeFavorites) const } } else { for (const auto *countObj : std::as_const(m_callCounts)) { - if (m_favoriteLookup.contains(countObj->phoneNumber)) { + if (!m_favoriteLookup.contains(countObj->phoneNumber)) { result.append(countObj->phoneNumber); } diff --git a/src/ui/SystemTrayMenu.cpp b/src/ui/SystemTrayMenu.cpp index 2f77585f..d20c43a0 100644 --- a/src/ui/SystemTrayMenu.cpp +++ b/src/ui/SystemTrayMenu.cpp @@ -406,9 +406,14 @@ void SystemTrayMenu::updateMostCalled() m_mostCalledActions.reserve(mostCalled.size()); for (const auto &number : mostCalled) { - const auto &numStat = *(NumberStats::instance().numberStat(number)); - auto action = new QAction(QIcon::fromTheme(contactIcon(numStat)), contactText(numStat), - m_trayIconMenu); + const auto numStat = NumberStats::instance().numberStat(number); + QAction *action = nullptr; + if (numStat) { + action = new QAction(QIcon::fromTheme(contactIcon(*numStat)), contactText(*numStat), + m_trayIconMenu); + } else { + action = new QAction(QString("⚫ %2").arg(number), m_trayIconMenu); + } m_trayIconMenu->insertAction(m_mostCalledSeparator, action); m_mostCalledActions.insert(number, action); connect(action, &QAction::triggered, this, From 75ed3f23ebe0730f10c43f6867d91cd1d54acf3f Mon Sep 17 00:00:00 2001 From: Markus Bader Date: Tue, 12 May 2026 14:29:25 +0200 Subject: [PATCH 093/122] fix(ui): do not treat room names beginning with digits as phone numbers (#461) --- src/contacts/PhoneNumberUtil.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/contacts/PhoneNumberUtil.cpp b/src/contacts/PhoneNumberUtil.cpp index 745d7093..96aa1f8f 100644 --- a/src/contacts/PhoneNumberUtil.cpp +++ b/src/contacts/PhoneNumberUtil.cpp @@ -82,23 +82,26 @@ ContactInfo PhoneNumberUtil::contactInfoBySipUrl(const QString &sipUrl) // Extract phone number from sip url auto phoneNumber = sipUrl; - static const QRegularExpression sipNumberRegex("^.*sips?:(.*)@.*$", - QRegularExpression::CaseInsensitiveOption); - phoneNumber.replace(sipNumberRegex, "\\1"); - static const QRegularExpression international("^000(.*)$"); - phoneNumber.replace(international, "+\\1"); + if (isSipUri(sipUrl)) { + static const QRegularExpression sipNumberRegex("^.*sips?:(.*)@.*$", + QRegularExpression::CaseInsensitiveOption); + phoneNumber.replace(sipNumberRegex, "\\1"); - static const QRegularExpression national("^00(.*)$"); - QString nationalPrefix = settings.value("generic/nationalPrefix").toString(); - if (!nationalPrefix.isEmpty()) { - phoneNumber.replace(national, nationalPrefix + "\\1"); - } + static const QRegularExpression international("^000(.*)$"); + phoneNumber.replace(international, "+\\1"); - static const QRegularExpression regional("^0(.*)$"); - QString regionalPrefix = settings.value("generic/regionalPrefix").toString(); - if (!regionalPrefix.isEmpty()) { - phoneNumber.replace(regional, regionalPrefix + "\\1"); + static const QRegularExpression national("^00(.*)$"); + QString nationalPrefix = settings.value("generic/nationalPrefix").toString(); + if (!nationalPrefix.isEmpty()) { + phoneNumber.replace(national, nationalPrefix + "\\1"); + } + + static const QRegularExpression regional("^0(.*)$"); + QString regionalPrefix = settings.value("generic/regionalPrefix").toString(); + if (!regionalPrefix.isEmpty()) { + phoneNumber.replace(regional, regionalPrefix + "\\1"); + } } ContactInfo info; From 18caebb4c39ec5df1c8455e03e7a19d2f988642c Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 07:24:36 +0200 Subject: [PATCH 094/122] chore(deps): update actions/create-github-app-token digest to bcd2ba4 (#463) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec54e398..a3b88abe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 8a099e1a..779fbdca 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Get token id: get_token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_KEY }} From 62d48f313a4aed5c971dc52d8e33a13e8396a58d Mon Sep 17 00:00:00 2001 From: "Leah J." <150920490+ljgonicus@users.noreply.github.com> Date: Wed, 13 May 2026 10:01:46 +0200 Subject: [PATCH 095/122] feat: rtt ui integration (#443) * feat: add rtt message & model --------- Co-authored-by: Cajus Pollmeier Co-authored-by: Markus Bader --- .gitignore | 3 +- docs/modules/ROOT/examples/sample.conf | 5 + resources/templates/sample.conf | 5 + src/CMakeLists.txt | 9 ++ src/rtt/RTTMessage.cpp | 6 + src/rtt/RTTMessage.h | 24 ++++ src/rtt/RTTModel.cpp | 84 +++++++++++++ src/rtt/RTTModel.h | 30 +++++ src/rtt/RTTProvider.cpp | 166 +++++++++++++++++++++++++ src/rtt/RTTProvider.h | 88 +++++++++++++ src/sip/SIPCall.h | 2 + src/ui/Theme.cpp | 8 ++ src/ui/Theme.h | 20 +++ src/ui/components/CallerBigAvatar.qml | 6 +- src/ui/components/RTTDisplay.qml | 158 +++++++++++++++++++++++ src/ui/components/pages/Call.qml | 140 +++++++++++++-------- 16 files changed, 696 insertions(+), 58 deletions(-) create mode 100644 src/rtt/RTTMessage.cpp create mode 100644 src/rtt/RTTMessage.h create mode 100644 src/rtt/RTTModel.cpp create mode 100644 src/rtt/RTTModel.h create mode 100644 src/rtt/RTTProvider.cpp create mode 100644 src/rtt/RTTProvider.h create mode 100644 src/ui/components/RTTDisplay.qml diff --git a/.gitignore b/.gitignore index daf0da6e..55733278 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ samples/callhistory.db .qtcreator - # Used https://www.toptal.com/developers/gitignore/api/qtcreator,vim # as a starting point. @@ -66,4 +65,4 @@ tags # Updater updater.sqlite __pycache__ -tmp/ \ No newline at end of file +tmp/ diff --git a/docs/modules/ROOT/examples/sample.conf b/docs/modules/ROOT/examples/sample.conf index 1d1a6590..2070633d 100644 --- a/docs/modules/ROOT/examples/sample.conf +++ b/docs/modules/ROOT/examples/sample.conf @@ -440,6 +440,11 @@ ## default: "" #voiceMailUri= +## Enable RTT functionality for all calls regardless of peer support. If enabled, the RTT console is always +## visible in the call interface. Otherwise, the RTT console only appears after receiving an RTT message. +## default: false +#showRealTimeTextConsole=false + #[auth0] ## The authentication scheme (e.g. “digest”). diff --git a/resources/templates/sample.conf b/resources/templates/sample.conf index b6ffe3a0..bbbf90e5 100644 --- a/resources/templates/sample.conf +++ b/resources/templates/sample.conf @@ -446,6 +446,11 @@ port=5061 ## default: "" #voiceMailUri= +## Enable RTT functionality for all calls regardless of peer support. If enabled, the RTT console is always +## visible in the call interface. Otherwise, the RTT console only appears after receiving an RTT message. +## default: false +#showRealTimeTextConsole=false + #[auth0] ## The authentication scheme (e.g. “digest”). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6610cb43..86baa5b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -410,6 +410,7 @@ qt_add_qml_module(gonnect ui/components/HistoryList.qml ui/components/MainTabBar.qml ui/components/ParticipantsList.qml + ui/components/RTTDisplay.qml ui/components/SettingsPage.qml ui/components/TogglerList.qml ui/components/VerticalLevelMeter.qml @@ -618,6 +619,13 @@ qt_add_qml_module(gonnect platform/SearchProvider.h ${PLATFORM_SOURCES} + rtt/RTTProvider.h + rtt/RTTProvider.cpp + rtt/RTTMessage.h + rtt/RTTMessage.cpp + rtt/RTTModel.h + rtt/RTTModel.cpp + ui/CallsModel.h ui/CallsModel.cpp ui/DateEventsModel.h @@ -790,6 +798,7 @@ target_include_directories(gonnect usb/busylight media platform + rtt ${logfault_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/src/rtt/RTTMessage.cpp b/src/rtt/RTTMessage.cpp new file mode 100644 index 00000000..3f89b303 --- /dev/null +++ b/src/rtt/RTTMessage.cpp @@ -0,0 +1,6 @@ +#include "RTTMessage.h" + +RTTMessage::RTTMessage(qint64 timestamp, const QString &message, bool isMe, bool isFinished) + : m_timestamp{ timestamp }, m_message{ message }, m_isMe{ isMe }, m_isFinished{ isFinished } +{ +} diff --git a/src/rtt/RTTMessage.h b/src/rtt/RTTMessage.h new file mode 100644 index 00000000..919e681d --- /dev/null +++ b/src/rtt/RTTMessage.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class RTTMessage +{ +public: + explicit RTTMessage(qint64 timestamp, const QString &message, bool isMe, bool isFinished); + + qint64 timestamp() const { return m_timestamp; }; + QString message() const { return m_message; }; + bool isMe() const { return m_isMe; }; + bool isFinished() const { return m_isFinished; }; + + void setMessage(const QString &message) { m_message = message; }; + void setIsFinished(bool finished) { m_isFinished = finished; }; + +private: + qint64 m_timestamp = 0; + QString m_message; + bool m_isMe = false; + bool m_isFinished = false; +}; diff --git a/src/rtt/RTTModel.cpp b/src/rtt/RTTModel.cpp new file mode 100644 index 00000000..2a2a9f10 --- /dev/null +++ b/src/rtt/RTTModel.cpp @@ -0,0 +1,84 @@ +#include "RTTModel.h" + +RTTModel::RTTModel(QObject *parent) : QAbstractListModel(parent) { } + +int RTTModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_messages.size(); +} + +QVariant RTTModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_messages.size()) { + return QVariant(); + } + + const RTTMessage &msg = m_messages.at(index.row()); + + switch (role) { + case static_cast(Roles::Timestamp): + return msg.timestamp(); + case static_cast(Roles::Message): + return msg.message(); + case static_cast(Roles::IsMe): + return msg.isMe(); + case static_cast(Roles::IsFinished): + return msg.isFinished(); + default: + return QVariant(); + } +} + +QHash RTTModel::roleNames() const +{ + return { + { static_cast(Roles::Timestamp), "timestamp" }, + { static_cast(Roles::Message), "message" }, + { static_cast(Roles::IsMe), "isMe" }, + { static_cast(Roles::IsFinished), "isFinished" }, + }; +} + +void RTTModel::reset() +{ + beginResetModel(); + m_messages.clear(); + endResetModel(); +} + +void RTTModel::addMessage(qint64 timestamp, const QString &message, bool isMe) +{ + beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size()); + m_messages.append(RTTMessage{ timestamp, message, isMe, false }); + endInsertRows(); +} + +void RTTModel::updateMessage(const QString &message, bool isMe, bool isFinished) +{ + if (m_messages.isEmpty()) { + return; + } + + // Retrieve the last unfinished message of the participant + int targetRow = -1; + for (int i = m_messages.size() - 1; i >= 0; --i) { + auto &msg = m_messages[i]; + if (msg.isMe() == isMe && !msg.isFinished()) { + targetRow = i; + break; + } + } + + if (targetRow == -1) { + return; + } + + m_messages[targetRow].setMessage(message); + m_messages[targetRow].setIsFinished(isFinished); + + QModelIndex idx = index(targetRow); + Q_EMIT dataChanged(idx, idx, + { static_cast(Roles::Message), static_cast(Roles::IsFinished) }); +} diff --git a/src/rtt/RTTModel.h b/src/rtt/RTTModel.h new file mode 100644 index 00000000..248a966b --- /dev/null +++ b/src/rtt/RTTModel.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "RTTMessage.h" + +class RTTModel : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + +public: + enum class Roles { Timestamp = Qt::UserRole + 1, Message, IsMe, IsFinished }; + + explicit RTTModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + Q_INVOKABLE void reset(); + + Q_INVOKABLE void addMessage(qint64 timestamp, const QString &text, bool isMe); + Q_INVOKABLE void updateMessage(const QString &message, bool isMe, bool isFinished); + +private: + QList m_messages; +}; diff --git a/src/rtt/RTTProvider.cpp b/src/rtt/RTTProvider.cpp new file mode 100644 index 00000000..e52693f3 --- /dev/null +++ b/src/rtt/RTTProvider.cpp @@ -0,0 +1,166 @@ +#include "GlobalCallState.h" +#include "SIPCallManager.h" + +#include "RTTProvider.h" + +RTTProvider::RTTProvider(QObject *parent) : QObject(parent) +{ + m_showRealTimeTextConsole = + m_settings.value("account0/showRealTimeTextConsole", false).toBool(); + + m_model = new RTTModel(this); + + connect(m_model, &RTTModel::rowsInserted, this, &RTTProvider::hasMessagesChanged); + connect(m_model, &RTTModel::rowsRemoved, this, &RTTProvider::hasMessagesChanged); + connect(m_model, &RTTModel::modelReset, this, &RTTProvider::hasMessagesChanged); + + connect(&GlobalCallState::instance(), &GlobalCallState::callInForegroundChanged, this, + [this]() { + // Disconnect from signals of the old call + if (m_establishedCall) { + disconnect(m_establishedCall); + m_establishedCall = QMetaObject::Connection(); + } + if (m_rttCall) { + disconnect(m_rttCall); + m_rttCall = QMetaObject::Connection(); + } + if (m_rttBubbleChanged) { + disconnect(m_rttBubbleChanged); + m_rttBubbleChanged = QMetaObject::Connection(); + } + if (m_rttBubbleCommitted) { + disconnect(m_rttBubbleCommitted); + m_rttBubbleCommitted = QMetaObject::Connection(); + } + m_call = nullptr; + + // Clear model containing the RTT messages + m_model->reset(); + m_newMessage = true; + + // Connect to signals of the new call + m_state = GlobalCallState::instance().callInForeground(); + if (m_state) { + m_call = SIPCallManager::instance().findCallById(m_state->uuid()); + } + + if (m_call) { + m_establishedCall = + connect(m_call, &SIPCall::establishedChanged, this, + &RTTProvider::isEstablishedCallChanged, Qt::QueuedConnection); + + m_rttCall = connect(m_call, &SIPCall::hasRttChanged, this, + &RTTProvider::isRttCallChanged, Qt::QueuedConnection); + + m_rttBubbleChanged = connect( + m_call, &SIPCall::rttBubbleChanged, this, + [this](QString message) { + if (m_newMessage) { + m_model->addMessage(QDateTime::currentMSecsSinceEpoch(), + message, false); + m_newMessage = false; + } else { + m_model->updateMessage(message, false, false); + } + }, + Qt::QueuedConnection); + + m_rttBubbleCommitted = connect( + m_call, &SIPCall::rttBubbleCommitted, this, + [this](QString message) { + m_model->updateMessage(message, false, true); + m_newMessage = true; + }, + Qt::QueuedConnection); + } + }); +} + +RTTProvider::~RTTProvider() +{ + if (m_establishedCall) { + disconnect(m_establishedCall); + m_establishedCall = QMetaObject::Connection(); + } + if (m_rttCall) { + disconnect(m_rttCall); + m_rttCall = QMetaObject::Connection(); + } + if (m_rttBubbleChanged) { + disconnect(m_rttBubbleChanged); + m_rttBubbleChanged = QMetaObject::Connection(); + } + if (m_rttBubbleCommitted) { + disconnect(m_rttBubbleCommitted); + m_rttBubbleCommitted = QMetaObject::Connection(); + } + m_call = nullptr; + + if (m_model) { + delete m_model; + m_model = nullptr; + } +} + +bool RTTProvider::hasMessages() +{ + if (m_model) { + return m_model->rowCount() > 0; + } + + return false; +} + +bool RTTProvider::isEstablishedCall() +{ + if (m_call) { + return m_call->isEstablished(); + } + + return false; +} + +bool RTTProvider::isRttCall() +{ + if (m_call) { + return m_call->hasRtt(); + } + + return false; +} + +void RTTProvider::rttSend(const QString &text) +{ + if (m_call) { + m_call->rttSend(text); + } +} + +void RTTProvider::rttSendLineSeperator() +{ + if (m_call) { + m_call->rttSendLineSeperator(); + } +} + +void RTTProvider::rttSendCRLF() +{ + if (m_call) { + m_call->rttSendCRLF(); + } +} + +void RTTProvider::rttSendBackspace() +{ + if (m_call) { + m_call->rttSendBackspace(); + } +} + +void RTTProvider::rttSendBell() +{ + if (m_call) { + m_call->rttSendBell(); + } +} diff --git a/src/rtt/RTTProvider.h b/src/rtt/RTTProvider.h new file mode 100644 index 00000000..0f9a4402 --- /dev/null +++ b/src/rtt/RTTProvider.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include "ReadOnlyConfdSettings.h" +#include "RTTModel.h" +#include "ICallState.h" +#include "SIPCall.h" + +class RTTProvider : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(RTTProvider) + + Q_PROPERTY(RTTModel *model READ model CONSTANT) + Q_PROPERTY(bool showRealTimeTextConsole READ showRealTimeTextConsole CONSTANT FINAL) + Q_PROPERTY(bool hasMessages READ hasMessages NOTIFY hasMessagesChanged FINAL) + Q_PROPERTY(bool isEstablishedCall READ isEstablishedCall NOTIFY isEstablishedCallChanged FINAL) + Q_PROPERTY(bool isRttCall READ isRttCall NOTIFY isRttCallChanged FINAL) + +public: + Q_REQUIRED_RESULT static RTTProvider &instance() + { + static RTTProvider *_instance = nullptr; + + if (_instance == nullptr) { + _instance = new RTTProvider(); + } + + return *_instance; + } + + ~RTTProvider(); + + RTTModel *model() const { return m_model; } + + /// Flags to determine QML visibility + bool showRealTimeTextConsole() const { return m_showRealTimeTextConsole; } + bool hasMessages(); + bool isEstablishedCall(); + bool isRttCall(); + + /// These methods wrap the SIPCall RTT functionality + Q_INVOKABLE void rttSend(const QString &text); + Q_INVOKABLE void rttSendLineSeperator(); + Q_INVOKABLE void rttSendCRLF(); + Q_INVOKABLE void rttSendBackspace(); + Q_INVOKABLE void rttSendBell(); + +Q_SIGNALS: + void hasMessagesChanged(); + void isEstablishedCallChanged(); + void isRttCallChanged(); + +private: + RTTProvider(QObject *parent = nullptr); + + ReadOnlyConfdSettings m_settings; + bool m_showRealTimeTextConsole = false; + + RTTModel *m_model = nullptr; + + ICallState *m_state = nullptr; + SIPCall *m_call = nullptr; + + QMetaObject::Connection m_establishedCall; + QMetaObject::Connection m_rttCall; + QMetaObject::Connection m_rttBubbleChanged; + QMetaObject::Connection m_rttBubbleCommitted; + + bool m_newMessage = false; +}; + +class RTTProviderWrapper +{ + Q_GADGET + QML_FOREIGN(RTTProvider) + QML_NAMED_ELEMENT(RTTProvider) + QML_SINGLETON + +public: + static RTTProvider *create(QQmlEngine *, QJSEngine *) { return &RTTProvider::instance(); } + +private: + RTTProviderWrapper() = default; +}; diff --git a/src/sip/SIPCall.h b/src/sip/SIPCall.h index a0cd2205..45dc9d64 100644 --- a/src/sip/SIPCall.h +++ b/src/sip/SIPCall.h @@ -93,6 +93,8 @@ class SIPCall : public ICallState, public pj::Call double rxJitter() const { return m_jitterRx; } double rxEffectiveDelay() const { return m_effDelayRx; } + bool hasRtt() const { return m_hasRtt; } + void rttSend(const QString &text); void rttSendLineSeperator(); void rttSendCRLF(); diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp index 94ebd204..e37ce5ed 100644 --- a/src/ui/Theme.cpp +++ b/src/ui/Theme.cpp @@ -111,6 +111,10 @@ void Theme::updateColorPalette() m_borderHeaderIconHovered = QColor(206, 201, 196); m_highlightColor = QColor(30, 57, 143, 76); m_paneColor = QColor(246, 245, 244); + m_rttBubbleSelf = QColor(45, 92, 229); + m_rttTextSelf = QColor(233, 233, 233); + m_rttBubbleOther = QColor(233, 233, 233); + m_rttTextOther = QColor(0, 0, 0); m_highContrastColor = QColor(0, 0, 0); m_backgroundColor = QColor(255, 255, 255); m_backgroundSecondaryColor = QColor(250, 250, 250); @@ -147,6 +151,10 @@ void Theme::updateColorPalette() m_accentColor = QColor(255, 255, 255, 120); m_highlightColor = QColor(15, 83, 158, 36); m_paneColor = QColor(45, 45, 45); + m_rttBubbleSelf = QColor(50, 96, 230); + m_rttTextSelf = QColor(255, 255, 255); + m_rttBubbleOther = QColor(44, 44, 46); + m_rttTextOther = QColor(255, 255, 255); } Q_EMIT colorPaletteChanged(); diff --git a/src/ui/Theme.h b/src/ui/Theme.h index be027a16..011409af 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -88,6 +88,11 @@ class Theme : public QObject Q_PROPERTY(QColor toolbarTopSeparator READ toolbarTopSeparator NOTIFY toolbarTopSeparatorChanged FINAL) + Q_PROPERTY(QColor rttBubbleSelf READ rttBubbleSelf NOTIFY rttBubbleSelfChanged FINAL) + Q_PROPERTY(QColor rttTextSelf READ rttTextSelf NOTIFY rttTextSelfChanged FINAL) + Q_PROPERTY(QColor rttBubbleOther READ rttBubbleOther NOTIFY rttBubbleOtherChanged FINAL) + Q_PROPERTY(QColor rttTextOther READ rttTextOther NOTIFY rttTextOtherChanged FINAL) + public: static Theme &instance() { @@ -163,6 +168,11 @@ class Theme : public QObject QColor toolbarText() const { return m_toolbarText; } QColor toolbarTopSeparator() const { return m_toolbarTopSeparator; } + QColor rttBubbleSelf() const { return m_rttBubbleSelf; } + QColor rttTextSelf() const { return m_rttTextSelf; } + QColor rttBubbleOther() const { return m_rttBubbleOther; } + QColor rttTextOther() const { return m_rttTextOther; } + private Q_SLOTS: void updateColorPalette(); void onThemeVariantChanged(); @@ -198,6 +208,11 @@ private Q_SLOTS: void toolbarTextChanged(); void toolbarTopSeparatorChanged(); + void rttBubbleSelfChanged(); + void rttTextSelfChanged(); + void rttBubbleOtherChanged(); + void rttTextOtherChanged(); + private: void setDarkMode(bool value); QString toCamelCase(const QString &str) const; @@ -261,6 +276,11 @@ private Q_SLOTS: QColor m_toolbarFieldTextFocus; QColor m_toolbarText; QColor m_toolbarTopSeparator; + + QColor m_rttBubbleSelf; + QColor m_rttTextSelf; + QColor m_rttBubbleOther; + QColor m_rttTextOther; }; class ThemeWrapper diff --git a/src/ui/components/CallerBigAvatar.qml b/src/ui/components/CallerBigAvatar.qml index cf27a381..59bf14a3 100644 --- a/src/ui/components/CallerBigAvatar.qml +++ b/src/ui/components/CallerBigAvatar.qml @@ -53,7 +53,7 @@ Item { anchors { horizontalCenter: parent.horizontalCenter top: avatarImage.bottom - topMargin: 50 + topMargin: 25 } Accessible.ignored: true @@ -68,7 +68,7 @@ Item { anchors { horizontalCenter: parent.horizontalCenter top: otherName.bottom - topMargin: 30 + topMargin: 15 } Accessible.ignored: true @@ -83,7 +83,7 @@ Item { anchors { horizontalCenter: parent.horizontalCenter top: otherName.bottom - topMargin: 30 + topMargin: 15 } Accessible.ignored: true diff --git a/src/ui/components/RTTDisplay.qml b/src/ui/components/RTTDisplay.qml new file mode 100644 index 00000000..35fd32b8 --- /dev/null +++ b/src/ui/components/RTTDisplay.qml @@ -0,0 +1,158 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls.Material +import base + +Item { + id: control + + Item { + id: rttChatContainer + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: rttInputContainer.top + bottomMargin: 24 + } + + ListView { + id: rttListView + anchors.fill: parent + clip: true + bottomMargin: 20 + model: RTTProvider.model + spacing: 12 + + Accessible.role: Accessible.List + Accessible.name: qsTr("RTT message list") + Accessible.description: qsTr("List of all the RTT messages of the current call") + + onCountChanged: () => Qt.callLater(rttListView.positionViewAtEnd) + + delegate: Item { + id: rttDelg + width: rttListView.width + height: rttBubble.height + + property int maxBubbleSize: rttDelg.width * 0.8 + + required property int timestamp + required property string message + required property bool isMe + required property bool isFinished + + Accessible.role: Accessible.ListItem + Accessible.name: qsTr("RTT message") + Accessible.description: qsTr("Selected RTT message from %1: %2").arg(rttDelg.isMe ? qsTr("you") : qsTr("call participant")).arg(rttDelg.message) + Accessible.focusable: true + + Rectangle { + id: rttBubble + width: Math.min(rttMessage.implicitWidth, rttDelg.maxBubbleSize) + height: rttMessage.height + radius: 8 + + onHeightChanged: () => Qt.callLater(rttListView.positionViewAtEnd) + + anchors.right: rttDelg.isMe ? parent.right : undefined + anchors.left: !rttDelg.isMe ? parent.left : undefined + + color: rttDelg.isMe ? Theme.rttBubbleSelf : Theme.rttBubbleOther + + Label { + id: rttMessage + text: rttDelg.message + color: rttDelg.isMe ? Theme.rttTextSelf : Theme.rttTextOther + width: parent.width + wrapMode: Label.Wrap + padding: 10 + anchors { + centerIn: parent + } + } + + Accessible.ignored: true + } + } + } + } + + Item { + id: rttInputContainer + height: rttInputField.y + rttInputField.height + rttInputField.anchors.margins + anchors { + left: rttChatContainer.left + right: rttChatContainer.right + bottom: parent.bottom + } + + property bool newMessage: true + + Timer { + id: rttTimeoutTimer + interval: 6000 + repeat: true + + onTriggered: () => { + rttListView.model.updateMessage(rttInputField.text, true, true) + rttInputContainer.newMessage = true + rttInputField.text = "" + } + } + + TextField { + id: rttInputField + placeholderText: "Message..." + anchors { + left: parent.left + right: parent.right + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + event.accepted = true + + rttListView.model.updateMessage(rttInputField.text, true, true) + rttInputContainer.newMessage = true + rttInputField.text = "" + rttTimeoutTimer.restart() + + RTTProvider.rttSendLineSeperator() + } else if (event.key === Qt.Key_Backspace) { + event.accepted = true + + rttInputField.text = rttInputField.text.slice(0, -1) + rttListView.model.updateMessage(rttInputField.text, true, false) + + RTTProvider.rttSendBackspace() + } else if (event.key === Qt.Key_G && (event.modifiers & Qt.ControlModifier)) { + event.accepted = true + + RTTProvider.rttSendBell() + } + } + + onTextEdited: { + const lastChar = rttInputField.text.charAt(rttInputField.length - 1); + if (lastChar !== "") { + if (rttInputContainer.newMessage) { + rttListView.model.addMessage(Date.now(), rttInputField.text, true) + rttInputContainer.newMessage = false + } else { + rttListView.model.updateMessage(rttInputField.text, true, false) + } + + rttTimeoutTimer.restart() + + RTTProvider.rttSend(lastChar) + } + } + + Accessible.role: Accessible.EditableText + Accessible.name: rttInputField.placeholderText + Accessible.focusable: true + } + } +} diff --git a/src/ui/components/pages/Call.qml b/src/ui/components/pages/Call.qml index 302f7a43..dbace147 100644 --- a/src/ui/components/pages/Call.qml +++ b/src/ui/components/pages/Call.qml @@ -1,6 +1,8 @@ pragma ComponentBehavior: Bound +import QtCore import QtQuick +import QtQuick.Layouts import QtQuick.Controls.Material import base @@ -16,6 +18,15 @@ Item { readonly property alias selectedCallItem: callSideBar.selectedCallItem + // INFO: RTT is currently limited to 1:1 calls, see GONGONNECT-396 + property bool isRttEnabled: !SIPCallManager.isConferenceMode + && RTTProvider.isEstablishedCall + && RTTProvider.isRttCall + && (RTTProvider.hasMessages || RTTProvider.showRealTimeTextConsole) + + // The avatar should grow in relation to card height, but only to maximum of 202-254 px + property int maxAvatarSize: control.isRttEnabled ? 202 : 254 + Keys.onPressed: (event) => { const callItem = callSideBar.selectedCallItem @@ -100,77 +111,100 @@ Item { } } - Loader { - id: avatarLoader - sourceComponent: SIPCallManager.isConferenceMode ? multiAvatarComponent : singleAvatarComponent + Column { anchors { top: topBar.bottom left: parent.left right: parent.right - bottom: parent.bottom + bottom: nameLabel.top - topMargin: Math.max(24, 24 + callMainCard.height / 2 - 254) + topMargin: control.isRttEnabled ? 30 + : Math.max(24, 24 + callMainCard.height / 2 - 254) + bottomMargin: 15 } - } - Component { - id: singleAvatarComponent - - CallerBigAvatar { - bubbleSize: Math.min(254 / 700 * callMainCard.height, 254) // Grow in relation to card height, but only to maximum of 254 px - name: callSideBar.selectedCallItem?.contactName ?? "" - avatarUrl: callSideBar.selectedCallItem?.hasAvatar ? ("file://" + callSideBar.selectedCallItem.avatarPath) : "" - isIncoming: topBar.isIncoming - isEstablished: topBar.isEstablished - isInProgress: topBar.isInProgress - isIncomingAudioLevel: callSideBar.selectedCallItem?.hasIncomingAudioLevel ?? false - anchors.horizontalCenter: parent.horizontalCenter + Loader { + id: avatarLoader + width: parent.width + height: control.isRttEnabled ? parent.height / 2 : parent.height + sourceComponent: SIPCallManager.isConferenceMode ? multiAvatarComponent : singleAvatarComponent + } + + Component { + id: singleAvatarComponent + + CallerBigAvatar { + bubbleSize: Math.min(control.maxAvatarSize / 850 * callMainCard.height, control.maxAvatarSize) + name: callSideBar.selectedCallItem?.contactName ?? "" + avatarUrl: callSideBar.selectedCallItem?.hasAvatar ? ("file://" + callSideBar.selectedCallItem.avatarPath) : "" + isIncoming: topBar.isIncoming + isEstablished: topBar.isEstablished + isInProgress: topBar.isInProgress + isIncomingAudioLevel: callSideBar.selectedCallItem?.hasIncomingAudioLevel ?? false + anchors.horizontalCenter: parent.horizontalCenter + } } - } - Component { - id: multiAvatarComponent + Component { + id: multiAvatarComponent - Item { - id: multiAvatarContainer // Additional item is necessary to center the row as the Loader uses the maximum available space + Item { + id: multiAvatarContainer - Row { - anchors.horizontalCenter: parent?.horizontalCenter + Row { + anchors.horizontalCenter: parent?.horizontalCenter - Repeater { - id: callsRepeater - model: CallsProxyModel { - onlyEstablishedCalls: true - CallsModel {} - } - delegate: Item { - id: callerDelg - implicitWidth: Math.max(0.75 * multiAvatarContainer.width / callsRepeater.count, bigAvatar.implicitWidth) - implicitHeight: bigAvatar.implicitHeight - - required property string contactName - required property string avatarPath - required property bool hasIncomingAudioLevel - required property bool isEstablished - required property bool isInProgress - required property bool isIncoming - - CallerBigAvatar { - id: bigAvatar - bubbleSize: Math.min(254 / 700 * callMainCard.height, 254) // Grow in relation to card height, but only to maximum of 254 px - name: callerDelg.contactName - avatarUrl: callerDelg.avatarPath - isIncoming: callerDelg.isIncoming - isEstablished: callerDelg.isEstablished - isInProgress: callerDelg.isInProgress - isIncomingAudioLevel: callerDelg.hasIncomingAudioLevel - anchors.horizontalCenter: parent.horizontalCenter + Repeater { + id: callsRepeater + model: CallsProxyModel { + onlyEstablishedCalls: true + CallsModel {} + } + delegate: Item { + id: callerDelg + implicitWidth: Math.max(0.75 * multiAvatarContainer.width / callsRepeater.count, bigAvatar.implicitWidth) + implicitHeight: bigAvatar.implicitHeight + + required property string contactName + required property string avatarPath + required property bool hasIncomingAudioLevel + required property bool isEstablished + required property bool isInProgress + required property bool isIncoming + + CallerBigAvatar { + id: bigAvatar + bubbleSize: Math.min(avatarLoader.control / 850 * callMainCard.height, control.maxAvatarSize) + name: callerDelg.contactName + avatarUrl: callerDelg.avatarPath + isIncoming: callerDelg.isIncoming + isEstablished: callerDelg.isEstablished + isInProgress: callerDelg.isInProgress + isIncomingAudioLevel: callerDelg.hasIncomingAudioLevel + anchors.horizontalCenter: parent.horizontalCenter + } } } } } } + + Loader { + id: rttLoader + width: parent.width / 2 + height: control.isRttEnabled ? parent.height / 2 : 0 + anchors.horizontalCenter: parent.horizontalCenter + sourceComponent: control.isRttEnabled ? rttComponent : undefined + } + + Component { + id: rttComponent + + RTTDisplay { + id: rttDisplay + } + } } Label { From 3c5412a250d5a7e3c3404432ae57d28465bac48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20G=C3=A9czi?= Date: Mon, 18 May 2026 09:28:15 +0200 Subject: [PATCH 096/122] fix: Ensure correct `tag` value type in flatpak manifest (#466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the following type of warnings, that local builds and CI runs both emit: ``` ** (flatpak-builder:553): WARNING **: 08:03:36.881: 132:14: '2.17' will be parsed as a number by many YAML parsers ``` See the last occurence of this warning in the CI: https://github.com/gonicus/gonnect/actions/runs/25786376705/job/75740691679 Signed-off-by: Ferenc Géczi --- resources/flatpak/de.gonicus.gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/flatpak/de.gonicus.gonnect.yml b/resources/flatpak/de.gonicus.gonnect.yml index b706c07f..a98aa646 100644 --- a/resources/flatpak/de.gonicus.gonnect.yml +++ b/resources/flatpak/de.gonicus.gonnect.yml @@ -143,7 +143,7 @@ modules: sources: - type: git url: https://github.com/pjsip/pjproject.git - tag: 2.17 + tag: '2.17' commit: 5a457451fa2712ba18e12b01738e8ff3af2b26fd x-checker-data: type: git From b3efeb834a0def64cb1d58b30d5ec4e8e8ae9251 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 12:24:28 +0200 Subject: [PATCH 097/122] chore(deps): update github/codeql-action digest to 9e0d7b8 (#464) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c441151..125abba2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -92,7 +92,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -119,6 +119,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4 with: category: "/language:${{matrix.language}}" From f7dd63a69d2231c92c768154eae99326a7ba1c29 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 18 May 2026 14:46:14 +0200 Subject: [PATCH 098/122] feat: windows signing (#462) * Add configuration for clientIdentifier Build option for MS_APPLICATION_IDENTIFIER for MSGraph OAuth * Added initial signing for Windows package * Adjust README.md * Access clientIdentifier from CI * App / Installer signing experiments * Rename to secerts * Remove duplicate entry * Tweak formatting * Add missing RTT hint * Enable main/release for test signing * ci: optimize workflow runs --------- Co-authored-by: Michael Neuendorf --- .github/workflows/codeql.yml | 14 ++ .github/workflows/gonnect.yml | 126 +++++++++++++++++- CMakeLists.txt | 2 +- README.md | 9 +- src/CMakeLists.txt | 2 + src/MSOAuthManager.cpp | 4 +- src/MSOAuthManagerConfig.h.in | 3 + .../msgraph/MSGraphEventFeederFactory.cpp | 5 +- .../msgraph/MSGraphAddressBookFactory.cpp | 5 +- 9 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 src/MSOAuthManagerConfig.h.in diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 125abba2..703c6e61 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,10 +16,24 @@ on: branches: - release - main + paths: + - 'src/**' + - 'external/**' + - '**/CMakeLists.txt' + - '**/conanfile.py' + - '**/conanfile.txt' + - '.github/workflows/codeql.yml' pull_request: branches: - release - main + paths: + - 'src/**' + - 'external/**' + - '**/CMakeLists.txt' + - '**/conanfile.py' + - '**/conanfile.txt' + - '.github/workflows/codeql.yml' schedule: - cron: '18 19 * * 5' diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 5a6db61a..a8d94c62 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -7,11 +7,25 @@ on: branches: - release - main + tags: + - 'v*' + paths: + - 'src/**' + - 'CMakeLists.txt' + - 'conanfile.py' + - 'resources/conan/**' + - '.github/workflows/gonnect.yml' # Use the release/main branch workflow/actions definitions rather the definitions in the pull_request pull_request: branches: - release - main + paths-ignore: + - 'src/**' + - 'CMakeLists.txt' + - 'conanfile.py' + - 'resources/conan/**' + - '.github/workflows/gonnect.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,11 +39,43 @@ env: permissions: contents: read pull-requests: read + id-token: write jobs: + changes: + runs-on: ubuntu-24.04 + outputs: + src: ${{ steps.filter.outputs.src }} + build: ${{ steps.filter.outputs.build }} + flatpak: ${{ steps.filter.outputs.flatpak }} + tests: ${{ steps.filter.outputs.tests }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Get changes + uses: dorny/paths-filter@34a36178c3e9b7aabf2e30ee0e7b38c44716d90b # v3 + id: filter + with: + filters: | + src: + - 'src/**' + - 'external/**' + build: + - '**/CMakeLists.txt' + - '**/conanfile.py' + - '**/conanfile.txt' + - '**/CMakePresets.json' + flatpak: + - 'resources/flatpak/**' + - 'CMakeLists.txt' + tests: + - 'tests/**' + build: runs-on: ubuntu-24.04 name: "Build GOnnect (Linux)" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.build == 'true' }} env: CC: clang CXX: clang++ @@ -61,6 +107,8 @@ jobs: build-windows: runs-on: windows-2025 name: "Build GOnnect (Windows)" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -73,16 +121,59 @@ jobs: - name: Configure CMake shell: cmd - run: cmake --preset conan-default -DENABLE_MSGRAPH=ON + run: cmake --preset conan-default -DENABLE_MSGRAPH=ON -DMS_APPLICATION_IDENTIFIER=${{ secrets.AZURE_CLIENT_ID }} env: NSIS_USE_FAST_COMPRESSION: "1" - - name: Build + - name: Build application run: cmake --build --preset conan-release - - name: Package + - name: Azure Login via OIDC + if: | + startsWith(github.ref, 'refs/tags/v') || + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/release' + uses: azure/login@v3 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Sign application + if: | + startsWith(github.ref, 'refs/tags/v') || + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/release' + uses: azure/artifact-signing-action@v1 + with: + endpoint: https://weu.codesigning.azure.net/ + signing-account-name: GONICUS-signing + certificate-profile-name: gonicus-profile + files-folder: build/src + files-folder-filter: exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + - name: Build installer run: cd build && cpack ${{github.workspace}} + - name: Sign installer + if: | + startsWith(github.ref, 'refs/tags/v') || + github.ref == 'refs/heads/main' || + github.ref == 'refs/heads/release' + uses: azure/artifact-signing-action@v0 + with: + endpoint: https://weu.codesigning.azure.net/ + signing-account-name: GONICUS-signing + certificate-profile-name: gonicus-profile + files-folder: build/ + files-folder-filter: exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + - name: Save installer uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -93,6 +184,8 @@ jobs: build-macos: runs-on: macos-26 name: "Build GOnnect (MacOS)" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -122,6 +215,8 @@ jobs: format: runs-on: ubuntu-24.04 name: "Code Formatting" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run clang-format style check for C/C++/Protobuf programs. @@ -134,6 +229,8 @@ jobs: clang: runs-on: ubuntu-24.04 name: "Static Code Analyzer" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' }} env: CC: clang CXX: clang++ @@ -166,6 +263,8 @@ jobs: runs-on: ubuntu-24.04 container: fedora:43 name: "Qt Semantics" + needs: changes + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} steps: - name: Install dependencies @@ -195,7 +294,7 @@ jobs: - name: Install qt run: | pip install aqtinstall - aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebchannel qtwebengine qtpositioning + aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebengine qtpositioning echo "PATH=$PATH:${{ github.workspace }}/Qt/${{ steps.get-qt.outputs.qt-version }}/gcc_64/bin" >> $GITHUB_ENV - name: Prepare conan @@ -241,6 +340,8 @@ jobs: flatpak: name: "Flatpak" runs-on: ubuntu-24.04 + needs: changes + if: ${{ needs.changes.outputs.flatpak == 'true' || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} container: image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 options: --privileged @@ -267,7 +368,8 @@ jobs: tests: runs-on: ubuntu-24.04 name: "Unit Tests" - needs: build + needs: [changes, build] + if: ${{ always() && (needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.tests == 'true') && (needs.build.result == 'success' || needs.build.result == 'skipped') }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -293,3 +395,17 @@ jobs: uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6 with: name: gonnect-tests + + ci-status: + needs: [changes, build, build-windows, build-macos, format, clang, clazy, flatpak, tests] + if: always() # Ensure this runs even if upstream jobs were skipped + runs-on: ubuntu-24.04 + steps: + - name: Verify pipeline status + run: | + # Contains evaluates to true if any job failed or was cancelled + if ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}; then + echo "Pipeline failed." + exit 1 + fi + echo "Pipeline passed!" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e7e8a500..3a3aae9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ set(APPLICATION_VENDOR "GONICUS GmbH") set(APPLICATION_ICON_NAME "${APPLICATION_SHORTNAME}" ) set(APPLICATION_URL "https://www.gonnect-project.org") -#GOnnect_VERSION -> override from git tag +set(MS_APPLICATION_IDENTIFIER "" CACHE STRING "Application ID used for OAUTH / Microsoft 365") set(CMAKE_COMPILE_WARNING_AS_ERROR ON) set(CMAKE_CXX_STANDARD 23) diff --git a/README.md b/README.md index aa0941a6..dc25c102 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ # Overview _GOnnect_ is a simple, easy to use UC client, designed for -Linux / _Flatpak_ based installations. It makes use of various Flatpak -portals and is meant to integrate well into modern Desktop Environments -like _KDE_ or _GNOME_. +Linux / _Flatpak_ based installations and Microsoft Windows. +It is designed to integrate seamlessly into modern desktop +environments. ![Overview](media/main-screen.png) @@ -28,6 +28,7 @@ Here's a short feature list: * Call forwarding * Conference calls with three parties + * Real-Time Text (RTT) over SIP * Busy state for supported sources * Configurable busy on active call * Address sources @@ -35,12 +36,14 @@ Here's a short feature list: * CardDAV * CSV * GNOME Contacts / Evolution Data Service + * Microsoft 365 Contacts (on Windows) * Configurable identities for outgoing calls * Configurable Togglers (i.e. for call queues, CFNL, etc.) * **Jitsi Meet Integration: Upgrade call to Videoconference** * Calendar sources for upcoming conferences * CalDAV * GNOME Calendar / Evolution Data Service + * Microsoft 365 Calendar (on Windows) * **Support for various hardware headsets (i.e. Yealink, Jabra)** * Support for various busylights * Custom audio device profiles or managed by your system diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86baa5b0..780a5098 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,6 +175,8 @@ find_package(Qt6Keychain REQUIRED) set_source_files_properties(ui/scripts/util.js PROPERTIES QT_QML_SOURCE_TYPENAME Util) +configure_file(MSOAuthManagerConfig.h.in MSOAuthManagerConfig.h) + if(FLATPAK) set(PLATFORM_SOURCES dbus/portal/AbstractPortal.h diff --git a/src/MSOAuthManager.cpp b/src/MSOAuthManager.cpp index 7bd3c579..0abddea4 100644 --- a/src/MSOAuthManager.cpp +++ b/src/MSOAuthManager.cpp @@ -3,6 +3,7 @@ #include "Credentials.h" #include "ViewHelper.h" #include "ErrorBus.h" +#include "MSOAuthManagerConfig.h" #include #include #include @@ -118,7 +119,8 @@ void MSOAuthManager::initAuthCodeFlow() // This should be a Gonicus registered app (see the Microsoft Entra admin center), with the // redirect urls (see below) and the supported scopes configured. // (A different identifier, registered by someone else, could also be used, if desired) - const QString clientIdentifier = settings.value("clientIdentifier").toString(); + const QString clientIdentifier = + settings.value("clientIdentifier", MS_APPLICATION_IDENTIFIER).toString(); const QString authorizationUrl = settings.value("authorizationUrl", QStringLiteral("https://login.microsoftonline.com/common/oauth2/v2.0/" diff --git a/src/MSOAuthManagerConfig.h.in b/src/MSOAuthManagerConfig.h.in new file mode 100644 index 00000000..c148d682 --- /dev/null +++ b/src/MSOAuthManagerConfig.h.in @@ -0,0 +1,3 @@ +#pragma once + +inline constexpr const char* MS_APPLICATION_IDENTIFIER = "@MS_APPLICATION_IDENTIFIER@"; diff --git a/src/calendar/msgraph/MSGraphEventFeederFactory.cpp b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp index e36e940c..50ebf2f0 100644 --- a/src/calendar/msgraph/MSGraphEventFeederFactory.cpp +++ b/src/calendar/msgraph/MSGraphEventFeederFactory.cpp @@ -2,6 +2,7 @@ #include "ReadOnlyConfdSettings.h" #include "MSGraphEventFeeder.h" #include "DateEventFeederManager.h" +#include "MSOAuthManagerConfig.h" MSGraphEventFeederFactory::MSGraphEventFeederFactory(QObject *parent) : QObject{ parent } { } @@ -14,7 +15,9 @@ QStringList MSGraphEventFeederFactory::configurations() const const auto group = QStringLiteral("msgraphcalendar"); if (settings.childGroups().contains(msOAuthGroup) && settings.childGroups().contains(group)) { const auto &clientIdentifier = - settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier")).toString(); + settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier"), + MS_APPLICATION_IDENTIFIER) + .toString(); const bool enabled = settings.value(group + QStringLiteral("enabled"), true).toBool(); if (enabled && !clientIdentifier.isEmpty()) { diff --git a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp index ec8b43df..9d95f84d 100644 --- a/src/contacts/msgraph/MSGraphAddressBookFactory.cpp +++ b/src/contacts/msgraph/MSGraphAddressBookFactory.cpp @@ -1,6 +1,7 @@ #include "MSGraphAddressBookFactory.h" #include "MSGraphAddressBookFeeder.h" #include "ReadOnlyConfdSettings.h" +#include "MSOAuthManagerConfig.h" QStringList MSGraphAddressBookFactory::configurations() const { @@ -11,7 +12,9 @@ QStringList MSGraphAddressBookFactory::configurations() const const auto group = QStringLiteral("msgraphcontacts"); if (settings.childGroups().contains(msOAuthGroup) && settings.childGroups().contains(group)) { const auto &clientIdentifier = - settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier")).toString(); + settings.value(msOAuthGroup + QStringLiteral("/clientIdentifier"), + MS_APPLICATION_IDENTIFIER) + .toString(); const bool enabled = settings.value(group + QStringLiteral("enabled"), true).toBool(); if (enabled && !clientIdentifier.isEmpty()) { From 88a1c8e1556f1e4aebc0de582a1cb4831f02bc13 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Mon, 18 May 2026 12:48:45 +0000 Subject: [PATCH 099/122] ci: fix paths-filter sha --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index a8d94c62..4db1179c 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -53,7 +53,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Get changes - uses: dorny/paths-filter@34a36178c3e9b7aabf2e30ee0e7b38c44716d90b # v3 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d id: filter with: filters: | From 6b56ca18137e0393aeb5dfa75afcbe55bd36127c Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Mon, 18 May 2026 12:52:37 +0000 Subject: [PATCH 100/122] ci: trigger GOnnect workflow also on workflow changes --- .github/workflows/gonnect.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 4db1179c..f196cab5 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -49,6 +49,7 @@ jobs: build: ${{ steps.filter.outputs.build }} flatpak: ${{ steps.filter.outputs.flatpak }} tests: ${{ steps.filter.outputs.tests }} + workflow: ${{ steps.filter.outputs.workflow }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -70,12 +71,14 @@ jobs: - 'CMakeLists.txt' tests: - 'tests/**' + workflow: + - '.github/workflows/gonnect.yml' build: runs-on: ubuntu-24.04 name: "Build GOnnect (Linux)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.build == 'true' }} + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} env: CC: clang CXX: clang++ @@ -108,7 +111,7 @@ jobs: runs-on: windows-2025 name: "Build GOnnect (Windows)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -185,7 +188,7 @@ jobs: runs-on: macos-26 name: "Build GOnnect (MacOS)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} + if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -341,7 +344,7 @@ jobs: name: "Flatpak" runs-on: ubuntu-24.04 needs: changes - if: ${{ needs.changes.outputs.flatpak == 'true' || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} + if: ${{ needs.changes.outputs.flatpak == 'true' || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} container: image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 options: --privileged From db9b0206ab03739862d07eb62492fce4f21927b6 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 18 May 2026 15:46:40 +0200 Subject: [PATCH 101/122] Adjust signing path --- .github/workflows/gonnect.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index f196cab5..f5d3aa22 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -152,7 +152,7 @@ jobs: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing certificate-profile-name: gonicus-profile - files-folder: build/src + files-folder: build/Release/src files-folder-filter: exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com @@ -171,7 +171,7 @@ jobs: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing certificate-profile-name: gonicus-profile - files-folder: build/ + files-folder: build files-folder-filter: exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com @@ -411,4 +411,4 @@ jobs: echo "Pipeline failed." exit 1 fi - echo "Pipeline passed!" \ No newline at end of file + echo "Pipeline passed!" From dea634c760f3820f602d6e39b33fb62e4b09f8b7 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 18 May 2026 16:09:43 +0200 Subject: [PATCH 102/122] Fix exe path Removed left over /src --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index f5d3aa22..0892ac53 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -152,7 +152,7 @@ jobs: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing certificate-profile-name: gonicus-profile - files-folder: build/Release/src + files-folder: build/Release files-folder-filter: exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com From 40f10d775b18c65a4d45d6467ec1d489732ef791 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Mon, 18 May 2026 16:45:03 +0200 Subject: [PATCH 103/122] ci: update azure artifact signing action --- .github/workflows/gonnect.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 0892ac53..015c1a32 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -166,7 +166,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/artifact-signing-action@v0 + uses: azure/artifact-signing-action@v1 with: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing From 6ab46248f61501b18d06064d3ce918490318c020 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Mon, 18 May 2026 15:15:45 +0000 Subject: [PATCH 104/122] ci: allow choosing job to run on manual execution --- .github/workflows/gonnect.yml | 51 +++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 015c1a32..68d9fdf7 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -2,6 +2,31 @@ name: GOnnect Build on: workflow_dispatch: # can be manually dispatched under GitHub's "Actions" tab + inputs: + run_build: + description: Run build job + type: boolean + run_build_windows: + description: Run build-windows job + type: boolean + run_build_macos: + description: Run build-macos job + type: boolean + run_format: + description: Run format job + type: boolean + run_clang: + description: Run clang job + type: boolean + run_clazy: + description: Run clazy job + type: boolean + run_flatpak: + description: Run flatpak job + type: boolean + run_tests: + description: Run tests job + type: boolean # Run the build only, if the branch is release or main or a PR with release or main as target is used push: branches: @@ -49,7 +74,7 @@ jobs: build: ${{ steps.filter.outputs.build }} flatpak: ${{ steps.filter.outputs.flatpak }} tests: ${{ steps.filter.outputs.tests }} - workflow: ${{ steps.filter.outputs.workflow }} + resources: ${{ steps.filter.outputs.resources }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -71,14 +96,18 @@ jobs: - 'CMakeLists.txt' tests: - 'tests/**' - workflow: - - '.github/workflows/gonnect.yml' + resources: + - 'resources/artwork/**' + - 'resources/emojis/**' + - 'resources/font/**' + - 'resources/sounds/**' + - 'resources/templates/**' build: runs-on: ubuntu-24.04 name: "Build GOnnect (Linux)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} + if: ${{ inputs.run_build || needs.changes.outputs.src == 'true' || needs.changes.outputs.tests == 'true' || needs.changes.outputs.build == 'true' }} env: CC: clang CXX: clang++ @@ -111,7 +140,7 @@ jobs: runs-on: windows-2025 name: "Build GOnnect (Windows)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} + if: ${{ inputs.run_build_windows || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -188,7 +217,7 @@ jobs: runs-on: macos-26 name: "Build GOnnect (MacOS)" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} + if: ${{ inputs.run_build_macos || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -219,7 +248,7 @@ jobs: runs-on: ubuntu-24.04 name: "Code Formatting" needs: changes - if: ${{ needs.changes.outputs.src == 'true' }} + if: ${{ inputs.run_format || needs.changes.outputs.src == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Run clang-format style check for C/C++/Protobuf programs. @@ -233,7 +262,7 @@ jobs: runs-on: ubuntu-24.04 name: "Static Code Analyzer" needs: changes - if: ${{ needs.changes.outputs.src == 'true' }} + if: ${{ inputs.run_clang || needs.changes.outputs.src == 'true' }} env: CC: clang CXX: clang++ @@ -267,7 +296,7 @@ jobs: container: fedora:43 name: "Qt Semantics" needs: changes - if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} + if: ${{ inputs.run_clazy || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' }} steps: - name: Install dependencies @@ -344,7 +373,7 @@ jobs: name: "Flatpak" runs-on: ubuntu-24.04 needs: changes - if: ${{ needs.changes.outputs.flatpak == 'true' || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.workflow == 'true' }} + if: ${{ inputs.run_flatpak || needs.changes.outputs.flatpak == 'true' || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' }} container: image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 options: --privileged @@ -372,7 +401,7 @@ jobs: runs-on: ubuntu-24.04 name: "Unit Tests" needs: [changes, build] - if: ${{ always() && (needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.tests == 'true') && (needs.build.result == 'success' || needs.build.result == 'skipped') }} + if: ${{ (inputs.run_tests || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.tests == 'true') && needs.build.result == 'success' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 From 516c416cf2e464a71a57c963bbcf565acc2cb2b8 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 19 May 2026 08:08:11 +0200 Subject: [PATCH 105/122] Adjust Qt version --- .github/workflows/gonnect.yml | 2 +- conanfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 68d9fdf7..96d111df 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -326,7 +326,7 @@ jobs: - name: Install qt run: | pip install aqtinstall - aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebengine qtpositioning + aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebengine qtwebchannel qtpositioning echo "PATH=$PATH:${{ github.workspace }}/Qt/${{ steps.get-qt.outputs.qt-version }}/gcc_64/bin" >> $GITHUB_ENV - name: Prepare conan diff --git a/conanfile.py b/conanfile.py index 11dbdfde..a60f9f1f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -79,7 +79,7 @@ def requirements(self): self.requires("libpulse/system") if self.options.with_conan_qt: - self.requires("qt/6.10.2") + self.requires("qt/6.11.1") self.requires("mpg123/1.33.0", override=True) self.requires("wayland/1.24.0", override=True) From 6e4cf16c18f2fc8987286297d5773109d99ea6ab Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Tue, 19 May 2026 08:41:03 +0200 Subject: [PATCH 106/122] Use Qt 6.10 for now Looks like the Windows binaries for 6.11.1 are not available for whatever reason. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index a60f9f1f..f61cdd3d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -79,7 +79,7 @@ def requirements(self): self.requires("libpulse/system") if self.options.with_conan_qt: - self.requires("qt/6.11.1") + self.requires("qt/6.10.3") self.requires("mpg123/1.33.0", override=True) self.requires("wayland/1.24.0", override=True) From 3cb77a1691ddabbc5dca86f6209b0a20573a9084 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 08:47:15 +0200 Subject: [PATCH 107/122] chore(deps): pin dependencies (#468) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/gonnect.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index 96d111df..d55009f7 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -165,7 +165,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/login@v3 + uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} @@ -176,7 +176,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1 with: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing @@ -195,7 +195,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1 with: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing From 75e268ce5aabac8f7cc918e92b2d3ba45057c874 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Tue, 19 May 2026 06:51:53 +0000 Subject: [PATCH 108/122] ci: fix missing base branch on manual runs --- .github/workflows/gonnect.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index d55009f7..b1f1c22f 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -82,6 +82,8 @@ jobs: uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d id: filter with: + # Set the base branch, if not a pull request + base: ${{ !contains(github.event_name, 'pull_reguest') && github.ref_name || null }} filters: | src: - 'src/**' @@ -326,7 +328,7 @@ jobs: - name: Install qt run: | pip install aqtinstall - aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebengine qtwebchannel qtpositioning + aqt install-qt --outputdir ${{ github.workspace }}/Qt linux desktop ${{ steps.get-qt.outputs.qt-version }} linux_gcc_64 -m qt5compat qtgrpc qtimageformats qtmultimedia qtnetworkauth qtwebchannel qtwebengine qtpositioning echo "PATH=$PATH:${{ github.workspace }}/Qt/${{ steps.get-qt.outputs.qt-version }}/gcc_64/bin" >> $GITHUB_ENV - name: Prepare conan From 089d26a7edce20b02052f8e2db07eaa895af2666 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Tue, 19 May 2026 07:06:18 +0000 Subject: [PATCH 109/122] ci: skip changes job on manual runs --- .github/workflows/gonnect.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index b1f1c22f..a1a17772 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -69,6 +69,8 @@ permissions: jobs: changes: runs-on: ubuntu-24.04 + # Job selection comes from inputs, if started manually. No need to detect the changes. + if: ${{ github.event_name != 'workflow_dispatch' }} outputs: src: ${{ steps.filter.outputs.src }} build: ${{ steps.filter.outputs.build }} @@ -83,7 +85,6 @@ jobs: id: filter with: # Set the base branch, if not a pull request - base: ${{ !contains(github.event_name, 'pull_reguest') && github.ref_name || null }} filters: | src: - 'src/**' From 608bf086c092987a5122d7a52ae9ce8964a7fcb5 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Tue, 19 May 2026 07:44:01 +0000 Subject: [PATCH 110/122] ci: detect more changed files --- .github/workflows/gonnect.yml | 40 ++++++++++++++++------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index a1a17772..a48f2c3e 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -34,23 +34,11 @@ on: - main tags: - 'v*' - paths: - - 'src/**' - - 'CMakeLists.txt' - - 'conanfile.py' - - 'resources/conan/**' - - '.github/workflows/gonnect.yml' # Use the release/main branch workflow/actions definitions rather the definitions in the pull_request pull_request: branches: - release - main - paths-ignore: - - 'src/**' - - 'CMakeLists.txt' - - 'conanfile.py' - - 'resources/conan/**' - - '.github/workflows/gonnect.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -67,16 +55,19 @@ permissions: id-token: write jobs: + # Detect changes to allow selecting the jobs to run. + # If the workflow is triggered manually, the selection comes from the inputs there. changes: runs-on: ubuntu-24.04 - # Job selection comes from inputs, if started manually. No need to detect the changes. - if: ${{ github.event_name != 'workflow_dispatch' }} outputs: - src: ${{ steps.filter.outputs.src }} - build: ${{ steps.filter.outputs.build }} - flatpak: ${{ steps.filter.outputs.flatpak }} - tests: ${{ steps.filter.outputs.tests }} - resources: ${{ steps.filter.outputs.resources }} + # Job selection comes from inputs, if started manually. No need to use detected changes. + src: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.src }} + build: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.build }} + flatpak: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.flatpak }} + tests: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.tests }} + resources: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.resources }} + resources-windows: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.resources-windows }} + resources-macos: ${{ github.event_name != 'workflow_dispatch' && steps.filter.outputs.resources-macos }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -84,7 +75,6 @@ jobs: uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d id: filter with: - # Set the base branch, if not a pull request filters: | src: - 'src/**' @@ -105,6 +95,12 @@ jobs: - 'resources/font/**' - 'resources/sounds/**' - 'resources/templates/**' + - 'migrate/**' + - 'i18n/**/*.ts' + resources-windows: + - 'resources/windows/**' + resources-macos: + - 'resources/macos/**' build: runs-on: ubuntu-24.04 @@ -143,7 +139,7 @@ jobs: runs-on: windows-2025 name: "Build GOnnect (Windows)" needs: changes - if: ${{ inputs.run_build_windows || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' }} + if: ${{ inputs.run_build_windows || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' || needs.changes.outputs.resources-windows == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -220,7 +216,7 @@ jobs: runs-on: macos-26 name: "Build GOnnect (MacOS)" needs: changes - if: ${{ inputs.run_build_macos || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' }} + if: ${{ inputs.run_build_macos || needs.changes.outputs.src == 'true' || needs.changes.outputs.build == 'true' || needs.changes.outputs.resources == 'true' || needs.changes.outputs.resources-macos == 'true' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 From b045699741c9adcd1f54862fa419094e666d5ba2 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 12:05:09 +0200 Subject: [PATCH 111/122] chore(deps): update azure/artifact-signing-action action to v2 (#469) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Co-authored-by: Mik- --- .github/workflows/gonnect.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gonnect.yml b/.github/workflows/gonnect.yml index a48f2c3e..62f39e63 100644 --- a/.github/workflows/gonnect.yml +++ b/.github/workflows/gonnect.yml @@ -175,7 +175,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1 + uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2 with: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing @@ -194,7 +194,7 @@ jobs: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' - uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1 + uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2 with: endpoint: https://weu.codesigning.azure.net/ signing-account-name: GONICUS-signing From 865fcd975f5d030674ff2d916c0eea5e1fa29456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20G=C3=A9czi?= Date: Wed, 20 May 2026 09:02:08 +0200 Subject: [PATCH 112/122] feat: add template for 1&1 (#465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add template for 1&1 Corresponds to setting up 1&1 VOIP according to the [official instructions]( https://hilfe-center.1und1.de/konfigurationsdaten-internettelefonie). Signed-off-by: Ferenc Géczi * fix: use TLS transport in 1und1 template Signed-off-by: Ferenc Géczi --------- Signed-off-by: Ferenc Géczi Co-authored-by: Cajus Pollmeier --- resources/templates/1und1.conf | 70 +++++++++++++++++++++++++++++++ resources/templates/templates.qrc | 1 + 2 files changed, 71 insertions(+) create mode 100644 resources/templates/1und1.conf diff --git a/resources/templates/1und1.conf b/resources/templates/1und1.conf new file mode 100644 index 00000000..e3cb99de --- /dev/null +++ b/resources/templates/1und1.conf @@ -0,0 +1,70 @@ +[template] +name=1und1 + +[templateField0] +name=Phonenumber +name[de]=Rufnummer +description=Your local phonenumber +description[de]=Zu registrierende Telefonnummer +regex=[0-9]+ +target=PHONENUMBER +type=text + +[ua] +stunTryIpv6=false +stunIgnoreFailure=true +mwiUnsolicitedEnabled=true +enableUpnp=false +upnpIfName= +maxCalls=10 +natTypeInSdp=1 +stunServers=stun.1und1.de + +[media] +noVad=false +clockRate=16000 +sndClockRate= +sndRecLatency=100 +sndPlayLatency=160 +quality=4 +audioFramePtime=20 +jbInit=-1 +jbMinPre=-1 +jbMaxPre=-1 +jbMax=-1 +ecTailLen=200 + +[account0] +userUri=sip:%TPL[PHONENUMBER]%@tls-sip.1und1.de +registrarUri=sip:tls-sip.1und1.de +proxies=sip:tls-sip.1und1.de +srtpUse=mandatory +srtpSecureSignaling=0 +lockCodecEnabled=false +port=5061 +verifyServer=false +contactRewriteUse=1 +contactRewriteMethod=always-update +sipStunUse=false +mediaStunUse=true +sipUpnpUse=true +nat64Opt=false +iceEnabled=true +iceAggressiveNomination=true +iceNoRtcp=false +iceAlwaysUpdate=true +viaRewriteUse=true +sdpNatRewriteUse=false +sipOutboundUse=false +sipOutboundInstanceId= +sipOutboundRegId= +auth=auth0 +transport=tls +network=ipv4 + +[auth0] +scheme=Plain +username=%TPL[PHONENUMBER]% +realm=* +type=plain +data= diff --git a/resources/templates/templates.qrc b/resources/templates/templates.qrc index ab9e570e..380e5314 100644 --- a/resources/templates/templates.qrc +++ b/resources/templates/templates.qrc @@ -8,5 +8,6 @@ telekom.conf easybell_phoneline.conf easybell_pbx.conf + 1und1.conf From 8f04b95b6feeeb9f65c3125daa0ca0548d4c2873 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 20 May 2026 12:15:29 +0000 Subject: [PATCH 113/122] chore(release): 2.2.0-beta.7 [skip ci] # [2.2.0-beta.7](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.6...v2.2.0-beta.7) (2026-05-20) ### Bug Fixes * do not show favorites in "most called" list ([#456](https://github.com/gonicus/gonnect/issues/456)) ([8ee676d](https://github.com/gonicus/gonnect/commit/8ee676d08a6e79eba5c31749fb62531dfa6c7069)) * Ensure correct `tag` value type in flatpak manifest ([#466](https://github.com/gonicus/gonnect/issues/466)) ([3c5412a](https://github.com/gonicus/gonnect/commit/3c5412a250d5a7e3c3404432ae57d28465bac48c)) * **ui:** do not treat room names beginning with digits as phone numbers ([#461](https://github.com/gonicus/gonnect/issues/461)) ([75ed3f2](https://github.com/gonicus/gonnect/commit/75ed3f23ebe0730f10c43f6867d91cd1d54acf3f)) ### Features * add template for 1&1 ([#465](https://github.com/gonicus/gonnect/issues/465)) ([865fcd9](https://github.com/gonicus/gonnect/commit/865fcd975f5d030674ff2d916c0eea5e1fa29456)) * ical rrule parsing optimization ([#457](https://github.com/gonicus/gonnect/issues/457)) ([c46f066](https://github.com/gonicus/gonnect/commit/c46f066fd5f4f1aee410676f6e502ac682f4b265)) * rtt ui integration ([#443](https://github.com/gonicus/gonnect/issues/443)) ([62d48f3](https://github.com/gonicus/gonnect/commit/62d48f313a4aed5c971dc52d8e33a13e8396a58d)) * windows signing ([#462](https://github.com/gonicus/gonnect/issues/462)) ([f7dd63a](https://github.com/gonicus/gonnect/commit/f7dd63a69d2231c92c768154eae99326a7ba1c29)) --- CHANGELOG.md | 17 +++++++++++++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- .../flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0844199..ec99cffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# [2.2.0-beta.7](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.6...v2.2.0-beta.7) (2026-05-20) + + +### Bug Fixes + +* do not show favorites in "most called" list ([#456](https://github.com/gonicus/gonnect/issues/456)) ([8ee676d](https://github.com/gonicus/gonnect/commit/8ee676d08a6e79eba5c31749fb62531dfa6c7069)) +* Ensure correct `tag` value type in flatpak manifest ([#466](https://github.com/gonicus/gonnect/issues/466)) ([3c5412a](https://github.com/gonicus/gonnect/commit/3c5412a250d5a7e3c3404432ae57d28465bac48c)) +* **ui:** do not treat room names beginning with digits as phone numbers ([#461](https://github.com/gonicus/gonnect/issues/461)) ([75ed3f2](https://github.com/gonicus/gonnect/commit/75ed3f23ebe0730f10c43f6867d91cd1d54acf3f)) + + +### Features + +* add template for 1&1 ([#465](https://github.com/gonicus/gonnect/issues/465)) ([865fcd9](https://github.com/gonicus/gonnect/commit/865fcd975f5d030674ff2d916c0eea5e1fa29456)) +* ical rrule parsing optimization ([#457](https://github.com/gonicus/gonnect/issues/457)) ([c46f066](https://github.com/gonicus/gonnect/commit/c46f066fd5f4f1aee410676f6e502ac682f4b265)) +* rtt ui integration ([#443](https://github.com/gonicus/gonnect/issues/443)) ([62d48f3](https://github.com/gonicus/gonnect/commit/62d48f313a4aed5c971dc52d8e33a13e8396a58d)) +* windows signing ([#462](https://github.com/gonicus/gonnect/issues/462)) ([f7dd63a](https://github.com/gonicus/gonnect/commit/f7dd63a69d2231c92c768154eae99326a7ba1c29)) + # [2.2.0-beta.6](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.5...v2.2.0-beta.6) (2026-05-11) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a3aae9e..04a512cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.6 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.7 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index 070c8e08..efebf090 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.6 +version: 2.2.0-beta.7 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index b7898127..79173808 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From e4d9bce243a6ab81a664ce207718603f232413d9 Mon Sep 17 00:00:00 2001 From: Michael Neuendorf Date: Wed, 20 May 2026 12:29:11 +0000 Subject: [PATCH 114/122] ci: fix warnings --- .github/workflows/release.yml | 2 +- .github/workflows/renovate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3b88abe..1ea7c509 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: id: get_token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 with: - app-id: ${{ secrets.RELEASE_APP_ID }} + client-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_KEY }} - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 779fbdca..40834b93 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -29,7 +29,7 @@ jobs: id: get_token uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 with: - app-id: ${{ secrets.APP_ID }} + client-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_KEY }} - name: Checkout From d879e1b6a16ddca85658b20b8fb0fca63e51f05c Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Thu, 21 May 2026 10:29:17 +0200 Subject: [PATCH 115/122] fix: robustness for media and contacts cache (#471) * Catch "device broken" issues * Also read missing configId * Abort on potentially broken number of contacts --- src/contacts/Contact.cpp | 4 +- .../carddav/CardDAVAddressBookFeeder.cpp | 37 ++++++++++++++++--- src/media/AudioPort.cpp | 20 +++++++--- src/sip/SIPCall.cpp | 33 ++++++++++++++--- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/contacts/Contact.cpp b/src/contacts/Contact.cpp index 084e7f72..e927b3e3 100644 --- a/src/contacts/Contact.cpp +++ b/src/contacts/Contact.cpp @@ -338,8 +338,8 @@ QDataStream &operator>>(QDataStream &in, Contact &contact) BlockInfo blockInfo; QList phoneNumbers; - in >> id >> dn >> sourceUid >> name >> prio >> displayName >> company >> mail >> lastModified - >> sipStatusSubscriptable >> phoneNumbers >> blockInfo; + in >> id >> dn >> sourceUid >> name >> prio >> displayName >> configId >> company >> mail + >> lastModified >> sipStatusSubscriptable >> phoneNumbers >> blockInfo; contact = Contact(id, dn, sourceUid, { prio, displayName, configId }, name, company, mail, lastModified, phoneNumbers, blockInfo); diff --git a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp index 74635e92..dae8fa1d 100644 --- a/src/contacts/carddav/CardDAVAddressBookFeeder.cpp +++ b/src/contacts/carddav/CardDAVAddressBookFeeder.cpp @@ -219,18 +219,29 @@ void CardDAVAddressBookFeeder::loadCachedData(const size_t hash) QDataStream in(&cacheFile); - quint16 magic; - quint8 version; - qsizetype numberOfContacts; + quint16 magic = 0; + quint8 version = 0; in >> magic; in >> version; - in >> m_ignoredIds; - in >> numberOfContacts; - if (magic != CARDDAV_MAGIC || version != CARDDAV_VERSION) { + if (in.status() != QDataStream::Ok || magic != CARDDAV_MAGIC || version != CARDDAV_VERSION) { qCInfo(lcCardDAVAddressBookFeeder) << "CardDAV cache file at" << filePath << "is invalid and will therefore be removed."; + cacheFile.close(); + cacheFile.remove(); + return; + } + + qsizetype numberOfContacts = 0; + in >> m_ignoredIds; + in >> numberOfContacts; + + if (in.status() != QDataStream::Ok || numberOfContacts < 0 || numberOfContacts > 1000000) { + qCWarning(lcCardDAVAddressBookFeeder) << "CardDAV cache file at" << filePath + << "is corrupted and will therefore be removed."; + m_ignoredIds.clear(); + cacheFile.close(); cacheFile.remove(); return; } @@ -240,6 +251,20 @@ void CardDAVAddressBookFeeder::loadCachedData(const size_t hash) Contact *contact = new Contact(this); in >> key; in >> *contact; + + if (in.status() != QDataStream::Ok) { + delete contact; + qCWarning(lcCardDAVAddressBookFeeder) + << "CardDAV cache file at" << filePath + << "is truncated or corrupted - aborting cache load."; + qDeleteAll(m_cachedContacts); + m_cachedContacts.clear(); + m_ignoredIds.clear(); + cacheFile.close(); + cacheFile.remove(); + return; + } + m_cachedContacts.insert(key, contact); } diff --git a/src/media/AudioPort.cpp b/src/media/AudioPort.cpp index 1a713bbd..e3e0049f 100644 --- a/src/media/AudioPort.cpp +++ b/src/media/AudioPort.cpp @@ -35,7 +35,11 @@ bool AudioPort::initialize() createPort(m_device.id().toStdString(), m_pj_fmt); if (m_device.mode() == QAudioDevice::Mode::Input) { - adjustTxLevel(NORMAL_AUDIO_LEVEL); + try { + adjustTxLevel(NORMAL_AUDIO_LEVEL); + } catch (pj::Error &err) { + qCCritical(lcAudioPort) << "failed to adjust tx level: " << err.info(); + } } return true; @@ -45,7 +49,11 @@ void AudioPort::setMuted(bool value) { if (m_isMuted != value) { if (m_device.mode() == QAudioDevice::Mode::Input) { - adjustTxLevel(value ? 0.0f : NORMAL_AUDIO_LEVEL); + try { + adjustTxLevel(value ? 0.0f : NORMAL_AUDIO_LEVEL); + } catch (pj::Error &err) { + qCCritical(lcAudioPort) << "failed to adjust tx level: " << err.info(); + } } m_isMuted = value; @@ -166,8 +174,8 @@ void AudioPort::startSinkIO() qCInfo(lcAudioPort).noquote().nospace() << "Initialize sink of device_descr=\"" << m_device.description() << "\", device_id=\"" - << m_device.id() << "\", with settings:" - << "\nsampleRate=" << m_audioFormat.sampleRate() + << m_device.id() + << "\", with settings:" << "\nsampleRate=" << m_audioFormat.sampleRate() << "\nchannelCount=" << m_audioFormat.channelCount() << "\nbytesPerSample=" << m_audioFormat.bytesPerSample() << "\nsampleFormat=" << m_audioFormat.sampleFormat(); @@ -208,8 +216,8 @@ void AudioPort::startSourceIO() qCInfo(lcAudioPort).noquote().nospace() << "Initialize source of device_descr=\"" << m_device.description() - << "\", device_id=\"" << m_device.id() << "\", with settings:" - << "\nsampleRate=" << m_audioFormat.sampleRate() + << "\", device_id=\"" << m_device.id() + << "\", with settings:" << "\nsampleRate=" << m_audioFormat.sampleRate() << "\nchannelCount=" << m_audioFormat.channelCount() << "\nbytesPerSample=" << m_audioFormat.bytesPerSample() << "\nsampleFormat=" << m_audioFormat.sampleFormat(); diff --git a/src/sip/SIPCall.cpp b/src/sip/SIPCall.cpp index 8b30f53c..a4274e8e 100644 --- a/src/sip/SIPCall.cpp +++ b/src/sip/SIPCall.cpp @@ -20,6 +20,7 @@ #include "NotificationManager.h" #include "AvatarManager.h" #include "GlobalCallState.h" +#include "ErrorBus.h" #include #include @@ -159,8 +160,8 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm) qCInfo(lcSIPCall).nospace() << "Call State: " << ci.stateText << " (" << remoteUri << ")" << " last status code: " << EnumTranslation::instance().sipStatusCode(statusCode) << " (" - << statusCode << ") " - << " last reason " << ci.lastReason << " contactId " << m_contactId; + << statusCode << ") " << " last reason " << ci.lastReason + << " contactId " << m_contactId; if (statusCode == PJSIP_SC_RINGING) { ringToneFactory.ringingTone()->start(); @@ -359,13 +360,35 @@ void SIPCall::onCallMediaState(pj::OnCallMediaStateParam &prm) qCInfo(lcSIPCall) << "Found media, index" << i << "of" << ci.media.size(); aud_med = getAudioMedia(i); - mic_media.startTransmit(aud_med); - aud_med.startTransmit(speaker_media); + + try { + mic_media.startTransmit(aud_med); + } catch (pj::Error &err) { + qCCritical(lcSIPCall) + << "failed to start mic media transmission: " << err.info(); + ErrorBus::instance().addFatalError( + tr("Failed to initialize microphone audio")); + } + + try { + aud_med.startTransmit(speaker_media); + } catch (pj::Error &err) { + qCCritical(lcSIPCall) + << "failed to start aud media transmission: " << err.info(); + ErrorBus::instance().addFatalError(tr("Failed to initialize call audio")); + } if (!m_sniffer) { m_sniffer = new Sniffer(this); m_sniffer->initialize(); - aud_med.startTransmit(dynamic_cast(*m_sniffer)); + + try { + aud_med.startTransmit(dynamic_cast(*m_sniffer)); + } catch (pj::Error &err) { + qCCritical(lcSIPCall) + << "failed to start audio level transmission: " << err.info(); + } + connect(m_sniffer, &Sniffer::audioLevelChanged, this, [this]() { Q_EMIT SIPCallManager::instance().audioLevelChanged( this, m_sniffer->audioLevel()); From 46bc4188d8eb9f55188c55b16274ac3310aef416 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 21 May 2026 08:53:54 +0000 Subject: [PATCH 116/122] chore(release): 2.2.0-beta.8 [skip ci] # [2.2.0-beta.8](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.7...v2.2.0-beta.8) (2026-05-21) ### Bug Fixes * robustness for media and contacts cache ([#471](https://github.com/gonicus/gonnect/issues/471)) ([d879e1b](https://github.com/gonicus/gonnect/commit/d879e1b6a16ddca85658b20b8fb0fca63e51f05c)) --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- docs/antora.yml | 2 +- resources/flatpak/de.gonicus.gonnect.releases.xml | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec99cffd..43dd9ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.2.0-beta.8](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.7...v2.2.0-beta.8) (2026-05-21) + + +### Bug Fixes + +* robustness for media and contacts cache ([#471](https://github.com/gonicus/gonnect/issues/471)) ([d879e1b](https://github.com/gonicus/gonnect/commit/d879e1b6a16ddca85658b20b8fb0fca63e51f05c)) + # [2.2.0-beta.7](https://github.com/gonicus/gonnect/compare/v2.2.0-beta.6...v2.2.0-beta.7) (2026-05-20) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04a512cb..73a96345 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -project(GOnnect VERSION 2.1.0.7 LANGUAGES CXX) +project(GOnnect VERSION 2.1.0.8 LANGUAGES CXX) include(cmake/CCache.cmake) include(cmake/Workarounds.cmake) include(cmake/Versioning.cmake) diff --git a/docs/antora.yml b/docs/antora.yml index efebf090..f684262e 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,3 +1,3 @@ name: gonnect-examples -version: 2.2.0-beta.7 +version: 2.2.0-beta.8 title: GOnnect examples diff --git a/resources/flatpak/de.gonicus.gonnect.releases.xml b/resources/flatpak/de.gonicus.gonnect.releases.xml index 79173808..683aabd3 100644 --- a/resources/flatpak/de.gonicus.gonnect.releases.xml +++ b/resources/flatpak/de.gonicus.gonnect.releases.xml @@ -1,5 +1,6 @@ + From 12b2bc18690bbf446b91981464817ee0424712e9 Mon Sep 17 00:00:00 2001 From: "renovate-tokenizer[bot]" <192083161+renovate-tokenizer[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 20:18:41 +0200 Subject: [PATCH 117/122] chore(deps): update github/codeql-action digest to 7211b7c (#472) Co-authored-by: renovate-tokenizer[bot] <192083161+renovate-tokenizer[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 703c6e61..ffa4cb39 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -106,7 +106,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -133,6 +133,6 @@ jobs: cmake --build --preset conan-release --parallel $(nproc --all) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 with: category: "/language:${{matrix.language}}" From cd1c126b55aef57532db88ae432705d8421f7fe7 Mon Sep 17 00:00:00 2001 From: Cajus Pollmeier Date: Wed, 27 May 2026 14:18:06 +0200 Subject: [PATCH 118/122] fix: clear out URI encoded number input (#475) * Clear out URI encoded number input In some cases websites contain broken callto/tel refs that contain URI encoded parts (i.e. %20 for space). Convert these before applying the number cleaner. * Bump conan * test: add tests for URL encoded phone numbers --------- Co-authored-by: Michael Neuendorf --- .github/actions/prepare-linux/action.yml | 2 +- .github/actions/prepare-macos/action.yml | 2 +- .github/actions/prepare-windows/action.yml | 2 +- src/contacts/PhoneNumberUtil.cpp | 2 +- tests/Contacts.test.cpp | 3 +++ 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/actions/prepare-linux/action.yml b/.github/actions/prepare-linux/action.yml index 5aa2b824..c7011157 100644 --- a/.github/actions/prepare-linux/action.yml +++ b/.github/actions/prepare-linux/action.yml @@ -52,7 +52,7 @@ runs: if: inputs.conan == 'true' uses: conan-io/setup-conan@2f4bd34e8e0af00e1a77a66e1d284e06d71d703f # v1 with: - version: 2.25.1 + version: 2.28.1 cache_packages: true - name: Install Conan dependencies diff --git a/.github/actions/prepare-macos/action.yml b/.github/actions/prepare-macos/action.yml index cd3ad1f3..da531f14 100644 --- a/.github/actions/prepare-macos/action.yml +++ b/.github/actions/prepare-macos/action.yml @@ -37,7 +37,7 @@ runs: - name: Install Conan uses: conan-io/setup-conan@2f4bd34e8e0af00e1a77a66e1d284e06d71d703f # v1 with: - version: 2.25.1 + version: 2.28.1 cache_packages: true - name: Install Conan dependencies diff --git a/.github/actions/prepare-windows/action.yml b/.github/actions/prepare-windows/action.yml index cba71464..504fbd7f 100644 --- a/.github/actions/prepare-windows/action.yml +++ b/.github/actions/prepare-windows/action.yml @@ -40,7 +40,7 @@ runs: - name: Install Conan uses: conan-io/setup-conan@2f4bd34e8e0af00e1a77a66e1d284e06d71d703f # v1 with: - version: 2.25.1 + version: 2.28.1 cache_packages: true - name: Install Conan dependencies diff --git a/src/contacts/PhoneNumberUtil.cpp b/src/contacts/PhoneNumberUtil.cpp index 96aa1f8f..9d93eb68 100644 --- a/src/contacts/PhoneNumberUtil.cpp +++ b/src/contacts/PhoneNumberUtil.cpp @@ -45,7 +45,7 @@ bool ContactInfo::operator!=(const ContactInfo &other) QString PhoneNumberUtil::cleanPhoneNumber(const QString &number) { - QString result(number); + QString result(QUrl::fromPercentEncoding(number.toLocal8Bit())); static const QRegularExpression stripRegEx("[^0-9#+*]"); result.replace(stripRegEx, ""); diff --git a/tests/Contacts.test.cpp b/tests/Contacts.test.cpp index dd6de398..8a77a3ed 100644 --- a/tests/Contacts.test.cpp +++ b/tests/Contacts.test.cpp @@ -13,6 +13,9 @@ void ContactsTest::testCleanPhoneNumber() // Strip whitespace and - QCOMPARE(PhoneNumberUtil::cleanPhoneNumber(" +49 2931 9160 "), QString("+4929319160")); QCOMPARE(PhoneNumberUtil::cleanPhoneNumber(" +49-2931-9160 "), QString("+4929319160")); + // Handle URL encoded numbers + QCOMPARE(PhoneNumberUtil::cleanPhoneNumber("callto:+49%202932%209160"), QString("+4929329160")); + QCOMPARE(PhoneNumberUtil::cleanPhoneNumber("%2B49%202932%209160"), QString("+4929329160")); } void ContactsTest::testLevenshteinDistance() From ca0be87fceb07067ce3030d345bcc5e0a992bc62 Mon Sep 17 00:00:00 2001 From: Mik- Date: Wed, 27 May 2026 14:20:39 +0200 Subject: [PATCH 119/122] chore: a tool to convert log files (#476) --- scripts/GOnnectlog2pcap.md | 50 +++++++ scripts/GOnnectlog2pcap.py | 266 +++++++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 scripts/GOnnectlog2pcap.md create mode 100644 scripts/GOnnectlog2pcap.py diff --git a/scripts/GOnnectlog2pcap.md b/scripts/GOnnectlog2pcap.md new file mode 100644 index 00000000..da1e904e --- /dev/null +++ b/scripts/GOnnectlog2pcap.md @@ -0,0 +1,50 @@ +# GOnnectlog2pcap.py + +Extract SIP messages from GOnnect/pjsua log files and write them to a PCAP file for analysis in Wireshark or tshark. + +## Features + +- Handles multiple log formats: + - File dump (ISO date + timezone) + - Console output (ANSI escape codes) + - journalctl dumps (locale-agnostic) + - Mixed timezone formats +- Supports IPv4 and IPv6 addresses +- Real timestamps extracted from log entries +- PCAP output with LINK_TYPE 228 (Wireshark auto-detects IPv4/IPv6) + +## Usage + +```bash +python3 GOnnectlog2pcap.py [output.pcap] +``` + +- `log_file` — path to the GOnnect/pjsua log file +- `output.pcap` — optional, defaults to `sip_messages.pcap` + +## Examples + +```bash +# Extract from a journalctl log +python3 GOnnectlog2pcap.py gonnect-journal.log + +# Extract with custom output filename +python3 GOnnectlog2pcap.py gonnect-journal.log capture.pcap + +# Inspect with tshark +tshark -r sip_messages.pcap -Y sip +``` + +## Supported Log Formats + +| Format | Example | +|--------|---------| +| File dump | `2026-05-21 13:37:16.128 CEST DEBUG ...` | +| Console | `[32m10:04:38.593[32m gonnect.pjsip: ...` | +| journalctl | `Mai 26 16:00:26 host app[pid]: 16:00:26.071 ...` | +| ISO + TZ | `2026-03-19 13:51:15.883 Mitteleuropäische Zeit ...` | + +## Requirements + +- Python 3.10+ +- No external dependencies (stdlib only) diff --git a/scripts/GOnnectlog2pcap.py b/scripts/GOnnectlog2pcap.py new file mode 100644 index 00000000..44bf05f2 --- /dev/null +++ b/scripts/GOnnectlog2pcap.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +"""Extract SIP messages from GOnnect/pjsua logs into a PCAP file for Wireshark. + +Handles multiple log formats: + - File dump: 2026-05-21 13:37:16.128 CEST DEBUG ... + - Console: [32m10:04:38.593[32m gonnect.pjsip: ... + - Journalctl: Mai 26 16:00:26 host app[pid]: 16:00:26.071 gonnect.pjsip: ... + - ISO date + local TZ: 2026-03-19 13:51:15.883 Mitteleuropäische Zeit ... + +Supports IPv4 and IPv6 addresses. +""" + +import datetime +import ipaddress +import re +import struct +import sys + +# ANSI escape codes +ANSI_RE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]") + +# journalctl prefix: