mirror of
https://github.com/ashaduri/gsmartcontrol.git
synced 2026-09-27 06:15:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ad46585b6 | ||
|
|
7346db784a | ||
|
|
9f62577161 | ||
|
|
a79907bd05 | ||
|
|
b574294a51 | ||
|
|
f1fd685441 | ||
|
|
bd30bbe3e5 |
@@ -1,128 +0,0 @@
|
||||
name: Build AppImage
|
||||
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: RelWithDebInfo
|
||||
|
||||
jobs:
|
||||
build-appimage:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Update Package Database
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get install libgtkmm-3.0-dev gettext wget file libfuse2
|
||||
|
||||
- name: Download linuxdeploy
|
||||
# Note: Using 'continuous' release for latest version. For reproducible builds,
|
||||
# pin to a specific release tag and verify SHA256 checksum.
|
||||
run: |
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
chmod +x linuxdeploy-x86_64.AppImage
|
||||
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin/linuxdeploy
|
||||
|
||||
- name: Download appimagetool
|
||||
# Note: Using 'continuous' release for latest version. For reproducible builds,
|
||||
# pin to a specific release tag and verify SHA256 checksum.
|
||||
run: |
|
||||
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
chmod +x appimagetool-x86_64.AppImage
|
||||
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
|
||||
|
||||
- name: Create Build Directory
|
||||
run: cmake -E make_directory build-appimage
|
||||
|
||||
- name: Configure CMake for AppImage
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-appimage
|
||||
env:
|
||||
CC: gcc-11
|
||||
CXX: g++-11
|
||||
run: >
|
||||
cmake $GITHUB_WORKSPACE
|
||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
-DCMAKE_INSTALL_PREFIX=/usr
|
||||
-DAPP_BUILD_APPIMAGE=ON
|
||||
-DCMAKE_TOOLCHAIN_FILE="$GITHUB_WORKSPACE/toolchains/linux-dev.cmake"
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-appimage
|
||||
run: cmake --build . --config $BUILD_TYPE -j$(nproc)
|
||||
|
||||
- name: Install to AppDir
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-appimage
|
||||
run: DESTDIR=${{github.workspace}}/AppDir cmake --install .
|
||||
|
||||
- name: Setup AppDir structure
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
run: |
|
||||
# Copy desktop file and icon to AppDir root
|
||||
cp AppDir/usr/share/applications/dev.shaduri.gsmartcontrol.desktop AppDir/
|
||||
cp AppDir/usr/share/icons/hicolor/256x256/apps/gsmartcontrol.png AppDir/
|
||||
|
||||
# Create AppRun script
|
||||
cat > AppDir/AppRun << 'EOF'
|
||||
#!/bin/bash
|
||||
SELF=$(readlink -f "$0")
|
||||
HERE=${SELF%/*}
|
||||
export LD_LIBRARY_PATH="$HERE/usr/lib:${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
export PATH="$HERE/usr/bin:${PATH:+:$PATH}"
|
||||
export GSETTINGS_SCHEMA_DIR="$HERE/usr/share/glib-2.0/schemas:${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
|
||||
export GDK_PIXBUF_MODULEDIR="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
export GDK_PIXBUF_MODULE_FILE="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
|
||||
exec "$HERE/usr/bin/gsmartcontrol" "$@"
|
||||
EOF
|
||||
chmod +x AppDir/AppRun
|
||||
|
||||
- name: Bundle dependencies with linuxdeploy
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
run: |
|
||||
linuxdeploy --appdir AppDir \
|
||||
--executable AppDir/usr/bin/gsmartcontrol \
|
||||
--desktop-file AppDir/dev.shaduri.gsmartcontrol.desktop \
|
||||
--icon-file AppDir/gsmartcontrol.png
|
||||
|
||||
- name: Update GDK pixbuf cache
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -d "AppDir/usr/lib/gdk-pixbuf-2.0" ]; then
|
||||
gdk-pixbuf-query-loaders > AppDir/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache || true
|
||||
fi
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(sed -n 's/.*CMAKE_PROJECT_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' version.txt)
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Create AppImage
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
run: |
|
||||
ARCH=x86_64 appimagetool AppDir GSmartControl-${{ steps.version.outputs.VERSION }}-x86_64.AppImage
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: GSmartControl AppImage
|
||||
path: ${{github.workspace}}/GSmartControl-*-x86_64.AppImage
|
||||
@@ -175,16 +175,6 @@ build_main_workflow:
|
||||
- x86_64
|
||||
- i586
|
||||
|
||||
# AppImage build (using Ubuntu 22.04 as base)
|
||||
- name: AppImage
|
||||
paths:
|
||||
- target_project: openSUSE:Tools
|
||||
target_repository: xUbuntu_22.04
|
||||
- target_project: Ubuntu:22.04
|
||||
target_repository: universe
|
||||
architectures:
|
||||
- x86_64
|
||||
|
||||
- set_flags:
|
||||
flags:
|
||||
- type: publish
|
||||
|
||||
@@ -39,7 +39,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
|
||||
# User-controlled build options
|
||||
option(APP_BUILD_EXAMPLES "Build examples" OFF)
|
||||
option(APP_BUILD_TESTS "Build tests" OFF)
|
||||
option(APP_BUILD_APPIMAGE "Build for AppImage (portable data file paths)" OFF)
|
||||
|
||||
|
||||
# Install documentation
|
||||
|
||||
+6
-24
@@ -6,17 +6,8 @@
|
||||
|
||||
|
||||
# Generate files
|
||||
if (APP_BUILD_APPIMAGE)
|
||||
set(APPIMAGE_DESKTOP_ID "dev.shaduri.gsmartcontrol")
|
||||
set(APPIMAGE_EXEC_LINE "Exec=\"${CMAKE_INSTALL_FULL_BINDIR}/gsmartcontrol\"")
|
||||
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
|
||||
configure_file("gsmartcontrol.in.desktop" "dev.shaduri.gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
|
||||
else()
|
||||
set(APPIMAGE_DESKTOP_ID "gsmartcontrol")
|
||||
set(APPIMAGE_EXEC_LINE "Exec=\"${CMAKE_INSTALL_FULL_BINDIR}/gsmartcontrol-root\"")
|
||||
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
|
||||
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
|
||||
endif()
|
||||
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
|
||||
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
|
||||
configure_file("gsmartcontrol-root.in.sh" "gsmartcontrol-root.sh" ESCAPE_QUOTES @ONLY)
|
||||
configure_file("org.gsmartcontrol.in.policy" "org.gsmartcontrol.policy" ESCAPE_QUOTES @ONLY)
|
||||
|
||||
@@ -51,13 +42,8 @@ endif()
|
||||
|
||||
# Desktop file
|
||||
if (NOT WIN32)
|
||||
if (APP_BUILD_APPIMAGE)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dev.shaduri.gsmartcontrol.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
|
||||
else()
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
|
||||
endif()
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.desktop"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
|
||||
|
||||
# Appdata file
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.appdata.xml"
|
||||
@@ -69,13 +55,9 @@ if (NOT WIN32)
|
||||
|
||||
# Man pages
|
||||
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
|
||||
if (NOT APP_BUILD_APPIMAGE)
|
||||
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" RENAME "gsmartcontrol-root.1")
|
||||
endif()
|
||||
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" RENAME "gsmartcontrol-root.1")
|
||||
|
||||
# Scripts (this goes to bin, not sbin, as it doesn't require root privileges before running)
|
||||
if (NOT APP_BUILD_APPIMAGE)
|
||||
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol-root.sh" TYPE BIN RENAME "gsmartcontrol-root")
|
||||
endif()
|
||||
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol-root.sh" TYPE BIN RENAME "gsmartcontrol-root")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>dev.shaduri.gsmartcontrol</id>
|
||||
<id>gsmartcontrol</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<name>GSmartControl</name>
|
||||
@@ -14,7 +14,7 @@
|
||||
on it.
|
||||
</p>
|
||||
</description>
|
||||
<launchable type="desktop-id">@APPIMAGE_DESKTOP_ID@.desktop</launchable>
|
||||
<launchable type="desktop-id">gsmartcontrol.desktop</launchable>
|
||||
<url type="homepage">https://gsmartcontrol.shaduri.dev</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
|
||||
@@ -63,5 +63,4 @@ Icon=gsmartcontrol
|
||||
#X-KDE-RootOnly=true
|
||||
|
||||
# Run with root permissions.
|
||||
# For AppImage builds, execute the binary directly (AppImage does not support embedded sudo scripts)
|
||||
@APPIMAGE_EXEC_LINE@
|
||||
Exec="@CMAKE_INSTALL_FULL_BINDIR@/gsmartcontrol-root"
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
# GSmartControl AppImage Build
|
||||
|
||||
This directory contains scripts and configurations for building GSmartControl as an AppImage.
|
||||
|
||||
## What is AppImage?
|
||||
|
||||
AppImage is a format for distributing portable software on Linux without requiring installation. AppImages are self-contained applications that can run on most Linux distributions.
|
||||
|
||||
## Building the AppImage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You need to have the following installed:
|
||||
- cmake (>= 3.14)
|
||||
- g++ or clang with C++20 support
|
||||
- GTK3 and gtkmm-3.0 development packages
|
||||
- smartmontools (for runtime)
|
||||
- linuxdeploy and appimagetool (the script will download them if not available)
|
||||
|
||||
On Ubuntu/Debian:
|
||||
```bash
|
||||
sudo apt-get install cmake g++ libgtkmm-3.0-dev gettext smartmontools
|
||||
```
|
||||
|
||||
On Fedora:
|
||||
```bash
|
||||
sudo dnf install cmake gcc-c++ gtkmm30-devel gettext smartmontools
|
||||
```
|
||||
|
||||
### Build Steps
|
||||
|
||||
From the repository root directory, run:
|
||||
|
||||
```bash
|
||||
./packaging/appimage/build-appimage.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Configure CMake with `-DAPP_BUILD_APPIMAGE=ON`
|
||||
2. Build the application
|
||||
3. Create an AppDir with the proper structure
|
||||
4. Bundle dependencies using linuxdeploy
|
||||
5. Create the final AppImage file
|
||||
|
||||
### Output
|
||||
|
||||
The script creates an AppImage file named `GSmartControl-<version>-<arch>.AppImage` in the repository root.
|
||||
|
||||
## Running the AppImage
|
||||
|
||||
**Important:** GSmartControl requires root privileges to access disk drives. You must run the AppImage with sudo:
|
||||
|
||||
```bash
|
||||
sudo ./GSmartControl-<version>-x86_64.AppImage
|
||||
```
|
||||
|
||||
### Why sudo is required?
|
||||
|
||||
- AppImage format does not support embedded privilege escalation scripts
|
||||
- The binary must be executed directly as root to access `/dev/sd*` and `/dev/nvme*` devices
|
||||
- This is different from traditional package installations which use the `gsmartcontrol-root` wrapper script
|
||||
|
||||
## AppImage-Specific Changes
|
||||
|
||||
When building with `-DAPP_BUILD_APPIMAGE=ON`, the following changes are applied:
|
||||
|
||||
1. **Data file paths**: Uses relative paths from binary location (`bin/../share/...`)
|
||||
2. **Desktop file**:
|
||||
- Uses rDNS format: `dev.shaduri.gsmartcontrol.desktop`
|
||||
- Executes binary directly without `gsmartcontrol-root` wrapper
|
||||
3. **Binary location**: Installed to `bin/` instead of `sbin/`
|
||||
4. **No wrapper script**: `gsmartcontrol-root` is not installed
|
||||
|
||||
## Compatibility
|
||||
|
||||
The AppImage should work on:
|
||||
- Fedora Atomic (Silverblue, Kinoite, etc.)
|
||||
- Most modern Linux distributions with GTK3 support
|
||||
- Systems with read-only root filesystems
|
||||
|
||||
Tested architectures:
|
||||
- x86_64 (Intel/AMD 64-bit)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Permission denied" when running AppImage
|
||||
Make the AppImage executable:
|
||||
```bash
|
||||
chmod +x GSmartControl-*.AppImage
|
||||
```
|
||||
|
||||
### "No drives detected"
|
||||
Make sure to run with sudo:
|
||||
```bash
|
||||
sudo ./GSmartControl-*.AppImage
|
||||
```
|
||||
|
||||
### GTK theme issues
|
||||
You may need to install GTK3 themes on your system. The AppImage bundles the necessary libraries but uses system themes when available.
|
||||
|
||||
## Notes for Developers
|
||||
|
||||
- The AppImage build uses the same source code as regular builds
|
||||
- The `BuildEnv::is_appimage_build()` function can be used to detect AppImage builds at runtime
|
||||
- Icon and UI file paths are resolved at runtime relative to the binary location
|
||||
- The AppImage includes all necessary GTK3 and gtkmm libraries
|
||||
@@ -1,147 +0,0 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# License: BSD Zero Clause License file
|
||||
# Copyright:
|
||||
# (C) 2025 Alexander Shaduri <ashaduri@gmail.com>
|
||||
###############################################################################
|
||||
|
||||
# Script to build GSmartControl AppImage
|
||||
# This script should be run from the repository root directory
|
||||
#
|
||||
# Requirements:
|
||||
# - cmake, wget, file
|
||||
# - libgtkmm-3.0-dev (or equivalent GTK3 development packages)
|
||||
# - libfuse2 (required to run linuxdeploy and appimagetool AppImages)
|
||||
# On Ubuntu/Debian: sudo apt-get install libfuse2
|
||||
# On Ubuntu 24.04+: sudo apt-get install libfuse2t64
|
||||
|
||||
set -e # Exit on error
|
||||
set -u # Exit on undefined variable
|
||||
|
||||
# Configuration
|
||||
APPDIR="${APPDIR:-AppDir}"
|
||||
BUILD_DIR="${BUILD_DIR:-build-appimage}"
|
||||
ARCH="${ARCH:-x86_64}"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}Building GSmartControl AppImage${NC}"
|
||||
echo "Architecture: $ARCH"
|
||||
echo "Build directory: $BUILD_DIR"
|
||||
echo "AppDir: $APPDIR"
|
||||
echo
|
||||
|
||||
# Check dependencies
|
||||
if ! command -v cmake &> /dev/null; then
|
||||
echo -e "${RED}Error: cmake not found. Please install cmake.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v linuxdeploy &> /dev/null; then
|
||||
echo -e "${YELLOW}Warning: linuxdeploy not found. Will attempt to download it.${NC}"
|
||||
# Note: Using 'continuous' release for latest version. For reproducible builds,
|
||||
# pin to a specific release tag and verify SHA256 checksum.
|
||||
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${ARCH}.AppImage"
|
||||
wget -N "$LINUXDEPLOY_URL" -O linuxdeploy
|
||||
chmod +x linuxdeploy
|
||||
LINUXDEPLOY="./linuxdeploy"
|
||||
else
|
||||
LINUXDEPLOY="linuxdeploy"
|
||||
fi
|
||||
|
||||
if ! command -v appimagetool &> /dev/null; then
|
||||
echo -e "${YELLOW}Warning: appimagetool not found. Will attempt to download it.${NC}"
|
||||
# Note: Using 'continuous' release for latest version. For reproducible builds,
|
||||
# pin to a specific release tag and verify SHA256 checksum.
|
||||
APPIMAGETOOL_URL="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage"
|
||||
wget -N "$APPIMAGETOOL_URL" -O appimagetool
|
||||
chmod +x appimagetool
|
||||
APPIMAGETOOL="./appimagetool"
|
||||
else
|
||||
APPIMAGETOOL="appimagetool"
|
||||
fi
|
||||
|
||||
# Clean previous build
|
||||
echo -e "${GREEN}Cleaning previous build...${NC}"
|
||||
rm -rf "$BUILD_DIR"
|
||||
rm -rf "$APPDIR"
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# Configure with CMake for AppImage build
|
||||
echo -e "${GREEN}Configuring CMake for AppImage build...${NC}"
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DAPP_BUILD_APPIMAGE=ON \
|
||||
-DCMAKE_TOOLCHAIN_FILE="../toolchains/linux-dev.cmake"
|
||||
|
||||
# Build
|
||||
echo -e "${GREEN}Building...${NC}"
|
||||
cmake --build . --config RelWithDebInfo -j$(nproc)
|
||||
|
||||
# Install to AppDir
|
||||
echo -e "${GREEN}Installing to AppDir...${NC}"
|
||||
DESTDIR="../$APPDIR" cmake --install .
|
||||
|
||||
cd ..
|
||||
|
||||
# Copy desktop file and icon to AppDir root (required by AppImage)
|
||||
echo -e "${GREEN}Setting up AppDir structure...${NC}"
|
||||
cp "$APPDIR/usr/share/applications/dev.shaduri.gsmartcontrol.desktop" "$APPDIR/"
|
||||
cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/gsmartcontrol.png" "$APPDIR/"
|
||||
|
||||
# Create AppRun script
|
||||
cat > "$APPDIR/AppRun" << 'EOF'
|
||||
#!/bin/bash
|
||||
# AppRun script for GSmartControl AppImage
|
||||
|
||||
SELF=$(readlink -f "$0")
|
||||
HERE=${SELF%/*}
|
||||
|
||||
# Export library paths
|
||||
export LD_LIBRARY_PATH="$HERE/usr/lib:${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
export PATH="$HERE/usr/bin:${PATH:+:$PATH}"
|
||||
|
||||
# Export GTK settings
|
||||
export GSETTINGS_SCHEMA_DIR="$HERE/usr/share/glib-2.0/schemas:${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
|
||||
export GDK_PIXBUF_MODULEDIR="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
export GDK_PIXBUF_MODULE_FILE="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
|
||||
|
||||
# Run the application
|
||||
exec "$HERE/usr/bin/gsmartcontrol" "$@"
|
||||
EOF
|
||||
chmod +x "$APPDIR/AppRun"
|
||||
|
||||
# Use linuxdeploy to bundle dependencies
|
||||
echo -e "${GREEN}Bundling dependencies with linuxdeploy...${NC}"
|
||||
$LINUXDEPLOY --appdir "$APPDIR" \
|
||||
--executable "$APPDIR/usr/bin/gsmartcontrol" \
|
||||
--desktop-file "$APPDIR/dev.shaduri.gsmartcontrol.desktop" \
|
||||
--icon-file "$APPDIR/gsmartcontrol.png"
|
||||
|
||||
# Update GDK pixbuf cache
|
||||
if [ -d "$APPDIR/usr/lib/gdk-pixbuf-2.0" ]; then
|
||||
echo -e "${GREEN}Updating GDK pixbuf cache...${NC}"
|
||||
gdk-pixbuf-query-loaders > "$APPDIR/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Get version from version.txt (extract quoted CMAKE_PROJECT_VERSION value)
|
||||
VERSION=$(sed -n 's/.*CMAKE_PROJECT_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' version.txt | head -n1)
|
||||
|
||||
# Create AppImage
|
||||
echo -e "${GREEN}Creating AppImage...${NC}"
|
||||
ARCH=$ARCH $APPIMAGETOOL "$APPDIR" "GSmartControl-${VERSION}-${ARCH}.AppImage"
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}AppImage created successfully!${NC}"
|
||||
echo -e "Output: ${GREEN}GSmartControl-${VERSION}-${ARCH}.AppImage${NC}"
|
||||
echo
|
||||
echo -e "${YELLOW}Note: The AppImage must be run with sudo to access disk drives.${NC}"
|
||||
echo -e "Example: ${GREEN}sudo ./GSmartControl-${VERSION}-${ARCH}.AppImage${NC}"
|
||||
@@ -11,6 +11,8 @@ Copyright:
|
||||
|
||||
#include "gui_utils.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -165,6 +167,59 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/// Storage for Windows fractional scaling percentage (0 if no fractional scaling)
|
||||
int g_windows_fractional_scaling_percent = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int app_get_windows_fractional_scaling_percent()
|
||||
{
|
||||
return g_windows_fractional_scaling_percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void app_set_windows_fractional_scaling_percent(int percent)
|
||||
{
|
||||
g_windows_fractional_scaling_percent = percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h)
|
||||
{
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
int size_w = config_size_w;
|
||||
int size_h = config_size_h;
|
||||
|
||||
// Apply fractional scaling adjustment on Windows if no custom size is configured
|
||||
// This compensates for GTK3's lack of fractional scaling support
|
||||
const int full_percent = g_windows_fractional_scaling_percent;
|
||||
if (full_percent > 0 && size_w == 0 && size_h == 0) {
|
||||
// Get the default size from glade and scale it
|
||||
int glade_w = 0, glade_h = 0;
|
||||
window->get_default_size(glade_w, glade_h);
|
||||
if (glade_w > 0 && glade_h > 0) {
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0;
|
||||
const int integer_ui_scale = full_percent / 100; // GTK3 uses floor (integer) scaling
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
size_w = static_cast<int>(std::lround(glade_w * correction));
|
||||
size_h = static_cast<int>(std::lround(glade_h * correction));
|
||||
}
|
||||
}
|
||||
|
||||
if (size_w > 0 && size_h > 0) {
|
||||
window->set_default_size(size_w, size_h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,23 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
|
||||
|
||||
|
||||
|
||||
/// Get the fractional scaling percentage detected on Windows (0 if not detected or integer scale).
|
||||
/// For example, at 150% scaling, this returns 150; at 125% scaling, this returns 125; at 250% scaling, this returns 250.
|
||||
/// Returns 0 for exact integer scales (100%, 200%, etc.).
|
||||
int app_get_windows_fractional_scaling_percent();
|
||||
|
||||
|
||||
/// Set the fractional scaling percentage detected on Windows.
|
||||
/// This should only be called once during application initialization.
|
||||
void app_set_windows_fractional_scaling_percent(int percent);
|
||||
|
||||
|
||||
/// Apply fractional scaling to default window size if fractional scaling is detected.
|
||||
/// This compensates for GTK3's lack of fractional scaling support on Windows.
|
||||
/// \param window The window to apply scaling to
|
||||
/// \param config_size_w Configured width (0 if using glade default)
|
||||
/// \param config_size_h Configured height (0 if using glade default)
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -82,12 +82,6 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "QNX")
|
||||
set(CONFIG_KERNEL_QNX TRUE)
|
||||
endif()
|
||||
|
||||
# AppImage build configuration
|
||||
if (APP_BUILD_APPIMAGE)
|
||||
set(CONFIG_APPIMAGE_BUILD TRUE)
|
||||
message(STATUS "AppImage build enabled - using portable data file paths")
|
||||
endif()
|
||||
|
||||
|
||||
# Additionally, these macros are defined by OS / compilers:
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ Copyright:
|
||||
#cmakedefine CONFIG_KERNEL_SOLARIS
|
||||
#cmakedefine CONFIG_KERNEL_DARWIN
|
||||
#cmakedefine CONFIG_KERNEL_QNX
|
||||
#cmakedefine CONFIG_APPIMAGE_BUILD
|
||||
|
||||
#if defined CONFIG_KERNEL_WINDOWS32 || defined CONFIG_KERNEL_WINDOWS64
|
||||
#define CONFIG_KERNEL_FAMILY_WINDOWS
|
||||
@@ -184,16 +183,6 @@ struct BuildEnv {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Check if this is an AppImage build (portable data file paths).
|
||||
static constexpr bool is_appimage_build()
|
||||
{
|
||||
#ifdef CONFIG_APPIMAGE_BUILD
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -62,12 +62,11 @@ target_link_libraries(gsmartcontrol
|
||||
|
||||
if (WIN32)
|
||||
install(TARGETS gsmartcontrol DESTINATION .)
|
||||
elseif (APP_BUILD_APPIMAGE)
|
||||
install(TARGETS gsmartcontrol DESTINATION "${CMAKE_INSTALL_BINDIR}/")
|
||||
else()
|
||||
install(TARGETS gsmartcontrol DESTINATION "${CMAKE_INSTALL_SBINDIR}/")
|
||||
endif()
|
||||
|
||||
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(tests)
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ Copyright:
|
||||
GscExecutorLogWindow::GscExecutorLogWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)
|
||||
: AppBuilderWidget<GscExecutorLogWindow, false>(gtkcobj, std::move(ui))
|
||||
{
|
||||
// Apply fractional scaling to default window size
|
||||
app_apply_fractional_scaling_to_default_size(this, 0, 0);
|
||||
|
||||
// Connect callbacks
|
||||
|
||||
Gtk::Button* window_close_button = nullptr;
|
||||
|
||||
@@ -35,6 +35,7 @@ Copyright:
|
||||
#include "gsc_info_window.h"
|
||||
#include "gsc_executor_error_dialog.h"
|
||||
#include "gsc_startup_settings.h"
|
||||
#include "gsc_init.h" // app_apply_fractional_scaling_to_default_size()
|
||||
|
||||
|
||||
|
||||
@@ -166,9 +167,7 @@ GscInfoWindow::GscInfoWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder>
|
||||
{
|
||||
const int def_size_w = rconfig::get_data<int>("gui/info_window/default_size_w");
|
||||
const int def_size_h = rconfig::get_data<int>("gui/info_window/default_size_h");
|
||||
if (def_size_w > 0 && def_size_h > 0) {
|
||||
set_default_size(def_size_w, def_size_h);
|
||||
}
|
||||
app_apply_fractional_scaling_to_default_size(this, def_size_w, def_size_h);
|
||||
}
|
||||
|
||||
// Create missing widgets
|
||||
|
||||
+10
-10
@@ -47,6 +47,7 @@
|
||||
|
||||
#include "applib/window_instance_manager.h"
|
||||
#include "applib/gsc_settings.h"
|
||||
#include "applib/gui_utils.h" // app_set_windows_fractional_scaling_percent()
|
||||
#include "gsc_main_window.h"
|
||||
#include "gsc_executor_log_window.h"
|
||||
#include "gsc_init.h"
|
||||
@@ -80,6 +81,7 @@ namespace {
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -475,13 +477,7 @@ bool app_init_and_loop(int& argc, char**& argv)
|
||||
hz::data_file_add_search_directory("icons", application_dir / "icons");
|
||||
hz::data_file_add_search_directory("ui", application_dir / "ui");
|
||||
hz::data_file_add_search_directory("doc", application_dir / "doc");
|
||||
} else if constexpr(BuildEnv::is_appimage_build()) {
|
||||
// AppImage: Use relative paths from binary location (bin/../share/...)
|
||||
hz::data_file_add_search_directory("icons", application_dir.parent_path() / "share" / BuildEnv::package_name() / "icons");
|
||||
hz::data_file_add_search_directory("ui", application_dir.parent_path() / "share" / BuildEnv::package_name() / "ui");
|
||||
hz::data_file_add_search_directory("doc", application_dir.parent_path() / "share" / "doc" / BuildEnv::package_name());
|
||||
} else {
|
||||
// Traditional Unix installation: Use absolute paths from BuildEnv
|
||||
hz::data_file_add_search_directory("icons", hz::fs_path_from_string(BuildEnv::package_pkgdata_dir()) / BuildEnv::package_name() / "icons"); // /usr/share/program_name/icons
|
||||
hz::data_file_add_search_directory("ui", hz::fs_path_from_string(BuildEnv::package_pkgdata_dir()) / BuildEnv::package_name() / "ui"); // /usr/share/program_name/ui
|
||||
hz::data_file_add_search_directory("doc", hz::fs_path_from_string(BuildEnv::package_doc_dir())); // /usr/share/doc/[packages/]gsmartcontrol
|
||||
@@ -525,10 +521,14 @@ bool app_init_and_loop(int& argc, char**& argv)
|
||||
if (h_ppi > 0) {
|
||||
const double scale = h_ppi / 96.0;
|
||||
debug_out_info("app", "Windows system DPI: " << h_ppi << ", scale: " << scale << "\n");
|
||||
const int fraction_percent = static_cast<int>(std::round(scale * 100)) % 100;
|
||||
if (fraction_percent != 0) { // fractional scaling
|
||||
// Increase the font size by fraction, but round down the size to match the Windows behavior (?)
|
||||
debug_out_dump("app", "Fractional scaling detected, increasing font size by " << fraction_percent << "%.\n");
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100; // 250 -> 200, 150 -> 100
|
||||
if (full_scale_percent != integer_scale_percent) { // fractional scaling detected
|
||||
// Store the full scale percent for use in window sizing
|
||||
app_set_windows_fractional_scaling_percent(full_scale_percent);
|
||||
// Increase the font size by the fractional amount
|
||||
const int fraction_percent = full_scale_percent - integer_scale_percent;
|
||||
debug_out_dump("app", "Fractional scaling detected (" << full_scale_percent << "%), increasing font size by " << fraction_percent << "%.\n");
|
||||
Gtk::Settings::get_default()->property_gtk_font_name()
|
||||
.set_value("Segoe UI " + hz::number_to_string_nolocale(static_cast<int>(9 * (1. + fraction_percent/100.))));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ Copyright:
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace Gtk {
|
||||
class Window;
|
||||
}
|
||||
|
||||
|
||||
/// Initialize the application and run the main loop
|
||||
bool app_init_and_loop(int& argc, char**& argv);
|
||||
|
||||
@@ -28,6 +33,20 @@ void app_quit();
|
||||
std::string app_get_debug_buffer_str();
|
||||
|
||||
|
||||
/// Get the fractional scaling percentage detected on Windows (0 if not detected or integer scale).
|
||||
/// For example, at 150% scaling, this returns 150; at 125% scaling, this returns 125; at 250% scaling, this returns 250.
|
||||
/// Returns 0 for exact integer scales (100%, 200%, etc.).
|
||||
int app_get_windows_fractional_scaling_percent();
|
||||
|
||||
|
||||
/// Apply fractional scaling to default window size if fractional scaling is detected.
|
||||
/// This compensates for GTK3's lack of fractional scaling support on Windows.
|
||||
/// \param window The window to apply scaling to
|
||||
/// \param config_size_w Configured width (0 if using glade default)
|
||||
/// \param config_size_h Configured height (0 if using glade default)
|
||||
void app_apply_fractional_scaling_to_default_size(Gtk::Window* window, int config_size_w, int config_size_h);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -60,9 +60,7 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder>
|
||||
{
|
||||
const int def_size_w = rconfig::get_data<int>("gui/main_window/default_size_w");
|
||||
const int def_size_h = rconfig::get_data<int>("gui/main_window/default_size_h");
|
||||
if (def_size_w > 0 && def_size_h > 0) {
|
||||
set_default_size(def_size_w, def_size_h);
|
||||
}
|
||||
app_apply_fractional_scaling_to_default_size(this, def_size_w, def_size_h);
|
||||
}
|
||||
|
||||
// show the window first, scan later
|
||||
|
||||
@@ -24,6 +24,7 @@ Copyright:
|
||||
|
||||
#include "applib/app_builder_widget.h"
|
||||
#include "applib/app_gtkmm_tools.h"
|
||||
#include "applib/gui_utils.h" // app_apply_fractional_scaling_to_default_size()
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +50,9 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
|
||||
GscTextWindow(typename Gtk::Window::BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)
|
||||
: AppBuilderWidget<GscTextWindow<InstanceSwitch>, InstanceSwitch::multi_instance>(gtkcobj, std::move(ui))
|
||||
{
|
||||
// Apply fractional scaling to default window size
|
||||
app_apply_fractional_scaling_to_default_size(this, 0, 0);
|
||||
|
||||
// Connect callbacks
|
||||
|
||||
Gtk::Button* save_as_button = nullptr;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
###############################################################################
|
||||
# License: BSD Zero Clause License file
|
||||
# Copyright:
|
||||
# (C) 2026 Alexander Shaduri <ashaduri@gmail.com>
|
||||
###############################################################################
|
||||
|
||||
if (NOT APP_BUILD_TESTS)
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
else()
|
||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)
|
||||
endif()
|
||||
|
||||
|
||||
# Use Object libraries to allow runtime test discovery
|
||||
add_library(gui_tests OBJECT)
|
||||
target_sources(gui_tests PRIVATE
|
||||
test_fractional_scaling.cpp
|
||||
)
|
||||
target_link_libraries(gui_tests PRIVATE
|
||||
Catch2
|
||||
)
|
||||
@@ -0,0 +1,216 @@
|
||||
/******************************************************************************
|
||||
License: BSD Zero Clause License
|
||||
Copyright:
|
||||
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
|
||||
******************************************************************************/
|
||||
/// \file
|
||||
/// \author Alexander Shaduri
|
||||
/// \ingroup gui_tests
|
||||
/// \weakgroup gui_tests
|
||||
/// @{
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
/// Test the fractional scaling calculation logic
|
||||
/// This tests the mathematical correctness of the scaling correction ratio
|
||||
TEST_CASE("FractionalScalingCalculation", "[gui][scaling]")
|
||||
{
|
||||
// Test the calculation logic that's used in app_apply_fractional_scaling_to_default_size()
|
||||
// correction = system_scale / integer_ui_scale
|
||||
|
||||
SECTION("125% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 125;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.25
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.25);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.25);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1000x750
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1000);
|
||||
REQUIRE(scaled_h == 750);
|
||||
}
|
||||
|
||||
SECTION("150% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 150;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.5
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.5);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.5);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1200x900
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1200);
|
||||
REQUIRE(scaled_h == 900);
|
||||
}
|
||||
|
||||
SECTION("175% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 175;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 1.75
|
||||
const int integer_ui_scale = full_percent / 100; // 1
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 1.75);
|
||||
REQUIRE(integer_ui_scale == 1);
|
||||
REQUIRE(correction == 1.75);
|
||||
|
||||
// Test window scaling: 800x600 base should become 1400x1050
|
||||
const int base_w = 800;
|
||||
const int base_h = 600;
|
||||
const int scaled_w = static_cast<int>(std::lround(base_w * correction));
|
||||
const int scaled_h = static_cast<int>(std::lround(base_h * correction));
|
||||
|
||||
REQUIRE(scaled_w == 1400);
|
||||
REQUIRE(scaled_h == 1050);
|
||||
}
|
||||
|
||||
SECTION("250% scaling (fractional) - Critical test case")
|
||||
{
|
||||
// This is the case that was broken before the fix
|
||||
const int full_percent = 250;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 2.5
|
||||
const int integer_ui_scale = full_percent / 100; // 2
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 2.5);
|
||||
REQUIRE(integer_ui_scale == 2);
|
||||
REQUIRE(correction == 1.25); // NOT 1.5 (the old broken behavior)
|
||||
|
||||
// Test window scaling: GTK already applies 2x, we need to apply 1.25x more
|
||||
// For a base of 800x600, GTK makes it 1600x1200, we should scale to 2000x1500
|
||||
const int gtk_scaled_w = 1600; // After GTK's 2x integer scaling
|
||||
const int gtk_scaled_h = 1200;
|
||||
const int final_w = static_cast<int>(std::lround(gtk_scaled_w * correction));
|
||||
const int final_h = static_cast<int>(std::lround(gtk_scaled_h * correction));
|
||||
|
||||
REQUIRE(final_w == 2000); // 2.5x total = 800 * 2.5
|
||||
REQUIRE(final_h == 1500); // 2.5x total = 600 * 2.5
|
||||
}
|
||||
|
||||
SECTION("225% scaling (fractional)")
|
||||
{
|
||||
const int full_percent = 225;
|
||||
const double system_scale = static_cast<double>(full_percent) / 100.0; // 2.25
|
||||
const int integer_ui_scale = full_percent / 100; // 2
|
||||
const double correction = system_scale / static_cast<double>(integer_ui_scale);
|
||||
|
||||
REQUIRE(system_scale == 2.25);
|
||||
REQUIRE(integer_ui_scale == 2);
|
||||
REQUIRE(correction == 1.125);
|
||||
|
||||
// Test window scaling: 800x600 at 2x GTK = 1600x1200, with correction = 1800x1350
|
||||
const int gtk_scaled_w = 1600;
|
||||
const int gtk_scaled_h = 1200;
|
||||
const int final_w = static_cast<int>(std::lround(gtk_scaled_w * correction));
|
||||
const int final_h = static_cast<int>(std::lround(gtk_scaled_h * correction));
|
||||
|
||||
REQUIRE(final_w == 1800);
|
||||
REQUIRE(final_h == 1350);
|
||||
}
|
||||
|
||||
SECTION("100% scaling (integer, no fractional)")
|
||||
{
|
||||
// Should not trigger fractional scaling at all (full_percent would be 0)
|
||||
const int full_percent = 100;
|
||||
const int integer_scale_percent = (full_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_percent == integer_scale_percent); // No fractional component
|
||||
}
|
||||
|
||||
SECTION("200% scaling (integer, no fractional)")
|
||||
{
|
||||
// Should not trigger fractional scaling at all (full_percent would be 0)
|
||||
const int full_percent = 200;
|
||||
const int integer_scale_percent = (full_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_percent == integer_scale_percent); // No fractional component
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Test the DPI detection logic
|
||||
TEST_CASE("DPIDetectionLogic", "[gui][scaling][dpi]")
|
||||
{
|
||||
SECTION("Detect 125% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 120; // 120 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 125);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("Detect 150% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 144; // 144 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 150);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("Detect 250% scaling from DPI")
|
||||
{
|
||||
const int h_ppi = 240; // 240 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 250);
|
||||
REQUIRE(integer_scale_percent == 200);
|
||||
REQUIRE(full_scale_percent != integer_scale_percent); // Fractional scaling detected
|
||||
}
|
||||
|
||||
SECTION("No fractional scaling at 100% (96 DPI)")
|
||||
{
|
||||
const int h_ppi = 96; // 96 DPI (standard)
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 100);
|
||||
REQUIRE(integer_scale_percent == 100);
|
||||
REQUIRE(full_scale_percent == integer_scale_percent); // No fractional scaling
|
||||
}
|
||||
|
||||
SECTION("No fractional scaling at 200% (192 DPI)")
|
||||
{
|
||||
const int h_ppi = 192; // 192 DPI
|
||||
const double scale = static_cast<double>(h_ppi) / 96.0;
|
||||
const int full_scale_percent = static_cast<int>(std::lround(scale * 100.0));
|
||||
const int integer_scale_percent = (full_scale_percent / 100) * 100;
|
||||
|
||||
REQUIRE(full_scale_percent == 200);
|
||||
REQUIRE(integer_scale_percent == 200);
|
||||
REQUIRE(full_scale_percent == integer_scale_percent); // No fractional scaling
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @}
|
||||
@@ -22,6 +22,7 @@ target_link_libraries(test_all PRIVATE
|
||||
libdebug
|
||||
applib_tests
|
||||
hz_tests
|
||||
gui_tests
|
||||
Catch2
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user