From 1b3da5fe7e5a702499e80dfefbf75f04cbbec78c Mon Sep 17 00:00:00 2001 From: djantti Date: Sat, 31 Jan 2026 21:45:23 +0200 Subject: [PATCH 01/10] Add initial Flatpak build job --- .github/workflows/build.yml | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c832932fa412..5d03329bf0a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -514,6 +514,46 @@ jobs: name: ${{ matrix.artifacts_name }} path: ${{ matrix.artifacts_path }} + build-flatpak: + name: "Flatpak" + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 + options: --privileged + volumes: + - /usr:/host/usr + - /opt:/host/opt + strategy: + matrix: + variant: + - arch: x86_64 + runner: ubuntu-24.04 + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.variant.runner }} + steps: + - name: "Recover host disk space" + run: | + rm -rf /host/opt/hostedtoolcache/CodeQL + rm -rf /host/opt/hostedtoolcache/go + rm -rf /host/usr/local/lib/android + rm -rf /host/usr/local/.ghcup + rm -rf /host/usr/local/share/powershell + rm -rf /host/usr/share/swift + rm -rf /host/usr/share/dotnet + + - name: "Check out repository" + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: "Build Flatpak" + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: Mixxx_${{ matrix.variant.arch }}.flatpak + manifest-path: packaging/flatpak/org.mixxx.Mixxx.yaml + arch: ${{ matrix.variant.arch }} + upload-artifact: true + update_manifest: name: "Update manifest file on download server" runs-on: ubuntu-latest From 3bc6ae559547bd1fa0a88c308cd6a9c6d9b0c33a Mon Sep 17 00:00:00 2001 From: djantti Date: Wed, 4 Feb 2026 08:23:57 +0200 Subject: [PATCH 02/10] Build Flatpak Debug extension --- .github/workflows/build.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d03329bf0a3..d50f495ea646 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -549,10 +549,29 @@ jobs: - name: "Build Flatpak" uses: flatpak/flatpak-github-actions/flatpak-builder@v6 with: - bundle: Mixxx_${{ matrix.variant.arch }}.flatpak + bundle: Mixxx-${{ matrix.variant.arch }}.flatpak manifest-path: packaging/flatpak/org.mixxx.Mixxx.yaml arch: ${{ matrix.variant.arch }} - upload-artifact: true + upload-artifact: false + + - name: "Create Flatpak Debug extension" + run: | + /app/bin/flatpak build-bundle repo \ + --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \ + --arch=${{ matrix.variant.arch }} \ + --runtime Mixxx-${{ matrix.variant.arch }}.Debug.flatpak org.mixxx.Mixxx.Debug + + - name: "Upload Flatpak" + uses: actions/upload-artifact@v6 + with: + name: Flatpak ${{ matrix.variant.arch }} + path: Mixxx-${{ matrix.variant.arch }}.flatpak + + - name: "Upload Flatpak Debug extension" + uses: actions/upload-artifact@v6 + with: + name: Flatpak Debug ${{ matrix.variant.arch }} + path: Mixxx-${{ matrix.variant.arch }}.Debug.flatpak update_manifest: name: "Update manifest file on download server" From 43fbe3669774a013db4af169e5155c415b3b32cb Mon Sep 17 00:00:00 2001 From: djantti Date: Thu, 12 Feb 2026 09:46:41 +0200 Subject: [PATCH 03/10] Sign repo commits and add version info to bundle names --- .github/workflows/build.yml | 40 ++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d50f495ea646..6de023d1c462 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -546,32 +546,54 @@ jobs: with: fetch-depth: 0 + - name: "Store Git version" + run: | + git config --global --add safe.directory "${GITHUB_WORKSPACE}" + GIT_DESC=$(git describe --always --first-parent --dirty=-modified) + if [ -z "$GIT_DESC" ]; then + GIT_DESC="unknown" + fi + echo "GIT_DESC=$GIT_DESC" >> $GITHUB_ENV + - name: "Build Flatpak" uses: flatpak/flatpak-github-actions/flatpak-builder@v6 with: - bundle: Mixxx-${{ matrix.variant.arch }}.flatpak manifest-path: packaging/flatpak/org.mixxx.Mixxx.yaml arch: ${{ matrix.variant.arch }} + build-bundle: false upload-artifact: false - - name: "Create Flatpak Debug extension" + - name: "Sign repo commit" + if: env.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY + run: | + gpg --import <(echo "${{ secrets.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY }}") + flatpak build-sign --gpg-sign="rryan@mixxx.org" repo org.mixxx.Mixxx + env: + RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY: ${{ secrets.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY }} + + - name: "Create Flatpak bundle" + run: | + flatpak build-bundle repo \ + --arch=${{ matrix.variant.arch }} \ + Mixxx-${GIT_DESC}-${{ matrix.variant.arch }}.flatpak org.mixxx.Mixxx + + - name: "Create Debug extension" run: | - /app/bin/flatpak build-bundle repo \ - --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo \ + flatpak build-bundle repo \ --arch=${{ matrix.variant.arch }} \ - --runtime Mixxx-${{ matrix.variant.arch }}.Debug.flatpak org.mixxx.Mixxx.Debug + --runtime Mixxx-${GIT_DESC}-${{ matrix.variant.arch }}.Debug.flatpak org.mixxx.Mixxx.Debug - - name: "Upload Flatpak" + - name: "Upload Flatpak bundle" uses: actions/upload-artifact@v6 with: name: Flatpak ${{ matrix.variant.arch }} - path: Mixxx-${{ matrix.variant.arch }}.flatpak + path: Mixxx-${{ env.GIT_DESC }}-${{ matrix.variant.arch }}.flatpak - - name: "Upload Flatpak Debug extension" + - name: "Upload Debug extension" uses: actions/upload-artifact@v6 with: name: Flatpak Debug ${{ matrix.variant.arch }} - path: Mixxx-${{ matrix.variant.arch }}.Debug.flatpak + path: Mixxx-${{ env.GIT_DESC }}-${{ matrix.variant.arch }}.Debug.flatpak update_manifest: name: "Update manifest file on download server" From 69d48cbdd491eb8eeb2406f542e8bd5f9c165160 Mon Sep 17 00:00:00 2001 From: fixx Date: Tue, 17 Feb 2026 20:08:34 +0100 Subject: [PATCH 04/10] fix send ramping in reverb effect --- lib/reverb/Reverb.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reverb/Reverb.cc b/lib/reverb/Reverb.cc index 7e0bf2e56f61..1049235823f3 100644 --- a/lib/reverb/Reverb.cc +++ b/lib/reverb/Reverb.cc @@ -450,7 +450,7 @@ void MixxxPlateX2::processBuffer(const sample_t* in, sample_t* out, const uint f double damp = exp(-M_PI * (.0005+.9995*dampingParam)); tank.damping[0].set(damp); tank.damping[1].set(damp); - RampingValue send(pow(currentSend, 1.53), previousSend, frames); + RampingValue send(pow(previousSend, 1.53), pow(currentSend, 1.53), frames); // the modulated lattices interpolate, which needs truncated float DSP::FPTruncateMode _truncate; From c12e8058964b78a257976c061ba573e05cf1921c Mon Sep 17 00:00:00 2001 From: djantti Date: Wed, 18 Feb 2026 08:42:39 +0200 Subject: [PATCH 05/10] Use a more descriptive Debug extension name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6de023d1c462..8ea9cb91bb83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -592,7 +592,7 @@ jobs: - name: "Upload Debug extension" uses: actions/upload-artifact@v6 with: - name: Flatpak Debug ${{ matrix.variant.arch }} + name: Flatpak Debug Extension ${{ matrix.variant.arch }} path: Mixxx-${{ env.GIT_DESC }}-${{ matrix.variant.arch }}.Debug.flatpak update_manifest: From 7f9449fe5f7020637ee4b42e5c115d635751e3fd Mon Sep 17 00:00:00 2001 From: fixx Date: Wed, 18 Feb 2026 18:28:54 +0100 Subject: [PATCH 06/10] fixed ramping of the send and feedback parameters in echo effect --- src/effects/backends/builtin/echoeffect.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/effects/backends/builtin/echoeffect.cpp b/src/effects/backends/builtin/echoeffect.cpp index 37306f88f1de..bfcb3cbe5aa4 100644 --- a/src/effects/backends/builtin/echoeffect.cpp +++ b/src/effects/backends/builtin/echoeffect.cpp @@ -167,13 +167,13 @@ void EchoEffect::processChannel( int read_position = pGroupState->write_position; decrementRing(&read_position, delay_samples, pGroupState->delay_buf.size()); - RampingValue send(send_current, - pGroupState->prev_send, + RampingValue send(pGroupState->prev_send, + send_current, engineParameters.framesPerBuffer()); // Feedback the delay buffer and then add the new input. - RampingValue feedback(feedback_current, - pGroupState->prev_feedback, + RampingValue feedback(pGroupState->prev_feedback, + feedback_current, engineParameters.framesPerBuffer()); int rampIndex = 0; From f38381d0a5200cfdee4b2b6ceeeb9da4b96c52e5 Mon Sep 17 00:00:00 2001 From: djantti Date: Wed, 18 Feb 2026 23:10:49 +0200 Subject: [PATCH 07/10] Remove bundle artifact signing step --- .github/workflows/build.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ea9cb91bb83..7b3ec0e04956 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -563,14 +563,6 @@ jobs: build-bundle: false upload-artifact: false - - name: "Sign repo commit" - if: env.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY - run: | - gpg --import <(echo "${{ secrets.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY }}") - flatpak build-sign --gpg-sign="rryan@mixxx.org" repo org.mixxx.Mixxx - env: - RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY: ${{ secrets.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY }} - - name: "Create Flatpak bundle" run: | flatpak build-bundle repo \ From 9fbb4e1ff248586f4502a71ecbe7159e6035a7c6 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 16 Feb 2026 20:52:37 +0100 Subject: [PATCH 08/10] History: prevent deletion of current history playlist after purging tracks --- src/library/dao/playlistdao.cpp | 33 ++++++++++++++++++++++---- src/library/dao/playlistdao.h | 8 ++++++- src/library/trackset/setlogfeature.cpp | 2 ++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index a4a75485cb57..07e355ec58f6 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -13,7 +13,8 @@ #include "util/math.h" PlaylistDAO::PlaylistDAO() - : m_pAutoDJProcessor(nullptr) { + : m_currentHistoryPlaylist(kInvalidPlaylistId), + m_pAutoDJProcessor(nullptr) { } void PlaylistDAO::initialize(const QSqlDatabase& database) { @@ -277,19 +278,35 @@ bool PlaylistDAO::deleteUnlockedPlaylists(QStringList&& idStringList) { } bool PlaylistDAO::deleteAllUnlockedPlaylistsWithFewerTracks( - PlaylistDAO::HiddenType type, int minNumberOfTracks) { + PlaylistDAO::HiddenType type, + int minNumberOfTracks, + bool skipCurrHistory) { + // Note: this slot is also called after purging tracks in order to delete + // now empty history playlists. + // Though, if the current History is now also empty, we must not delete that! + // Else the following session log is lost -- or rather not recorded in the + // first place since the id passed to appendTrackToPlaylist() does not exist + // anymore. + // skipCurrHistory prevents that. VERIFY_OR_DEBUG_ASSERT(minNumberOfTracks > 0) { return false; // nothing to do, probably unintended invocation } QSqlQuery query(m_database); - query.prepare(QStringLiteral( + QString queryString = QStringLiteral( "SELECT id FROM Playlists " "WHERE (SELECT count(playlist_id) FROM PlaylistTracks WHERE " "Playlists.ID = PlaylistTracks.playlist_id) < :length AND " - "Playlists.hidden = :hidden AND Playlists.locked = 0")); + "Playlists.hidden = :hidden AND Playlists.locked = 0"); + if (skipCurrHistory) { + queryString.append(QStringLiteral(" AND Playlists.ID != :currHistoryId")); + } + query.prepare(queryString); query.bindValue(":hidden", static_cast(type)); query.bindValue(":length", minNumberOfTracks); + if (skipCurrHistory) { + query.bindValue(":currHistoryId", m_currentHistoryPlaylist); + } if (!query.exec()) { LOG_FAILED_QUERY(query); return false; @@ -299,6 +316,11 @@ bool PlaylistDAO::deleteAllUnlockedPlaylistsWithFewerTracks( while (query.next()) { idStringList.append(query.value(0).toString()); } + + if (idStringList.isEmpty()) { + return false; + } + qInfo() << "Prepared deletion of" << idStringList.size() << "playlists of type" << type << "that contain fewer than" << minNumberOfTracks << "tracks"; @@ -991,7 +1013,8 @@ void PlaylistDAO::removeTracksFromPlaylists(const QList& trackIds, bool transaction.commit(); // We may now have empty history playlists. Remove them. - deleteAllUnlockedPlaylistsWithFewerTracks(PlaylistDAO::PLHT_SET_LOG, 1); + // Note: does not delete current History playlist. + deleteAllUnlockedPlaylistsWithFewerTracks(PlaylistDAO::PLHT_SET_LOG, 1, true); // update the sidebar emit playlistContentChanged(playlistIds); diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h index fc66e23dcf9f..4c5ac00494a6 100644 --- a/src/library/dao/playlistdao.h +++ b/src/library/dao/playlistdao.h @@ -47,7 +47,8 @@ class PlaylistDAO : public QObject, public virtual DAO { /// Needs to be called inside a transaction. /// @return true on success, false on error bool deleteAllUnlockedPlaylistsWithFewerTracks(const PlaylistDAO::HiddenType type, - int minNumberOfTracks); + int minNumberOfTracks, + bool skipCurrHistory = false); // Rename a playlist void renamePlaylist(const int playlistId, const QString& newName); // Lock or unlock a playlist @@ -115,6 +116,10 @@ class PlaylistDAO : public QObject, public virtual DAO { void getPlaylistsTrackIsIn(TrackId trackId, QSet* playlistSet) const; + void setCurrentHistoryPlaylistId(int id) { + m_currentHistoryPlaylist = id; + } + void setAutoDJProcessor(AutoDJProcessor* pAutoDJProcessor); signals: @@ -146,6 +151,7 @@ class PlaylistDAO : public QObject, public virtual DAO { void populatePlaylistMembershipCache(); QMultiHash m_playlistsTrackIsIn; + int m_currentHistoryPlaylist; AutoDJProcessor* m_pAutoDJProcessor; DISALLOW_COPY_AND_ASSIGN(PlaylistDAO); }; diff --git a/src/library/trackset/setlogfeature.cpp b/src/library/trackset/setlogfeature.cpp index 093ff3703eb3..cfff3fddf90a 100644 --- a/src/library/trackset/setlogfeature.cpp +++ b/src/library/trackset/setlogfeature.cpp @@ -383,6 +383,7 @@ void SetlogFeature::slotGetNewPlaylist() { << set_log_name; } else { m_recentTracks.clear(); + m_playlistDao.setCurrentHistoryPlaylistId(m_currentPlaylistId); } // reload child model again because the 'added' signal fired by PlaylistDAO @@ -447,6 +448,7 @@ void SetlogFeature::slotJoinWithPrevious() { // Change current setlog m_currentPlaylistId = previousPlaylistId; + m_playlistDao.setCurrentHistoryPlaylistId(m_currentPlaylistId); } qDebug() << "slotJoinWithPrevious() current:" << clickedPlaylistId From cb30f0c34dd24b97b472261ad19cdcd75e8c2737 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 19 Feb 2026 00:19:28 +0100 Subject: [PATCH 09/10] PlaylistDAO: add helper playlistExist(), use when appending tracks --- src/library/dao/playlistdao.cpp | 20 ++++++++++++++++++++ src/library/dao/playlistdao.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index 07e355ec58f6..ffe6522b98d4 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -427,9 +427,29 @@ bool PlaylistDAO::removeTracksFromPlaylist(int playlistId, int startIndex) { return true; } +bool PlaylistDAO::playlistExists(const int playlistId) const { + ScopedTransaction transaction(m_database); + QSqlQuery query(m_database); + query.prepare(QStringLiteral("SELECT id FROM Playlists WHERE id = :id")); + query.bindValue(":id", playlistId); + + if (!query.exec()) { + LOG_FAILED_QUERY(query); + return false; + } + + if (query.next()) { + // id is guaranteed to be unique, so we can return here + return true; + } + // not found + return false; +} + bool PlaylistDAO::appendTracksToPlaylist(const QList& trackIds, const int playlistId) { // qDebug() << "PlaylistDAO::appendTracksToPlaylist" // << QThread::currentThread() << m_database.connectionName(); + DEBUG_ASSERT(playlistExists(playlistId)); // Start the transaction ScopedTransaction transaction(m_database); diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h index 4c5ac00494a6..d79884350095 100644 --- a/src/library/dao/playlistdao.h +++ b/src/library/dao/playlistdao.h @@ -56,6 +56,8 @@ class PlaylistDAO : public QObject, public virtual DAO { int setPlaylistsLocked(const QSet& playlistIds, const bool lock); // Find out the state of a playlist lock bool isPlaylistLocked(const int playlistId) const; + // Check if a playlist exists + bool playlistExists(const int playlistId) const; // Append a list of tracks to a playlist bool appendTracksToPlaylist(const QList& trackIds, const int playlistId); // Append a track to a playlist From 5629defb06147a3ea8a0e2916f4a2acca40425da Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 12 Feb 2026 16:09:24 +0100 Subject: [PATCH 10/10] Tracks: avoid GUI lag when restoring large track selections --- src/widget/wlibrarytableview.cpp | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/widget/wlibrarytableview.cpp b/src/widget/wlibrarytableview.cpp index 582e4733775e..aab492ccb661 100644 --- a/src/widget/wlibrarytableview.cpp +++ b/src/widget/wlibrarytableview.cpp @@ -110,15 +110,45 @@ bool WLibraryTableView::restoreTrackModelState( verticalScrollBar()->setValue(state->verticalScrollPosition); horizontalScrollBar()->setValue(state->horizontalScrollPosition); - auto* pSelection = selectionModel(); - pSelection->clearSelection(); + // Build a selection range rather than selecting each track individually, + // which can lag the GUI by spamming selectionChanged() handlers. + QItemSelectionModel* pSelectionModel = selectionModel(); + pSelectionModel->clearSelection(); + QItemSelection newSelection; QModelIndexList selectedRows = state->selectedRows; + QModelIndex topLeft; + QModelIndex bottomRight; if (!selectedRows.isEmpty()) { - for (auto index : std::as_const(selectedRows)) { - pSelection->select(index, - QItemSelectionModel::Select | QItemSelectionModel::Rows); + // In saveTrackModelState() we fill state->selectedRows with the sorted + // rows, hence no need to sort here. + for (const QModelIndex& index : std::as_const(selectedRows)) { + if (!topLeft.isValid()) { + // start new range. only done once for first row + topLeft = index; + bottomRight = index; + continue; + } + + if (index.row() == bottomRight.row() + 1) { + // continuous range + bottomRight = index; + continue; + } else { + // prev index was end of range, add current range to selection + // and start a new one + newSelection.select(topLeft, bottomRight); + topLeft = index; + bottomRight = index; + } + } + + // If we reached end, submit the last selection + if (bottomRight == selectedRows.last()) { + newSelection.select(topLeft, bottomRight); } } + pSelectionModel->select(newSelection, + QItemSelectionModel::Select | QItemSelectionModel::Rows); QModelIndex currIndex = state->currentIndex; restoreCurrentIndex(currIndex);