diff --git a/.github/workflows/README.md b/.github/workflows/README.md
new file mode 100644
index 0000000..b385d0d
--- /dev/null
+++ b/.github/workflows/README.md
@@ -0,0 +1,116 @@
+# GitHub Actions Build System
+
+This project uses GitHub Actions for automated building and releasing across multiple platforms.
+
+## Available Workflows
+
+### 1. CI Build (`ci.yml`)
+
+**Trigger**: Push to main/master branches, pull requests
+**Purpose**: Quick validation builds on all platforms
+**Artifacts**: Platform-specific executables
+
+### 2. Build Qt (`build.yml`)
+
+**Trigger**: Push to main/master branches, pull requests, manual dispatch
+**Purpose**: Comprehensive builds using both qmake and CMake
+**Artifacts**:
+
+- qmake builds for each platform
+- CMake builds for each platform
+
+### 3. Release (`release.yml`)
+
+**Trigger**: Git tags (v*), manual dispatch
+**Purpose**: Create official releases with distributable packages
+**Artifacts**:
+
+- macOS: DMG installer
+- Windows: ZIP package with dependencies
+- Linux: TAR.GZ package
+
+## Usage
+
+### Automatic Builds
+
+Every push to main/master branches triggers CI builds to ensure code quality.
+
+### Creating a Release
+
+1. Create and push a version tag:
+
+ ```bash
+ git tag v1.0.0
+ git push origin v1.0.0
+ ```
+
+2. The release workflow will automatically:
+ - Build for all platforms
+ - Create distributable packages
+ - Create a GitHub release with artifacts
+
+### Manual Release
+
+1. Go to Actions tab in GitHub
+2. Select "Build and Release Clouck"
+3. Click "Run workflow"
+4. Enter version number (e.g., v1.0.0)
+5. Click "Run workflow"
+
+## Build Requirements
+
+### macOS
+
+- Qt 6.5.0
+- Xcode Command Line Tools
+- macdeployqt for app bundling
+
+### Windows
+
+- Qt 6.5.0
+- Visual Studio Build Tools or MinGW
+- windeployqt for dependency deployment
+
+### Linux
+
+- Qt 6.5.0
+- GCC/Clang
+- X11 development libraries
+- XFixes development libraries
+
+## Artifacts
+
+Each workflow produces platform-specific artifacts:
+
+- **macOS**: `.app` bundle or `.dmg` installer
+- **Windows**: Executable with Qt dependencies
+- **Linux**: Executable with shared libraries
+
+## Configuration
+
+The workflows use:
+
+- `jurplel/install-qt-action@v4` for Qt installation
+- Matrix builds for multi-platform support
+- Artifact upload for build retention
+- Automatic release creation for tagged builds
+
+## Troubleshooting
+
+### Build Failures
+
+1. Check Qt version compatibility
+2. Verify platform-specific dependencies
+3. Review build logs in GitHub Actions
+
+### Missing Dependencies
+
+- Ensure all required Qt modules are specified
+- Check platform-specific library installations
+- Verify CMake/qmake configuration
+
+### Release Issues
+
+- Ensure proper version tagging format (v*)
+- Check GitHub token permissions
+- Verify artifact upload paths
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..9c3d1ee
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,143 @@
+name: Build Qt Application
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+ branches: [ main, master ]
+ workflow_dispatch:
+
+jobs:
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ modules: "qt5compat"
+ cache: true
+ tools: "tools_ninja"
+ install-deps: "true"
+
+ - name: Build with qmake
+ run: |
+ qmake Clouck.pro CONFIG+=release
+ make
+
+ - name: Create macOS Bundle
+ run: |
+ macdeployqt build/Clouck.app -dmg
+
+ - name: Upload macOS Build
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-qmake
+ path: build/Clouck.dmg
+
+ build-windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Setup MSVC Environment
+ uses: ilammy/msvc-dev-cmd@v1
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ modules: "qt5compat"
+ cache: true
+ tools: "tools_ninja"
+ install-deps: "true"
+
+ - name: Build with qmake
+ shell: cmd
+ run: |
+ qmake Clouck.pro CONFIG+=release
+ nmake
+
+ - name: Deploy Windows Build
+ run: |
+ mkdir Clouck-windows-dist
+ copy build\Clouck.exe Clouck-windows-dist\
+ windeployqt --release Clouck-windows-dist\Clouck.exe
+
+ - name: Upload Windows Build
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-windows-qmake
+ path: Clouck-windows-dist
+
+ # build-linux:
+ # runs-on: ubuntu-latest
+ # steps:
+ # - uses: actions/checkout@v6
+
+ # - name: Install Dependencies
+ # run: |
+ # sudo apt-get update
+ # sudo apt-get install -y build-essential qt6-base-dev qt6-tools-dev libx11-dev libxfixes-dev
+
+ # - name: Install Qt
+ # uses: jurplel/install-qt-action@v4
+ # with:
+ # version: "6.5.*"
+ # modules: "qt5compat"
+ # cache: true
+
+ # - name: Build with qmake
+ # run: |
+ # qmake Clouck.pro CONFIG+=release
+ # make
+
+ # - name: Create Linux Distribution
+ # run: |
+ # mkdir -p Clouck-linux-dist
+ # cp Clouck Clouck-linux-dist/
+ # ldd Clouck | grep -o '/lib[^ ]*' | xargs -I {} cp {} Clouck-linux-dist/ 2>/dev/null || true
+
+ # - name: Upload Linux Build
+ # uses: actions/upload-artifact@v7
+ # with:
+ # name: Clouck-linux-qmake
+ # path: Clouck-linux-dist
+
+ build-cmake:
+ strategy:
+ matrix:
+ os: [ windows-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ modules: "qt5compat"
+ cache: true
+
+ - name: Install Linux Dependencies
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y build-essential cmake libx11-dev libxfixes-dev
+
+ - name: Configure CMake
+ run: |
+ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+
+ - name: Build with CMake
+ run: cmake --build build --config Release
+
+ - name: Upload CMake Build
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-${{ runner.os }}-cmake
+ path: build/Clouck*
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..4073be9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,200 @@
+name: CI Build
+
+on:
+ push:
+ branches: [ main, master ]
+ paths:
+ - "**.cpp"
+ - "**.h"
+ - "**.pro"
+ - "CMakeLists.txt"
+ - ".github/workflows/ci.yml"
+ pull_request:
+ branches: [ main, master ]
+ paths:
+ - "**.cpp"
+ - "**.h"
+ - "**.pro"
+ - "CMakeLists.txt"
+ - ".github/workflows/ci.yml"
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ windows-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Setup MSVC Environment
+ if: runner.os == 'Windows'
+ uses: ilammy/msvc-dev-cmd@v1
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ modules: "qt5compat"
+ cache: true
+ tools: "tools_ninja"
+ install-deps: "true"
+ aqtversion: "==3.1.*"
+ py7zrversion: ">=0.20.2"
+
+ # - name: Install Linux Dependencies
+ # if: runner.os == 'Linux'
+ # run: |
+ # sudo apt-get update
+ # sudo apt-get install -y build-essential cmake libx11-dev libxfixes-dev
+
+ - name: Configure CMake (Windows)
+ if: runner.os == 'Windows'
+ shell: cmd
+ run: |
+ call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+ echo "Configuring CMake for Windows..."
+ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+ echo "CMake configuration completed."
+ echo "Checking if build directory was created:"
+ dir build 2>nul && echo "Build directory exists" || echo "Build directory NOT found"
+ echo "Contents of current directory:"
+ dir
+
+ - name: Configure CMake (macOS)
+ if: runner.os == 'macOS'
+ run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+
+ - name: Build (Windows)
+ if: runner.os == 'Windows'
+ shell: cmd
+ run: |
+ call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+ echo "Starting Windows build..."
+ echo "=== Pre-build diagnostics ==="
+ echo "Current directory: %CD%"
+ echo "Checking if CMakeLists.txt exists:"
+ dir CMakeLists.txt 2>nul && echo "✓ CMakeLists.txt found" || echo "✗ CMakeLists.txt NOT found"
+ echo "Contents before build:"
+ dir
+ echo "=== Building with CMake ==="
+ cmake --build build --config Release
+ echo "=== Post-build diagnostics ==="
+ echo "Build completed with exit code: %ERRORLEVEL%"
+ echo "Contents after build:"
+ dir
+ echo "=== Searching for Clouck executable ==="
+ echo "Checking build directory structure:"
+ dir build /s /b 2>nul || echo "Build directory not found or empty"
+ echo "Looking for Clouck.exe in specific locations:"
+ dir build\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\" || echo "✗ Clouck.exe not in build\"
+ dir build\Release\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\Release\" || echo "✗ Clouck.exe not in build\Release\"
+ dir build\MinSizeRel\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\MinSizeRel\" || echo "✗ Clouck.exe not in build\MinSizeRel\"
+ echo "Searching entire directory tree for any Clouck.exe:"
+ dir Clouck.exe /s /b 2>nul || echo "No Clouck.exe found anywhere"
+ echo "Searching for any .exe files in build directory:"
+ dir build\*.exe /s /b 2>nul || echo "No .exe files found in build"
+
+ - name: Build (macOS)
+ if: runner.os == 'macOS'
+ run: cmake --build build --config Release
+
+ - name: Quick Binary Validation (macOS)
+ if: runner.os == 'macOS'
+ run: find ./build -name "Clouck" -type f
+
+ - name: Test Build (macOS)
+ if: runner.os == 'macOS'
+ run: timeout 3s ./build/Clouck.app/Contents/MacOS/Clouck || echo "Test
+ completed"
+
+ - name: Quick Binary Validation (Windows)
+ if: runner.os == 'Windows'
+ shell: cmd
+ run: |
+ echo "=== Windows Quick Binary Validation ==="
+ echo "Current directory: %CD%"
+ echo "Looking for build directory..."
+ dir build 2>nul && echo "Build directory exists" || echo "Build directory NOT found"
+ echo "Looking for Clouck executable in possible locations:"
+ dir build\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\" || echo "✗ Clouck.exe not in build\"
+ dir build\Release\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\Release\" || echo "✗ Clouck.exe not in build\Release\"
+ dir build\MinSizeRel\Clouck.exe 2>nul && echo "✓ Found Clouck.exe in build\MinSizeRel\" || echo "✗ Clouck.exe not in build\MinSizeRel\"
+ echo "Searching entire build directory for any .exe files:"
+ dir build\*.exe /s /b 2>nul || echo "No .exe files found in build directory"
+ echo "=== End Validation ==="
+
+ - name: Test Build (Windows)
+ if: runner.os == 'Windows'
+ shell: cmd
+ run: |
+ echo "=== Windows Test Build Diagnostics ==="
+ echo "Current directory: %CD%"
+ echo "Visual Studio environment is configured (see env vars above)"
+ echo "Checking for build directory..."
+ if exist build (
+ echo "Build directory exists"
+ echo "Contents of build directory:"
+ dir build
+ echo "Looking for Clouck.exe in build directory..."
+ if exist build\Clouck.exe (
+ echo "Found Clouck.exe in build\"
+ echo "Testing Windows binary..."
+ start /min build\Clouck.exe & timeout /t 2 /nobreak >nul 2>&1 & tasklist | findstr Clouck.exe >nul 2>&1
+ ) else (
+ echo "Clouck.exe not found in build\"
+ echo "Checking build\Release directory..."
+ if exist build\Release\Clouck.exe (
+ echo "Found Clouck.exe in build\Release\"
+ echo "Testing Windows binary..."
+ start /min build\Release\Clouck.exe & timeout /t 2 /nobreak >nul 2>&1 & tasklist | findstr Clouck.exe >nul 2>&1
+ ) else (
+ echo "Clouck.exe not found in build\Release\"
+ echo "Checking build\MinSizeRel directory..."
+ if exist build\MinSizeRel\Clouck.exe (
+ echo "Found Clouck.exe in build\MinSizeRel\"
+ echo "Testing Windows binary..."
+ start /min build\MinSizeRel\Clouck.exe & timeout /t 2 /nobreak >nul 2>&1 & tasklist | findstr Clouck.exe >nul 2>&1
+ ) else (
+ echo "ERROR: Clouck.exe not found in any expected location!"
+ echo "Searching entire build directory for any .exe files:"
+ dir build\*.exe /s /b
+ echo "Searching entire working directory for Clouck.exe:"
+ dir Clouck.exe /s /b
+ echo "Build failed - no executable found"
+ exit 1
+ )
+ )
+ )
+ ) else (
+ echo "ERROR: Build directory does not exist!"
+ echo "Contents of current directory:"
+ dir
+ exit 1
+ )
+
+ - name: Upload Build Artifact (Windows)
+ if: runner.os == 'Windows'
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-windows
+ path: build/Clouck*.exe
+
+ - name: Upload Build Artifact (macOS)
+ if: runner.os == 'macOS'
+ run: |
+ echo "Checking for macOS app bundle..."
+ ls -la ./build/Clouck.app/Contents/MacOS/Clouck || echo "Clouck.app not found"
+
+ - name: Upload macOS Artifact
+ if: runner.os == 'macOS'
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-macos
+ path: |
+ build/Clouck.app/**/*
+ !build/Clouck.app/Contents/Frameworks/*
+ !build/Clouck.app/Contents/PlugIns/*
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..6f07a43
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,285 @@
+name: Build and Release Clouck
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Version to build (e.g., v1.0.0)"
+ required: true
+ default: "v1.0.0"
+
+jobs:
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ cache: true
+
+ - name: Configure CMake
+ run: |
+ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Create macOS App Bundle
+ run: |
+ # Debug: Check current directory and build output
+ echo "Current directory: $(pwd)"
+ echo "Build directory contents:"
+ ls -la build/
+ echo "Checking if Clouck executable exists:"
+ ls -la build/Clouck || echo "ERROR: Clouck executable not found!"
+
+ # Create app bundle structure
+ mkdir -p Clouck.app/Contents/MacOS
+ mkdir -p Clouck.app/Contents/Resources
+
+ # Copy executable
+ cp build/Clouck Clouck.app/Contents/MacOS/
+
+ # Verify executable was copied
+ echo "App bundle contents:"
+ ls -la Clouck.app/Contents/MacOS/
+
+ # Create Info.plist
+ cat > Clouck.app/Contents/Info.plist << EOF
+
+
+
+
+ CFBundleExecutable
+ Clouck
+ CFBundleIdentifier
+ com.clouck.app
+ CFBundleName
+ Clouck
+ CFBundleVersion
+ 1.0.0
+ CFBundlePackageType
+ APPL
+ LSUIElement
+
+
+
+ EOF
+
+ # Verify app bundle exists before running macdeployqt
+ echo "Verifying app bundle structure:"
+ if [ -d "Clouck.app" ]; then
+ echo "App bundle directory exists"
+ ls -la Clouck.app/
+ ls -la Clouck.app/Contents/
+ ls -la Clouck.app/Contents/MacOS/
+ else
+ echo "ERROR: Clouck.app directory not found!"
+ exit 1
+ fi
+
+ # Check if macdeployqt is available
+ echo "Checking macdeployqt availability:"
+ which macdeployqt || echo "macdeployqt not found in PATH"
+
+ # Deploy Qt dependencies (ignore non-critical errors)
+ echo "Running macdeployqt..."
+ echo "Current directory before macdeployqt: $(pwd)"
+ echo "Listing current directory contents:"
+ ls -la
+ echo "Calling: macdeployqt Clouck.app -dmg"
+ macdeployqt build/Clouck.app -dmg || echo "macdeployqt failed, will use fallback"
+
+ # Check what was created
+ echo "Files after macdeployqt:"
+ ls -la *.dmg 2>/dev/null || echo "No DMG files found"
+
+ - name: Check DMG Creation
+ run: |
+ echo "Checking for DMG files after macdeployqt:"
+ ls -la *.dmg 2>/dev/null || echo "No DMG files found"
+
+ # Check if DMG was created by macdeployqt
+ if [ -f "Clouck.dmg" ]; then
+ echo "Found Clouck.dmg, renaming to Clouck.dmg"
+ mv Clouck.dmg Clouck.dmg
+ echo "DMG created successfully"
+ else
+ echo "Clouck.dmg not found, checking for other DMG files:"
+ ls -la *.dmg 2>/dev/null || echo "Still no DMG files found"
+ echo "Creating DMG manually using hdiutil..."
+ # Fallback: create DMG manually if macdeployqt failed
+ hdiutil create -volname "Clouck" -srcfolder Clouck.app -ov -format UDZO Clouck.dmg
+ fi
+
+ - name: Upload macOS Artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck
+ path: Clouck.dmg
+
+ build-windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: "6.5.*"
+ cache: true
+
+ - name: Configure CMake
+ run: |
+ cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+
+ - name: Build
+ run: cmake --build build --config Release
+
+ - name: Create Windows Installer
+ run: |
+ # Copy executable and dependencies
+ mkdir Clouck-windows
+ cp build/Release/Clouck.exe Clouck-windows/
+ cp build/Release/*.dll Clouck-windows/ 2>/dev/null || true
+
+ # Deploy Qt dependencies
+ windeployqt --release Clouck-windows/Clouck.exe
+
+ # Create ZIP archive
+ Compress-Archive -Path Clouck-windows -DestinationPath Clouck-windows.zip
+
+ - name: Upload Windows Artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: Clouck-windows
+ path: Clouck-windows.zip
+
+ # build-linux:
+ # runs-on: ubuntu-latest
+ # steps:
+ # - uses: actions/checkout@v6
+ #
+ # - name: Install Dependencies
+ # run: |
+ # sudo apt-get update
+ # sudo apt-get install -y build-essential cmake qt6-base-dev qt6-tools-dev libx11-dev libxfixes-dev
+ #
+ # - name: Install Qt
+ # uses: jurplel/install-qt-action@v4
+ # with:
+ # version: "6.5.*"
+ # cache: true
+ #
+ # - name: Configure CMake
+ # run: |
+ # cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+ #
+ # - name: Build
+ # run: cmake --build build --config Release
+ #
+ # - name: Create Linux AppImage
+ # run: |
+ # mkdir -p Clouck.AppDir/usr/bin
+ # mkdir -p Clouck.AppDir/usr/lib
+ #
+ # cp build/Clouck Clouck.AppDir/usr/bin/
+ #
+ # cat > Clouck.AppDir/clouck.desktop << EOF
+ # [Desktop Entry]
+ # Name=Clouck
+ # Exec=Clouck
+ # Icon=clouck
+ # Type=Application
+ # Categories=Utility;
+ # EOF
+ #
+ # # Copy desktop file
+ # cp Clouck.AppDir/clouck.desktop Clouck.AppDir/
+ #
+ # # Deploy Qt dependencies (simplified)
+ # ldd build/Clouck | grep -o '/lib[^ ]*' | xargs -I {} cp {} Clouck.AppDir/usr/lib/ 2>/dev/null || true
+ #
+ # # Create AppImage (simplified approach)
+ # cd Clouck.AppDir
+ # ln -s usr/bin/Clouck AppRun
+ # cd ..
+ #
+ # # Create tar.gz for distribution
+ # tar -czf Clouck-linux.tar.gz Clouck.AppDir/
+ #
+ # - name: Upload Linux Artifact
+ # uses: actions/upload-artifact@v7
+ # with:
+ # name: Clouck-linux
+ # path: Clouck-linux.tar.gz
+
+ create-release:
+ needs: [ build-macos, build-windows ]
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/v') || github.event_name ==
+ 'workflow_dispatch'
+
+ steps:
+ - name: Download All Artifacts
+ uses: actions/download-artifact@v8
+ with:
+ path: artifacts
+
+ - name: Get Version
+ id: version
+ run: |
+ if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
+ echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
+ else
+ echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v3
+ with:
+ name: Clouck ${{ steps.version.outputs.VERSION }}
+ body: |
+ Cross-platform clock widget application
+
+ ## Features
+ - Always-on-top functionality across all platforms
+ - Fullscreen mode compatibility
+ - Resizable interface
+ - Context menu with customization options
+ - Native platform integration
+
+ ## Downloads
+ - **macOS**: Clouck.dmg
+ - **Windows**: Clouck-windows.zip
+ - **Linux**: Clouck-linux.tar.gz
+
+ ## Installation
+ ### macOS
+ 1. Download Clouck.dmg
+ 2. Open the DMG file
+ 3. Drag Clouck.app to Applications folder
+
+ ### Windows
+ 1. Download Clouck-windows.zip
+ 2. Extract the ZIP file
+ 3. Run Clouck.exe
+
+ ### Linux
+ 1. Download Clouck-linux.tar.gz
+ 2. Extract the tar.gz file
+ 3. Run the Clouck executable
+ files: |
+ artifacts/Clouck/Clouck.dmg
+ artifacts/Clouck-windows/Clouck-windows.zip
+ artifacts/Clouck-linux/Clouck-linux.tar.gz
+ draft: false
+ prerelease: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 980f7d9..9931355 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,48 +47,7 @@
*.idb
*.pdb
-# Kernel Module Compile Results
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-
### C++ ###
-# Prerequisites
-
-# Compiled Object files
-*.slo
-
-# Precompiled Headers
-
-# Compiled Dynamic libraries
-
-# Fortran module files
-*.mod
-*.smod
-
-# Compiled Static libraries
-*.lai
-
-# Executables
-
-## Various built-in generators
-# cmake_find_package generator https://docs.conan.io/en/latest/reference/generators/cmake_find_package.html
-Find*.cmake
-
-# cmake_paths generator https://docs.conan.io/en/1.4/integrations/cmake/cmake_paths_generator.html
-conan_paths.*
-
-# Environment activation scripts produced by https://docs.conan.io/en/latest/mastering/virtualenv.html#virtualenv-generator
-activate_run.ps1
-activate_run.sh
-deactivate_run.ps1
-deactivate_run.sh
-environment_run.ps1.env
-environment_run.sh.env
# C++ objects and libs
*.slo
@@ -102,31 +61,6 @@ environment_run.sh.env
*.dll
*.dylib
-# Qt-es
-object_script.*.Release
-object_script.*.Debug
-*_plugin_import.cpp
-/.qmake.cache
-/.qmake.stash
-*.pro.user
-*.pro.user.*
-*.qbs.user
-*.qbs.user.*
-*.moc
-moc_*.cpp
-moc_*.h
-qrc_*.cpp
-ui_*.h
-*.qmlc
-*.jsc
-Makefile*
-*build-*
-*.qm
-*.prl
-
-# Qt unit tests
-target_wrapper.*
-
# QtCreator
*.autosave
@@ -137,14 +71,6 @@ target_wrapper.*
# QtCreator CMake
CMakeLists.txt.user*
-# QtCreator 4.8< compilation database
-compile_commands.json
-
-# QtCreator local machine specific files for imported projects
-*creator.user*
-
-*_qmlcache.qrc
-
### macOS ###
# General
.DS_Store
@@ -157,39 +83,10 @@ Icon
# Thumbnails
._*
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-### macOS Patch ###
-# iCloud generated files
-*.icloud
-
-CMakeLists.txt.user
-CMakeCache.txt
-CMakeFiles
-CMakeScripts
-Testing
-Makefile
-cmake_install.cmake
-install_manifest.txt
-compile_commands.json
-CTestTestfile.cmake
-_deps
-
+##
.cache
.cmake
.qtc_clangd
+.qmake.stash
build
+*.dmg
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc69017..b3b55f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
-project(flock)
+project(Clouck)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -7,34 +7,69 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find Qt6
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
+# Platform-specific dependencies
+if(WIN32)
+ # Windows-specific libraries
+ set(PLATFORM_LIBS user32 dwmapi gdi32 kernel32)
+elseif(UNIX AND NOT APPLE)
+ # Linux-specific libraries
+ find_package(X11 REQUIRED)
+ set(PLATFORM_LIBS ${X11_LIBRARIES} Xfixes)
+endif()
+
# Set up Qt6
qt6_standard_project_setup()
# Sources
set(SOURCES
main.cpp
- clockwidget.cpp
- configmanager.cpp
+ clock_widget.cpp
+ config_manager.cpp
+ window_helper.cpp
)
set(HEADERS
- clockwidget.h
- configmanager.h
+ clock_widget.h
+ config_manager.h
+ window_helper.h
)
+# Platform-specific headers
+if(WIN32)
+ list(APPEND HEADERS window_helper_windows.h)
+elseif(UNIX AND NOT APPLE)
+ list(APPEND HEADERS window_helper_linux.h)
+elseif(APPLE)
+ list(APPEND HEADERS window_helper_macos.h)
+endif()
+
# Create executable
-qt6_add_executable(flock
+qt6_add_executable(Clouck WIN32 MACOSX_BUNDLE
${SOURCES}
${HEADERS}
)
+# Platform-specific sources and libraries
+if(APPLE)
+ target_sources(Clouck PRIVATE window_helper_macos.mm)
+ target_link_libraries(Clouck PRIVATE "-framework AppKit" "-framework CoreGraphics")
+endif()
+
+if(WIN32)
+ target_sources(Clouck PRIVATE window_helper_windows.cpp)
+ target_link_libraries(Clouck PRIVATE user32 dwmapi)
+endif()
+
+if(UNIX AND NOT APPLE)
+ target_sources(Clouck PRIVATE window_helper_linux.cpp)
+ target_link_libraries(Clouck PRIVATE ${X11_LIBRARIES} Xfixes)
+endif()
+
# Link Qt libraries
-target_link_libraries(flock
- Qt6::Core
- Qt6::Widgets
-)
+target_link_libraries(Clouck PRIVATE Qt6::Core Qt6::Widgets)
# Set up install
-install(TARGETS flock
+install(TARGETS Clouck
RUNTIME DESTINATION bin
+ BUNDLE DESTINATION bin
)
diff --git a/Clouck.pro b/Clouck.pro
new file mode 100644
index 0000000..b826104
--- /dev/null
+++ b/Clouck.pro
@@ -0,0 +1,47 @@
+# Created by and for Qt Creator This file was created for editing the project sources only.
+# You may attempt to use it for building too, by modifying this file here.
+
+TARGET = Clouck
+
+QT = core gui widgets
+
+SOURCES = \
+ $$PWD/main.cpp \
+ $$PWD/clock_widget.cpp \
+ $$PWD/config_manager.cpp \
+ $$PWD/window_helper.cpp
+
+HEADERS = \
+ $$PWD/clock_widget.h \
+ $$PWD/config_manager.h \
+ $$PWD/window_helper.h \
+ $$PWD/window_helper_macos.h \
+ $$PWD/window_helper_windows.h \
+ $$PWD/window_helper_linux.h
+
+macx {
+ OBJECTIVE_SOURCES = $$PWD/window_helper_macos.mm
+ LIBS += -framework AppKit -framework CoreGraphics
+ QMAKE_CXXFLAGS += -x objective-c++
+}
+
+win32 {
+ SOURCES += $$PWD/window_helper_windows.cpp
+ LIBS += -luser32 -ldwmapi -luser32 -lgdi32 -lkernel32
+}
+
+unix:!macx {
+ LIBS += -lX11 -lXfixes
+}
+
+INCLUDEPATH = \
+ $$PWD/.
+
+# Set build output directories
+DESTDIR = $$PWD/build
+OBJECTS_DIR = $$PWD/build
+MOC_DIR = $$PWD/build
+RCC_DIR = $$PWD/build
+UI_DIR = $$PWD/build
+
+#DEFINES =
diff --git a/Info.plist.in b/Info.plist.in
new file mode 100644
index 0000000..a5f4ef9
--- /dev/null
+++ b/Info.plist.in
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleIconFile
+ ${MACOSX_BUNDLE_ICON_FILE}
+ CFBundleIdentifier
+ ${MACOSX_BUNDLE_GUI_IDENTIFIER}
+ CFBundleName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundleDisplayName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundleVersion
+ ${MACOSX_BUNDLE_BUNDLE_VERSION}
+ CFBundleShortVersionString
+ ${MACOSX_BUNDLE_SHORT_VERSION_STRING}
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ ????
+ LSMinimumSystemVersion
+ ${CMAKE_OSX_DEPLOYMENT_TARGET}
+ NSPrincipalClass
+ NSApplication
+ NSHighResolutionCapable
+
+
+
diff --git a/README-CN.md b/README-CN.md
index 944e27e..97f09e4 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -1,4 +1,4 @@
-# QFlock - Qt6 浮动时钟
+# Clouck 浮动时钟
## 功能特性
@@ -11,7 +11,7 @@
- **透明背景** - 支持半透明背景效果
- **自定义样式** - 可调整字体颜色、背景颜色、字体大小
- **设置持久化** - 自动保存和恢复用户设置
-- **跨平台支持** - 基于Qt6,支持macOS/Linux/Windows
+- **跨平台支持** - 基于Qt6,支持 macOS/Linux/Windows
## 使用方法
@@ -38,8 +38,8 @@
./build.sh
# 运行应用
-./build/flock.app/Contents/MacOS/flock # macOS
-./build/flock # Linux
+./build/Clouck.app/Contents/MacOS/Clouck # macOS
+./build/Clouck # Linux
```
#### 手动构建:
@@ -49,14 +49,14 @@
./clean.sh
# 生成Makefile
-qmake flock.pro
+qmake Clouck.pro
# 编译
make
# 运行
-./build/flock.app/Contents/MacOS/flock # macOS
-./build/flock # Linux
+./build/Clouck.app/Contents/MacOS/Clouck # macOS
+./build/Clouck # Linux
```
#### 使用CMake构建:
@@ -65,22 +65,22 @@ make
mkdir build && cd build
cmake ..
make
-./flock
+./Clouck
```
### 项目结构
```text
-qflock/
+qClouck/
├── build/ # 构建输出目录
-│ ├── flock.app/ # macOS应用包
+│ ├── Clouck.app/ # macOS应用包
│ ├── *.o # 目标文件
│ ├── moc_*.cpp # Qt元对象编译器输出
│ └── Makefile # 构建文件
├── src/ # 源代码(建议)
├── build.sh # 一键构建脚本
├── clean.sh # 清理脚本
-├── flock.pro # qmake项目文件
+├── Clouck.pro # qmake项目文件
├── CMakeLists.txt # CMake项目文件
└── README.md
```
@@ -98,13 +98,13 @@ qflock/
### 构建输出管理
- 所有编译生成的文件(.o, moc_*.cpp, Makefile等)都会输出到`build/`目录
-- 可执行文件位于`build/flock.app/Contents/MacOS/flock`(macOS)或`build/flock`(Linux)
+- 可执行文件位于`build/Clouck.app/Contents/MacOS/Clouck`(macOS)或`build/Clouck`(Linux)
- 使用`./clean.sh`可以快速清理所有构建文件
- 使用`./build.sh`可以进行完整的一键构建
### 自定义构建配置
-可以在`flock.pro`中修改以下配置:
+可以在`Clouck.pro`中修改以下配置:
- `DESTDIR`:可执行文件输出目录
- `OBJECTS_DIR`:目标文件输出目录
diff --git a/README.md b/README.md
index d558c56..8121ce9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
-# ⏰ Flock
+# ☁ Clouck
-
-
+
+
+
+
This project aims to build a cross-platform floating clock based on Qt6.
@@ -15,10 +17,11 @@ When on macOS / Linux, the clock on the menu bar is too small to be noticeable,
## ✨ Features
- [ ] **Main Window**
- - [ ] Frameless
+ - [x] Always On Top
+ - [x] Frameless
- [x] Drag and Move
- [x] Right Click Menu
- - [x] Always On Top
+ - [x] Resizable
- [ ] Show in Fullscreen Mode
- [x] **Style**
- [x] Set Background Color
@@ -30,7 +33,6 @@ When on macOS / Linux, the clock on the menu bar is too small to be noticeable,
- [x] Reset Settings
- [ ] **Time**
- [ ] Select Time Zone
- - [ ] Set Alarms
## Quick Start
@@ -73,6 +75,37 @@ make
./flock
```
+## 🚀 GitHub Actions
+
+This project uses GitHub Actions for automated building and releasing:
+
+### CI/CD Workflows
+
+- **CI Build**: Validates builds on all platforms for every push/PR
+- **Build Qt**: Comprehensive builds using both qmake and CMake
+- **Release**: Creates distributable packages for tagged releases
+
+### Download Pre-built Binaries
+
+Visit the [Releases](https://github.com/ivaquero/clouck/releases) page to download pre-built binaries for:
+
+- **macOS**: DMG installer with native app bundle
+- **Windows**: ZIP package with all dependencies
+- **Linux**: TAR.GZ package with shared libraries
+
+### Creating a Release
+
+1. Tag a new version:
+
+ ```bash
+ git tag v1.0.0
+ git push origin v1.0.0
+ ```
+
+2. GitHub Actions will automatically build and create a release with binaries for all platforms
+
+See [`.github/workflows/`](.github/workflows/) for detailed workflow configuration.
+
### Configuration
The application now uses XML configuration files instead of QSettings. Configuration is stored in `config.xml` with the following options:
diff --git a/XML_CONFIG_GUIDE.md b/XML_CONFIG_GUIDE.md
deleted file mode 100644
index e85f1e6..0000000
--- a/XML_CONFIG_GUIDE.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# XML Configuration Implementation
-
-## Overview
-
-The configuration system has been successfully implemented using XML files instead of QSettings. The new ConfigManager class handles all configuration operations.
-
-## Files Created/Modified
-
-### New Files
-
-- [`configmanager.h`](file:///Users/integzz/Documents/GitHub/qflock/configmanager.h) - Configuration manager header
-- [`configmanager.cpp`](file:///Users/integzz/Documents/GitHub/qflock/configmanager.cpp) - Configuration manager implementation
-- [`config.xml`](file:///Users/integzz/Documents/GitHub/qflock/config.xml) - Default configuration file
-
-### Modified Files
-
-- [`clockwidget.h`](file:///Users/integzz/Documents/GitHub/qflock/clockwidget.h) - Replaced QSettings with ConfigManager
-- [`clockwidget.cpp`](file:///Users/integzz/Documents/GitHub/qflock/clockwidget.cpp) - Updated to use XML configuration
-- [`flock.pro`](file:///Users/integzz/Documents/GitHub/qflock/flock.pro) - Added new source files
-- [`CMakeLists.txt`](file:///Users/integzz/Documents/GitHub/qflock/CMakeLists.txt) - Added new source files
-
-## XML Configuration Format
-
-The configuration is stored in XML format with the following structure:
-
-```xml
-
-
- #ffffff
- #96000000
- 24
- true
- 100,100
-
-```
-
-## Configuration Options
-
-- **FontColor**: Font color in hex format (e.g., #ffffff for white)
-- **BackgroundColor**: Background color in hex format with alpha (e.g., #96000000 for semi-transparent black)
-- **FontSize**: Font size in points (8-72)
-- **AlwaysOnTop**: Boolean value (true/false)
-- **WindowPosition**: Window position as "x,y" coordinates
-
-## Usage
-
-The configuration is automatically loaded when the application starts and saved when it closes. Users can:
-
-1. **Modify settings through the UI**: Right-click on the clock to access the context menu
-2. **Edit config.xml directly**: The file is human-readable and can be edited manually
-3. **Reset to defaults**: Use the "Reset Settings" option in the context menu
-
-## Features
-
-- ✅ XML-based configuration storage
-- ✅ Automatic loading on startup
-- ✅ Automatic saving on exit
-- ✅ Default values if config file doesn't exist
-- ✅ Human-readable XML format
-- ✅ Error handling for corrupted config files
-- ✅ Manual editing support
-
-## Build and Test
-
-Use the provided test script to build and verify the XML configuration:
-
-```bash
-./test_xml_config.sh
-```
-
-This will:
-
-1. Clean previous build files
-2. Build the project with XML configuration support
-3. Display the current config.xml content
-4. Provide instructions to run the application
-
-The configuration file will be created automatically when you first run the application and make changes to the settings.
diff --git a/build.sh b/build.sh
index e08e087..a12c682 100755
--- a/build.sh
+++ b/build.sh
@@ -12,18 +12,18 @@ rm -rf build/*
# Generate Makefile using qmake
echo "Generating Makefile..."
-qmake flock.pro
+qmake Clouck.pro
# Build project
echo "Starting compilation..."
make
# Check build result
-if [ -f "build/flock.app/Contents/MacOS/flock" ] || [ -f "build/flock" ]; then
+if [ -f "build/Clouck.app/Contents/MacOS/Clouck" ] || [ -f "build/Clouck" ]; then
echo "Build successful!"
echo "Executable location:"
- [ -f "build/flock.app/Contents/MacOS/flock" ] && echo " - build/flock.app/Contents/MacOS/flock (macOS)"
- [ -f "build/flock" ] && echo " - build/flock (Linux)"
+ [ -f "build/Clouck.app/Contents/MacOS/Clouck" ] && echo " - build/Clouck.app/Contents/MacOS/Clouck (macOS)"
+ [ -f "build/Clouck" ] && echo " - build/Clouck (Linux)"
else
echo "Build failed!"
exit 1
diff --git a/build_cross_platform.sh b/build_cross_platform.sh
new file mode 100755
index 0000000..904e1dd
--- /dev/null
+++ b/build_cross_platform.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+# Cross-platform build script for Clouck
+
+echo "Building Clouck - Cross-platform Clock Application"
+echo "=================================================="
+
+# Detect operating system
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ OS="macOS"
+elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ OS="Linux"
+elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
+ OS="Windows"
+else
+ echo "Unsupported operating system: $OSTYPE"
+ exit 1
+fi
+
+echo "Detected OS: $OS"
+echo ""
+
+# Create build directory
+mkdir -p build
+
+# Clean old build files
+echo "Cleaning old build files..."
+rm -rf build/*
+
+# Build using CMake (cross-platform)
+echo "Configuring with CMake..."
+cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+
+if [ $? -ne 0 ]; then
+ echo "CMake configuration failed!"
+ exit 1
+fi
+
+echo "Building project..."
+cmake --build build --config Release
+
+if [ $? -ne 0 ]; then
+ echo "Build failed!"
+ exit 1
+fi
+
+echo ""
+echo "Build successful!"
+echo ""
+echo "Executable location:"
+
+if [ "$OS" == "macOS" ]; then
+ echo " - build/Clouck.app/Contents/MacOS/Clouck (macOS)"
+elif [ "$OS" == "Windows" ]; then
+ echo " - build/Release/Clouck.exe (Windows)"
+elif [ "$OS" == "Linux" ]; then
+ echo " - build/Clouck (Linux)"
+fi
+
+echo ""
+echo "To run the application:"
+if [ "$OS" == "macOS" ]; then
+ echo " ./build/Clouck.app/Contents/MacOS/Clouck"
+elif [ "$OS" == "Windows" ]; then
+ echo " ./build/Release/Clouck.exe"
+elif [ "$OS" == "Linux" ]; then
+ echo " ./build/Clouck"
+fi
diff --git a/clean.sh b/clean.sh
index 118a202..3dc4e7b 100755
--- a/clean.sh
+++ b/clean.sh
@@ -10,9 +10,9 @@ if [ -d "build" ]; then
fi
# Delete .app bundle
-if [ -d "flock.app" ]; then
- rm -rf flock.app
- echo "flock.app deleted"
+if [ -d "Clouck.app" ]; then
+ rm -rf Clouck.app
+ echo "Clouck.app deleted"
fi
echo "Cleanup complete!"
diff --git a/clockwidget.cpp b/clock_widget.cpp
similarity index 64%
rename from clockwidget.cpp
rename to clock_widget.cpp
index 75abef5..c676f62 100644
--- a/clockwidget.cpp
+++ b/clock_widget.cpp
@@ -1,23 +1,36 @@
-#include "clockwidget.h"
-#include "configmanager.h"
+#include "clock_widget.h"
+#include "config_manager.h"
+#include "window_helper.h"
#include
#include
#include
#include
#include
#include
+#include
ClockWidget::ClockWidget(QWidget *parent)
- : QWidget(parent), m_dragging(false)
+ : QWidget(parent), m_dragging(false), m_resizing(false), m_resizeBorder(5)
{
setupUI();
createMenu();
loadSettings();
- // Set window properties
- setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
+ // Set window properties for cross-platform always-on-top
+ Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool;
+
+#ifdef Q_OS_MAC
+ // On macOS, we need additional flags for proper always-on-top behavior in fullscreen
+ flags |= Qt::WindowDoesNotAcceptFocus;
+ setAttribute(Qt::WA_ShowWithoutActivating);
+#endif
+
+ setWindowFlags(flags);
setAttribute(Qt::WA_TranslucentBackground);
- setFixedSize(200, 80);
+ setMouseTracking(true); // Enable mouse tracking for resize cursor
+
+ // Set up platform-specific window properties after window creation
+ WindowHelper::setupAlwaysOnTopForFullscreen(this);
// Start timer
m_timer = new QTimer(this);
@@ -29,7 +42,7 @@ ClockWidget::ClockWidget(QWidget *parent)
ClockWidget::~ClockWidget()
{
- saveSettings();
+ // Settings are now saved immediately after changes, no need to save here
}
void ClockWidget::setupUI()
@@ -80,19 +93,82 @@ void ClockWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
- m_dragging = true;
- m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
- event->accept();
+ if (isInResizeArea(event->pos()))
+ {
+ m_resizing = true;
+ m_resizeStartPos = event->globalPosition().toPoint();
+ m_resizeStartSize = size();
+ event->accept();
+ }
+ else
+ {
+ m_dragging = true;
+ m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
+ event->accept();
+ }
}
}
void ClockWidget::mouseMoveEvent(QMouseEvent *event)
{
- if (m_dragging && (event->buttons() & Qt::LeftButton))
+ if (m_resizing && (event->buttons() & Qt::LeftButton))
+ {
+ QPoint delta = event->globalPosition().toPoint() - m_resizeStartPos;
+ QSize newSize = m_resizeStartSize + QSize(delta.x(), delta.y());
+
+ // Minimum size constraints
+ newSize = newSize.expandedTo(QSize(100, 50));
+
+ resize(newSize);
+ event->accept();
+ }
+ else if (m_dragging && (event->buttons() & Qt::LeftButton))
{
move(event->globalPosition().toPoint() - m_dragPosition);
event->accept();
}
+ else
+ {
+ // Update cursor shape when hovering over resize area
+ if (isInResizeArea(event->pos()))
+ {
+ setCursor(Qt::SizeFDiagCursor);
+ }
+ else
+ {
+ setCursor(Qt::ArrowCursor);
+ }
+ }
+}
+
+void ClockWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton)
+ {
+ if (m_resizing)
+ {
+ m_resizing = false;
+ // Save the new size and position to config
+ if (m_configManager)
+ {
+ m_configManager->setWindowPosition(pos());
+ m_configManager->setWindowSize(size());
+ m_configManager->saveSettings();
+ }
+ event->accept();
+ }
+ else if (m_dragging)
+ {
+ m_dragging = false;
+ // Save the new position to config
+ if (m_configManager)
+ {
+ m_configManager->setWindowPosition(pos());
+ m_configManager->saveSettings();
+ }
+ event->accept();
+ }
+ }
}
void ClockWidget::contextMenuEvent(QContextMenuEvent *event)
@@ -121,6 +197,12 @@ void ClockWidget::toggleAlwaysOnTop()
}
setWindowFlags(flags);
show();
+
+ // Re-apply platform-specific window properties after changing window flags
+ if (m_alwaysOnTop)
+ {
+ WindowHelper::setupAlwaysOnTopForFullscreen(this);
+ }
}
void ClockWidget::changeFontColor()
@@ -165,6 +247,8 @@ void ClockWidget::resetSettings()
m_configManager->resetToDefaults();
loadSettings();
updateStyleSheet();
+ // Reset to default size
+ resize(m_configManager->windowSize());
}
}
@@ -173,6 +257,14 @@ void ClockWidget::quitApplication()
qApp->quit();
}
+bool ClockWidget::isInResizeArea(const QPoint &pos) const
+{
+ // Check if mouse is in the bottom-right corner resize area
+ QRect resizeArea(rect().bottomRight() - QPoint(m_resizeBorder * 2, m_resizeBorder * 2),
+ QSize(m_resizeBorder * 2, m_resizeBorder * 2));
+ return resizeArea.contains(pos);
+}
+
void ClockWidget::loadSettings()
{
m_configManager = new ConfigManager(this);
@@ -190,14 +282,15 @@ void ClockWidget::loadSettings()
m_fontSize = m_configManager->fontSize();
m_alwaysOnTop = m_configManager->alwaysOnTop();
- // Restore window position
+ // Restore window position and size
move(m_configManager->windowPosition());
+ resize(m_configManager->windowSize());
// Update always on top checkbox in menu
QList actions = m_contextMenu->actions();
for (QAction *action : actions)
{
- if (action->text() == "Always on Top")
+ if (action->text() == QString("Always on Top"))
{
action->setChecked(m_alwaysOnTop);
break;
@@ -218,6 +311,7 @@ void ClockWidget::saveSettings()
m_configManager->setFontSize(m_fontSize);
m_configManager->setAlwaysOnTop(m_alwaysOnTop);
m_configManager->setWindowPosition(pos());
+ m_configManager->setWindowSize(size());
// Save to XML file
if (!m_configManager->saveSettings())
diff --git a/clockwidget.h b/clock_widget.h
similarity index 74%
rename from clockwidget.h
rename to clock_widget.h
index 961b133..3a42148 100644
--- a/clockwidget.h
+++ b/clock_widget.h
@@ -1,5 +1,5 @@
-#ifndef CLOCKWIDGET_H
-#define CLOCKWIDGET_H
+#ifndef CLOCK_WIDGET_H
+#define CLOCK_WIDGET_H
#include
#include
@@ -22,6 +22,7 @@ public:
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private slots:
@@ -48,12 +49,25 @@ private:
QPoint m_dragPosition;
bool m_dragging;
+ // Resize related
+ bool m_resizing;
+ QPoint m_resizeStartPos;
+ QSize m_resizeStartSize;
+ int m_resizeBorder;
+
// Settings
ConfigManager *m_configManager;
QColor m_fontColor;
QColor m_backgroundColor;
int m_fontSize;
bool m_alwaysOnTop;
+
+ // Helper methods
+ bool isInResizeArea(const QPoint &pos) const;
+
+#ifdef Q_OS_MAC
+ void setupMacOSWindowProperties();
+#endif
};
-#endif // CLOCKWIDGET_H
+#endif // CLOCK_WIDGET_H
diff --git a/config.xml b/config.xml
index 95e0d4b..229452a 100644
--- a/config.xml
+++ b/config.xml
@@ -1,8 +1,9 @@
- #ffffff
+ #ffffffff
#96000000
24
true
- 100,100
+ 1132,708
+ 137,59
diff --git a/configmanager.cpp b/config_manager.cpp
similarity index 81%
rename from configmanager.cpp
rename to config_manager.cpp
index 73f78ad..315b219 100644
--- a/configmanager.cpp
+++ b/config_manager.cpp
@@ -1,4 +1,4 @@
-#include "configmanager.h"
+#include "config_manager.h"
#include
#include
@@ -26,7 +26,7 @@ bool ConfigManager::loadSettings(const QString &filename)
QXmlStreamReader reader(&file);
// Check if it's a valid XML file
- if (!reader.readNextStartElement() || reader.name() != "QFlockConfig")
+ if (!reader.readNextStartElement() || reader.name() != QString("QFlockConfig"))
{
qDebug() << "Invalid config file format";
file.close();
@@ -53,7 +53,7 @@ bool ConfigManager::loadSettings(const QString &filename)
}
else if (name == "AlwaysOnTop")
{
- m_alwaysOnTop = (text.toLower() == "true");
+ m_alwaysOnTop = (text.toLower() == QString("true"));
}
else if (name == "WindowPosition")
{
@@ -66,6 +66,17 @@ bool ConfigManager::loadSettings(const QString &filename)
m_windowPosition = QPoint(x, y);
}
}
+ else if (name == "WindowSize")
+ {
+ // Parse size format: "width,height"
+ QStringList dims = text.split(',');
+ if (dims.size() == 2)
+ {
+ int width = dims[0].toInt();
+ int height = dims[1].toInt();
+ m_windowSize = QSize(width, height);
+ }
+ }
else
{
reader.skipCurrentElement();
@@ -117,6 +128,12 @@ bool ConfigManager::saveSettings(const QString &filename)
.arg(m_windowPosition.y());
writer.writeTextElement("WindowPosition", positionStr);
+ // Window size
+ QString sizeStr = QString("%1,%2")
+ .arg(m_windowSize.width())
+ .arg(m_windowSize.height());
+ writer.writeTextElement("WindowSize", sizeStr);
+
// Close root element
writer.writeEndElement();
@@ -143,4 +160,5 @@ void ConfigManager::setDefaultValues()
m_fontSize = 24;
m_alwaysOnTop = true;
m_windowPosition = QPoint(100, 100);
+ m_windowSize = QSize(200, 80); // Default clock size
}
diff --git a/configmanager.h b/config_manager.h
similarity index 86%
rename from configmanager.h
rename to config_manager.h
index 8fa4b39..c89338d 100644
--- a/configmanager.h
+++ b/config_manager.h
@@ -1,9 +1,10 @@
-#ifndef CONFIGMANAGER_H
-#define CONFIGMANAGER_H
+#ifndef CONFIG_MANAGER_H
+#define CONFIG_MANAGER_H
#include
#include
#include
+#include
#include
#include
#include
@@ -27,6 +28,7 @@ public:
int fontSize() const { return m_fontSize; }
bool alwaysOnTop() const { return m_alwaysOnTop; }
QPoint windowPosition() const { return m_windowPosition; }
+ QSize windowSize() const { return m_windowSize; }
// Setters
void setFontColor(const QColor &color) { m_fontColor = color; }
@@ -34,6 +36,7 @@ public:
void setFontSize(int size) { m_fontSize = size; }
void setAlwaysOnTop(bool onTop) { m_alwaysOnTop = onTop; }
void setWindowPosition(const QPoint &pos) { m_windowPosition = pos; }
+ void setWindowSize(const QSize &size) { m_windowSize = size; }
// Reset to default values
void resetToDefaults();
@@ -51,6 +54,7 @@ private:
int m_fontSize;
bool m_alwaysOnTop;
QPoint m_windowPosition;
+ QSize m_windowSize;
};
-#endif // CONFIGMANAGER_H
+#endif // CONFIG_MANAGER_H
diff --git a/doc/clock.png b/doc/clock.png
index 390e557..6165996 100644
Binary files a/doc/clock.png and b/doc/clock.png differ
diff --git a/flock.pro b/flock.pro
deleted file mode 100644
index 7547abd..0000000
--- a/flock.pro
+++ /dev/null
@@ -1,27 +0,0 @@
-# Created by and for Qt Creator This file was created for editing the project sources only.
-# You may attempt to use it for building too, by modifying this file here.
-
-TARGET = flock
-
-QT = core gui widgets
-
-SOURCES = \
- $$PWD/main.cpp \
- $$PWD/clockwidget.cpp \
- $$PWD/configmanager.cpp
-
-HEADERS = \
- $$PWD/clockwidget.h \
- $$PWD/configmanager.h
-
-INCLUDEPATH = \
- $$PWD/.
-
-# Set build output directories
-DESTDIR = $$PWD/build
-OBJECTS_DIR = $$PWD/build
-MOC_DIR = $$PWD/build
-RCC_DIR = $$PWD/build
-UI_DIR = $$PWD/build
-
-#DEFINES =
diff --git a/main.cpp b/main.cpp
index dd73e3c..986638c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,5 +1,5 @@
#include
-#include "clockwidget.h"
+#include "clock_widget.h"
int main(int argc, char *argv[])
{
diff --git a/test_completion.sh b/test_completion.sh
new file mode 100755
index 0000000..9b335e7
--- /dev/null
+++ b/test_completion.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+echo "QFlock Clock Application - macOS Fullscreen Always-on-Top Feature Test"
+echo "======================================================================"
+echo ""
+echo "[SUCCESS] Compilation successful! Application build completed"
+echo "[SUCCESS] macOS fullscreen always-on-top feature integrated"
+echo ""
+echo "Feature highlights:"
+echo "• Basic always-on-top using Qt::WindowStaysOnTopHint"
+echo "• Native macOS window level with NSWindowLevel"
+echo "• Support for all desktop spaces (NSWindowCollectionBehaviorCanJoinAllSpaces)"
+echo "• Fullscreen mode support (NSWindowCollectionBehaviorFullScreenAuxiliary)"
+echo "• Prevents window focus stealing (Qt::WindowDoesNotAcceptFocus)"
+echo ""
+echo "Testing method:"
+echo "1. Run ./build/Clouck.app/Contents/MacOS/Clouck"
+echo "2. Open Safari or other apps in fullscreen mode"
+echo "3. Observe if clock stays on top layer"
+echo ""
+echo "Optimized file structure:"
+echo "• clock_widget.h/cpp - Main clock component"
+echo "• config_manager.h/cpp - Configuration management"
+echo "• window_helper_macos.h/mm - macOS native window helper"
+echo ""
+echo "Press any key to exit..."
+read -n 1 -s
diff --git a/test_config_save.cpp b/test_config_save.cpp
new file mode 100644
index 0000000..08243af
--- /dev/null
+++ b/test_config_save.cpp
@@ -0,0 +1,39 @@
+#include
+#include
+#include
+#include
+#include
+#include "config_manager.h"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ ConfigManager configManager;
+
+ // Set some test values
+ configManager.setWindowPosition(QPoint(100, 200));
+ configManager.setWindowSize(QSize(300, 400));
+
+ // Save to file
+ if (configManager.saveSettings("test_config.xml"))
+ {
+ qDebug() << "Config saved successfully";
+
+ // Read the file content
+ QFile file("test_config.xml");
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text))
+ {
+ QString content = file.readAll();
+ qDebug() << "File content:";
+ qDebug() << content;
+ file.close();
+ }
+ }
+ else
+ {
+ qDebug() << "Failed to save config";
+ }
+
+ return 0;
+}
diff --git a/test_cross_platform.sh b/test_cross_platform.sh
new file mode 100755
index 0000000..3288f52
--- /dev/null
+++ b/test_cross_platform.sh
@@ -0,0 +1,128 @@
+#!/bin/bash
+# Cross-platform test script for Clouck
+
+echo "Clouck Cross-Platform Test Suite"
+echo "================================"
+echo ""
+
+# Detect operating system
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ OS="macOS"
+ EXECUTABLE="./build/Clouck"
+elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ OS="Linux"
+ EXECUTABLE="./build/Clouck"
+elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then
+ OS="Windows"
+ EXECUTABLE="./build/Clouck"
+else
+ echo "Unsupported operating system: $OSTYPE"
+ exit 1
+fi
+
+echo "Detected OS: $OS"
+echo "Executable: $EXECUTABLE"
+echo ""
+
+# Check if executable exists
+if [ ! -f "$EXECUTABLE" ]; then
+ echo "[ERROR] Executable not found: $EXECUTABLE"
+ echo "Please build the project first using: ./build_cross_platform.sh"
+ exit 1
+fi
+
+echo "[SUCCESS] Executable found"
+echo ""
+
+# Test 1: Basic functionality
+echo "Test 1: Basic Clock Functionality"
+echo "---------------------------------"
+echo "• Starting Clouck application..."
+echo "• Application should display a clock widget"
+echo "• Clock should update every second"
+echo ""
+
+# Start the application in background
+$EXECUTABLE &
+PID=$!
+sleep 3
+
+# Check if application is running
+if ps -p $PID >/dev/null; then
+ echo "[SUCCESS] Application started successfully (PID: $PID)"
+else
+ echo "[ERROR] Application failed to start"
+ exit 1
+fi
+
+echo ""
+echo "Test 2: Always-on-Top Feature"
+echo "------------------------------"
+if [ "$OS" == "macOS" ]; then
+ echo "• Open Safari or any other application"
+ echo "• Enter fullscreen mode (Control + Command + F)"
+ echo "• Clock should remain visible on top"
+elif [ "$OS" == "Windows" ]; then
+ echo "• Open any application in fullscreen mode"
+ echo "• Clock should remain visible on top"
+elif [ "$OS" == "Linux" ]; then
+ echo "• Switch to different workspace"
+ echo "• Clock should appear on all workspaces"
+fi
+
+echo ""
+echo "Test 3: Resizable Feature"
+echo "-------------------------"
+echo "• Move mouse to bottom-right corner of clock"
+echo "• Cursor should change to diagonal resize arrow"
+echo "• Click and drag to resize"
+echo "• Release mouse to save new size"
+
+echo ""
+echo "Test 4: Context Menu"
+echo "---------------------"
+echo "• Right-click on clock to show context menu"
+echo "• Test various menu options:"
+echo " - Change color"
+echo " - Change font"
+echo " - Toggle always on top"
+echo " - Toggle click through"
+echo " - Quit application"
+
+echo ""
+echo "Platform-Specific Features:"
+echo "---------------------------"
+if [ "$OS" == "macOS" ]; then
+ echo "• Native macOS window level integration"
+ echo "• Support for all desktop spaces"
+ echo "• Fullscreen mode compatibility"
+elif [ "$OS" == "Windows" ]; then
+ echo "• Windows native window management"
+ echo "• Taskbar exclusion"
+ echo "• Click-through support"
+elif [ "$OS" == "Linux" ]; then
+ echo "• X11 window manager integration"
+ echo "• Workspace stickiness"
+ echo "• EWMH (Extended Window Manager Hints) support"
+fi
+
+echo ""
+echo "To stop testing:"
+echo "• Right-click on clock and select 'Quit'"
+echo "• Or run: kill $PID"
+echo ""
+echo "Test the features now! Press any key when finished..."
+read -n 1 -s
+
+echo ""
+echo "Stopping application..."
+kill $PID 2>/dev/null
+sleep 1
+
+echo ""
+echo "Test completed!"
+echo ""
+echo "Configuration file location:"
+echo "• macOS: ~/Library/Preferences/Clouck/config.xml"
+echo "• Windows: %APPDATA%\\Clouck\\config.xml"
+echo "• Linux: ~/.config/Clouck/config.xml"
diff --git a/test_fullscreen.sh b/test_fullscreen.sh
new file mode 100755
index 0000000..3903c88
--- /dev/null
+++ b/test_fullscreen.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+echo "Testing QFlock Clock Application Fullscreen Always-on-Top Feature..."
+echo ""
+echo "Test Steps:"
+echo "1. Clock application should already be running"
+echo "2. Open any application (like Safari) and enter fullscreen mode"
+echo "3. Observe if the clock still displays above the fullscreen application"
+echo "4. You can use Command+Tab to switch applications, the clock should remain visible"
+echo ""
+echo "Press any key to exit test instructions..."
+read -n 1 -s
+
+echo ""
+echo "Test complete! If the clock remains visible in fullscreen mode, the feature is working correctly."
diff --git a/test_github_actions.sh b/test_github_actions.sh
new file mode 100755
index 0000000..186a238
--- /dev/null
+++ b/test_github_actions.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# GitHub Actions Local Testing Script
+# This script helps test GitHub Actions workflows locally using act
+
+echo "GitHub Actions Local Testing for Clouck"
+echo "========================================"
+
+# Check if act is installed
+if ! command -v act &> /dev/null; then
+ echo "❌ act is not installed. Installing..."
+ echo "Please install act from: https://github.com/nektos/act"
+ echo "On macOS: brew install act"
+ echo "On Linux: curl https://raw.githubusercontent.com/nektos/act/master/install.sh | bash"
+ exit 1
+fi
+
+# Display available workflows
+echo "Available workflows:"
+echo "1. CI Build (ci.yml) - Quick validation builds"
+echo "2. Build Qt (build.yml) - Comprehensive builds"
+echo "3. Release (release.yml) - Create releases"
+echo "4. All workflows"
+echo ""
+
+# Function to run workflow
+run_workflow() {
+ local workflow=$1
+ local event=$2
+
+ echo "Running $workflow with event: $event"
+ act -W .github/workflows/$workflow -e $event --dry-run
+}
+
+# Menu
+read -p "Which workflow do you want to test? (1-4): " choice
+
+case $choice in
+ 1)
+ echo "Testing CI Build workflow..."
+ run_workflow "ci.yml" "push"
+ ;;
+ 2)
+ echo "Testing Build Qt workflow..."
+ run_workflow "build.yml" "push"
+ ;;
+ 3)
+ echo "Testing Release workflow..."
+ # Create a mock release event
+ cat > /tmp/release-event.json << EOF
+ {
+ "ref": "refs/tags/v1.0.0",
+ "repository": {
+ "name": "floating-clock",
+ "owner": {
+ "login": "ivaquero"
+ }
+ }
+ }
+ EOF
+ run_workflow "release.yml" "/tmp/release-event.json"
+ ;;
+ 4)
+ echo "Testing all workflows..."
+ act --dry-run
+ ;;
+ *)
+ echo "Invalid choice. Exiting."
+ exit 1
+ ;;
+esac
+
+echo ""
+echo "To run the workflows for real (not dry-run), remove the --dry-run flag"
+echo "Example: act -W .github/workflows/ci.yml"
+echo ""
+echo "For local testing with Docker, you may need to use different images:"
+echo "act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04"
+echo "act -P macos-latest=nektos/act-environments-macos:latest"
+echo "act -P windows-latest=nektos/act-environments-windows:latest"
diff --git a/test_resizable.sh b/test_resizable.sh
new file mode 100755
index 0000000..7614d76
--- /dev/null
+++ b/test_resizable.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+echo "Testing QFlock Resizable Feature"
+echo "================================"
+echo ""
+echo "1. Starting QFlock application..."
+./build/Clouck.app/Contents/MacOS/Clouck &
+PID=$!
+
+sleep 2
+
+echo ""
+echo "2. Checking if application is running..."
+if ps -p $PID >/dev/null; then
+ echo "[SUCCESS] Application started successfully (PID: $PID)"
+else
+ echo "[ERROR] Application failed to start"
+ exit 1
+fi
+
+echo ""
+echo "3. Checking configuration file..."
+if [ -f "config.xml" ]; then
+ echo "[INFO] Configuration file exists"
+ echo "Current configuration:"
+ cat config.xml
+else
+ echo "[WARNING] Configuration file not found"
+fi
+
+echo ""
+echo "4. Instructions for testing resizable feature:"
+echo " - Move mouse to bottom-right corner of the clock"
+echo " - Cursor should change to diagonal resize arrow"
+echo " - Click and drag to resize"
+echo " - Release mouse to save new size"
+echo " - Size will be saved to config.xml"
+echo ""
+echo "5. To stop the application:"
+echo " - Right-click on clock and select 'Quit'"
+echo " - Or run: kill $PID"
+echo ""
+echo "Test the resizable feature now!"
+echo "Press any key to continue..."
+read -n 1 -s
+
+echo ""
+echo "6. Checking final configuration..."
+if [ -f "config.xml" ]; then
+ echo "Final configuration:"
+ cat config.xml
+else
+ echo "Configuration file not found"
+fi
+
+echo ""
+echo "Test completed!"
diff --git a/window_helper.cpp b/window_helper.cpp
new file mode 100644
index 0000000..f447c21
--- /dev/null
+++ b/window_helper.cpp
@@ -0,0 +1,95 @@
+#include "window_helper.h"
+
+#ifdef Q_OS_MAC
+#include "window_helper_macos.h"
+#elif defined(Q_OS_WIN)
+#include "window_helper_windows.h"
+#elif defined(Q_OS_LINUX)
+#include "window_helper_linux.h"
+#endif
+
+void WindowHelper::setupAlwaysOnTopForFullscreen(QWidget *widget)
+{
+ if (!widget)
+ return;
+
+#ifdef Q_OS_MAC
+ setupMacOS(widget);
+#elif defined(Q_OS_WIN)
+ setupWindows(widget);
+#elif defined(Q_OS_LINUX)
+ setupLinux(widget);
+#endif
+}
+
+void WindowHelper::setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop)
+{
+ if (!widget)
+ return;
+
+ Qt::WindowFlags flags = widget->windowFlags();
+ if (alwaysOnTop)
+ {
+ flags |= Qt::WindowStaysOnTopHint;
+ }
+ else
+ {
+ flags &= ~Qt::WindowStaysOnTopHint;
+ }
+ widget->setWindowFlags(flags);
+ widget->show(); // Required to apply new flags
+}
+
+void WindowHelper::setWindowClickThrough(QWidget *widget, bool clickThrough)
+{
+ if (!widget)
+ return;
+
+#ifdef Q_OS_MAC
+ // macOS: Make window ignore mouse events
+ widget->setAttribute(Qt::WA_TransparentForMouseEvents, clickThrough);
+#elif defined(Q_OS_WIN)
+ // Windows: Will be implemented in Windows helper
+ widget->setAttribute(Qt::WA_TransparentForMouseEvents, clickThrough);
+#elif defined(Q_OS_LINUX)
+ // Linux: Will be implemented in Linux helper
+ widget->setAttribute(Qt::WA_TransparentForMouseEvents, clickThrough);
+#endif
+}
+
+void WindowHelper::setWindowTransparent(QWidget *widget, bool transparent)
+{
+ if (!widget)
+ return;
+
+ if (transparent)
+ {
+ widget->setAttribute(Qt::WA_TranslucentBackground);
+ widget->setWindowFlags(widget->windowFlags() | Qt::FramelessWindowHint);
+ }
+ else
+ {
+ widget->setAttribute(Qt::WA_TranslucentBackground, false);
+ }
+}
+
+#ifdef Q_OS_MAC
+void WindowHelper::setupMacOS(QWidget *widget)
+{
+ MacOSWindowHelper::setupAlwaysOnTopForFullscreen(widget);
+}
+#endif
+
+#ifdef Q_OS_WIN
+void WindowHelper::setupWindows(QWidget *widget)
+{
+ WindowHelperWindows::setupAlwaysOnTopForFullscreen(widget);
+}
+#endif
+
+#ifdef Q_OS_LINUX
+void WindowHelper::setupLinux(QWidget *widget)
+{
+ WindowHelperLinux::setupAlwaysOnTopForFullscreen(widget);
+}
+#endif
diff --git a/window_helper.h b/window_helper.h
new file mode 100644
index 0000000..7374bdb
--- /dev/null
+++ b/window_helper.h
@@ -0,0 +1,21 @@
+#ifndef WINDOW_HELPER_H
+#define WINDOW_HELPER_H
+
+#include
+
+class WindowHelper
+{
+public:
+ static void setupAlwaysOnTopForFullscreen(QWidget *widget);
+ static void setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop);
+ static void setWindowClickThrough(QWidget *widget, bool clickThrough);
+ static void setWindowTransparent(QWidget *widget, bool transparent);
+
+private:
+ // Platform-specific implementations
+ static void setupMacOS(QWidget *widget);
+ static void setupWindows(QWidget *widget);
+ static void setupLinux(QWidget *widget);
+};
+
+#endif // WINDOW_HELPER_H
diff --git a/window_helper_linux.cpp b/window_helper_linux.cpp
new file mode 100644
index 0000000..ed000ae
--- /dev/null
+++ b/window_helper_linux.cpp
@@ -0,0 +1,114 @@
+#include "window_helper_linux.h"
+
+#ifdef Q_OS_LINUX
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+void WindowHelperLinux::setupAlwaysOnTopForFullscreen(QWidget *widget)
+{
+ if (!widget)
+ return;
+
+ // Get X11 display from Qt platform interface
+ QNativeInterface::QX11Application *x11App = qApp->nativeInterface();
+ if (!x11App)
+ return;
+
+ Display *display = x11App->display();
+ if (!display)
+ return;
+
+ WId windowId = widget->winId();
+ if (!windowId)
+ return;
+
+ // Set window type to utility (similar to macOS panel)
+ Atom windowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
+ Atom windowTypeUtility = XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ XChangeProperty(display, windowId, windowType, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&windowTypeUtility, 1);
+
+ // Set window state to above
+ Atom windowState = XInternAtom(display, "_NET_WM_STATE", False);
+ Atom windowStateAbove = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);
+ XChangeProperty(display, windowId, windowState, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&windowStateAbove, 1);
+
+ // Set window state to skip taskbar
+ Atom windowStateSkipTaskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);
+ XChangeProperty(display, windowId, windowState, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&windowStateSkipTaskbar, 1);
+
+ // Set window state to skip pager
+ Atom windowStateSkipPager = XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False);
+ XChangeProperty(display, windowId, windowState, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&windowStateSkipPager, 1);
+
+ // Set window state to sticky (appear on all workspaces)
+ Atom windowStateSticky = XInternAtom(display, "_NET_WM_STATE_STICKY", False);
+ XChangeProperty(display, windowId, windowState, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&windowStateSticky, 1);
+}
+
+void WindowHelperLinux::setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop)
+{
+ if (!widget)
+ return;
+
+ Qt::WindowFlags flags = widget->windowFlags();
+ if (alwaysOnTop)
+ {
+ flags |= Qt::WindowStaysOnTopHint;
+ }
+ else
+ {
+ flags &= ~Qt::WindowStaysOnTopHint;
+ }
+ widget->setWindowFlags(flags);
+ widget->show(); // Required to apply new flags
+}
+
+void WindowHelperLinux::setWindowClickThrough(QWidget *widget, bool clickThrough)
+{
+ if (!widget)
+ return;
+
+ // Get X11 display from Qt platform interface
+ QNativeInterface::QX11Application *x11App = qApp->nativeInterface();
+ if (!x11App)
+ return;
+
+ Display *display = x11App->display();
+ if (!display)
+ return;
+
+ WId windowId = widget->winId();
+ if (!windowId)
+ return;
+
+ if (clickThrough)
+ {
+ // Set window input shape to empty region
+ XRectangle rect = {0, 0, 0, 0};
+ XFixesSetWindowShapeRegion(display, windowId, ShapeInput, 0, 0, None);
+ XFixesSetWindowShapeRegion(display, windowId, ShapeInput, 0, 0,
+ XFixesCreateRegion(display, &rect, 1));
+ }
+ else
+ {
+ // Reset window input shape
+ XFixesSetWindowShapeRegion(display, windowId, ShapeInput, 0, 0, None);
+ }
+}
+
+void WindowHelperLinux::setupLinux(QWidget *widget)
+{
+ setupAlwaysOnTopForFullscreen(widget);
+}
+
+#endif // Q_OS_LINUX
diff --git a/window_helper_linux.h b/window_helper_linux.h
new file mode 100644
index 0000000..cde334a
--- /dev/null
+++ b/window_helper_linux.h
@@ -0,0 +1,15 @@
+#ifndef WINDOW_HELPER_LINUX_H
+#define WINDOW_HELPER_LINUX_H
+
+#include
+
+class WindowHelperLinux
+{
+public:
+ static void setupAlwaysOnTopForFullscreen(QWidget *widget);
+ static void setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop);
+ static void setWindowClickThrough(QWidget *widget, bool clickThrough);
+ static void setupLinux(QWidget *widget);
+};
+
+#endif // WINDOW_HELPER_LINUX_H
diff --git a/window_helper_macos.h b/window_helper_macos.h
new file mode 100644
index 0000000..e724c75
--- /dev/null
+++ b/window_helper_macos.h
@@ -0,0 +1,16 @@
+#ifndef WINDOW_HELPER_MACOS_H
+#define WINDOW_HELPER_MACOS_H
+
+class QWidget;
+
+#ifdef __OBJC__
+@class NSWindow;
+#endif
+
+class MacOSWindowHelper
+{
+public:
+ static void setupAlwaysOnTopForFullscreen(QWidget *widget);
+};
+
+#endif // WINDOW_HELPER_MACOS_H
diff --git a/window_helper_macos.mm b/window_helper_macos.mm
new file mode 100644
index 0000000..e5be438
--- /dev/null
+++ b/window_helper_macos.mm
@@ -0,0 +1,36 @@
+#import "window_helper_macos.h"
+#import
+
+#ifdef Q_OS_MAC
+
+#import
+#import
+
+void MacOSWindowHelper::setupAlwaysOnTopForFullscreen(QWidget *widget)
+{
+ if (!widget) return;
+
+ // Get the native NSView and NSWindow
+ NSView *nativeView = (NSView *)widget->winId();
+ NSWindow *nativeWindow = [nativeView window];
+
+ if (nativeWindow) {
+ // Set window level to be above everything, including fullscreen apps
+ [nativeWindow setLevel:NSScreenSaverWindowLevel - 1];
+
+ // Allow window to appear on all spaces (including fullscreen spaces)
+ [nativeWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces |
+ NSWindowCollectionBehaviorFullScreenAuxiliary |
+ NSWindowCollectionBehaviorStationary];
+
+ // Make it a floating panel that won't steal focus
+ [nativeWindow setHidesOnDeactivate:NO];
+ // Note: setFloatingPanel is deprecated, but we keep it for compatibility
+ // The key is setting the proper window level and collection behavior
+
+ // Ensure it stays above dock and menu bar
+ [nativeWindow setLevel:CGWindowLevelForKey(kCGFloatingWindowLevelKey)];
+ }
+}
+
+#endif // Q_OS_MAC
diff --git a/window_helper_windows.cpp b/window_helper_windows.cpp
new file mode 100644
index 0000000..ddc723c
--- /dev/null
+++ b/window_helper_windows.cpp
@@ -0,0 +1,122 @@
+#include "window_helper_windows.h"
+
+#ifdef Q_OS_WIN
+
+#include
+#include
+#include
+#include
+
+// Ensure we have the proper Windows version for API functions
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 // Windows Vista and later
+#endif
+
+void WindowHelperWindows::setupAlwaysOnTopForFullscreen(QWidget *widget)
+{
+ if (!widget)
+ return;
+
+ HWND hwnd = getWindowHandle(widget);
+ if (!hwnd)
+ return;
+
+ // Set window as topmost
+ SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+
+ // Make window appear on all desktops (similar to macOS spaces)
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
+
+ // Set extended window style to stay on top in fullscreen mode
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_TOPMOST);
+
+ // Make window not appear in taskbar
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_NOACTIVATE);
+}
+
+void WindowHelperWindows::setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop)
+{
+ if (!widget)
+ return;
+
+ HWND hwnd = getWindowHandle(widget);
+ if (!hwnd)
+ return;
+
+ SetWindowPos(hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
+ 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+}
+
+void WindowHelperWindows::setWindowClickThrough(QWidget *widget, bool clickThrough)
+{
+ if (!widget)
+ return;
+
+ HWND hwnd = getWindowHandle(widget);
+ if (!hwnd)
+ return;
+
+ if (clickThrough)
+ {
+ // Make window transparent to mouse events
+ SetWindowLong(hwnd, GWL_EXSTYLE,
+ GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_TRANSPARENT);
+ }
+ else
+ {
+ // Remove transparent mouse event handling
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) & ~WS_EX_TRANSPARENT);
+ }
+}
+
+void WindowHelperWindows::setWindowExStyle(QWidget *widget, DWORD style, bool enable)
+{
+ if (!widget)
+ return;
+
+ HWND hwnd = getWindowHandle(widget);
+ if (!hwnd)
+ return;
+
+ DWORD currentStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+ if (enable)
+ {
+ currentStyle |= style;
+ }
+ else
+ {
+ currentStyle &= ~style;
+ }
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE, currentStyle);
+}
+
+HWND WindowHelperWindows::getWindowHandle(QWidget *widget)
+{
+ if (!widget)
+ return nullptr;
+
+ QWindow *window = widget->windowHandle();
+ if (!window)
+ {
+ // Try to get window handle from native parent
+ QWidget *nativeParent = widget->nativeParentWidget();
+ if (nativeParent)
+ {
+ window = nativeParent->windowHandle();
+ }
+ }
+
+ if (window)
+ {
+ return reinterpret_cast(window->winId());
+ }
+
+ return nullptr;
+}
+
+#endif // Q_OS_WIN
diff --git a/window_helper_windows.h b/window_helper_windows.h
new file mode 100644
index 0000000..7c37917
--- /dev/null
+++ b/window_helper_windows.h
@@ -0,0 +1,22 @@
+#ifndef WINDOW_HELPER_WINDOWS_H
+#define WINDOW_HELPER_WINDOWS_H
+
+#include
+
+#ifdef Q_OS_WIN
+#include
+#endif
+
+class WindowHelperWindows
+{
+public:
+ static void setupAlwaysOnTopForFullscreen(QWidget *widget);
+ static void setWindowAlwaysOnTop(QWidget *widget, bool alwaysOnTop);
+ static void setWindowClickThrough(QWidget *widget, bool clickThrough);
+
+private:
+ static void setWindowExStyle(QWidget *widget, DWORD style, bool enable);
+ static HWND getWindowHandle(QWidget *widget);
+};
+
+#endif // WINDOW_HELPER_WINDOWS_H