mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-11-04 08:10:18 +08:00
Compare commits
116 Commits
nightly/20
...
beta/20240
| Author | SHA1 | Date | |
|---|---|---|---|
| 61163037d4 | |||
| 3577982a93 | |||
| 3ad0b92dcd | |||
| 91475c3a3f | |||
| 9379af23ec | |||
| 406a251c85 | |||
| 20e2852e44 | |||
| 3225f90ce8 | |||
| df3e76a1cf | |||
| c5e15d26a0 | |||
| 77e019b02b | |||
| 89bed2bf85 | |||
| 519871e458 | |||
| 0a842042b0 | |||
| 9aeb2377dc | |||
| 6ad5f4b850 | |||
| f5c63d24fb | |||
| 5cb34bd31c | |||
| e56a966de1 | |||
| acc0c97234 | |||
| 665af7c0c3 | |||
| fa51e042e5 | |||
| 3b9fb0bfca | |||
| 3673b0646c | |||
| 2e2f6423f8 | |||
| 28c1cbbb34 | |||
| 87c215deb7 | |||
| 77eddcd962 | |||
| 043a715c59 | |||
| 5bd3ead22d | |||
| a9aa1cac80 | |||
| 409ba70258 | |||
| dc50f19815 | |||
| d83895dcc9 | |||
| e24a3d6a4d | |||
| 06de33e1be | |||
| c8fbcd8c6b | |||
| 31269fe8fc | |||
| a676ad395a | |||
| 04c71d02e0 | |||
| 1fe60b9c33 | |||
| 51ef7a83da | |||
| 588a8abdac | |||
| 8a149b6c4f | |||
| 6105f4f7ce | |||
| 92341b27b6 | |||
| f39afdac4c | |||
| 690f2dd85c | |||
| bd45d6a406 | |||
| 5b92e4708a | |||
| 63c01f1439 | |||
| 73aeb02ebd | |||
| 9d91317089 | |||
| 474bc5f6a4 | |||
| f5b64e955b | |||
| b88627d125 | |||
| 200978a044 | |||
| a673ff9890 | |||
| 7803dd0991 | |||
| a8a736bc8c | |||
| ff7acf9932 | |||
| afde816b23 | |||
| 0745c3b798 | |||
| 1376ee1f4b | |||
| 2b03107bd5 | |||
| cd1ab0ed12 | |||
| a13c6ae0e7 | |||
| 1ef9a85148 | |||
| 072eafbaf4 | |||
| 201f3182ca | |||
| 23130a5752 | |||
| f28d47bc51 | |||
| ee7818eefb | |||
| f25e66aa6a | |||
| 79b19aec01 | |||
| 4c92cb9936 | |||
| 1c81553245 | |||
| 5c2fec53da | |||
| f706abe5a6 | |||
| 610c27f751 | |||
| 6d20d3b515 | |||
| a0b583aa8d | |||
| 3855a5e951 | |||
| 6689bce782 | |||
| 860ddf22b6 | |||
| ef716d657d | |||
| b0fe0251d1 | |||
| 1ec2d5f27b | |||
| 23316993e5 | |||
| d42fe78676 | |||
| 78724c2a7b | |||
| e14fbe9437 | |||
| 82c63d5a89 | |||
| a72af9cba5 | |||
| d7c642a2fe | |||
| 08f3339693 | |||
| 402515365d | |||
| df102068bc | |||
| d40e884a1f | |||
| 5371dac882 | |||
| 0f62829588 | |||
| 39da97396c | |||
| 406edda453 | |||
| bbbeda6a26 | |||
| 6b3efff7cc | |||
| 3531b8b354 | |||
| 487446cbc3 | |||
| d5349490f5 | |||
| 7650f45d6f | |||
| a98f6ca4e3 | |||
| 0b96cf5f1f | |||
| 07e0b10478 | |||
| b38e216721 | |||
| 91f32f2421 | |||
| 06c3ffa6ce | |||
| ae53d92c2e |
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,9 +1,18 @@
|
||||
*.user
|
||||
doc/Doxyfile
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/**/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
**/.history
|
||||
|
||||
GeneratedFiles/
|
||||
.vs/
|
||||
.vscode/
|
||||
x64/
|
||||
x86/
|
||||
[wW]in32/
|
||||
|
||||
49
.vscode/launch.json
vendored
Normal file
49
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Jami-Client-Debug",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux":{
|
||||
"MIMode": "gdb",
|
||||
"program": "${workspaceFolder}/build/jami",
|
||||
"args": [
|
||||
"-d",
|
||||
],
|
||||
},
|
||||
"osx": {
|
||||
"MIMode": "lldb",
|
||||
"program": "${workspaceFolder}/build/Jami.app/Contents/MacOS/Jami",
|
||||
"environment": [
|
||||
{
|
||||
"name": "NO_COLOR",
|
||||
"value": "true",
|
||||
}
|
||||
],
|
||||
},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "cmake-build",
|
||||
"externalConsole": false, // A macOS dev may want to set this to true.
|
||||
},
|
||||
{
|
||||
// Using this configuration will require manually reconfiguring the project using
|
||||
// build.py --no-libwrap, otherwise the daemon executable will not be built and the
|
||||
// client will not be built with ENABLE_LIBWRAP=False.
|
||||
"name": "Jami-Daemon-Debug",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"linux": {
|
||||
"MIMode": "gdb",
|
||||
"program": "${workspaceFolder}/daemon/bin/dbus/jamid",
|
||||
},
|
||||
"program": "",
|
||||
"args": [
|
||||
"-cdp",
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "cmake-build",
|
||||
}
|
||||
]
|
||||
}
|
||||
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"C_Cpp.default.includePath": [
|
||||
"${default}",
|
||||
"${workspaceFolder}/**",
|
||||
"/usr/lib/libqt-jami/include/**",
|
||||
"/usr/lib64/qt-jami/include/**",
|
||||
],
|
||||
"C_Cpp.default.cppStandard": "c++17",
|
||||
"C_Cpp.default.cStandard": "c11",
|
||||
"cmake.configureOnOpen": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "xaver.clang-format",
|
||||
"files.eol": "\n",
|
||||
"cSpell.enabled": false,
|
||||
}
|
||||
93
.vscode/tasks.json
vendored
Normal file
93
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run-tests",
|
||||
"type": "shell",
|
||||
"command": "ctest",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/tests",
|
||||
"env": {
|
||||
"HOME": "/tmp"
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"-V",
|
||||
"-R"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"detail": "Run the tests using CTest."
|
||||
},
|
||||
{
|
||||
"label": "cmake-configure",
|
||||
"type": "shell",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"-S", ".",
|
||||
"-B", "build",
|
||||
"-DCMAKE_BUILD_TYPE=Debug",
|
||||
"-DCMAKE_PREFIX_PATH=\"/usr/lib64/qt-jami;/usr/lib/libqt-jami\"",
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "Generate the build system files with CMake."
|
||||
},
|
||||
{
|
||||
"label": "cmake-configure-tests",
|
||||
"type": "shell",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"-S", ".",
|
||||
"-B", "build",
|
||||
"-DBUILD_TESTING=${input:buildTestingInput}"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "Generate the build system files with CMake."
|
||||
},
|
||||
{
|
||||
"label": "cmake-build",
|
||||
"type": "shell",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build", "build",
|
||||
"-j$(nproc)",
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
],
|
||||
"detail": "Compile the project using the generated build system.",
|
||||
"dependsOn": [
|
||||
"cmake-configure"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "cmake-clean",
|
||||
"type": "shell",
|
||||
"command": "rm",
|
||||
"args": [
|
||||
"-rf",
|
||||
"build"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "Clean the build directory."
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "buildTestingInput",
|
||||
"type": "pickString",
|
||||
"description": "Do you want to enable testing?",
|
||||
"options": ["True", "False"],
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -33,7 +33,6 @@ include(${PROJECT_SOURCE_DIR}/extras/build/cmake/extra_tools.cmake)
|
||||
|
||||
option(WITH_DAEMON_SUBMODULE "Build with daemon submodule" ON)
|
||||
option(JAMICORE_AS_SUBDIR "Build Jami-core as a subdir dependency" OFF)
|
||||
option(ENABLE_TESTS "Build with tests" OFF)
|
||||
option(WITH_WEBENGINE "Build with WebEngine" ON)
|
||||
option(ENABLE_LIBWRAP "Enable libwrap (single process mode)" ON)
|
||||
if(NOT (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
@ -88,7 +87,6 @@ list(APPEND QWINDOWKIT_OPTIONS
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND QWINDOWKIT_PATCHES ${EXTRA_PATCHES_DIR}/0002-workaround-right-margin.patch)
|
||||
list(APPEND QWINDOWKIT_OPTIONS QWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS OFF)
|
||||
endif()
|
||||
|
||||
@ -141,6 +139,11 @@ else()
|
||||
find_package(QT NAMES Qt6 REQUIRED)
|
||||
endif()
|
||||
if (${QT_VERSION_MINOR} GREATER_EQUAL ${QT6_MINVER_MINOR})
|
||||
# Enforce a minimum Qt version of 6.6.2 for the Windows build
|
||||
# https://github.com/stdware/qwindowkit/issues/23
|
||||
if(MSVC AND ${QT_VERSION_MINOR} EQUAL 6 AND ${QT_VERSION_PATCH} LESS 2)
|
||||
message(FATAL_ERROR "Qt 6.6.2 or higher is required. Found ${QT_VERSION}")
|
||||
endif()
|
||||
# Qt version is 6.6 or higher
|
||||
message(STATUS "Found a suitable Qt version ${QT_VERSION}")
|
||||
else()
|
||||
@ -452,10 +455,12 @@ elseif (NOT APPLE)
|
||||
${APP_SRC_DIR}/xrectsel.c
|
||||
${APP_SRC_DIR}/connectivitymonitor.cpp
|
||||
${APP_SRC_DIR}/dbuserrorhandler.cpp
|
||||
${APP_SRC_DIR}/appversionmanager.cpp)
|
||||
${APP_SRC_DIR}/appversionmanager.cpp
|
||||
${APP_SRC_DIR}/screencastportal.cpp)
|
||||
list(APPEND COMMON_HEADERS
|
||||
${APP_SRC_DIR}/xrectsel.h
|
||||
${APP_SRC_DIR}/dbuserrorhandler.h)
|
||||
${APP_SRC_DIR}/dbuserrorhandler.h
|
||||
${APP_SRC_DIR}/screencastportal.h)
|
||||
list(APPEND QT_MODULES DBus)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
@ -470,6 +475,11 @@ elseif (NOT APPLE)
|
||||
add_definitions(${GIO_CFLAGS})
|
||||
endif()
|
||||
|
||||
pkg_check_modules(GIOUNIX REQUIRED gio-unix-2.0)
|
||||
if(GIOUNIX_FOUND)
|
||||
add_definitions(${GIOUNIX_CFLAGS})
|
||||
endif()
|
||||
|
||||
pkg_check_modules(LIBNM libnm)
|
||||
if(LIBNM_FOUND)
|
||||
add_definitions(-DUSE_LIBNM)
|
||||
@ -581,6 +591,7 @@ include_directories(
|
||||
if(ENABLE_LIBWRAP)
|
||||
list(APPEND COMMON_HEADERS
|
||||
${LIBCLIENT_SRC_DIR}/qtwrapper/instancemanager_wrap.h)
|
||||
add_definitions(-DENABLE_LIBWRAP=true)
|
||||
endif()
|
||||
|
||||
# SFPM
|
||||
@ -808,12 +819,20 @@ else()
|
||||
"-framework Security"
|
||||
compression
|
||||
resolv
|
||||
)
|
||||
)
|
||||
|
||||
set(APP_CONTAINER "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app/Contents")
|
||||
|
||||
# ringtones. Copy the entire directory to the app bundle.
|
||||
# daemon/ringtones -> Jami.app/Contents/Resources/ringtones
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${DAEMON_DIR}/ringtones
|
||||
${APP_CONTAINER}/Resources/ringtones
|
||||
)
|
||||
|
||||
# translations
|
||||
if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
|
||||
set(APP_CONTAINER
|
||||
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app/Contents")
|
||||
file(GLOB TS_FILES ${PROJECT_SOURCE_DIR}/translations/*.ts)
|
||||
|
||||
# Generate lproj folders.
|
||||
@ -841,26 +860,26 @@ else()
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${JAMI_VERSION}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${JAMI_BUILD}"
|
||||
MACOSX_BUNDLE_COPYRIGHT "${PROJ_COPYRIGHT}")
|
||||
if(APPSTORE)
|
||||
message(STATUS "app store version")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/appstore/Jami.entitlements")
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
SPARKLE_URL "${SPARKLE_URL}"
|
||||
SPARKLE_PUBLIC_KEY "${SPARKLE_PUBLIC_KEY}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements"
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE)
|
||||
endif()
|
||||
if(DEPLOY)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DQML_SRC_DIR=${SRC_DIR}
|
||||
-DMAC_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
|
||||
-DEXE_NAME="${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app"
|
||||
-DSPARKLE_PATH=${SPARKLE_FRAMEWORK}
|
||||
-DENABLE_SPARKLE=${ENABLE_SPARKLE}
|
||||
-P ${EXTRAS_DIR}/build/cmake/macos_qt_deploy.cmake)
|
||||
endif()
|
||||
if(APPSTORE)
|
||||
message(STATUS "app store version")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/appstore/Jami.entitlements")
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
SPARKLE_URL "${SPARKLE_URL}"
|
||||
SPARKLE_PUBLIC_KEY "${SPARKLE_PUBLIC_KEY}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements"
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE)
|
||||
endif()
|
||||
if(DEPLOY)
|
||||
execute_process(COMMAND
|
||||
"${CMAKE_PREFIX_PATH}/bin/macdeployqt"
|
||||
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app"
|
||||
-qmldir=${QML_SRC_DIR})
|
||||
if(${ENABLE_SPARKLE} MATCHES true)
|
||||
file(COPY ${SPARKLE_FRAMEWORK} DESTINATION ${EXE_NAME}/Contents/Frameworks/)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CLIENT_INCLUDE_DIRS})
|
||||
@ -871,7 +890,7 @@ qt_import_qml_plugins(${PROJECT_NAME})
|
||||
qt_finalize_executable(${PROJECT_NAME})
|
||||
|
||||
# tests
|
||||
if(ENABLE_TESTS)
|
||||
if(BUILD_TESTING)
|
||||
message("Add Jami tests")
|
||||
add_subdirectory(${TESTS_DIR})
|
||||
endif()
|
||||
|
||||
13
INSTALL.md
13
INSTALL.md
@ -131,6 +131,11 @@ Notes:
|
||||
|
||||
- `--global-install` to install client-qt globally under /usr/local
|
||||
- `--prefix` to change the destination of the install.
|
||||
+ For developers:
|
||||
+ `--asan` add address sanitizer on the binary
|
||||
+ `--debug` enable debug symbols
|
||||
+ `--testing` will build the tests for both the daemon and client
|
||||
+ `--no-libwrap` will build the DBUS version.
|
||||
|
||||
## Build only the client
|
||||
|
||||
@ -207,7 +212,7 @@ Only 64-bit MSVC build can be compiled.
|
||||
|
||||
| | Qt Version |
|
||||
| -------------------- | ---------- |
|
||||
| Minimum requirement: | 6.6.1 |
|
||||
| Minimum requirement: | 6.6.2 |
|
||||
|
||||
- Install [Python3](https://www.python.org/downloads/) for Windows
|
||||
|
||||
@ -233,7 +238,7 @@ Only 64-bit MSVC build can be compiled.
|
||||
- Using a new **Non-Elevated Command Prompt**
|
||||
|
||||
```bash
|
||||
python build.py --install --qt <path-to-qt-bin-folder> (e.g. C:/Qt/6.6.1/msvc2019_64)
|
||||
python build.py --install --qt <path-to-qt-bin-folder> (e.g. C:/Qt/6.6.2/msvc2019_64)
|
||||
```
|
||||
|
||||
> **SDK** Note:
|
||||
@ -276,14 +281,14 @@ Once the build has finished, you should then be able to use the Visual Studio So
|
||||
|
||||
```
|
||||
python extras\scripts\build-windows.py --init
|
||||
python extras\scripts\build-windows.py --qt <path-to-qt-bin-folder> (e.g. C:/Qt/6.6.1/msvc2019_64)
|
||||
python extras\scripts\build-windows.py --qt <path-to-qt-bin-folder> (e.g. C:/Qt/6.6.2/msvc2019_64)
|
||||
```
|
||||
|
||||
## Building On MacOS
|
||||
|
||||
**Set up**
|
||||
|
||||
- macOS minimum version 10.15
|
||||
- macOS minimum version 11.0
|
||||
- install python3
|
||||
- download xcode
|
||||
- install Qt 6.6
|
||||
|
||||
5
build.py
5
build.py
@ -389,6 +389,8 @@ def run_install(args):
|
||||
install_args.append('-u')
|
||||
if args.debug:
|
||||
install_args.append('-d')
|
||||
if args.testing:
|
||||
install_args.append('-t')
|
||||
if args.asan:
|
||||
install_args.append('-A')
|
||||
if args.no_libwrap:
|
||||
@ -727,6 +729,9 @@ def parse_args():
|
||||
default=True, action='store_false')
|
||||
ap.add_argument('--qt', type=str,
|
||||
help='Use the Qt path supplied')
|
||||
ap.add_argument('--testing', dest='testing',
|
||||
default=False, action='store_true',
|
||||
help='Enable testing for both client and daemon')
|
||||
ap.add_argument('--no-libwrap', dest='no_libwrap',
|
||||
default=False, action='store_true',
|
||||
help='Disable libwrap. Also set --disable-shared option to daemon configure')
|
||||
|
||||
2
daemon
2
daemon
Submodule daemon updated: 66d8100264...2e49d649d7
@ -1,7 +0,0 @@
|
||||
message("Qt deploying in dir " ${QML_SRC_DIR})
|
||||
execute_process(COMMAND "${MAC_DEPLOY_QT_PATH}/macdeployqt"
|
||||
${EXE_NAME}
|
||||
-qmldir=${QML_SRC_DIR})
|
||||
if(${ENABLE_SPARKLE} MATCHES true)
|
||||
file(COPY ${SPARKLE_PATH} DESTINATION ${EXE_NAME}/Contents/Frameworks/)
|
||||
endif()
|
||||
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV QT_QUICK_BACKEND software
|
||||
@ -10,7 +10,7 @@ RUN apt-get update && \
|
||||
|
||||
RUN apt install gnupg dirmngr ca-certificates curl --no-install-recommends
|
||||
RUN curl -s https://dl.jami.net/public-key.gpg | tee /usr/share/keyrings/jami-archive-keyring.gpg > /dev/null
|
||||
RUN sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/internal/ubuntu_20.04/ jami main' > /etc/apt/sources.list.d/jami.list"
|
||||
RUN sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/internal/ubuntu_22.04/ jami main' > /etc/apt/sources.list.d/jami.list"
|
||||
RUN apt-get update && apt-get install libqt-jami -y
|
||||
|
||||
RUN apt-get install -y -o Acquire::Retries=10 \
|
||||
@ -51,6 +51,7 @@ RUN apt-get install -y -o Acquire::Retries=10 \
|
||||
libswscale-dev \
|
||||
libavdevice-dev \
|
||||
libopus-dev \
|
||||
libpipewire-0.3-dev \
|
||||
libudev-dev \
|
||||
libgsm1-dev \
|
||||
libjsoncpp-dev \
|
||||
@ -65,6 +66,7 @@ RUN apt-get install -y -o Acquire::Retries=10 \
|
||||
libvdpau-dev \
|
||||
libssl-dev
|
||||
RUN apt-get install -y pandoc \
|
||||
libcppunit-dev \
|
||||
googletest \
|
||||
libgtest-dev \
|
||||
wget
|
||||
|
||||
2
extras/ci/client-qt-gnulinux/Jenkinsfile
vendored
2
extras/ci/client-qt-gnulinux/Jenkinsfile
vendored
@ -113,7 +113,7 @@ pipeline {
|
||||
cd ${dockerTopDir}
|
||||
./build.py --install --qt /usr/lib/libqt-jami/
|
||||
cd build
|
||||
cmake .. -DENABLE_TESTS=True
|
||||
cmake .. -DBUILD_TESTING=True
|
||||
make -j${cpuCount}
|
||||
""")
|
||||
// Run tests
|
||||
|
||||
@ -49,7 +49,7 @@ QT_MAJOR := 6
|
||||
QT_MINOR := 6
|
||||
QT_PATCH := 1
|
||||
QT_TARBALL_CHECKSUM := dd3668f65645fe270bc615d748bd4dc048bd17b9dc297025106e6ecc419ab95d
|
||||
DEBIAN_QT_VERSION := $(QT_MAJOR).$(QT_MINOR).$(QT_PATCH)-0
|
||||
DEBIAN_QT_VERSION := $(QT_MAJOR).$(QT_MINOR).$(QT_PATCH)-1
|
||||
DEBIAN_QT_DSC_FILENAME := libqt-jami_$(DEBIAN_QT_VERSION).dsc
|
||||
QT_JAMI_PREFIX := /usr/lib/libqt-jami
|
||||
|
||||
@ -168,9 +168,11 @@ DISTRIBUTIONS := \
|
||||
ubuntu_22.04 \
|
||||
ubuntu_23.04 \
|
||||
ubuntu_23.10 \
|
||||
ubuntu_24.04 \
|
||||
fedora_37 \
|
||||
fedora_38 \
|
||||
fedora_39 \
|
||||
fedora_40 \
|
||||
alma_9 \
|
||||
opensuse-leap_15.4 \
|
||||
opensuse-leap_15.5 \
|
||||
|
||||
@ -100,6 +100,7 @@ RUN dnf install -y \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
python3-html5lib \
|
||||
cups-devel
|
||||
cups-devel \
|
||||
pipewire-devel
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
@ -28,4 +28,10 @@ ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
|
||||
RUN /opt/install-cmake.sh
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
|
||||
|
||||
# Setting this variable so that FFmpeg gets built without pipewiregrab
|
||||
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
|
||||
# We rely on PipeWire for screen sharing on Wayland, but the version available on Debian 11 is too old.
|
||||
ENV DISABLE_PIPEWIRE=true
|
||||
|
||||
CMD ["/opt/build-package-debian.sh"]
|
||||
|
||||
@ -98,6 +98,7 @@ RUN dnf install -y \
|
||||
clang \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
pipewire-devel \
|
||||
cups-devel #Chromium for Qt
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
@ -98,7 +98,8 @@ RUN dnf install -y \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
python3-html5lib \
|
||||
cups-devel
|
||||
cups-devel \
|
||||
pipewire-devel
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
|
||||
@ -97,7 +97,8 @@ RUN dnf install -y \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
python3.10 \
|
||||
cups-devel
|
||||
cups-devel \
|
||||
pipewire-devel
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
|
||||
105
extras/packaging/gnu-linux/docker/Dockerfile_fedora_40
Normal file
105
extras/packaging/gnu-linux/docker/Dockerfile_fedora_40
Normal file
@ -0,0 +1,105 @@
|
||||
FROM fedora:40
|
||||
|
||||
RUN dnf clean all
|
||||
RUN dnf update -y
|
||||
|
||||
RUN dnf install -y dnf-command\(builddep\) rpmdevtools && \
|
||||
dnf install -y mock
|
||||
|
||||
RUN dnf groupinstall -y "X Software Development"
|
||||
|
||||
RUN dnf install -y \
|
||||
git \
|
||||
rpm-build \
|
||||
tar \
|
||||
make \
|
||||
autoconf \
|
||||
automake \
|
||||
nasm \
|
||||
speexdsp-devel \
|
||||
pulseaudio-libs-devel \
|
||||
libcanberra-devel \
|
||||
libcurl-devel \
|
||||
libtool \
|
||||
mesa-libgbm-devel \
|
||||
mesa-dri-drivers \
|
||||
dbus-devel \
|
||||
expat-devel \
|
||||
pcre-devel \
|
||||
yaml-cpp-devel \
|
||||
libXext-devel \
|
||||
libXfixes-devel \
|
||||
yasm \
|
||||
speex-devel \
|
||||
gsm-devel \
|
||||
chrpath \
|
||||
check \
|
||||
astyle \
|
||||
uuid-c++-devel \
|
||||
gettext-devel \
|
||||
gcc-c++ \
|
||||
which \
|
||||
alsa-lib-devel \
|
||||
systemd-devel \
|
||||
libuuid-devel \
|
||||
uuid-devel \
|
||||
gnutls-devel \
|
||||
nettle-devel \
|
||||
opus-devel \
|
||||
patch \
|
||||
jsoncpp-devel \
|
||||
libnatpmp-devel \
|
||||
webkitgtk4-devel \
|
||||
cryptopp-devel \
|
||||
libva-devel \
|
||||
libvdpau-devel \
|
||||
msgpack-devel \
|
||||
NetworkManager-libnm-devel \
|
||||
openssl-devel \
|
||||
clutter-devel \
|
||||
clutter-gtk-devel \
|
||||
libappindicator-gtk3-devel \
|
||||
libnotify-devel \
|
||||
libupnp-devel \
|
||||
qrencode-devel \
|
||||
libargon2-devel \
|
||||
libsndfile-devel \
|
||||
libdrm \
|
||||
gperf \
|
||||
bison \
|
||||
clang \
|
||||
clang-devel \
|
||||
llvm-devel \
|
||||
nodejs \
|
||||
flex \
|
||||
gstreamer1 gstreamer1-devel \
|
||||
gstreamer1-plugins-base-devel \
|
||||
gstreamer1-plugins-good \
|
||||
gstreamer1-plugins-bad-free-devel \
|
||||
nss-devel \
|
||||
libxcb* \
|
||||
libxkb* \
|
||||
libX11-devel \
|
||||
vulkan-devel \
|
||||
libXrender-devel \
|
||||
xcb-util-* \
|
||||
xz \
|
||||
xkeyboard-config \
|
||||
libnotify \
|
||||
wget \
|
||||
libstdc++-static \
|
||||
sqlite-devel \
|
||||
perl-generators \
|
||||
perl-English \
|
||||
libxshmfence-devel \
|
||||
ninja-build \
|
||||
clang \
|
||||
cmake \
|
||||
fmt-devel \
|
||||
python3.10 \
|
||||
cups-devel \
|
||||
pipewire-devel
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
|
||||
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
@ -99,7 +99,8 @@ RUN zypper --non-interactive install -y \
|
||||
gstreamer-plugins-bad-devel \
|
||||
gstreamer-plugins-base-devel \
|
||||
cmake \
|
||||
wget
|
||||
wget \
|
||||
pipewire-devel
|
||||
|
||||
# openSUSE Leap 15.4 comes with Python 3.6 by default,
|
||||
# but we need at least 3.7 to compile Qt 6.6.1
|
||||
@ -112,4 +113,10 @@ ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-r
|
||||
|
||||
ENV CC=gcc
|
||||
ENV CXX=g++
|
||||
|
||||
# Setting this variable so that FFmpeg gets built without pipewiregrab
|
||||
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
|
||||
# We rely on PipeWire for screen sharing on Wayland, but the version available on openSUSE Leap 15.4 is too old.
|
||||
ENV DISABLE_PIPEWIRE=true
|
||||
|
||||
CMD ["/opt/build-package-rpm.sh"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
FROM opensuse/leap:15.5
|
||||
|
||||
RUN zypper refresh
|
||||
RUN zypper --gpg-auto-import-keys refresh
|
||||
|
||||
RUN zypper --non-interactive install -y \
|
||||
dnf \
|
||||
@ -100,7 +100,8 @@ RUN zypper --non-interactive install -y \
|
||||
gstreamer-plugins-bad-devel \
|
||||
gstreamer-plugins-base-devel \
|
||||
cmake \
|
||||
wget
|
||||
wget \
|
||||
pipewire-devel
|
||||
|
||||
# openSUSE Leap 15.5 comes with Python 3.6 by default,
|
||||
# but we need at least 3.7 to compile Qt 6.6.1
|
||||
|
||||
@ -33,4 +33,10 @@ ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
|
||||
RUN /opt/install-cmake.sh
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
|
||||
|
||||
# Setting this variable so that FFmpeg gets built without pipewiregrab
|
||||
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
|
||||
# We rely on PipeWire for screen sharing on Wayland, but the version available on Ubuntu 20.04 is too old.
|
||||
ENV DISABLE_PIPEWIRE=true
|
||||
|
||||
CMD ["/opt/build-package-debian.sh"]
|
||||
|
||||
25
extras/packaging/gnu-linux/docker/Dockerfile_ubuntu_24.04
Normal file
25
extras/packaging/gnu-linux/docker/Dockerfile_ubuntu_24.04
Normal file
@ -0,0 +1,25 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get clean
|
||||
RUN apt-get update && \
|
||||
apt-get install -y -o Acquire::Retries=10 \
|
||||
devscripts \
|
||||
equivs \
|
||||
python-is-python3 \
|
||||
wget
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/install-gcc-debian.sh /opt/install-gcc-debian.sh
|
||||
RUN /opt/install-gcc-debian.sh 13
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/prebuild-package-debian.sh /opt/prebuild-package-debian.sh
|
||||
|
||||
COPY extras/packaging/gnu-linux/rules/debian-qt/control /tmp/builddeps/debian/control
|
||||
RUN /opt/prebuild-package-debian.sh qt-deps
|
||||
|
||||
COPY extras/packaging/gnu-linux/rules/debian/control /tmp/builddeps/debian/control
|
||||
RUN /opt/prebuild-package-debian.sh jami-deps
|
||||
|
||||
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
|
||||
CMD ["/opt/build-package-debian.sh"]
|
||||
@ -92,6 +92,7 @@ Build-Depends: debhelper (>= 9),
|
||||
libgl1-mesa-dri,
|
||||
# pkg-kde-tools (>= 0.15.17~),
|
||||
python3:any,
|
||||
python3-bs4,
|
||||
python3-html5lib,
|
||||
# qtbase5-private-dev (>= 5.15.2+dfsg~),
|
||||
xauth <!nocheck>,
|
||||
|
||||
@ -0,0 +1,314 @@
|
||||
From deeacfdb5a6d1d300d4ba991df76aa12e5dbaa42 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
|
||||
<sebastien.blin@savoirfairelinux.com>
|
||||
Date: Tue, 16 Apr 2024 09:54:32 -0400
|
||||
Subject: [PATCH] fix imp->importlib
|
||||
|
||||
---
|
||||
.../mojo/public/tools/mojom/mojom/fileutil.py | 1 -
|
||||
.../tools/mojom/mojom/fileutil_unittest.py | 5 +----
|
||||
.../mojom/mojom/generate/generator_unittest.py | 7 ++-----
|
||||
.../mojom/mojom/generate/translate_unittest.py | 4 ----
|
||||
.../tools/mojom/mojom/parse/ast_unittest.py | 6 ------
|
||||
.../mojom/parse/conditional_features_unittest.py | 8 ++------
|
||||
.../mojo/public/tools/mojom/mojom/parse/lexer.py | 1 -
|
||||
.../tools/mojom/mojom/parse/lexer_unittest.py | 7 ++-----
|
||||
.../tools/mojom/mojom/parse/parser_unittest.py | 5 -----
|
||||
.../3rdparty/chromium/third_party/six/src/six.py | 16 ++++++++++++++++
|
||||
10 files changed, 23 insertions(+), 37 deletions(-)
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
|
||||
index 29daec367c..124f12c134 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
|
||||
@@ -3,7 +3,6 @@
|
||||
# found in the LICENSE file.
|
||||
|
||||
import errno
|
||||
-import imp
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
|
||||
index 48eaf4eca9..c93d22898d 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
|
||||
@@ -2,19 +2,16 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
import os.path
|
||||
import shutil
|
||||
-import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from mojom import fileutil
|
||||
|
||||
-
|
||||
class FileUtilTest(unittest.TestCase):
|
||||
def testEnsureDirectoryExists(self):
|
||||
- """Test that EnsureDirectoryExists fuctions correctly."""
|
||||
+ """Test that EnsureDirectoryExists functions correctly."""
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
|
||||
index 76cda3981f..7143e07c4d 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
|
||||
@@ -2,12 +2,11 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
+import importlib.util
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
-
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
@@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
|
||||
|
||||
|
||||
try:
|
||||
- imp.find_module("mojom")
|
||||
+ importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
|
||||
from mojom.generate import generator
|
||||
|
||||
-
|
||||
class StringManipulationTest(unittest.TestCase):
|
||||
"""generator contains some string utilities, this tests only those."""
|
||||
|
||||
@@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
|
||||
self.assertEquals("SNAKE_D3D11_CASE",
|
||||
generator.ToUpperSnakeCase("snakeD3d11Case"))
|
||||
|
||||
-
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
|
||||
index 4259374513..558e71e119 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
|
||||
@@ -2,16 +2,12 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
-import os.path
|
||||
-import sys
|
||||
import unittest
|
||||
|
||||
from mojom.generate import module as mojom
|
||||
from mojom.generate import translate
|
||||
from mojom.parse import ast
|
||||
|
||||
-
|
||||
class TranslateTest(unittest.TestCase):
|
||||
"""Tests |parser.Parse()|."""
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
|
||||
index c36376712e..b289f7b11f 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
|
||||
@@ -2,14 +2,10 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
-import os.path
|
||||
-import sys
|
||||
import unittest
|
||||
|
||||
from mojom.parse import ast
|
||||
|
||||
-
|
||||
class _TestNode(ast.NodeBase):
|
||||
"""Node type for tests."""
|
||||
|
||||
@@ -20,13 +16,11 @@ class _TestNode(ast.NodeBase):
|
||||
def __eq__(self, other):
|
||||
return super().__eq__(other) and self.value == other.value
|
||||
|
||||
-
|
||||
class _TestNodeList(ast.NodeListBase):
|
||||
"""Node list type for tests."""
|
||||
|
||||
_list_item_type = _TestNode
|
||||
|
||||
-
|
||||
class ASTTest(unittest.TestCase):
|
||||
"""Tests various AST classes."""
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
|
||||
index 5fc582025e..2fa5d2be6a 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
|
||||
@@ -2,12 +2,11 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
+import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
-
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
@@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
|
||||
if tail == dirname:
|
||||
return path
|
||||
|
||||
-
|
||||
try:
|
||||
- imp.find_module('mojom')
|
||||
+ importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
|
||||
import mojom.parse.ast as ast
|
||||
@@ -29,7 +27,6 @@ import mojom.parse.parser as parser
|
||||
|
||||
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
|
||||
|
||||
-
|
||||
class ConditionalFeaturesTest(unittest.TestCase):
|
||||
"""Tests |mojom.parse.conditional_features|."""
|
||||
|
||||
@@ -356,6 +353,5 @@ class ConditionalFeaturesTest(unittest.TestCase):
|
||||
conditional_features.RemoveDisabledDefinitions,
|
||||
definition, ENABLED_FEATURES)
|
||||
|
||||
-
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
|
||||
index 73ca15df94..1083a1af7b 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
|
||||
@@ -2,7 +2,6 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
|
||||
index ce376da66e..bc9f835431 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
|
||||
@@ -2,12 +2,11 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
+import importlib.util
|
||||
import os.path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
-
|
||||
def _GetDirAbove(dirname):
|
||||
"""Returns the directory "above" this file containing |dirname| (which must
|
||||
also be "above" this file)."""
|
||||
@@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
|
||||
if tail == dirname:
|
||||
return path
|
||||
|
||||
-
|
||||
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
|
||||
from ply import lex
|
||||
|
||||
try:
|
||||
- imp.find_module("mojom")
|
||||
+ importlib.util.find_spec("mojom")
|
||||
except ImportError:
|
||||
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
|
||||
import mojom.parse.lexer
|
||||
|
||||
-
|
||||
# This (monkey-patching LexToken to make comparison value-based) is evil, but
|
||||
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
|
||||
# for object identity.)
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
|
||||
index 0513343ec7..0a26307b1a 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
|
||||
@@ -2,16 +2,12 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
-import imp
|
||||
-import os.path
|
||||
-import sys
|
||||
import unittest
|
||||
|
||||
from mojom.parse import ast
|
||||
from mojom.parse import lexer
|
||||
from mojom.parse import parser
|
||||
|
||||
-
|
||||
class ParserTest(unittest.TestCase):
|
||||
"""Tests |parser.Parse()|."""
|
||||
|
||||
@@ -1375,6 +1371,5 @@ class ParserTest(unittest.TestCase):
|
||||
r" *associated\? MyInterface& a;$"):
|
||||
parser.Parse(source3, "my_file.mojom")
|
||||
|
||||
-
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/third_party/six/src/six.py b/qtwebengine/src/3rdparty/chromium/third_party/six/src/six.py
|
||||
index 5fe9f8e141..96b06f8ce7 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/third_party/six/src/six.py
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/third_party/six/src/six.py
|
||||
@@ -71,6 +71,11 @@ else:
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
+if PY34:
|
||||
+ from importlib.util import spec_from_loader
|
||||
+else:
|
||||
+ spec_from_loader = None
|
||||
+
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
@@ -186,6 +191,11 @@ class _SixMetaPathImporter(object):
|
||||
return self
|
||||
return None
|
||||
|
||||
+ def find_spec(self, fullname, path, target=None):
|
||||
+ if fullname in self.known_modules:
|
||||
+ return spec_from_loader(fullname, self)
|
||||
+ return None
|
||||
+
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
@@ -223,6 +233,12 @@ class _SixMetaPathImporter(object):
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
+ def create_module(self, spec):
|
||||
+ return self.load_module(spec.name)
|
||||
+
|
||||
+ def exec_module(self, module):
|
||||
+ pass
|
||||
+
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp b/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
|
||||
index 3488120543..120e47a76f 100644
|
||||
--- a/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
|
||||
+++ b/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
|
||||
@@ -472,7 +472,7 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length)
|
||||
}
|
||||
catch (const DeadlyImportError& e)
|
||||
{
|
||||
- if (!is64bits && (length > std::numeric_limits<std::uint32_t>::max())) {
|
||||
+ if (!is64bits && (length > std::numeric_limits<uint32_t>::max())) {
|
||||
throw DeadlyImportError("The FBX file is invalid. This may be because the content is too big for this older version (", ai_to_string(version), ") of the FBX format. (", e.what(), ")");
|
||||
}
|
||||
throw;
|
||||
@ -0,0 +1,26 @@
|
||||
From cf208d11dc8a9a02160a57283596ec8bab964a09 Mon Sep 17 00:00:00 2001
|
||||
From: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
Date: Mon, 27 May 2024 16:01:21 -0400
|
||||
Subject: [PATCH] qtwayland: downgrade wl-seat to avoid high-resolution
|
||||
scrolling events
|
||||
|
||||
---
|
||||
qtwayland/src/client/qwaylandinputdevice.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/qtwayland/src/client/qwaylandinputdevice.cpp b/qtwayland/src/client/qwaylandinputdevice.cpp
|
||||
index a4f8757e3c..ad0aa7941c 100644
|
||||
--- a/qtwayland/src/client/qwaylandinputdevice.cpp
|
||||
+++ b/qtwayland/src/client/qwaylandinputdevice.cpp
|
||||
@@ -383,7 +383,7 @@ QWaylandInputDevice::Touch::~Touch()
|
||||
}
|
||||
|
||||
QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id)
|
||||
- : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 9))
|
||||
+ : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 7))
|
||||
, mQDisplay(display)
|
||||
, mDisplay(display->wl_display())
|
||||
{
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
0001-replace_imp_by_importlib.patch
|
||||
0002-fix-binary-tokenizer.patch
|
||||
0003-qtwayland-downgrade-wl-seat-to-avoid-high-resolution.patch
|
||||
@ -45,6 +45,8 @@ Build-Depends: debhelper (>= 9),
|
||||
libvdpau-dev,
|
||||
libssl-dev,
|
||||
libargon2-dev | libargon2-0-dev,
|
||||
# TODO: remove libpipewire-0.2-dev once we stop supporting Ubuntu 20.04
|
||||
libpipewire-0.3-dev | libpipewire-0.2-dev,
|
||||
# other
|
||||
nasm,
|
||||
yasm,
|
||||
|
||||
@ -103,6 +103,8 @@ if [ -f /etc/os-release ]; then
|
||||
ENDTAG="ubuntu_23.04"
|
||||
elif [ "${UBUNTU_CODENAME}" = "mantic" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_23.10" ]; then
|
||||
ENDTAG="ubuntu_23.10"
|
||||
elif [ "${UBUNTU_CODENAME}" = "noble" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_24.04" ]; then
|
||||
ENDTAG="ubuntu_24.04"
|
||||
elif [ "${ID}" = "debian" ] && \
|
||||
[ "$(command -v lsb_release)" ] && \
|
||||
[ "$(lsb_release -rs)" = "testing" ]; then
|
||||
|
||||
@ -50,6 +50,7 @@ BuildRequires: libuuid-devel
|
||||
BuildRequires: libva-devel
|
||||
BuildRequires: libvdpau-devel
|
||||
BuildRequires: pcre-devel
|
||||
BuildRequires: pipewire-devel
|
||||
BuildRequires: uuid-devel
|
||||
BuildRequires: yaml-cpp-devel
|
||||
|
||||
|
||||
@ -16,6 +16,9 @@
|
||||
%define computed_job_count_ %(echo $(( %available_memory / %memory_required_per_core / %max_parallel_builds )))
|
||||
%define computed_job_count %max %computed_job_count_ 1
|
||||
%define job_count %min %cpu_count %computed_job_count
|
||||
# Exclude vendored Qt6 from dependency generator
|
||||
%define __provides_exclude_from ^%{_libdir}/qt-jami/.*$
|
||||
%define __requires_exclude ^libQt6.*$
|
||||
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
@ -26,6 +29,7 @@ License: GPLv3+
|
||||
Vendor: Savoir-faire Linux Inc.
|
||||
URL: https://jami.net/
|
||||
Source: jami-libqt-%{version}.tar.xz
|
||||
Patch0: 0001-fix-gcc14.patch
|
||||
|
||||
%global gst 0.10
|
||||
%if 0%{?fedora} || 0%{?rhel} > 7
|
||||
@ -61,6 +65,7 @@ This package contains Qt libraries for Jami.
|
||||
|
||||
%prep
|
||||
%setup -n qt-everywhere-src-%{version}
|
||||
%patch0 -p1
|
||||
|
||||
%build
|
||||
echo "Building Qt using %{job_count} parallel jobs"
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
%define version RELEASE_VERSION
|
||||
%define release 0
|
||||
|
||||
# Exclude vendored Qt6 from dependency generator
|
||||
%define __requires_exclude ^libQt6.*$
|
||||
|
||||
Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}%{?dist}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
From 9721082687c9529fe6ae3c5304dcf079158e8a77 Mon Sep 17 00:00:00 2001
|
||||
From: Sam James <sam@gentoo.org>
|
||||
Date: Sun, 04 Jun 2023 04:15:16 +0100
|
||||
Subject: [PATCH] heap: Add missing <algorithm> include for std::remove
|
||||
|
||||
GCC 14 changes some internal includes within libstdc++ so this transient
|
||||
include gets lost. Include <algorithm> explicitly for std::remove.
|
||||
|
||||
Change-Id: Iab8a2c751a0f9c9dc6a770d6296ad6de724ef3bb
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4583222
|
||||
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
|
||||
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#88037}
|
||||
---
|
||||
|
||||
diff --git a/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h b/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
|
||||
index 2cf728489d..d8414ae3c6 100644
|
||||
--- a/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
|
||||
+++ b/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
+#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
@ -138,6 +138,10 @@ apps:
|
||||
autostart: jami.desktop
|
||||
common-id: net.jami.Jami
|
||||
desktop: usr/share/applications/jami.desktop
|
||||
environment:
|
||||
PIPEWIRE_CONFIG_NAME: "$SNAP/usr/share/pipewire/pipewire.conf"
|
||||
PIPEWIRE_MODULE_DIR: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pipewire-0.3"
|
||||
SPA_PLUGIN_DIR: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/spa-0.2"
|
||||
slots:
|
||||
- dbus-jami
|
||||
- dbus-ring
|
||||
@ -168,7 +172,7 @@ package-repositories:
|
||||
components: [main]
|
||||
suites: [jami]
|
||||
key-id: A295D773307D25A33AE72F2F64CD5FA175348F84
|
||||
url: https://dl.jami.net/nightly/ubuntu_20.04/
|
||||
url: https://dl.jami.net/internal/ubuntu_20.04/
|
||||
|
||||
parts:
|
||||
desktop-launch:
|
||||
@ -255,6 +259,8 @@ parts:
|
||||
sed -i -E 's|(tmpName) << (PACKAGE_NAME << "_shm_")|\1 << "snap.jami." << \2|' ./daemon/src/media/video/sinkclient.cpp
|
||||
sed -i -E 's|^Icon=.*|Icon=${SNAP}/usr/share/icons/hicolor/scalable/apps/jami.svg|' extras/data/jami.desktop
|
||||
override-build: |
|
||||
$SNAPCRAFT_PART_BUILD/extras/packaging/gnu-linux/scripts/install-pipewire-from-source.sh
|
||||
|
||||
cd $SNAPCRAFT_PART_BUILD/daemon/contrib
|
||||
mkdir -p native
|
||||
cd native
|
||||
|
||||
@ -44,6 +44,23 @@ QUILT_REFRESH_ARGS="-p 1"
|
||||
|
||||
if [ ! -f "${qt_deb_path}" ] || [ "${FORCE_REBUILD_QT}" = "true" ]; then
|
||||
(
|
||||
|
||||
# HACK: For now on ubuntu 24.04 there is no python3.10 package
|
||||
# So create a PyEnv environment to install the required packages
|
||||
if cat /etc/os-release | grep -Eq "24.04"; then
|
||||
apt-get install git gcc make python3-pip libssl-dev curl libreadline-dev -y
|
||||
curl https://pyenv.run | bash
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init -)"
|
||||
pyenv install 3.10.0
|
||||
pyenv local 3.10.0
|
||||
|
||||
python -m pip install html5lib
|
||||
python -m pip install six
|
||||
fi
|
||||
|
||||
|
||||
flock 9 # block until the lock file is gone
|
||||
test -f "${qt_deb_path}" && exit 0 # check again
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ rpmdev-setuptree
|
||||
|
||||
# Copy the source tarball.
|
||||
cp --reflink=auto "/src/$RELEASE_TARBALL_FILENAME" /root/rpmbuild/SOURCES
|
||||
cp patches/*.patch /root/rpmbuild/SOURCES/
|
||||
|
||||
QT_JAMI_PREFIX="/usr/lib64/qt-jami"
|
||||
PATH="${QT_JAMI_PREFIX}/bin:${PATH}"
|
||||
@ -43,7 +44,7 @@ PKG_CONFIG_PATH="${QT_JAMI_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
||||
CMAKE_PREFIX_PATH="${QT_JAMI_PREFIX}/lib/cmake:${CMAKE_PREFIX_PATH}"
|
||||
QT_MAJOR=6
|
||||
QT_MINOR=6
|
||||
QT_PATCH=1
|
||||
QT_PATCH=3
|
||||
QT_RELEASE_PATCH=0
|
||||
|
||||
QT_MAJOR_MINOR=${QT_MAJOR}.${QT_MINOR}
|
||||
@ -52,7 +53,7 @@ QT_MAJOR_MINOR_PATCH=${QT_MAJOR}.${QT_MINOR}.${QT_PATCH}
|
||||
QT_TARBALL_URL=https://download.qt.io/archive/qt/$QT_MAJOR_MINOR/\
|
||||
$QT_MAJOR_MINOR_PATCH/single/qt-everywhere-src-$QT_MAJOR_MINOR_PATCH.tar.xz
|
||||
|
||||
QT_TARBALL_SHA256="dd3668f65645fe270bc615d748bd4dc048bd17b9dc297025106e6ecc419ab95d"
|
||||
QT_TARBALL_SHA256="69d0348fef415da98aa890a34651e9cfb232f1bffcee289b7b4e21386bf36104"
|
||||
QT_TARBALL_FILE_NAME=$(basename "$QT_TARBALL_URL")
|
||||
CACHED_QT_TARBALL=$TARBALLS/$QT_TARBALL_FILE_NAME
|
||||
|
||||
@ -111,6 +112,8 @@ if [ ! -f "${RPM_PATH}" ]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc38.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_39" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc39.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "fedora_40" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc40.x86_64.rpm "${RPM_PATH}"
|
||||
elif [[ "${DISTRIBUTION}" == "alma_9" ]]; then
|
||||
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.el9.x86_64.rpm "${RPM_PATH}"
|
||||
else
|
||||
|
||||
38
extras/packaging/gnu-linux/scripts/install-pipewire-from-source.sh
Executable file
38
extras/packaging/gnu-linux/scripts/install-pipewire-from-source.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# The purpose of this script is to build PipeWire from source in a snap based on core20 / Ubuntu 20.04
|
||||
# It must be called in the "override-build" section of the relevant part in snapcraft.yaml
|
||||
|
||||
set -e
|
||||
|
||||
OLD_WD=$(pwd)
|
||||
cd /tmp
|
||||
|
||||
# Install PipeWire's build dependencies
|
||||
apt-get install --yes gcc git libasound2-dev libdbus-1-dev libglib2.0-dev ninja-build pkg-config
|
||||
|
||||
# Get a version of Meson that's recent enough to build PipeWire 1.0.5 (the one available via apt is too old)
|
||||
wget -q https://github.com/mesonbuild/meson/releases/download/0.61.1/meson-0.61.1.tar.gz
|
||||
echo "feb2cefb325b437dbf36146df7c6b87688ddff0b0205caa31dc64055c6da410c meson-0.61.1.tar.gz" | sha256sum --check
|
||||
tar xzf meson-0.61.1.tar.gz
|
||||
|
||||
# Build PipeWire 1.0.5 and install it in the /usr directory of the build environment
|
||||
wget -q https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/1.0.5/pipewire-1.0.5.tar.gz
|
||||
echo "c5a5de26d684a1a84060ad7b6131654fb2835e03fccad85059be92f8e3ffe993 pipewire-1.0.5.tar.gz" | sha256sum --check
|
||||
tar xzf pipewire-1.0.5.tar.gz
|
||||
cd pipewire-1.0.5
|
||||
../meson-0.61.1/meson.py setup builddir -Dsession-managers=media-session -Dalsa=disabled -Dprefix=/usr
|
||||
../meson-0.61.1/meson.py compile -C builddir
|
||||
../meson-0.61.1/meson.py install -C builddir
|
||||
|
||||
# The files installed by the previous command are only for the "Build" step of the snap
|
||||
# creation process (https://snapcraft.io/docs/how-snapcraft-builds). In order to ensure
|
||||
# that PipeWire is installed in the final snap archive, we also need to copy all the
|
||||
# required files under the $SNAPCRAFT_PART_INSTALL directory.
|
||||
../meson-0.61.1/meson.py configure builddir -Dprefix=$SNAPCRAFT_PART_INSTALL/usr/
|
||||
../meson-0.61.1/meson.py install -C builddir
|
||||
|
||||
# Cleanup
|
||||
cd /tmp
|
||||
rm -rf meson-0.61.1 meson-0.61.1.tar.gz pipewire-1.0.5 pipewire-1.0.5.tar.gz
|
||||
cd $OLD_WD
|
||||
@ -34,7 +34,7 @@ cat << EOFILE > ${REPO_FOLDER}/${SPARKLE_FILE}
|
||||
<pubDate>$DATE_RFC2822</pubDate>
|
||||
<sparkle:version>${BUILD}</sparkle:version>
|
||||
<sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>10.15.0</sparkle:minimumSystemVersion>
|
||||
<sparkle:minimumSystemVersion>11.0</sparkle:minimumSystemVersion>
|
||||
<enclosure url="${REPO_URL}/$(basename ${PACKAGE})" type="application/octet-stream" $(./sign_update ${PACKAGE}) />
|
||||
</item>
|
||||
$(echo -e "${ITEMS}")
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
From ca2be6466c150d1b82a646d97b27df35b45d90f1 Mon Sep 17 00:00:00 2001
|
||||
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
|
||||
Date: Tue, 9 Jan 2024 15:25:19 -0500
|
||||
Subject: [PATCH] workaround right margin
|
||||
|
||||
---
|
||||
src/core/contexts/win32windowcontext.cpp | 11 +++++++++++
|
||||
1 file changed, 11 insertions(+)
|
||||
|
||||
diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
|
||||
index 3f6623e..9ee7752 100644
|
||||
--- a/src/core/contexts/win32windowcontext.cpp
|
||||
+++ b/src/core/contexts/win32windowcontext.cpp
|
||||
@@ -402,6 +402,17 @@ namespace QWK {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+
|
||||
+#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
|
||||
+ if (msg->message == WM_MOVE || msg->message == WM_SIZE ||
|
||||
+ (msg->message == WM_IME_SETCONTEXT && (GetForegroundWindow() == msg->hwnd))) {
|
||||
+ static const auto flags = SWP_FRAMECHANGED | SWP_NOMOVE |
|
||||
+ SWP_NOSIZE | SWP_NOZORDER |
|
||||
+ SWP_NOOWNERZORDER;
|
||||
+ SetWindowPos(msg->hwnd, NULL, 0, 0, 0, 0, flags);
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
return false;
|
||||
}
|
||||
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@ -28,7 +28,7 @@ mutually exclusive required arguments:
|
||||
-z, --zip Build portable archive
|
||||
|
||||
examples:
|
||||
1. build.py --qt=C:/Qt/6.6.1/msvc2019_64 # Build the app using a specific Qt
|
||||
1. build.py --qt=C:/Qt/6.6.2/msvc2019_64 # Build the app using a specific Qt
|
||||
2. build.py --init pack --msi # Build the app and an MSI installer
|
||||
3. build.py --init --tests # Build the app and run tests
|
||||
build.py pack --zip --skip-build # Generate a 7z archive of the app
|
||||
@ -280,7 +280,7 @@ def build(config_str, qt_dir, tests):
|
||||
"-DCMAKE_INSTALL_PREFIX=" + os.getcwd(),
|
||||
"-DCMAKE_SYSTEM_VERSION=" + WIN_SDK_VERSION,
|
||||
"-DCMAKE_BUILD_TYPE=" + "Release",
|
||||
"-DENABLE_TESTS=" + str(tests).lower(),
|
||||
"-DBUILD_TESTING=" + str(tests).lower(),
|
||||
"-DBETA=" + str((0, 1)[config_str == "Beta"]),
|
||||
]
|
||||
|
||||
|
||||
@ -48,8 +48,9 @@ enable_webengine=true
|
||||
asan=
|
||||
extra_cmake_flags=''
|
||||
arch=''
|
||||
enable_testing=false
|
||||
|
||||
while getopts gsc:dQ:P:p:uWwa:AD: OPT; do
|
||||
while getopts gsc:dQ:P:p:uWwa:AtD: OPT; do
|
||||
case "$OPT" in
|
||||
g)
|
||||
global='true'
|
||||
@ -84,6 +85,9 @@ while getopts gsc:dQ:P:p:uWwa:AD: OPT; do
|
||||
A)
|
||||
asan='true'
|
||||
;;
|
||||
t)
|
||||
enable_testing='true'
|
||||
;;
|
||||
D)
|
||||
extra_cmake_flags="${OPTARG}"
|
||||
;;
|
||||
@ -202,6 +206,12 @@ if [ "${asan}" = "true" ]; then
|
||||
client_cmake_flags+=(-DENABLE_ASAN=true)
|
||||
fi
|
||||
|
||||
if [ "${enable_testing}" = "true" ]; then
|
||||
client_cmake_flags+=(-DBUILD_TESTING=On)
|
||||
else
|
||||
client_cmake_flags+=(-DBUILD_TESTING=Off)
|
||||
fi
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
#detect arch for macos
|
||||
CMAKE_OSX_ARCHITECTURES="arm64"
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.15</string>
|
||||
<string>11.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
10
resources/icons/Receive.svg
Normal file
10
resources/icons/Receive.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg id="Receive" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="Rectangle_429" data-name="Rectangle 429" width="12" height="12" fill="none"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Group_225" data-name="Group 225" clip-path="url(#clip-path)">
|
||||
<path id="Path_333" data-name="Path 333" d="M6.43,8.784,3.007,5.362,4.06,4.309l2.37,2.37,4.314-4.314A5.966,5.966,0,0,0,6,0c-.032,0-.061.008-.094.01A5.98,5.98,0,0,0,.094,5.074,5.911,5.911,0,0,0,0,6a5.911,5.911,0,0,0,.094.926A5.98,5.98,0,0,0,5.906,11.99c.032,0,.061.01.094.01a6,6,0,0,0,5.533-8.32Z" fill="#60c880"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
3
resources/icons/incoming-call.svg
Normal file
3
resources/icons/incoming-call.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22.08" height="22.08" viewBox="0 0 22.08 22.08">
|
||||
<path id="noun-arrow-1167262" d="M35.45,26.488l-4.476,4.476V18.9H28.916V30.964l-4.476-4.476L23,27.955,29.945,34.9l6.971-6.945Z" transform="translate(8.913 -29.202) rotate(45)" stroke="#000" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
8
resources/icons/missed-incoming-call.svg
Normal file
8
resources/icons/missed-incoming-call.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8.814" viewBox="0 0 16 8.814">
|
||||
<g id="noun-missed-3555066" transform="translate(-23.455 -28)">
|
||||
<g id="Group_82" data-name="Group 82" transform="translate(23.455 28)">
|
||||
<path id="Path_289" data-name="Path 289" d="M37.727,37.615a2.761,2.761,0,0,1-1.964-.815L31.17,32.211l1.782-1.782,4.589,4.593a.268.268,0,0,0,.368,0l5.852-5.852,1.782,1.782L39.691,36.8A2.761,2.761,0,0,1,37.727,37.615Z" transform="translate(-29.543 -28.802)"/>
|
||||
<path id="Path_290" data-name="Path 290" d="M28.518,35.555H26v-6.3A1.259,1.259,0,0,1,27.259,28h6.3v2.518H28.518Z" transform="translate(-26 -28)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
8
resources/icons/missed-outgoing-call.svg
Normal file
8
resources/icons/missed-outgoing-call.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8.814" viewBox="0 0 16 8.814">
|
||||
<g id="noun-missed-3555066" transform="translate(-0.001)">
|
||||
<g id="Group_82" data-name="Group 82" transform="translate(0.001)">
|
||||
<path id="Path_289" data-name="Path 289" d="M38.986,37.615A2.761,2.761,0,0,0,40.95,36.8l4.593-4.589-1.782-1.782-4.589,4.593a.268.268,0,0,1-.368,0L32.952,29.17,31.17,30.952,37.022,36.8A2.761,2.761,0,0,0,38.986,37.615Z" transform="translate(-31.17 -28.802)"/>
|
||||
<path id="Path_290" data-name="Path 290" d="M31.036,35.555h2.518v-6.3A1.259,1.259,0,0,0,32.3,28H26v2.518h5.036Z" transform="translate(-17.555 -28)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
3
resources/icons/outgoing-call.svg
Normal file
3
resources/icons/outgoing-call.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22.08" height="22.08" viewBox="0 0 22.08 22.08">
|
||||
<path id="noun-arrow-1167262" d="M12.45,7.589,7.974,12.064V0H5.916V12.064L1.44,7.588,0,9.055,6.945,16l6.971-6.945Z" transform="translate(10.267 21.654) rotate(-135)" stroke="#000" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@ -1,5 +1,6 @@
|
||||
<h4 align="left"><span style="font-weight:600"> Created by</span></h4>
|
||||
<p>Adrien Béraud<br>
|
||||
<p>Abhishek Ojha<br>
|
||||
Adrien Béraud<br>
|
||||
Albert Babí<br>
|
||||
Alexandre Lision<br>
|
||||
Alexandr Sergheev<br>
|
||||
@ -25,6 +26,7 @@ Emma Falkiewitz<br>
|
||||
Emmanuel Lepage-Vallée<br>
|
||||
Fadi Shehadeh<br>
|
||||
Franck Laurent<br>
|
||||
François-Simon Fauteux-Chapleau<br>
|
||||
Frédéric Guimont<br>
|
||||
Guillaume Heller<br>
|
||||
Guillaume Roguez<br>
|
||||
|
||||
217
src/app/ComponentTestWindow.qml
Normal file
217
src/app/ComponentTestWindow.qml
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import net.jami.Helpers 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "mainview"
|
||||
import "mainview/components"
|
||||
import "wizardview"
|
||||
import "commoncomponents"
|
||||
|
||||
// A window into which we can load a QML file for testing.
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
visible: true
|
||||
width: testWidth || loader.implicitWidth || 800
|
||||
height: testHeight || loader.implicitHeight || 600
|
||||
title: testComponentURI
|
||||
|
||||
// WARNING: The following currently must be maintained in tandem with MainApplicationWindow.qml
|
||||
// Used to manage full screen mode and save/restore window geometry.
|
||||
readonly property bool useFrameless: false
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
LayoutMirroring.enabled: isRTL
|
||||
LayoutMirroring.childrenInherit: isRTL
|
||||
property LayoutManager layoutManager: LayoutManager {
|
||||
appContainer: null
|
||||
}
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
// Used to manage the view stack and the current view.
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
source: Qt.resolvedUrl(testComponentURI)
|
||||
onStatusChanged: {
|
||||
console.log("Status changed to:", loader.status)
|
||||
if (loader.status == Loader.Error || loader.status == Loader.Null) {
|
||||
console.error("Couldn't load component:", source)
|
||||
Qt.exit(1);
|
||||
} else if (loader.status == Loader.Ready) {
|
||||
console.info("Loaded component:", source);
|
||||
// If any of the dimensions are not set, set them to the appWindow's dimensions
|
||||
item.width = item.width || Qt.binding(() => appWindow.width);
|
||||
item.height = item.height || Qt.binding(() => appWindow.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Closing this window should always exit the application.
|
||||
onClosing: Qt.quit()
|
||||
|
||||
// A window to modify properties for Jamified components.
|
||||
// Sometimes we need to modify properties including current conversation ID, account ID, etc.
|
||||
// This window should have a simple layout: a list of editable parameters within a scroll view.
|
||||
Window {
|
||||
id: configTool
|
||||
width: 400
|
||||
height: 400
|
||||
title: "Config tool"
|
||||
|
||||
visible: true
|
||||
// Cannot be closed.
|
||||
flags: Qt.SplashScreen
|
||||
|
||||
// Anchor the window to the right of the parent window.
|
||||
x: appWindow.x + appWindow.width
|
||||
y: appWindow.y
|
||||
|
||||
color: "lightgray"
|
||||
|
||||
Page {
|
||||
anchors.fill: parent
|
||||
header: Control {
|
||||
contentItem: Text {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "Config tool"
|
||||
}
|
||||
background: Rectangle { color: configTool.color }
|
||||
}
|
||||
contentItem: Control {
|
||||
background: Rectangle { color: Qt.lighter(configTool.color, 1.1) }
|
||||
padding: 10
|
||||
contentItem: ListView {
|
||||
// Declare types of controls. TODO: add as needed.
|
||||
Component {
|
||||
id: checkComponent
|
||||
CheckBox {
|
||||
text: label
|
||||
onCheckedChanged: checkChangedCb(checked)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: comboComponent
|
||||
Control {
|
||||
contentItem: RowLayout {
|
||||
Text { text: label }
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
displayText: CurrentConversation.title || "undefined"
|
||||
model: getDataModel()
|
||||
delegate: ItemDelegate {
|
||||
highlighted: comboBox.highlightedIndex === index
|
||||
width: parent.width
|
||||
text: JamiQmlUtils.getModelData(comboBox.model, index, displayRole)
|
||||
}
|
||||
onCurrentIndexChanged: onIndexChanged(model, currentIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spacing: 5
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
label: "Conversation ID"
|
||||
type: "combobox"
|
||||
getDataModel: () => ConversationsAdapter.convListProxyModel
|
||||
displayRole: ConversationList.Title
|
||||
onIndexChanged: function(model, index) {
|
||||
const convUid = JamiQmlUtils.getModelData(model, index, ConversationList.UID);
|
||||
LRCInstance.selectConversation(convUid);
|
||||
}
|
||||
}
|
||||
ListElement {
|
||||
label: "Account ID"
|
||||
type: "combobox"
|
||||
getDataModel: () => AccountListModel
|
||||
displayRole: AccountList.Username
|
||||
onIndexChanged: function(model, index) {
|
||||
const accountId = JamiQmlUtils.getModelData(model, index, AccountList.ID);
|
||||
LRCInstance.currentAccountId = accountId;
|
||||
}
|
||||
}
|
||||
ListElement {
|
||||
label: "Force local preview"
|
||||
type: "checkbox"
|
||||
value: false
|
||||
checkChangedCb: function(checked) {
|
||||
// Find any child component of type `LocalVideo` and start it.
|
||||
const localVideo = findChild(loader.item, LocalVideo, "type");
|
||||
if (localVideo) {
|
||||
if (checked) {
|
||||
localVideo.startWithId(VideoDevices.getDefaultDevice());
|
||||
} else {
|
||||
localVideo.startWithId("");
|
||||
}
|
||||
} else {
|
||||
console.error("LocalVideo not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "type"
|
||||
DelegateChoice {
|
||||
roleValue: "checkbox"
|
||||
delegate: checkComponent
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "combobox"
|
||||
delegate: comboComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From TestCase.qml, refactored to find a child by type or name.
|
||||
function findChild(parent, searchValue, searchBy = "name") {
|
||||
if (!parent || parent.children === undefined) {
|
||||
console.error("No children found");
|
||||
return null;
|
||||
}
|
||||
// Search directly under the given parent
|
||||
for (var i = 0; i < parent.children.length; ++i) {
|
||||
var child = parent.children[i];
|
||||
var match = false;
|
||||
if (searchBy === "name" && child.objectName === searchValue) {
|
||||
match = true;
|
||||
} else if (searchBy === "type" && child instanceof searchValue) {
|
||||
match = true;
|
||||
}
|
||||
if (match) return child;
|
||||
}
|
||||
// Recursively search in child objects
|
||||
for (i = 0; i < parent.children.length; ++i) {
|
||||
var found = findChild(parent.children[i], searchValue, searchBy);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -41,6 +41,20 @@ QtObject {
|
||||
// Used to store if a OngoingCallPage component is fullscreened.
|
||||
property bool isCallFullscreen: false
|
||||
|
||||
// QWK: Provide spacing for widgets that may be occluded by the system buttons.
|
||||
property QtObject qwkSystemButtonSpacing: QtObject {
|
||||
id: qwkSystemButtonSpacing
|
||||
readonly property bool isMacOS: Qt.platform.os.toString() === "osx"
|
||||
// macOS buttons are on the left.
|
||||
readonly property real left: {
|
||||
appWindow.useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0
|
||||
}
|
||||
// Windows and Linux buttons are on the right.
|
||||
readonly property real right: {
|
||||
appWindow.useFrameless && !isMacOS && !root.isFullscreen ? sysBtnsLoader.width + 24 : 0
|
||||
}
|
||||
}
|
||||
|
||||
// Restore a visible windowed mode.
|
||||
function restoreApp() {
|
||||
if (isHidden) {
|
||||
|
||||
@ -41,14 +41,11 @@ import QWindowKit
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
|
||||
readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
|
||||
LayoutMirroring.enabled: isRTL
|
||||
LayoutMirroring.childrenInherit: isRTL
|
||||
|
||||
// This needs to be set from the start.
|
||||
readonly property bool useFrameless: UtilsAdapter.getAppValue(Settings.Key.UseFramelessWindow)
|
||||
|
||||
onActiveFocusItemChanged: {
|
||||
focusOverlay.margin = -5;
|
||||
if (activeFocusItem && ((activeFocusItem.focusReason === Qt.TabFocusReason) || (activeFocusItem.focusReason === Qt.BacktabFocusReason))) {
|
||||
@ -94,16 +91,10 @@ ApplicationWindow {
|
||||
id: layoutManager
|
||||
appContainer: fullscreenContainer
|
||||
}
|
||||
|
||||
// Used to manage dynamic view loading and unloading.
|
||||
ViewManager {
|
||||
id: viewManager
|
||||
}
|
||||
|
||||
property ViewManager viewManager: ViewManager {}
|
||||
// Used to manage the view stack and the current view.
|
||||
ViewCoordinator {
|
||||
id: viewCoordinator
|
||||
}
|
||||
property ViewCoordinator viewCoordinator: ViewCoordinator {}
|
||||
|
||||
// Used to prevent the window from being visible until the
|
||||
// window geometry has been restored and the view stack has
|
||||
@ -125,7 +116,8 @@ ApplicationWindow {
|
||||
function close(force = false) {
|
||||
// If we're in the onboarding wizard or 'MinimizeOnClose'
|
||||
// is set, then we can quit
|
||||
if (force || !UtilsAdapter.getAppValue(Settings.MinimizeOnClose) || !UtilsAdapter.getAccountListSize()) {
|
||||
var minimizeToTray = UtilsAdapter.getAppValue(Settings.MinimizeOnClose) && UtilsAdapter.isSystemTrayIconVisible();
|
||||
if (force || !minimizeToTray || !UtilsAdapter.getAccountListSize()) {
|
||||
Qt.quit();
|
||||
} else {
|
||||
layoutManager.closeToTray();
|
||||
@ -234,17 +226,6 @@ ApplicationWindow {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// QWK: Provide spacing for widgets that may be occluded by the system buttons.
|
||||
QtObject {
|
||||
id: qwkSystemButtonSpacing
|
||||
readonly property bool isMacOS: Qt.platform.os.toString() === "osx"
|
||||
readonly property bool isFullscreen: layoutManager.isFullScreen
|
||||
// macOS buttons are on the left.
|
||||
readonly property real left: useFrameless && isMacOS && viewCoordinator.isInSinglePaneMode ? 80 : 0
|
||||
// Windows and Linux buttons are on the right.
|
||||
readonly property real right: useFrameless && !isMacOS && !isFullscreen ? sysBtnsLoader.width + 24 : 0
|
||||
}
|
||||
|
||||
// QWK: Window Title bar
|
||||
Item {
|
||||
id: titleBar
|
||||
@ -289,6 +270,12 @@ ApplicationWindow {
|
||||
raise();
|
||||
layoutManager.restoreApp();
|
||||
}
|
||||
|
||||
function onCurrentAccountRemoved() {
|
||||
if (UtilsAdapter.getAccountListSize() === 0) {
|
||||
viewCoordinator.present("WizardView");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@ -227,7 +227,7 @@ AccountAdapter::createJAMSAccount(const QVariantMap& settings)
|
||||
&lrcInstance_->accountModel(),
|
||||
&lrc::api::AccountModel::accountAdded,
|
||||
[this](const QString& accountId) {
|
||||
if (!lrcInstance_->accountModel().getAccountList().size())
|
||||
if (!lrcInstance_->accountModel().getAccountCount())
|
||||
return;
|
||||
|
||||
Utils::oneShotConnect(&lrcInstance_->accountModel(),
|
||||
|
||||
@ -29,13 +29,42 @@ AccountListModel::AccountListModel(LRCInstance* instance, QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
{
|
||||
lrcInstance_ = instance;
|
||||
|
||||
// Avoid resetting/redrawing the model when the account status changes.
|
||||
QObject::connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::accountStatusChanged,
|
||||
this,
|
||||
[&](const QString& accountId) {
|
||||
auto accountList = lrcInstance_->accountModel().getAccountList();
|
||||
auto index = accountList.indexOf(accountId);
|
||||
if (index != -1) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex /*, ALL ROLES */);
|
||||
}
|
||||
});
|
||||
// If there's a reorder, it's reasonable to reset the model for simplicity, instead
|
||||
// of computing the difference. The same goes for accounts being added and removed.
|
||||
// These operations will only occur when the list is hidden, unless dbus is used while
|
||||
// the list is visible.
|
||||
QObject::connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::accountsReordered,
|
||||
this,
|
||||
&AccountListModel::reset);
|
||||
QObject::connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::accountAdded,
|
||||
this,
|
||||
&AccountListModel::reset);
|
||||
QObject::connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::accountRemoved,
|
||||
this,
|
||||
&AccountListModel::reset);
|
||||
}
|
||||
|
||||
int
|
||||
AccountListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (!parent.isValid() && lrcInstance_) {
|
||||
return lrcInstance_->accountModel().getAccountList().size();
|
||||
return lrcInstance_->accountModel().getAccountCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -71,7 +100,7 @@ AccountListModel::data(const QModelIndex& index, int role) const
|
||||
void
|
||||
AccountListModel::updateNotifications()
|
||||
{
|
||||
for (int i = 0; i < lrcInstance_->accountModel().getAccountList().size(); ++i) {
|
||||
for (int i = 0; i < lrcInstance_->accountModel().getAccountCount(); ++i) {
|
||||
QModelIndex modelIndex = QAbstractListModel::index(i, 0);
|
||||
Q_EMIT dataChanged(modelIndex, modelIndex, {Role::NotificationCount});
|
||||
}
|
||||
@ -91,6 +120,7 @@ AccountListModel::roleNames() const
|
||||
void
|
||||
AccountListModel::reset()
|
||||
{
|
||||
// Used to invalidate proxy models.
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@ -20,6 +20,8 @@
|
||||
|
||||
#include "appsettingsmanager.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QLibraryInfo>
|
||||
|
||||
@ -101,7 +103,7 @@ AppSettingsManager::loadTranslations()
|
||||
installedTr_.clear();
|
||||
|
||||
QString locale_name = getLanguage();
|
||||
qDebug() << QString("Using locale: %1").arg(locale_name);
|
||||
C_INFO << QString("Using locale: %1").arg(locale_name);
|
||||
QString locale_lang = locale_name.split('_')[0];
|
||||
|
||||
QTranslator* qtTranslator_lang = new QTranslator(qApp);
|
||||
|
||||
@ -43,11 +43,10 @@ extern const QString defaultDownloadPath;
|
||||
|
||||
// Common key-value pairs for both APPSTORE and non-APPSTORE builds
|
||||
#define COMMON_KEYS \
|
||||
X(MinimizeOnClose, false) \
|
||||
X(MinimizeOnClose, true) \
|
||||
X(DownloadPath, defaultDownloadPath) \
|
||||
X(ScreenshotPath, {}) \
|
||||
X(EnableNotifications, true) \
|
||||
X(EnableTypingIndicator, true) \
|
||||
X(EnableReadReceipt, true) \
|
||||
X(AcceptTransferBelow, 20) \
|
||||
X(AutoAcceptFiles, true) \
|
||||
|
||||
@ -25,7 +25,11 @@
|
||||
#include "api/devicemodel.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "screencastportal.h"
|
||||
#include "xrectsel.h"
|
||||
#ifndef ENABLE_LIBWRAP
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
@ -58,6 +62,12 @@ AvAdapter::AvAdapter(LRCInstance* instance, QObject* parent)
|
||||
&lrc::api::AVModel::onRendererFpsChange,
|
||||
this,
|
||||
&AvAdapter::updateRenderersFPSInfo);
|
||||
#ifdef Q_OS_LINUX
|
||||
connect(&lrcInstance_->behaviorController(),
|
||||
&BehaviorController::callStatusChanged,
|
||||
this,
|
||||
&AvAdapter::onCallStatusChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
// The top left corner of primary screen is (0, 0).
|
||||
@ -119,6 +129,93 @@ AvAdapter::shareEntireScreen(int screenNumber)
|
||||
->addMedia(callId, resource, lrc::api::CallModel::MediaRequestType::SCREENSHARING);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
static std::map<QString, std::unique_ptr<ScreenCastPortal>> callPortal;
|
||||
|
||||
void
|
||||
AvAdapter::onCallStatusChanged(const QString& accountId, const QString& callId)
|
||||
{
|
||||
auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
|
||||
auto& callModel = accInfo.callModel;
|
||||
const auto call = callModel->getCall(callId);
|
||||
|
||||
if (call.status == lrc::api::call::Status::ENDED) {
|
||||
closePortal(callId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::closePortal(const QString& callId)
|
||||
{
|
||||
if (callPortal.count(callId)) {
|
||||
lrcInstance_->avModel().stopPreview(callPortal[callId]->videoInputId);
|
||||
callPortal.erase(callId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareWayland(bool entireScreen)
|
||||
{
|
||||
QString callId = lrcInstance_->getCurrentCallId();
|
||||
closePortal(callId);
|
||||
|
||||
PortalCaptureType captureType = entireScreen ? PortalCaptureType::SCREEN
|
||||
: PortalCaptureType::WINDOW;
|
||||
auto portal = std::make_unique<ScreenCastPortal>(captureType);
|
||||
|
||||
int err = portal->getPipewireFd();
|
||||
if (err == EACCES) {
|
||||
qInfo() << "Can't share screen: permission denied";
|
||||
return;
|
||||
} else if (err != 0) {
|
||||
qWarning() << "Failed to get PipeWire fd. Error code:" << err;
|
||||
return;
|
||||
}
|
||||
QString resource = QString("%1%2pipewire pid:%3 fd:%4 node:%5")
|
||||
.arg(libjami::Media::VideoProtocolPrefix::DISPLAY)
|
||||
.arg(libjami::Media::VideoProtocolPrefix::SEPARATOR)
|
||||
.arg(getpid())
|
||||
.arg(portal->pipewireFd)
|
||||
.arg(portal->pipewireNode);
|
||||
#ifndef ENABLE_LIBWRAP
|
||||
// If the daemon is running as a separate process, then it can't directly use the
|
||||
// PipeWire file descriptor opened by the client, so it will attempt to duplicate
|
||||
// it using the pidfd_getfd system call. This requires the daemon process to have
|
||||
// ptrace permission on the client process. On some systems, this will be true by
|
||||
// default (as long as the client and daemon processes have the same uid), but it
|
||||
// may not be if the Yama Linux Security Module is used. The call to prctl below
|
||||
// will grant permission if the Yama LSM is enabled and set to mode 1.
|
||||
//
|
||||
// References:
|
||||
// https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html
|
||||
// https://man7.org/linux/man-pages/man2/prctl.2.html
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/LSM/Yama.rst
|
||||
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
|
||||
#endif
|
||||
// We open the video input here (instead of letting the daemon do it) to ensure
|
||||
// that the daemon doesn't try to restart it while we still need it, since this
|
||||
// would require getting a new file descriptor for PipeWire.
|
||||
portal->videoInputId = lrcInstance_->avModel().startPreview(resource);
|
||||
|
||||
callPortal[callId] = std::move(portal);
|
||||
muteCamera_ = !isCapturing();
|
||||
lrcInstance_->getCurrentCallModel()
|
||||
->addMedia(callId, resource, lrc::api::CallModel::MediaRequestType::SCREENSHARING);
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareEntireScreenWayland()
|
||||
{
|
||||
shareWayland(true);
|
||||
}
|
||||
|
||||
void
|
||||
AvAdapter::shareWindowWayland()
|
||||
{
|
||||
shareWayland(false);
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
void
|
||||
AvAdapter::shareAllScreens()
|
||||
{
|
||||
@ -204,10 +301,14 @@ AvAdapter::shareFile(const QString& filePath)
|
||||
&lrc::api::AVModel::fileOpened,
|
||||
this,
|
||||
[this, callId, filePath, resource](bool hasAudio, bool hasVideo) {
|
||||
lrcInstance_->avModel().setAutoRestart(resource, true);
|
||||
lrcInstance_->getCurrentCallModel()
|
||||
->addMedia(callId, filePath, lrc::api::CallModel::MediaRequestType::FILESHARING, false, hasAudio);
|
||||
lrcInstance_->avModel().pausePlayer(resource, false);
|
||||
lrcInstance_->avModel().setAutoRestart(resource, true);
|
||||
lrcInstance_->getCurrentCallModel()
|
||||
->addMedia(callId,
|
||||
filePath,
|
||||
lrc::api::CallModel::MediaRequestType::FILESHARING,
|
||||
false,
|
||||
hasAudio);
|
||||
lrcInstance_->avModel().pausePlayer(resource, false);
|
||||
});
|
||||
|
||||
lrcInstance_->avModel().createMediaPlayer(resource);
|
||||
@ -307,6 +408,9 @@ void
|
||||
AvAdapter::stopSharing(const QString& source)
|
||||
{
|
||||
auto callId = lrcInstance_->getCurrentCallId();
|
||||
#ifdef Q_OS_LINUX
|
||||
closePortal(callId);
|
||||
#endif
|
||||
if (!source.isEmpty() && !callId.isEmpty()) {
|
||||
if (source.startsWith(libjami::Media::VideoProtocolPrefix::DISPLAY)) {
|
||||
qDebug() << "Stopping display: " << source;
|
||||
|
||||
@ -69,9 +69,18 @@ protected:
|
||||
*/
|
||||
Q_INVOKABLE bool hasCamera() const;
|
||||
|
||||
// Share the screen specificed by screen number.
|
||||
// Share the screen specificed by screen number (all platforms except Wayland).
|
||||
Q_INVOKABLE void shareEntireScreen(int screenNumber);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Share a screen on Wayland.
|
||||
// Sharing a screen on Wayland requires getting permission from the user. The logic for
|
||||
// this is handled by the ScreenCastPortal class using xdg-desktop-portal.
|
||||
// The choice of screen is also handled by xdg-desktop-portal, which is why we don't need
|
||||
// an argument for it (whereas we do on other platforms, cf. shareEntireScreen above).
|
||||
Q_INVOKABLE void shareEntireScreenWayland();
|
||||
#endif
|
||||
|
||||
// Share the all screens connected.
|
||||
Q_INVOKABLE void shareAllScreens();
|
||||
|
||||
@ -87,9 +96,18 @@ protected:
|
||||
// Select screen area to display (from all screens).
|
||||
Q_INVOKABLE void shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height);
|
||||
|
||||
// Select window to display.
|
||||
// Select window to display (all platforms except Wayland).
|
||||
Q_INVOKABLE void shareWindow(const QString& windowProcessId, const QString& windowId);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Share a window on Wayland.
|
||||
// Sharing a window on Wayland requires getting permission from the user. The logic for
|
||||
// this is handled by the ScreenCastPortal class using xdg-desktop-portal.
|
||||
// The choice of window is also handled by xdg-desktop-portal, which is why we don't need
|
||||
// arguments for it (whereas we do on other platforms, cf. shareWindow above).
|
||||
Q_INVOKABLE void shareWindowWayland();
|
||||
#endif
|
||||
|
||||
// Returns the screensharing resource
|
||||
Q_INVOKABLE QString getSharingResource(int screenId = -2,
|
||||
const QString& windowProcessId = "",
|
||||
@ -121,11 +139,25 @@ private Q_SLOTS:
|
||||
void onAudioDeviceEvent();
|
||||
void onRendererStarted(const QString& id, const QSize& size);
|
||||
void onRendererStopped(const QString& id);
|
||||
#ifdef Q_OS_LINUX
|
||||
// This function needs to be called whenever a screen/window share stops on Wayland.
|
||||
// Failure to do so can cause subsequent sharing attempts to fail.
|
||||
void closePortal(const QString& callId);
|
||||
|
||||
// On Wayland, we need to be informed of call status changes so that we can call
|
||||
// closePortal if a call ends while a screen/window share was in progress.
|
||||
void onCallStatusChanged(const QString& accountId, const QString& callId);
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Get screens arrangement rect relative to primary screen.
|
||||
const QRect getAllScreensBoundingRect();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Used internally by shareEntireScreenWayland and shareWindowWayland
|
||||
void shareWayland(bool entireScreen);
|
||||
#endif
|
||||
|
||||
// Get the screen number
|
||||
int getScreenNumber(int screenId = 0) const;
|
||||
|
||||
|
||||
@ -25,52 +25,74 @@
|
||||
|
||||
#include <QImage>
|
||||
|
||||
class AvatarImageProvider : public QuickImageProviderBase
|
||||
class AsyncAvatarImageResponseRunnable : public AsyncImageResponseRunnable
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarImageProvider(LRCInstance* instance = nullptr)
|
||||
: QuickImageProviderBase(QQuickImageProvider::Image,
|
||||
QQmlImageProviderBase::ForceAsynchronousImageLoading,
|
||||
instance)
|
||||
AsyncAvatarImageResponseRunnable(const QString& id,
|
||||
const QSize& requestedSize,
|
||||
LRCInstance* lrcInstance)
|
||||
: AsyncImageResponseRunnable(id, requestedSize, lrcInstance)
|
||||
{}
|
||||
|
||||
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override
|
||||
void run() override
|
||||
{
|
||||
Q_UNUSED(size)
|
||||
|
||||
if (requestedSize == QSize(0, 0)) {
|
||||
qWarning() << Q_FUNC_INFO << "Image request has no dimensions";
|
||||
return {};
|
||||
// For avatar images, the requested size should be a square. Anything else
|
||||
// is a request made prior to an aspect ratio guard calculation.
|
||||
if (requestedSize_ == QSize(0, 0) || requestedSize_.width() != requestedSize_.height()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the first string is the item uri and the second is a uid
|
||||
// that is used for trigger a reload of the underlying image
|
||||
// data and can be discarded at this point
|
||||
auto idInfo = id.split("_");
|
||||
auto idInfo = id_.split("_");
|
||||
|
||||
if (idInfo.size() < 2) {
|
||||
qWarning() << Q_FUNC_INFO << "Missing element(s) in the image url";
|
||||
return {};
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& imageId = idInfo.at(1);
|
||||
if (!imageId.size()) {
|
||||
qWarning() << Q_FUNC_INFO << "Missing id in the image url";
|
||||
return {};
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image;
|
||||
const auto& type = idInfo.at(0);
|
||||
|
||||
if (type == "conversation") {
|
||||
if (imageId == "temp")
|
||||
return Utils::tempConversationAvatar(requestedSize);
|
||||
return Utils::conversationAvatar(lrcInstance_, imageId, requestedSize);
|
||||
image = Utils::tempConversationAvatar(requestedSize_);
|
||||
else
|
||||
image = Utils::conversationAvatar(lrcInstance_, imageId, requestedSize_);
|
||||
} else if (type == "account") {
|
||||
image = Utils::accountPhoto(lrcInstance_, imageId, requestedSize_);
|
||||
} else if (type == "contact") {
|
||||
image = Utils::contactPhoto(lrcInstance_, imageId, requestedSize_);
|
||||
} else {
|
||||
qWarning() << Q_FUNC_INFO << "Missing valid prefix in the image url";
|
||||
return;
|
||||
}
|
||||
if (type == "account")
|
||||
return Utils::accountPhoto(lrcInstance_, imageId, requestedSize);
|
||||
if (type == "contact")
|
||||
return Utils::contactPhoto(lrcInstance_, imageId, requestedSize);
|
||||
|
||||
qWarning() << Q_FUNC_INFO << "Missing valid prefix in the image url";
|
||||
return {};
|
||||
Q_EMIT done(image);
|
||||
}
|
||||
};
|
||||
|
||||
class AvatarImageProvider : public AsyncImageProviderBase
|
||||
{
|
||||
public:
|
||||
AvatarImageProvider(LRCInstance* instance = nullptr)
|
||||
: AsyncImageProviderBase(instance)
|
||||
{}
|
||||
|
||||
QQuickImageResponse* requestImageResponse(const QString& id, const QSize& requestedSize) override
|
||||
{
|
||||
auto response = new AsyncImageResponse<AsyncAvatarImageResponseRunnable>(id,
|
||||
requestedSize,
|
||||
&pool_,
|
||||
lrcInstance_);
|
||||
return response;
|
||||
}
|
||||
};
|
||||
|
||||
@ -33,8 +33,7 @@ AvatarRegistry::AvatarRegistry(LRCInstance* instance, QObject* parent)
|
||||
connect(&lrcInstance_->accountModel(),
|
||||
&AccountModel::profileUpdated,
|
||||
this,
|
||||
&AvatarRegistry::addOrUpdateImage,
|
||||
Qt::UniqueConnection);
|
||||
&AvatarRegistry::addOrUpdateImage);
|
||||
|
||||
connect(lrcInstance_, &LRCInstance::base64SwarmAvatarChanged, this, [&] {
|
||||
addOrUpdateImage("temp");
|
||||
|
||||
@ -43,7 +43,7 @@ public:
|
||||
Q_INVOKABLE QString getUid(const QString& id);
|
||||
|
||||
// add or update a specific image in the cache
|
||||
QString addOrUpdateImage(const QString& id);
|
||||
Q_SLOT QString addOrUpdateImage(const QString& id);
|
||||
|
||||
Q_SIGNALS:
|
||||
void avatarUidChanged(const QString& id);
|
||||
|
||||
@ -20,6 +20,9 @@
|
||||
#include "bannedlistmodel.h"
|
||||
|
||||
#include "lrcinstance.h"
|
||||
#include "global.h"
|
||||
|
||||
#include <api/contact.h>
|
||||
|
||||
BannedListModel::BannedListModel(QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
@ -106,7 +109,13 @@ void
|
||||
BannedListModel::reset()
|
||||
{
|
||||
beginResetModel();
|
||||
bannedlist_ = lrcInstance_->getCurrentAccountInfo().contactModel->getBannedContacts();
|
||||
auto contactModel = lrcInstance_->getCurrentContactModel();
|
||||
if (!contactModel) {
|
||||
C_DBG << "Contact model is not available.";
|
||||
bannedlist_.clear();
|
||||
} else {
|
||||
bannedlist_ = contactModel->getBannedContacts();
|
||||
}
|
||||
endResetModel();
|
||||
set_count(rowCount());
|
||||
}
|
||||
|
||||
@ -354,10 +354,11 @@ CallAdapter::onCallInfosChanged(const QString& accountId, const QString& callId)
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId)
|
||||
CallAdapter::onCallAddedToConference(const QString& callId, const QString& conversationId, const QString& confId)
|
||||
{
|
||||
Q_UNUSED(callId)
|
||||
Q_UNUSED(confId)
|
||||
Q_UNUSED(conversationId)
|
||||
saveConferenceSubcalls();
|
||||
}
|
||||
|
||||
@ -389,16 +390,13 @@ CallAdapter::hangUpACall(const QString& accountId, const QString& convUid)
|
||||
}
|
||||
|
||||
void
|
||||
CallAdapter::setCallMedia(const QString& accountId, const QString& convUid, bool video)
|
||||
CallAdapter::setCallMedia(const QString& accountId, const QString& convUid, bool videoMuted)
|
||||
{
|
||||
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId);
|
||||
if (convInfo.uid.isEmpty())
|
||||
return;
|
||||
try {
|
||||
lrcInstance_->getAccountInfo(accountId).callModel->updateCallMediaList(convInfo.callId,
|
||||
video);
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
lrcInstance_->getAccountInfo(accountId).callModel->setVideoMuted(convInfo.callId, videoMuted);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -67,7 +67,7 @@ public:
|
||||
Q_INVOKABLE void placeAudioOnlyCall();
|
||||
Q_INVOKABLE void placeCall();
|
||||
Q_INVOKABLE void hangUpACall(const QString& accountId, const QString& convUid);
|
||||
Q_INVOKABLE void setCallMedia(const QString& accountId, const QString& convUid, bool video);
|
||||
Q_INVOKABLE void setCallMedia(const QString& accountId, const QString& convUid, bool videoMuted);
|
||||
Q_INVOKABLE void acceptACall(const QString& accountId, const QString& convUid);
|
||||
|
||||
Q_INVOKABLE void connectCallModel(const QString& accountId);
|
||||
@ -122,7 +122,7 @@ public Q_SLOTS:
|
||||
void onAccountChanged();
|
||||
void onCallStatusChanged(const QString& accountId, const QString& callId);
|
||||
void onCallStatusChanged(const QString& callId, int code);
|
||||
void onCallAddedToConference(const QString& callId, const QString& confId);
|
||||
void onCallAddedToConference(const QString& callId, const QString& conversationId, const QString& confId);
|
||||
void onCallStarted(const QString& callId);
|
||||
void onCallEnded(const QString& callId);
|
||||
void onCallInfosChanged(const QString& accountId, const QString& callId);
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
#include "calloverlaymodel.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QQuickWindow>
|
||||
@ -360,23 +362,33 @@ CallOverlayModel::clearControls()
|
||||
}
|
||||
|
||||
void
|
||||
CallOverlayModel::registerFilter(QQuickWindow* object, QQuickItem* item)
|
||||
CallOverlayModel::setEventFilterActive(QObject* object, QQuickItem* item, bool isActive)
|
||||
{
|
||||
if (!object || !item || watchedItems_.contains(item))
|
||||
QQuickWindow* window = qobject_cast<QQuickWindow*>(object);
|
||||
if (!window || !item) {
|
||||
C_WARN << "Attempting to" << (isActive ? "register" : "unregister")
|
||||
<< "an invalid object or item" << window << item;
|
||||
return;
|
||||
watchedItems_.push_back(item);
|
||||
if (watchedItems_.size() == 1)
|
||||
object->installEventFilter(this);
|
||||
}
|
||||
|
||||
void
|
||||
CallOverlayModel::unregisterFilter(QQuickWindow* object, QQuickItem* item)
|
||||
{
|
||||
if (!object || !item || !watchedItems_.contains(item))
|
||||
return;
|
||||
watchedItems_.removeOne(item);
|
||||
if (watchedItems_.size() == 0)
|
||||
object->removeEventFilter(this);
|
||||
}
|
||||
if (isActive) {
|
||||
if (watchedItems_.contains(item)) {
|
||||
C_DBG << "Item already registered" << item;
|
||||
} else {
|
||||
watchedItems_.push_back(item);
|
||||
if (watchedItems_.size() == 1) {
|
||||
window->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!watchedItems_.contains(item)) {
|
||||
C_DBG << "Item not registered" << item;
|
||||
} else {
|
||||
watchedItems_.removeOne(item);
|
||||
if (watchedItems_.size() == 0) {
|
||||
window->removeEventFilter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@ -137,8 +137,7 @@ public:
|
||||
Q_INVOKABLE QVariant overflowHiddenModel();
|
||||
Q_INVOKABLE QVariant pendingConferenceesModel();
|
||||
|
||||
Q_INVOKABLE void registerFilter(QQuickWindow* object, QQuickItem* item);
|
||||
Q_INVOKABLE void unregisterFilter(QQuickWindow* object, QQuickItem* item);
|
||||
Q_INVOKABLE void setEventFilterActive(QObject* object, QQuickItem* item, bool isActive);
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
@ -25,8 +26,10 @@ import net.jami.Constants 1.1
|
||||
SBSMessageBase {
|
||||
id: root
|
||||
|
||||
property var confId: ConfId
|
||||
property var currentCallId: CurrentCall.id
|
||||
component JoinCallButton: MaterialButton {
|
||||
visible: root.isActive
|
||||
visible: root.isActive && root.currentCallId !== root.confId
|
||||
toolTipText: JamiStrings.joinCall
|
||||
color: JamiTheme.blackColor
|
||||
background.opacity: hovered ? 0.2 : 0.1
|
||||
@ -47,13 +50,15 @@ SBSMessageBase {
|
||||
|
||||
bubble.border.color: CurrentConversation.color
|
||||
bubble.border.width: root.isActive ? 1.5 : 0
|
||||
bubble.color: JamiTheme.messageInBgColor
|
||||
bubble.opacity: 0.6
|
||||
|
||||
Connections {
|
||||
target: CurrentConversation
|
||||
enabled: root.isActive
|
||||
|
||||
function onActiveCallsChanged() {
|
||||
root.isActive = LRCInstance.indexOfActiveCall(ConfId, ActionUri, DeviceId) !== -1;
|
||||
root.isActive = LRCInstance.indexOfActiveCall(root.confId, ActionUri, DeviceId) !== -1;
|
||||
if (root.isActive) {
|
||||
bubble.mask.border.color = CurrentConversation.color;
|
||||
bubble.mask.border.width = 1.5;
|
||||
@ -62,10 +67,10 @@ SBSMessageBase {
|
||||
}
|
||||
}
|
||||
|
||||
property bool isActive: LRCInstance.indexOfActiveCall(ConfId, ActionUri, DeviceId) !== -1
|
||||
visible: isActive || ConfId === "" || Duration > 0
|
||||
property bool isActive: LRCInstance.indexOfActiveCall(root.confId, ActionUri, DeviceId) !== -1
|
||||
visible: isActive || root.confId === "" || Duration > 0
|
||||
|
||||
property var baseColor: isOutgoing? CurrentConversation.color : JamiTheme.messageInBgColor
|
||||
property var baseColor: JamiTheme.messageInBgColor
|
||||
|
||||
innerContent.children: [
|
||||
RowLayout {
|
||||
@ -74,22 +79,60 @@ SBSMessageBase {
|
||||
spacing: 10
|
||||
visible: root.visible
|
||||
|
||||
Label {
|
||||
id: callLabel
|
||||
Image {
|
||||
id: statusIcon
|
||||
Layout.leftMargin: 8
|
||||
width: 10
|
||||
height: 10
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
visible: !root.isActive
|
||||
|
||||
source: {
|
||||
if (root.isOutgoing) {
|
||||
if (Duration > 0)
|
||||
return "qrc:/icons/outgoing-call.svg";
|
||||
else
|
||||
return "qrc:/icons/missed-outgoing-call.svg";
|
||||
} else {
|
||||
if (Duration > 0)
|
||||
return "qrc:/icons/incoming-call.svg";
|
||||
else
|
||||
return "qrc:/icons/missed-incoming-call.svg";
|
||||
}
|
||||
}
|
||||
layer {
|
||||
enabled: true
|
||||
effect: ColorOverlay {
|
||||
color: {
|
||||
if (Duration > 0)
|
||||
return UtilsAdapter.luma(root.baseColor) ? JamiTheme.chatviewTextColorLight : JamiTheme.chatviewTextColorDark
|
||||
return JamiTheme.redColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: callLabel
|
||||
objectName: "callLabel"
|
||||
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
|
||||
Layout.margins: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: root.isActive ? 0 : root.timeWidth + 16
|
||||
Layout.leftMargin: root.isActive ? 10 : 8
|
||||
Layout.rightMargin: root.isActive && root.currentCallId !== root.confId ? 0 : root.timeWidth + 16
|
||||
Layout.leftMargin: root.isActive ? 10 : -5 /* spacing is 10 and we want 5px with icon */
|
||||
|
||||
text: {
|
||||
if (root.isActive)
|
||||
return JamiStrings.startedACall;
|
||||
return Body;
|
||||
}
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
|
||||
font.pointSize: JamiTheme.mediumFontSize
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
renderType: Text.NativeRendering
|
||||
textFormat: Text.MarkdownText
|
||||
@ -99,20 +142,22 @@ SBSMessageBase {
|
||||
|
||||
JoinCallButton {
|
||||
id: joinCallInAudio
|
||||
objectName: "joinCallInAudio"
|
||||
Layout.topMargin: 4
|
||||
Layout.bottomMargin: 4
|
||||
|
||||
text: JamiStrings.joinInAudio
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, ConfId, true)
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, root.confId, true)
|
||||
}
|
||||
|
||||
JoinCallButton {
|
||||
id: joinCallInVideo
|
||||
objectName: "joinCallInVideo"
|
||||
text: JamiStrings.joinInVideo
|
||||
Layout.topMargin: 4
|
||||
Layout.bottomMargin: 4
|
||||
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, ConfId)
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, root.confId)
|
||||
Layout.rightMargin: 4
|
||||
}
|
||||
}
|
||||
@ -125,7 +170,7 @@ SBSMessageBase {
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
bubble.timestampItem.visible = !root.isActive;
|
||||
bubble.timestampItem.visible = !root.isActive || root.currentCallId === root.confId;
|
||||
opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,9 +40,9 @@ Loader {
|
||||
property int seq: MsgSeq.single
|
||||
property string author: Author
|
||||
property string body: Body
|
||||
property int transferStatus: Status
|
||||
property int transferStatus: TransferStatus
|
||||
onTransferStatusChanged: {
|
||||
if (transferStatus === Interaction.Status.TRANSFER_FINISHED) {
|
||||
if (transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED) {
|
||||
mediaInfo = MessagesAdapter.getMediaInfo(root.body);
|
||||
if (Object.keys(mediaInfo).length !== 0 && WITH_WEBENGINE) {
|
||||
sourceComponent = localMediaMsgComp;
|
||||
@ -66,7 +66,7 @@ Loader {
|
||||
|
||||
transferId: Id
|
||||
property var transferStats: MessagesAdapter.getTransferStats(transferId, root.transferStatus)
|
||||
property bool canOpen: root.transferStatus === Interaction.Status.TRANSFER_FINISHED || isOutgoing
|
||||
property bool canOpen: root.transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED || isOutgoing
|
||||
property real maxMsgWidth: root.width - senderMargin -
|
||||
2 * hPadding - avatarBlockWidth
|
||||
- buttonsLoader.width - 24 - 6 - 24
|
||||
@ -112,18 +112,18 @@ Loader {
|
||||
|
||||
sourceComponent: {
|
||||
switch (root.transferStatus) {
|
||||
case Interaction.Status.TRANSFER_CREATED:
|
||||
case Interaction.Status.TRANSFER_FINISHED:
|
||||
case Interaction.TransferStatus.TRANSFER_CREATED:
|
||||
case Interaction.TransferStatus.TRANSFER_FINISHED:
|
||||
iconSource = JamiResources.link_black_24dp_svg
|
||||
return terminatedComp
|
||||
case Interaction.Status.TRANSFER_CANCELED:
|
||||
case Interaction.Status.TRANSFER_ERROR:
|
||||
case Interaction.Status.TRANSFER_UNJOINABLE_PEER:
|
||||
case Interaction.Status.TRANSFER_TIMEOUT_EXPIRED:
|
||||
case Interaction.Status.TRANSFER_AWAITING_HOST:
|
||||
case Interaction.TransferStatus.TRANSFER_CANCELED:
|
||||
case Interaction.TransferStatus.TRANSFER_ERROR:
|
||||
case Interaction.TransferStatus.TRANSFER_UNJOINABLE_PEER:
|
||||
case Interaction.TransferStatus.TRANSFER_TIMEOUT_EXPIRED:
|
||||
case Interaction.TransferStatus.TRANSFER_AWAITING_HOST:
|
||||
iconSource = JamiResources.download_black_24dp_svg
|
||||
return optionsComp
|
||||
case Interaction.Status.TRANSFER_ONGOING:
|
||||
case Interaction.TransferStatus.TRANSFER_ONGOING:
|
||||
iconSource = JamiResources.close_black_24dp_svg
|
||||
return optionsComp
|
||||
default:
|
||||
@ -158,7 +158,7 @@ Loader {
|
||||
normalColor: JamiTheme.chatviewBgColor
|
||||
imageColor: JamiTheme.chatviewButtonColor
|
||||
onClicked: {
|
||||
if (root.transferStatus === Interaction.Status.TRANSFER_ONGOING) {
|
||||
if (root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING) {
|
||||
return MessagesAdapter.cancelFile(transferId)
|
||||
} else {
|
||||
return MessagesAdapter.acceptFile(transferId)
|
||||
@ -191,7 +191,7 @@ Loader {
|
||||
onClicked: function (mouse) {
|
||||
if (canOpen) {
|
||||
dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location)
|
||||
Qt.openUrlExternally(new Url(dataTransferItem.hoveredLink))
|
||||
Qt.openUrlExternally(new URL(dataTransferItem.hoveredLink))
|
||||
} else {
|
||||
dataTransferItem.hoveredLink = ""
|
||||
}
|
||||
@ -227,7 +227,7 @@ Loader {
|
||||
,ProgressBar {
|
||||
id: progressBar
|
||||
|
||||
visible: root.transferStatus === Interaction.Status.TRANSFER_ONGOING
|
||||
visible: root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING
|
||||
height: visible * implicitHeight
|
||||
value: transferStats.progress / transferStats.totalSize
|
||||
width: transferItem.width
|
||||
|
||||
@ -1,83 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
// A SplitView that supports dynamic RTL and splitView state saving.
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
property bool isSinglePane: false
|
||||
property bool isSwapped: false
|
||||
|
||||
onIsRTLChanged: {
|
||||
if (isRTL && isSinglePane && !isSwapped)
|
||||
return
|
||||
if ((isRTL && !isSwapped) || (!isRTL && isSwapped))
|
||||
swapItems()
|
||||
}
|
||||
onIsSinglePaneChanged: {
|
||||
if (isSwapped || isRTL)
|
||||
swapItems()
|
||||
}
|
||||
|
||||
property string splitViewStateKey: objectName
|
||||
property bool autoManageState: !(parent instanceof BaseView)
|
||||
|
||||
function saveSplitViewState() {
|
||||
UtilsAdapter.setAppValue("sv_" + splitViewStateKey, root.saveState());
|
||||
}
|
||||
|
||||
function restoreSplitViewState() {
|
||||
root.restoreState(UtilsAdapter.getAppValue("sv_" + splitViewStateKey));
|
||||
}
|
||||
|
||||
onResizingChanged: if (!resizing)
|
||||
saveSplitViewState()
|
||||
onVisibleChanged: {
|
||||
if (!autoManageState)
|
||||
return;
|
||||
visible ? restoreSplitViewState() : saveSplitViewState();
|
||||
}
|
||||
|
||||
function swapItems() {
|
||||
isSwapped = !isSwapped
|
||||
var qqci = children[0];
|
||||
if (qqci.children.length > 1) {
|
||||
// swap the children
|
||||
var tempPane = qqci.children[0];
|
||||
qqci.children[0] = qqci.children[1];
|
||||
qqci.children.push(tempPane);
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
visible: !isSinglePane
|
||||
implicitWidth: JamiTheme.splitViewHandlePreferredWidth
|
||||
implicitHeight: root.height
|
||||
color: JamiTheme.primaryBackgroundColor
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
implicitWidth: 1
|
||||
implicitHeight: root.height
|
||||
color: JamiTheme.tabbarBorderColor
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (C) 2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
// A SplitView that supports dynamic RTL and splitView state saving.
|
||||
SplitView {
|
||||
id: control
|
||||
|
||||
property bool isRTL: UtilsAdapter.isRTL
|
||||
property bool isSinglePane: false
|
||||
property bool isSwapped: false
|
||||
property real handleSize: 1
|
||||
|
||||
onIsRTLChanged: {
|
||||
if (isRTL && isSinglePane && !isSwapped)
|
||||
return
|
||||
if ((isRTL && !isSwapped) || (!isRTL && isSwapped))
|
||||
swapItems()
|
||||
}
|
||||
onIsSinglePaneChanged: {
|
||||
if (isSwapped || isRTL)
|
||||
swapItems()
|
||||
}
|
||||
|
||||
property string splitViewStateKey: objectName
|
||||
property bool autoManageState: !(parent instanceof BaseView)
|
||||
|
||||
function saveSplitViewState() {
|
||||
UtilsAdapter.setAppValue("sv_" + splitViewStateKey, control.saveState());
|
||||
}
|
||||
|
||||
function restoreSplitViewState() {
|
||||
control.restoreState(UtilsAdapter.getAppValue("sv_" + splitViewStateKey));
|
||||
}
|
||||
|
||||
onResizingChanged: if (!resizing)
|
||||
saveSplitViewState()
|
||||
onVisibleChanged: {
|
||||
if (!autoManageState)
|
||||
return;
|
||||
visible ? restoreSplitViewState() : saveSplitViewState();
|
||||
}
|
||||
|
||||
function swapItems() {
|
||||
isSwapped = !isSwapped
|
||||
var qqci = children[0];
|
||||
if (qqci.children.length > 1) {
|
||||
// swap the children
|
||||
var tempPane = qqci.children[0];
|
||||
qqci.children[0] = qqci.children[1];
|
||||
qqci.children.push(tempPane);
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
id: handleRoot
|
||||
|
||||
readonly property int defaultSize: control.handleSize
|
||||
|
||||
implicitWidth: control.orientation === Qt.Horizontal ? handleRoot.defaultSize : control.width
|
||||
implicitHeight: control.orientation === Qt.Horizontal ? control.height : handleRoot.defaultSize
|
||||
|
||||
color: JamiTheme.tabbarBorderColor
|
||||
|
||||
containmentMask: Item {
|
||||
// In the default configuration, the total handle size is the sum of the default size of the
|
||||
// handle and the extra handle size (4). If the layout is not right-to-left (RTL), the handle
|
||||
// is positioned at 0 on the X-axis, otherwise it's positioned to the left by the extra handle
|
||||
// size (4 pixels). This is done to make it easier to grab small scroll-view handles that are
|
||||
// adjacent to the SplitView handle. Note: vertically oriented handles are not offset.
|
||||
readonly property real extraHandleSize: 4
|
||||
readonly property real handleXPosition: !isRTL ? 0 : -extraHandleSize
|
||||
readonly property real handleSize: handleRoot.defaultSize + extraHandleSize
|
||||
|
||||
x: control.orientation === Qt.Horizontal ? handleXPosition : 0
|
||||
width: control.orientation === Qt.Horizontal ? handleSize : handleRoot.width
|
||||
height: control.orientation === Qt.Horizontal ? handleRoot.height : handleSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,19 +23,28 @@ import net.jami.Adapters 1.1
|
||||
VideoView {
|
||||
id: root
|
||||
|
||||
property bool visibilityCondition: true
|
||||
|
||||
crop: true
|
||||
visible: isRendering && visibilityCondition
|
||||
|
||||
Component.onDestruction: VideoDevices.stopDevice(rendererId);
|
||||
|
||||
function startWithId(id, force = false) {
|
||||
if (id !== undefined && id.length === 0) {
|
||||
VideoDevices.stopDevice(rendererId);
|
||||
rendererId = id;
|
||||
} else {
|
||||
const forceRestart = rendererId === id;
|
||||
if (!forceRestart) {
|
||||
// Stop previous device
|
||||
VideoDevices.stopDevice(rendererId);
|
||||
}
|
||||
rendererId = VideoDevices.startDevice(id, forceRestart);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
const forceRestart = rendererId === id || force;
|
||||
if (!forceRestart) {
|
||||
// Stop previous device
|
||||
VideoDevices.stopDevice(rendererId);
|
||||
}
|
||||
rendererId = VideoDevices.startDevice(id, forceRestart);
|
||||
}
|
||||
|
||||
function stop() {
|
||||
VideoDevices.stopDevice(rendererId);
|
||||
rendererId = "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,17 @@ Rectangle {
|
||||
property int status: Account.Status.REGISTERED
|
||||
property int size: 15
|
||||
|
||||
MaterialToolTip {
|
||||
visible: text !== "" && hoverHandler.hovered
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
text: status === 2 ? qsTr("Connected") : status === 1 ? qsTr("Available") : ""
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
target: parent
|
||||
}
|
||||
|
||||
width: size
|
||||
height: size
|
||||
radius: size * 0.5
|
||||
@ -41,6 +52,10 @@ Rectangle {
|
||||
return JamiTheme.presenceGreen;
|
||||
else if (status === Account.Status.TRYING)
|
||||
return JamiTheme.unPresenceOrange;
|
||||
else if (status === 2)
|
||||
return JamiTheme.presenceGreen;
|
||||
else if (status === 1)
|
||||
return JamiTheme.unPresenceOrange;
|
||||
return JamiTheme.notificationRed;
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,10 +98,18 @@ Control {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
|
||||
Label {
|
||||
id: username
|
||||
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author)
|
||||
|
||||
wrapMode: Text.NoWrap
|
||||
text: textMetricsUsername.elidedText
|
||||
TextMetrics {
|
||||
id: textMetricsUsername
|
||||
|
||||
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author)
|
||||
elideWidth: 200
|
||||
elide: Qt.ElideMiddle
|
||||
}
|
||||
visible: (seq === MsgSeq.first || seq === MsgSeq.single) && !isOutgoing && !isReply
|
||||
|
||||
font.pointSize: JamiTheme.smallFontSize
|
||||
@ -141,7 +149,15 @@ Control {
|
||||
Label {
|
||||
id: replyTo
|
||||
|
||||
text: isOutgoing ? JamiStrings.inReplyTo : UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + JamiStrings.repliedTo
|
||||
wrapMode: Text.NoWrap
|
||||
text: textMetricsUsername1.elidedText
|
||||
TextMetrics {
|
||||
id: textMetricsUsername1
|
||||
text: isOutgoing ? JamiStrings.inReplyTo : UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + JamiStrings.repliedTo
|
||||
elideWidth: 200
|
||||
elide: Qt.ElideMiddle
|
||||
}
|
||||
|
||||
color: JamiTheme.messageReplyColor
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
@ -166,7 +182,15 @@ Control {
|
||||
Label {
|
||||
id: replyToUserName
|
||||
|
||||
text: replyItem.isSelf ? JamiStrings.inReplyToMe : replyToLayout.replyUserName
|
||||
wrapMode: Text.NoWrap
|
||||
text: textMetricsUsername2.elidedText
|
||||
TextMetrics {
|
||||
id: textMetricsUsername2
|
||||
text: replyItem.isSelf ? JamiStrings.inReplyToMe : replyToLayout.replyUserName
|
||||
elideWidth: 200
|
||||
elide: Qt.ElideMiddle
|
||||
}
|
||||
|
||||
color: JamiTheme.messageReplyColor
|
||||
font.pointSize: JamiTheme.textFontSize
|
||||
font.kerning: true
|
||||
@ -275,10 +299,7 @@ Control {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: isOutgoing ? optionButtonItem.right : undefined
|
||||
anchors.left: !isOutgoing ? optionButtonItem.left : undefined
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP
|
||||
&& root.type !== Interaction.Type.CALL
|
||||
&& Body !== ""
|
||||
&& (bubbleArea.bubbleHovered || hovered || reply.hovered || bgHandler.hovered)
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP && root.type !== Interaction.Type.CALL && Body !== "" && (bubbleArea.bubbleHovered || hovered || reply.hovered || bgHandler.hovered)
|
||||
source: JamiResources.more_vert_24dp_svg
|
||||
width: optionButtonItem.width / 2
|
||||
height: optionButtonItem.height
|
||||
@ -333,10 +354,7 @@ Control {
|
||||
anchors.rightMargin: 5
|
||||
anchors.right: isOutgoing ? more.left : undefined
|
||||
anchors.left: !isOutgoing ? more.right : undefined
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP
|
||||
&& root.type !== Interaction.Type.CALL
|
||||
&& Body !== ""
|
||||
&& (bubbleArea.bubbleHovered || hovered || more.hovered || bgHandler.hovered)
|
||||
visible: CurrentAccount.type !== Profile.Type.SIP && root.type !== Interaction.Type.CALL && Body !== "" && (bubbleArea.bubbleHovered || hovered || more.hovered || bgHandler.hovered)
|
||||
|
||||
onClicked: {
|
||||
MessagesAdapter.editId = "";
|
||||
@ -364,14 +382,14 @@ Control {
|
||||
property bool bubbleHovered
|
||||
property string imgSource
|
||||
|
||||
width: (root.type === Interaction.Type.TEXT ? root.textContentWidth + ( IsEmojiOnly || root.bigMsg ? 0 : root.timeWidth + root.editedWidth): innerContent.childrenRect.width)
|
||||
width: (root.type === Interaction.Type.TEXT ? root.textContentWidth + (IsEmojiOnly || root.bigMsg ? 0 : root.timeWidth + root.editedWidth) : innerContent.childrenRect.width)
|
||||
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0) + (root.bigMsg ? 15 : 0)
|
||||
|
||||
HoverHandler {
|
||||
target: root
|
||||
enabled: root.type === Interaction.Type.DATA_TRANSFER
|
||||
onHoveredChanged: {
|
||||
root.hoveredLink = enabled && hovered ? bubble.imgSource : ""
|
||||
root.hoveredLink = enabled && hovered ? bubble.imgSource : "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,12 +399,12 @@ Control {
|
||||
showTime: IsEmojiOnly && !(root.seq === MsgSeq.last || root.seq === MsgSeq.single) ? false : true
|
||||
formattedTime: root.formattedTime
|
||||
|
||||
timeColor: IsEmojiOnly || root.timeUnderBubble? (JamiTheme.darkTheme ? "white" : "dark") : (UtilsAdapter.luma(bubble.color) ? "white" : "dark")
|
||||
timeColor: IsEmojiOnly || root.timeUnderBubble ? (JamiTheme.darkTheme ? "white" : "dark") : (UtilsAdapter.luma(bubble.color) ? "white" : "dark")
|
||||
timeLabel.opacity: 0.5
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: IsEmojiOnly ? (isOutgoing ? parent.right : undefined) : parent.right
|
||||
anchors.left: ((IsEmojiOnly|| root.timeUnderBubble) && !isOutgoing) ? parent.left : undefined
|
||||
anchors.left: ((IsEmojiOnly || root.timeUnderBubble) && !isOutgoing) ? parent.left : undefined
|
||||
anchors.leftMargin: (IsEmojiOnly && !isOutgoing && emojiReactions.visible) ? bubble.timePosition : 0
|
||||
anchors.rightMargin: IsEmojiOnly ? ((isOutgoing && emojiReactions.visible) ? bubble.timePosition : 0) : (root.timeUnderBubble ? 0 : 10)
|
||||
timeLabel.Layout.bottomMargin: {
|
||||
@ -396,6 +414,8 @@ Control {
|
||||
return -20;
|
||||
if (root.bigMsg || bubble.isDeleted)
|
||||
return 5;
|
||||
if (root.type === Interaction.Type.CALL)
|
||||
return 8;
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
@ -404,9 +424,9 @@ Control {
|
||||
id: editedRow
|
||||
anchors.left: root.bigMsg ? bubble.left : timestampItem.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.bigMsg || bubble.isDeleted ? 6 : 10
|
||||
anchors.leftMargin: root.bigMsg ? 10 : - timestampItem.width - 16
|
||||
visible: bubble.isEdited
|
||||
anchors.bottomMargin: root.bigMsg ? 6 : 10
|
||||
anchors.leftMargin: root.bigMsg ? 10 : -timestampItem.width - 16
|
||||
visible: bubble.isEdited && !bubble.isDeleted
|
||||
z: 1
|
||||
ResponsiveImage {
|
||||
id: editedImage
|
||||
@ -462,7 +482,7 @@ Control {
|
||||
borderColor: root.getBaseColor()
|
||||
maxWidth: 2 / 3 * maxMsgWidth - JamiTheme.emojiMargins
|
||||
|
||||
state: root.isOutgoing ? "anchorsRight" : (IsEmojiOnly ? "anchorsLeft" :(emojiReactions.width > bubble.width - JamiTheme.emojiMargins ? "anchorsLeft" : "anchorsRight"))
|
||||
state: root.isOutgoing ? "anchorsRight" : (IsEmojiOnly ? "anchorsLeft" : (emojiReactions.width > bubble.width - JamiTheme.emojiMargins ? "anchorsLeft" : "anchorsRight"))
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
@ -570,7 +590,7 @@ Control {
|
||||
radius: width / 2
|
||||
width: 12
|
||||
height: 12
|
||||
border.color: JamiTheme.tintedBlue
|
||||
border.color: JamiTheme.sending
|
||||
border.width: 1
|
||||
color: JamiTheme.transparentColor
|
||||
visible: isOutgoing && Status === Interaction.Status.SENDING
|
||||
@ -578,18 +598,27 @@ Control {
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
ResponsiveImage {
|
||||
id: sent
|
||||
|
||||
containerHeight: 12
|
||||
containerWidth: 12
|
||||
|
||||
width: 12
|
||||
height: 12
|
||||
|
||||
visible: IsLastSent === true && root.readers.length === 0
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
source: JamiResources.receive_svg
|
||||
}
|
||||
|
||||
ReadStatus {
|
||||
id: readsOne
|
||||
|
||||
visible: root.readers.length === 1 && CurrentAccount.sendReadReceipt
|
||||
|
||||
width: {
|
||||
if (root.readers.length === 0)
|
||||
return 0;
|
||||
var nbAvatars = root.readers.length;
|
||||
var margin = JamiTheme.avatarReadReceiptSize / 3;
|
||||
return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin;
|
||||
}
|
||||
width: JamiTheme.avatarReadReceiptSize
|
||||
height: JamiTheme.avatarReadReceiptSize
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@ -613,14 +642,23 @@ Control {
|
||||
|
||||
ReadStatus {
|
||||
id: readsMultiple
|
||||
visible: root.readers.length > 1 && CurrentAccount.sendReadReceipt
|
||||
visible: {
|
||||
if (!readers)
|
||||
return false;
|
||||
return readers.length > 1 && CurrentAccount.sendReadReceipt;
|
||||
}
|
||||
width: {
|
||||
if (root.readers.length === 0)
|
||||
if (readers.length === 0)
|
||||
return 0;
|
||||
var nbAvatars = root.readers.length;
|
||||
var nbAvatars = readers.length;
|
||||
var margin = JamiTheme.avatarReadReceiptSize / 3;
|
||||
return nbAvatars * JamiTheme.avatarReadReceiptSize - (nbAvatars - 1) * margin;
|
||||
}
|
||||
height: {
|
||||
if (readers.length === 0)
|
||||
return 0;
|
||||
return JamiTheme.avatarReadReceiptSize;
|
||||
}
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
@ -153,7 +153,7 @@ BaseContextMenu {
|
||||
GeneralMenuItem {
|
||||
id: removeLocally
|
||||
|
||||
canTrigger: type === Interaction.Type.DATA_TRANSFER && Status === Interaction.Status.TRANSFER_FINISHED
|
||||
canTrigger: type === Interaction.Type.DATA_TRANSFER && TransferStatus === Interaction.TransferStatus.TRANSFER_FINISHED
|
||||
iconSource: JamiResources.trash_black_24dp_svg
|
||||
itemName: JamiStrings.removeLocally
|
||||
onClicked: {
|
||||
|
||||
@ -29,6 +29,10 @@ Item {
|
||||
property real invAspectRatio: (videoOutput.sourceRect.height / videoOutput.sourceRect.width) || 0.5625 // 16:9 default
|
||||
property bool crop: false
|
||||
property bool flip: false
|
||||
property real blurRadius: 0
|
||||
|
||||
// We need to know if the frames are being rendered to the screen or not.
|
||||
readonly property bool isRendering: videoProvider.activeRenderers[rendererId] === true
|
||||
|
||||
// This rect describes the actual rendered content rectangle
|
||||
// as the VideoOutput component may use PreserveAspectFit
|
||||
@ -55,7 +59,7 @@ Item {
|
||||
|
||||
antialiasing: true
|
||||
anchors.fill: parent
|
||||
opacity: videoProvider.activeRenderers[rendererId] === true
|
||||
opacity: isRendering
|
||||
visible: opacity
|
||||
|
||||
fillMode: crop ? VideoOutput.PreserveAspectCrop : VideoOutput.PreserveAspectFit
|
||||
@ -70,7 +74,7 @@ Item {
|
||||
layer.effect: FastBlur {
|
||||
source: videoOutput
|
||||
anchors.fill: root
|
||||
radius: (1. - opacity) * 100
|
||||
radius: blurRadius ? blurRadius : (1. - opacity) * 100
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
@ -171,10 +173,10 @@ static void
|
||||
logConnectionInfo(NMActiveConnection* connection)
|
||||
{
|
||||
if (connection) {
|
||||
qDebug() << "primary network connection:" << nm_active_connection_get_uuid(connection)
|
||||
<< "default: " << (nm_active_connection_get_default(connection) ? "yes" : "no");
|
||||
C_INFO << "primary network connection:" << nm_active_connection_get_uuid(connection)
|
||||
<< "default: " << (nm_active_connection_get_default(connection) ? "yes" : "no");
|
||||
} else {
|
||||
qWarning() << "no primary network connection detected, check network settings";
|
||||
C_WARN << "no primary network connection detected, check network settings";
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,11 +193,10 @@ nmClientCallback(G_GNUC_UNUSED GObject* source_object, GAsyncResult* result, Con
|
||||
{
|
||||
GError* error = nullptr;
|
||||
if (auto nm_client = nm_client_new_finish(result, &error)) {
|
||||
qDebug() << "NetworkManager client initialized, version: "
|
||||
<< nm_client_get_version(nm_client)
|
||||
<< ", daemon running:" << (nm_client_get_nm_running(nm_client) ? "yes" : "no")
|
||||
<< ", networking enabled:"
|
||||
<< (nm_client_networking_get_enabled(nm_client) ? "yes" : "no");
|
||||
C_INFO << "NetworkManager client initialized, version: " << nm_client_get_version(nm_client)
|
||||
<< ", daemon running:" << (nm_client_get_nm_running(nm_client) ? "yes" : "no")
|
||||
<< ", networking enabled:"
|
||||
<< (nm_client_networking_get_enabled(nm_client) ? "yes" : "no");
|
||||
|
||||
auto connection = nm_client_get_primary_connection(nm_client);
|
||||
logConnectionInfo(connection);
|
||||
@ -205,7 +206,7 @@ nmClientCallback(G_GNUC_UNUSED GObject* source_object, GAsyncResult* result, Con
|
||||
cm);
|
||||
|
||||
} else {
|
||||
qWarning() << "error initializing NetworkManager client: " << error->message;
|
||||
C_WARN << "error initializing NetworkManager client: " << error->message;
|
||||
g_clear_error(&error);
|
||||
}
|
||||
}
|
||||
@ -222,7 +223,7 @@ ConnectivityMonitor::ConnectivityMonitor(QObject* parent)
|
||||
|
||||
ConnectivityMonitor::~ConnectivityMonitor()
|
||||
{
|
||||
qDebug() << "Destroying connectivity monitor";
|
||||
C_DBG << "Destroying connectivity monitor";
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@ -81,7 +81,7 @@ ContactAdapter::getContactSelectableModel(int type)
|
||||
}
|
||||
case SmartListModel::Type::CONFERENCE:
|
||||
selectableProxyModel_->setPredicate([](const QModelIndex& index, const QRegularExpression&) {
|
||||
return index.data(Role::Presence).toBool();
|
||||
return index.data(Role::Presence).toInt();
|
||||
});
|
||||
break;
|
||||
case SmartListModel::Type::TRANSFER:
|
||||
@ -259,7 +259,7 @@ ContactAdapter::connectSignals()
|
||||
&ContactAdapter::bannedStatusChanged,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentContactModel(),
|
||||
&ContactModel::modelUpdated,
|
||||
&ContactModel::contactAdded,
|
||||
this,
|
||||
&ContactAdapter::onModelUpdated,
|
||||
Qt::UniqueConnection);
|
||||
|
||||
@ -112,7 +112,8 @@ ConversationListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
|
||||
using namespace ConversationList;
|
||||
if (index.data(Role::Uris).toStringList().isEmpty()) {
|
||||
// TODO: Find out why, and fix in libjami/libjamiclient.
|
||||
qCritical() << "Filtering 0 member conversation. Fix me";
|
||||
qCritical() << "Filtering 0 member conversation. Fix me"
|
||||
<< index.data(Role::UID).toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
|
||||
#include "conversationlistmodelbase.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <api/contact.h>
|
||||
|
||||
ConversationListModelBase::ConversationListModelBase(LRCInstance* instance, QObject* parent)
|
||||
: AbstractListModelBase(parent)
|
||||
{
|
||||
@ -169,18 +173,19 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
||||
return ret;
|
||||
}
|
||||
case Role::Presence: {
|
||||
// The conversation can show a green dot if at least one peer is present
|
||||
// A conversation presence is the max of the members presence
|
||||
auto maxPresence = 0;
|
||||
Q_FOREACH (const auto& peerUri, model_->peersForConversation(item.uid))
|
||||
try {
|
||||
auto& accInfo = lrcInstance_->getAccountInfo(accountId_);
|
||||
if (peerUri == accInfo.profileInfo.uri)
|
||||
return true; // Self account
|
||||
return 2; // Self account
|
||||
auto contact = accInfo.contactModel->getContact(peerUri);
|
||||
if (contact.isPresent)
|
||||
return true;
|
||||
if (contact.presence > maxPresence)
|
||||
maxPresence = contact.presence;
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
return false;
|
||||
return maxPresence;
|
||||
};
|
||||
default:
|
||||
break;
|
||||
@ -215,9 +220,9 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
||||
try {
|
||||
contact = contactModel->getContact(peerUri);
|
||||
} catch (const std::exception&) {
|
||||
qWarning() << Q_FUNC_INFO << "Can't find contact" << peerUri << " for account "
|
||||
<< lrcInstance_->accountModel().bestNameForAccount(accInfo.id)
|
||||
<< " - Conv: " << item.uid;
|
||||
C_WARN << "Can't find contact" << peerUri << "for account"
|
||||
<< lrcInstance_->accountModel().bestNameForAccount(accInfo.id)
|
||||
<< "- Conv:" << item.uid;
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
|
||||
@ -23,8 +23,9 @@
|
||||
#include "systemtray.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "namedirectory.h"
|
||||
#include <namedirectory.h>
|
||||
#endif
|
||||
#include <api/contact.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QJsonObject>
|
||||
@ -602,9 +603,11 @@ ConversationsAdapter::openDialogConversationWith(const QString& peerUri)
|
||||
void
|
||||
ConversationsAdapter::onCurrentAccountRemoved()
|
||||
{
|
||||
// Unbind proxy model source models.
|
||||
convModel_->bindSourceModel(nullptr);
|
||||
searchModel_->bindSourceModel(nullptr);
|
||||
// Unbind proxy model source models if there is no current account
|
||||
if (lrcInstance_->get_currentAccountId().isEmpty()) {
|
||||
convModel_->bindSourceModel(nullptr);
|
||||
searchModel_->bindSourceModel(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -144,7 +144,9 @@ CurrentAccount::updateData()
|
||||
set_deviceId(accConfig.deviceId);
|
||||
set_peerDiscovery(accConfig.peerDiscovery, true);
|
||||
set_sendReadReceipt(accConfig.sendReadReceipt, true);
|
||||
set_sendComposing(accConfig.sendComposing, true);
|
||||
set_isRendezVous(accConfig.isRendezVous, true);
|
||||
set_dhtPort(accConfig.dhtPort, true);
|
||||
set_autoAnswer(accConfig.autoAnswer, true);
|
||||
set_proxyEnabled(accConfig.proxyEnabled, true);
|
||||
set_upnpEnabled(accConfig.upnpEnabled, true);
|
||||
|
||||
@ -116,8 +116,10 @@ class CurrentAccount final : public QObject
|
||||
QML_RO_PROPERTY(lrc::api::profile::Type, type)
|
||||
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, keepAliveEnabled)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(int, dhtPort)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, peerDiscovery)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, sendReadReceipt)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, sendComposing)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, isRendezVous)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, autoAnswer)
|
||||
QML_ACCOUNT_CONFIG_SETTINGS_PROPERTY(bool, proxyEnabled)
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
#include "currentcall.h"
|
||||
|
||||
#include "callparticipantsmodel.h"
|
||||
#include "global.h"
|
||||
|
||||
#include <api/callparticipantsmodel.h>
|
||||
#include <api/devicemodel.h>
|
||||
@ -223,43 +224,14 @@ CurrentCall::updateCallInfo()
|
||||
set_isGrid(callInfo.layout == call::Layout::GRID);
|
||||
set_isAudioOnly(callInfo.isAudioOnly);
|
||||
|
||||
bool isAudioMuted {};
|
||||
bool isVideoMuted {};
|
||||
bool isSharing {};
|
||||
QString sharingSource {};
|
||||
bool isCapturing {};
|
||||
QString previewId {};
|
||||
using namespace libjami::Media;
|
||||
if (callInfo.status != lrc::api::call::Status::ENDED) {
|
||||
for (const auto& media : callInfo.mediaList) {
|
||||
if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) {
|
||||
if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY)
|
||||
|| media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) {
|
||||
isSharing = true;
|
||||
sharingSource = media[MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[MediaAttributeKey::ENABLED] == TRUE_STR
|
||||
&& media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) {
|
||||
previewId = media[libjami::Media::MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith(
|
||||
libjami::Media::VideoProtocolPrefix::CAMERA)) {
|
||||
isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
isCapturing = media[MediaAttributeKey::MUTED] == FALSE_STR;
|
||||
}
|
||||
} else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) {
|
||||
if (media[MediaAttributeKey::LABEL] == "audio_0") {
|
||||
isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set_previewId(previewId);
|
||||
set_isAudioMuted(isAudioMuted);
|
||||
set_isVideoMuted(isVideoMuted);
|
||||
set_isSharing(isSharing);
|
||||
set_sharingSource(sharingSource);
|
||||
set_isCapturing(isCapturing);
|
||||
auto callInfoEx = callInfo.getCallInfoEx();
|
||||
set_previewId(callInfoEx["preview_id"].toString());
|
||||
set_isAudioMuted(callInfoEx["is_audio_muted"].toBool());
|
||||
set_isVideoMuted(callInfoEx["is_video_muted"].toBool());
|
||||
set_isSharing(callInfoEx["is_sharing"].toBool());
|
||||
set_sharingSource(isSharing_ ? callInfoEx["preview_id"].toString() : QString());
|
||||
set_isCapturing(callInfoEx["is_capturing"].toBool());
|
||||
|
||||
set_isHandRaised(callModel->isHandRaised(id_));
|
||||
set_isModerator(callModel->isModerator(id_));
|
||||
|
||||
@ -377,7 +349,7 @@ CurrentCall::onCurrentAccountIdChanged()
|
||||
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
|
||||
set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
|
||||
} catch (const std::exception& e) {
|
||||
qWarning() << "Can't update current call type" << e.what();
|
||||
C_DBG << "Can't update current call type" << e.what();
|
||||
}
|
||||
|
||||
connectModel();
|
||||
|
||||
@ -18,7 +18,10 @@
|
||||
|
||||
#include "currentconversation.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <api/conversationmodel.h>
|
||||
#include <api/contact.h>
|
||||
|
||||
CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* parent)
|
||||
: QObject(parent)
|
||||
@ -263,51 +266,39 @@ void
|
||||
CurrentConversation::connectModel()
|
||||
{
|
||||
membersModel_->setMembers({}, {}, {});
|
||||
auto convModel = lrcInstance_->getCurrentConversationModel();
|
||||
if (!convModel)
|
||||
|
||||
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
|
||||
auto currentCallModel = lrcInstance_->getCurrentCallModel();
|
||||
if (!currentConversationModel || !currentCallModel) {
|
||||
C_DBG << "CurrentConversation: can't connect to unavailable models";
|
||||
return;
|
||||
}
|
||||
|
||||
auto connectObjectSignal = [this](auto obj, auto signal, auto slot) {
|
||||
connect(obj, signal, this, slot, Qt::UniqueConnection);
|
||||
};
|
||||
|
||||
connectObjectSignal(convModel,
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationUpdated,
|
||||
&CurrentConversation::onConversationUpdated);
|
||||
connectObjectSignal(convModel,
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::profileUpdated,
|
||||
&CurrentConversation::updateProfile);
|
||||
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::profileUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateProfile,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::onConversationErrorsUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateErrors,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::activeCallsChanged,
|
||||
this,
|
||||
&CurrentConversation::updateActiveCalls,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::conversationPreferencesUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateConversationPreferences,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::needsHost,
|
||||
this,
|
||||
&CurrentConversation::onNeedsHost,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentCallModel(),
|
||||
&CallModel::callStatusChanged,
|
||||
this,
|
||||
&CurrentConversation::onCallStatusChanged,
|
||||
Qt::UniqueConnection);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationErrorsUpdated,
|
||||
&CurrentConversation::updateErrors);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::activeCallsChanged,
|
||||
&CurrentConversation::updateActiveCalls);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationPreferencesUpdated,
|
||||
&CurrentConversation::updateConversationPreferences);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::needsHost,
|
||||
&CurrentConversation::onNeedsHost);
|
||||
connectObjectSignal(currentCallModel,
|
||||
&CallModel::callStatusChanged,
|
||||
&CurrentConversation::onCallStatusChanged);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
*/
|
||||
|
||||
#include "lrcinstance.h"
|
||||
|
||||
#include "global.h"
|
||||
#include "connectivitymonitor.h"
|
||||
|
||||
#include <QBuffer>
|
||||
@ -28,13 +30,11 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
LRCInstance::LRCInstance(migrateCallback willMigrateCb,
|
||||
migrateCallback didMigrateCb,
|
||||
const QString& updateUrl,
|
||||
LRCInstance::LRCInstance(const QString& updateUrl,
|
||||
ConnectivityMonitor* connectivityMonitor,
|
||||
bool debugMode,
|
||||
bool muteDaemon)
|
||||
: lrc_(std::make_unique<Lrc>(willMigrateCb, didMigrateCb, !debugMode || muteDaemon))
|
||||
: lrc_(std::make_unique<Lrc>(!debugMode || muteDaemon))
|
||||
, updateManager_(std::make_unique<AppVersionManager>(updateUrl, connectivityMonitor, this))
|
||||
, connectivityMonitor_(*connectivityMonitor)
|
||||
, threadPool_(new QThreadPool(this))
|
||||
@ -43,9 +43,8 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
|
||||
muteDaemon_ = muteDaemon;
|
||||
threadPool_->setMaxThreadCount(1);
|
||||
|
||||
connect(this, &LRCInstance::currentAccountIdChanged, [this] {
|
||||
// save to config, editing the accountlistmodel's underlying data
|
||||
accountModel().setTopAccount(currentAccountId_);
|
||||
// Update the current account when the account list changes.
|
||||
connect(&accountModel(), &AccountModel::accountsReordered, this, [this] {
|
||||
Q_EMIT accountListChanged();
|
||||
|
||||
profile::Info profileInfo;
|
||||
@ -62,6 +61,11 @@ LRCInstance::LRCInstance(migrateCallback willMigrateCb,
|
||||
set_currentAccountAvatarSet(!profileInfo.avatar.isEmpty());
|
||||
});
|
||||
|
||||
connect(this, &LRCInstance::currentAccountIdChanged, [this] {
|
||||
// This will trigger `AccountModel::accountsReordered`.
|
||||
accountModel().setTopAccount(currentAccountId_);
|
||||
});
|
||||
|
||||
connect(&accountModel(), &AccountModel::profileUpdated, this, [this](const QString& id) {
|
||||
if (id != currentAccountId_)
|
||||
return;
|
||||
@ -282,7 +286,7 @@ LRCInstance::getCurrentContactModel()
|
||||
int
|
||||
LRCInstance::getCurrentAccountIndex()
|
||||
{
|
||||
for (int i = 0; i < accountModel().getAccountList().size(); i++) {
|
||||
for (int i = 0; i < accountModel().getAccountCount(); i++) {
|
||||
if (accountModel().getAccountList()[i] == get_currentAccountId()) {
|
||||
return i;
|
||||
}
|
||||
@ -410,6 +414,10 @@ LRCInstance::indexOfActiveCall(const QString& confId, const QString& uri, const
|
||||
void
|
||||
LRCInstance::deselectConversation()
|
||||
{
|
||||
// Only do this if we have an account selected
|
||||
if (get_currentAccountId().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
set_selectedConvUid();
|
||||
}
|
||||
|
||||
@ -482,5 +490,15 @@ LRCInstance::onAccountRemoved(const QString& accountId)
|
||||
{
|
||||
if (accountId != currentAccountId_)
|
||||
return;
|
||||
|
||||
// If there are any accounts left, select the first one, otherwise clear the current account
|
||||
// and request presentation of the wizard view.
|
||||
auto accountList = accountModel().getAccountList();
|
||||
if (accountList.size()) {
|
||||
set_currentAccountId(accountList.at(0));
|
||||
} else {
|
||||
set_currentAccountId();
|
||||
}
|
||||
|
||||
Q_EMIT currentAccountRemoved();
|
||||
}
|
||||
|
||||
@ -28,16 +28,15 @@
|
||||
#include "qtutils.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "api/lrc.h"
|
||||
#include "api/account.h"
|
||||
#include "api/avmodel.h"
|
||||
#include "api/behaviorcontroller.h"
|
||||
#include "api/contact.h"
|
||||
#include "api/contactmodel.h"
|
||||
#include "api/conversation.h"
|
||||
#include "api/conversationmodel.h"
|
||||
#include "api/accountmodel.h"
|
||||
#include "api/callmodel.h"
|
||||
#include <api/lrc.h>
|
||||
#include <api/account.h>
|
||||
#include <api/avmodel.h>
|
||||
#include <api/behaviorcontroller.h>
|
||||
#include <api/contactmodel.h>
|
||||
#include <api/conversation.h>
|
||||
#include <api/conversationmodel.h>
|
||||
#include <api/accountmodel.h>
|
||||
#include <api/callmodel.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QThreadPool>
|
||||
@ -48,7 +47,6 @@ class ConnectivityMonitor;
|
||||
|
||||
using namespace lrc::api;
|
||||
|
||||
using migrateCallback = std::function<void()>;
|
||||
using getConvPredicate = std::function<bool(const conversation::Info& conv)>;
|
||||
|
||||
class LRCInstance : public QObject
|
||||
@ -61,9 +59,7 @@ class LRCInstance : public QObject
|
||||
QML_PROPERTY(bool, currentAccountAvatarSet)
|
||||
|
||||
public:
|
||||
explicit LRCInstance(migrateCallback willMigrateCb,
|
||||
migrateCallback didMigrateCb,
|
||||
const QString& updateUrl,
|
||||
explicit LRCInstance(const QString& updateUrl,
|
||||
ConnectivityMonitor* connectivityMonitor,
|
||||
bool debugMode,
|
||||
bool muteDaemon);
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
#include <clocale>
|
||||
|
||||
#ifndef ENABLE_TESTS
|
||||
#ifndef BUILD_TESTING
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
@ -63,6 +63,7 @@ messageHandler(QtMsgType type, const QMessageLogContext& context, const QString&
|
||||
const static std::string fmt[5] = {"DBG", "WRN", "CRT", "FTL", "INF"};
|
||||
const QByteArray localMsg = msg.toUtf8();
|
||||
const auto ts = QString::number(QDateTime::currentMSecsSinceEpoch());
|
||||
const auto tid = QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16);
|
||||
|
||||
QString fileLineInfo = "";
|
||||
const auto isQml = QString(context.category) == QLatin1String("qml");
|
||||
@ -84,8 +85,8 @@ messageHandler(QtMsgType type, const QMessageLogContext& context, const QString&
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto fmtMsg = QString("[%1][%2]:%3 %4")
|
||||
.arg(ts, fmt[type].c_str(), fileLineInfo, localMsg.constData());
|
||||
const auto fmtMsg = QString("[%1][%2][%3]:%4 %5")
|
||||
.arg(ts, fmt[type].c_str(), tid, fileLineInfo, localMsg.constData());
|
||||
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, fmtMsg);
|
||||
}
|
||||
@ -161,6 +162,8 @@ MainApplication::MainApplication(int& argc, char** argv)
|
||||
"libclient.debug=false\n"
|
||||
"qt.*=false\n"
|
||||
"qml.debug=false\n"
|
||||
"default.debug=false\n"
|
||||
"client.debug=false\n"
|
||||
"\n");
|
||||
// These can be set in the environment as well.
|
||||
// e.g. QT_LOGGING_RULES="*.debug=false;qml.debug=true"
|
||||
@ -298,30 +301,7 @@ MainApplication::initLrc(const QString& downloadUrl,
|
||||
bool debugMode,
|
||||
bool muteDaemon)
|
||||
{
|
||||
/*
|
||||
* Init mainwindow and finish splash when mainwindow shows up.
|
||||
*/
|
||||
std::atomic_bool isMigrating(false);
|
||||
lrcInstance_.reset(new LRCInstance(
|
||||
[this, &isMigrating] {
|
||||
/*
|
||||
* TODO: splash screen for account migration.
|
||||
*/
|
||||
isMigrating = true;
|
||||
while (isMigrating) {
|
||||
this->processEvents();
|
||||
}
|
||||
},
|
||||
[&isMigrating] {
|
||||
while (!isMigrating) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
isMigrating = false;
|
||||
},
|
||||
downloadUrl,
|
||||
cm,
|
||||
debugMode,
|
||||
muteDaemon));
|
||||
lrcInstance_.reset(new LRCInstance(downloadUrl, cm, debugMode, muteDaemon));
|
||||
lrcInstance_->subscribeToDebugReceived();
|
||||
}
|
||||
|
||||
@ -335,49 +315,59 @@ MainApplication::parseArguments()
|
||||
}
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser_.addHelpOption();
|
||||
parser_.addVersionOption();
|
||||
|
||||
QCommandLineOption webDebugOption(QStringList() << "remote-debugging-port",
|
||||
"Web debugging port.",
|
||||
"port");
|
||||
parser.addOption(webDebugOption);
|
||||
parser_.addOption(webDebugOption);
|
||||
|
||||
QCommandLineOption minimizedOption({"m", "minimized"}, "Start minimized.");
|
||||
parser.addOption(minimizedOption);
|
||||
parser_.addOption(minimizedOption);
|
||||
|
||||
QCommandLineOption debugOption({"d", "debug"}, "Debug out.");
|
||||
parser.addOption(debugOption);
|
||||
parser_.addOption(debugOption);
|
||||
|
||||
QCommandLineOption logFileOption({"f", "file"}, "Debug to <file>.", "file");
|
||||
parser.addOption(logFileOption);
|
||||
parser_.addOption(logFileOption);
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QCommandLineOption updateUrlOption({"u", "url"}, "<url> for debugging version queries.", "url");
|
||||
parser.addOption(updateUrlOption);
|
||||
parser_.addOption(updateUrlOption);
|
||||
|
||||
#endif
|
||||
QCommandLineOption terminateOption({"t", "term"}, "Terminate all instances.");
|
||||
parser.addOption(terminateOption);
|
||||
parser_.addOption(terminateOption);
|
||||
|
||||
QCommandLineOption muteDaemonOption({"q", "quiet"}, "Mute daemon logging. (only if debug)");
|
||||
parser.addOption(muteDaemonOption);
|
||||
parser_.addOption(muteDaemonOption);
|
||||
|
||||
parser.process(*this);
|
||||
#ifdef QT_DEBUG
|
||||
// In debug mode, add an option to test a specific QML component via its name.
|
||||
// e.g. ./jami --test AccountComboBox
|
||||
parser_.addOption(QCommandLineOption("test", "Test a QML component via its name.", "uri"));
|
||||
// We may need to force the test window dimensions in the case that the component to test
|
||||
// does not specify its own dimensions and is dependent on parent/sibling dimensions.
|
||||
// e.g. ./jami --test AccountComboBox -w 200
|
||||
parser_.addOption(QCommandLineOption("width", "Width for the test window.", "width"));
|
||||
parser_.addOption(QCommandLineOption("height", "Height for the test window.", "height"));
|
||||
#endif
|
||||
|
||||
runOptions_[Option::StartMinimized] = parser.isSet(minimizedOption);
|
||||
runOptions_[Option::Debug] = parser.isSet(debugOption);
|
||||
if (parser.isSet(logFileOption)) {
|
||||
auto logFileValue = parser.value(logFileOption);
|
||||
parser_.process(*this);
|
||||
|
||||
runOptions_[Option::StartMinimized] = parser_.isSet(minimizedOption);
|
||||
runOptions_[Option::Debug] = parser_.isSet(debugOption);
|
||||
if (parser_.isSet(logFileOption)) {
|
||||
auto logFileValue = parser_.value(logFileOption);
|
||||
auto logFile = logFileValue.isEmpty() ? Utils::getDebugFilePath() : logFileValue;
|
||||
qputenv("JAMI_LOG_FILE", logFile.toStdString().c_str());
|
||||
}
|
||||
#ifdef Q_OS_WINDOWS
|
||||
runOptions_[Option::UpdateUrl] = parser.value(updateUrlOption);
|
||||
runOptions_[Option::UpdateUrl] = parser_.value(updateUrlOption);
|
||||
#endif
|
||||
runOptions_[Option::TerminationRequested] = parser.isSet(terminateOption);
|
||||
runOptions_[Option::MuteDaemon] = parser.isSet(muteDaemonOption);
|
||||
runOptions_[Option::TerminationRequested] = parser_.isSet(terminateOption);
|
||||
runOptions_[Option::MuteDaemon] = parser_.isSet(muteDaemonOption);
|
||||
}
|
||||
|
||||
void
|
||||
@ -393,6 +383,35 @@ MainApplication::setApplicationFont()
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
QString
|
||||
findResource(const QString& targetBasename, const QString& basePath = ":/")
|
||||
{
|
||||
QDir dir(basePath);
|
||||
// List all entries in the directory excluding special entries '.' and '..'
|
||||
QStringList entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
|
||||
QDir::DirsFirst);
|
||||
|
||||
Q_FOREACH (const QString& entry, entries) {
|
||||
QString fullPath = basePath + "/" + entry;
|
||||
QFileInfo fileInfo(fullPath);
|
||||
|
||||
if (fileInfo.isDir()) {
|
||||
// Recursively search in subdirectories
|
||||
QString found = findResource(targetBasename, fullPath);
|
||||
if (!found.isEmpty()) {
|
||||
return found; // Return the first match found in any subdirectory
|
||||
}
|
||||
} else if (fileInfo.isFile()
|
||||
&& fileInfo.fileName().contains(targetBasename, Qt::CaseInsensitive)) {
|
||||
// Match found, return the full path but remove the leading ":/".
|
||||
return fileInfo.absoluteFilePath().mid(2);
|
||||
}
|
||||
}
|
||||
|
||||
// No match found in this directory or its subdirectories
|
||||
return QString();
|
||||
}
|
||||
|
||||
void
|
||||
MainApplication::initQmlLayer()
|
||||
{
|
||||
@ -406,10 +425,33 @@ MainApplication::initQmlLayer()
|
||||
&screenInfo_,
|
||||
this);
|
||||
|
||||
engine_->load(QUrl(QStringLiteral("qrc:/MainApplicationWindow.qml")));
|
||||
QUrl url = u"qrc:/MainApplicationWindow.qml"_qs;
|
||||
#ifdef QT_DEBUG
|
||||
if (parser_.isSet("test")) {
|
||||
// List the QML files in the project source tree.
|
||||
const auto targetTestComponent = findResource(parser_.value("test"));
|
||||
if (targetTestComponent.isEmpty()) {
|
||||
C_FATAL << "Failed to find QML component:" << parser_.value("test");
|
||||
}
|
||||
engine_->rootContext()->setContextProperty("testComponentURI", targetTestComponent);
|
||||
// Log the width and height values for the test window.
|
||||
const auto testWidth = parser_.isSet("width") ? parser_.value("width").toInt() : 0;
|
||||
const auto testHeight = parser_.isSet("height") ? parser_.value("height").toInt() : 0;
|
||||
engine_->rootContext()->setContextProperty("testWidth", testWidth);
|
||||
engine_->rootContext()->setContextProperty("testHeight", testHeight);
|
||||
url = u"qrc:/ComponentTestWindow.qml"_qs;
|
||||
}
|
||||
#endif
|
||||
QObject::connect(
|
||||
engine_.get(),
|
||||
&QQmlApplicationEngine::objectCreationFailed,
|
||||
this,
|
||||
[url]() { C_FATAL << "Failed to load QML component:" << url; },
|
||||
Qt::QueuedConnection);
|
||||
engine_->load(url);
|
||||
|
||||
// Report the render interface used.
|
||||
C_DBG << "Main window loaded using" << getRenderInterfaceString();
|
||||
C_INFO << "Main window loaded using" << getRenderInterfaceString();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@ -122,6 +123,6 @@ private:
|
||||
SystemTray* systemTray_;
|
||||
AppSettingsManager* settingsManager_;
|
||||
PreviewEngine* previewEngine_;
|
||||
|
||||
ScreenInfo screenInfo_;
|
||||
QCommandLineParser parser_;
|
||||
};
|
||||
|
||||
@ -48,10 +48,10 @@ ListSelectionView {
|
||||
leftPaneItem: viewCoordinator.getView("SidePanel", true)
|
||||
|
||||
rightPaneItem: StackLayout {
|
||||
id: conversationStackLayout
|
||||
objectName: "ConversationLayout"
|
||||
|
||||
currentIndex: !CurrentConversation.hasCall ? 0 : 1
|
||||
onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer
|
||||
currentIndex: CurrentConversation.hasCall ? 1 : 0
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
@ -64,24 +64,35 @@ ListSelectionView {
|
||||
ChatView {
|
||||
id: chatView
|
||||
anchors.fill: parent
|
||||
inCallView: parent == callStackView.chatViewContainer
|
||||
|
||||
// Use callStackView.chatViewContainer only when hasCall is true
|
||||
// and callStackView.chatViewContainer not null.
|
||||
// Because after a swarm call ends, callStackView.chatViewContainer might not be null
|
||||
// due to a lack of call state change signals for the swarm call.
|
||||
readonly property bool hasCall: CurrentConversation.hasCall
|
||||
readonly property var inCallChatContainer: hasCall ? callStackView.chatViewContainer : null
|
||||
|
||||
// Parent the chat view to the call stack view when in call.
|
||||
parent: inCallChatContainer ? inCallChatContainer : chatViewContainer
|
||||
inCallView: parent === callStackView.chatViewContainer
|
||||
|
||||
readonly property string currentConvId: CurrentConversation.id
|
||||
onCurrentConvIdChanged: {
|
||||
if (!CurrentConversation.hasCall) {
|
||||
Qt.callLater(focusChatView);
|
||||
} else {
|
||||
dismiss();
|
||||
callStackView.contentView.forceActiveFocus();
|
||||
}
|
||||
Qt.callLater(function() {
|
||||
if (CurrentConversation.hasCall) {
|
||||
callStackView.contentView.forceActiveFocus();
|
||||
} else {
|
||||
focusChatView();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDismiss: {
|
||||
if (!inCallView) {
|
||||
viewNode.dismiss();
|
||||
} else {
|
||||
if (inCallView) {
|
||||
callStackView.chatViewContainer.visible = false;
|
||||
callStackView.contentView.forceActiveFocus();
|
||||
} else {
|
||||
viewNode.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,29 +29,11 @@ Label {
|
||||
|
||||
property alias popup: comboBoxPopup
|
||||
|
||||
width: parent ? parent.width : o
|
||||
width: parent ? parent.width : 0
|
||||
height: JamiTheme.accountListItemHeight
|
||||
|
||||
property bool inSettings: viewCoordinator.currentViewName === "SettingsView"
|
||||
|
||||
// TODO: remove these refresh hacks use QAbstractItemModels correctly
|
||||
Connections {
|
||||
target: AccountAdapter
|
||||
|
||||
function onAccountStatusChanged(accountId) {
|
||||
AccountListModel.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: LRCInstance
|
||||
|
||||
function onAccountListChanged() {
|
||||
root.update();
|
||||
AccountListModel.reset();
|
||||
}
|
||||
}
|
||||
|
||||
function togglePopup() {
|
||||
if (root.popup.opened) {
|
||||
root.popup.close();
|
||||
@ -111,6 +93,7 @@ Label {
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
objectName: "accountComboBoxAvatar"
|
||||
|
||||
Layout.preferredWidth: JamiTheme.accountListAvatarSize
|
||||
Layout.preferredHeight: JamiTheme.accountListAvatarSize
|
||||
|
||||
@ -60,23 +60,6 @@ Popup {
|
||||
|
||||
property bool inSettings: viewCoordinator.currentViewName === "SettingsView"
|
||||
|
||||
// TODO: remove these refresh hacks use QAbstractItemModels correctly
|
||||
Connections {
|
||||
target: AccountAdapter
|
||||
|
||||
function onAccountStatusChanged(accountId) {
|
||||
AccountListModel.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: LRCInstance
|
||||
|
||||
function onAccountListChanged() {
|
||||
AccountListModel.reset();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
@ -105,6 +88,7 @@ Popup {
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
objectName: "accountComboBoxPopupAvatar"
|
||||
|
||||
Layout.preferredWidth: JamiTheme.accountListAvatarSize
|
||||
Layout.preferredHeight: JamiTheme.accountListAvatarSize
|
||||
@ -223,6 +207,7 @@ Popup {
|
||||
|
||||
JamiListView {
|
||||
id: listView
|
||||
objectName: "accountList"
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: parent.width
|
||||
@ -255,11 +240,6 @@ Popup {
|
||||
color: JamiTheme.smartListHoveredColor
|
||||
}
|
||||
|
||||
// fake footer item as workaround for Qt 5.15 bug
|
||||
// https://bugreports.qt.io/browse/QTBUG-85302
|
||||
// don't use the clip trick and footer item overlay
|
||||
// explained here https://stackoverflow.com/a/64625149
|
||||
// as it causes other complexities in handling the drop shadow
|
||||
ItemDelegate {
|
||||
id: addAccountItem
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ ItemDelegate {
|
||||
height: 1
|
||||
width: parent.width - 20
|
||||
color: JamiTheme.hoverColor
|
||||
|
||||
visible: index !== 0
|
||||
}
|
||||
|
||||
color: {
|
||||
@ -62,6 +62,7 @@ ItemDelegate {
|
||||
spacing: 10
|
||||
|
||||
Avatar {
|
||||
objectName: "accountComboBoxDelegateAvatar"
|
||||
Layout.preferredWidth: JamiTheme.accountListAvatarSize
|
||||
Layout.preferredHeight: JamiTheme.accountListAvatarSize
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
@ -112,6 +112,7 @@ Control {
|
||||
},
|
||||
Action {
|
||||
id: shareMenuAction
|
||||
enabled: !CurrentCall.isSharing
|
||||
text: JamiStrings.selectShareMethod
|
||||
property int popupMode: CallActionBar.ActionPopupMode.ListElement
|
||||
property var listModel: ListModel {
|
||||
@ -123,7 +124,7 @@ Control {
|
||||
"Name": JamiStrings.shareScreen,
|
||||
"IconSource": JamiResources.laptop_black_24dp_svg
|
||||
});
|
||||
if (Qt.platform.os.toString() !== "osx" && !UtilsAdapter.isWayland()) {
|
||||
if (Qt.platform.os.toString() !== "osx") {
|
||||
shareModel.append({
|
||||
"Name": JamiStrings.shareWindow,
|
||||
"IconSource": JamiResources.window_black_24dp_svg
|
||||
@ -293,7 +294,24 @@ Control {
|
||||
},
|
||||
Action {
|
||||
id: muteVideoAction
|
||||
onTriggered: CallAdapter.muteCameraToggle()
|
||||
onTriggered: {
|
||||
if (CurrentCall.isSharing && UtilsAdapter.isWayland()) {
|
||||
// Unmuting the camera while a screen share is ongoing causes the daemon
|
||||
// to stop sharing. However, on Wayland, every share has an associated
|
||||
// ScreenCastPortal object which is managed by the client and needs to
|
||||
// be destroyed when the share ends. This is why we explicitly call the
|
||||
// stopSharing function below.
|
||||
//
|
||||
// The muteCamera variable is set whenever a share starts and is normally used
|
||||
// by the stopSharing function to restore the camera to its previous state
|
||||
// when a share ends. Here we know that the user wants to unmute the camera,
|
||||
// so we have to explicitly set muteCamera to false.
|
||||
AvAdapter.muteCamera = false;
|
||||
AvAdapter.stopSharing(CurrentCall.sharingSource);
|
||||
} else {
|
||||
CallAdapter.muteCameraToggle();
|
||||
}
|
||||
}
|
||||
checkable: true
|
||||
icon.source: checked ? JamiResources.videocam_off_24dp_svg : JamiResources.videocam_24dp_svg
|
||||
icon.color: checked ? "red" : "white"
|
||||
|
||||
@ -273,14 +273,46 @@ ItemDelegate {
|
||||
popup: Popup {
|
||||
id: itemPopup
|
||||
|
||||
y: isVertical ? -(implicitHeight - root.height) / 2 - 18 : -implicitHeight - 12
|
||||
y: {
|
||||
// Determine the y position based on the orientation.
|
||||
if (isVertical) {
|
||||
// For a vertical layout, adjust the y position to center the item vertically
|
||||
// relative to the root's height, with an additional upward offset of 18 pixels.
|
||||
y = -(implicitHeight - root.height) / 2 - 18;
|
||||
} else {
|
||||
// For non-vertical layouts, position the item fully above its normal position
|
||||
// with an upward offset of 12 pixels from its implicit height.
|
||||
y = -implicitHeight - 12;
|
||||
}
|
||||
}
|
||||
|
||||
x: {
|
||||
if (isVertical)
|
||||
return -implicitWidth - 12;
|
||||
var xValue = -(implicitWidth - root.width) / 2 - 18;
|
||||
var mainPoint = mapToItem(viewCoordinator.rootView, xValue, y);
|
||||
var diff = mainPoint.x + itemListView.implicitWidth - viewCoordinator.rootView.width;
|
||||
return diff > 0 ? xValue - diff - 24 : xValue;
|
||||
// Initialize the x position based on the orientation.
|
||||
if (isVertical) {
|
||||
// If the layout is vertical, position the item to the left of its implicit width
|
||||
// with an additional offset of 12 pixels.
|
||||
x = -implicitWidth - 12;
|
||||
} else {
|
||||
// Note: isn't some of this logic built into the Popup?
|
||||
|
||||
// Calculate an initial x value aiming to center the item horizontally
|
||||
// relative to the root's width, with an additional offset.
|
||||
var xValue = -(implicitWidth - root.width) / 2 - 18;
|
||||
|
||||
// Map the adjusted x value to the coordinate space of the callOverlay to
|
||||
// determine the actual position of the item within the overlay.
|
||||
var pointMappedContainer = mapToItem(callOverlay, xValue, y);
|
||||
|
||||
// Calculate the difference between the right edge of the itemListView
|
||||
// (considering its position within callOverlay) and the right edge of the callOverlay.
|
||||
// This checks if the item extends outside the overlay.
|
||||
var diff = pointMappedContainer.x + itemListView.implicitWidth - callOverlay.width;
|
||||
|
||||
// If the item extends beyond the overlay, adjust x value to the left to ensure
|
||||
// it fits within the overlay, with an extra leftward margin of 24 pixels.
|
||||
x = diff > 0 ? xValue - diff - 24 : xValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
implicitWidth: contentItem.implicitWidth
|
||||
|
||||
@ -32,6 +32,7 @@ Item {
|
||||
id: root
|
||||
|
||||
property bool participantsSide: UtilsAdapter.getAppValue(Settings.ParticipantsSide)
|
||||
property alias mainOverlayOpacity: mainOverlay.opacity
|
||||
|
||||
signal chatButtonClicked
|
||||
signal fullScreenClicked
|
||||
@ -113,7 +114,9 @@ Item {
|
||||
}
|
||||
|
||||
function openShareScreen() {
|
||||
if (Qt.application.screens.length === 1) {
|
||||
if (UtilsAdapter.isWayland()) {
|
||||
AvAdapter.shareEntireScreenWayland();
|
||||
} else if (Qt.application.screens.length === 1) {
|
||||
AvAdapter.shareEntireScreen(0);
|
||||
} else {
|
||||
SelectScreenWindowCreation.presentSelectScreenWindow(appWindow, false);
|
||||
@ -121,6 +124,10 @@ Item {
|
||||
}
|
||||
|
||||
function openShareWindow() {
|
||||
if (UtilsAdapter.isWayland()) {
|
||||
AvAdapter.shareWindowWayland();
|
||||
return;
|
||||
}
|
||||
AvAdapter.getListWindows();
|
||||
if (AvAdapter.windowsNames.length >= 1) {
|
||||
SelectScreenWindowCreation.presentSelectScreenWindow(appWindow, true);
|
||||
|
||||
@ -25,7 +25,11 @@ import "../../commoncomponents"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias chatViewContainer: ongoingCallPage.chatViewContainer
|
||||
property var chatViewContainer: {
|
||||
if (callStackMainView.item instanceof OngoingCallPage)
|
||||
return callStackMainView.item.chatViewContainer;
|
||||
return undefined;
|
||||
}
|
||||
property alias contentView: callStackMainView
|
||||
|
||||
property var sipKeys: ["1", "2", "3", "A", "4", "5", "6", "B", "7", "8", "9", "C", "*", "0", "#", "D"]
|
||||
@ -61,44 +65,49 @@ Item {
|
||||
// TODO: this should all be done by listening to
|
||||
// parent visibility change or parent `Component.onDestruction`
|
||||
function needToCloseInCallConversationAndPotentialWindow() {
|
||||
ongoingCallPage.closeInCallConversation();
|
||||
ongoingCallPage.closeContextMenuAndRelatedWindows();
|
||||
if (callStackMainView.item instanceof OngoingCallPage) {
|
||||
callStackMainView.item.closeInCallConversation();
|
||||
callStackMainView.item.closeContextMenuAndRelatedWindows();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFullScreen() {
|
||||
if (!layoutManager.isCallFullscreen) {
|
||||
layoutManager.pushFullScreenItem(callStackMainView.currentItem, callStackMainView, null, null);
|
||||
layoutManager.pushFullScreenItem(callStackMainView.item, callStackMainView, null, null);
|
||||
} else {
|
||||
layoutManager.removeFullScreenItem(callStackMainView.currentItem);
|
||||
layoutManager.removeFullScreenItem(callStackMainView.item);
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
Loader {
|
||||
id: callStackMainView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
property Item currentItem: itemAt(currentIndex)
|
||||
|
||||
currentIndex: {
|
||||
sourceComponent: {
|
||||
switch (CurrentCall.status) {
|
||||
case Call.Status.IN_PROGRESS:
|
||||
case Call.Status.CONNECTED:
|
||||
case Call.Status.PAUSED:
|
||||
return 1;
|
||||
return ongoingCallPageComponent;
|
||||
case Call.Status.SEARCHING:
|
||||
case Call.Status.CONNECTING:
|
||||
case Call.Status.INCOMING_RINGING:
|
||||
case Call.Status.OUTGOING_RINGING:
|
||||
return initialCallPageComponent;
|
||||
default:
|
||||
return 0;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
InitialCallPage {
|
||||
Component {
|
||||
id: initialCallPageComponent
|
||||
InitialCallPage {}
|
||||
}
|
||||
OngoingCallPage {
|
||||
id: ongoingCallPage
|
||||
|
||||
Component {
|
||||
id: ongoingCallPageComponent
|
||||
OngoingCallPage {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
|
||||
|
||||
@ -39,6 +38,10 @@ Rectangle {
|
||||
|
||||
property var mapPositions: PositionManager.mapStatus
|
||||
|
||||
// The purpose of this alias is to make the message bar
|
||||
// accessible to the EmojiPicker
|
||||
property alias messageBar: chatViewFooter.messageBar
|
||||
|
||||
required property bool inCallView
|
||||
|
||||
// Hide the extrasPanel when going into a call view, but save the previous
|
||||
@ -129,6 +132,11 @@ Rectangle {
|
||||
Connections {
|
||||
target: CurrentConversation
|
||||
|
||||
function onIdChanged() {
|
||||
if (!chatViewHeader.interactionButtonsVisibility)
|
||||
extrasPanel.closePanel();
|
||||
}
|
||||
|
||||
function onNeedsHost() {
|
||||
viewCoordinator.presentDialog(appWindow, "mainview/components/HostPopup.qml");
|
||||
}
|
||||
@ -214,7 +222,7 @@ Rectangle {
|
||||
onExtrasPanelWidthChanged: {
|
||||
resolvePanes();
|
||||
// This range should ensure that the panel won't restore to maximized.
|
||||
if (extrasPanelWidth !== 0 && extrasPanelWidth !== width) {
|
||||
if (extrasPanelWidth !== 0 && extrasPanelWidth !== this.width) {
|
||||
console.debug("Saving previous extras panel width: %1".arg(extrasPanelWidth));
|
||||
previousExtrasPanelWidth = extrasPanelWidth;
|
||||
}
|
||||
@ -242,14 +250,10 @@ Rectangle {
|
||||
chatContents.visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const isExpanding = width > previousWidth;
|
||||
|
||||
// Provide a detailed log here, as this function seems problematic.
|
||||
console.debug("ChatViewSplitView.resolvePanes: f: %1 w: %2 pw: %3 epw: %4 pepw: %5 ie: %6"
|
||||
.arg(force).arg(width).arg(previousWidth)
|
||||
.arg(extrasPanelWidth).arg(previousExtrasPanelWidth).arg(isExpanding));
|
||||
|
||||
console.debug("ChatViewSplitView.resolvePanes: f: %1 w: %2 pw: %3 epw: %4 pepw: %5 ie: %6".arg(force).arg(width).arg(previousWidth).arg(extrasPanelWidth).arg(previousExtrasPanelWidth).arg(isExpanding));
|
||||
const maximizePredicate = (!isExpanding || force) && chatContents.visible;
|
||||
const minimizePredicate = (isExpanding || force) && !chatContents.visible;
|
||||
const mainViewMinWidth = JamiTheme.mainViewPaneMinWidth;
|
||||
@ -314,6 +318,7 @@ Rectangle {
|
||||
|
||||
ChatViewFooter {
|
||||
id: chatViewFooter
|
||||
objectName: "chatViewFooter"
|
||||
|
||||
visible: {
|
||||
if (CurrentAccount.type === Profile.Type.SIP)
|
||||
@ -322,7 +327,7 @@ Rectangle {
|
||||
return false;
|
||||
else if (CurrentConversation.needsSyncing)
|
||||
return false;
|
||||
else if (CurrentConversation.isSwarm && CurrentConversation.isRequest)
|
||||
else if (CurrentConversation.isRequest)
|
||||
return false;
|
||||
return CurrentConversation.isSwarm || CurrentConversation.isTemporary;
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import "../../commoncomponents"
|
||||
Rectangle {
|
||||
id: root
|
||||
property alias textInput: messageBar.textAreaObj
|
||||
property alias messageBar: messageBar
|
||||
property string previousConvId
|
||||
property string previousAccountId
|
||||
property bool showTypo: messageBar.showTypo
|
||||
|
||||
@ -75,8 +75,8 @@ Rectangle {
|
||||
|
||||
anchors.fill: parent
|
||||
// QWK: spacing
|
||||
anchors.leftMargin: qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: 10 + qwkSystemButtonSpacing.right
|
||||
anchors.leftMargin: layoutManager.qwkSystemButtonSpacing.left
|
||||
anchors.rightMargin: 10 + layoutManager.qwkSystemButtonSpacing.right
|
||||
spacing: 16
|
||||
|
||||
JamiPushButton { QWKSetParentHitTestVisible {}
|
||||
@ -230,8 +230,7 @@ Rectangle {
|
||||
|
||||
checkable: true
|
||||
checked: extrasPanel.isOpen(ChatView.SwarmDetailsPanel)
|
||||
visible: interactionButtonsVisibility &&
|
||||
(swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request
|
||||
visible: (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP)
|
||||
source: JamiResources.swarm_details_panel_svg
|
||||
toolTipText: JamiStrings.details
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ Item {
|
||||
id: root
|
||||
|
||||
property alias imageId: avatar.imageId
|
||||
property alias presenceStatus: avatar.presenceStatus
|
||||
property alias showPresenceIndicator: avatar.showPresenceIndicator
|
||||
property alias animationMode: animation.mode
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ JamiListView {
|
||||
|
||||
property var messageListModel: MessagesAdapter.mediaMessageListModel
|
||||
readonly property int documentType: Interaction.Type.DATA_TRANSFER
|
||||
readonly property int transferFinishedType: Interaction.Status.TRANSFER_FINISHED
|
||||
readonly property int transferFinishedType: Interaction.TransferStatus.TRANSFER_FINISHED
|
||||
readonly property int transferSuccesType: Interaction.Status.SUCCESS
|
||||
|
||||
onMessageListModelChanged: sourceModel = root.visible && messageListModel ? messageListModel : null
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user