Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package union6 for openSUSE:Factory checked in at 2026-10-22 15:48:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/union6 (Old) and /work/SRC/openSUSE:Factory/.union6.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "union6" Thu Oct 22 15:48:50 2026 rev:8 rq:1386878 version:6.8.1 Changes: -------- --- /work/SRC/openSUSE:Factory/union6/union6.changes 2026-10-16 20:09:10.844872426 +0200 +++ /work/SRC/openSUSE:Factory/.union6.new.2252680/union6.changes 2026-10-22 15:49:21.355738008 +0200 @@ -1,0 +2,23 @@ +Tue Oct 20 11:35:47 UTC 2026 - Fabian Vogt <[email protected]> + +- Update to 6.8.1: + * New bugfix release + * For more details see https://kde.org/announcements/plasma/6/6.8.1 +- Changes since 6.8.0: + * TreeViewElement: draw the branch arrow at the size of the indicator + * UnionStyle: underline accelerators of selectable labels only with Alt + * Update version for new release 6.8.1 + * CheckBox/RadioButtonElement: Don't draw as a push button without a widget (kde#526764) + * Elements: Add validity checks + * :cherries: tools/styletool: Support Tar archived styles in install/inspect/update commands + * tools/styletool: Add support for extracting files from a Tar archive + * tools/styletool: Add support for reading existing archives to TarArchive + * :cherries: tools/styletool: Refactor Tar to TarArchive and make it a proper class + * ScrollAreaCornerElement: Fix validity check + * UnionStyle: Remove unused QSplitterHandle check + * CMakeLists: Remove Breeze QStyle dependency + * delegate: Add focusframe + * FocusElement: Fix validity check + * AbstractElement: Add changed hint on _kde_highlight_neutral + +------------------------------------------------------------------- Old: ---- union-6.8.0.tar.xz union-6.8.0.tar.xz.sig New: ---- union-6.8.1.tar.xz union-6.8.1.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ union6.spec ++++++ --- /var/tmp/diff_new_pack.NIvciu/_old 2026-10-22 15:49:22.058767478 +0200 +++ /var/tmp/diff_new_pack.NIvciu/_new 2026-10-22 15:49:22.059767520 +0200 @@ -26,14 +26,14 @@ %{!?_plasma6_version: %define _plasma6_version %(echo %{_plasma6_bugfix} | awk -F. '{print $1"."$2}')} %bcond_without released Name: union6 -Version: 6.8.0 +Version: 6.8.1 Release: 0 Summary: Qt style supporting both QtQuick and QtWidgets License: LGPL-2.1-or-later URL: https://www.kde.org/ -Source0: %{rname}-%{version}.tar.xz +Source0: https://download.kde.org/stable/plasma/%{version}/%{rname}-%{version}.tar.xz %if %{with released} -Source1: %{rname}-%{version}.tar.xz.sig +Source1: https://download.kde.org/stable/plasma/%{version}/%{rname}-%{version}.tar.xz.sig Source2: plasma.keyring %endif BuildRequires: kf6-extra-cmake-modules >= %{kf6_version} ++++++ union-6.8.0.tar.xz -> union-6.8.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/CMakeLists.txt new/union-6.8.1/CMakeLists.txt --- old/union-6.8.0/CMakeLists.txt 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/CMakeLists.txt 2026-10-20 11:13:08.000000000 +0200 @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.27) -set(PROJECT_VERSION "6.8.0") # handled by release scripts +set(PROJECT_VERSION "6.8.1") # handled by release scripts set(KF6_MIN_VERSION "6.30.0") # handled by release scripts set(CSS_PARSER_VERSION "1.1.0") @@ -46,7 +46,6 @@ # Options to control building outputs option(BUILD_OUTPUT_QTQUICK "Build the QtQuick output" ON) cmake_dependent_option(BUILD_OUTPUT_KIRIGAMI "Build the Kirigami output. Requires QtQuick output." ON "BUILD_OUTPUT_QTQUICK" OFF) -# Builds a proxy style that uses Breeze as the base by default option(BUILD_OUTPUT_QTWIDGETS "Build the QtWidgets output" ON) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") @@ -83,8 +82,6 @@ if (BUILD_OUTPUT_QTWIDGETS) find_package(Qt6Widgets ${QT_MIN_VERSION} CONFIG REQUIRED) - find_package(Breeze CONFIG) - set_package_properties(Breeze PROPERTIES TYPE RUNTIME PURPOSE "Breeze is used as the QtWidgets base style by default.") endif() if (BUILD_OUTPUT_KIRIGAMI) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/input/css/styles/breeze/contents/css/delegate.css new/union-6.8.1/src/input/css/styles/breeze/contents/css/delegate.css --- old/union-6.8.0/src/input/css/styles/breeze/contents/css/delegate.css 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/input/css/styles/breeze/contents/css/delegate.css 2026-10-20 11:13:08.000000000 +0200 @@ -12,6 +12,7 @@ --delegate-spacing: var(--medium-spacing); --delegate-padding: var(--small-spacing); --delegate-total-padding: 10px; /* spacing + padding */ + --focus-highlight-color: mix(var(--highlight-color), var(--view-background-color), 0.45); } itemdelegate, @@ -206,6 +207,24 @@ } } +/* Dolphin fileitem */ +graphicsview itemviewitem { + &:highlighted { + background-color: modify-color(var(--highlight-color) set-alpha 0.75); + border: none; + } +} + +focusframe { + background-color: transparent; + border: 1.25px solid var(--highlight-color); + border-radius: var(--corner-radius); + inset: 2px; + &:highlighted { + border: 1.75px solid var(--highlight-color); + } +} + /* For delegates with known indicators, place the indicator at the top left and * reduce the effective indicator size to 0. This ensures the indicator will be * placed inside the content item. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/UnionStyle.cpp new/union-6.8.1/src/output/qtwidgets/UnionStyle.cpp --- old/union-6.8.0/src/output/qtwidgets/UnionStyle.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/UnionStyle.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -652,8 +652,10 @@ return false; case SH_ScrollBar_LeftClickAbsolutePosition: return true; + // Accelerator underlines follow the Alt key in text that is not drawn through drawItemText() as well, such as + // selectable labels. case SH_UnderlineShortcut: - return true; + return m_showMnemonics; default: return QCommonStyle::styleHint(hint, option, widget, returnData); } @@ -776,8 +778,6 @@ } else if (qobject_cast<const QFrame *>(currentWidget)) { members.prepend(ElementString::Frame); // Toplevels - } else if (qobject_cast<const QSplitterHandle *>(currentWidget)) { - members.prepend(ElementString::SplitterHandle); } else if (qobject_cast<const QAbstractSpinBox *>(currentWidget)) { members.prepend(ElementString::SpinBox); } else if (qobject_cast<const QGroupBox *>(currentWidget)) { @@ -812,8 +812,6 @@ members.prepend(ElementString::RubberBand); } else if (qobject_cast<const QSizeGrip *>(currentWidget)) { members.prepend(ElementString::SizeGrip); - } else if (qobject_cast<const QSplitterHandle *>(currentWidget)) { - members.prepend(ElementString::SplitterHandle); } else if (qobject_cast<const QStatusBar *>(currentWidget)) { members.prepend(ElementString::StatusBar); } else if (qobject_cast<const QTabBar *>(currentWidget)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/AbstractElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/AbstractElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/AbstractElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/AbstractElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -457,7 +457,7 @@ elementTypes = m_style->widgetToElementHierarchy(widget); } } else { - elementTypes = {styleOptionToElementName(opt)}; + elementTypes = {fallbackElementName(opt)}; } // Meld duplicate elements that appear next to each other @@ -477,6 +477,11 @@ return elements; } +QString AbstractElement::fallbackElementName(const QStyleOption *opt) const +{ + return styleOptionToElementName(opt); +} + Union::Properties::StylePropertyGroup *AbstractElement::queryProperties(const Union::ElementList &elements) const { Q_ASSERT(!elements.isEmpty()); @@ -642,14 +647,20 @@ unionElement->setAttributes(elementAttributes()); auto hints = elementHints(); - // Window is a bit special and needs to be handled during element hierarchy creation, - // as the QStyle does not directly draw the window element. - if (m_widget && name == u"ApplicationWindow"_s) { - if (auto window = m_widget->window()) { - if (!window->isActiveWindow()) { - hints.append(u"inactive"_s); + if (m_widget) { + // Window is a bit special and needs to be handled during element hierarchy creation, + // as the QStyle does not directly draw the window element.) + if (name == u"ApplicationWindow"_s) { + if (auto window = m_widget->window()) { + if (!window->isActiveWindow()) { + hints.append(u"inactive"_s); + } } } + // Add support for Widget KCMs showing the changed items. + if (m_widget->property("_kde_highlight_neutral").toBool()) { + hints.append(u"changed"_s); + } } unionElement->setHints(hints); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/AbstractElement.h new/union-6.8.1/src/output/qtwidgets/elements/AbstractElement.h --- old/union-6.8.0/src/output/qtwidgets/elements/AbstractElement.h 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/AbstractElement.h 2026-10-20 11:13:08.000000000 +0200 @@ -176,6 +176,12 @@ virtual Union::ElementList prepareElements(const QStyleOption *opt, const QWidget *widget, QStringList targetHierarchy = {}) const; /*! + * \brief The element name used by prepareElements() when there is no widget, such as when + * drawing for QtQuick. By default it is guessed from the style option type. + */ + virtual QString fallbackElementName(const QStyleOption *opt) const; + + /*! * \brief Queries the properties from list of elements. The properties match to the last element in the list, * inheriting anything it needs from its parents. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/CheckBoxElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/CheckBoxElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/CheckBoxElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/CheckBoxElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -140,3 +140,10 @@ } return 0; } + +QString CheckBoxElement::fallbackElementName(const QStyleOption *opt) const +{ + Q_UNUSED(opt); + // QStyleOptionButton is also used by push buttons, so the option type would make this a Button + return ElementString::CheckBox; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/CheckBoxElement.h new/union-6.8.1/src/output/qtwidgets/elements/CheckBoxElement.h --- old/union-6.8.0/src/output/qtwidgets/elements/CheckBoxElement.h 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/CheckBoxElement.h 2026-10-20 11:13:08.000000000 +0200 @@ -50,5 +50,6 @@ void updateSubElementList() override; void drawIndicator(QPainter *painter) const override; QStringList elementHints() const override; + QString fallbackElementName(const QStyleOption *opt) const override; const QStyleOptionButton *m_buttonOption = nullptr; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/FocusElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/FocusElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/FocusElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/FocusElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -46,6 +46,9 @@ m_backgroundElementList = prepareElements(m_styleOption, m_widget, {ElementString::FocusFrame}); if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); + m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/MenuItemElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/MenuItemElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/MenuItemElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/MenuItemElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -144,6 +144,8 @@ } m_isValid = true; + } else { + m_isValid = false; } if (!m_shortcutText.isEmpty()) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/RadioButtonElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/RadioButtonElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/RadioButtonElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/RadioButtonElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -134,3 +134,10 @@ } return 0; } + +QString RadioButtonElement::fallbackElementName(const QStyleOption *opt) const +{ + Q_UNUSED(opt); + // QStyleOptionButton is also used by push buttons, so the option type would make this a Button + return ElementString::RadioButton; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/RadioButtonElement.h new/union-6.8.1/src/output/qtwidgets/elements/RadioButtonElement.h --- old/union-6.8.0/src/output/qtwidgets/elements/RadioButtonElement.h 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/RadioButtonElement.h 2026-10-20 11:13:08.000000000 +0200 @@ -50,5 +50,6 @@ void updateSubElementList() override; void drawIndicator(QPainter *painter) const override; QStringList elementHints() const override; + QString fallbackElementName(const QStyleOption *opt) const override; const QStyleOptionButton *m_buttonOption = nullptr; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/RubberBandElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/RubberBandElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/RubberBandElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/RubberBandElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -48,5 +48,7 @@ if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/ScrollAreaCornerElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/ScrollAreaCornerElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/ScrollAreaCornerElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/ScrollAreaCornerElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -47,5 +47,8 @@ m_backgroundElementList = prepareElements(m_cornerOption, m_widget, {ElementString::ScrollAreaCorner}); if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); + m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/SizeGripElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/SizeGripElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/SizeGripElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/SizeGripElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -49,6 +49,8 @@ if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/SplitterElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/SplitterElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/SplitterElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/SplitterElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -53,6 +53,8 @@ if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/TabElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/TabElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/TabElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/TabElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -160,6 +160,8 @@ subopt.rect = tabRect; m_layoutMap = layoutMap(m_backgroundElementList, &subopt, m_subElementList); m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/ToolTipElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/ToolTipElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/ToolTipElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/ToolTipElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -47,6 +47,8 @@ if (!m_backgroundElementList.isEmpty()) { m_backgroundProperties = queryProperties(m_backgroundElementList); m_isValid = true; + } else { + m_isValid = false; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/elements/TreeViewElement.cpp new/union-6.8.1/src/output/qtwidgets/elements/TreeViewElement.cpp --- old/union-6.8.0/src/output/qtwidgets/elements/TreeViewElement.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/elements/TreeViewElement.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -50,6 +50,9 @@ m_indicatorElementList = prepareElements(m_styleOption, m_widget, {ElementString::TreeViewDelegate, ElementString::Indicator}); if (!m_indicatorElementList.isEmpty()) { m_indicatorProperties = queryProperties(m_indicatorElementList); + m_isValid = true; + } else { + m_isValid = false; } } @@ -84,8 +87,12 @@ const QPalette activePalette = m_styleOption->palette; const qreal dpr = painter->device() ? painter->device()->devicePixelRatioF() : qApp->devicePixelRatio(); - auto iconSize = m_styleOption->rect.size(); - const QPixmap pixmap = icon.pixmap(iconSize, dpr, enabled ? QIcon::Normal : QIcon::Disabled); + QSize iconSize = size.toSize().boundedTo(m_styleOption->rect.size()); + if (iconSize.isEmpty()) { + iconSize = m_styleOption->rect.size(); + } + const int side = std::min(iconSize.width(), iconSize.height()); + const QPixmap pixmap = icon.pixmap(QSize(side, side), dpr, enabled ? QIcon::Normal : QIcon::Disabled); painter->save(); auto rect = centerRect(m_styleOption->rect, size.width(), size.height()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/src/output/qtwidgets/union.themerc new/union-6.8.1/src/output/qtwidgets/union.themerc --- old/union-6.8.0/src/output/qtwidgets/union.themerc 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/src/output/qtwidgets/union.themerc 2026-10-20 11:13:08.000000000 +0200 @@ -21,6 +21,7 @@ Name[nn]=Union (under utvikling) Name[pt_BR]=Union (Em desenvolvimento) Name[ro]=Union (în dezvoltare) +Name[ru]=Union (в разработке) Name[sk]=Union (vo vývoji) Name[sl]=Union (V razvoju) Name[sr]=Union (у развоју) @@ -50,6 +51,7 @@ Comment[nn]=Ein stilmotor som er under utvikling og er meint å sameina dei ulike KDE-stilsystema. Comment[pt_BR]=Um mecanismo de estilo em desenvolvimento, destinado a unificar os vários sistemas de estilo do KDE. Comment[ro]=Un motor de stiluri aflat în dezvoltare ce are ca scop unificarea diferitelor sisteme de stilizare din KDE. +Comment[ru]=Разрабатываемый механизм оформления, призванный объединить различные системы оформления в KDE. Comment[sk]=Vyvíjaný štýlovací engine určený na zjednotenie rôznych štýlovacích systémov v KDE. Comment[sl]=Slogovni mehanizem v razvoju, namenjen poenotenju različnih slogovnih sistemov v KDE. Comment[sr]=Механизам стилова у развоју, намењен уједначавању разних система стилизовања у КДЕ‑у. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/CMakeLists.txt new/union-6.8.1/tools/styletool/CMakeLists.txt --- old/union-6.8.0/tools/styletool/CMakeLists.txt 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/tools/styletool/CMakeLists.txt 2026-10-20 11:13:08.000000000 +0200 @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2026 Arjen Hiemstra <[email protected]> add_executable(union-styletool) -target_sources(union-styletool PRIVATE styletool.cpp) +target_sources(union-styletool PRIVATE styletool.cpp TarArchive.cpp) target_link_libraries(union-styletool PRIVATE Union::Union Qt::Core ZLIB::ZLIB) target_compile_definitions(union-styletool PRIVATE PROJECT_VERSION="${PROJECT_VERSION}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/Common.h new/union-6.8.1/tools/styletool/Common.h --- old/union-6.8.0/tools/styletool/Common.h 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/tools/styletool/Common.h 2026-10-20 11:13:08.000000000 +0200 @@ -8,11 +8,14 @@ #include <QCoreApplication> #include <QFile> #include <QStandardPaths> +#include <QTemporaryDir> #include <QTextStream> #include <PackageHandler.h> #include <StylePackage.h> +#include "TarArchive.h" + using namespace Qt::StringLiterals; namespace fs = std::filesystem; @@ -126,3 +129,33 @@ std::cerr << "Operation on package " << package.path() << " failed: " << errorCode.message() << "\n"; return errorCode.value(); } + +std::pair<QTemporaryDir, fs::path> extractStyleArchive(const fs::path &path, std::error_code &errorCode) +{ + auto tempDir = QTemporaryDir{}; + auto tempPath = fs::path(tempDir.path().toStdString()); + + Tar::TarArchive archive(path); + if (!archive.read(errorCode)) { + std::cerr << "Operation on package " << path << " failed: " << errorCode.message() << "\n"; + return std::make_pair(std::move(tempDir), fs::path{}); + } + + if (!archive.extractAll(tempPath, errorCode)) { + std::cerr << "Operation on package " << path << " failed: " << errorCode.message() << "\n"; + return std::make_pair(std::move(tempDir), fs::path{}); + } else { + QList<fs::path> paths; + for (auto entry : fs::directory_iterator(tempPath)) { + paths.append(tempPath / entry.path()); + } + + if (paths.size() == 1) { + return std::make_pair(std::move(tempDir), paths.first()); + } + } + + std::cerr << "Operation on package " << path << " failed: The package is an invalid Union style package.\n"; + errorCode = std::make_error_code(std::errc::io_error); + return std::make_pair(std::move(tempDir), fs::path{}); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/Tar.h new/union-6.8.1/tools/styletool/Tar.h --- old/union-6.8.0/tools/styletool/Tar.h 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/tools/styletool/Tar.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL -// SPDX-FileCopyrightText: 2026 Arjen Hiemstra <[email protected]> - -#pragma once - -#include <cstdint> -#include <filesystem> -#include <format> -#include <iostream> - -#include <QByteArray> -#include <QFile> -#include <QFileInfo> -#include <QList> - -extern "C" { -#include <zlib.h> -} - -/* - * This is a very simple and naive implementation of creating an in-memory tar - * archive that is written to disk with gzip compression. This avoids having to - * drag in a complete - */ - -static constexpr uint8_t regularFileType = '0'; -static constexpr uint8_t directoryType = '5'; - -// Header entry for an USTar format tar file -struct USTarHeader { - char name[100] = {'\0'}; - char mode[8] = {'\0'}; - uint8_t uid[8] = {0}; - uint8_t gid[8] = {0}; - uint8_t size[12] = {0}; - uint8_t mtime[12] = {0}; - char checksum[8] = {0}; - uint8_t type = {0}; - uint8_t linkName[100] = {0}; - uint8_t ustar[8] = "ustar "; - uint8_t userName[32] = {0}; - uint8_t groupName[32] = {0}; - uint8_t deviceMajor[8] = {0}; - uint8_t deviceMinor[8] = {0}; - uint8_t namePrefix[155] = {0}; - uint8_t padding[12] = {0}; -}; - -struct TarEntry { - USTarHeader header; - QByteArray data; - QByteArray padding; -}; - -void tarDirectoryContents(const std::filesystem::path &directory, const std::filesystem::path &baseDirectory, QList<TarEntry> &output) -{ - for (auto entry : fs::directory_iterator(directory)) { - auto pathString = fs::relative(entry.path(), baseDirectory).string(); - if (pathString.size() >= 100) { - std::cout << "Skipping entry " << pathString << " because the file name is too long.\n"; - continue; - } - - TarEntry tarEntry; - - auto &header = tarEntry.header; - std::format_to_n(header.name, sizeof(header.name), "{}", pathString); - std::format_to_n(header.mode, sizeof(header.mode), "{:07o}", static_cast<int>(entry.status().permissions())); - std::format_to_n(header.size, sizeof(header.size), "{:011o}", entry.is_directory() ? 0 : entry.file_size()); - - QFileInfo fileInfo(entry.path()); - - std::format_to_n(header.uid, sizeof(header.uid), "{:07o}", fileInfo.ownerId()); - std::format_to_n(header.gid, sizeof(header.uid), "{:07o}", fileInfo.groupId()); - std::format_to_n(header.mtime, sizeof(header.mtime), "{:011o}", fileInfo.lastModified().toSecsSinceEpoch()); - - std::format_to_n(header.userName, sizeof(header.userName), "{}", fileInfo.owner().toStdString()); - std::format_to_n(header.groupName, sizeof(header.groupName), "{}", fileInfo.group().toStdString()); - - if (entry.is_directory()) { - header.name[pathString.size()] = '/'; - header.type = directoryType; - } else { - header.type = regularFileType; - - QFile file(entry.path()); - if (!file.open(QIODevice::ReadOnly)) { - std::cout << "Skipping entry " << pathString << " because it could not be opened.\n"; - continue; - } - - tarEntry.data = file.readAll(); - tarEntry.padding = QByteArray{512 - tarEntry.data.size() % 512, '\0'}; - - file.close(); - } - - strncpy(header.checksum, " ", sizeof(header.checksum)); - - auto headerData = reinterpret_cast<uint8_t *>(&header); - uint32_t checksum = 0; - for (int i = 0; i < 512; ++i) { - checksum += headerData[i]; - } - - std::format_to_n(header.checksum, sizeof(header.checksum), "{:06o}", checksum); - header.checksum[6] = '\0'; - header.checksum[7] = ' '; - - output.append(tarEntry); - - if (entry.is_directory()) { - tarDirectoryContents(entry.path(), baseDirectory, output); - } - } -} - -bool tarDirectory(const std::filesystem::path &directory, const std::filesystem::path &destination, std::error_code &errorCode) -{ - namespace fs = std::filesystem; - - if (!fs::exists(directory)) { - errorCode = std::make_error_code(std::errc::no_such_file_or_directory); - return false; - } - - if (!fs::is_directory(directory)) { - errorCode = std::make_error_code(std::errc::not_a_directory); - return false; - } - - if (fs::exists(destination)) { - errorCode = std::make_error_code(std::errc::file_exists); - return false; - } - - QList<TarEntry> entries; - tarDirectoryContents(directory, directory.parent_path(), entries); - - QByteArray data; - for (const auto &entry : std::as_const(entries)) { - data.append(reinterpret_cast<const char *>(&entry.header), sizeof(USTarHeader)); - data.append(entry.data); - data.append(entry.padding); - } - - data.append(QByteArray{1024, '\0'}); - - auto outfile = gzopen(destination.string().data(), "wb"); - if (!outfile) { - errorCode = std::error_code(errno, std::system_category()); - return false; - } - - gzwrite(outfile, data.data(), data.size()); - gzclose(outfile); - - return true; -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/TarArchive.cpp new/union-6.8.1/tools/styletool/TarArchive.cpp --- old/union-6.8.0/tools/styletool/TarArchive.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/union-6.8.1/tools/styletool/TarArchive.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +// SPDX-FileCopyrightText: 2026 Arjen Hiemstra <[email protected]> + +#include "TarArchive.h" + +#include <format> +#include <fstream> + +#include <QFileInfo> + +extern "C" { +#include <zlib.h> +} + +using namespace Tar; +using namespace Qt::StringLiterals; + +namespace fs = std::filesystem; + +TarArchive::TarArchive() +{ +} + +TarArchive::TarArchive(const std::filesystem::path &path) + : m_path(path) +{ +} + +bool TarArchive::addEntry(const std::filesystem::path &path, std::error_code &errorCode) +{ + return addEntry(path, path.parent_path(), errorCode); +} + +bool TarArchive::addEntry(const std::filesystem::path &path, const std::filesystem::path &baseDirectory, std::error_code &errorCode) +{ + QFileInfo fileInfo(path); + + if (!fileInfo.exists()) { + errorCode = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + + if (m_path.empty() || !fs::is_directory(m_path.parent_path())) { + errorCode = std::make_error_code(std::errc::not_a_directory); + return false; + } + + auto pathString = fs::relative(path, baseDirectory).string(); + if (pathString.size() >= 100) { + errorCode = std::make_error_code(std::errc::filename_too_long); + return false; + } + + TarEntry tarEntry; + + auto &header = tarEntry.header; + std::format_to_n(header.name, sizeof(header.name), "{}", pathString); + std::format_to_n(header.mode, sizeof(header.mode), "{:07o}", static_cast<int>(fs::status(path).permissions())); + std::format_to_n(header.size, sizeof(header.size), "{:011o}", fileInfo.isDir() ? 0 : fileInfo.size()); + + std::format_to_n(header.uid, sizeof(header.uid), "{:07o}", fileInfo.ownerId()); + std::format_to_n(header.gid, sizeof(header.uid), "{:07o}", fileInfo.groupId()); + std::format_to_n(header.mtime, sizeof(header.mtime), "{:011o}", fileInfo.lastModified().toSecsSinceEpoch()); + + std::format_to_n(header.userName, sizeof(header.userName), "{}", fileInfo.owner().toStdString()); + std::format_to_n(header.groupName, sizeof(header.groupName), "{}", fileInfo.group().toStdString()); + + if (fileInfo.isDir()) { + header.name[pathString.size()] = '/'; + header.type = DirectoryType; + } else { + header.type = RegularFileType; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + errorCode = std::make_error_code(std::errc::io_error); + return false; + } + + tarEntry.data = file.readAll(); + tarEntry.padding = QByteArray{512 - tarEntry.data.size() % 512, '\0'}; + + file.close(); + } + + std::format_to_n(header.checksum, sizeof(header.checksum), " "); + + auto headerData = reinterpret_cast<uint8_t *>(&header); + uint32_t checksum = 0; + for (int i = 0; i < 512; ++i) { + checksum += headerData[i]; + } + + std::format_to_n(header.checksum, sizeof(header.checksum), "{:06o}", checksum); + header.checksum[6] = '\0'; + header.checksum[7] = ' '; + + m_entries.append(tarEntry); + + if (fileInfo.isDir()) { + for (auto entry : fs::directory_iterator(path)) { + if (!addEntry(entry.path(), baseDirectory, errorCode)) { + return false; + } + } + } + + return true; +} + +bool TarArchive::read(std::error_code &errorCode) +{ + m_entries.clear(); + + if (!fs::exists(m_path)) { + errorCode = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + + auto infile = gzopen(m_path.string().data(), "rb"); + if (!infile) { + errorCode = std::error_code(errno, std::system_category()); + return false; + } + + constexpr int BufferSize = 1024; + QByteArray data; + std::array<char, BufferSize> buffer; + while (true) { + int read = gzread(infile, buffer.data(), buffer.size()); + if (read == BufferSize) { + data.append(buffer); + } else if (read > 0) { + data.append(buffer); + break; + } else { + errorCode = std::make_error_code(std::errc::io_error); + return false; + } + } + + if (data.size() == 0) { + errorCode = std::make_error_code(std::errc::io_error); + return false; + } + + constexpr int ChunkSize = 512; + auto offset = 0; + auto remainingSize = data.size(); + auto view = QByteArrayView(data); + TarEntry currentEntry; + bool first = true; + while (remainingSize > 0) { + auto chunk = view.sliced(offset, ChunkSize); + remainingSize -= ChunkSize; + offset += ChunkSize; + + if (chunk.sliced(257, 5) == "ustar") { + if (!first) { + currentEntry.data = currentEntry.data.sliced(0, currentEntry.header.actualSize()); + m_entries.append(currentEntry); + currentEntry = TarEntry{}; + } else { + currentEntry = TarEntry{}; + first = false; + } + + memcpy(¤tEntry.header, chunk.data(), ChunkSize); + } else { + currentEntry.data.append(chunk); + } + } + + currentEntry.data = currentEntry.data.sliced(0, currentEntry.header.actualSize()); + m_entries.append(currentEntry); + currentEntry = TarEntry{}; + + return true; +} + +bool TarArchive::write(std::error_code &errorCode) +{ + if (fs::exists(m_path)) { + errorCode = std::make_error_code(std::errc::file_exists); + return false; + } + + QByteArray data; + for (const auto &entry : std::as_const(m_entries)) { + data.append(reinterpret_cast<const char *>(&entry.header), sizeof(USTarHeader)); + data.append(entry.data); + data.append(entry.padding); + } + + data.append(QByteArray{1024, '\0'}); + + auto outfile = gzopen(m_path.string().data(), "wb"); + if (!outfile) { + errorCode = std::error_code(errno, std::system_category()); + return false; + } + + gzwrite(outfile, data.data(), data.size()); + gzclose(outfile); + + return true; +} + +bool Tar::TarArchive::extract(const std::string &name, const std::filesystem::path &destination, std::error_code &errorCode) +{ + if (!fs::is_directory(destination)) { + errorCode = std::make_error_code(std::errc::not_a_directory); + return false; + } + + auto itr = std::ranges::find_if(m_entries, [&name](const TarEntry &entry) { + return entry.header.name == name; + }); + if (itr == m_entries.end()) { + errorCode = std::make_error_code(std::errc::no_such_file_or_directory); + return false; + } + + return extractEntry(destination, *itr, errorCode); +} + +bool Tar::TarArchive::extractAll(const std::filesystem::path &destination, std::error_code &errorCode) +{ + if (!fs::is_directory(destination)) { + errorCode = std::make_error_code(std::errc::not_a_directory); + return false; + } + + for (const auto &entry : std::as_const(m_entries)) { + if (!extractEntry(destination, entry, errorCode)) { + return false; + } + } + + return true; +} + +bool TarArchive::extractEntry(const std::filesystem::path &destination, const TarEntry &entry, std::error_code &errorCode) +{ + Q_ASSERT(fs::is_directory(destination)); + + auto destinationPath = destination / entry.header.name; + if (fs::exists(destinationPath)) { + errorCode = std::make_error_code(std::errc::file_exists); + return false; + } + + if (entry.header.type == RegularFileType) { + if (!fs::exists(destinationPath.parent_path())) { + errorCode = std::make_error_code(std::errc::not_a_directory); + return false; + } + + std::ofstream stream(destinationPath, std::ios::binary); + stream.write(entry.data.data(), entry.data.size()); + stream.close(); + + if (stream.fail()) { + errorCode = std::make_error_code(std::errc::io_error); + return false; + } else { + return true; + } + } else if (entry.header.type == DirectoryType) { + fs::create_directories(destinationPath, errorCode); + if (errorCode) { + return false; + } else { + return true; + } + } + + errorCode = std::make_error_code(std::errc::operation_not_supported); + return false; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/TarArchive.h new/union-6.8.1/tools/styletool/TarArchive.h --- old/union-6.8.0/tools/styletool/TarArchive.h 1970-01-01 01:00:00.000000000 +0100 +++ new/union-6.8.1/tools/styletool/TarArchive.h 2026-10-20 11:13:08.000000000 +0200 @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +// SPDX-FileCopyrightText: 2026 Arjen Hiemstra <[email protected]> + +#pragma once + +#include <cstdint> +#include <filesystem> + +#include <QByteArray> +#include <QList> + +namespace Tar +{ + +static constexpr uint8_t RegularFileType = '0'; +static constexpr uint8_t DirectoryType = '5'; + +/* Header entry for an USTar format tar file + */ +struct USTarHeader { + char name[100] = {'\0'}; + char mode[8] = {'\0'}; + uint8_t uid[8] = {0}; + uint8_t gid[8] = {0}; + uint8_t size[12] = {0}; + uint8_t mtime[12] = {0}; + char checksum[8] = {0}; + uint8_t type = {0}; + uint8_t linkName[100] = {0}; + uint8_t ustar[8] = "ustar "; + uint8_t userName[32] = {0}; + uint8_t groupName[32] = {0}; + uint8_t deviceMajor[8] = {0}; + uint8_t deviceMinor[8] = {0}; + uint8_t namePrefix[155] = {0}; + uint8_t padding[12] = {0}; + + inline int actualSize() const + { + QByteArrayView sizeData(size, 11); + return sizeData.toInt(nullptr, 8); + } +}; + +/* A single entry in a tar file. + */ +struct TarEntry { + USTarHeader header; + QByteArray data; + QByteArray padding; +}; + +/* + * This is a very simple and naive implementation of creating an in-memory tar + * archive that is written to disk with gzip compression. This avoids having to + * drag in a complete archival library just to write a single package format. + */ +class TarArchive +{ +public: + TarArchive(); + TarArchive(const std::filesystem::path &path); + + bool addEntry(const std::filesystem::path &path, std::error_code &errorCode); + bool addEntry(const std::filesystem::path &path, const std::filesystem::path &baseDirectory, std::error_code &errorCode); + + bool read(std::error_code &errorCode); + bool write(std::error_code &errorCode); + + bool extract(const std::string &name, const std::filesystem::path &destination, std::error_code &errorCode); + bool extractAll(const std::filesystem::path &destination, std::error_code &errorCode); + +private: + bool extractEntry(const std::filesystem::path &destination, const TarEntry &entry, std::error_code &errorCode); + + std::filesystem::path m_path; + QList<TarEntry> m_entries; +}; + +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/union-6.8.0/tools/styletool/styletool.cpp new/union-6.8.1/tools/styletool/styletool.cpp --- old/union-6.8.0/tools/styletool/styletool.cpp 2026-10-08 13:28:01.000000000 +0200 +++ new/union-6.8.1/tools/styletool/styletool.cpp 2026-10-20 11:13:08.000000000 +0200 @@ -9,7 +9,7 @@ #include <StyleRegistry.h> #include "Common.h" -#include "Tar.h" +#include "TarArchive.h" int handleInstallCommand([[maybe_unused]] const QStringList &arguments) { @@ -25,12 +25,26 @@ auto packageName = parser->positionalArguments().first(); auto packagePath = fs::absolute(fs::path(packageName.toStdString())); - auto package = Union::StylePackage{packagePath}; + Union::StylePackage package; + + std::error_code errorCode; + std::optional<QTemporaryDir> tempDir; + if (packagePath.extension() == ".unionstyle") { + auto [dir, path] = extractStyleArchive(packagePath, errorCode); + if (errorCode) { + return errorCode.value(); + } else { + tempDir = std::move(dir); + package = Union::StylePackage(path); + } + } else { + package = Union::StylePackage{packagePath}; + } + if (!package.isValid()) { return printPackageError(package); } - std::error_code errorCode; auto handler = Union::StyleRegistry::instance()->packageHandler(); if (!handler->install(package, errorCode)) { return printErrorCode(package, errorCode); @@ -142,19 +156,33 @@ } auto pathOrId = parser->positionalArguments().first(); + auto packagePath = fs::absolute(fs::path(pathOrId.toStdString())); - auto handler = Union::StyleRegistry::instance()->packageHandler(); + Union::StylePackage package; + std::error_code errorCode; + std::optional<QTemporaryDir> tempDir; - auto package = handler->package(pathOrId); - if (!package.isValid()) { - package = Union::StylePackage{fs::absolute(fs::path(pathOrId.toStdString()))}; + if (!fs::exists(packagePath)) { + auto handler = Union::StyleRegistry::instance()->packageHandler(); + package = handler->package(pathOrId); + packagePath = package.path(); + } else if (packagePath.extension() == ".unionstyle") { + auto [dir, path] = extractStyleArchive(packagePath, errorCode); + if (errorCode) { + return errorCode.value(); + } else { + tempDir = std::move(dir); + package = Union::StylePackage(path); + } + } else { + package = Union::StylePackage(packagePath); } if (!package.isValid()) { return printPackageError(package); } - std::cout << "Path: " << package.path().string() << "\n"; + std::cout << "Path: " << packagePath.string() << "\n"; std::cout << "Input Type: " << qPrintable(package.inputType()) << "\n"; std::cout << "Name: " << qPrintable(package.name()) << "\n"; std::cout << "Description: " << qPrintable(package.description()) << "\n"; @@ -221,7 +249,23 @@ } auto packageName = parser->positionalArguments().first(); - auto package = Union::StylePackage{fs::absolute(fs::path(packageName.toStdString()))}; + auto packagePath = fs::absolute(fs::path(packageName.toStdString())); + + Union::StylePackage package; + + std::error_code errorCode; + std::optional<QTemporaryDir> tempDir; + if (packagePath.extension() == ".unionstyle") { + auto [dir, path] = extractStyleArchive(packagePath, errorCode); + if (errorCode) { + return errorCode.value(); + } else { + tempDir = std::move(dir); + package = Union::StylePackage(path); + } + } else { + package = Union::StylePackage{packagePath}; + } if (!package.isValid()) { return printPackageError(package); @@ -232,7 +276,6 @@ flags |= Union::PackageHandler::OperationFlag::SkipVersionCheck; } - std::error_code errorCode; auto handler = Union::StyleRegistry::instance()->packageHandler(); if (!handler->update(package, errorCode, flags)) { return printErrorCode(package, errorCode); @@ -299,8 +342,13 @@ auto packagedPath = fs::current_path() / QString{package.id() + u".unionstyle"}.toStdString(); std::error_code errorCode; - tarDirectory(package.path(), packagedPath, errorCode); - if (errorCode) { + + Tar::TarArchive archive(packagedPath); + if (!archive.addEntry(package.path(), errorCode)) { + return printErrorCode(package, errorCode); + } + + if (!archive.write(errorCode)) { return printErrorCode(package, errorCode); }
