├── .flatpak-manifest.json ├── .flatpak-manifest.json.license ├── .gitignore ├── .gitlab-ci.yml ├── .kde-ci.yml ├── CMakeLists.txt ├── KJournaldConfig.cmake.in ├── LICENSES ├── BSD-3-Clause.txt ├── CC0-1.0.txt ├── LGPL-2.1-or-later.txt └── MIT.txt ├── Messages.sh ├── README.md ├── autotests ├── CMakeLists.txt ├── containertesthelper.h ├── containertesthelper │ ├── CMakeLists.txt │ ├── containertesthelpertest.cpp │ └── containertesthelpertest.h ├── filtercriteriamodel │ ├── CMakeLists.txt │ ├── test_filtercriteriamodel.cpp │ └── test_filtercriteriamodel.h ├── journal.tar.gz ├── journal.tar.gz.license ├── journalexportformat_binary_example.export ├── journalexportformat_binary_example.export.license ├── journalexportformat_example.export ├── journalexportformat_example.export.license ├── localjournal │ ├── CMakeLists.txt │ ├── test_localjournal.cpp │ └── test_localjournal.h ├── remotejournal │ ├── CMakeLists.txt │ ├── test_remotejournal.cpp │ └── test_remotejournal.h ├── testdatalocation.h.inc ├── uniquequery │ ├── CMakeLists.txt │ ├── test_uniquequery.cpp │ └── test_uniquequery.h └── viewmodel │ ├── CMakeLists.txt │ ├── test_viewmodel.cpp │ └── test_viewmodel.h ├── browser ├── CMakeLists.txt ├── main.cpp ├── org.kde.kjournaldbrowser.appdata.xml └── org.kde.kjournaldbrowser.desktop ├── lib └── CMakeLists.txt ├── org └── kde │ ├── kjournald │ ├── CMakeLists.txt │ ├── bootmodel.cpp │ ├── bootmodel.h │ ├── bootmodel_p.h │ ├── colorizer.cpp │ ├── colorizer.h │ ├── fieldfilterproxymodel.cpp │ ├── fieldfilterproxymodel.h │ ├── filter.cpp │ ├── filter.h │ ├── filtercriteriamodel.cpp │ ├── filtercriteriamodel.h │ ├── filtercriteriamodel_p.h │ ├── flattenedfiltercriteriaproxymodel.cpp │ ├── flattenedfiltercriteriaproxymodel.h │ ├── ijournal.h │ ├── journaldexportreader.cpp │ ├── journaldexportreader.h │ ├── journaldhelper.cpp │ ├── journaldhelper.h │ ├── journalduniquequerymodel.cpp │ ├── journalduniquequerymodel.h │ ├── journalduniquequerymodel_p.h │ ├── journaldviewmodel.cpp │ ├── journaldviewmodel.h │ ├── journaldviewmodel_p.h │ ├── journaldviewmodelslidingwindow.cpp │ ├── journaldviewmodelslidingwindow.h │ ├── journaldviewmodelslidingwindow_p.h │ ├── localjournal.cpp │ ├── localjournal.h │ ├── localjournal_p.h │ ├── logentry.cpp │ ├── logentry.h │ ├── memory.h │ ├── systemdjournalremote.cpp │ ├── systemdjournalremote.h │ └── systemdjournalremote_p.h │ └── kjournaldbrowser │ ├── CMakeLists.txt │ ├── ColoredCheckbox.qml │ ├── FilterCriteriaView.qml │ ├── GlobalMenu.qml │ ├── LogLine.qml │ ├── LogView.qml │ ├── Main.qml │ ├── RemoteJournalConfigDialog.qml │ ├── TopMenuBar.qml │ ├── browserapplication.cpp │ ├── browserapplication.h │ ├── clipboardproxy.cpp │ ├── clipboardproxy.h │ ├── databaseprovider.cpp │ ├── databaseprovider.h │ ├── formatter.cpp │ ├── formatter.h │ ├── textsearch.cpp │ └── textsearch.h ├── po ├── ar │ └── kjournald.po ├── ast │ └── kjournald.po ├── ca │ └── kjournald.po ├── ca@valencia │ └── kjournald.po ├── cs │ └── kjournald.po ├── de │ └── kjournald.po ├── en_GB │ └── kjournald.po ├── eo │ └── kjournald.po ├── es │ └── kjournald.po ├── eu │ └── kjournald.po ├── fi │ └── kjournald.po ├── fr │ └── kjournald.po ├── gl │ └── kjournald.po ├── he │ └── kjournald.po ├── hi │ └── kjournald.po ├── ia │ └── kjournald.po ├── ie │ └── kjournald.po ├── is │ └── kjournald.po ├── it │ └── kjournald.po ├── ja │ └── kjournald.po ├── ka │ └── kjournald.po ├── ko │ └── kjournald.po ├── lt │ └── kjournald.po ├── lv │ └── kjournald.po ├── nl │ └── kjournald.po ├── pl │ └── kjournald.po ├── pt │ └── kjournald.po ├── pt_BR │ └── kjournald.po ├── ru │ └── kjournald.po ├── sa │ └── kjournald.po ├── sk │ └── kjournald.po ├── sl │ └── kjournald.po ├── sv │ └── kjournald.po ├── tr │ └── kjournald.po ├── uk │ └── kjournald.po ├── zh_CN │ └── kjournald.po └── zh_TW │ └── kjournald.po └── snapcraft.yaml /.flatpak-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.kde.kjournaldbrowser", 3 | "branch": "master", 4 | "runtime": "org.kde.Platform", 5 | "runtime-version": "6.9", 6 | "sdk": "org.kde.Sdk", 7 | "command": "kjournaldbrowser", 8 | "tags": ["nightly"], 9 | "desktop-file-name-suffix": " (Nightly)", 10 | "finish-args": [ 11 | "--share=network", 12 | "--share=ipc", 13 | "--socket=x11", 14 | "--socket=wayland", 15 | "--device=dri", 16 | "--filesystem=host", 17 | "--filesystem=/var/log/journal" 18 | ], 19 | "modules": [ 20 | { 21 | "name": "icon", 22 | "buildsystem": "simple", 23 | "build-commands": [ 24 | "mkdir -p /app/share/icons/hicolor/32x32/apps/", 25 | "install -D /usr/share/icons/breeze/apps/32/utilities-log-viewer.svg /app/share/icons/hicolor/32x32/apps/" 26 | ] 27 | }, 28 | { 29 | "name": "kirigami-addons", 30 | "config-opts": [ 31 | "-DBUILD_TESTING=OFF", 32 | "-DCMAKE_BUILD_TYPE=Release", 33 | "-DBUILD_WITH_QT6=ON" 34 | ], 35 | "buildsystem": "cmake-ninja", 36 | "sources": [ 37 | { 38 | "type": "git", 39 | "url": "https://invent.kde.org/libraries/kirigami-addons.git", 40 | "branch": "master" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "kjournald", 46 | "buildsystem": "cmake-ninja", 47 | "sources": [ 48 | { 49 | "type": "dir", 50 | "path": "." 51 | } 52 | ] 53 | } 54 | 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /.flatpak-manifest.json.license: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # SPDX-FileCopyrightText: 2022 Andreas Cord-Landwehr 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: none 3 | # 4 | # This file is used to ignore files which are generated 5 | # ---------------------------------------------------------------------------- 6 | TESTDATA/* 7 | CMakeLists.txt.user 8 | *~ 9 | *.autosave 10 | *.a 11 | *.core 12 | *.moc 13 | *.o 14 | *.obj 15 | *.orig 16 | *.rej 17 | *.so 18 | *.so.* 19 | *_pch.h.cpp 20 | *_resource.rc 21 | *.qm 22 | .#* 23 | *.*# 24 | core 25 | !core/ 26 | tags 27 | .DS_Store 28 | .directory 29 | *.debug 30 | Makefile* 31 | *.prl 32 | *.app 33 | moc_*.cpp 34 | ui_*.h 35 | qrc_*.cpp 36 | Thumbs.db 37 | *.res 38 | *.rc 39 | /.qmake.cache 40 | /.qmake.stash 41 | /build*/ 42 | # qtcreator generated files 43 | *.pro.user* 44 | **/.qmlls.ini 45 | 46 | # xemacs temporary files 47 | *.flc 48 | 49 | # Vim temporary files 50 | .*.swp 51 | 52 | /.clang-format 53 | /compile_commands.json 54 | 55 | # Visual Studio generated files 56 | *.ib_pdb_index 57 | *.idb 58 | *.ilk 59 | *.pdb 60 | *.sln 61 | *.suo 62 | *.vcproj 63 | *vcproj.*.*.user 64 | *.ncb 65 | *.sdf 66 | *.opensdf 67 | *.vcxproj 68 | *vcxproj.* 69 | 70 | # MinGW generated files 71 | *.Debug 72 | *.Release 73 | 74 | # Python byte code 75 | *.pyc 76 | 77 | # Binaries 78 | # -------- 79 | *.dll 80 | *.exe 81 | 82 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: none 3 | include: 4 | - project: sysadmin/ci-utilities 5 | file: 6 | - /gitlab-templates/linux-qt6.yml 7 | # - /gitlab-templates/linux-qt6-next.yml 8 | - /gitlab-templates/flatpak.yml 9 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['@all'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kcoreaddons': '@latest-kf6' 9 | 'frameworks/kcrash': '@latest-kf6' 10 | 'frameworks/ki18n': '@latest-kf6' 11 | 'frameworks/kirigami': '@latest-kf6' 12 | 'libraries/kirigami-addons': '@latest-kf6' 13 | 14 | Options: 15 | require-passing-tests-on: ['@all'] 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | cmake_minimum_required(VERSION 3.19) 5 | 6 | set(RELEASE_SERVICE_VERSION_MAJOR "25") 7 | set(RELEASE_SERVICE_VERSION_MINOR "07") 8 | set(RELEASE_SERVICE_VERSION_MICRO "70") 9 | set(KJOURNALD_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") 10 | set(KJOURNALD_SOVERSION 0) 11 | 12 | project(kjournald VERSION ${KJOURNALD_VERSION}) 13 | 14 | # generate QML LS integration 15 | set(QT_QML_GENERATE_QMLLS_INI ON) 16 | 17 | set(CMAKE_AUTOUIC ON) 18 | set(CMAKE_CXX_STANDARD 17) 19 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 20 | include(FeatureSummary) 21 | include(GenerateExportHeader) 22 | include(CMakePackageConfigHelpers) 23 | 24 | add_definitions( 25 | -DQT_DISABLE_DEPRECATED_BEFORE=0x060800 26 | -DQT_DEPRECATED_WARNINGS_SINCE=0x060800 27 | -DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x0060800 28 | -DKF_DEPRECATED_WARNINGS_SINCE=0x060800 29 | ) 30 | 31 | set(KF_VERSION 6.8.0) 32 | find_package(ECM ${KF_VERSION} NO_MODULE) 33 | set_package_properties(ECM PROPERTIES 34 | TYPE REQUIRED 35 | DESCRIPTION "Extra CMake Modules." 36 | URL "https://invent.kde.org/frameworks/extra-cmake-modules") 37 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 38 | include(ECMAddTests) 39 | include(ECMEnableSanitizers) 40 | include(ECMCoverageOption) 41 | include(ECMSetupVersion) 42 | include(ECMQtDeclareLoggingCategory) 43 | include(KDEInstallDirs6) 44 | include(KDECMakeSettings) 45 | include(KDEClangFormat) 46 | include(ECMQmlModule) 47 | 48 | ecm_setup_version(${KJOURNALD_VERSION} 49 | VARIABLE_PREFIX KJOURNALD 50 | VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kjournald_version.h" 51 | PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KJournaldConfigVersion.cmake" 52 | SOVERSION ${KJOURNALD_SOVERSION} 53 | ) 54 | 55 | find_package(KF6 ${KF_VERSION} REQUIRED COMPONENTS 56 | CoreAddons 57 | Crash 58 | Config 59 | I18n 60 | Kirigami 61 | ) 62 | add_definitions(-DTRANSLATION_DOMAIN=\"kjournald\") 63 | 64 | find_package(KF6KirigamiAddons 1.4.0 REQUIRED) 65 | 66 | find_package(PkgConfig REQUIRED) 67 | pkg_check_modules(SYSTEMD REQUIRED IMPORTED_TARGET libsystemd) 68 | 69 | find_package(Qt6 6.5.0 REQUIRED COMPONENTS 70 | Core 71 | Quick 72 | QuickControls2 73 | Test 74 | Widgets 75 | ) 76 | 77 | option(INSTALL_EXPERIMENTAL_HEADERS "Install headers for libkjournald (no stable API yet)" OFF) 78 | 79 | enable_testing() 80 | 81 | add_subdirectory(org/kde/kjournald/) 82 | add_subdirectory(org/kde/kjournaldbrowser/) 83 | add_subdirectory(browser) 84 | add_subdirectory(autotests) 85 | 86 | set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KJournald") 87 | configure_package_config_file( 88 | "${CMAKE_CURRENT_SOURCE_DIR}/KJournaldConfig.cmake.in" 89 | "${CMAKE_CURRENT_BINARY_DIR}/KJournaldConfig.cmake" 90 | INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" 91 | ) 92 | 93 | if(INSTALL_EXPERIMENTAL_HEADERS) 94 | install( 95 | FILES 96 | "${CMAKE_CURRENT_BINARY_DIR}/KJournaldConfig.cmake" 97 | "${CMAKE_CURRENT_BINARY_DIR}/KJournaldConfigVersion.cmake" 98 | DESTINATION "${CMAKECONFIG_INSTALL_DIR}" 99 | COMPONENT Devel 100 | ) 101 | 102 | install( 103 | FILES 104 | ${CMAKE_CURRENT_BINARY_DIR}/kjournald_version.h 105 | DESTINATION ${KDE_INSTALL_INCLUDEDIR} 106 | COMPONENT Devel 107 | ) 108 | 109 | install(EXPORT KJournaldTargets 110 | DESTINATION "${CMAKECONFIG_INSTALL_DIR}" 111 | FILE KJournaldTargets.cmake) 112 | endif() 113 | 114 | ki18n_install(po) 115 | 116 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 117 | -------------------------------------------------------------------------------- /KJournaldConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | @PACKAGE_INIT@ 5 | 6 | # Required components to use this library 7 | include(CMakeFindDependencyMacro) 8 | find_dependency(Qt6Core "@REQUIRED_QT_VERSION@") 9 | 10 | include("${CMAKE_CURRENT_LIST_DIR}/KJournaldTargets.cmake") 11 | 12 | @PACKAGE_INCLUDE_QCHTARGETS@ 13 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 26 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 18 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 20 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | # SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | $XGETTEXT $(find . -name \*.qml -or -name \*.cpp) -o $podir/kjournald.pot 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KJournald 2 | 3 | This project aims to provide an abstraction of the systemd's journald API in terms of QAbstractItemModel classes. The main purpose is to ease the integration of journald into Qt based applications (both QML and QtWidget). 4 | 5 | In addition to the library, the project provides a reference implementation of the API, called `kjournaldbrowser`. Even though that application provides a powerful journal database reader, we aim to do a clear split between reusable library and application logic. 6 | 7 | #### Screenshot 8 | 9 | ![](https://cdn.kde.org/screenshots/kjournald/kjournald.png) 10 | 11 | ## License 12 | All source files in the repository are supposed to be compatible with LGPL-2.1-or-later in order to easily move them between application and library part. 13 | 14 | - SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 15 | - SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 16 | 17 | ## Library Features 18 | - abstraction for unique query API, which can obtain unique values like list of all boot IDs, list of all units. 19 | - QAbstractItemModel for list of boots 20 | - QAbstractItemModel for full journald DB with filter options per boot and unit 21 | 22 | ### Library Usage 23 | 24 | In your CMakeLists.txt, add: 25 | 26 | ``` 27 | find_package(KJournald 0.0.2) 28 | target_link_libraries(mycoolapp 29 | PUBLIC KJournald) 30 | ``` 31 | 32 | ## kjournaldbrowser Features 33 | The browser provides a single window overview that can be configured to show a desired combination of systemd units and their prioritized messages. 34 | 35 | - the browser can read journald DB entries via the following ways: 36 | - direct access to a local journald DB folder 37 | - UI filters: 38 | - filter by systemd_unit (also via rainbow colors) 39 | - filter by boot 40 | - filter by priority (also via rainbow colors) 41 | - filter by kernel messages or not 42 | - additional log information: 43 | - time available as UTC 44 | - browsing 45 | - copy current view (CTRL+C) 46 | - key-based navigation 47 | - scroll forward: PAGE_DOWN 48 | - scroll backward: PAGE_UP 49 | - scroll to most recent entry: CTRL+PAGE_DOWN 50 | - scroll to the oldest entry: CTRL+PAGE_UP 51 | - highlight search strings 52 | - use "kjournaldbrowser -D " to directly open specific database folder 53 | 54 | ## Library Dependencies 55 | - Qt::Core, Qt::Quick 56 | - systemd 57 | -------------------------------------------------------------------------------- /autotests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | configure_file(testdatalocation.h.inc testdatalocation.h) 5 | configure_file(journalexportformat_example.export journalexportformat_example.export COPYONLY) 6 | configure_file(journalexportformat_binary_example.export journalexportformat_binary_example.export COPYONLY) 7 | 8 | add_custom_target(extract_testdata 9 | ALL 10 | COMMAND ${CMAKE_COMMAND} -E tar "xzf" ${CMAKE_CURRENT_SOURCE_DIR}/journal.tar.gz 11 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 12 | ) 13 | 14 | add_subdirectory(containertesthelper) 15 | add_subdirectory(localjournal) 16 | add_subdirectory(uniquequery) 17 | add_subdirectory(viewmodel) 18 | add_subdirectory(remotejournal) 19 | add_subdirectory(filtercriteriamodel) 20 | -------------------------------------------------------------------------------- /autotests/containertesthelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef CONTAINER_TEST_H 7 | #define CONTAINER_TEST_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | // TODO currently only the first mismatching element is returned: document or change to print all 14 | 15 | #define CONTAINER_EQUAL(actual, expected) \ 16 | do {\ 17 | if (!QTest::compare_helper(std::size(actual) >= std::size(expected), "Container elements differ", \ 18 | QTest::toString(std::size(expected)), QTest::toString(std::size(actual)), __FILE__, __LINE__)) \ 19 | return; \ 20 | auto mismatch = std::mismatch(actual.cbegin(), actual.cend(), expected.cbegin()); \ 21 | if (!QTest::compare_helper(mismatch.first == actual.cend(), "Container elements differ", \ 22 | QTest::toString(*mismatch.second), QTest::toString(*mismatch.first), __FILE__, __LINE__)) \ 23 | return; \ 24 | } while (false) 25 | 26 | /** 27 | * @param container a container poviding cbegin and cend; TODO std::cbegin 28 | */ 29 | #define CONTAINER_CONTAINS(container, expected) \ 30 | do {\ 31 | const bool found = std::any_of(container.cbegin(), container.cend(), [=](int value) {\ 32 | return value == expected;\ 33 | }); \ 34 | if (!QTest::qVerify(found, "Container does not contain element", #expected, __FILE__, __LINE__))\ 35 | return;\ 36 | } while (false) 37 | 38 | #define CONTAINER_IS_SUBSET_OF(subset, superset) \ 39 | do {\ 40 | const bool found = std::all_of(subset.cbegin(), subset.cend(), [=](auto value) { \ 41 | return superset.contains(value); \ 42 | }); \ 43 | if (!QTest::qVerify(found, "Container does not contain subset", #subset, __FILE__, __LINE__))\ 44 | return;\ 45 | } while (false) 46 | 47 | /** 48 | * Iterate over all elements of a list model @p model for certain @p role and check if it contains @p expected 49 | * @param container a container poviding cbegin and cend; TODO std::cbegin 50 | */ 51 | // TODO constexpr test of derived from model 52 | #define MODEL_CONTAINS(model, role, expected) \ 53 | do {\ 54 | bool found{ false }; \ 55 | for (int i = 0; i < model.rowCount(); ++i) { \ 56 | if (model.data(model.index(i, 0), role) == expected) { \ 57 | found = true; \ 58 | return; \ 59 | } \ 60 | } \ 61 | if (!QTest::qVerify(found, "Model does not contain element", #expected, __FILE__, __LINE__))\ 62 | return;\ 63 | } while (false) 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /autotests/containertesthelper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | containertesthelpertest.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test 7 | TEST_NAME test_containertesthelper 8 | ) 9 | -------------------------------------------------------------------------------- /autotests/containertesthelper/containertesthelpertest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "containertesthelpertest.h" 7 | #include "../containertesthelper.h" 8 | #include 9 | #include 10 | #include 11 | 12 | void ContainerTestHelperTest::testContainerTestChecks() 13 | { 14 | QVector a = {1, 1, 2, 3, 5, 8, 13, 21}; 15 | QSet b = {1, 4, 9, 16, 25}; 16 | QSet c = {4, 9}; 17 | 18 | // ContainerEq(container) 19 | // compare container a with b 20 | QVERIFY(!std::equal(a.cbegin(), a.cend(), b.cbegin())); 21 | // CONTAINER_EQUAL(a, b); 22 | 23 | // Contains(e) 24 | // check if container contains 2 25 | QVERIFY(std::any_of(a.cbegin(), a.cend(), [=](int value) { 26 | return value == 2; 27 | })); 28 | // CONTAINER_CONTAINS(a, 7); 29 | 30 | // Contains(e).Times(n) 31 | // check if container contains "1" two times 32 | QCOMPARE(std::accumulate(a.cbegin(), 33 | a.cend(), 34 | 0, 35 | [=](int acc, int value) { 36 | return acc += (value == 1 ? 1 : 0); 37 | }), 38 | 2); 39 | 40 | // IsSubsetOf(a_container) 41 | // check that a is not a subset of b 42 | QVERIFY(!std::all_of(a.cbegin(), a.cend(), [=](int value) { 43 | return b.contains(value); 44 | })); 45 | // check that c is subset of b 46 | CONTAINER_IS_SUBSET_OF(c, b); 47 | } 48 | 49 | QTEST_GUILESS_MAIN(ContainerTestHelperTest) 50 | -------------------------------------------------------------------------------- /autotests/containertesthelper/containertesthelpertest.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | class ContainerTestHelperTest : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | private Q_SLOTS: 15 | void testContainerTestChecks(); 16 | }; 17 | -------------------------------------------------------------------------------- /autotests/filtercriteriamodel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | test_filtercriteriamodel.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test kjournald 7 | TEST_NAME test_filtercriteriamodel 8 | ) 9 | -------------------------------------------------------------------------------- /autotests/filtercriteriamodel/test_filtercriteriamodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "test_filtercriteriamodel.h" 7 | #include "../containertesthelper.h" 8 | #include "../testdatalocation.h" 9 | #include "filtercriteriamodel.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // note: this test request several data from a real example journald database 19 | // you can check them by using "journalctl -D journal" and requesting the values 20 | // that are checked here 21 | 22 | void TestFilterCriteriaModel::basicTreeModelStructure() 23 | { 24 | FilterCriteriaModel model; 25 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 26 | 27 | // test failure handling for invalid journal: categories shall also be available there 28 | QTemporaryFile invalidJournal; // file is surely invalid 29 | QCOMPARE(model.setJournaldPath(invalidJournal.fileName()), false); 30 | QVERIFY(model.rowCount() > 0); 31 | 32 | // use extracted journal 33 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 34 | QVERIFY(model.rowCount() > 0); 35 | 36 | // check for all expected categories 37 | MODEL_CONTAINS(model, FilterCriteriaModel::Roles::CATEGORY, FilterCriteriaModel::Category::SYSTEMD_UNIT); 38 | MODEL_CONTAINS(model, FilterCriteriaModel::Roles::CATEGORY, FilterCriteriaModel::Category::EXE); 39 | MODEL_CONTAINS(model, FilterCriteriaModel::Roles::CATEGORY, FilterCriteriaModel::Category::PRIORITY); 40 | } 41 | 42 | void TestFilterCriteriaModel::standaloneTestSystemdUnitSelectionOptions() 43 | { 44 | FilterCriteriaModel model; 45 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 46 | 47 | // use extracted journal 48 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 49 | QVERIFY(model.rowCount() > 0); 50 | QVERIFY(model.entries(FilterCriteriaModel::Category::SYSTEMD_UNIT).count() > 0); 51 | 52 | { 53 | const auto container = model.entries(FilterCriteriaModel::Category::SYSTEMD_UNIT); 54 | QVERIFY(std::any_of(container.cbegin(), container.cend(), [=](std::pair value) { 55 | return value.first == "user@1000.service"; // arbitrary service from test journal 56 | })); 57 | } 58 | 59 | { // QAbstractItemModel interface acccess 60 | QModelIndex categoryIndex; 61 | for (int i = 0; i < model.rowCount(); ++i) { 62 | if (model.data(model.index(i, 0), FilterCriteriaModel::Roles::CATEGORY) == FilterCriteriaModel::Category::SYSTEMD_UNIT) { 63 | categoryIndex = model.index(i, 0); 64 | break; 65 | } 66 | } 67 | QVERIFY(categoryIndex.isValid()); 68 | QVERIFY(model.hasChildren(categoryIndex)); 69 | // value from first position 70 | QCOMPARE(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::TEXT).toString(), "busybox-klogd.service"); 71 | } 72 | } 73 | 74 | void TestFilterCriteriaModel::standaloneTestExeSelectionOptions() 75 | { 76 | FilterCriteriaModel model; 77 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 78 | 79 | // use extracted journal 80 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 81 | QVERIFY(model.rowCount() > 0); 82 | QVERIFY(model.entries(FilterCriteriaModel::Category::EXE).count() > 0); 83 | 84 | { 85 | const auto container = model.entries(FilterCriteriaModel::Category::EXE); 86 | QVERIFY(std::any_of(container.cbegin(), container.cend(), [=](std::pair value) { 87 | return value.first == "/lib/systemd/systemd"; // arbitrary service from test journal 88 | })); 89 | } 90 | 91 | { // QAbstractItemModel interface acccess 92 | QModelIndex categoryIndex; 93 | for (int i = 0; i < model.rowCount(); ++i) { 94 | if (model.data(model.index(i, 0), FilterCriteriaModel::Roles::CATEGORY) == FilterCriteriaModel::Category::EXE) { 95 | categoryIndex = model.index(i, 0); 96 | break; 97 | } 98 | } 99 | QVERIFY(categoryIndex.isValid()); 100 | QVERIFY(model.hasChildren(categoryIndex)); 101 | // value from first position 102 | QCOMPARE(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::TEXT).toString(), "/bin/bash.bash"); 103 | // all elements are disabled initially 104 | QCOMPARE(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED).toBool(), false); 105 | } 106 | } 107 | 108 | void TestFilterCriteriaModel::standaloneTestPrioritySelectionOptions() 109 | { 110 | FilterCriteriaModel model; 111 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 112 | 113 | // use extracted journal 114 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 115 | QVERIFY(model.rowCount() > 0); 116 | QVERIFY(model.entries(FilterCriteriaModel::Category::PRIORITY).count() > 0); 117 | 118 | { // direct access 119 | const auto container = model.entries(FilterCriteriaModel::Category::PRIORITY); 120 | QVERIFY(std::any_of(container.cbegin(), container.cend(), [=](std::pair value) { 121 | return value.first == QString::number(2); // arbitrary priority from test journal 122 | })); 123 | } 124 | 125 | { // QAbstractItemModel interface acccess 126 | QModelIndex categoryIndex; 127 | for (int i = 0; i < model.rowCount(); ++i) { 128 | if (model.data(model.index(i, 0), FilterCriteriaModel::Roles::CATEGORY) == FilterCriteriaModel::Category::PRIORITY) { 129 | categoryIndex = model.index(i, 0); 130 | break; 131 | } 132 | } 133 | QVERIFY(categoryIndex.isValid()); 134 | QVERIFY(model.hasChildren(categoryIndex)); 135 | // at first position expect priority '0' / emergency level 136 | QCOMPARE(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::DATA).toString(), "0"); 137 | } 138 | 139 | { // QAbstractItemData::setData operations 140 | QModelIndex categoryIndex; 141 | for (int i = 0; i < model.rowCount(); ++i) { 142 | if (model.data(model.index(i, 0), FilterCriteriaModel::Roles::CATEGORY) == FilterCriteriaModel::Category::PRIORITY) { 143 | categoryIndex = model.index(i, 0); 144 | break; 145 | } 146 | } 147 | QVERIFY(categoryIndex.isValid()); 148 | QVERIFY(model.hasChildren(categoryIndex)); 149 | // already set, default beavhior is to return false 150 | // QVERIFY(model.setData(model.index(1, 0, categoryIndex), false, FilterCriteriaModel::Roles::SELECTED) == false); 151 | 152 | // select priority entry for level 1, check for result 153 | model.setData(model.index(1, 0, categoryIndex), true, FilterCriteriaModel::Roles::SELECTED); 154 | // setData shall always return false if no data is changed 155 | QVERIFY(model.setData(model.index(1, 0, categoryIndex), true, FilterCriteriaModel::Roles::SELECTED) == false); 156 | QCOMPARE(model.priorityFilter(), 1); 157 | QVERIFY(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == false); 158 | QVERIFY(model.data(model.index(1, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == true); 159 | 160 | // select priority entry for level 0, then again for 1 to check resetting behavior of level 1 161 | QVERIFY(model.setData(model.index(0, 0, categoryIndex), true, FilterCriteriaModel::Roles::SELECTED) == true); 162 | QCOMPARE(model.priorityFilter(), 0); 163 | QVERIFY(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == true); 164 | QVERIFY(model.data(model.index(1, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == false); 165 | 166 | QVERIFY(model.setData(model.index(1, 0, categoryIndex), true, FilterCriteriaModel::Roles::SELECTED) == true); 167 | QCOMPARE(model.priorityFilter(), 1); 168 | QVERIFY(model.data(model.index(0, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == false); 169 | QVERIFY(model.data(model.index(1, 0, categoryIndex), FilterCriteriaModel::Roles::SELECTED) == true); 170 | } 171 | } 172 | 173 | QTEST_GUILESS_MAIN(TestFilterCriteriaModel); 174 | -------------------------------------------------------------------------------- /autotests/filtercriteriamodel/test_filtercriteriamodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEST_FILTERCRITERIAMODEL_H 7 | #define TEST_FILTERCRITERIAMODEL_H 8 | 9 | #include 10 | 11 | class TestFilterCriteriaModel : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | /** 17 | * @brief Test basic assumptions about this model when loading a journal 18 | */ 19 | void basicTreeModelStructure(); 20 | 21 | // check for filter options availability 22 | void standaloneTestSystemdUnitSelectionOptions(); 23 | void standaloneTestExeSelectionOptions(); 24 | void standaloneTestPrioritySelectionOptions(); 25 | 26 | private: 27 | const QStringList mBoots{"68f2e61d061247d8a8ba0b8d53a97a52", "27acae2fe35a40ac93f9c7732c0b8e59", "2dbe99dd855049af8f2865c5da2b8fda"}; 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /autotests/journal.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kjournald/f1b1e1aec32ed7506fd42626bcb470c20457267d/autotests/journal.tar.gz -------------------------------------------------------------------------------- /autotests/journal.tar.gz.license: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: none 3 | 4 | -------------------------------------------------------------------------------- /autotests/journalexportformat_binary_example.export: -------------------------------------------------------------------------------- 1 | __CURSOR=s=4801b45403ee41f9bfc72b56ef154ecf;i=1799;b=750d24b817364f5ebc286c0b32df2ad0;m=a4d22d016;t=5c8678d812639;x=8420cef2a679132b 2 | __REALTIME_TIMESTAMP=1627721964791353 3 | __MONOTONIC_TIMESTAMP=44243800086 4 | _BOOT_ID=750d24b817364f5ebc286c0b32df2ad0 5 | _TRANSPORT=journal 6 | _UID=1000 7 | _GID=1000 8 | _CAP_EFFECTIVE=0 9 | _SELINUX_CONTEXT 10 | unconfined 11 | 12 | _AUDIT_LOGINUID=1000 13 | _SYSTEMD_OWNER_UID=1000 14 | _SYSTEMD_UNIT=user@1000.service 15 | _SYSTEMD_SLICE=user-1000.slice 16 | _MACHINE_ID=83a52f20bd334d7f82cb6c7db0b85681 17 | _HOSTNAME=behemoth 18 | _SYSTEMD_USER_SLICE=app.slice 19 | _AUDIT_SESSION=3 20 | _SYSTEMD_CGROUP=/user.slice/user-1000.slice/user@1000.service/app.slice/app-org.kde.yakuake-c0faec5b95cf49f6b49d3eb582fa7991.scope 21 | _SYSTEMD_USER_UNIT=app-org.kde.yakuake-c0faec5b95cf49f6b49d3eb582fa7991.scope 22 | _SYSTEMD_INVOCATION_ID=d8ff5db7d38e4274a5744b388a816ac6 23 | MESSAGE 24 | foo 25 | bar 26 | CODE_FILE= 27 | CODE_LINE=1 28 | CODE_FUNC= 29 | SYSLOG_IDENTIFIER=python3 30 | _COMM=python3 31 | _EXE=/usr/bin/python3.9 32 | _CMDLINE=python3 -c from systemd import journal; journal.send("foo\nbar") 33 | _PID=19336 34 | _SOURCE_REALTIME_TIMESTAMP=1627721964791314 35 | 36 | -------------------------------------------------------------------------------- /autotests/journalexportformat_binary_example.export.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: none 3 | -------------------------------------------------------------------------------- /autotests/journalexportformat_example.export: -------------------------------------------------------------------------------- 1 | __CURSOR=s=739ad463348b4ceca5a9e69c95a3c93f;i=4ece7;b=6c7c6013a26343b29e964691ff25d04c;m=4fc72436e;t=4c508a72423d9;x=d3e5610681098c10;p=system.journal 2 | __REALTIME_TIMESTAMP=1342540861416409 3 | __MONOTONIC_TIMESTAMP=21415215982 4 | _BOOT_ID=6c7c6013a26343b29e964691ff25d04c 5 | _TRANSPORT=syslog 6 | PRIORITY=4 7 | SYSLOG_FACILITY=3 8 | SYSLOG_IDENTIFIER=gdm-password] 9 | SYSLOG_PID=587 10 | MESSAGE=AccountsService-DEBUG(+): ActUserManager: ignoring unspecified session '8' since it's not graphical: Success 11 | _PID=587 12 | _UID=0 13 | _GID=500 14 | _COMM=gdm-session-wor 15 | _EXE=/usr/libexec/gdm-session-worker 16 | _CMDLINE=gdm-session-worker [pam/gdm-password] 17 | _AUDIT_SESSION=2 18 | _AUDIT_LOGINUID=500 19 | _SYSTEMD_CGROUP=/user/lennart/2 20 | _SYSTEMD_SESSION=2 21 | _SELINUX_CONTEXT=system_u:system_r:xdm_t:s0-s0:c0.c1023 22 | _SOURCE_REALTIME_TIMESTAMP=1342540861413961 23 | _MACHINE_ID=a91663387a90b89f185d4e860000001a 24 | _HOSTNAME=epsilon 25 | 26 | __CURSOR=s=739ad463348b4ceca5a9e69c95a3c93f;i=4ece8;b=6c7c6013a26343b29e964691ff25d04c;m=4fc72572f;t=4c508a7243799;x=68597058a89b7246;p=system.journal 27 | __REALTIME_TIMESTAMP=1342540861421465 28 | __MONOTONIC_TIMESTAMP=21415221039 29 | _BOOT_ID=6c7c6013a26343b29e964691ff25d04c 30 | _TRANSPORT=syslog 31 | PRIORITY=6 32 | SYSLOG_FACILITY=9 33 | SYSLOG_IDENTIFIER=/USR/SBIN/CROND 34 | SYSLOG_PID=8278 35 | MESSAGE=(root) CMD (run-parts /etc/cron.hourly) 36 | _PID=8278 37 | _UID=0 38 | _GID=0 39 | _COMM=run-parts 40 | _EXE=/usr/bin/bash 41 | _CMDLINE=/bin/bash /bin/run-parts /etc/cron.hourly 42 | _AUDIT_SESSION=8 43 | _AUDIT_LOGINUID=0 44 | _SYSTEMD_CGROUP=/user/root/8 45 | _SYSTEMD_SESSION=8 46 | _SELINUX_CONTEXT=system_u:system_r:crond_t:s0-s0:c0.c1023 47 | _SOURCE_REALTIME_TIMESTAMP=1342540861416351 48 | _MACHINE_ID=a91663387a90b89f185d4e860000001a 49 | _HOSTNAME=epsilon 50 | 51 | -------------------------------------------------------------------------------- /autotests/journalexportformat_example.export.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: none 3 | -------------------------------------------------------------------------------- /autotests/localjournal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | test_localjournal.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test kjournald 7 | TEST_NAME test_localjournal 8 | ) 9 | -------------------------------------------------------------------------------- /autotests/localjournal/test_localjournal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "test_localjournal.h" 7 | #include "../testdatalocation.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // note: this test request several data from a real example journald database 17 | // you can check them by using "journalctl -D journal" and requesting the values 18 | // that are checked here 19 | 20 | void TestLocalJournal::journalAccess() 21 | { 22 | LocalJournal journal(JOURNAL_LOCATION); 23 | 24 | QCOMPARE(journal.usage(), 12845056); 25 | } 26 | 27 | QTEST_GUILESS_MAIN(TestLocalJournal); 28 | -------------------------------------------------------------------------------- /autotests/localjournal/test_localjournal.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEST_LOCALJOURNAL_H 7 | #define TEST_LOCALJOURNAL_H 8 | 9 | #include 10 | 11 | class TestLocalJournal : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | void journalAccess(); 17 | 18 | private: 19 | const QStringList mBoots{"68f2e61d061247d8a8ba0b8d53a97a52", "27acae2fe35a40ac93f9c7732c0b8e59", "2dbe99dd855049af8f2865c5da2b8fda"}; 20 | }; 21 | #endif 22 | -------------------------------------------------------------------------------- /autotests/remotejournal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | test_remotejournal.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test kjournald PkgConfig::SYSTEMD 7 | TEST_NAME test_remotejournal 8 | ) 9 | -------------------------------------------------------------------------------- /autotests/remotejournal/test_remotejournal.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEST_REMOTEJOURNAL_H 7 | #define TEST_REMOTEJOURNAL_H 8 | 9 | #include 10 | 11 | class TestRemoteJournal : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | // parser tests 17 | void exportFormatReaderBasicAccess(); 18 | void exportFormatReaderBinaryMessageAccess(); 19 | 20 | void systemdJournalRemoteJournalFromFile(); 21 | void systemdJournalRemoteJournalFromLocalhost(); 22 | }; 23 | #endif 24 | -------------------------------------------------------------------------------- /autotests/testdatalocation.h.inc: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TESDATALOCATION_H 7 | #define TESDATALOCATION_H 8 | 9 | static constexpr const char* JOURNAL_LOCATION("@CMAKE_CURRENT_BINARY_DIR@/journal/"); 10 | static constexpr const char* JOURNAL_EXPORT_FORMAT_EXAMPLE("@CMAKE_CURRENT_BINARY_DIR@/journalexportformat_example.export"); 11 | static constexpr const char* JOURNAL_EXPORT_FORMAT_BINARY_EXAMPLE("@CMAKE_CURRENT_BINARY_DIR@/journalexportformat_binary_example.export"); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /autotests/uniquequery/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | test_uniquequery.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test kjournald 7 | TEST_NAME test_uniquequery 8 | ) 9 | -------------------------------------------------------------------------------- /autotests/uniquequery/test_uniquequery.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "test_uniquequery.h" 7 | #include "../testdatalocation.h" 8 | #include "journalduniquequerymodel.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // note: this test request several data from a real example journald database 18 | // you can check them by using "journalctl -D journal" and requesting the values 19 | // that are checked here 20 | 21 | void TestUniqueQuery::journalAccess() 22 | { 23 | JournaldUniqueQueryModel model; 24 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 25 | 26 | // test failure handling for invalid journal 27 | QTemporaryFile invalidJournal; // file is surely invalid 28 | QCOMPARE(model.setJournaldPath(invalidJournal.fileName()), false); 29 | 30 | // use extracted journal 31 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 32 | } 33 | 34 | void TestUniqueQuery::boots() 35 | { 36 | JournaldUniqueQueryModel model; 37 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 38 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 39 | 40 | model.setFieldString("_BOOT_ID"); 41 | QCOMPARE(model.fieldString(), "_BOOT_ID"); 42 | model.setField(JournaldHelper::Field::_BOOT_ID); 43 | QCOMPARE(model.fieldString(), "_BOOT_ID"); 44 | QCOMPARE(model.rowCount(), 3); 45 | 46 | // check one example value 47 | QStringList values; 48 | for (int i = 0; i < model.rowCount(); ++i) { 49 | values.append(model.data(model.index(i, 0), JournaldUniqueQueryModel::FIELD).toString()); 50 | } 51 | QVERIFY(values.contains("2dbe99dd855049af8f2865c5da2b8fda")); 52 | } 53 | 54 | void TestUniqueQuery::systemdUnits() 55 | { 56 | JournaldUniqueQueryModel model; 57 | QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal); 58 | QCOMPARE(model.setJournaldPath(JOURNAL_LOCATION), true); 59 | 60 | model.setFieldString("_SYSTEMD_UNIT"); 61 | QCOMPARE(model.fieldString(), "_SYSTEMD_UNIT"); 62 | model.setField(JournaldHelper::Field::_SYSTEMD_UNIT); 63 | QCOMPARE(model.fieldString(), "_SYSTEMD_UNIT"); 64 | QCOMPARE(model.rowCount(), 17); 65 | 66 | QStringList values; 67 | for (int i = 0; i < model.rowCount(); ++i) { 68 | values.append(model.data(model.index(i, 0), JournaldUniqueQueryModel::FIELD).toString()); 69 | } 70 | QVERIFY(values.contains("systemd-journald.service")); 71 | } 72 | 73 | QTEST_GUILESS_MAIN(TestUniqueQuery); 74 | -------------------------------------------------------------------------------- /autotests/uniquequery/test_uniquequery.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEST_UNIQUEQUERY_H 7 | #define TEST_UNIQUEQUERY_H 8 | 9 | #include 10 | 11 | class TestUniqueQuery : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | void journalAccess(); 17 | void boots(); 18 | void systemdUnits(); 19 | }; 20 | #endif 21 | -------------------------------------------------------------------------------- /autotests/viewmodel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | ecm_add_test( 5 | test_viewmodel.cpp 6 | LINK_LIBRARIES Qt::Core Qt::Quick Qt::Test kjournald 7 | TEST_NAME test_viewmodel 8 | ) 9 | 10 | target_include_directories(test_viewmodel PRIVATE 11 | ${CMAKE_SOURCE_DIR}/org/kde/kjournald 12 | ) 13 | -------------------------------------------------------------------------------- /autotests/viewmodel/test_viewmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEST_VIEWMODEL_H 7 | #define TEST_VIEWMODEL_H 8 | 9 | #include 10 | 11 | class TestViewModel : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | void journalAccess(); 17 | void rowAccess(); 18 | void bootFilter(); 19 | void unitFilter(); 20 | void showKernelMessages(); 21 | void closestIndexForDateComputation(); 22 | /** 23 | * Check that exactly the full size of the journal is read and not more 24 | */ 25 | void readFullJournal(); 26 | /** 27 | * Reset model by changing the boot ID and and test that cursor for head/tail are updated accordingly 28 | */ 29 | void resetModelHeadAndTailCursorTest(); 30 | /** 31 | * Search mechanism with automatic fetching 32 | */ 33 | void stringSearch(); 34 | 35 | private: 36 | const QStringList mBoots{"68f2e61d061247d8a8ba0b8d53a97a52", "27acae2fe35a40ac93f9c7732c0b8e59", "2dbe99dd855049af8f2865c5da2b8fda"}; 37 | }; 38 | #endif 39 | -------------------------------------------------------------------------------- /browser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | include_directories( 5 | ${CMAKE_SOURCE_DIR}/lib 6 | ${CMAKE_BINARY_DIR}/lib 7 | ${CMAKE_BINARY_DIR} 8 | ) 9 | 10 | add_executable(kjournaldbrowser 11 | main.cpp 12 | ) 13 | 14 | target_compile_definitions(kjournaldbrowser 15 | PRIVATE $<$,$>:QT_QML_DEBUG>) 16 | 17 | target_link_libraries(kjournaldbrowser 18 | LINK_PUBLIC 19 | Qt::Core 20 | Qt::Quick 21 | Qt::QuickControls2 22 | Qt::Widgets # because of QApplication and QQC2 style integration 23 | KF6::I18n 24 | KF6::I18nQml 25 | KF6::CoreAddons 26 | KF6::Crash 27 | kjournald 28 | LINK_PRIVATE 29 | kjournaldbrowser_backing 30 | kjournaldbrowser_backingplugin 31 | ) 32 | install(TARGETS kjournaldbrowser DESTINATION bin) 33 | install(PROGRAMS org.kde.kjournaldbrowser.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 34 | install(FILES org.kde.kjournaldbrowser.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) 35 | -------------------------------------------------------------------------------- /browser/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "filtercriteriamodel.h" 7 | #include "kjournald_version.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | Q_IMPORT_PLUGIN(org_kde_kjournaldbrowserPlugin) 24 | 25 | int main(int argc, char *argv[]) 26 | { 27 | QApplication app(argc, argv); 28 | QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("utilities-log-viewer"))); 29 | app.setOrganizationName("KDE"); 30 | 31 | KCrash::initialize(); 32 | 33 | // use org.kde.desktop style unless another style is forced 34 | if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) { 35 | QQuickStyle::setStyle(QStringLiteral("org.kde.desktop")); 36 | } 37 | 38 | KLocalizedString::setApplicationDomain("kjournald"); 39 | static KAboutData aboutData(QStringLiteral("kjournald"), 40 | i18nc("@title Displayed program name", "KJournald Browser"), 41 | KJOURNALD_VERSION_STRING, 42 | i18nc("@title KAboutData: short program description", "Viewer for Journald logs"), 43 | KAboutLicense::LGPL_V2_1, 44 | i18nc("@info:credit", "(c) 2021-2025 The KJournald Developers"), 45 | i18nc("@title Short program description", "Viewer for Journald databases, which are generated by the Journald logging tool.")); 46 | aboutData.setProgramLogo(app.windowIcon()); 47 | aboutData.addAuthor(i18nc("@info:credit Developer name", "Andreas Cord-Landwehr"), 48 | i18nc("@info:credit Role", "Original Author"), 49 | QStringLiteral("cordlandwehr@kde.org")); 50 | aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); 51 | aboutData.setDesktopFileName(QStringLiteral("org.kde.kjournaldbrowser")); 52 | aboutData.setProductName("kjournald"); 53 | aboutData.setBugAddress("submit@bugs.kde.org"); 54 | KAboutData::setApplicationData(aboutData); 55 | 56 | FilterCriteriaModel filterCriteriaModel; 57 | 58 | QCommandLineParser parser; 59 | parser.setApplicationDescription("Journald Log Viewer"); 60 | parser.addHelpOption(); 61 | parser.addVersionOption(); 62 | const QCommandLineOption pathOption("D", "Path to journald database folder", "path"); 63 | parser.addOption(pathOption); 64 | parser.process(app); 65 | 66 | QString initialJournalPath; 67 | if (parser.isSet(pathOption)) { 68 | initialJournalPath = parser.value(pathOption); 69 | } 70 | 71 | QQmlApplicationEngine engine; 72 | const QVariantMap initialProperties = {{"filterModel", QVariant::fromValue(&filterCriteriaModel)}, {"initialJournalPath", initialJournalPath}}; 73 | engine.setInitialProperties(initialProperties); 74 | KLocalization::setupLocalizedContext(&engine); 75 | const QUrl url(QStringLiteral("qrc:/qt/qml/org/kde/kjournaldbrowser/Main.qml")); 76 | QObject::connect( 77 | &engine, 78 | &QQmlApplicationEngine::objectCreated, 79 | &app, 80 | [url](QObject *obj, const QUrl &objUrl) { 81 | if (!obj && url == objUrl) 82 | QCoreApplication::exit(-1); 83 | }, 84 | Qt::QueuedConnection); 85 | engine.load(url); 86 | 87 | return app.exec(); 88 | } 89 | -------------------------------------------------------------------------------- /browser/org.kde.kjournaldbrowser.desktop: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: none 2 | # SPDX-License-Identifier: CC0-1.0 3 | [Desktop Entry] 4 | Type=Application 5 | Exec=kjournaldbrowser %u 6 | Icon=utilities-log-viewer 7 | GenericName=Journald Browser 8 | GenericName[ar]=متصفح يوميات دي 9 | GenericName[ca]=Navegador de «journald» 10 | GenericName[ca@valencia]=Navegador de «journald» 11 | GenericName[cs]=Prohlížeč journald 12 | GenericName[de]=Journald-Browser 13 | GenericName[el]=Journald Browser 14 | GenericName[en_GB]=Journald Browser 15 | GenericName[eo]=Journald-Foliumilo 16 | GenericName[es]=Explorador de Journald 17 | GenericName[eu]=Journald arakatzailea 18 | GenericName[fi]=Journald-selain 19 | GenericName[fr]=Navigateur « Journald » 20 | GenericName[gl]=Navegador de Journald 21 | GenericName[he]=דפדפן Journald 22 | GenericName[hi]=जर्नलडी ब्राउज़र 23 | GenericName[ia]=Banigator de Journald 24 | GenericName[ie]=Navigator de Journald 25 | GenericName[is]=Journald vafri 26 | GenericName[it]=Navigatore journald 27 | GenericName[ka]=Journald -ის დათვალიერება 28 | GenericName[ko]=Journald 탐색기 29 | GenericName[lv]=Journald pārlūks 30 | GenericName[nl]=Browser van journald 31 | GenericName[pl]=Przeglądarka Journald 32 | GenericName[pt]=Navegador do Journald 33 | GenericName[pt_BR]=Navegador do journald 34 | GenericName[ro]=Navigator Journald 35 | GenericName[ru]=Браузер journald 36 | GenericName[sa]=जर्नल्ड् ब्राउज़र 37 | GenericName[sk]=Prehliadač Journald 38 | GenericName[sl]=Brskalnik journald 39 | GenericName[sv]=Journald-bläddrare 40 | GenericName[tr]=Journald Tarayıcısı 41 | GenericName[uk]=Навігатор для Journald 42 | GenericName[x-test]=xxJournald Browserxx 43 | GenericName[zh_CN]=Journald 浏览器 44 | GenericName[zh_TW]=Journald 日誌瀏覽器 45 | Terminal=false 46 | Name=Journald Browser 47 | Name[ar]=متصفح يوميات دي 48 | Name[ca]=Navegador de «journald» 49 | Name[ca@valencia]=Navegador de «journald» 50 | Name[cs]=Prohlížeč journald 51 | Name[de]=Journald-Browser 52 | Name[el]=Περιηγητής journald 53 | Name[en_GB]=Journald Browser 54 | Name[eo]=Journald Browser 55 | Name[es]=Explorador de Journald 56 | Name[eu]=Journald arakatzailea 57 | Name[fi]=Journald-selain 58 | Name[fr]=Navigateur « Journald » 59 | Name[gl]=Navegador de Journald 60 | Name[he]=דפדפן Journald 61 | Name[hi]=जर्नलडी ब्राउज़र 62 | Name[ia]=Journald Browser (Navigator de Journald) 63 | Name[ie]=Navigator de Journald 64 | Name[is]=Journald vafri 65 | Name[it]=Navigatore journald 66 | Name[ka]=Journald -ის დათვალიერება 67 | Name[ko]=Journald 탐색기 68 | Name[lv]=Journald pārlūks 69 | Name[nl]=Browser van journald 70 | Name[pl]=Przeglądarka Journald 71 | Name[pt]=Navegador do Journald 72 | Name[pt_BR]=Navegador do journald 73 | Name[ro]=Navigator Journald 74 | Name[ru]=Браузер journald 75 | Name[sa]=जर्नल्ड् ब्राउज़र 76 | Name[sk]=Prehliadač Journald 77 | Name[sl]=Brskalnik journald 78 | Name[sv]=Journald-bläddrare 79 | Name[tr]=Journald Tarayıcısı 80 | Name[uk]=Навігатор для Journald 81 | Name[x-test]=xxJournald Browserxx 82 | Name[zh_CN]=Journald 浏览器 83 | Name[zh_TW]=Journald 日誌瀏覽器 84 | Categories=Qt;KDE;System;Monitor; 85 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | add_library(kjournald SHARED) 5 | 6 | ecm_qt_declare_logging_category( 7 | kjournald 8 | HEADER kjournaldlib_log_general.h 9 | IDENTIFIER "KJOURNALDLIB_GENERAL" 10 | CATEGORY_NAME kjournald.lib.general 11 | DESCRIPTION "KJournald General Debug Logs" 12 | EXPORT kjournald 13 | ) 14 | ecm_qt_declare_logging_category( 15 | kjournald 16 | HEADER kjournaldlib_log_filtertrace.h 17 | IDENTIFIER "KJOURNALDLIB_FILTERTRACE" 18 | CATEGORY_NAME kjournald.lib.general 19 | DESCRIPTION "KJournald Trace Logs for Filter Operations" 20 | EXPORT kjournald 21 | ) 22 | ecm_qt_install_logging_categories( 23 | EXPORT kjournald 24 | FILE kjournald.categories 25 | DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}" 26 | ) 27 | 28 | generate_export_header(kjournald BASE_NAME KJournald) 29 | target_sources(kjournald PRIVATE 30 | colorizer.cpp 31 | colorizer.h 32 | filtercriteriamodel.cpp 33 | filtercriteriamodel.h 34 | filtercriteriamodel_p.h 35 | ijournal.h 36 | localjournal.cpp 37 | localjournal.h 38 | localjournal_p.h 39 | journaldexportreader.cpp 40 | journaldexportreader.h 41 | journaldhelper.cpp 42 | journaldhelper.h 43 | journalduniquequerymodel.cpp 44 | journalduniquequerymodel.h 45 | journalduniquequerymodel_p.h 46 | memory.h 47 | systemdjournalremote.cpp 48 | systemdjournalremote.h 49 | systemdjournalremote_p.h 50 | ) 51 | target_link_libraries(kjournald 52 | PRIVATE 53 | Qt6::Core 54 | Qt6::Quick 55 | PkgConfig::SYSTEMD 56 | KF6::I18n 57 | ) 58 | 59 | set_property(TARGET kjournald 60 | APPEND PROPERTY COMPILE_DEFINITIONS 61 | QT_NO_CAST_FROM_ASCII 62 | QT_NO_CAST_TO_ASCII) 63 | target_include_directories(kjournald PUBLIC 64 | $ 65 | $ 66 | ) 67 | set_target_properties(kjournald PROPERTIES 68 | VERSION ${KJOURNALD_VERSION} 69 | SOVERSION ${KJOURNALD_SOVERSION} 70 | EXPORT_NAME KJournald 71 | ) 72 | install(TARGETS kjournald 73 | EXPORT KJournaldTargets 74 | DESTINATION ${KDE_INSTALL_LIBDIR}) 75 | 76 | if(INSTALL_EXPERIMENTAL_HEADERS) 77 | install(FILES 78 | filter.h 79 | ijournal.h 80 | localjournal.h 81 | journaldhelper.h 82 | journaldviewmodel.h 83 | journalduniquequerymodel.h 84 | systemdjournalremote.h 85 | ${CMAKE_CURRENT_BINARY_DIR}/kjournald_export.h 86 | DESTINATION ${KDE_INSTALL_INCLUDEDIR} 87 | ) 88 | endif() 89 | -------------------------------------------------------------------------------- /org/kde/kjournald/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | add_library(kjournald SHARED) 5 | generate_export_header(kjournald BASE_NAME KJournald) 6 | target_link_directories(kjournald PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 7 | 8 | ecm_qt_declare_logging_category( 9 | kjournald 10 | HEADER kjournaldlib_log_general.h 11 | IDENTIFIER "KJOURNALDLIB_GENERAL" 12 | CATEGORY_NAME kjournald.lib.general 13 | DESCRIPTION "KJournald General Debug Logs" 14 | EXPORT kjournald 15 | ) 16 | ecm_qt_declare_logging_category( 17 | kjournald 18 | HEADER kjournaldlib_log_filtertrace.h 19 | IDENTIFIER "KJOURNALDLIB_FILTERTRACE" 20 | CATEGORY_NAME kjournald.lib.general 21 | DESCRIPTION "KJournald Trace Logs for Filter Operations" 22 | EXPORT kjournald 23 | ) 24 | ecm_qt_install_logging_categories( 25 | EXPORT kjournald 26 | FILE kjournald.categories 27 | DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}" 28 | ) 29 | 30 | qt6_add_qml_module(kjournald 31 | URI org.kde.kjournald 32 | DEPENDENCIES 33 | QtQuick 34 | ) 35 | 36 | target_sources(kjournald PRIVATE 37 | bootmodel.cpp 38 | bootmodel.h 39 | bootmodel_p.h 40 | fieldfilterproxymodel.cpp 41 | fieldfilterproxymodel.h 42 | filter.cpp 43 | filter.h 44 | flattenedfiltercriteriaproxymodel.cpp 45 | flattenedfiltercriteriaproxymodel.h 46 | journaldviewmodel.cpp 47 | journaldviewmodel.h 48 | journaldviewmodel_p.h 49 | colorizer.cpp 50 | colorizer.h 51 | filtercriteriamodel.cpp 52 | filtercriteriamodel.h 53 | filtercriteriamodel_p.h 54 | ijournal.h 55 | localjournal.cpp 56 | localjournal.h 57 | localjournal_p.h 58 | logentry.cpp 59 | logentry.h 60 | journaldexportreader.cpp 61 | journaldexportreader.h 62 | journaldhelper.cpp 63 | journaldhelper.h 64 | journalduniquequerymodel.cpp 65 | journalduniquequerymodel.h 66 | journalduniquequerymodel_p.h 67 | memory.h 68 | systemdjournalremote.cpp 69 | systemdjournalremote.h 70 | systemdjournalremote_p.h 71 | ) 72 | 73 | target_link_libraries(kjournald 74 | PRIVATE 75 | Qt6::Core 76 | Qt6::Quick 77 | PkgConfig::SYSTEMD 78 | KF6::I18n 79 | ) 80 | 81 | set_property(TARGET kjournald 82 | APPEND PROPERTY COMPILE_DEFINITIONS 83 | QT_NO_CAST_FROM_ASCII 84 | QT_NO_CAST_TO_ASCII) 85 | 86 | target_include_directories(kjournald PUBLIC 87 | $ 88 | $ 89 | ) 90 | 91 | ecm_finalize_qml_module(kjournald DESTINATION ${KDE_INSTALL_QMLDIR}) 92 | 93 | #set_target_properties(kjournald PROPERTIES 94 | # QT_QMLCACHEGEN_ARGUMENTS "--verbose" 95 | #) 96 | 97 | install(TARGETS kjournald 98 | EXPORT KJournaldTargets 99 | DESTINATION ${KDE_INSTALL_LIBDIR}) 100 | 101 | if(INSTALL_EXPERIMENTAL_HEADERS) 102 | install(FILES 103 | filter.h 104 | ijournal.h 105 | localjournal.h 106 | journaldhelper.h 107 | journaldviewmodel.h 108 | journalduniquequerymodel.h 109 | systemdjournalremote.h 110 | ${CMAKE_CURRENT_BINARY_DIR}/kjournald_export.h 111 | DESTINATION ${KDE_INSTALL_INCLUDEDIR} 112 | ) 113 | endif() 114 | -------------------------------------------------------------------------------- /org/kde/kjournald/bootmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "bootmodel.h" 7 | #include "bootmodel_p.h" 8 | #include "kjournaldlib_log_general.h" 9 | #include "localjournal.h" 10 | 11 | BootModelPrivate::BootModelPrivate(std::unique_ptr journal) 12 | : mJournal(std::move(journal)) 13 | { 14 | } 15 | 16 | BootModel::BootModel(QObject *parent) 17 | : QAbstractListModel(parent) 18 | , d(new BootModelPrivate(std::make_unique())) 19 | { 20 | d->mBootInfo = JournaldHelper::queryOrderedBootIds(*d->mJournal.get()); 21 | d->sort(Qt::SortOrder::DescendingOrder); 22 | } 23 | 24 | BootModel::BootModel(const QString &journaldPath, QObject *parent) 25 | : QAbstractListModel(parent) 26 | , d(new BootModelPrivate(std::make_unique(journaldPath))) 27 | { 28 | d->mBootInfo = JournaldHelper::queryOrderedBootIds(*d->mJournal.get()); 29 | d->sort(Qt::SortOrder::DescendingOrder); 30 | } 31 | 32 | BootModel::BootModel(std::unique_ptr journal, QObject *parent) 33 | : QAbstractListModel(parent) 34 | , d(new BootModelPrivate(std::move(journal))) 35 | { 36 | d->mBootInfo = JournaldHelper::queryOrderedBootIds(*d->mJournal.get()); 37 | d->sort(Qt::SortOrder::DescendingOrder); 38 | } 39 | 40 | BootModel::~BootModel() = default; 41 | 42 | bool BootModel::setJournaldPath(const QString &path) 43 | { 44 | qCDebug(KJOURNALDLIB_GENERAL) << "load journal from path" << path; 45 | bool success{true}; 46 | beginResetModel(); 47 | d->mJournaldPath = path; 48 | d->mJournal = std::make_unique(path); 49 | success = d->mJournal->isValid(); 50 | if (success) { 51 | d->mBootInfo = JournaldHelper::queryOrderedBootIds(*d->mJournal.get()); 52 | d->sort(Qt::SortOrder::DescendingOrder); 53 | } 54 | endResetModel(); 55 | return success; 56 | } 57 | 58 | QString BootModel::journaldPath() const 59 | { 60 | return d->mJournaldPath; 61 | } 62 | 63 | void BootModel::setSystemJournal() 64 | { 65 | qCDebug(KJOURNALDLIB_GENERAL) << "load system journal"; 66 | beginResetModel(); 67 | d->mJournaldPath = QString(); 68 | d->mJournal = std::make_unique(); 69 | d->mBootInfo = JournaldHelper::queryOrderedBootIds(*d->mJournal.get()); 70 | d->sort(Qt::SortOrder::DescendingOrder); 71 | endResetModel(); 72 | } 73 | 74 | QHash BootModel::roleNames() const 75 | { 76 | QHash roles; 77 | roles[BootModel::BOOT_ID] = "bootid"; 78 | roles[BootModel::CURRENT] = "current"; 79 | roles[BootModel::SINCE] = "since"; 80 | roles[BootModel::UNTIL] = "until"; 81 | roles[BootModel::DISPLAY_SHORT_UTC] = "displayshort_utc"; 82 | roles[BootModel::DISPLAY_SHORT_LOCALTIME] = "displayshort_localtime"; 83 | return roles; 84 | } 85 | 86 | int BootModel::rowCount(const QModelIndex &parent) const 87 | { 88 | return d->mBootInfo.size(); 89 | } 90 | 91 | QVariant BootModel::data(const QModelIndex &index, int role) const 92 | { 93 | if (index.row() < 0 || index.row() >= d->mBootInfo.size()) { 94 | return QVariant(); 95 | } 96 | switch (role) { 97 | case BootModel::BOOT_ID: 98 | return d->mBootInfo.at(index.row()).mBootId; 99 | case BootModel::SINCE: 100 | return d->mBootInfo.at(index.row()).mSince; 101 | case BootModel::UNTIL: 102 | return d->mBootInfo.at(index.row()).mUntil; 103 | case BootModel::DISPLAY_SHORT_UTC: 104 | return d->prettyPrintBoot(d->mBootInfo.at(index.row()), BootModelPrivate::TIME_FORMAT::UTC); 105 | case BootModel::DISPLAY_SHORT_LOCALTIME: 106 | return d->prettyPrintBoot(d->mBootInfo.at(index.row()), BootModelPrivate::TIME_FORMAT::LOCALTIME); 107 | case BootModel::CURRENT: 108 | return QVariant::fromValue(d->mJournal->currentBootId() == d->mBootInfo.at(index.row()).mBootId); 109 | } 110 | 111 | return QVariant(); 112 | } 113 | -------------------------------------------------------------------------------- /org/kde/kjournald/bootmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef BOOTMODEL_H 7 | #define BOOTMODEL_H 8 | 9 | #include "ijournal.h" 10 | #include "kjournald_export.h" 11 | #include 12 | #include 13 | #include 14 | 15 | class BootModelPrivate; 16 | 17 | /** 18 | * @brief Item model class that provides convienence access to boot information 19 | * 20 | * This QAbstractItemModel derived class provides a model/view abstraction for information of all 21 | * boots provided by a given journald database. 22 | */ 23 | class KJOURNALD_EXPORT BootModel : public QAbstractListModel 24 | { 25 | Q_OBJECT 26 | Q_PROPERTY(QString journalPath WRITE setJournaldPath READ journaldPath RESET setSystemJournal) 27 | 28 | QML_ELEMENT 29 | 30 | public: 31 | enum Roles { 32 | BOOT_ID = Qt::DisplayRole, //!< the journald ID of the boot 33 | SINCE = Qt::UserRole + 1, //!< the time of the earliest log entry for the boot 34 | UNTIL, //!< the time of the latest log entry for the boot 35 | DISPLAY_SHORT_UTC, //!< compact representation of the boot ID with all of its information: date, since-time, until-time, abbreviated hash 36 | DISPLAY_SHORT_LOCALTIME, //!< compact representation of the boot ID with all of its information: date, since-time, until-time, abbreviated hash 37 | CURRENT, //!< boolen role the tells if this boot is current for the system and thus expands 38 | }; 39 | Q_ENUM(Roles) 40 | 41 | /** 42 | * @brief Construct model from the default local jouurnald database 43 | * 44 | * @param parent the QObject parent 45 | */ 46 | explicit BootModel(QObject *parent = nullptr); 47 | 48 | /** 49 | * @brief Construct model from a journal database object 50 | * 51 | * @param journal object that contains a journald database object 52 | * @param parent the QObject parent 53 | */ 54 | BootModel(std::unique_ptr journal, QObject *parent = nullptr); 55 | 56 | /** 57 | * @brief Construct model from a journal database object 58 | * 59 | * This constructor works similar to "journalctl -D" and allows to use a custom path to the 60 | * journald database. 61 | * 62 | * @param journalPath path to journald database 63 | * @param parent the QObject parent 64 | */ 65 | BootModel(const QString &journalPath, QObject *parent = nullptr); 66 | 67 | /** 68 | * Destroys the boot model 69 | */ 70 | ~BootModel() override; 71 | 72 | /** 73 | * Reset model by reading from a new journald database 74 | * 75 | * @param path The path to directory that obtains the journald DB, usually ending with "journal". 76 | * @return true if path could be found and opened, otherwise false 77 | */ 78 | bool setJournaldPath(const QString &path); 79 | 80 | QString journaldPath() const; 81 | 82 | /** 83 | * Switch to local system's default journald database 84 | * 85 | * For details regarding preference, see journald documentation. 86 | */ 87 | void setSystemJournal(); 88 | 89 | /** 90 | * @copydoc QAbstractItemModel::roleNames() 91 | */ 92 | QHash roleNames() const override; 93 | 94 | /** 95 | * @copydoc QAbstractItemModel::rowCount() 96 | */ 97 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 98 | 99 | /** 100 | * @copydoc QAbstractItemModel::data() 101 | */ 102 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 103 | 104 | private: 105 | std::unique_ptr d; 106 | }; 107 | 108 | #endif // BOOTMODEL_H 109 | -------------------------------------------------------------------------------- /org/kde/kjournald/bootmodel_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef BOOT_MODEL_PRIVATE_H 7 | #define BOOT_MODEL_PRIVATE_H 8 | 9 | #include "journaldhelper.h" 10 | 11 | class BootModelPrivate 12 | { 13 | public: 14 | enum class TIME_FORMAT { 15 | UTC, 16 | LOCALTIME, 17 | }; 18 | 19 | using BootInfo = JournaldHelper::BootInfo; 20 | 21 | explicit BootModelPrivate(std::unique_ptr journal); 22 | 23 | static QString prettyPrintBoot(const BootInfo &bootInfo, TIME_FORMAT format); 24 | 25 | void sort(Qt::SortOrder order); 26 | 27 | QVector mBootInfo; 28 | QString mJournaldPath; 29 | std::unique_ptr mJournal; 30 | }; 31 | 32 | QString BootModelPrivate::prettyPrintBoot(const BootInfo &bootInfo, TIME_FORMAT format) 33 | { 34 | const QString id = bootInfo.mBootId.left(10); 35 | QString sinceTime; 36 | QString sinceDate; 37 | QString untilTime; 38 | QString untilDate; 39 | 40 | if (format == TIME_FORMAT::UTC) { 41 | sinceTime = bootInfo.mSince.toUTC().toString(QLatin1String("hh:mm")); 42 | sinceDate = bootInfo.mSince.toUTC().toString(QLatin1String("yyyy-MM-dd")); 43 | untilTime = bootInfo.mUntil.toUTC().toString(QLatin1String("hh:mm")); 44 | untilDate = bootInfo.mUntil.toUTC().toString(QLatin1String("yyyy-MM-dd")); 45 | } else { 46 | sinceTime = bootInfo.mSince.toString(QLatin1String("hh:mm")); 47 | sinceDate = bootInfo.mSince.toString(QLatin1String("yyyy-MM-dd")); 48 | untilTime = bootInfo.mUntil.toString(QLatin1String("hh:mm")); 49 | untilDate = bootInfo.mUntil.toString(QLatin1String("yyyy-MM-dd")); 50 | } 51 | if (sinceDate == untilDate) { 52 | return QString(QLatin1String("%1 %2-%3 [%4...]")).arg(sinceDate, sinceTime, untilTime, id); 53 | } else { 54 | return QString(QLatin1String("%1 %2-%3 %4 [%5...]")).arg(sinceDate, sinceTime, untilDate, untilTime, id); 55 | } 56 | } 57 | 58 | void BootModelPrivate::sort(Qt::SortOrder order) 59 | { 60 | std::sort(std::begin(mBootInfo), std::end(mBootInfo), [order](const BootInfo &left, const BootInfo &right) { 61 | if (order == Qt::AscendingOrder) { 62 | return left.mSince <= right.mSince; 63 | } else { 64 | return left.mSince > right.mSince; 65 | } 66 | }); 67 | } 68 | 69 | #endif // JOURNAL_H 70 | -------------------------------------------------------------------------------- /org/kde/kjournald/colorizer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "colorizer.h" 7 | #include 8 | #include 9 | 10 | struct LineColor { 11 | QColor foreground; 12 | QColor background; 13 | }; 14 | 15 | QColor Colorizer::color(const QString &key, COLOR_TYPE type) 16 | { 17 | QColor color; 18 | static QRandomGenerator sFixedSeedGenerator{1}; // used fixed seed to ensure that colors for same units never change 19 | static QMap sUnitToColorMap; 20 | 21 | auto needle = sUnitToColorMap.constFind(key); 22 | if (needle != sUnitToColorMap.cend()) { 23 | color = type == COLOR_TYPE::FOREGROUND ? needle->foreground : needle->background; 24 | } else { 25 | int hue = sFixedSeedGenerator.bounded(255); 26 | QColor foreground = QColor::fromHsl(hue, 220, 150); 27 | QColor background = QColor::fromHsl(hue, 200, 220); 28 | sUnitToColorMap[key] = {foreground, background}; 29 | color = type == COLOR_TYPE::FOREGROUND ? foreground : background; 30 | } 31 | return color; 32 | } 33 | -------------------------------------------------------------------------------- /org/kde/kjournald/colorizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef COLORIZER_H 7 | #define COLORIZER_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | 13 | class KJOURNALD_EXPORT Colorizer 14 | { 15 | public: 16 | enum class COLOR_TYPE { 17 | FOREGROUND, 18 | BACKGROUND, 19 | }; 20 | 21 | static QColor color(const QString &key, COLOR_TYPE = COLOR_TYPE::FOREGROUND); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /org/kde/kjournald/fieldfilterproxymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "fieldfilterproxymodel.h" 7 | #include "journaldviewmodel.h" 8 | #include "kjournaldlib_log_general.h" 9 | #include 10 | #include 11 | 12 | FieldFilterProxyModel::FieldFilterProxyModel(QObject *parent) 13 | : QSortFilterProxyModel(parent) 14 | , mComplete(false) 15 | , mFilterRole{JournaldViewModel::Roles::SYSTEMD_UNIT} 16 | { 17 | connect(this, &QSortFilterProxyModel::rowsInserted, this, &FieldFilterProxyModel::countChanged); 18 | connect(this, &QSortFilterProxyModel::rowsRemoved, this, &FieldFilterProxyModel::countChanged); 19 | } 20 | 21 | void FieldFilterProxyModel::setField(const QString &field) 22 | { 23 | JournaldViewModel::Roles role = mFilterRole; 24 | if (field == QLatin1String("_SYSTEMD_UNIT")) { 25 | role = JournaldViewModel::Roles::SYSTEMD_UNIT; 26 | } else if (field == QLatin1String("MESSAGE")) { 27 | role = JournaldViewModel::Roles::MESSAGE; 28 | } else if (field == QLatin1String("PRIORITY")) { 29 | role = JournaldViewModel::Roles::PRIORITY; 30 | } else if (field == QLatin1String("_BOOT_ID")) { 31 | role = JournaldViewModel::Roles::BOOT_ID; 32 | } else if (field == QLatin1String("DATE")) { 33 | role = JournaldViewModel::Roles::DATE; 34 | } 35 | if (role == mFilterRole) { 36 | // nothing to do 37 | return; 38 | } 39 | 40 | mFilterRole = role; 41 | if (mComplete) { 42 | QSortFilterProxyModel::setFilterRole(mFilterRole); 43 | } 44 | } 45 | 46 | QString FieldFilterProxyModel::filterString() const 47 | { 48 | return mFilter; 49 | } 50 | 51 | void FieldFilterProxyModel::setFilterString(const QString &filter) 52 | { 53 | mFilter = filter; 54 | setFilterFixedString(filter); 55 | } 56 | 57 | QJSValue FieldFilterProxyModel::get(int idx) const 58 | { 59 | QJSEngine *engine = qmlEngine(this); 60 | QJSValue value = engine->newObject(); 61 | if (idx >= 0 && idx < rowCount()) { 62 | QHash roles = roleNames(); 63 | QHashIterator it(roles); 64 | while (it.hasNext()) { 65 | it.next(); 66 | value.setProperty(QString::fromUtf8(it.value()), data(index(idx, 0), it.key()).toString()); 67 | } 68 | } 69 | return value; 70 | } 71 | 72 | void FieldFilterProxyModel::classBegin() 73 | { 74 | } 75 | 76 | void FieldFilterProxyModel::componentComplete() 77 | { 78 | mComplete = true; 79 | QSortFilterProxyModel::setFilterRole(mFilterRole); 80 | } 81 | 82 | int FieldFilterProxyModel::roleKey(const QByteArray &role) const 83 | { 84 | QHash roles = roleNames(); 85 | QHashIterator it(roles); 86 | while (it.hasNext()) { 87 | it.next(); 88 | if (it.value() == role) 89 | return it.key(); 90 | } 91 | return -1; 92 | } 93 | 94 | QHash FieldFilterProxyModel::roleNames() const 95 | { 96 | if (QAbstractItemModel *source = sourceModel()) { 97 | return source->roleNames(); 98 | } 99 | return QHash(); 100 | } 101 | -------------------------------------------------------------------------------- /org/kde/kjournald/fieldfilterproxymodel.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 4 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 5 | */ 6 | 7 | #ifndef FIELDFILTERPROXYMODEL_H 8 | #define FIELDFILTERPROXYMODEL_H 9 | 10 | #include "journaldviewmodel.h" 11 | #include "kjournald_export.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class KJOURNALD_EXPORT FieldFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus 18 | { 19 | Q_OBJECT 20 | Q_INTERFACES(QQmlParserStatus) 21 | 22 | Q_PROPERTY(int count READ rowCount NOTIFY countChanged) 23 | Q_PROPERTY(QString field WRITE setField) 24 | 25 | QML_ELEMENT 26 | 27 | public: 28 | explicit FieldFilterProxyModel(QObject *parent = 0); 29 | 30 | void setField(const QString &field); 31 | 32 | QString filterString() const; 33 | void setFilterString(const QString &filter); 34 | 35 | Q_INVOKABLE QJSValue get(int index) const; 36 | 37 | void classBegin() override; 38 | void componentComplete() override; 39 | 40 | Q_SIGNALS: 41 | void countChanged(); 42 | 43 | protected: 44 | int roleKey(const QByteArray &role) const; 45 | QHash roleNames() const override; 46 | 47 | private: 48 | bool mComplete; 49 | JournaldViewModel::Roles mFilterRole; 50 | QString mFilter; 51 | }; 52 | 53 | #endif // FIELDFILTERPROXYMODEL_H 54 | -------------------------------------------------------------------------------- /org/kde/kjournald/filter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2024 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "filter.h" 7 | 8 | std::optional Filter::priorityFilter() const 9 | { 10 | return mPriority; 11 | } 12 | 13 | int Filter::priorityFilterInt() const 14 | { 15 | return mPriority.value_or(0); 16 | } 17 | 18 | void Filter::setPriorityFilter(int priority) 19 | { 20 | if (priority >= 0) { 21 | mPriority = priority; 22 | } else { 23 | mPriority = std::nullopt; 24 | } 25 | } 26 | 27 | void Filter::resetPriorityFilter() 28 | { 29 | mPriority.reset(); 30 | } 31 | 32 | QStringList Filter::bootFilter() const 33 | { 34 | return mBootFilter; 35 | } 36 | 37 | void Filter::setBootFilter(const QStringList &boots) 38 | { 39 | mBootFilter = boots; 40 | } 41 | 42 | QStringList Filter::systemdUnitFilter() const 43 | { 44 | return mUnitFilter; 45 | } 46 | 47 | void Filter::setSystemdUnitFilter(const QStringList &units) 48 | { 49 | mUnitFilter = units; 50 | } 51 | 52 | QStringList Filter::exeFilter() const 53 | { 54 | return mExeFilter; 55 | } 56 | 57 | void Filter::setExeFilter(const QStringList &exe) 58 | { 59 | mExeFilter = exe; 60 | } 61 | 62 | bool Filter::areKernelMessagesEnabled() const 63 | { 64 | return mEnableKernelMessages; 65 | } 66 | 67 | void Filter::setKernelMessagesEnabled(bool enabled) 68 | { 69 | mEnableKernelMessages = enabled; 70 | } 71 | 72 | QDebug operator<<(QDebug debug, const Filter &c) 73 | { 74 | debug.nospace() << "filter(priority: " << c.priorityFilterInt() << ", boot: " << c.bootFilter() << ", exe: " << c.exeFilter() 75 | << ", unit: " << c.systemdUnitFilter() << ", kernel: " << c.areKernelMessagesEnabled() << ")"; 76 | return debug.space(); 77 | } 78 | -------------------------------------------------------------------------------- /org/kde/kjournald/filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2024 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef FILTER_H 7 | #define FILTER_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * Configuration struct for the filter configuration if a view model. 16 | */ 17 | class KJOURNALD_EXPORT Filter 18 | { 19 | Q_GADGET 20 | 21 | /** 22 | * filter for message priorities 23 | */ 24 | Q_PROPERTY(int priority READ priorityFilterInt WRITE setPriorityFilter RESET resetPriorityFilter) 25 | /** 26 | * filter list for systemd units 27 | **/ 28 | Q_PROPERTY(QStringList boots READ bootFilter WRITE setBootFilter) 29 | /** 30 | * filter list for systemd units 31 | **/ 32 | Q_PROPERTY(QStringList units READ systemdUnitFilter WRITE setSystemdUnitFilter) 33 | /** 34 | * filter list for executables (see journald '_EXE' field) 35 | **/ 36 | Q_PROPERTY(QStringList exes READ exeFilter WRITE setExeFilter) 37 | /** 38 | * if set to true, Kernel messages are added to the log output 39 | **/ 40 | Q_PROPERTY(bool kernel READ areKernelMessagesEnabled WRITE setKernelMessagesEnabled) 41 | 42 | QML_ANONYMOUS 43 | 44 | public: 45 | /** 46 | * \return the currently selected priority threshold for displayed log entries 47 | */ 48 | [[nodiscard]] std::optional priorityFilter() const; 49 | 50 | /** 51 | * \return the currently selected priority threshold for displayed log entries 52 | */ 53 | [[nodiscard]] int priorityFilterInt() const; 54 | 55 | /** 56 | * \brief Filter messages such that only messages with this and higher priority are provided 57 | * 58 | * \note Non-systemd services may not follow systemd's priority values 59 | * 60 | * \param priority the minimal priority for messages that shall be provided by model 61 | */ 62 | void setPriorityFilter(int priority); 63 | 64 | /** 65 | * reset priority filter 66 | */ 67 | void resetPriorityFilter(); 68 | 69 | /** 70 | * \return list of currently set boot ids for filtering 71 | */ 72 | [[nodiscard]] QStringList bootFilter() const; 73 | 74 | /** 75 | * \brief Configure for which boots messages shall be shown 76 | * 77 | * If no boot id is configured, this filter is deactivated. The given values are compared 78 | * to the _BOOT_ID journal value. 79 | * 80 | * \param bootFilter list of boot ids 81 | */ 82 | void setBootFilter(const QStringList &bootFilter); 83 | 84 | /** 85 | * \return the list of enabled system units 86 | */ 87 | [[nodiscard]] QStringList systemdUnitFilter() const; 88 | 89 | /** 90 | * \brief Configure for which systemd units messages shall be shown 91 | * 92 | * If no unit is configured, this filter is deactivated. The given values are compared 93 | * to the _SYSTEMD_UNIT journal value. 94 | * 95 | * \param units list of system units 96 | */ 97 | void setSystemdUnitFilter(const QStringList &units); 98 | 99 | /** 100 | * \return the list of enabled processes 101 | */ 102 | [[nodiscard]] QStringList exeFilter() const; 103 | 104 | /** 105 | * Configure for which executable messages shall be shown 106 | * 107 | * If no executable is configured, this filter is deactivated. The given values are compared 108 | * to the _EXE journal value. 109 | * 110 | * \param exeFilter list of executable paths 111 | */ 112 | void setExeFilter(const QStringList &exeFilter); 113 | 114 | /** 115 | * \return true if kernel log entries shall be included, otherwise false 116 | */ 117 | [[nodiscard]] bool areKernelMessagesEnabled() const; 118 | 119 | /** 120 | * \brief Configure if Kernel messages shall be included 121 | * 122 | * Per default, Kernel messages are deactivated. 123 | * 124 | * \param showKernelMessages parameter that defines if Kernel messages shall be shown 125 | */ 126 | void setKernelMessagesEnabled(bool showKernelMessages); 127 | 128 | private: 129 | std::optional mPriority{std::nullopt}; 130 | QStringList mBootFilter; 131 | QStringList mExeFilter; 132 | QStringList mUnitFilter; 133 | bool mEnableKernelMessages{false}; 134 | }; 135 | 136 | QDebug operator<<(QDebug dbg, const Filter &c); 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /org/kde/kjournald/filtercriteriamodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef FILTERCRITERIAMODEL_H 7 | #define FILTERCRITERIAMODEL_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class FilterCriteriaModelPrivate; 16 | 17 | /** 18 | * @brief The JournaldUniqueQueryModel class provides an item model abstraction for journald queryunique API 19 | * 20 | * This class is useful when creating a list model for contents like: 21 | * - boot ids in specific journal database 22 | * - systemd units in specific journal database 23 | * - priorities in specific journal database 24 | * 25 | * The model can be create from an arbitrary local journald database by defining a path or from the 26 | * system's default journal. Values can either be set by @a setFieldString for arbitrary values or in a 27 | * typesafe manner via @a setField for most common fields. 28 | */ 29 | class KJOURNALD_EXPORT FilterCriteriaModel : public QAbstractItemModel 30 | { 31 | Q_OBJECT 32 | Q_PROPERTY(QString journalPath WRITE setJournaldPath RESET setSystemJournal) 33 | /** 34 | * Filter for message priorities 35 | */ 36 | Q_PROPERTY(int priorityFilter READ priorityFilter NOTIFY priorityFilterChanged) 37 | /** 38 | * Filter list for systemd units 39 | **/ 40 | Q_PROPERTY(QStringList systemdUnitFilter READ systemdUnitFilter NOTIFY systemdUnitFilterChanged) 41 | /** 42 | * Filter list for executables (see journald '_EXE' field) 43 | **/ 44 | Q_PROPERTY(QStringList exeFilter READ exeFilter NOTIFY exeFilterChanged) 45 | /** 46 | * if set to true, Kernel messages are added to the log output 47 | **/ 48 | Q_PROPERTY(bool kernelFilter READ isKernelFilterEnabled NOTIFY kernelFilterChanged) 49 | 50 | QML_ELEMENT 51 | 52 | public: 53 | enum Category : quint8 { 54 | TRANSPORT = 0, 55 | PRIORITY = 1, 56 | SYSTEMD_UNIT = 2, 57 | EXE = 3, 58 | }; 59 | Q_ENUM(Category) 60 | 61 | enum Roles { 62 | TEXT = Qt::DisplayRole, 63 | LONGTEXT = Qt::ToolTipRole, 64 | SELECTED = Qt::CheckStateRole, 65 | CATEGORY = Qt::UserRole + 1, 66 | DATA = Qt::UserRole + 2, 67 | }; 68 | Q_ENUM(Roles) 69 | 70 | /** 71 | * @brief Create filter criteria model for the system journal 72 | * 73 | * This tree model provides a tree based structure for the most common filter options. 74 | * 75 | * @param parent the QObject parent 76 | */ 77 | explicit FilterCriteriaModel(QObject *parent = nullptr); 78 | 79 | /** 80 | * @brief Create filter criteria model 81 | * 82 | * This tree model provides a tree based structure for the most common filter options. 83 | * 84 | * @param journalPath path to the journald database 85 | * @param parent the QObject parent 86 | */ 87 | FilterCriteriaModel(const QString &journalPath, QObject *parent = nullptr); 88 | 89 | /** 90 | * @brief Destroys the object 91 | */ 92 | ~FilterCriteriaModel() override; 93 | 94 | /** 95 | * Reset model by reading from a new journald database 96 | * 97 | * @param path The path to directory that obtains the journald DB, usually ending with "journal". 98 | * @return true if path could be found and opened, otherwise false 99 | */ 100 | bool setJournaldPath(const QString &path); 101 | 102 | /** 103 | * Switch to local system's default journald database 104 | * 105 | * For details regarding preference, see journald documentation. 106 | */ 107 | void setSystemJournal(); 108 | 109 | /** 110 | * @return the currently selected priority threshold for displayed log entries 111 | */ 112 | int priorityFilter() const; 113 | 114 | /** 115 | * @return the list of enabled system units 116 | */ 117 | QStringList systemdUnitFilter() const; 118 | 119 | /** 120 | * @return the list of enabled processes 121 | */ 122 | QStringList exeFilter() const; 123 | 124 | /** 125 | * @return true if kernel log entries shall be shown, otherwise false 126 | */ 127 | bool isKernelFilterEnabled() const; 128 | 129 | /** 130 | * @copydoc QAbstractItemModel::rolesNames() 131 | */ 132 | QHash roleNames() const override; 133 | 134 | /** 135 | * @copydoc QAbstractItemModel::index() 136 | */ 137 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; 138 | 139 | /** 140 | * @copydoc QAbstractItemModel::parent() 141 | */ 142 | QModelIndex parent(const QModelIndex &index) const override; 143 | 144 | /** 145 | * @copydoc QAbstractItemModel::rowCount() 146 | */ 147 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 148 | 149 | /** 150 | * @copydoc QAbstractItemModel::columnCount() 151 | */ 152 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 153 | 154 | /** 155 | * @copydoc QAbstractItemModel::data() 156 | */ 157 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 158 | 159 | /** 160 | * @copydoc QAbstractItemModel::setData() 161 | */ 162 | Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 163 | 164 | /** 165 | * @brief Convenience method for access to list of all entries' data of @p category with their selected states 166 | */ 167 | QVector> entries(FilterCriteriaModel::Category category) const; 168 | 169 | Q_SIGNALS: 170 | void priorityFilterChanged(int priority); 171 | void systemdUnitFilterChanged(); 172 | void exeFilterChanged(); 173 | void kernelFilterChanged(); 174 | 175 | private: 176 | std::unique_ptr d; 177 | }; 178 | 179 | #endif 180 | -------------------------------------------------------------------------------- /org/kde/kjournald/filtercriteriamodel_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef FILTERCRITERIAMODEL_P_H 7 | #define FILTERCRITERIAMODEL_P_H 8 | 9 | #include "filtercriteriamodel.h" 10 | #include "ijournal.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class SelectionEntry 18 | { 19 | public: 20 | explicit SelectionEntry() = default; 21 | explicit SelectionEntry(const QString &text, 22 | const QVariant &data, 23 | FilterCriteriaModel::Category category, 24 | bool selected = false, 25 | std::shared_ptr parentItem = nullptr); 26 | 27 | void appendChild(std::shared_ptr child); 28 | 29 | std::shared_ptr child(int row); 30 | int childCount() const; 31 | int columnCount() const; 32 | QVariant data(FilterCriteriaModel::Roles role) const; 33 | bool setData(const QVariant &value, FilterCriteriaModel::Roles role); 34 | int row() const; 35 | std::shared_ptr parentItem(); 36 | 37 | private: 38 | std::vector> mChildItems; 39 | std::weak_ptr mParentItem; 40 | QString mText; //!< user formatted string 41 | QVariant mData; //!< verbatim string as needed for journald filtering 42 | bool mSelected{true}; 43 | FilterCriteriaModel::Category mCategory; 44 | }; 45 | 46 | class FilterCriteriaModelPrivate 47 | { 48 | public: 49 | FilterCriteriaModelPrivate(); 50 | ~FilterCriteriaModelPrivate(); 51 | /** 52 | * @brief clear all model data and read units, processes... from currently set journal 53 | */ 54 | void rebuildModel(); 55 | 56 | static QString mapPriorityToString(int priority); 57 | 58 | std::shared_ptr mJournal; 59 | std::shared_ptr mRootItem; 60 | std::optional mPriorityLevel; 61 | }; 62 | 63 | #endif // FILTERCRITERIAMODEL_P_H 64 | -------------------------------------------------------------------------------- /org/kde/kjournald/flattenedfiltercriteriaproxymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef FLATTENEDFILTERCRITERIAPROXYMODEL_H 7 | #define FLATTENEDFILTERCRITERIAPROXYMODEL_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | /** 14 | * @brief Linear list proxy model for QML integration of @see FilterCriteriaModel 15 | * 16 | * This proxy model takes a tree model based @see FilterCriteriaModel and provides its linearization as 17 | * a @a QAbstractItemModel. This easies integration with QML where a ListModel or Repeater based vew can be constructed 18 | * that emulates the behavior of such a tree model. 19 | * 20 | * Specifically, this model provides expansing and collapsing of first level nodes. 21 | * 22 | * @note the model is constructud to deeply integrate with the FilterCriteriaModel and thus requires guarantees 23 | * of that model (e.g. max-level is 2). 24 | */ 25 | class FlattenedFilterCriteriaProxyModel : public QAbstractListModel 26 | { 27 | Q_OBJECT 28 | 29 | /** 30 | * FilterCriteriaModel, which holds data of this proxy model 31 | */ 32 | Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged); 33 | 34 | QML_ELEMENT 35 | 36 | public: 37 | /** model roles **/ 38 | enum Roles { 39 | TEXT = Qt::DisplayRole, //!< the user visible (possibley abbreviated) text 40 | LONGTEXT = Qt::ToolTipRole, //!< the long text which typically is used for tooltips 41 | SELECTED = Qt::CheckStateRole, //!< boolean role that informs if element is selected 42 | INDENTATION = Qt::UserRole + 1, //!< provides indentation level based in source model tree structure 43 | EXPANDED = Qt::UserRole + 2, //!< for expansible nodes, provides state if node is expanded 44 | TYPE = Qt::UserRole + 3, //!< provides data type for nodes 45 | COLOR = Qt::UserRole + 4, //!< provides color state for node, if present 46 | }; 47 | Q_ENUM(Roles) 48 | 49 | enum DelgateType { 50 | SECTION, 51 | CHECKBOX, 52 | RADIOBUTTON, 53 | UNSELECT_OPTION, 54 | FIRST_LEVEL, 55 | }; 56 | Q_ENUM(DelgateType) 57 | 58 | /** 59 | * @copydoc QAbstractItemModel::roleNames() 60 | */ 61 | QHash roleNames() const override; 62 | 63 | /** 64 | * @brief setter for source model @p model 65 | */ 66 | void setSourceModel(QAbstractItemModel *model); 67 | 68 | /** 69 | * @return current sourde model of proxy 70 | */ 71 | QAbstractItemModel *sourceModel() const; 72 | 73 | /** 74 | * @copydoc QAbstractItemModel::rowCount() 75 | */ 76 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 77 | 78 | /** 79 | * @copydoc QAbstractItemModel::columnCount() 80 | */ 81 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 82 | 83 | /** 84 | * @copydoc QAbstractItemModel::index() 85 | */ 86 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; 87 | 88 | /** 89 | * @copydoc QAbstractItemModel::parent() 90 | */ 91 | QModelIndex parent(const QModelIndex &index) const override; 92 | 93 | /** 94 | * @copydoc QAbstractItemModel::data() 95 | */ 96 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 97 | 98 | /** 99 | * @copydoc QAbstractItemModel::setData() 100 | */ 101 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 102 | 103 | private Q_SLOTS: 104 | void handleSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); 105 | void handleSourceModelOnModelReset(); 106 | void handleSourceModelOnModelAboutToBeReset(); 107 | 108 | Q_SIGNALS: 109 | /** 110 | * @brief signal is fired when the source model was changed 111 | */ 112 | void sourceModelChanged(); 113 | 114 | private: 115 | /** 116 | * @brief Proxy data objects that store references to source model objects 117 | */ 118 | struct SourceIndex { 119 | QModelIndex mSourceIndex; 120 | bool mIsExpanded{false}; 121 | int mIndentation{0}; 122 | }; 123 | QAbstractItemModel *mSourceModel{nullptr}; 124 | QVector mMapToSourceIndex; 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /org/kde/kjournald/ijournal.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef IJOURNAL_H 7 | #define IJOURNAL_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | 13 | class sd_journal; 14 | 15 | /** 16 | * @brief Interface class for all journal types 17 | */ 18 | class KJOURNALD_EXPORT IJournal : public QObject 19 | { 20 | Q_OBJECT 21 | public: 22 | /** 23 | * @brief Construct journal object for system journald DB 24 | */ 25 | explicit IJournal() = default; 26 | 27 | /** 28 | * @brief Destroys the journal wrapper 29 | */ 30 | virtual ~IJournal() = default; 31 | 32 | /** 33 | * @brief Getter for raw sd_journ´al pointer 34 | * 35 | * This pointer can be nullptr if an error during opening of journal occured. Test 36 | * with @s isValid() before using. 37 | */ 38 | virtual sd_journal *sdJournal() const = 0; 39 | 40 | /** 41 | * @brief returns true if and only if the sd_journal pointer is valid 42 | * 43 | * This method shall be used to check of the journal is ready to be used 44 | */ 45 | virtual bool isValid() const = 0; 46 | 47 | /** 48 | * @return ID for the current boot (b0) of the system, empty string if none is current 49 | */ 50 | virtual QString currentBootId() const = 0; 51 | 52 | Q_SIGNALS: 53 | /** 54 | * @brief signal is fired when new entries are added to the journal 55 | * @param bootId the ID for the boot to which entries are added 56 | */ 57 | void journalUpdated(const QString &bootId); 58 | }; 59 | 60 | #endif // IJOURNAL_H 61 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldexportreader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "journaldexportreader.h" 7 | #include "kjournaldlib_log_general.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | JournaldExportReader::JournaldExportReader(QIODevice *device) 15 | : mDevice(device) 16 | { 17 | if (!mDevice || !mDevice->open(QIODevice::ReadOnly)) { 18 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open device for reading"; 19 | return; 20 | } 21 | } 22 | 23 | bool JournaldExportReader::atEnd() const 24 | { 25 | return mDevice->atEnd(); 26 | } 27 | 28 | // Format description: 29 | // - Two journal entries that follow each other are separated by a double newline. 30 | // - Journal fields consisting only of valid non-control UTF-8 codepoints are serialized as they are 31 | // (i.e. the field name, followed by '=', followed by field data), followed by a newline as 32 | // separator to the next field. Note that fields containing newlines cannot be formatted like 33 | // this. Non-control UTF-8 codepoints are the codepoints with value at or above 32 (' '), or 34 | // equal to 9 (TAB). 35 | // - Other journal fields are serialized in a special binary safe way: field name, followed by 36 | // newline, followed by a binary 64bit little endian size value, followed by the binary field 37 | // data, followed by a newline as separator to the next field. 38 | // - Entry metadata that is not actually a field is serialized like it was a field, but beginning 39 | // with two underscores. More specifically, __CURSOR=, __REALTIME_TIMESTAMP=, 40 | // __MONOTONIC_TIMESTAMP= are introduced this way. Note that these meta-fields are only generated 41 | // when actual journal files are serialized. They are omitted for entries that do not originate 42 | // from a journal file (for example because they are transferred for the first time to be stored 43 | // in one). Or in other words: if you are generating this format you shouldn't care about these 44 | // special double-underscore fields. But you might find them usable when you deserialize the 45 | // format generated by us. Additional fields prefixed with two underscores might be added later 46 | // on, your parser should skip over the fields it does not know. 47 | // - The order in which fields appear in an entry is undefined and might be different for each entry 48 | // that is serialized. 49 | 50 | bool JournaldExportReader::readNext() 51 | { 52 | if (mDevice->atEnd()) { 53 | return false; 54 | } 55 | 56 | mCurrentEntry.clear(); 57 | while (!mDevice->atEnd()) { 58 | QString line = QString::fromLocal8Bit(mDevice->readLine().trimmed()); 59 | // empty line = beginning of new log entry 60 | if (line.isEmpty()) { 61 | break; // found break in log entry 62 | } 63 | 64 | // if line does not contain "=" then switch to binary reading mode 65 | int separatorIndex = line.indexOf(QLatin1Char('=')); 66 | if (separatorIndex > 0) { 67 | mCurrentEntry[line.left(separatorIndex)] = line.right(line.length() - separatorIndex - 1).trimmed(); 68 | } else { 69 | QString fieldId = line; 70 | union { 71 | uint64_t uint64; 72 | char chars[4]; 73 | } size; 74 | qint64 bytes = mDevice->read(size.chars, 8); 75 | if (bytes != 8) { 76 | qCWarning(KJOURNALDLIB_GENERAL) << "Journal entry read that has unexpected number of bytes (8 bytes expected)" << bytes; 77 | } 78 | mCurrentEntry[fieldId] = QString::fromLocal8Bit(mDevice->read(le64toh(size.uint64))); 79 | // read line break after binary content, such that reader points to next line 80 | mDevice->read(1); 81 | } 82 | } 83 | 84 | return true; 85 | } 86 | 87 | JournaldExportReader::LogEntry JournaldExportReader::entry() const 88 | { 89 | return mCurrentEntry; 90 | } 91 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldexportreader.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDEXPORTREADER_H 7 | #define JOURNALDEXPORTREADER_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | #include 13 | 14 | class QIODevice; 15 | 16 | class KJOURNALD_EXPORT JournaldExportReader : public QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | using LogEntry = QHash; 21 | 22 | JournaldExportReader(QIODevice *device); 23 | bool atEnd() const; 24 | bool readNext(); 25 | LogEntry entry() const; 26 | 27 | private: 28 | QIODevice *mDevice{}; 29 | LogEntry mCurrentEntry; 30 | }; 31 | #endif 32 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldhelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "journaldhelper.h" 7 | #include "kjournaldlib_log_general.h" 8 | #include "localjournal.h" 9 | #include 10 | #include 11 | #include 12 | 13 | QVector JournaldHelper::queryUnique(const IJournal &journal, Field field) 14 | { 15 | QVector dataList; 16 | const void *data; 17 | size_t length; 18 | int result; 19 | 20 | const QString fieldString = mapField(field); 21 | 22 | result = sd_journal_query_unique(journal.sdJournal(), qUtf8Printable(fieldString)); 23 | if (result < 0) { 24 | qCritical() << "Failed to query journal:" << strerror(-result); 25 | return dataList; 26 | } 27 | const int fieldLength = fieldString.length() + 1; 28 | SD_JOURNAL_FOREACH_UNIQUE(journal.sdJournal(), data, length) 29 | { 30 | QString dataStr = QString::fromUtf8(static_cast(data), length); 31 | dataList << dataStr.remove(0, fieldLength); 32 | } 33 | return dataList; 34 | } 35 | 36 | QVector JournaldHelper::queryUnique(std::shared_ptr journal, Field field) 37 | { 38 | if (!journal) { 39 | return {}; 40 | } 41 | 42 | QVector dataList; 43 | const void *data; 44 | size_t length; 45 | int result; 46 | 47 | QString fieldString = mapField(field); 48 | 49 | result = sd_journal_query_unique(journal->sdJournal(), qUtf8Printable(fieldString)); 50 | if (result < 0) { 51 | qCritical() << "Failed to query journal:" << strerror(-result); 52 | return dataList; 53 | } 54 | const int fieldLength = fieldString.length() + 1; 55 | SD_JOURNAL_FOREACH_UNIQUE(journal->sdJournal(), data, length) 56 | { 57 | QString dataStr = QString::fromUtf8(static_cast(data), length); 58 | dataList << dataStr.remove(0, fieldLength); 59 | } 60 | return dataList; 61 | } 62 | 63 | QVector JournaldHelper::queryOrderedBootIds(const IJournal &journal) 64 | { 65 | QVector boots; 66 | 67 | QVector bootIds = JournaldHelper::queryUnique(journal, Field::_BOOT_ID); 68 | 69 | sd_journal *sdJournal = journal.sdJournal(); 70 | for (const QString &id : bootIds) { 71 | int result{0}; 72 | uint64_t time{0}; 73 | 74 | sd_journal_flush_matches(sdJournal); 75 | QString filterExpression = QLatin1String("_BOOT_ID=") + id; 76 | result = sd_journal_add_match(sdJournal, filterExpression.toStdString().c_str(), filterExpression.length()); 77 | if (result < 0) { 78 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed add filter:" << strerror(-result); 79 | continue; 80 | } 81 | 82 | QDateTime since; 83 | result = sd_journal_seek_head(sdJournal); 84 | if (result < 0) { 85 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to seek head:" << strerror(-result); 86 | continue; 87 | } 88 | result = sd_journal_next(sdJournal); 89 | if (result < 0) { 90 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to obtain first entry:" << strerror(-result); 91 | continue; 92 | } 93 | result = sd_journal_get_realtime_usec(sdJournal, &time); 94 | if (result == 0) { 95 | since.setMSecsSinceEpoch(time / 1000); 96 | } else { 97 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to obtain time:" << strerror(-result); 98 | } 99 | 100 | QDateTime until; 101 | result = sd_journal_seek_tail(sdJournal); 102 | if (result < 0) { 103 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to seek tail:" << strerror(-result); 104 | continue; 105 | } 106 | result = sd_journal_previous(sdJournal); 107 | if (result < 0) { 108 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to obtain first entry:" << strerror(-result); 109 | } 110 | result = sd_journal_get_realtime_usec(sdJournal, &time); 111 | if (result == 0) { 112 | until.setMSecsSinceEpoch(time / 1000); 113 | } else { 114 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to obtain time:" << strerror(-result); 115 | } 116 | 117 | if (since.isValid() && until.isValid()) { 118 | boots << BootInfo{id, since, until}; 119 | } else { 120 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not correctly parse start/end time for boot" << id << "skipping from list"; 121 | } 122 | } 123 | 124 | std::sort(boots.begin(), boots.end(), [](const JournaldHelper::BootInfo &lhs, const JournaldHelper::BootInfo &rhs) { 125 | return lhs.mSince < rhs.mSince; 126 | }); 127 | 128 | return boots; 129 | } 130 | 131 | QString JournaldHelper::mapField(Field field) 132 | { 133 | static QMetaEnum metaEnum = QMetaEnum::fromType(); 134 | return QString::fromLatin1(metaEnum.valueToKey(static_cast(field))); 135 | } 136 | 137 | QString JournaldHelper::cleanupString(const QString &string) 138 | { 139 | QString cleaned; 140 | cleaned.reserve(string.size()); 141 | int i = 0; 142 | while (i < string.size()) { 143 | if (string.at(i) == QLatin1Char('\u0001')) { // skip certain unicode characters 144 | ++i; 145 | continue; 146 | } else if (i + 3 >= string.size() || string.at(i) != QLatin1Char('\\') || string.at(i + 1) != QLatin1Char('x')) { 147 | cleaned.append(string.at(i)); 148 | ++i; 149 | continue; 150 | } else { 151 | // handle ascii escape sequence, eg. "\x2d" for "-" 152 | bool ok; 153 | auto character = QChar::fromLatin1(QStringView(string).mid(i + 2, 2).toUInt(&ok, 16)); 154 | Q_ASSERT(ok); 155 | cleaned.append(character); 156 | i += 4; 157 | continue; 158 | } 159 | } 160 | return cleaned; 161 | } 162 | 163 | QDebug operator<<(QDebug debug, const JournaldHelper::BootInfo &bootInfo) 164 | { 165 | QDebugStateSaver saver(debug); 166 | debug.noquote() << bootInfo.mBootId << ':' << bootInfo.mSince.toString(Qt::DateFormat::ISODateWithMs) << '-' 167 | << bootInfo.mUntil.toString(Qt::DateFormat::ISODateWithMs); 168 | return debug; 169 | } 170 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldhelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDHELPER_H 7 | #define JOURNALDHELPER_H 8 | 9 | #include "kjournald_export.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | class KJOURNALD_EXPORT JournaldHelper 17 | { 18 | Q_GADGET 19 | public: 20 | /** 21 | * @brief Basic information of a boot 22 | */ 23 | struct BootInfo { 24 | QString mBootId; //!< unique identifier of the boot 25 | QDateTime mSince; //!< time of oldest log entry for the specific boot 26 | QDateTime mUntil; //!< time of newest log entry for the specific boot 27 | }; 28 | 29 | /** 30 | * @brief Enumeration of most prominent field contents 31 | * 32 | * Names of these fields are identitical to their actual names with the only difference 33 | * that the "_" is removed from the reserved fields for Q_ENUM compatability. 34 | */ 35 | enum class Field { 36 | // user fields 37 | MESSAGE, 38 | MESSAGE_ID, 39 | PRIORITY, 40 | CODE_FILE, 41 | CODE_LINE, 42 | CODE_FUNC, 43 | // trusted fields 44 | _BOOT_ID, 45 | _EXE, 46 | _SYSTEMD_CGROUP, 47 | _SYSTEMD_SLICE, 48 | _SYSTEMD_UNIT, 49 | _SYSTEMD_USER_UNIT, 50 | _SYSTEMD_USER_SLICE, 51 | _SYSTEMD_SESSION, 52 | _SYSTEMD_OWNER_UID, 53 | _TRANSPORT, 54 | }; 55 | Q_ENUM(Field) 56 | 57 | /** 58 | * @brief Query unique field values fro given field 59 | * 60 | * This method returns all unique values provided by a defined journald database 61 | * field, e.g. the list of all boot-ids or the list of all services. 62 | * 63 | * This method wraps sd_journal_query_unique, which according to its documentation 64 | * ignores any add_match settings of the used @a journal. Yet this may change in the 65 | * future and it is encouraged to use a separate journal object to request unique valus. 66 | * 67 | * @param journal the wrapper object for an sd_journal instance 68 | * @param field the requested field 69 | * @return the list of unique field contents 70 | */ 71 | static QVector queryUnique(const IJournal &journal, Field field); 72 | 73 | /** 74 | * @copydoc JournaldHelper::queryUnique 75 | */ 76 | static QVector queryUnique(std::shared_ptr journal, Field field); 77 | 78 | /** 79 | * @brief Query boot information for @p journal 80 | * 81 | * @return ordered list of boots (first is earliest boot in time) 82 | */ 83 | static QVector queryOrderedBootIds(const IJournal &journal); 84 | 85 | /** 86 | * @brief Mapper method that maps from field enum to textual representation 87 | * 88 | * @param field the field enum for which the textual repesentation is requested 89 | * @return string representation of enum 90 | */ 91 | static QString mapField(Field field); 92 | 93 | /** 94 | * @brief Cleanup typical decorations from strings as found in journald databases 95 | * @param string the string to process 96 | * @return cleaned string 97 | */ 98 | static QString cleanupString(const QString &string); 99 | }; 100 | 101 | QDebug operator<<(QDebug debug, const JournaldHelper::BootInfo &bootInfo); 102 | 103 | #endif // JOURNALDHELPER_H 104 | -------------------------------------------------------------------------------- /org/kde/kjournald/journalduniquequerymodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "journalduniquequerymodel.h" 7 | #include "journalduniquequerymodel_p.h" 8 | #include "kjournald_export.h" 9 | #include "kjournaldlib_log_general.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | JournaldUniqueQueryModelPrivate::~JournaldUniqueQueryModelPrivate() 16 | { 17 | sd_journal_close(mJournal); 18 | mJournal = nullptr; 19 | } 20 | 21 | void JournaldUniqueQueryModelPrivate::closeJournal() 22 | { 23 | if (mJournal) { 24 | sd_journal_close(mJournal); 25 | mJournal = nullptr; 26 | } 27 | } 28 | 29 | bool JournaldUniqueQueryModelPrivate::openJournal() 30 | { 31 | closeJournal(); 32 | // TODO allow custom selection of journal type 33 | int result = sd_journal_open(&mJournal, SD_JOURNAL_LOCAL_ONLY); 34 | if (result < 0) { 35 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result); 36 | return false; 37 | } 38 | return true; 39 | } 40 | 41 | bool JournaldUniqueQueryModelPrivate::openJournalFromPath(const QString &path) 42 | { 43 | closeJournal(); 44 | if (path.isEmpty() || !QDir().exists(path)) { 45 | qCCritical(KJOURNALDLIB_GENERAL) << "Journal directory does not exist, abort opening"; 46 | return false; 47 | } 48 | const QFileInfo fileInfo = QFileInfo(path); 49 | if (fileInfo.isDir()) { 50 | int result = sd_journal_open_directory(&mJournal, path.toStdString().c_str(), 0 /* no flags, directory defines type */); 51 | if (result < 0) { 52 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result); 53 | return false; 54 | } 55 | } else if (fileInfo.isFile()) { 56 | const char **files = new const char *[1]; 57 | QByteArray journalPath = path.toLocal8Bit(); 58 | files[0] = journalPath.data(); 59 | 60 | int result = sd_journal_open_files(&mJournal, files, 0 /* no flags, directory defines type */); 61 | delete[] files; 62 | if (result < 0) { 63 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result); 64 | return false; 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | void JournaldUniqueQueryModelPrivate::runQuery() 72 | { 73 | if (!mJournal || mFieldString.isEmpty()) { 74 | return; 75 | } 76 | mEntries.clear(); 77 | 78 | QVector> dataList; 79 | const void *data; 80 | size_t length; 81 | int result = sd_journal_query_unique(mJournal, mFieldString.toStdString().c_str()); 82 | if (result < 0) { 83 | qCritical() << "Failed to query journal:" << strerror(-result); 84 | return; 85 | } 86 | const int fieldLength = mFieldString.length() + 1; 87 | SD_JOURNAL_FOREACH_UNIQUE(mJournal, data, length) 88 | { 89 | QString dataStr = QString::fromLocal8Bit(static_cast(data)); 90 | dataStr = dataStr.remove(0, fieldLength); 91 | if (dataStr.endsWith(QLatin1String("\u0001"))) { 92 | dataStr = dataStr.left(dataStr.length() - QString(QLatin1String("\u0001")).length()); 93 | } 94 | if (dataStr.endsWith(QLatin1String("\u0002"))) { 95 | dataStr = dataStr.left(dataStr.length() - QString(QLatin1String("\u0002")).length()); 96 | } 97 | dataStr = JournaldHelper::cleanupString(dataStr); 98 | dataList << std::pair{dataStr, true}; 99 | } 100 | 101 | mEntries = dataList; 102 | } 103 | 104 | JournaldUniqueQueryModel::JournaldUniqueQueryModel(QObject *parent) 105 | : QAbstractItemModel(parent) 106 | , d(new JournaldUniqueQueryModelPrivate) 107 | { 108 | d->openJournal(); 109 | d->runQuery(); 110 | } 111 | 112 | JournaldUniqueQueryModel::JournaldUniqueQueryModel(const QString &journalPath, QObject *parent) 113 | : QAbstractItemModel(parent) 114 | , d(new JournaldUniqueQueryModelPrivate) 115 | { 116 | d->openJournalFromPath(journalPath); 117 | d->runQuery(); 118 | } 119 | 120 | JournaldUniqueQueryModel::~JournaldUniqueQueryModel() = default; 121 | 122 | bool JournaldUniqueQueryModel::setJournaldPath(const QString &path) 123 | { 124 | bool success{true}; 125 | beginResetModel(); 126 | success = d->openJournalFromPath(path); 127 | if (success) { 128 | d->runQuery(); 129 | } 130 | endResetModel(); 131 | return success; 132 | } 133 | 134 | void JournaldUniqueQueryModel::setField(JournaldHelper::Field field) 135 | { 136 | setFieldString(JournaldHelper::mapField(field)); 137 | } 138 | 139 | void JournaldUniqueQueryModel::setFieldString(const QString &fieldString) 140 | { 141 | beginResetModel(); 142 | d->mFieldString = fieldString; 143 | d->runQuery(); 144 | endResetModel(); 145 | } 146 | 147 | QString JournaldUniqueQueryModel::fieldString() const 148 | { 149 | return d->mFieldString; 150 | } 151 | 152 | QHash JournaldUniqueQueryModel::roleNames() const 153 | { 154 | QHash roles; 155 | roles[JournaldUniqueQueryModel::FIELD] = "field"; 156 | roles[JournaldUniqueQueryModel::SELECTED] = "selected"; 157 | return roles; 158 | } 159 | 160 | void JournaldUniqueQueryModel::setSystemJournal() 161 | { 162 | beginResetModel(); 163 | d->openJournal(); 164 | endResetModel(); 165 | } 166 | 167 | QModelIndex JournaldUniqueQueryModel::index(int row, int column, const QModelIndex &parent) const 168 | { 169 | return createIndex(row, column); 170 | } 171 | 172 | QModelIndex JournaldUniqueQueryModel::parent(const QModelIndex &index) const 173 | { 174 | // no tree model, thus no parent 175 | return QModelIndex(); 176 | } 177 | 178 | int JournaldUniqueQueryModel::rowCount(const QModelIndex &parent) const 179 | { 180 | // model represents a list and has has no children 181 | if (!parent.isValid()) { 182 | return d->mEntries.size(); 183 | } else { 184 | return 0; 185 | } 186 | } 187 | 188 | int JournaldUniqueQueryModel::columnCount(const QModelIndex &parent) const 189 | { 190 | return 1; 191 | } 192 | 193 | QVariant JournaldUniqueQueryModel::data(const QModelIndex &index, int role) const 194 | { 195 | if (d->mEntries.count() <= index.row()) { 196 | return QVariant(); 197 | } 198 | switch (role) { 199 | case Qt::DisplayRole: 200 | Q_FALLTHROUGH(); 201 | case JournaldUniqueQueryModel::Roles::FIELD: 202 | return d->mEntries.at(index.row()).first; 203 | case JournaldUniqueQueryModel::Roles::SELECTED: 204 | return QVariant::fromValue(d->mEntries.at(index.row()).second); 205 | } 206 | return QVariant(); 207 | } 208 | 209 | bool JournaldUniqueQueryModel::setData(const QModelIndex &index, const QVariant &value, int role) 210 | { 211 | if (d->mEntries.count() <= index.row()) { 212 | return false; 213 | } 214 | if (role == JournaldUniqueQueryModel::Roles::SELECTED) { 215 | if (d->mEntries.at(index.row()).second == value.toBool()) { 216 | return false; 217 | } else { 218 | d->mEntries[index.row()].second = value.toBool(); 219 | Q_EMIT dataChanged(index, index); 220 | return true; 221 | } 222 | } 223 | return QAbstractItemModel::setData(index, value, role); 224 | } 225 | -------------------------------------------------------------------------------- /org/kde/kjournald/journalduniquequerymodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDUNIQUEQUERYMODEL_H 7 | #define JOURNALDUNIQUEQUERYMODEL_H 8 | 9 | #include "journaldhelper.h" 10 | #include "kjournald_export.h" 11 | #include 12 | #include 13 | #include 14 | 15 | class JournaldUniqueQueryModelPrivate; 16 | 17 | /** 18 | * @brief The JournaldUniqueQueryModel class provides an item model abstraction for journald queryunique API 19 | * 20 | * This class is useful when creating a list model for contents like: 21 | * - boot ids in specific journal database 22 | * - systemd units in specific journal database 23 | * - priorities in specific journal database 24 | * 25 | * The model can be create from an arbitrary local journald database by defining a path or from the 26 | * system's default journal. Values can either be set by @a setFieldString for arbitrary values or in a 27 | * typesafe manner via @a setField for most common fields. 28 | */ 29 | class KJOURNALD_EXPORT JournaldUniqueQueryModel : public QAbstractItemModel 30 | { 31 | Q_OBJECT 32 | QML_ELEMENT 33 | Q_PROPERTY(QString journalPath WRITE setJournaldPath RESET setSystemJournal) 34 | Q_PROPERTY(QString field WRITE setFieldString) 35 | 36 | public: 37 | enum Roles { 38 | FIELD = Qt::UserRole + 1, 39 | SELECTED, //!< supports UI integration by storing checked 40 | }; 41 | Q_ENUM(Roles) 42 | 43 | explicit JournaldUniqueQueryModel(QObject *parent = nullptr); 44 | 45 | /** 46 | * @brief Create model from a specific journal database 47 | * 48 | * This constructor works similar to "journalctl -D" and allows to use a custom path to the 49 | * journald database. 50 | * 51 | * @param journalPath path to the journald database 52 | * @param parent the QObject parent 53 | */ 54 | JournaldUniqueQueryModel(const QString &journalPath, QObject *parent = nullptr); 55 | 56 | /** 57 | * @brief Destroys the JournaldUniqueQueryModel object 58 | */ 59 | ~JournaldUniqueQueryModel() override; 60 | 61 | /** 62 | * Reset model by reading from a new journald database 63 | * 64 | * @param path The path to directory that obtains the journald DB, usually ending with "journal". 65 | * @return true if path could be found and opened, otherwise false 66 | */ 67 | bool setJournaldPath(const QString &path); 68 | 69 | /** 70 | * Switch to local system's default journald database 71 | * 72 | * For details regarding preference, see journald documentation. 73 | */ 74 | void setSystemJournal(); 75 | 76 | /** 77 | * Set field for which unique query shall be run 78 | * 79 | * This method allows defining an arbitrary string as query string. The most common fields are available 80 | * by type-safe @a setField setter. Examples for the argument are "_SYSTEMD_UNIT", "PRIORITY", "_BOOT_ID". 81 | * 82 | * @param fieldString the string that names the field 83 | */ 84 | void setFieldString(const QString &fieldString); 85 | 86 | /** 87 | * Set field for which unique query shall be run 88 | * 89 | * @param field enum for field for which query shall be run 90 | */ 91 | void setField(JournaldHelper::Field field); 92 | 93 | /** 94 | * @return the currently set field string 95 | */ 96 | QString fieldString() const; 97 | 98 | /** 99 | * @copydoc QAbstractItemModel::rolesNames() 100 | */ 101 | QHash roleNames() const override; 102 | 103 | /** 104 | * @copydoc QAbstractItemModel::index() 105 | */ 106 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; 107 | 108 | /** 109 | * @copydoc QAbstractItemModel::parent() 110 | */ 111 | QModelIndex parent(const QModelIndex &index) const override; 112 | 113 | /** 114 | * @copydoc QAbstractItemModel::rowCount() 115 | */ 116 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 117 | 118 | /** 119 | * @copydoc QAbstractItemModel::columnCount() 120 | */ 121 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 122 | 123 | /** 124 | * @copydoc QAbstractItemModel::data() 125 | */ 126 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 127 | 128 | /** 129 | * @copydoc QAbstractItemModel::setData() 130 | */ 131 | Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 132 | 133 | private: 134 | std::unique_ptr d; 135 | }; 136 | 137 | #endif // JOURNALDUNIQUEQUERYMODEL_H 138 | -------------------------------------------------------------------------------- /org/kde/kjournald/journalduniquequerymodel_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDUNIQUEQUERYMODEL_P_H 7 | #define JOURNALDUNIQUEQUERYMODEL_P_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class JournaldUniqueQueryModelPrivate 15 | { 16 | public: 17 | ~JournaldUniqueQueryModelPrivate(); 18 | void closeJournal(); 19 | bool openJournal(); 20 | bool openJournalFromPath(const QString &directory); 21 | void runQuery(); 22 | 23 | sd_journal *mJournal{nullptr}; 24 | QString mFieldString; 25 | QVector> mEntries; 26 | }; 27 | 28 | #endif // JOURNALDUNIQUEQUERYMODEL_P_H 29 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldviewmodel_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDVIEWMODEL_P_H 7 | #define JOURNALDVIEWMODEL_P_H 8 | 9 | #include "filter.h" 10 | #include "ijournal.h" 11 | #include "logentry.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class JournaldViewModelPrivate 22 | { 23 | public: 24 | enum class Direction { 25 | TOWARDS_HEAD, 26 | TOWARDS_TAIL, 27 | }; 28 | 29 | enum class SeekCursorResult { 30 | CURSOR_MADE_CURRENT, 31 | ERROR, 32 | }; 33 | 34 | /** 35 | * reapply all filters and seek journal at head 36 | * ensure to guard this call with beginModelReset and endModelReset 37 | */ 38 | void resetJournal(); 39 | 40 | /** 41 | * Seek head of journal and already position at first entry with 42 | * sd_journal_next(). 43 | * 44 | * @return if head could be seeked (e.g. false if filter result to empty set) 45 | * 46 | * @note this call also updates all internal cursors (for window head/tail) as well 47 | * as the internal state if head/tail are reached. 48 | */ 49 | bool seekHeadAndMakeCurrent(); 50 | 51 | /** 52 | * Seek tail of journal and already position at first entry with 53 | * sd_journal_next(). 54 | * 55 | * @note this call also updates all internal cursors (for window head/tail) as well 56 | * as the internal state if head/tail are reached. 57 | */ 58 | bool seekTailAndMakeCurrent(); 59 | 60 | /** 61 | * fetch data from current cursor position in forwards direction if @p forwards 62 | * is true, otherwards backwards in time 63 | * @note depending on the direction, the method relies on correctly initialized head and tail 64 | * cursors and upon calling sets the current entry to the respective cursor. 65 | * 66 | * @return if tail could be seeked (e.g. false if filter result to empty set) 67 | * 68 | * @note it is responsibility of the caller to ensure that data entries are not 69 | * placed twice into the journal. this means, only call this method after a model 70 | * reset and then only in the respective direction 71 | */ 72 | QVector readEntries(Direction direction); 73 | 74 | /** 75 | * @brief seekCursor in journal an handle issues 76 | * @return 77 | */ 78 | SeekCursorResult seekCursor(const QString &cursor); 79 | 80 | std::unique_ptr mJournal; 81 | QList mLog; 82 | Filter mFilter; 83 | bool mHeadCursorReached{false}; 84 | bool mTailCursorReached{false}; 85 | bool mModelResetActive{false}; 86 | QAtomicInt mActiveFetchOperations{0}; 87 | uint32_t mChunkSize{500}; 88 | }; 89 | 90 | #endif // JOURNALDVIEWMODEL_P_H 91 | -------------------------------------------------------------------------------- /org/kde/kjournald/journaldviewmodelslidingwindow_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef JOURNALDVIEWMODEL_P_H 7 | #define JOURNALDVIEWMODEL_P_H 8 | 9 | #include "ijournal.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | struct LogEntry { 21 | QDateTime mDate; 22 | quint64 mMonotonicTimestamp{0}; 23 | QString mId; 24 | QString mMessage; 25 | QString mSystemdUnit; 26 | QString mBootId; 27 | QString mExe; 28 | int mPriority{0}; 29 | QString mCursor; 30 | }; 31 | 32 | class JournaldViewModelPrivate 33 | { 34 | public: 35 | enum class Direction { 36 | TOWARDS_HEAD, 37 | TOWARDS_TAIL, 38 | }; 39 | 40 | enum class SeekCursorResult { 41 | CURSOR_MADE_CURRENT, 42 | ERROR, 43 | }; 44 | 45 | /** 46 | * reapply all filters and seek journal at head 47 | * ensure to guard this call with beginModelReset and endModelReset 48 | */ 49 | void resetJournal(); 50 | 51 | /** 52 | * Seek head of journal and already position at first entry with 53 | * sd_journal_next(). 54 | * 55 | * @return if head could be seeked (e.g. false if filter result to empty set) 56 | * 57 | * @note this call also updates all internal cursors (for window head/tail) as well 58 | * as the internal state if head/tail are reached. 59 | */ 60 | bool seekHeadAndMakeCurrent(); 61 | 62 | /** 63 | * Seek tail of journal and already position at first entry with 64 | * sd_journal_next(). 65 | * 66 | * @note this call also updates all internal cursors (for window head/tail) as well 67 | * as the internal state if head/tail are reached. 68 | */ 69 | bool seekTailAndMakeCurrent(); 70 | 71 | /** 72 | * fetch data from current cursor position in forwards direction if @p forwards 73 | * is true, otherwards backwards in time 74 | * @note depending on the direction, the method relies on correctly initialized head and tail 75 | * cursors and upon calling sets the current entry to the respective cursor. 76 | * 77 | * @return if tail could be seeked (e.g. false if filter result to empty set) 78 | * 79 | * @note it is responsibility of the caller to ensure that data entries are not 80 | * placed twice into the journal. this means, only call this method after a model 81 | * reset and then only in the respective direction 82 | */ 83 | QVector readEntries(Direction direction); 84 | 85 | /** 86 | * @brief seekCursor in journal an handle issues 87 | * @return 88 | */ 89 | SeekCursorResult seekCursor(const QString &cursor); 90 | 91 | std::unique_ptr mJournal; 92 | QVector mLog; 93 | QStringList mSystemdUnitFilter; 94 | QStringList mExeFilter; 95 | QStringList mBootFilter; 96 | std::optional mPriorityFilter; 97 | bool mShowKernelMessages{false}; 98 | bool mHeadCursorReached{false}; 99 | bool mTailCursorReached{false}; 100 | bool mModelResetActive{false}; 101 | QAtomicInt mActiveFetchOperations{0}; 102 | uint32_t mChunkSize{500}; 103 | }; 104 | 105 | #endif // JOURNALDVIEWMODEL_P_H 106 | -------------------------------------------------------------------------------- /org/kde/kjournald/localjournal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "localjournal.h" 7 | #include "localjournal_p.h" 8 | #include "kjournaldlib_log_general.h" 9 | #include 10 | #include 11 | 12 | LocalJournalPrivate::LocalJournalPrivate() 13 | { 14 | QFile file(QLatin1String("/proc/sys/kernel/random/boot_id")); 15 | if (file.open(QIODevice::ReadOnly | QFile::Text)) { 16 | QTextStream stream(&file); 17 | // example value: "918581c5-a27a-4ac6-9f37-c160fd20d1b5" 18 | mCurrentBootId = stream.readAll().trimmed().remove(QLatin1Char('-')); 19 | } else { 20 | qCWarning(KJOURNALDLIB_GENERAL) << "Could not obtain ID of current boot"; 21 | } 22 | } 23 | 24 | LocalJournal::LocalJournal() 25 | : d(new LocalJournalPrivate) 26 | { 27 | auto expectedJournal = owning_ptr_call(sd_journal_open, SD_JOURNAL_LOCAL_ONLY); 28 | if (expectedJournal.ret < 0) { 29 | qCCritical(KJOURNALDLIB_GENERAL) << "Failed to open journal:" << strerror(-expectedJournal.ret); 30 | } else { 31 | d->mJournal = std::move(expectedJournal.value); 32 | d->mFd = sd_journal_get_fd(d->mJournal.get()); 33 | if (d->mFd > 0) { 34 | d->mJournalSocketNotifier = std::make_unique(d->mFd, QSocketNotifier::Read); 35 | connect(d->mJournalSocketNotifier.get(), &QSocketNotifier::activated, this, &LocalJournal::handleJournalDescriptorUpdate); 36 | } else { 37 | qCWarning(KJOURNALDLIB_GENERAL) << "Could not create FD" << strerror(-d->mFd); 38 | d->mFd = 0; 39 | } 40 | } 41 | } 42 | 43 | LocalJournal::LocalJournal(const QString &path) 44 | : d(new LocalJournalPrivate) 45 | { 46 | if (!QDir().exists(path)) { 47 | qCCritical(KJOURNALDLIB_GENERAL) << "Journal directory does not exist, abort opening" << path; 48 | return; 49 | } 50 | if (QFileInfo(path).isDir()) { 51 | auto expectedJournal = owning_ptr_call(sd_journal_open_directory, path.toStdString().c_str(), 0 /* no flags, directory defines type */); 52 | if (expectedJournal.ret < 0) { 53 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal from directory" << path << ":" << strerror(-expectedJournal.ret); 54 | } else { 55 | d->mJournal = std::move(expectedJournal.value); 56 | } 57 | } else if (QFileInfo(path).isFile()) { 58 | const char **files = new const char *[1]; 59 | QByteArray journalPath = path.toLocal8Bit(); 60 | files[0] = journalPath.data(); 61 | 62 | auto expectedJournal = owning_ptr_call(sd_journal_open_files, files, 0 /* no flags, directory defines type */); 63 | if (expectedJournal.ret < 0) { 64 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal from file" << path << ":" << strerror(-expectedJournal.ret); 65 | } else { 66 | d->mJournal = std::move(expectedJournal.value); 67 | } 68 | delete[] files; 69 | } 70 | } 71 | 72 | LocalJournal::~LocalJournal() = default; 73 | 74 | sd_journal *LocalJournal::sdJournal() const 75 | { 76 | return d->mJournal.get(); 77 | } 78 | 79 | bool LocalJournal::isValid() const 80 | { 81 | return d->mJournal != nullptr; 82 | } 83 | 84 | QString LocalJournal::currentBootId() const 85 | { 86 | return d->mCurrentBootId; 87 | } 88 | 89 | uint64_t LocalJournal::usage() const 90 | { 91 | uint64_t size{0}; 92 | int res = sd_journal_get_usage(d->mJournal.get(), &size); 93 | if (res < 0) { 94 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not obtain journal size:" << strerror(-res); 95 | } 96 | return size; 97 | } 98 | 99 | void LocalJournal::handleJournalDescriptorUpdate() 100 | { 101 | // reset descriptor 102 | QFile file; 103 | file.open(d->mFd, QIODevice::ReadOnly); 104 | file.readAll(); 105 | file.close(); 106 | qCDebug(KJOURNALDLIB_GENERAL) << "Local journal FD updated"; 107 | Q_EMIT journalUpdated(d->mCurrentBootId); 108 | } 109 | -------------------------------------------------------------------------------- /org/kde/kjournald/localjournal.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef LOCALJOURNAL_H 7 | #define LOCALJOURNAL_H 8 | 9 | #include "ijournal.h" 10 | #include "kjournald_export.h" 11 | #include 12 | #include 13 | 14 | class LocalJournalPrivate; 15 | class sd_journal; 16 | 17 | /** 18 | * @brief The LocalJournal class encapsulates a local sd_journal object 19 | * 20 | * Local journal in this sense means a journald database that can be access via direct file access, i.e. 21 | * where operations can be performed directly on the database files. 22 | * 23 | * @note The journald documentation specifically says that using the same sd_journal object in multiple 24 | * queries (or models in this case) might have side effects; even though there are none at the moment. Thus, 25 | * ensure that the same Journal object is only used for one model. 26 | */ 27 | class KJOURNALD_EXPORT LocalJournal : public IJournal 28 | { 29 | public: 30 | /** 31 | * @brief Construct journal object for system journald DB 32 | */ 33 | explicit LocalJournal(); 34 | 35 | /** 36 | * @brief Construct journal object from journald DB at path @p path 37 | * This path can be a directory or a file. 38 | */ 39 | LocalJournal(const QString &path); 40 | 41 | /** 42 | * @brief Destroys the journal wrapper 43 | */ 44 | ~LocalJournal() override; 45 | 46 | /** 47 | * @brief Getter for raw sd_journal pointer 48 | * 49 | * This pointer can be nullptr if an error during opening of journal occured. Test 50 | * with @s isValid() before using. 51 | */ 52 | sd_journal *sdJournal() const override; 53 | 54 | /** 55 | * @brief returns true if and only if the sd_journal pointer is valid 56 | */ 57 | bool isValid() const override; 58 | 59 | /** 60 | * @copydoc IJournal::currentBootId() 61 | */ 62 | QString currentBootId() const override; 63 | 64 | /** 65 | * @brief Get file system usage of journal 66 | * @return size of journal in bytes 67 | */ 68 | uint64_t usage() const; 69 | 70 | private 71 | Q_SLOT : void handleJournalDescriptorUpdate(); 72 | 73 | private: 74 | std::unique_ptr d; 75 | }; 76 | 77 | #endif // JOURNAL_H 78 | -------------------------------------------------------------------------------- /org/kde/kjournald/localjournal_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef LOCALJOURNAL_PRIVATE_H 7 | #define LOCALJOURNAL_PRIVATE_H 8 | 9 | #include "memory.h" 10 | #include 11 | #include 12 | #include 13 | 14 | class LocalJournalPrivate 15 | { 16 | public: 17 | LocalJournalPrivate(); 18 | mutable std::unique_ptr mJournal; 19 | qintptr mFd{0}; 20 | QString mCurrentBootId; 21 | std::unique_ptr mJournalSocketNotifier; 22 | }; 23 | 24 | #endif // LOCALJOURNAL_H 25 | -------------------------------------------------------------------------------- /org/kde/kjournald/logentry.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "logentry.h" 7 | 8 | LogEntry::LogEntry(const QDateTime &date, 9 | quint64 monotonicTimestamp, 10 | const QString &id, 11 | const QString &message, 12 | const QString &unit, 13 | const QString &bootId, 14 | const QString &exe, 15 | int priority, 16 | const QString &cursor) 17 | : m_id{id} 18 | , m_date{date} 19 | , m_monotonicTimestamp{monotonicTimestamp} 20 | , m_unit{unit} 21 | , m_bootId{bootId} 22 | , m_exe{exe} 23 | , m_priority{priority} 24 | , m_cursor{cursor} 25 | { 26 | setMessage(message); 27 | } 28 | 29 | bool LogEntry::matches(const QString &needle, bool caseSensitive) const 30 | { 31 | if (needle.isEmpty()) { 32 | return false; 33 | } 34 | const Qt::CaseSensitivity caseSensitiveEnum = caseSensitive ? Qt::CaseSensitivity::CaseSensitive : Qt::CaseSensitivity::CaseInsensitive; 35 | if (m_message.contains(needle, caseSensitiveEnum)) { 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | void LogEntry::setMessage(const QString &message) 43 | { 44 | // TODO handle cleanup of arbitrary color codes 45 | // cleanup color codes 46 | m_message = message; 47 | m_message.remove(QLatin1String("\u001B[96m")).remove(QLatin1String("\u001B[0m")).remove(QLatin1String("\u001B[93m")).remove(QLatin1String("\u001B[31m")); 48 | } 49 | 50 | void LogEntry::setDate(const QDateTime &date) 51 | { 52 | m_date = date; 53 | } 54 | 55 | void LogEntry::setMonotonicTimestamp(quint64 timestamp) 56 | { 57 | m_monotonicTimestamp = timestamp; 58 | } 59 | 60 | void LogEntry::setPriority(int priority) 61 | { 62 | m_priority = priority; 63 | } 64 | 65 | void LogEntry::setId(const QString &id) 66 | { 67 | m_id = id; 68 | } 69 | 70 | void LogEntry::setBootId(const QString &bootId) 71 | { 72 | m_bootId = bootId; 73 | } 74 | 75 | void LogEntry::setUnit(const QString &unit) 76 | { 77 | m_unit = unit; 78 | } 79 | 80 | void LogEntry::setExe(const QString &exe) 81 | { 82 | m_exe = exe; 83 | } 84 | 85 | void LogEntry::setCursor(const QString &cursor) 86 | { 87 | m_cursor = cursor; 88 | } 89 | -------------------------------------------------------------------------------- /org/kde/kjournald/logentry.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef LOGENTRY_H 7 | #define LOGENTRY_H 8 | 9 | #include 10 | #include 11 | 12 | class LogEntry 13 | { 14 | Q_GADGET 15 | 16 | Q_PROPERTY(QString id READ id WRITE setId) 17 | Q_PROPERTY(QString message READ message WRITE setMessage) 18 | Q_PROPERTY(QDateTime date READ date WRITE setDate) 19 | Q_PROPERTY(quint64 monotonicTimestamp READ monotonicTimestamp) 20 | Q_PROPERTY(int priority READ priority WRITE setPriority) 21 | Q_PROPERTY(QString bootId READ bootId WRITE setBootId) 22 | Q_PROPERTY(QString unit READ unit WRITE setUnit) 23 | Q_PROPERTY(QString exe READ exe WRITE setExe) 24 | Q_PROPERTY(QString cursor READ cursor WRITE setCursor) 25 | 26 | QML_VALUE_TYPE(entry) 27 | 28 | public: 29 | LogEntry() = default; 30 | ~LogEntry() = default; 31 | /** convenience constructor **/ 32 | LogEntry(const QDateTime &date, 33 | quint64 monotonicTimestamp, 34 | const QString &id, 35 | const QString &message, 36 | const QString &unit, 37 | const QString &bootId, 38 | const QString &exe, 39 | int priority, 40 | const QString &cursor); 41 | 42 | Q_INVOKABLE bool matches(const QString &needle, bool caseSensitive) const; 43 | 44 | inline QString message() const 45 | { 46 | return m_message; 47 | } 48 | void setMessage(const QString &message); 49 | inline QDateTime date() const 50 | { 51 | return m_date; 52 | } 53 | void setDate(const QDateTime &date); 54 | inline quint64 monotonicTimestamp() const 55 | { 56 | return m_monotonicTimestamp; 57 | } 58 | void setMonotonicTimestamp(quint64 timestamp); 59 | inline int priority() const 60 | { 61 | return m_priority; 62 | } 63 | void setPriority(int priority); 64 | inline QString id() const 65 | { 66 | return m_id; 67 | } 68 | void setId(const QString &id); 69 | inline QString bootId() const 70 | { 71 | return m_bootId; 72 | } 73 | void setBootId(const QString &bootId); 74 | inline QString unit() const 75 | { 76 | return m_unit; 77 | } 78 | void setUnit(const QString &unit); 79 | inline QString exe() const 80 | { 81 | return m_exe; 82 | } 83 | void setExe(const QString &exe); 84 | inline QString cursor() const 85 | { 86 | return m_cursor; 87 | } 88 | void setCursor(const QString &cursor); 89 | 90 | private: 91 | QString m_id; 92 | QString m_message; 93 | QDateTime m_date; 94 | quint64 m_monotonicTimestamp{0}; 95 | int m_priority{0}; 96 | QString m_bootId; 97 | QString m_unit; 98 | QString m_exe; 99 | QString m_cursor; 100 | }; 101 | 102 | #endif // LOGENTRY_H 103 | -------------------------------------------------------------------------------- /org/kde/kjournald/memory.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later 3 | SPDX-FileCopyrightText: 2019-2021 Harald Sitter 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace std 12 | { 13 | template<> 14 | struct default_delete { 15 | void operator()(sd_journal *ptr) const 16 | { 17 | sd_journal_close(ptr); 18 | } 19 | }; 20 | } // namespace std 21 | 22 | template 23 | struct Expected { 24 | const int ret; // return value of call 25 | const int error; // errno immediately after the call 26 | std::unique_ptr value; // the newly owned object (may be null) 27 | }; 28 | 29 | // Wrapper around C double pointer API of which we must take ownership. 30 | // errno may or may not be 31 | template 32 | Expected owning_ptr_call(Func func, Args &&...args) 33 | { 34 | T *raw = nullptr; 35 | const int ret = func(&raw, std::forward(args)...); 36 | return {ret, errno, std::unique_ptr(raw)}; 37 | } 38 | 39 | // Same as owning_ptr_call but for (sd_journal *, foo **, ...) API 40 | template 41 | Expected contextual_owning_ptr_call(Func func, sd_journal *context, Args &&...args) 42 | { 43 | T *raw = nullptr; 44 | const int ret = func(context, &raw, std::forward(args)...); 45 | return {ret, errno, std::unique_ptr(raw)}; 46 | } 47 | -------------------------------------------------------------------------------- /org/kde/kjournald/systemdjournalremote.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "systemdjournalremote.h" 7 | #include "kjournaldlib_log_general.h" 8 | #include "systemdjournalremote_p.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | SystemdJournalRemotePrivate::SystemdJournalRemotePrivate(SystemdJournalRemote *q) 16 | { 17 | QObject::connect(&mJournalRemoteProcess, &QProcess::errorOccurred, q, &SystemdJournalRemote::handleJournalRemoteProcessErrors); 18 | mJournalRemoteProcess.setProcessChannelMode(QProcess::ForwardedChannels); 19 | if (!sanityCheckForSystemdJournalRemoteExec()) { 20 | qCCritical(KJOURNALDLIB_GENERAL) << "Sanity checks failed, which indidate systemd-journal-remote libexe is not available"; 21 | } 22 | } 23 | 24 | bool SystemdJournalRemotePrivate::sanityCheckForSystemdJournalRemoteExec() const 25 | { 26 | bool result{true}; 27 | if (!QFile::exists(mSystemdJournalRemoteExec)) { 28 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not find executable:" << mSystemdJournalRemoteExec; 29 | result = false; 30 | } 31 | 32 | QFileInfo info(mSystemdJournalRemoteExec); 33 | if (result && !info.isExecutable()) { 34 | qCCritical(KJOURNALDLIB_GENERAL) << "systemd-journal-remote not marked as executable:" << mSystemdJournalRemoteExec; 35 | result = false; 36 | } 37 | 38 | return result; 39 | } 40 | 41 | QString SystemdJournalRemotePrivate::journalFile() const 42 | { 43 | return mTemporyJournalDir.path() + QLatin1String("/remote.journal"); 44 | } 45 | 46 | // TODO additional access can easily be implemented by using systemd-journal-remote CLI: 47 | // --listen-raw=ADDR Listen for connections at ADDR 48 | // --listen-http=ADDR Listen for HTTP connections at ADDR 49 | // --listen-https=ADDR Listen for HTTPS connections at ADDR 50 | 51 | // TODO add option to persistently safe journal in DB format 52 | 53 | // TODO introduce special handling when reaching max system file size due to: https://github.com/systemd/systemd/issues/5242 54 | 55 | SystemdJournalRemote::SystemdJournalRemote(const QString &filePath) 56 | : d(new SystemdJournalRemotePrivate(this)) 57 | { 58 | if (!QFile::exists(filePath)) { 59 | qCCritical(KJOURNALDLIB_GENERAL) << "Provided export journal file format does not exists, no journal created" << filePath; 60 | } 61 | if (!filePath.endsWith(QLatin1String("export"))) { 62 | qCWarning(KJOURNALDLIB_GENERAL) << "Provided export file has uncommon file ending that is not \".export\":" << filePath; 63 | } 64 | 65 | // start import 66 | if (!d->mTemporaryJournalDirWatcher.addPath(d->mTemporyJournalDir.path())) { 67 | qCWarning(KJOURNALDLIB_GENERAL) << "could not add path to system watcher:" << d->mTemporyJournalDir.path(); 68 | } 69 | // command structure: systemd-journal-remote --output=foo.journal foo.export 70 | qCDebug(KJOURNALDLIB_GENERAL) << QLatin1String("starting process: ") + d->mSystemdJournalRemoteExec + QLatin1String(" --output=") + d->journalFile() 71 | + QLatin1String(" ") + filePath; 72 | d->mJournalRemoteProcess.start(d->mSystemdJournalRemoteExec, QStringList() << QLatin1String("--output=") + d->journalFile() << filePath); 73 | // local file access currently only works when the full journal file is written 74 | d->mJournalRemoteProcess.waitForFinished(); 75 | 76 | connect(&d->mTemporaryJournalDirWatcher, 77 | &QFileSystemWatcher::directoryChanged, 78 | this, 79 | &SystemdJournalRemote::handleJournalFileCreated, 80 | Qt::QueuedConnection); 81 | } 82 | 83 | void SystemdJournalRemote::handleJournalFileCreated(const QString &path) 84 | { 85 | qCDebug(KJOURNALDLIB_GENERAL) << "handle created journal file at:" << path; 86 | if (path.isEmpty() || !QDir().exists(d->journalFile())) { 87 | qCCritical(KJOURNALDLIB_GENERAL) << "Journal directory does not exist, abort opening" << d->journalFile(); 88 | return; 89 | } 90 | 91 | const char **files = new const char *[1]; 92 | QByteArray journalPath = d->journalFile().toLocal8Bit(); 93 | files[0] = journalPath.data(); 94 | 95 | int result = sd_journal_open_files(&d->mJournal, files, 0 /* no flags, directory defines type */); 96 | if (result < 0) { 97 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not open journal:" << strerror(-result); 98 | } 99 | delete[] files; 100 | 101 | Q_EMIT journalFileChanged(); 102 | } 103 | 104 | void SystemdJournalRemote::handleJournalRemoteProcessErrors(QProcess::ProcessError error) 105 | { 106 | qCCritical(KJOURNALDLIB_GENERAL) << "systemd-journal-remote error occured:" << error; 107 | } 108 | 109 | SystemdJournalRemote::SystemdJournalRemote(const QString &url, const QString &port) 110 | : d(new SystemdJournalRemotePrivate(this)) 111 | { 112 | if (!(url.startsWith(QLatin1String("https://")) || url.startsWith(QLatin1String("http://")))) { 113 | qCWarning(KJOURNALDLIB_GENERAL) << "URL seems not begin a valid URL, no http/https prefix:" << url; 114 | } 115 | d->mTemporaryJournalDirWatcher.addPath(d->mTemporyJournalDir.path()); 116 | // command structure /lib/systemd/systemd-journal-remote --url http://127.0.0.1 -o /tmp/asdf.journal --split-mode=none 117 | d->mJournalRemoteProcess.start(d->mSystemdJournalRemoteExec, 118 | QStringList() << QLatin1String("--output=") + d->journalFile() << QLatin1String("--url=") + url + QLatin1Char(':') + port 119 | << QLatin1String("--split-mode=none")); 120 | d->mJournalRemoteProcess.waitForStarted(); 121 | 122 | connect(&d->mTemporaryJournalDirWatcher, 123 | &QFileSystemWatcher::directoryChanged, 124 | this, 125 | &SystemdJournalRemote::handleJournalFileCreated, 126 | Qt::QueuedConnection); 127 | } 128 | 129 | SystemdJournalRemote::~SystemdJournalRemote() 130 | { 131 | d->mJournalRemoteProcess.terminate(); 132 | d->mJournalRemoteProcess.waitForFinished(1000); 133 | if (d->mJournalRemoteProcess.state() == QProcess::Running) { 134 | qCWarning(KJOURNALDLIB_GENERAL) << "Process did not react to SIGTERM in time, sending SIGKILL"; 135 | d->mJournalRemoteProcess.kill(); 136 | } 137 | d->mJournalRemoteProcess.waitForFinished(); 138 | sd_journal_close(d->mJournal); 139 | d->mJournal = nullptr; 140 | } 141 | 142 | QString SystemdJournalRemote::journalFile() const 143 | { 144 | return d->journalFile(); 145 | } 146 | 147 | sd_journal *SystemdJournalRemote::sdJournal() const 148 | { 149 | return d->mJournal; 150 | } 151 | 152 | bool SystemdJournalRemote::isValid() const 153 | { 154 | return d->mJournal != nullptr; 155 | } 156 | 157 | QString SystemdJournalRemote::currentBootId() const 158 | { 159 | qCWarning(KJOURNALDLIB_GENERAL) << "Access to remote journal boot ID is not implemented"; 160 | return QString(); 161 | } 162 | 163 | uint64_t SystemdJournalRemote::usage() const 164 | { 165 | uint64_t size{0}; 166 | int res = sd_journal_get_usage(d->mJournal, &size); 167 | if (res < 0) { 168 | qCCritical(KJOURNALDLIB_GENERAL) << "Could not obtain journal size:" << strerror(-res); 169 | } 170 | return size; 171 | } 172 | 173 | bool SystemdJournalRemote::isSystemdRemoteAvailable() const 174 | { 175 | return d->sanityCheckForSystemdJournalRemoteExec(); 176 | } 177 | -------------------------------------------------------------------------------- /org/kde/kjournald/systemdjournalremote.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef SYSTEMDJOURNALREMOTE_H 7 | #define SYSTEMDJOURNALREMOTE_H 8 | 9 | #include "ijournal.h" 10 | #include "kjournald_export.h" 11 | #include 12 | #include 13 | #include 14 | 15 | class SystemdJournalRemotePrivate; 16 | class sd_journal; 17 | class QIODevice; 18 | 19 | /** 20 | * @brief The SystemdJournalRemote provides access to a remote journal via the systemd-journal-remote tool 21 | * 22 | */ 23 | class KJOURNALD_EXPORT SystemdJournalRemote : public IJournal 24 | { 25 | Q_OBJECT 26 | Q_PROPERTY(QString journalFile READ journalFile NOTIFY journalFileChanged) 27 | public: 28 | /** 29 | * @brief Construct journal object from file containing logs in systemd's journal export format 30 | */ 31 | SystemdJournalRemote(const QString &filePath); 32 | 33 | /** 34 | * @brief Construct journal object from file containing logs in systemd's journal export format 35 | */ 36 | SystemdJournalRemote(const QString &url, const QString &port); 37 | 38 | /** 39 | * @brief Destroys the journal wrapper 40 | */ 41 | ~SystemdJournalRemote() override; 42 | 43 | /** 44 | * @brief Path to journal file that temporarily stores data from remote journal 45 | * 46 | * @note the lifetime of this file is bound to the lifetime of the SystemJournalRemote object that 47 | * relays the remote data to the file. 48 | * @return path to the journald ".journal" file 49 | */ 50 | QString journalFile() const; 51 | 52 | /** 53 | * @brief Getter for raw sd_journal pointer 54 | * 55 | * This pointer can be nullptr if an error during opening of journal occured. Test 56 | * with @s isValid() before using. 57 | */ 58 | sd_journal *sdJournal() const override; 59 | 60 | /** 61 | * @brief returns true if and only if the sd_journal pointer is valid 62 | */ 63 | bool isValid() const override; 64 | 65 | /** 66 | * @copydoc IJournal::currentBootId() 67 | */ 68 | QString currentBootId() const override; 69 | 70 | /** 71 | * @brief Get file system usage of journal 72 | * @return size of journal in bytes 73 | */ 74 | uint64_t usage() const; 75 | 76 | bool isSystemdRemoteAvailable() const; 77 | 78 | Q_SIGNALS: 79 | void journalFileChanged(); 80 | 81 | private Q_SLOTS: 82 | void handleJournalFileCreated(const QString &path); 83 | void handleJournalRemoteProcessErrors(QProcess::ProcessError error); 84 | 85 | private: 86 | std::unique_ptr d; 87 | 88 | friend SystemdJournalRemotePrivate; 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /org/kde/kjournald/systemdjournalremote_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef SYSTEMDJOURNALREMOTE_PRIVATE_H 7 | #define SYSTEMDJOURNALREMOTE_PRIVATE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class SystemdJournalRemote; 16 | 17 | class SystemdJournalRemotePrivate 18 | { 19 | public: 20 | SystemdJournalRemotePrivate(SystemdJournalRemote *q); 21 | bool sanityCheckForSystemdJournalRemoteExec() const; 22 | QString journalFile() const; 23 | 24 | mutable sd_journal *mJournal{nullptr}; 25 | QTemporaryDir mTemporyJournalDir; 26 | QFileSystemWatcher mTemporaryJournalDirWatcher; 27 | QProcess mJournalRemoteProcess; 28 | const QString mSystemdJournalRemoteExec = QLatin1String("/lib/systemd/systemd-journal-remote"); 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: Andreas Cord-Landwehr 3 | 4 | add_library(kjournaldbrowser_backing STATIC) 5 | 6 | qt6_add_qml_module( 7 | kjournaldbrowser_backing 8 | URI org.kde.kjournaldbrowser 9 | IMPORTS 10 | org.kde.kirigami 11 | org.kde.kjournald 12 | DEPENDENCIES 13 | QtQuick 14 | org.kde.kjournald 15 | org.kde.kirigamiaddons. 16 | org.kde.kirigamiaddons.statefulapp 17 | SOURCES 18 | browserapplication.cpp 19 | browserapplication.h 20 | clipboardproxy.cpp 21 | clipboardproxy.h 22 | databaseprovider.cpp 23 | databaseprovider.h 24 | formatter.cpp 25 | formatter.h 26 | textsearch.cpp 27 | textsearch.h 28 | QML_FILES 29 | Main.qml 30 | LogLine.qml 31 | GlobalMenu.qml 32 | RemoteJournalConfigDialog.qml 33 | TopMenuBar.qml 34 | LogView.qml 35 | FilterCriteriaView.qml 36 | ColoredCheckbox.qml 37 | ) 38 | 39 | target_link_libraries(kjournaldbrowser_backing 40 | PUBLIC kjournald Qt6::Core Qt6::Gui KF6::Kirigami KirigamiAddonsStatefulApp 41 | ) 42 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/ColoredCheckbox.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Claudio Cambra 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import org.kde.kirigami as Kirigami 7 | 8 | QQC2.CheckBox { 9 | id: checkbox 10 | 11 | property color color: Kirigami.Theme.highlightColor 12 | property real radius: 4 13 | 14 | indicator: Rectangle { 15 | anchors.horizontalCenter: parent.horizontalCenter 16 | anchors.verticalCenter: parent.verticalCenter 17 | height: parent.height * 0.8 18 | width: height 19 | x: checkbox.leftPadding 20 | y: parent.height / 2 - height / 2 21 | radius: checkbox.radius 22 | border.color: checkbox.color 23 | border.width: checkbox.visualFocus ? 2 : 1 24 | color: Qt.rgba(0,0,0,0) 25 | 26 | Rectangle { 27 | anchors.margins: parent.height * 0.2 28 | anchors.fill: parent 29 | radius: checkbox.radius / 3 30 | color: checkbox.color 31 | visible: checkbox.checked 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/FilterCriteriaView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | pragma ComponentBehavior: Bound 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kjournald 13 | import Qt.labs.qmlmodels 14 | 15 | ListView { 16 | id: root 17 | ButtonGroup { id: priorityGroup } 18 | activeFocusOnTab: true 19 | reuseItems: true 20 | delegate: DelegateChooser { 21 | role: "type" 22 | DelegateChoice { 23 | roleValue: FlattenedFilterCriteriaProxyModel.FIRST_LEVEL 24 | delegate: ItemDelegate { 25 | id: expandDelegate 26 | required property bool expanded 27 | required property bool selected 28 | required text 29 | required property var model 30 | 31 | width: ListView.view.width 32 | onClicked: model.expanded = !expandDelegate.expanded 33 | Accessible.name: expandDelegate.text 34 | Accessible.description: expandDelegate.expanded ? i18n("Expanded") : i18n("Collapsed") 35 | contentItem: ColumnLayout { 36 | RowLayout { 37 | Label { 38 | text: expandDelegate.text 39 | textFormat: Text.PlainText 40 | elide: Text.ElideRight 41 | Layout.fillWidth: true 42 | } 43 | Kirigami.Icon { 44 | id: collapseIcon 45 | implicitWidth: Kirigami.Units.iconSizes.small 46 | implicitHeight: Kirigami.Units.iconSizes.small 47 | source: expandDelegate.expanded ? "collapse" : "expand" 48 | } 49 | } 50 | ItemDelegate { // clear button for checkbox selection 51 | visible: expandDelegate.expanded && expandDelegate.selected 52 | leftPadding: 10 53 | onClicked: expandDelegate.model.selected = false 54 | activeFocusOnTab: true 55 | Accessible.name: clearLabel.text 56 | Layout.fillWidth: true 57 | contentItem: RowLayout { 58 | Label { 59 | id: clearLabel 60 | text: i18n("Clear") 61 | Layout.fillWidth: true 62 | } 63 | Kirigami.Icon { 64 | Layout.preferredWidth: Kirigami.Units.iconSizes.small 65 | Layout.preferredHeight: Kirigami.Units.iconSizes.small 66 | source: "edit-clear" 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | DelegateChoice { 74 | roleValue: FlattenedFilterCriteriaProxyModel.CHECKBOX 75 | delegate: ItemDelegate { 76 | id: checkboxDelegate 77 | required property bool selected 78 | required property color color 79 | required text 80 | required property string longtext 81 | required property var model 82 | 83 | width: ListView.view.width 84 | leftPadding: 20 85 | onClicked: model.selected = !checkboxDelegate.selected 86 | 87 | contentItem: Row { 88 | spacing: 0.5 * Kirigami.Units.gridUnit 89 | Label { 90 | text: checkboxDelegate.text 91 | textFormat: Text.PlainText 92 | elide: Text.ElideRight 93 | width: parent.width - checkbox.width - 0.5 * Kirigami.Units.gridUnit 94 | } 95 | ColoredCheckbox { 96 | id: checkbox 97 | 98 | // hard overwrite of internal calculations for speedup, check with profiler when modify 99 | implicitWidth: Kirigami.Units.iconSizes.small 100 | implicitHeight: Kirigami.Units.iconSizes.small 101 | baselineOffset: 0 102 | contentItem: null 103 | 104 | color: checkboxDelegate.color 105 | checked: checkboxDelegate.selected 106 | onToggled: { 107 | if (checkboxDelegate.selected !== checked) { 108 | checkboxDelegate.model.selected = checked 109 | } 110 | } 111 | } 112 | } 113 | ToolTip.delay: 1000 114 | ToolTip.timeout: 5000 115 | ToolTip.visible: hovered 116 | ToolTip.text: checkboxDelegate.longtext 117 | } 118 | } 119 | DelegateChoice { 120 | roleValue: FlattenedFilterCriteriaProxyModel.RADIOBUTTON 121 | delegate: ItemDelegate { 122 | id: radioDelegate 123 | required property bool selected 124 | required text 125 | required property string longtext 126 | required property var model 127 | 128 | width: ListView.view.width 129 | leftPadding: 20 130 | onClicked: radioDelegate.model.selected = true 131 | contentItem: RowLayout { 132 | Label { 133 | text: radioDelegate.text 134 | textFormat: Text.PlainText 135 | elide: Text.ElideRight 136 | Layout.fillWidth: true 137 | } 138 | 139 | RadioButton { 140 | id: radiobox 141 | autoExclusive: true 142 | checked: radioDelegate.selected 143 | spacing: 0 144 | onToggled: { 145 | if (radioDelegate.selected !== checked) { 146 | radioDelegate.model.selected = checked 147 | } 148 | } 149 | ButtonGroup.group: priorityGroup 150 | } 151 | } 152 | ToolTip.delay: 1000 153 | ToolTip.timeout: 5000 154 | ToolTip.visible: hovered 155 | ToolTip.text: radioDelegate.longtext 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/GlobalMenu.qml: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 2 | // SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 3 | // SPDX-FileCopyrightText: 2021 Carl Schwan 4 | 5 | import Qt.labs.platform as Labs 6 | import org.kde.kirigamiaddons.statefulapp.labs as StatefulAppLabs 7 | import org.kde.kirigamiaddons.statefulapp as StatefulApp 8 | import org.kde.kjournaldbrowser 9 | 10 | Labs.MenuBar { 11 | id: root 12 | 13 | property StatefulApp.AbstractKirigamiApplication application: BrowserApplication 14 | 15 | Labs.Menu { 16 | title: i18nc("@action:menu", "File") 17 | 18 | Labs.MenuItem { 19 | text: i18n("Open system journal") 20 | icon.name: "document-open" 21 | onTriggered: { 22 | DatabaseProvider.setSystemJournal() 23 | } 24 | } 25 | Labs.MenuItem { 26 | text: i18n("Open from folder") 27 | icon.name: "document-open" 28 | onTriggered: { 29 | folderDialog.folder = DatabaseProvider.localJournalPath 30 | folderDialog.open() 31 | } 32 | } 33 | Labs.MenuItem { 34 | text: i18n("Open from file") 35 | icon.name: "document-open" 36 | onTriggered: { 37 | fileDialog.folder = DatabaseProvider.localJournalPath 38 | fileDialog.open() 39 | } 40 | } 41 | // disable option: it is not yet end-user ready 42 | // Labs.MenuItem { 43 | // text: i18n("Open remote journal stream") 44 | // icon.name: "document-import" 45 | // onTriggered: { 46 | // remoteJournalDialog.open() 47 | // } 48 | // } 49 | 50 | Labs.MenuSeparator { } 51 | 52 | Labs.MenuItem { 53 | text: i18n("Close") 54 | icon.name: "application-exit" 55 | onTriggered: Qt.quit() 56 | } 57 | } 58 | Labs.Menu { 59 | title: i18n("Current Journal") 60 | Labs.MenuItem { 61 | text: "Copy current view" 62 | icon.name: "edit-copy" 63 | onTriggered: copyViewToClipboard() 64 | } 65 | } 66 | Labs.Menu { 67 | title: i18n("View") 68 | 69 | Labs.Menu { 70 | title: i18n("Timestamp Display") 71 | 72 | Labs.MenuItem { 73 | text: i18n("Localized Realtime") 74 | checkable: true 75 | checked: BrowserApplication.timeDisplay === BrowserApplication.LOCALTIME 76 | onTriggered: { 77 | BrowserApplication.timeDisplay = BrowserApplication.LOCALTIME 78 | } 79 | } 80 | Labs.MenuItem { 81 | text: i18n("UTC Realtime") 82 | checkable: true 83 | checked: BrowserApplication.timeDisplay === BrowserApplication.UTC 84 | onTriggered: { 85 | BrowserApplication.timeDisplay = BrowserApplication.UTC 86 | } 87 | } 88 | Labs.MenuItem { 89 | text: i18n("Monotonic Time") 90 | checkable: true 91 | checked: BrowserApplication.timeDisplay === BrowserApplication.MONOTONIC_TIMESTAMP 92 | onTriggered: { 93 | BrowserApplication.timeDisplay = BrowserApplication.MONOTONIC_TIMESTAMP 94 | } 95 | } 96 | } 97 | Labs.Menu { 98 | title: i18n("Colorize") 99 | 100 | Labs.MenuItem { 101 | text: i18n("Systemd Unit") 102 | checkable: true 103 | checked: BrowserApplication.filterCriterium === BrowserApplication.SYSTEMD_UNIT 104 | onTriggered: { 105 | BrowserApplication.filterCriterium = BrowserApplication.SYSTEMD_UNIT 106 | } 107 | } 108 | Labs.MenuItem { 109 | text: i18n("Executable") 110 | checkable: true 111 | checked: BrowserApplication.filterCriterium === BrowserApplication.EXECUTABLE 112 | onTriggered: { 113 | BrowserApplication.filterCriterium = BrowserApplication.EXECUTABLE 114 | } 115 | } 116 | } 117 | } 118 | Labs.Menu { 119 | title: i18nc("@action:menu", "Help") 120 | 121 | StatefulAppLabs.NativeMenuItem { 122 | actionName: "open_about_page" 123 | application: root.application 124 | } 125 | 126 | StatefulAppLabs.NativeMenuItem { 127 | actionName: "open_about_kde_page" 128 | application: root.application 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/LogLine.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | import QtQuick 7 | import QtQuick.Controls.Material 8 | import org.kde.kjournaldbrowser 9 | 10 | Item { 11 | id: root 12 | required property entry logEntry 13 | readonly property bool __isHighlighted : logEntry.matches(TextSearch.needle, TextSearch.caseSensitive) 14 | 15 | implicitWidth: text.implicitWidth 16 | implicitHeight: text.implicitHeight 17 | 18 | Rectangle { 19 | visible: root.__isHighlighted 20 | anchors.fill: parent 21 | color: Material.accent 22 | } 23 | Row { 24 | spacing: 4 25 | Text { 26 | readonly property string timeString: { 27 | switch (BrowserApplication.timeDisplay) { 28 | case BrowserApplication.UTC: return Formatter.formatTime(root.logEntry.date, true); 29 | case BrowserApplication.LOCALTIME: return Formatter.formatTime(root.logEntry.date, false); 30 | case BrowserApplication.MONOTONIC_TIMESTAMP: return (root.logEntry.monotonicTimestamp / 1000).toFixed(3) // display miliseconds 31 | } 32 | return "" 33 | } 34 | color: root.__isHighlighted ? Material.primaryHighlightedTextColor : Material.iconDisabledColor 35 | text: timeString 36 | HoverHandler { 37 | id: timeHoverHandler 38 | cursorShape: Qt.PointingHandCursor 39 | } 40 | ToolTip.delay: 1000 41 | ToolTip.timeout: 5000 42 | ToolTip.visible: timeHoverHandler.hovered 43 | ToolTip.text: "UTC " + Formatter.formatTime(root.logEntry.date, true) 44 | } 45 | 46 | Text { 47 | id: text 48 | text: root.logEntry.message 49 | color: { 50 | if (root.__isHighlighted) { 51 | return Material.primaryHighlightedTextColor 52 | } 53 | 54 | switch(root.logEntry.priority) { 55 | case 0: return "#700293" // emergency (violet) 56 | case 1: return "#930269" // alert 57 | case 2: return "#930202" // critical 58 | case 3: return "#ff0000" // error (red) 59 | case 4: return "#cc9c00" // warning (orange) 60 | case 5: return "#015eff" // notice (blue) 61 | case 6: return "#029346" // information (green) 62 | case 7: return "#000000" // debug 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | import QtQuick 7 | import QtQuick.Controls 8 | import QtQuick.Layouts 9 | 10 | Dialog { 11 | id: root 12 | 13 | readonly property string url : urlTextField.text 14 | readonly property int port : portSpinbox.value 15 | 16 | title: i18n("Configure Remote Journal Server") 17 | standardButtons: Dialog.Ok | Dialog.Cancel 18 | width: 300 19 | height: 200 20 | 21 | GridLayout { 22 | columns: 2 23 | anchors.fill: parent 24 | anchors.margins: 10 25 | rowSpacing: 10 26 | columnSpacing: 10 27 | 28 | // first line 29 | Label { 30 | text: i18n("URL:") 31 | } 32 | TextField { 33 | id: urlTextField 34 | text: "http://127.0.0.1" 35 | } 36 | 37 | // second line 38 | Label { 39 | text: i18n("Port:") 40 | } 41 | SpinBox { 42 | id: portSpinbox 43 | value: 19531 44 | to: 99999 45 | editable: true 46 | textFromValue: function(value, locale) { 47 | return Number(value) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/TopMenuBar.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | import QtQuick 7 | import QtQuick.Controls 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.statefulapp as StatefulApp 10 | import org.kde.kjournaldbrowser 11 | 12 | MenuBar { 13 | id: root 14 | 15 | property StatefulApp.AbstractKirigamiApplication application: BrowserApplication 16 | 17 | signal copyViewToClipboard() 18 | 19 | Kirigami.Theme.colorSet: Kirigami.Theme.Header 20 | Menu { 21 | title: i18nc("@action:menu", "File") 22 | MenuItem { 23 | text: i18n("Open System Journal") 24 | icon.name: "document-open" 25 | onTriggered: { 26 | DatabaseProvider.setSystemJournal() 27 | } 28 | } 29 | MenuItem { 30 | text: i18n("Open from Folder") 31 | icon.name: "document-open" 32 | onTriggered: { 33 | folderDialog.currentFolder = DatabaseProvider.localJournalPath 34 | folderDialog.open() 35 | } 36 | } 37 | MenuItem { 38 | text: i18n("Open from File") 39 | icon.name: "document-open" 40 | onTriggered: { 41 | fileDialog.currentFolder = DatabaseProvider.localJournalPath 42 | fileDialog.open() 43 | } 44 | } 45 | // disable feature for end-users until the dialog experience is more polished 46 | // MenuItem { 47 | // text: i18n("Open Remote Journal Stream") 48 | // icon.name: "document-import" 49 | // onTriggered: { 50 | // remoteJournalDialog.open() 51 | // } 52 | // } 53 | 54 | MenuSeparator { } 55 | 56 | MenuItem { 57 | text: i18n("Close") 58 | icon.name: "application-exit" 59 | onTriggered: Qt.quit() 60 | } 61 | } 62 | Menu { 63 | title: i18n("Current Journal") 64 | MenuItem { 65 | text: "Copy Current View" 66 | icon.name: "edit-copy" 67 | onTriggered: root.copyViewToClipboard() 68 | } 69 | } 70 | Menu { 71 | title: i18n("View") 72 | 73 | Menu { 74 | title: i18n("Timestamp Display") 75 | 76 | MenuItem { 77 | contentItem: RadioButton { 78 | text: i18n("Localized Realtime") 79 | checkable: true 80 | checked: BrowserApplication.timeDisplay === BrowserApplication.LOCALTIME 81 | onToggled: { 82 | BrowserApplication.timeDisplay = BrowserApplication.LOCALTIME 83 | } 84 | } 85 | } 86 | MenuItem { 87 | contentItem: RadioButton { 88 | text: i18n("UTC Realtime") 89 | checkable: true 90 | checked: BrowserApplication.timeDisplay === BrowserApplication.UTC 91 | onToggled: { 92 | BrowserApplication.timeDisplay = BrowserApplication.UTC 93 | } 94 | } 95 | } 96 | MenuItem { 97 | contentItem: RadioButton { 98 | text: i18n("Monotonic Time") 99 | checkable: true 100 | checked: BrowserApplication.timeDisplay === BrowserApplication.MONOTONIC_TIMESTAMP 101 | onToggled: { 102 | BrowserApplication.timeDisplay = BrowserApplication.MONOTONIC_TIMESTAMP 103 | } 104 | } 105 | } 106 | } 107 | Menu { 108 | title: i18n("Colorize") 109 | 110 | MenuItem { 111 | contentItem: RadioButton { 112 | text: i18n("Systemd Unit") 113 | checkable: true 114 | checked: BrowserApplication.filterCriterium === BrowserApplication.SYSTEMD_UNIT 115 | onToggled: { 116 | BrowserApplication.filterCriterium = BrowserApplication.SYSTEMD_UNIT 117 | } 118 | } 119 | } 120 | MenuItem { 121 | contentItem: RadioButton { 122 | text: i18n("Executable") 123 | checkable: true 124 | checked: BrowserApplication.filterCriterium === BrowserApplication.EXECUTABLE 125 | onToggled: { 126 | BrowserApplication.filterCriterium = BrowserApplication.EXECUTABLE 127 | } 128 | } 129 | } 130 | } 131 | } 132 | Menu { 133 | title: i18nc("@action:menu", "Help") 134 | 135 | Kirigami.Action { 136 | fromQAction: root.application.action('open_about_page') 137 | } 138 | 139 | Kirigami.Action { 140 | fromQAction: root.application.action('open_about_kde_page') 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/browserapplication.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "browserapplication.h" 7 | 8 | BrowserApplication::BrowserApplication(QObject *parent) 9 | : AbstractKirigamiApplication(parent) 10 | { 11 | mTimeDisplayFormat = mSettings.value("browser/timedisplay").value(); 12 | mFilterCriterium = mSettings.value("browser/filtercriterium").value(); 13 | 14 | BrowserApplication::setupActions(); 15 | } 16 | 17 | BrowserApplication::~BrowserApplication() 18 | { 19 | mSettings.sync(); 20 | } 21 | 22 | void BrowserApplication::setupActions() 23 | { 24 | AbstractKirigamiApplication::setupActions(); 25 | // no own actions defined yet 26 | } 27 | 28 | void BrowserApplication::setTimeDisplay(BrowserApplication::TimeDisplay format) 29 | { 30 | if (format == mTimeDisplayFormat) { 31 | return; 32 | } 33 | mTimeDisplayFormat = format; 34 | mSettings.setValue("browser/timedisplay", QVariant::fromValue(static_cast(format))); 35 | Q_EMIT timeDisplayChanged(); 36 | } 37 | 38 | BrowserApplication::TimeDisplay BrowserApplication::timeDisplay() const 39 | { 40 | return mTimeDisplayFormat; 41 | } 42 | 43 | void BrowserApplication::setFilterCriterium(FilterCriterium criterium) 44 | { 45 | if (criterium == mFilterCriterium) { 46 | return; 47 | } 48 | mFilterCriterium = criterium; 49 | mSettings.setValue("browser/filtercriterium", QVariant::fromValue(static_cast(criterium))); 50 | Q_EMIT filterCriteriumChanged(); 51 | } 52 | 53 | BrowserApplication::FilterCriterium BrowserApplication::filterCriterium() const 54 | { 55 | return mFilterCriterium; 56 | } 57 | 58 | void BrowserApplication::setViewMode(ViewMode mode) 59 | { 60 | if (mode == mViewMode) { 61 | return; 62 | } 63 | mViewMode = mode; 64 | Q_EMIT viewModeChanged(); 65 | } 66 | 67 | BrowserApplication::ViewMode BrowserApplication::viewMode() const 68 | { 69 | return mViewMode; 70 | } 71 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/browserapplication.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef BROWSERAPPLICATION_H 7 | #define BROWSERAPPLICATION_H 8 | 9 | #include 10 | #include 11 | 12 | class BrowserApplication : public AbstractKirigamiApplication 13 | { 14 | Q_OBJECT 15 | QML_ELEMENT 16 | QML_SINGLETON 17 | 18 | Q_PROPERTY(BrowserApplication::TimeDisplay timeDisplay READ timeDisplay WRITE setTimeDisplay NOTIFY timeDisplayChanged) 19 | Q_PROPERTY(BrowserApplication::FilterCriterium filterCriterium READ filterCriterium WRITE setFilterCriterium NOTIFY filterCriteriumChanged) 20 | Q_PROPERTY(BrowserApplication::ViewMode viewMode READ viewMode WRITE setViewMode NOTIFY viewModeChanged) 21 | 22 | public: 23 | enum class ViewMode { 24 | BROWSE, //!< interaction with log view leads to browsing 25 | SELECT, //!< interaction with log view leads to text selection 26 | }; 27 | Q_ENUM(ViewMode); 28 | 29 | enum class TimeDisplay : uint8_t { 30 | LOCALTIME, //!< display time as local time 31 | UTC, //!< display time as UTC time 32 | MONOTONIC_TIMESTAMP //!< display monotonic timestamp 33 | }; 34 | Q_ENUM(TimeDisplay); 35 | 36 | enum class FilterCriterium : uint8_t { 37 | SYSTEMD_UNIT, //!< filter by systemd unit to which the log belongs 38 | EXECUTABLE, //!< filter by executable name to which the log belogs 39 | }; 40 | Q_ENUM(FilterCriterium); 41 | 42 | BrowserApplication(QObject *parent = nullptr); 43 | ~BrowserApplication() override; 44 | 45 | void setTimeDisplay(TimeDisplay format); 46 | 47 | TimeDisplay timeDisplay() const; 48 | 49 | void setFilterCriterium(FilterCriterium criterium); 50 | 51 | FilterCriterium filterCriterium() const; 52 | 53 | void setViewMode(ViewMode mode); 54 | 55 | ViewMode viewMode() const; 56 | 57 | protected: 58 | void setupActions() override; 59 | 60 | Q_SIGNALS: 61 | void timeDisplayChanged(); 62 | void filterCriteriumChanged(); 63 | void viewModeChanged(); 64 | 65 | private: 66 | TimeDisplay mTimeDisplayFormat{TimeDisplay::UTC}; 67 | FilterCriterium mFilterCriterium{FilterCriterium::SYSTEMD_UNIT}; 68 | ViewMode mViewMode{ViewMode::BROWSE}; 69 | QSettings mSettings; 70 | }; 71 | 72 | #endif // BROWSERAPPLICATION_H 73 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/clipboardproxy.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "clipboardproxy.h" 7 | #include 8 | #include 9 | 10 | ClipboardProxy::ClipboardProxy(QObject *parent) 11 | : QObject(parent) 12 | { 13 | } 14 | 15 | void ClipboardProxy::setText(const QString &text) 16 | { 17 | QClipboard *clipboard = QGuiApplication::clipboard(); 18 | if (clipboard) { 19 | clipboard->setText(text); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/clipboardproxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef CLIPBOARDPROXY_H 7 | #define CLIPBOARDPROXY_H 8 | 9 | #include 10 | #include 11 | 12 | class ClipboardProxy : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | QML_ELEMENT 17 | QML_SINGLETON 18 | public: 19 | explicit ClipboardProxy(QObject *parent = nullptr); 20 | Q_INVOKABLE void setText(const QString &text); 21 | }; 22 | 23 | #endif // CLIPBOARDPROXY_H 24 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/databaseprovider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "databaseprovider.h" 7 | #include "kjournaldlib_log_general.h" 8 | #include 9 | 10 | DatabaseProvider::DatabaseProvider(QObject *parent) 11 | : QObject(parent) 12 | { 13 | } 14 | 15 | DatabaseProvider::~DatabaseProvider() = default; 16 | 17 | DatabaseProvider::Mode DatabaseProvider::mode() const 18 | { 19 | return mMode; 20 | } 21 | 22 | void DatabaseProvider::setSystemJournal() 23 | { 24 | // not setting any path defaults to system journal 25 | mJournalPath = QString(); 26 | mMode = Mode::SYSTEM; 27 | Q_EMIT journalPathChanged(); 28 | } 29 | 30 | QString DatabaseProvider::journalPath() const 31 | { 32 | return mJournalPath; 33 | } 34 | 35 | void DatabaseProvider::setLocalJournalPath(const QString &path) 36 | { 37 | qCDebug(KJOURNALDLIB_GENERAL) << "Open path" << path; 38 | // handle QUrl conversion for QML access 39 | QString resolvedPath = path; 40 | if (path.startsWith("file://")) { 41 | resolvedPath.remove(0, 7); 42 | } 43 | 44 | if (resolvedPath == mJournalPath) { 45 | return; 46 | } 47 | mJournalPath = resolvedPath; 48 | mMode = Mode::LOCALFOLDER; 49 | Q_EMIT journalPathChanged(); 50 | } 51 | 52 | void DatabaseProvider::setRemoteJournalUrl(const QString &url, quint32 port) 53 | { 54 | if (url == mRemoteJournalUrl && port == mRemoteJournalPort) { 55 | return; 56 | } 57 | mRemoteJournalUrl = url; 58 | mRemoteJournalPort = port; 59 | initRemoteJournal(); 60 | mMode = Mode::REMOTE; 61 | Q_EMIT journalPathChanged(); 62 | } 63 | 64 | QString DatabaseProvider::remoteJournalUrl() const 65 | { 66 | return mRemoteJournalUrl; 67 | } 68 | 69 | quint32 DatabaseProvider::remoteJournalPort() const 70 | { 71 | return mRemoteJournalPort; 72 | } 73 | 74 | QString DatabaseProvider::localJournalPath() const 75 | { 76 | return mJournalPath; 77 | } 78 | 79 | void DatabaseProvider::initRemoteJournal() 80 | { 81 | if (mRemoteJournalUrl.isEmpty() || mRemoteJournalPort == 0) { 82 | return; 83 | } 84 | mRemoteJournal = std::make_unique(mRemoteJournalUrl, QString::number(mRemoteJournalPort)); 85 | connect(mRemoteJournal.get(), &SystemdJournalRemote::journalFileChanged, this, [=]() { 86 | setLocalJournalPath(QFileInfo(mRemoteJournal->journalFile()).absolutePath()); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/databaseprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2021-2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef DATABASEPROVIDER_H 7 | #define DATABASEPROVIDER_H 8 | 9 | #include "systemdjournalremote.h" 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * @brief Provides path to current journald database 16 | */ 17 | class DatabaseProvider : public QObject 18 | { 19 | Q_OBJECT 20 | Q_PROPERTY(DatabaseProvider::Mode mode READ mode NOTIFY journalPathChanged) 21 | 22 | Q_PROPERTY(QString journalPath READ journalPath NOTIFY journalPathChanged) 23 | /** 24 | * @note this path can either be a directory or a file 25 | */ 26 | Q_PROPERTY(QString localJournalPath READ localJournalPath NOTIFY journalPathChanged) 27 | Q_PROPERTY(QString remoteJournalUrl READ remoteJournalUrl NOTIFY journalPathChanged) 28 | Q_PROPERTY(quint32 remoteJournalPort READ remoteJournalPort NOTIFY journalPathChanged) 29 | 30 | QML_ELEMENT 31 | QML_SINGLETON 32 | 33 | public: 34 | enum class Mode { 35 | LOCALFOLDER, //!< local available journald folder 36 | SYSTEM, //!< local system journald database 37 | REMOTE, //!< reading from remote port 38 | }; 39 | Q_ENUM(Mode); 40 | 41 | explicit DatabaseProvider(QObject *parent = nullptr); 42 | ~DatabaseProvider() override; 43 | 44 | DatabaseProvider::Mode mode() const; 45 | 46 | Q_INVOKABLE void setSystemJournal(); 47 | Q_INVOKABLE void setLocalJournalPath(const QString &path); 48 | Q_INVOKABLE void setRemoteJournalUrl(const QString &url, quint32 port); 49 | 50 | QString journalPath() const; 51 | QString localJournalPath() const; 52 | QString remoteJournalUrl() const; 53 | quint32 remoteJournalPort() const; 54 | 55 | Q_SIGNALS: 56 | void journalPathChanged(); 57 | 58 | private: 59 | void initRemoteJournal(); 60 | 61 | Mode mMode{Mode::SYSTEM}; 62 | QString mJournalPath; 63 | QString mRemoteJournalUrl; 64 | quint32 mRemoteJournalPort{0}; 65 | std::unique_ptr mRemoteJournal; 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/formatter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "formatter.h" 7 | #include 8 | #include 9 | 10 | Formatter::Formatter(QObject *parent) 11 | : QObject(parent) 12 | { 13 | } 14 | 15 | QString Formatter::formatTime(const QDateTime &datetime, bool utc) const 16 | { 17 | if (utc) { 18 | return datetime.toUTC().time().toString(QLatin1String("HH:mm:ss.zzz")); 19 | } else { 20 | return datetime.time().toString(QLatin1String("HH:mm:ss.zzz")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/formatter.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef FORMATTER_H 7 | #define FORMATTER_H 8 | 9 | #include 10 | #include 11 | 12 | class Formatter : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | QML_ELEMENT 17 | QML_SINGLETON 18 | public: 19 | explicit Formatter(QObject *parent = nullptr); 20 | 21 | /** 22 | * @brief Format time into string 23 | * @param datetime the datetime object 24 | * @param utc if set to true, the string will be UTC time, otherwise according to the current local 25 | * @return formatted string 26 | */ 27 | Q_INVOKABLE QString formatTime(const QDateTime &datetime, bool utc) const; 28 | }; 29 | 30 | #endif // FORMATTER_H 31 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/textsearch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #include "textsearch.h" 7 | 8 | TextSearch::TextSearch(QObject *parent) 9 | : QObject{parent} 10 | { 11 | } 12 | 13 | QString TextSearch::needle() const 14 | { 15 | return m_needle; 16 | } 17 | 18 | void TextSearch::setNeedle(const QString &needle) 19 | { 20 | if (m_needle == needle) { 21 | return; 22 | } 23 | m_needle = needle; 24 | Q_EMIT needleChanged(); 25 | } 26 | 27 | bool TextSearch::isHighlightMode() const 28 | { 29 | return m_hightlightMode; 30 | } 31 | 32 | void TextSearch::setHighlightMode(bool highlightMode) 33 | { 34 | if (m_hightlightMode == highlightMode) { 35 | return; 36 | } 37 | m_hightlightMode = highlightMode; 38 | Q_EMIT highlightModeChanged(); 39 | } 40 | 41 | bool TextSearch::isCaseSensitive() const 42 | { 43 | if (m_caseSensitive == Qt::CaseSensitivity::CaseSensitive) { 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | void TextSearch::setCaseSensitive(bool caseSensitive) 51 | { 52 | const auto nextCaseSensitive = caseSensitive ? Qt::CaseSensitivity::CaseSensitive : Qt::CaseSensitivity::CaseInsensitive; 53 | if (nextCaseSensitive == m_caseSensitive) { 54 | return; 55 | } 56 | m_caseSensitive = nextCaseSensitive; 57 | Q_EMIT caseSensitiveChanged(); 58 | } 59 | -------------------------------------------------------------------------------- /org/kde/kjournaldbrowser/textsearch.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-or-later OR MIT 3 | SPDX-FileCopyrightText: 2025 Andreas Cord-Landwehr 4 | */ 5 | 6 | #ifndef TEXTSEARCH_H 7 | #define TEXTSEARCH_H 8 | 9 | #include 10 | #include 11 | 12 | class TextSearch : public QObject 13 | { 14 | Q_OBJECT 15 | 16 | Q_PROPERTY(QString needle READ needle WRITE setNeedle NOTIFY needleChanged FINAL) 17 | Q_PROPERTY(bool highlightMode READ isHighlightMode WRITE setHighlightMode NOTIFY highlightModeChanged FINAL) 18 | Q_PROPERTY(bool caseSensitive READ isCaseSensitive WRITE setCaseSensitive NOTIFY caseSensitiveChanged FINAL) 19 | 20 | QML_ELEMENT 21 | QML_SINGLETON 22 | 23 | public: 24 | explicit TextSearch(QObject *parent = nullptr); 25 | QString needle() const; 26 | void setNeedle(const QString &needle); 27 | bool isHighlightMode() const; 28 | void setHighlightMode(bool highlightMode); 29 | bool isCaseSensitive() const; 30 | void setCaseSensitive(bool caseSensitive); 31 | 32 | Q_SIGNALS: 33 | void needleChanged(); 34 | void highlightModeChanged(); 35 | void caseSensitiveChanged(); 36 | 37 | private: 38 | QString m_needle; 39 | bool m_hightlightMode{false}; 40 | Qt::CaseSensitivity m_caseSensitive = Qt::CaseSensitivity::CaseInsensitive; 41 | }; 42 | 43 | #endif // TEXTSEARCH_H 44 | -------------------------------------------------------------------------------- /po/hi/kjournald.po: -------------------------------------------------------------------------------- 1 | # Hindi translations for kjournald package. 2 | # Copyright (C) 2024 This file is copyright: 3 | # This file is distributed under the same license as the kjournald package. 4 | # Kali , 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: kjournald\n" 9 | "Report-Msgid-Bugs-To: https://bugs.kde.org\n" 10 | "POT-Creation-Date: 2025-05-30 00:45+0000\n" 11 | "PO-Revision-Date: 2024-12-15 15:57+0530\n" 12 | "Last-Translator: Kali \n" 13 | "Language-Team: Hindi \n" 14 | "Language: hi\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n!=1);\n" 19 | 20 | #: browser/main.cpp:40 21 | #, kde-format 22 | msgctxt "@title Displayed program name" 23 | msgid "KJournald Browser" 24 | msgstr "" 25 | 26 | #: browser/main.cpp:42 27 | #, kde-format 28 | msgctxt "@title KAboutData: short program description" 29 | msgid "Viewer for Journald logs" 30 | msgstr "" 31 | 32 | #: browser/main.cpp:44 33 | #, kde-format 34 | msgctxt "@info:credit" 35 | msgid "(c) 2021-2025 The KJournald Developers" 36 | msgstr "" 37 | 38 | #: browser/main.cpp:45 39 | #, kde-format 40 | msgctxt "@title Short program description" 41 | msgid "" 42 | "Viewer for Journald databases, which are generated by the Journald logging " 43 | "tool." 44 | msgstr "" 45 | 46 | #: browser/main.cpp:47 47 | #, kde-format 48 | msgctxt "@info:credit Developer name" 49 | msgid "Andreas Cord-Landwehr" 50 | msgstr "" 51 | 52 | #: browser/main.cpp:48 53 | #, kde-format 54 | msgctxt "@info:credit Role" 55 | msgid "Original Author" 56 | msgstr "" 57 | 58 | #: browser/main.cpp:50 59 | #, kde-format 60 | msgctxt "NAME OF TRANSLATORS" 61 | msgid "Your names" 62 | msgstr "" 63 | 64 | #: browser/main.cpp:50 65 | #, kde-format 66 | msgctxt "EMAIL OF TRANSLATORS" 67 | msgid "Your emails" 68 | msgstr "" 69 | 70 | #: org/kde/kjournald/filtercriteriamodel.cpp:23 71 | #, kde-format 72 | msgctxt "Radio box option, log priority value" 73 | msgid "Emergency" 74 | msgstr "" 75 | 76 | #: org/kde/kjournald/filtercriteriamodel.cpp:25 77 | #, kde-format 78 | msgctxt "Radio box option, log priority value" 79 | msgid "Alert" 80 | msgstr "" 81 | 82 | #: org/kde/kjournald/filtercriteriamodel.cpp:27 83 | #, kde-format 84 | msgctxt "Radio box option, log priority value" 85 | msgid "Critical" 86 | msgstr "" 87 | 88 | #: org/kde/kjournald/filtercriteriamodel.cpp:29 89 | #, kde-format 90 | msgctxt "Radio box option, log priority value" 91 | msgid "Error" 92 | msgstr "" 93 | 94 | #: org/kde/kjournald/filtercriteriamodel.cpp:31 95 | #, kde-format 96 | msgctxt "Radio box option, log priority value" 97 | msgid "Warning" 98 | msgstr "" 99 | 100 | #: org/kde/kjournald/filtercriteriamodel.cpp:33 101 | #, kde-format 102 | msgctxt "Radio box option, log priority value" 103 | msgid "Notice" 104 | msgstr "" 105 | 106 | #: org/kde/kjournald/filtercriteriamodel.cpp:35 107 | #, kde-format 108 | msgctxt "Radio box option, log priority value" 109 | msgid "Info" 110 | msgstr "" 111 | 112 | #: org/kde/kjournald/filtercriteriamodel.cpp:37 113 | #, kde-format 114 | msgctxt "Radio box option, log priority value" 115 | msgid "Debug" 116 | msgstr "" 117 | 118 | #: org/kde/kjournald/filtercriteriamodel.cpp:39 119 | #, kde-format 120 | msgctxt "Radio box option, log priority value" 121 | msgid "No Filter" 122 | msgstr "" 123 | 124 | #: org/kde/kjournald/filtercriteriamodel.cpp:136 125 | #, kde-format 126 | msgctxt "Section title for log message source" 127 | msgid "Transport" 128 | msgstr "" 129 | 130 | #: org/kde/kjournald/filtercriteriamodel.cpp:143 131 | #, kde-format 132 | msgctxt "Checkbox option for kernel log messages" 133 | msgid "Kernel" 134 | msgstr "" 135 | 136 | #: org/kde/kjournald/filtercriteriamodel.cpp:150 137 | #, kde-format 138 | msgctxt "Section title for log message priority" 139 | msgid "Priority" 140 | msgstr "" 141 | 142 | #: org/kde/kjournald/filtercriteriamodel.cpp:171 143 | #, kde-format 144 | msgctxt "Section title for systemd unit" 145 | msgid "Unit" 146 | msgstr "" 147 | 148 | #: org/kde/kjournald/filtercriteriamodel.cpp:199 149 | #, kde-format 150 | msgctxt "Section title for process list" 151 | msgid "Process" 152 | msgstr "" 153 | 154 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:34 155 | #, kde-format 156 | msgid "Expanded" 157 | msgstr "" 158 | 159 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:34 160 | #, kde-format 161 | msgid "Collapsed" 162 | msgstr "" 163 | 164 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:60 165 | #, kde-format 166 | msgid "Clear" 167 | msgstr "" 168 | 169 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:16 170 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:21 171 | #, kde-format 172 | msgctxt "@action:menu" 173 | msgid "File" 174 | msgstr "" 175 | 176 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:19 177 | #, kde-format 178 | msgid "Open system journal" 179 | msgstr "" 180 | 181 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:26 182 | #, kde-format 183 | msgid "Open from folder" 184 | msgstr "" 185 | 186 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:34 187 | #, kde-format 188 | msgid "Open from file" 189 | msgstr "" 190 | 191 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:53 192 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:57 193 | #, kde-format 194 | msgid "Close" 195 | msgstr "" 196 | 197 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:59 198 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:63 199 | #, kde-format 200 | msgid "Current Journal" 201 | msgstr "" 202 | 203 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:67 204 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:71 205 | #, kde-format 206 | msgid "View" 207 | msgstr "" 208 | 209 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:70 210 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:74 211 | #, kde-format 212 | msgid "Timestamp Display" 213 | msgstr "" 214 | 215 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:73 216 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:78 217 | #, kde-format 218 | msgid "Localized Realtime" 219 | msgstr "" 220 | 221 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:81 222 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:88 223 | #, kde-format 224 | msgid "UTC Realtime" 225 | msgstr "" 226 | 227 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:89 228 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:98 229 | #, kde-format 230 | msgid "Monotonic Time" 231 | msgstr "" 232 | 233 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:98 234 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:108 235 | #, kde-format 236 | msgid "Colorize" 237 | msgstr "" 238 | 239 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:101 240 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:112 241 | #, kde-format 242 | msgid "Systemd Unit" 243 | msgstr "" 244 | 245 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:109 246 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:122 247 | #, kde-format 248 | msgid "Executable" 249 | msgstr "" 250 | 251 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:119 252 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:133 253 | #, kde-format 254 | msgctxt "@action:menu" 255 | msgid "Help" 256 | msgstr "" 257 | 258 | #: org/kde/kjournaldbrowser/LogView.qml:207 259 | #, kde-format 260 | msgid "Kernel" 261 | msgstr "" 262 | 263 | #: org/kde/kjournaldbrowser/Main.qml:62 264 | #, kde-format 265 | msgid "Boot:" 266 | msgstr "" 267 | 268 | #: org/kde/kjournaldbrowser/Main.qml:173 269 | #, kde-format 270 | msgctxt "@info:tooltip" 271 | msgid "Switch between case sensitive and case insensitive matching" 272 | msgstr "" 273 | 274 | #: org/kde/kjournaldbrowser/Main.qml:215 275 | #, kde-format 276 | msgctxt "@info:tooltip" 277 | msgid "Switch to browsing mode" 278 | msgstr "" 279 | 280 | #: org/kde/kjournaldbrowser/Main.qml:216 281 | #, kde-format 282 | msgctxt "@info:tooltip" 283 | msgid "Switch to selection mode" 284 | msgstr "" 285 | 286 | #: org/kde/kjournaldbrowser/Main.qml:228 287 | #, kde-format 288 | msgid "Select journal folder" 289 | msgstr "" 290 | 291 | #: org/kde/kjournaldbrowser/Main.qml:236 292 | #, kde-format 293 | msgid "Select journal file" 294 | msgstr "" 295 | 296 | #: org/kde/kjournaldbrowser/Main.qml:237 297 | #, kde-format 298 | msgid "Journal files (*.journal)" 299 | msgstr "" 300 | 301 | #: org/kde/kjournaldbrowser/Main.qml:237 302 | #, kde-format 303 | msgid "All files (*)" 304 | msgstr "" 305 | 306 | #: org/kde/kjournaldbrowser/Main.qml:307 307 | #, kde-format 308 | msgid "No log entries apply " 309 | msgstr "" 310 | 311 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:16 312 | #, kde-format 313 | msgid "Configure Remote Journal Server" 314 | msgstr "" 315 | 316 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:30 317 | #, kde-format 318 | msgid "URL:" 319 | msgstr "" 320 | 321 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:39 322 | #, kde-format 323 | msgid "Port:" 324 | msgstr "" 325 | 326 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:23 327 | #, kde-format 328 | msgid "Open System Journal" 329 | msgstr "" 330 | 331 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:30 332 | #, kde-format 333 | msgid "Open from Folder" 334 | msgstr "" 335 | 336 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:38 337 | #, kde-format 338 | msgid "Open from File" 339 | msgstr "" 340 | -------------------------------------------------------------------------------- /po/ja/kjournald.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: kjournald\n" 4 | "Report-Msgid-Bugs-To: https://bugs.kde.org\n" 5 | "POT-Creation-Date: 2025-05-30 00:45+0000\n" 6 | "PO-Revision-Date: 2021-12-29 21:20-0800\n" 7 | "Last-Translator: Japanese KDE translation team \n" 8 | "Language-Team: Japanese \n" 9 | "Language: ja\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | "X-Accelerator-Marker: &\n" 15 | "X-Text-Markup: kde4\n" 16 | 17 | #: browser/main.cpp:40 18 | #, kde-format 19 | msgctxt "@title Displayed program name" 20 | msgid "KJournald Browser" 21 | msgstr "" 22 | 23 | #: browser/main.cpp:42 24 | #, kde-format 25 | msgctxt "@title KAboutData: short program description" 26 | msgid "Viewer for Journald logs" 27 | msgstr "" 28 | 29 | #: browser/main.cpp:44 30 | #, kde-format 31 | msgctxt "@info:credit" 32 | msgid "(c) 2021-2025 The KJournald Developers" 33 | msgstr "" 34 | 35 | #: browser/main.cpp:45 36 | #, kde-format 37 | msgctxt "@title Short program description" 38 | msgid "" 39 | "Viewer for Journald databases, which are generated by the Journald logging " 40 | "tool." 41 | msgstr "" 42 | 43 | #: browser/main.cpp:47 44 | #, kde-format 45 | msgctxt "@info:credit Developer name" 46 | msgid "Andreas Cord-Landwehr" 47 | msgstr "" 48 | 49 | #: browser/main.cpp:48 50 | #, kde-format 51 | msgctxt "@info:credit Role" 52 | msgid "Original Author" 53 | msgstr "" 54 | 55 | #: browser/main.cpp:50 56 | #, kde-format 57 | msgctxt "NAME OF TRANSLATORS" 58 | msgid "Your names" 59 | msgstr "" 60 | 61 | #: browser/main.cpp:50 62 | #, kde-format 63 | msgctxt "EMAIL OF TRANSLATORS" 64 | msgid "Your emails" 65 | msgstr "" 66 | 67 | #: org/kde/kjournald/filtercriteriamodel.cpp:23 68 | #, kde-format 69 | msgctxt "Radio box option, log priority value" 70 | msgid "Emergency" 71 | msgstr "" 72 | 73 | #: org/kde/kjournald/filtercriteriamodel.cpp:25 74 | #, kde-format 75 | msgctxt "Radio box option, log priority value" 76 | msgid "Alert" 77 | msgstr "" 78 | 79 | #: org/kde/kjournald/filtercriteriamodel.cpp:27 80 | #, kde-format 81 | msgctxt "Radio box option, log priority value" 82 | msgid "Critical" 83 | msgstr "" 84 | 85 | #: org/kde/kjournald/filtercriteriamodel.cpp:29 86 | #, kde-format 87 | msgctxt "Radio box option, log priority value" 88 | msgid "Error" 89 | msgstr "" 90 | 91 | #: org/kde/kjournald/filtercriteriamodel.cpp:31 92 | #, kde-format 93 | msgctxt "Radio box option, log priority value" 94 | msgid "Warning" 95 | msgstr "" 96 | 97 | #: org/kde/kjournald/filtercriteriamodel.cpp:33 98 | #, kde-format 99 | msgctxt "Radio box option, log priority value" 100 | msgid "Notice" 101 | msgstr "" 102 | 103 | #: org/kde/kjournald/filtercriteriamodel.cpp:35 104 | #, kde-format 105 | msgctxt "Radio box option, log priority value" 106 | msgid "Info" 107 | msgstr "" 108 | 109 | #: org/kde/kjournald/filtercriteriamodel.cpp:37 110 | #, kde-format 111 | msgctxt "Radio box option, log priority value" 112 | msgid "Debug" 113 | msgstr "" 114 | 115 | #: org/kde/kjournald/filtercriteriamodel.cpp:39 116 | #, kde-format 117 | msgctxt "Radio box option, log priority value" 118 | msgid "No Filter" 119 | msgstr "" 120 | 121 | #: org/kde/kjournald/filtercriteriamodel.cpp:136 122 | #, kde-format 123 | msgctxt "Section title for log message source" 124 | msgid "Transport" 125 | msgstr "" 126 | 127 | #: org/kde/kjournald/filtercriteriamodel.cpp:143 128 | #, kde-format 129 | msgctxt "Checkbox option for kernel log messages" 130 | msgid "Kernel" 131 | msgstr "" 132 | 133 | #: org/kde/kjournald/filtercriteriamodel.cpp:150 134 | #, kde-format 135 | msgctxt "Section title for log message priority" 136 | msgid "Priority" 137 | msgstr "" 138 | 139 | #: org/kde/kjournald/filtercriteriamodel.cpp:171 140 | #, kde-format 141 | msgctxt "Section title for systemd unit" 142 | msgid "Unit" 143 | msgstr "" 144 | 145 | #: org/kde/kjournald/filtercriteriamodel.cpp:199 146 | #, kde-format 147 | msgctxt "Section title for process list" 148 | msgid "Process" 149 | msgstr "" 150 | 151 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:34 152 | #, kde-format 153 | msgid "Expanded" 154 | msgstr "" 155 | 156 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:34 157 | #, kde-format 158 | msgid "Collapsed" 159 | msgstr "" 160 | 161 | #: org/kde/kjournaldbrowser/FilterCriteriaView.qml:60 162 | #, kde-format 163 | msgid "Clear" 164 | msgstr "" 165 | 166 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:16 167 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:21 168 | #, kde-format 169 | msgctxt "@action:menu" 170 | msgid "File" 171 | msgstr "" 172 | 173 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:19 174 | #, kde-format 175 | msgid "Open system journal" 176 | msgstr "" 177 | 178 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:26 179 | #, kde-format 180 | msgid "Open from folder" 181 | msgstr "" 182 | 183 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:34 184 | #, kde-format 185 | msgid "Open from file" 186 | msgstr "" 187 | 188 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:53 189 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:57 190 | #, kde-format 191 | msgid "Close" 192 | msgstr "" 193 | 194 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:59 195 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:63 196 | #, kde-format 197 | msgid "Current Journal" 198 | msgstr "" 199 | 200 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:67 201 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:71 202 | #, kde-format 203 | msgid "View" 204 | msgstr "" 205 | 206 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:70 207 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:74 208 | #, kde-format 209 | msgid "Timestamp Display" 210 | msgstr "" 211 | 212 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:73 213 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:78 214 | #, kde-format 215 | msgid "Localized Realtime" 216 | msgstr "" 217 | 218 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:81 219 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:88 220 | #, kde-format 221 | msgid "UTC Realtime" 222 | msgstr "" 223 | 224 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:89 225 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:98 226 | #, kde-format 227 | msgid "Monotonic Time" 228 | msgstr "" 229 | 230 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:98 231 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:108 232 | #, kde-format 233 | msgid "Colorize" 234 | msgstr "" 235 | 236 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:101 237 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:112 238 | #, kde-format 239 | msgid "Systemd Unit" 240 | msgstr "" 241 | 242 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:109 243 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:122 244 | #, kde-format 245 | msgid "Executable" 246 | msgstr "" 247 | 248 | #: org/kde/kjournaldbrowser/GlobalMenu.qml:119 249 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:133 250 | #, kde-format 251 | msgctxt "@action:menu" 252 | msgid "Help" 253 | msgstr "" 254 | 255 | #: org/kde/kjournaldbrowser/LogView.qml:207 256 | #, kde-format 257 | msgid "Kernel" 258 | msgstr "" 259 | 260 | #: org/kde/kjournaldbrowser/Main.qml:62 261 | #, kde-format 262 | msgid "Boot:" 263 | msgstr "" 264 | 265 | #: org/kde/kjournaldbrowser/Main.qml:173 266 | #, kde-format 267 | msgctxt "@info:tooltip" 268 | msgid "Switch between case sensitive and case insensitive matching" 269 | msgstr "" 270 | 271 | #: org/kde/kjournaldbrowser/Main.qml:215 272 | #, kde-format 273 | msgctxt "@info:tooltip" 274 | msgid "Switch to browsing mode" 275 | msgstr "" 276 | 277 | #: org/kde/kjournaldbrowser/Main.qml:216 278 | #, kde-format 279 | msgctxt "@info:tooltip" 280 | msgid "Switch to selection mode" 281 | msgstr "" 282 | 283 | #: org/kde/kjournaldbrowser/Main.qml:228 284 | #, kde-format 285 | msgid "Select journal folder" 286 | msgstr "" 287 | 288 | #: org/kde/kjournaldbrowser/Main.qml:236 289 | #, kde-format 290 | msgid "Select journal file" 291 | msgstr "" 292 | 293 | #: org/kde/kjournaldbrowser/Main.qml:237 294 | #, kde-format 295 | msgid "Journal files (*.journal)" 296 | msgstr "" 297 | 298 | #: org/kde/kjournaldbrowser/Main.qml:237 299 | #, kde-format 300 | msgid "All files (*)" 301 | msgstr "" 302 | 303 | #: org/kde/kjournaldbrowser/Main.qml:307 304 | #, kde-format 305 | msgid "No log entries apply " 306 | msgstr "" 307 | 308 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:16 309 | #, kde-format 310 | msgid "Configure Remote Journal Server" 311 | msgstr "" 312 | 313 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:30 314 | #, kde-format 315 | msgid "URL:" 316 | msgstr "" 317 | 318 | #: org/kde/kjournaldbrowser/RemoteJournalConfigDialog.qml:39 319 | #, kde-format 320 | msgid "Port:" 321 | msgstr "" 322 | 323 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:23 324 | #, kde-format 325 | msgid "Open System Journal" 326 | msgstr "" 327 | 328 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:30 329 | #, kde-format 330 | msgid "Open from Folder" 331 | msgstr "" 332 | 333 | #: org/kde/kjournaldbrowser/TopMenuBar.qml:38 334 | #, kde-format 335 | msgid "Open from File" 336 | msgstr "" 337 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-2025 Scarlett Moore 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | --- 5 | name: kjournald 6 | confinement: strict 7 | grade: stable 8 | base: core24 9 | adopt-info: kjournald 10 | apps: 11 | kjournald: 12 | extensions: 13 | - kde-neon-6 14 | desktop: usr/share/applications/org.kde.kjournaldbrowser.desktop 15 | common-id: org.kde.kjournaldbrowser.desktop 16 | command: usr/bin/kjournaldbrowser 17 | plugs: 18 | - log-observe 19 | parts: 20 | kjournald: 21 | parse-info: 22 | - usr/share/metainfo/org.kde.kjournaldbrowser.appdata.xml 23 | plugin: cmake 24 | source: . 25 | source-type: local 26 | build-packages: 27 | - libsystemd-dev 28 | stage-packages: 29 | - libsystemd0 30 | cmake-parameters: 31 | - -DCMAKE_INSTALL_PREFIX=/usr 32 | - -DCMAKE_BUILD_TYPE=Release 33 | - -DQT_MAJOR_VERSION=6 34 | - -DBUILD_WITH_QT6=ON 35 | - -DBUILD_TESTING=OFF 36 | - -DCMAKE_INSTALL_SYSCONFDIR=/etc 37 | - -DCMAKE_INSTALL_LOCALSTATEDIR=/var 38 | - -DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON 39 | - -DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF 40 | - -DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON 41 | - -DCMAKE_INSTALL_RUNSTATEDIR=/run 42 | - -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON 43 | - -DCMAKE_VERBOSE_MAKEFILE=ON 44 | - -DCMAKE_INSTALL_LIBDIR=lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR 45 | - --log-level=STATUS 46 | - -DCMAKE_LIBRARY_PATH=lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR 47 | build-environment: &build-environment 48 | - LD_LIBRARY_PATH: > 49 | "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 50 | prime: 51 | - -usr/lib/*/cmake/* 52 | - -usr/include/* 53 | - -usr/share/ECM/* 54 | - -usr/share/man/* 55 | - -usr/bin/X11 56 | - -usr/lib/gcc/$CRAFT_ARCH_TRIPLET_BUILD_FOR/6.0.0 57 | - -usr/lib/aspell/* 58 | - -usr/share/lintian 59 | gpu-2404: 60 | after: [kjournald] 61 | source: https://github.com/canonical/gpu-snap.git 62 | plugin: dump 63 | override-prime: | 64 | craftctl default 65 | ${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404 66 | prime: 67 | - bin/gpu-2404-wrapper 68 | cleanup: 69 | after: 70 | - kjournald 71 | plugin: nil 72 | build-snaps: 73 | - core24 74 | - kf6-core24 75 | override-prime: | 76 | set -eux 77 | for snap in "core24" "kf6-core24"; do 78 | cd "/snap/$snap/current" && find . -type f,l -exec rm -rf "${CRAFT_PRIME}/{}" \; 79 | done 80 | 81 | --------------------------------------------------------------------------------