mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
3 Commits
967dc5937f
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66ff42c78b | ||
|
|
fc5f504509 | ||
|
|
6c963e0000 |
69
CHANGELOG.md
69
CHANGELOG.md
@@ -3,75 +3,6 @@
|
||||
## 2.8.0 (Pending)
|
||||
* Placeholder for future release notes
|
||||
|
||||
## 2.7.11 (2025-11-23)
|
||||
|
||||
### Changes
|
||||
- Add image, HTML, Markdown preview, and text editing support to inline attachment viewer [#12085, #12244, #12654]
|
||||
- Add database merge confirmation dialog [#10173]
|
||||
- Add option to auto-generate a password for new entries [#12593]
|
||||
- Add support for group sync in KeeShare [#11593]
|
||||
- Add {UUID} placeholder for use in references [#12511]
|
||||
- Add “Wait for Enter” search option [#12263]
|
||||
- Add keyboard shortcut to “Jump to Group” from search results [#12225]
|
||||
- Add predefined search for TOTP entries [#12199]
|
||||
- Add confirmation when closing database via ESC key [#11963]
|
||||
- Add support for escaping placeholder expressions [#11904]
|
||||
- Reduce tab indentation width in notes fields [#11919]
|
||||
- Cap default Argon2 parallelism when creating a new database [#11853]
|
||||
- Database lock after inactivity now enabled by default and set to 900 seconds [#12689, #12609]
|
||||
- Copying TOTP now opens setup dialog if none is configured for entry [#12584]
|
||||
- Make double click action configurable [#12322]
|
||||
- Remove unused “Last Accessed” from GUI [#12602]
|
||||
- Auto-Type: Add more granular confirmation settings [#12370]
|
||||
- Auto-Type: Add URL typing preset and add copy options to menu [#12341]
|
||||
- Browser: Do not allow sites automatically if entry added from browser extension [#12413]
|
||||
- Browser: Add options to restrict exposed groups [#9852, #12119]
|
||||
- Bitwarden Import: Add support for timestamps and password history [#12588]
|
||||
- macOS: Add Liquid Glass icon [#12642]
|
||||
- macOS: Remove theme-based menubar icon toggle [#12685]
|
||||
- macOS: Add Window and Help menus [#12357]
|
||||
- Windows: Add option to add KeePassXC to PATH during installation [#12171]
|
||||
|
||||
### Fixes
|
||||
- Fix window geometry not being restored properly when KeePassXC starts in tray [#12683]
|
||||
- Fix potential database truncation when using direct write save method with YubiKeys [#11841]
|
||||
- Fix issue with database backup saving [#11874]
|
||||
- Fix UI lockups during startup with multiple tabs [#12053]
|
||||
- Fix keyboard shortcuts when menubar is hidden [#12431]
|
||||
- Fix clipboard being cleared on exit even if no password was copied [#12603]
|
||||
- Fix single-instance detection when username contains invalid filename characters [#12559]
|
||||
- Fix “Search Wait for Enter” setting not being save [#12614]
|
||||
- Fix hotkey accelerators not being escaped properly on database tabs [#12630]
|
||||
- Fix confusing error if user cancels out of key file edit dialog [#12639]
|
||||
- Fix issues with saved searches and “Press Enter to Search” option [#12314]
|
||||
- Fix URL wildcard matching [#12257]
|
||||
- Fix TOTP visibility on unlock and settings change [#12220]
|
||||
- Fix KeeShare entries with reference attributes not updating [#11809]
|
||||
- Fix sort order not being maintained when toggling filters in database reports [#11849]
|
||||
- Fix several UI font and layout issues [#11967, #12102]
|
||||
- Prevent mouse wheel scroll on edit username field [#12398]
|
||||
- Improve base translation consistency [#12432]
|
||||
- Improve inactivity timer [#12246]
|
||||
- Documentation improvements [#12373, #12506]
|
||||
- Browser: Fix ordering of clientDataJSON in Passkey response object [#12120]
|
||||
- Browser: Fix URL matching for additional URLs [#12196]
|
||||
- Browser: Fix group settings inheritance [#12368]
|
||||
- Browser: Allow read-only native messaging config files [#12236]
|
||||
- Browser: Optimise entry iteration in browser access control dialog [#11817]
|
||||
- Browser: Fix “Do not ask permission for HTTP Basic Auth” option [#11871]
|
||||
- Browser: Fix native messaging path for Tor Browser launcher on Linux [#12005]
|
||||
- Auto-Type: Fix empty window behaviour [#12622]
|
||||
- Auto-Type: Take delays into account when typing TOTP [#12691]
|
||||
- SSH Agent: Fix out-of-memory crash with malformed SSH keys [#12606]
|
||||
- CSV Import: Fix modified and creation time import [#12379]
|
||||
- CSV Import: Fix duplication of root groups on import [#12240]
|
||||
- Proton Pass Import: Fix email addresses not being imported when no username set [#11888]
|
||||
- macOS: Fix secure input getting stuck [#11928]
|
||||
- Windows: Prevent launch as SYSTEM user from MSI installer [#12705]
|
||||
- Windows: Remove broken check for MSVC Redistributable from MSI installer [#11950]
|
||||
- Linux: Fix startup delay due to StartupNotify setting in desktop file [#12306]
|
||||
- Linux: Fix memory initialisation when --pw-stdin is used with a pipe [#12050]
|
||||
|
||||
## 2.7.10 (2025-03-02)
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -31,7 +31,7 @@ if(NOT CPACK_PACKAGE_FILES) # PRE_BUILD: Sign binaries
|
||||
|
||||
# Sign all binaries
|
||||
execute_process(
|
||||
COMMAND xcrun codesign --sign=${CODESIGN_IDENTITY} --force --options=runtime --deep "${APP_DIR}"
|
||||
COMMAND xcrun codesign --sign=${CODESIGN_IDENTITY} --force --options=runtime --deep ${APP_DIR}
|
||||
RESULT_VARIABLE SIGN_RESULT
|
||||
OUTPUT_VARIABLE SIGN_OUTPUT
|
||||
ERROR_VARIABLE SIGN_ERROR
|
||||
@@ -45,7 +45,7 @@ if(NOT CPACK_PACKAGE_FILES) # PRE_BUILD: Sign binaries
|
||||
|
||||
# (Re-)Sign main executable with --entitlements
|
||||
execute_process(
|
||||
COMMAND xcrun codesign --sign=${CODESIGN_IDENTITY} --force --options=runtime --entitlements=${ENTITLEMENTS} "${APP_DIR}/Contents/MacOS/${PROGNAME}"
|
||||
COMMAND xcrun codesign --sign=${CODESIGN_IDENTITY} --force --options=runtime --deep --entitlements=${ENTITLEMENTS} ${APP_DIR}
|
||||
RESULT_VARIABLE SIGN_RESULT
|
||||
OUTPUT_VARIABLE SIGN_OUTPUT
|
||||
ERROR_VARIABLE SIGN_ERROR
|
||||
@@ -61,41 +61,42 @@ if(NOT CPACK_PACKAGE_FILES) # PRE_BUILD: Sign binaries
|
||||
|
||||
else() # POST_BUILD: Notarize DMG
|
||||
set(KEYCHAIN_PROFILE "@WITH_XC_NOTARY_KEYCHAIN_PROFILE@")
|
||||
file(GLOB_RECURSE DMG_FILE "${CPACK_PACKAGE_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}.dmg")
|
||||
|
||||
if(NOT KEYCHAIN_PROFILE)
|
||||
message(FATAL_ERROR "No notarization credentials keychain profile specified.")
|
||||
endif()
|
||||
|
||||
foreach(DMG_FILE ${CPACK_PACKAGE_FILES})
|
||||
# Submit for notarization
|
||||
message(STATUS "Submitting DMG bundle for notarization, this may take while...")
|
||||
execute_process(
|
||||
COMMAND xcrun notarytool submit --keychain-profile=${KEYCHAIN_PROFILE} --wait "${DMG_FILE}"
|
||||
RESULT_VARIABLE NOTARIZE_RESULT
|
||||
OUTPUT_VARIABLE NOTARIZE_OUTPUT
|
||||
ERROR_VARIABLE NOTARIZE_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ECHO_OUTPUT_VARIABLE
|
||||
)
|
||||
if (NOT NOTARIZE_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Notarization failed: ${NOTARIZE_ERROR}")
|
||||
endif()
|
||||
message(STATUS "DMG bundle notarized successfully.")
|
||||
# Submit for notarization
|
||||
message(STATUS "Submitting DMG bundle for notarization, this may take while...")
|
||||
execute_process(
|
||||
COMMAND xcrun notarytool submit --keychain-profile=${KEYCHAIN_PROFILE} --wait ${DMG_FILE}
|
||||
RESULT_VARIABLE NOTARIZE_RESULT
|
||||
OUTPUT_VARIABLE NOTARIZE_OUTPUT
|
||||
ERROR_VARIABLE NOTARIZE_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ECHO_OUTPUT_VARIABLE
|
||||
)
|
||||
if (NOT NOTARIZE_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Notarization failed: ${NOTARIZE_ERROR}")
|
||||
endif()
|
||||
message(STATUS "DMG bundle notarized successfully.")
|
||||
|
||||
# Staple tickets
|
||||
message(STATUS "Stapling notarization ticket...")
|
||||
execute_process(
|
||||
COMMAND xcrun stapler staple ${DMG_FILE} && xcrun stapler validate ${DMG_FILE}
|
||||
RESULT_VARIABLE STAPLE_RESULT
|
||||
OUTPUT_VARIABLE STAPLE_OUTPUT
|
||||
ERROR_VARIABLE STAPLE_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ECHO_OUTPUT_VARIABLE
|
||||
)
|
||||
if (NOT STAPLE_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Stapling failed: ${STAPLE_ERROR}")
|
||||
endif()
|
||||
message(STATUS "DMG bundle notarization ticket stapled successfully.")
|
||||
|
||||
# Staple tickets
|
||||
message(STATUS "Stapling notarization ticket...")
|
||||
execute_process(
|
||||
COMMAND xcrun stapler staple "${DMG_FILE}" && xcrun stapler validate "${DMG_FILE}"
|
||||
RESULT_VARIABLE STAPLE_RESULT
|
||||
OUTPUT_VARIABLE STAPLE_OUTPUT
|
||||
ERROR_VARIABLE STAPLE_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ECHO_OUTPUT_VARIABLE
|
||||
)
|
||||
if (NOT STAPLE_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Stapling failed: ${STAPLE_ERROR}")
|
||||
endif()
|
||||
message(STATUS "DMG bundle notarization ticket stapled successfully.")
|
||||
endforeach()
|
||||
endif()
|
||||
@@ -25,10 +25,12 @@ import lzma
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import random
|
||||
import re
|
||||
import signal
|
||||
import shutil
|
||||
import stat
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
@@ -41,7 +43,7 @@ from urllib.request import urlretrieve
|
||||
###########################################################################################
|
||||
|
||||
# class Check(Command)
|
||||
# class Tag(Command)
|
||||
# class Merge(Command)
|
||||
# class Build(Command)
|
||||
# class BuildSrc(Command)
|
||||
# class AppSign(Command)
|
||||
@@ -129,8 +131,7 @@ fmt = LogFormatter()
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(fmt)
|
||||
logger = logging.getLogger(__file__)
|
||||
logger.setLevel(os.getenv('LOGLEVEL')
|
||||
if type(logging.getLevelName(os.environ.get('LOGLEVEL'))) is int else logging.INFO)
|
||||
logger.setLevel(os.getenv('LOGLEVEL') if 'LOGLEVEL' in os.environ else logging.INFO)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
###########################################################################################
|
||||
@@ -187,12 +188,11 @@ def _run(cmd, *args, cwd, path=None, env=None, input=None, capture_output=True,
|
||||
env['FORCE_COLOR'] = '1'
|
||||
|
||||
if docker_image:
|
||||
cwd2 = Path(cwd or '.').absolute()
|
||||
docker_cmd = ['docker', 'run', '--rm', '--tty=true', f'--workdir={cwd2}', f'--user={os.getuid()}:{os.getgid()}']
|
||||
docker_cmd = ['docker', 'run', '--rm', '--tty=true', f'--workdir={cwd}', f'--user={os.getuid()}:{os.getgid()}']
|
||||
docker_cmd.extend([f'--env={k}={v}' for k, v in env.items() if k in ['FORCE_COLOR', 'CC', 'CXX']])
|
||||
if path:
|
||||
docker_cmd.append(f'--env=PATH={path}')
|
||||
docker_cmd.append(f'--volume={cwd2}:{cwd2}:rw')
|
||||
docker_cmd.append(f'--volume={Path(cwd).absolute()}:{Path(cwd).absolute()}:rw')
|
||||
if docker_mounts:
|
||||
docker_cmd.extend([f'--volume={Path(d).absolute()}:{Path(d).absolute()}:rw' for d in docker_mounts])
|
||||
if docker_privileged:
|
||||
@@ -203,7 +203,7 @@ def _run(cmd, *args, cwd, path=None, env=None, input=None, capture_output=True,
|
||||
cmd = docker_cmd + cmd
|
||||
|
||||
try:
|
||||
logger.debug('Running command: %s', ' '.join(map(str, cmd)))
|
||||
logger.debug('Running command: %s', ' '.join(cmd))
|
||||
return subprocess.run(
|
||||
cmd, *args,
|
||||
input=input,
|
||||
@@ -450,7 +450,6 @@ class Check(Command):
|
||||
cls.check_version_in_cmake(version, src_dir)
|
||||
cls.check_changelog(version, src_dir)
|
||||
cls.check_app_stream_info(version, src_dir)
|
||||
return git_ref
|
||||
|
||||
@staticmethod
|
||||
def check_src_dir_exists(src_dir):
|
||||
@@ -491,7 +490,7 @@ class Check(Command):
|
||||
cmakelists = Path(cwd) / cmakelists
|
||||
if not cmakelists.is_file():
|
||||
raise Error('File not found: %s', cmakelists)
|
||||
cmakelists_text = cmakelists.read_text("UTF-8")
|
||||
cmakelists_text = cmakelists.read_text()
|
||||
major = re.search(r'^set\(KEEPASSXC_VERSION_MAJOR "(\d+)"\)$', cmakelists_text, re.MULTILINE).group(1)
|
||||
minor = re.search(r'^set\(KEEPASSXC_VERSION_MINOR "(\d+)"\)$', cmakelists_text, re.MULTILINE).group(1)
|
||||
patch = re.search(r'^set\(KEEPASSXC_VERSION_PATCH "(\d+)"\)$', cmakelists_text, re.MULTILINE).group(1)
|
||||
@@ -507,7 +506,7 @@ class Check(Command):
|
||||
if not changelog.is_file():
|
||||
raise Error('File not found: %s', changelog)
|
||||
major, minor, patch = _split_version(version)
|
||||
if not re.search(rf'^## {major}\.{minor}\.{patch} \(.+?\)\n+', changelog.read_text("UTF-8"), re.MULTILINE):
|
||||
if not re.search(rf'^## {major}\.{minor}\.{patch} \(.+?\)\n+', changelog.read_text(), re.MULTILINE):
|
||||
raise Error(f'{changelog} has not been updated to the "%s" release.', version)
|
||||
|
||||
@staticmethod
|
||||
@@ -542,8 +541,8 @@ class Check(Command):
|
||||
raise Error('xcrun command not found! Please check that you have correctly installed Xcode.')
|
||||
|
||||
|
||||
class Tag(Command):
|
||||
"""Update translations and tag release."""
|
||||
class Merge(Command):
|
||||
"""Merge release branch into main branch and create release tags."""
|
||||
|
||||
@classmethod
|
||||
def setup_arg_parser(cls, parser: argparse.ArgumentParser):
|
||||
@@ -565,7 +564,7 @@ class Tag(Command):
|
||||
skip_translations, tx_resource, tx_min_perc):
|
||||
major, minor, patch = _split_version(version)
|
||||
Check.perform_basic_checks(src_dir)
|
||||
release_branch = Check.perform_version_checks(version, src_dir, release_branch)
|
||||
Check.perform_version_checks(version, src_dir, release_branch)
|
||||
Check.check_gnupg()
|
||||
sign_key = GPGSign.get_secret_key(sign_key)
|
||||
|
||||
@@ -576,7 +575,7 @@ class Tag(Command):
|
||||
commit=True, yes=yes)
|
||||
|
||||
changelog = re.search(rf'^## ({major}\.{minor}\.{patch} \(.*?\)\n\n+.+?)\n\n+## ',
|
||||
(Path(src_dir) / 'CHANGELOG.md').read_text("UTF-8"), re.MULTILINE | re.DOTALL)
|
||||
(Path(src_dir) / 'CHANGELOG.md').read_text(), re.MULTILINE | re.DOTALL)
|
||||
if not changelog:
|
||||
raise Error(f'No changelog entry found for version {version}.')
|
||||
changelog = 'Release ' + changelog.group(1)
|
||||
@@ -828,8 +827,6 @@ class Build(Command):
|
||||
|
||||
if appimage:
|
||||
cmake_opts.append('-DKEEPASSXC_DIST_TYPE=AppImage')
|
||||
# Force install prefix to ensure proper AppDir structure for linuxdeploy
|
||||
install_prefix = '/usr'
|
||||
|
||||
with tempfile.TemporaryDirectory() as build_dir:
|
||||
logger.info('Configuring build...')
|
||||
@@ -847,7 +844,7 @@ class Build(Command):
|
||||
_run(['cmake', '--install', '.', '--strip',
|
||||
'--prefix', (app_dir.absolute() / install_prefix.lstrip('/')).as_posix()],
|
||||
cwd=build_dir, capture_output=False, **docker_args)
|
||||
shutil.copytree(app_dir, output_dir / app_dir.name, symlinks=True, dirs_exist_ok=True)
|
||||
shutil.copytree(app_dir, output_dir / app_dir.name, symlinks=True)
|
||||
|
||||
if appimage:
|
||||
self._build_linux_appimage(
|
||||
@@ -888,7 +885,7 @@ class Build(Command):
|
||||
_run(['linuxdeploy', '--plugin=qt', f'--appdir={app_dir}', f'--custom-apprun={app_run}',
|
||||
f'--desktop-file={desktop_file}', f'--icon-file={icon_file}',
|
||||
*[f'--executable={ex}' for ex in executables]],
|
||||
cwd=build_dir, capture_output=False, path=env_path, **docker_args, docker_privileged=True)
|
||||
cwd=build_dir, capture_output=False, path=env_path, **docker_args)
|
||||
|
||||
logger.debug('Running appimagetool...')
|
||||
appimage_name = f'KeePassXC-{version}-{platform_target}.AppImage'
|
||||
@@ -1199,9 +1196,9 @@ def main():
|
||||
Check.setup_arg_parser(check_parser)
|
||||
check_parser.set_defaults(_cmd=Check)
|
||||
|
||||
merge_parser = subparsers.add_parser('tag', help=Tag.__doc__)
|
||||
Tag.setup_arg_parser(merge_parser)
|
||||
merge_parser.set_defaults(_cmd=Tag)
|
||||
merge_parser = subparsers.add_parser('merge', help=Merge.__doc__)
|
||||
Merge.setup_arg_parser(merge_parser)
|
||||
merge_parser.set_defaults(_cmd=Merge)
|
||||
|
||||
build_parser = subparsers.add_parser('build', help=Build.__doc__)
|
||||
Build.setup_arg_parser(build_parser)
|
||||
|
||||
@@ -46,78 +46,9 @@
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="2.8.0" date="2025-01-01" type="development">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Placeholder for future release notes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.7.11" date="2025-11-23" type="stable">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add image, HTML, Markdown preview, and text editing support to inline attachment viewer [#12085, #12244, #12654]</li>
|
||||
<li>Add database merge confirmation dialog [#10173]</li>
|
||||
<li>Add option to auto-generate a password for new entries [#12593]</li>
|
||||
<li>Add support for group sync in KeeShare [#11593]</li>
|
||||
<li>Add {UUID} placeholder for use in references [#12511]</li>
|
||||
<li>Add “Wait for Enter” search option [#12263]</li>
|
||||
<li>Add keyboard shortcut to “Jump to Group” from search results [#12225]</li>
|
||||
<li>Add predefined search for TOTP entries [#12199]</li>
|
||||
<li>Add confirmation when closing database via ESC key [#11963]</li>
|
||||
<li>Add support for escaping placeholder expressions [#11904]</li>
|
||||
<li>Reduce tab indentation width in notes fields [#11919]</li>
|
||||
<li>Cap default Argon2 parallelism when creating a new database [#11853]</li>
|
||||
<li>Database lock after inactivity now enabled by default and set to 900 seconds [#12689, #12609]</li>
|
||||
<li>Copying TOTP now opens setup dialog if none is configured for entry [#12584]</li>
|
||||
<li>Make double click action configurable [#12322]</li>
|
||||
<li>Remove unused “Last Accessed” from GUI [#12602]</li>
|
||||
<li>Auto-Type: Add more granular confirmation settings [#12370]</li>
|
||||
<li>Auto-Type: Add URL typing preset and add copy options to menu [#12341]</li>
|
||||
<li>Browser: Do not allow sites automatically if entry added from browser extension [#12413]</li>
|
||||
<li>Browser: Add options to restrict exposed groups [#9852, #12119]</li>
|
||||
<li>Bitwarden Import: Add support for timestamps and password history [#12588]</li>
|
||||
<li>macOS: Add Liquid Glass icon [#12642]</li>
|
||||
<li>macOS: Remove theme-based menubar icon toggle [#12685]</li>
|
||||
<li>macOS: Add Window and Help menus [#12357]</li>
|
||||
<li>Windows: Add option to add KeePassXC to PATH during installation [#12171]</li>
|
||||
<li>Fix window geometry not being restored properly when KeePassXC starts in tray [#12683]</li>
|
||||
<li>Fix potential database truncation when using direct write save method with YubiKeys [#11841]</li>
|
||||
<li>Fix issue with database backup saving [#11874]</li>
|
||||
<li>Fix UI lockups during startup with multiple tabs [#12053]</li>
|
||||
<li>Fix keyboard shortcuts when menubar is hidden [#12431]</li>
|
||||
<li>Fix clipboard being cleared on exit even if no password was copied [#12603]</li>
|
||||
<li>Fix single-instance detection when username contains invalid filename characters [#12559]</li>
|
||||
<li>Fix “Search Wait for Enter” setting not being save [#12614]</li>
|
||||
<li>Fix hotkey accelerators not being escaped properly on database tabs [#12630]</li>
|
||||
<li>Fix confusing error if user cancels out of key file edit dialog [#12639]</li>
|
||||
<li>Fix issues with saved searches and “Press Enter to Search” option [#12314]</li>
|
||||
<li>Fix URL wildcard matching [#12257]</li>
|
||||
<li>Fix TOTP visibility on unlock and settings change [#12220]</li>
|
||||
<li>Fix KeeShare entries with reference attributes not updating [#11809]</li>
|
||||
<li>Fix sort order not being maintained when toggling filters in database reports [#11849]</li>
|
||||
<li>Fix several UI font and layout issues [#11967, #12102]</li>
|
||||
<li>Prevent mouse wheel scroll on edit username field [#12398]</li>
|
||||
<li>Improve base translation consistency [#12432]</li>
|
||||
<li>Improve inactivity timer [#12246]</li>
|
||||
<li>Documentation improvements [#12373, #12506]</li>
|
||||
<li>Browser: Fix ordering of clientDataJSON in Passkey response object [#12120]</li>
|
||||
<li>Browser: Fix URL matching for additional URLs [#12196]</li>
|
||||
<li>Browser: Fix group settings inheritance [#12368]</li>
|
||||
<li>Browser: Allow read-only native messaging config files [#12236]</li>
|
||||
<li>Browser: Optimise entry iteration in browser access control dialog [#11817]</li>
|
||||
<li>Browser: Fix “Do not ask permission for HTTP Basic Auth” option [#11871]</li>
|
||||
<li>Browser: Fix native messaging path for Tor Browser launcher on Linux [#12005]</li>
|
||||
<li>Auto-Type: Fix empty window behaviour [#12622]</li>
|
||||
<li>Auto-Type: Take delays into account when typing TOTP [#12691]</li>
|
||||
<li>SSH Agent: Fix out-of-memory crash with malformed SSH keys [#12606]</li>
|
||||
<li>CSV Import: Fix modified and creation time import [#12379]</li>
|
||||
<li>CSV Import: Fix duplication of root groups on import [#12240]</li>
|
||||
<li>Proton Pass Import: Fix email addresses not being imported when no username set [#11888]</li>
|
||||
<li>macOS: Fix secure input getting stuck [#11928]</li>
|
||||
<li>Windows: Prevent launch as SYSTEM user from MSI installer [#12705]</li>
|
||||
<li>Windows: Remove broken check for MSVC Redistributable from MSI installer [#11950]</li>
|
||||
<li>Linux: Fix startup delay due to StartupNotify setting in desktop file [#12306]</li>
|
||||
<li>Linux: Fix memory initialisation when --pw-stdin is used with a pipe [#12050]</li>
|
||||
<li>Placeholder for future release notes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
|
||||
@@ -1799,6 +1799,10 @@ Are you sure you want to continue with this file?.</source>
|
||||
<source>Press ESC again to close this database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file does not exist or is not accessible.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@@ -2608,10 +2612,6 @@ This is definitely a bug, please report it to the developers.</source>
|
||||
<source>Open database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to open %1. It either does not exist or is not accessible.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CSV file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "core/Global.h"
|
||||
#include "core/Resources.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/Totp.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
@@ -312,6 +311,9 @@ void AutoType::executeAutoTypeActions(const Entry* entry,
|
||||
// Restore executor mode
|
||||
m_executor->mode = mode;
|
||||
|
||||
int delay = qMax(100, config()->get(Config::AutoTypeStartDelay).toInt());
|
||||
Tools::wait(delay);
|
||||
|
||||
// Grab the current active window after everything settles
|
||||
if (window == 0) {
|
||||
window = m_plugin->activeWindow();
|
||||
@@ -343,8 +345,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry,
|
||||
break;
|
||||
}
|
||||
|
||||
// Retry wait delay
|
||||
Tools::wait(100);
|
||||
Tools::wait(delay);
|
||||
}
|
||||
|
||||
// Last action failed to complete, cancel the rest of the sequence
|
||||
@@ -545,14 +546,10 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
const int maxTypeDelay = 500;
|
||||
const int maxWaitDelay = 10000;
|
||||
const int maxRepetition = 100;
|
||||
int currentTypingDelay = qBound(0, config()->get(Config::AutoTypeDelay).toInt(), maxTypeDelay);
|
||||
int cumulativeDelay = qBound(0, config()->get(Config::AutoTypeStartDelay).toInt(), maxWaitDelay);
|
||||
|
||||
// Initial actions include start delay and initial inter-key delay
|
||||
QList<QSharedPointer<AutoTypeAction>> actions;
|
||||
actions << QSharedPointer<AutoTypeBegin>::create();
|
||||
actions << QSharedPointer<AutoTypeDelay>::create(currentTypingDelay, true);
|
||||
actions << QSharedPointer<AutoTypeDelay>::create(cumulativeDelay);
|
||||
actions << QSharedPointer<AutoTypeDelay>::create(qMax(0, config()->get(Config::AutoTypeDelay).toInt()), true);
|
||||
|
||||
// Replace escaped braces with a template for easier regex
|
||||
QString sequence = entrySequence;
|
||||
@@ -568,7 +565,7 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
// Group 1 = modifier key (opt)
|
||||
// Group 2 = full placeholder
|
||||
// Group 3 = inner placeholder (allows nested placeholders)
|
||||
// Group 4 = repeat / delay time (opt)
|
||||
// Group 4 = repeat (opt)
|
||||
// Group 5 = character
|
||||
QRegularExpression regex("([+%^#]*)(?:({((?>[^{}]+?|(?2))+?)(?:\\s+(\\d+))?})|(.))");
|
||||
auto results = regex.globalMatch(sequence);
|
||||
@@ -630,23 +627,19 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
}
|
||||
actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, delay, maxTypeDelay), true);
|
||||
} else if (placeholder == "delay") {
|
||||
// Mid typing delay (wait), repeat represents the desired delay in milliseconds
|
||||
// Mid typing delay (wait)
|
||||
if (repeat > maxWaitDelay) {
|
||||
error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder);
|
||||
return {};
|
||||
}
|
||||
cumulativeDelay += repeat;
|
||||
actions << QSharedPointer<AutoTypeDelay>::create(qBound(0, repeat, maxWaitDelay));
|
||||
} else if (placeholder == "clearfield") {
|
||||
// Platform-specific field clearing
|
||||
actions << QSharedPointer<AutoTypeClearField>::create();
|
||||
} else if (placeholder == "totp") {
|
||||
if (entry->hasValidTotp()) {
|
||||
// Calculate TOTP at the time of typing including delays
|
||||
bool isValid = false;
|
||||
auto time =
|
||||
Clock::currentSecondsSinceEpoch() + (cumulativeDelay + currentTypingDelay * actions.count()) / 1000;
|
||||
auto totp = Totp::generateTotp(entry->totpSettings(), &isValid, time);
|
||||
// Entry totp (requires special handling)
|
||||
QString totp = entry->totp();
|
||||
for (const auto& ch : totp) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
namespace
|
||||
{
|
||||
constexpr int clearFormsDelay = 30000;
|
||||
constexpr int fileExistsCheckInterval = 5000;
|
||||
|
||||
bool isQuickUnlockAvailable()
|
||||
{
|
||||
@@ -68,6 +69,16 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
m_ui->editPassword->setShowPassword(false);
|
||||
});
|
||||
|
||||
m_fileExistsTimer.setInterval(fileExistsCheckInterval);
|
||||
m_fileExistsTimer.setSingleShot(false);
|
||||
connect(&m_fileExistsTimer, &QTimer::timeout, this, [this] {
|
||||
if (!QFile::exists(m_filename)) {
|
||||
m_ui->messageWidget->showMessage(tr("The database file does not exist or is not accessible."),
|
||||
MessageWidget::Warning,
|
||||
fileExistsCheckInterval + 500);
|
||||
}
|
||||
});
|
||||
|
||||
QFont font;
|
||||
font.setPointSize(font.pointSize() + 4);
|
||||
font.setBold(true);
|
||||
@@ -215,6 +226,7 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
||||
}
|
||||
|
||||
if (isVisible()) {
|
||||
m_fileExistsTimer.start();
|
||||
m_hideTimer.stop();
|
||||
pollHardwareKey();
|
||||
}
|
||||
@@ -226,6 +238,8 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
||||
m_hideTimer.start();
|
||||
}
|
||||
|
||||
m_fileExistsTimer.stop();
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
if (type == QEvent::Hide) {
|
||||
m_deviceListener->deregisterAllHotplugCallbacks();
|
||||
|
||||
@@ -97,6 +97,7 @@ private:
|
||||
bool m_triedToQuit = false;
|
||||
QTimer m_hideTimer;
|
||||
QTimer m_hideNoHardwareKeysFoundTimer;
|
||||
QTimer m_fileExistsTimer;
|
||||
|
||||
Q_DISABLE_COPY(DatabaseOpenWidget)
|
||||
};
|
||||
|
||||
@@ -163,22 +163,29 @@ void DatabaseTabWidget::addDatabaseTab(const QString& filePath,
|
||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
|
||||
if (canonicalFilePath.isEmpty()) {
|
||||
emit messageGlobal(tr("Failed to open %1. It either does not exist or is not accessible.").arg(cleanFilePath),
|
||||
MessageWidget::Error);
|
||||
return;
|
||||
// The file does not exist, revert back to the cleaned path for comparison
|
||||
canonicalFilePath = cleanFilePath;
|
||||
}
|
||||
|
||||
// Try to find an existing tab with the same file path
|
||||
for (int i = 0, c = count(); i < c; ++i) {
|
||||
auto* dbWidget = databaseWidgetFromIndex(i);
|
||||
Q_ASSERT(dbWidget);
|
||||
if (dbWidget
|
||||
&& dbWidget->database()->canonicalFilePath().compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
|
||||
dbWidget->performUnlockDatabase(password, keyfile);
|
||||
if (!inBackground) {
|
||||
// switch to existing tab if file is already open
|
||||
setCurrentIndex(indexOf(dbWidget));
|
||||
if (dbWidget) {
|
||||
auto dbFilePath = dbWidget->database()->canonicalFilePath();
|
||||
if (dbFilePath.isEmpty()) {
|
||||
// The file does not exist, revert back to the cleaned path for comparison
|
||||
dbFilePath = QDir::toNativeSeparators(dbWidget->database()->filePath());
|
||||
}
|
||||
if (dbFilePath.compare(canonicalFilePath, FILE_CASE_SENSITIVE) == 0) {
|
||||
// Attempt to unlock the database if password and/or keyfile is provided
|
||||
dbWidget->performUnlockDatabase(password, keyfile);
|
||||
if (!inBackground) {
|
||||
// switch to existing tab if file is already open
|
||||
setCurrentIndex(indexOf(dbWidget));
|
||||
}
|
||||
// Prevent opening a new tab for this file
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -714,7 +714,7 @@ void MainWindow::restoreConfigState()
|
||||
if (config()->get(Config::OpenPreviousDatabasesOnStartup).toBool()) {
|
||||
const QStringList fileNames = config()->get(Config::LastOpenedDatabases).toStringList();
|
||||
for (const QString& filename : fileNames) {
|
||||
if (!filename.isEmpty() && QFile::exists(filename)) {
|
||||
if (!filename.isEmpty()) {
|
||||
openDatabase(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "core/Config.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Resources.h"
|
||||
#include "core/Totp.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
@@ -76,9 +75,6 @@ void TestAutoType::init()
|
||||
association.window = "custom window";
|
||||
association.sequence = "{username}association{password}";
|
||||
m_entry1->autoTypeAssociations()->add(association);
|
||||
// Create a totp with a short time step to test delayed typing
|
||||
auto totpSettings = Totp::createSettings("NNSWK4DBONZXQYZB", Totp::DEFAULT_DIGITS, 2);
|
||||
m_entry1->setTotp(totpSettings);
|
||||
|
||||
m_entry2 = new Entry();
|
||||
m_entry2->setGroup(m_group);
|
||||
@@ -474,24 +470,3 @@ void TestAutoType::testAutoTypeEmptyWindowAssociation()
|
||||
assoc = m_entry6->autoTypeSequences("Some Other Window");
|
||||
QVERIFY(assoc.isEmpty());
|
||||
}
|
||||
|
||||
void TestAutoType::testAutoTypeTotpDelay()
|
||||
{
|
||||
// Get the TOTP time step in milliseconds
|
||||
auto totpStep = m_entry1->totpSettings()->step * 1000;
|
||||
auto sequence = QString("{TOTP} {DELAY %1}{TOTP}").arg(QString::number(totpStep * 2));
|
||||
|
||||
// Test 1: Sequence with a 3 second delay before TOTP
|
||||
m_autoType->performAutoTypeWithSequence(m_entry1, sequence);
|
||||
auto typedChars = m_test->actionChars();
|
||||
|
||||
// The typed TOTP should be different between the first and second one
|
||||
auto totpParts = m_test->actionChars().split(' ');
|
||||
QCOMPARE(totpParts.size(), 2);
|
||||
QCOMPARE(totpParts[0].size(), m_entry1->totpSettings()->digits);
|
||||
QCOMPARE(totpParts[1].size(), m_entry1->totpSettings()->digits);
|
||||
QVERIFY2(totpParts[0] != totpParts[1],
|
||||
QString("Typed TOTP (%1) should differ from current TOTP (%2) due to delay")
|
||||
.arg(totpParts[0], totpParts[1])
|
||||
.toLatin1());
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ private slots:
|
||||
void testAutoTypeSyntaxChecks();
|
||||
void testAutoTypeEffectiveSequences();
|
||||
void testAutoTypeEmptyWindowAssociation();
|
||||
void testAutoTypeTotpDelay();
|
||||
|
||||
private:
|
||||
AutoTypePlatformInterface* m_platform;
|
||||
|
||||
@@ -2464,6 +2464,41 @@ void TestGui::testMenuActionStates()
|
||||
QVERIFY(isActionEnabled("actionPasswordGenerator"));
|
||||
}
|
||||
|
||||
void TestGui::testOpenMissingDatabaseFile()
|
||||
{
|
||||
// Test that when trying to open a non-existent database file,
|
||||
// the unlock dialog is still shown (instead of auto-closing)
|
||||
// This allows user to retry when the file becomes available (e.g., cloud storage mounting)
|
||||
|
||||
const QString nonExistentPath = "/tmp/does_not_exist.kdbx";
|
||||
|
||||
// Ensure the file doesn't exist
|
||||
QFile::remove(nonExistentPath);
|
||||
QVERIFY(!QFile::exists(nonExistentPath));
|
||||
|
||||
// Record initial tab count
|
||||
int initialTabCount = m_tabWidget->count();
|
||||
|
||||
// Try to add database tab with non-existent file
|
||||
// This should NOT fail but should create a tab and show unlock dialog
|
||||
m_tabWidget->addDatabaseTab(nonExistentPath);
|
||||
|
||||
// Verify that a tab was created (unlock dialog shown)
|
||||
QCOMPARE(m_tabWidget->count(), initialTabCount + 1);
|
||||
|
||||
// Get the database widget for the new tab
|
||||
auto* dbWidget = m_tabWidget->currentDatabaseWidget();
|
||||
QVERIFY(dbWidget);
|
||||
|
||||
// Verify the database is in a state where it can be unlocked
|
||||
// (not closed/rejected due to missing file)
|
||||
QVERIFY(dbWidget->isLocked());
|
||||
|
||||
// Close the tab to clean up
|
||||
m_tabWidget->closeDatabaseTab(m_tabWidget->currentIndex());
|
||||
QCOMPARE(m_tabWidget->count(), initialTabCount);
|
||||
}
|
||||
|
||||
void TestGui::addCannedEntries()
|
||||
{
|
||||
// Find buttons
|
||||
|
||||
@@ -71,6 +71,7 @@ private slots:
|
||||
void testTrayRestoreHide();
|
||||
void testShortcutConfig();
|
||||
void testMenuActionStates();
|
||||
void testOpenMissingDatabaseFile();
|
||||
|
||||
private:
|
||||
void addCannedEntries();
|
||||
|
||||
Reference in New Issue
Block a user