plyght's own C++ browser for macOS
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

up

plyght 43d9a4f5 5db88081

+176 -24
+132
.github/workflows/release.yml
··· 1 + name: Release Build 2 + 3 + on: 4 + push: 5 + branches: 6 + - main 7 + - master 8 + paths: 9 + - 'CMakeLists.txt' 10 + workflow_dispatch: 11 + inputs: 12 + version: 13 + description: 'Version to rebuild (e.g. 0.1.0). Skips version check, tag, and release creation — only builds and attaches the DMG.' 14 + required: true 15 + 16 + permissions: 17 + contents: write 18 + 19 + jobs: 20 + check-version: 21 + name: Check for version bump 22 + runs-on: ubuntu-latest 23 + outputs: 24 + should_release: ${{ steps.check.outputs.should_release }} 25 + version: ${{ steps.check.outputs.version }} 26 + steps: 27 + - name: Checkout code 28 + uses: actions/checkout@v4 29 + with: 30 + fetch-depth: 0 31 + 32 + - name: Check if version changed 33 + id: check 34 + run: | 35 + CURRENT_VERSION=$(grep -E '^project\(pocb VERSION ' CMakeLists.txt | sed -E 's/.*VERSION ([^ )]+).*/\1/') 36 + echo "Current version: $CURRENT_VERSION" 37 + 38 + git show HEAD^:CMakeLists.txt > /tmp/old_CMakeLists.txt 2>/dev/null || echo 'project(pocb VERSION 0.0.0 LANGUAGES CXX)' > /tmp/old_CMakeLists.txt 39 + PREV_VERSION=$(grep -E '^project\(pocb VERSION ' /tmp/old_CMakeLists.txt | sed -E 's/.*VERSION ([^ )]+).*/\1/') 40 + echo "Previous version: $PREV_VERSION" 41 + 42 + if git ls-remote --tags origin | grep -q "refs/tags/v$CURRENT_VERSION"; then 43 + echo "Tag v$CURRENT_VERSION already exists, skipping release" 44 + echo "should_release=false" >> $GITHUB_OUTPUT 45 + elif [ "$CURRENT_VERSION" != "$PREV_VERSION" ]; then 46 + echo "Version changed from $PREV_VERSION to $CURRENT_VERSION" 47 + echo "should_release=true" >> $GITHUB_OUTPUT 48 + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT 49 + else 50 + echo "Version unchanged, skipping release" 51 + echo "should_release=false" >> $GITHUB_OUTPUT 52 + fi 53 + 54 + create-tag: 55 + name: Create release tag 56 + runs-on: ubuntu-latest 57 + needs: check-version 58 + if: needs.check-version.outputs.should_release == 'true' 59 + steps: 60 + - name: Checkout code 61 + uses: actions/checkout@v4 62 + 63 + - name: Create and push tag 64 + run: | 65 + git config user.name "github-actions[bot]" 66 + git config user.email "github-actions[bot]@users.noreply.github.com" 67 + git tag -a "v${{ needs.check-version.outputs.version }}" -m "Release v${{ needs.check-version.outputs.version }}" 68 + git push origin "v${{ needs.check-version.outputs.version }}" 69 + 70 + create-release: 71 + name: Create GitHub release 72 + runs-on: ubuntu-latest 73 + needs: [check-version, create-tag] 74 + if: needs.check-version.outputs.should_release == 'true' 75 + steps: 76 + - name: Create release 77 + uses: softprops/action-gh-release@v2 78 + with: 79 + tag_name: v${{ needs.check-version.outputs.version }} 80 + name: v${{ needs.check-version.outputs.version }} 81 + body: macOS DMG build for pocb. 82 + draft: false 83 + prerelease: false 84 + env: 85 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 + 87 + build: 88 + name: Build macOS 26 arm64 DMG 89 + runs-on: macos-26 90 + needs: [check-version, create-release] 91 + if: always() && (needs.check-version.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch') 92 + steps: 93 + - name: Checkout code 94 + uses: actions/checkout@v4 95 + 96 + - name: Resolve version 97 + id: version 98 + run: | 99 + VERSION="${{ needs.check-version.outputs.version || github.event.inputs.version }}" 100 + echo "version=$VERSION" >> $GITHUB_OUTPUT 101 + 102 + - name: Setup Bun 103 + uses: oven-sh/setup-bun@v2 104 + 105 + - name: Install Qt 106 + uses: jurplel/install-qt-action@v4 107 + with: 108 + version: '6.7.3' 109 + cache: true 110 + 111 + - name: Configure CMake 112 + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=26.0 113 + 114 + - name: Build app bundle 115 + run: cmake --build build --config Release --parallel 116 + 117 + - name: Deploy Qt frameworks 118 + run: macdeployqt build/pocb.app -verbose=1 119 + 120 + - name: Create DMG 121 + run: | 122 + mkdir -p dist 123 + bunx --yes create-dmg build/pocb.app dist --overwrite 124 + mv dist/pocb*.dmg dist/pocb-${{ steps.version.outputs.version }}-macos26-arm64.dmg 125 + 126 + - name: Upload DMG to release 127 + uses: softprops/action-gh-release@v2 128 + with: 129 + tag_name: v${{ steps.version.outputs.version }} 130 + files: dist/pocb-${{ steps.version.outputs.version }}-macos26-arm64.dmg 131 + env: 132 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+20 -4
src/mac/Vibrancy.mm
··· 18 18 default: return NSVisualEffectMaterialWindowBackground; 19 19 } 20 20 } 21 - NSVisualEffectView *makeVev(NSRect frame, mac::VibrancyMaterial m) { 21 + NSVisualEffectView *makeVev(NSRect frame, mac::VibrancyMaterial m, 22 + NSVisualEffectBlendingMode blendingMode = NSVisualEffectBlendingModeBehindWindow) { 22 23 NSVisualEffectView *v = [[NSVisualEffectView alloc] initWithFrame:frame]; 23 24 v.material = materialFor(m); 24 - v.blendingMode = NSVisualEffectBlendingModeBehindWindow; 25 + v.blendingMode = blendingMode; 25 26 v.state = NSVisualEffectStateFollowsWindowActiveState; 26 27 v.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; 27 28 return v; ··· 101 102 widget->winId(); 102 103 NSView *view = (__bridge NSView *)reinterpret_cast<void *>(widget->winId()); 103 104 if (!view) return; 105 + view.wantsLayer = YES; 106 + view.layer.backgroundColor = NSColor.clearColor.CGColor; 107 + view.layer.cornerRadius = 12.0; 108 + view.layer.masksToBounds = YES; 109 + if (@available(macOS 10.15, *)) { 110 + [view.layer setValue:@"continuous" forKey:@"cornerCurve"]; 111 + } 104 112 for (NSView *sub in view.subviews) { 105 113 if ([sub isKindOfClass:[NSVisualEffectView class]] && 106 - [sub.identifier isEqualToString:@"PocbBehindVibrancy"]) return; 114 + [sub.identifier isEqualToString:@"PocbBehindVibrancy"]) { 115 + [view sortSubviewsUsingFunction:[](NSView *a, NSView *b, void *) -> NSComparisonResult { 116 + const BOOL av = [a isKindOfClass:[NSVisualEffectView class]] && [a.identifier isEqualToString:@"PocbBehindVibrancy"]; 117 + const BOOL bv = [b isKindOfClass:[NSVisualEffectView class]] && [b.identifier isEqualToString:@"PocbBehindVibrancy"]; 118 + if (av == bv) return NSOrderedSame; 119 + return av ? NSOrderedAscending : NSOrderedDescending; 120 + } context:nullptr]; 121 + return; 122 + } 107 123 } 108 - NSVisualEffectView *vev = makeVev(view.bounds, material); 124 + NSVisualEffectView *vev = makeVev(view.bounds, material, NSVisualEffectBlendingModeWithinWindow); 109 125 vev.identifier = @"PocbBehindVibrancy"; 110 126 [view addSubview:vev positioned:NSWindowBelow relativeTo:nil]; 111 127 #else
+16 -14
src/ui/AddressBarController.cpp
··· 18 18 #include <QKeyEvent> 19 19 #include <QLabel> 20 20 #include <QLineEdit> 21 - #include <QGuiApplication> 22 21 #include <QListWidget> 23 22 #include <QListWidgetItem> 24 23 #include <QNetworkAccessManager> ··· 26 25 #include <QNetworkRequest> 27 26 #include <QTimer> 28 27 #include <QUrl> 29 - #include <QScreen> 30 28 #include <QUrlQuery> 31 29 #include <QStyle> 32 30 ··· 433 431 // widget itself sits inside as a transparent child, so its rows 434 432 // can be painted/styled independently of the panel chrome. 435 433 QColor fill = m_theme.panel; 436 - fill.setAlphaF(0.52); 434 + fill.setAlphaF(0.08); 437 435 QColor border = m_theme.border; 438 - border.setAlpha(150); 436 + border.setAlpha(120); 439 437 m_popup = new AddrPopupFrame(fill, border); 440 - m_popup->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::WindowStaysOnTopHint); 441 - m_popup->setAttribute(Qt::WA_ShowWithoutActivating); 442 - m_popup->setAttribute(Qt::WA_MacAlwaysShowToolWindow); 438 + m_popup->setParent(m_bar ? m_bar->window() : nullptr); 439 + m_popup->setWindowFlags(Qt::Widget); 443 440 m_popup->setAttribute(Qt::WA_TranslucentBackground); 444 441 m_popup->setAttribute(Qt::WA_NoSystemBackground); 445 442 m_popup->setAutoFillBackground(false); ··· 514 511 && anchor->window()->findChild<QWidget *>("WebTopbar")->isAncestorOf(anchor); 515 512 516 513 const QPoint anchorBottom = anchor->mapToGlobal(QPoint(0, anchor->height())); 517 - QRect available; 518 - if (QScreen *screen = QGuiApplication::screenAt(anchorBottom)) { 519 - available = screen->availableGeometry(); 520 - } 514 + const QPoint parentBottom = m_popup->parentWidget() 515 + ? m_popup->parentWidget()->mapFromGlobal(anchorBottom) 516 + : anchorBottom; 517 + const QRect available = m_popup->parentWidget() 518 + ? m_popup->parentWidget()->rect() 519 + : QRect(); 521 520 522 521 const int rows = qMin(m_popupList ? m_popupList->count() : 0, 9); 523 522 const int height = qMax(1, rows) * 30 + 12; 524 523 int width = inTopbar ? qBound(420, anchor->width(), 720) 525 524 : qMax(anchor->width(), 320); 526 - int x = anchorBottom.x() + (anchor->width() - width) / 2; 527 - int y = anchorBottom.y() + (inTopbar ? 8 : 6); 525 + int x = parentBottom.x() + (anchor->width() - width) / 2; 526 + int y = parentBottom.y() + (inTopbar ? 8 : 6); 528 527 529 528 if (available.isValid()) { 530 529 width = qMin(width, available.width() - 24); ··· 539 538 void AddressBarController::showPopup() { 540 539 if (!m_popup) return; 541 540 positionPopup(); 542 - if (!m_popup->isVisible()) m_popup->show(); 541 + if (!m_popup->isVisible()) { 542 + m_popup->show(); 543 + mac::applyVibrancyBehind(m_popup, mac::VibrancyMaterial::Popover); 544 + } 543 545 m_popup->raise(); 544 546 if (m_bar && !m_bar->hasFocus()) m_bar->setFocus(Qt::OtherFocusReason); 545 547 }
+6 -5
src/ui/FloatingOmnibox.cpp
··· 270 270 void FloatingOmnibox::fetchSuggestions() { 271 271 if (m_pendingQuery.isEmpty()) return; 272 272 if (m_inflight) { 273 - m_inflight->abort(); 274 - m_inflight->deleteLater(); 275 - m_inflight = nullptr; 273 + QNetworkReply *oldReply = m_inflight.data(); 274 + m_inflight.clear(); 275 + oldReply->abort(); 276 + oldReply->deleteLater(); 276 277 } 277 278 const auto eng = engineFor(m_engineHost); 278 279 QUrl url; ··· 295 296 296 297 void FloatingOmnibox::onSuggestionsReceived(QNetworkReply *reply) { 297 298 reply->deleteLater(); 298 - if (reply != m_inflight) return; 299 - m_inflight = nullptr; 299 + if (reply != m_inflight.data()) return; 300 + m_inflight.clear(); 300 301 if (reply->error() != QNetworkReply::NoError) return; 301 302 if (reply->property("query").toString() != m_input->text().trimmed()) return; 302 303
+2 -1
src/ui/FloatingOmnibox.hpp
··· 2 2 3 3 #include "Theme.hpp" 4 4 5 + #include <QPointer> 5 6 #include <QWidget> 6 7 7 8 class QLineEdit; ··· 48 49 QListWidget *m_list = nullptr; 49 50 QWidget *m_divider = nullptr; 50 51 QNetworkAccessManager *m_net = nullptr; 51 - QNetworkReply *m_inflight = nullptr; 52 + QPointer<QNetworkReply> m_inflight; 52 53 QTimer *m_debounce = nullptr; 53 54 QString m_pendingQuery; 54 55 QString m_engineHost; // e.g. "duckduckgo.com"