Compare commits

...
46 Commits
Author SHA1 Message Date
Alexander Shaduri 871cd51e8a Merge pull request #121 from OldMan2525/main
Use bundled icons when theme icons are too small
2026-07-08 20:57:37 +02:00
OldMan2525 fa31a696a7 Use bundled icons when theme icons are too small
Some icon themes may return a lower-resolution icon than requested.
For example, under XQuartz on macOS, Gtk::IconTheme::load_icon()
returns a 16x16 drive-harddisk icon even when a 64x64 icon is
requested.

Because the theme lookup succeeds, the bundled higher-resolution
icons are never used, resulting in tiny drive icons in the main
window.

Fall back to the bundled icon when the theme-provided icon is
smaller than the requested size.
2026-07-07 02:13:11 -07:00
Alexander Shaduri cab21bd6dd Add scam alert and download instructions to index.md
Added a scam alert regarding a phishing site and provided guidance on downloading GSmartControl from official sources.
2026-05-31 11:55:43 +02:00
Alexander Shaduri 19c50fdd72 Add scam alert and official download links to README
Added a scam alert about a phishing site and provided official download links.
2026-05-31 11:54:47 +02:00
Alexander Shaduri 57a16f6971 Fix compilation. 2026-03-08 19:08:51 +01:00
Alexander Shaduri 79f1c7d5b0 Merge pull request #106 from ashaduri/claude/fix-red-text-legibility
Red text is barely legible when using a dark theme
2026-03-08 19:05:19 +01:00
Alexander Shaduri a7f0cb78cd Merge remote-tracking branch 'origin/claude/fix-red-text-legibility' into claude/fix-red-text-legibility
# Conflicts:
#	src/applib/warning_colors.h
#	src/gui/gsc_info_window.cpp
2026-03-08 19:04:00 +01:00
Alexander Shaduri 3642afc46f Use readable text highlight colors in dark mode. 2026-03-08 18:52:27 +01:00
Alexander ShaduriandCopilot e4c9afeed0 Update src/applib/warning_colors.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
Alexander ShaduriandCopilot 06a0a75349 Update src/gui/gsc_info_window.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri c322e96741 Revert to umbrella gtkmm.h include per maintainer request
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri a6e2dd0cc6 Add GTK init guard and optimize theme detection
- Guard theme detection with Gtk::Main::level() check to avoid crashes in non-GUI executables
- Use narrow headers (gtkmm/settings.h, gtkmm/main.h) instead of umbrella gtkmm.h
- Return early from app_property_get_label_highlight_color() for WarningLevel::None

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri 6de9bb897d Implement theme-aware red text colors for dark theme legibility
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot] ef30450c5f Initial plan 2026-03-08 18:52:27 +01:00
Claude 083936e99b Add Windows dark mode detection and automatic GTK theme switching (#111) 2026-03-08 18:31:58 +01:00
Alexander ShaduriandCopilot 68032c4f3c Update src/applib/warning_colors.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-07 17:39:35 +01:00
Alexander ShaduriandCopilot 7e626460c0 Update src/gui/gsc_info_window.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-07 16:15:24 +01:00
anthropic-code-agent[bot]andashaduri 258d06b755 Revert to umbrella gtkmm.h include per maintainer request
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 19:08:06 +00:00
anthropic-code-agent[bot]andashaduri 525fcfe3af Add GTK init guard and optimize theme detection
- Guard theme detection with Gtk::Main::level() check to avoid crashes in non-GUI executables
- Use narrow headers (gtkmm/settings.h, gtkmm/main.h) instead of umbrella gtkmm.h
- Return early from app_property_get_label_highlight_color() for WarningLevel::None

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 14:36:54 +00:00
anthropic-code-agent[bot]andashaduri f692e2879c Implement theme-aware red text colors for dark theme legibility
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-04 21:56:40 +00:00
anthropic-code-agent[bot] d110c7abbc Initial plan 2026-03-04 21:50:41 +00:00
Alexander Shaduri 5343ba9dae Cleanups 2026-03-04 22:48:21 +01:00
Alexander Shaduri 3d1e962107 Agentic: Tweak instructions. 2026-03-04 22:37:05 +01:00
Alexander Shaduri c14d7a7791 Windows: Manually install nsis since it's no longer available by default in CI. 2026-03-04 22:33:57 +01:00
Alexander Shaduri 4f3745f8ec Add copilot helper files. 2026-03-04 22:06:57 +01:00
Alexander Shaduri dadfd60ed3 Fix gcc shadow warning. 2025-11-25 19:29:46 +01:00
copilot-swe-agent[bot] 6c8fea7309 Add new natural sort order tests. 2025-11-25 19:29:28 +01:00
copilot-swe-agent[bot] 9d9e3b1a4d Update .gitignore to exclude CodeQL build directory 2025-11-25 13:00:03 +01:00
copilot-swe-agent[bot]andashaduri 54425a306a Implement natural sort order for drive names
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2025-11-25 12:59:41 +01:00
Alexander Shaduri be601f514d Upgrade CodeQL actions to version 4
Updated CodeQL actions to version 4 in the workflow configuration.
2025-11-18 19:44:28 +01:00
Alexander Shaduri a7c7c8190c Update CodeQL SARIF upload action to v4 2025-11-18 19:43:57 +01:00
Alexander Shaduri 44948de7d0 Fixed typos and updated visible copyright years. 2025-06-11 09:43:36 +02:00
Alexander Shaduri e2efde2d1a Updated nlohmann json to version 3.12.0. 2025-05-16 17:48:32 +04:00
Alexander Shaduri 6630116ba1 Properly implemented persistence of manually added devices (#87). Added --forget-devices option to clear the list if any of the devices causes trouble. 2025-04-28 18:51:42 +04:00
Alexander Shaduri 1f7c2b6a92 Manually added devices can be persistent now. 2025-04-28 11:19:44 +00:00
Alexander Shaduri 10ad6b74eb Merge remote-tracking branch 'origin/main' 2025-04-25 12:22:38 +04:00
Alexander Shaduri f62de567df MSYS no longer supports 32-bit Windows, so disable it in CI. 2025-04-25 12:12:52 +04:00
Alexander Shaduri d96dc79943 Merge pull request #86 from checktext00/patch-1
Update 36535 to 65535 hours in selftest log tooltip.
2025-04-17 11:32:32 +04:00
Alexander Shaduri e35c301605 Update gsc_info_window.cpp
Looking at the smartmontools code, it should be 65535 I believe.
2025-04-17 11:31:13 +04:00
checktext00 7af8dba9f3 update 36535 to 65536 hours 2025-04-16 11:58:09 +00:00
Alexander Shaduri caa80a30d1 Merge pull request #83 from stanislav-brabec/main
Typo fix in Czech desktop translation.
2025-03-12 20:46:01 +04:00
Stanislav Brabec db4928e78f Typo fix in Czech desktop translation. 2025-03-12 16:43:39 +01:00
Alexander Shaduri af96fa2cda Merge pull request #82 from stanislav-brabec/main
Add desktop translations
2025-03-11 11:55:47 +04:00
Stanislav Brabec c08ef9070b Add desktop translations
Add desktop translations imported from the openSUSE
desktop-file-translations project.
https://l10n.opensuse.org/projects/desktop-file-translations/
2025-03-10 22:10:45 +01:00
Alexander Shaduri 6fe5b43bc1 Document Vista. 2025-03-02 19:13:32 +04:00
Alexander Shaduri b3056aedd0 Update download links for 2.0.2. 2025-02-24 16:50:08 +04:00
45 changed files with 9665 additions and 8984 deletions
+124
View File
@@ -0,0 +1,124 @@
# GSmartControl – Copilot Instructions
GSmartControl is a C++20 GTK3/Gtkmm GUI for inspecting hard-drive and SSD health via SMART data
(wrapping `smartctl` from smartmontools). It supports Linux, Windows (MSYS2/MinGW), macOS, and BSDs.
## Build
```bash
mkdir build && cd build
# Standard Linux build (uses dev toolchain with warnings enabled)
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_TOOLCHAIN_FILE=../toolchains/linux-dev.cmake
# Windows (MSYS2/MinGW64)
cmake .. -G "MSYS Makefiles" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_TOOLCHAIN_FILE=../toolchains/win64-mingw-msys2.cmake
# Build on MSYS2/MinGW64 (from build dir)
cmake --build . --target all -j 18
```
The `configure-dev` script is a convenience wrapper: `./configure-dev -t <toolchain> -c <compiler> -b <build_type>`.
Key CMake options:
- `-DAPP_BUILD_TESTS=ON` – enable test targets
- `-DAPP_BUILD_EXAMPLES=ON` – enable example targets
- `-DAPP_COMPILER_ENABLE_WARNINGS=ON` – strict compiler warnings (set automatically by `linux-dev.cmake`)
## Tests
Tests use **Catch2** (vendored in `dependencies/catch2/`). They are only built when `-DAPP_BUILD_TESTS=ON` is passed.
```bash
# Build and run all tests
cmake .. -DAPP_BUILD_TESTS=ON
cmake --build .
ctest -C RelWithDebInfo --rerun-failed --output-on-failure
# Run a single test executable directly (from build dir)
./test_all "[smartctl_parser]" # Catch2 tag filter
./test_all "test name substring" # substring match
```
Test sources live in `src/applib/tests/` and `src/hz/tests/`; they are linked into `src/test_all/`.
## Linting
Clang-tidy config is at `.clang-tidy` in the repo root. Run it via CMake or directly:
```bash
clang-tidy src/applib/storage_device.cpp -- -std=c++20 $(pkg-config --cflags gtkmm-3.0)
```
## Architecture
The codebase is split into libraries and one GUI executable:
| Component | Location | Purpose |
|---|---|---|
| `applib` | `src/applib/` | Core logic: SMART parsing, device detection, command execution |
| `hz` | `src/hz/` | General-purpose C++ utilities (strings, filesystem, error types) |
| `rconfig` | `src/rconfig/` | Runtime config load/save with auto-save support |
| `libdebug` | `src/libdebug/` | Debug logging with named channels |
| `gui` | `src/gui/` | GTK/Gtkmm UI (windows, dialogs, Glade `.ui` files) |
### Parsing pipeline
`smartctl` output is parsed through a strategy pattern:
- JSON parsers (`smartctl_json_ata_parser`, `smartctl_json_nvme_parser`, `smartctl_json_basic_parser`) target smartctl ≥ 7.3.
- Text parsers (`smartctl_text_ata_parser`, `smartctl_text_basic_parser`) handle legacy output.
- Parsed results are stored as `StorageProperty` objects in a `StoragePropertyRepository` on `StorageDevice`.
### Device detection
`StorageDetector` is an abstract interface; platform implementations are:
- `storage_detector_linux.cpp` – scans `/dev/sd*`, `/dev/nvme*`, etc.
- `storage_detector_win32.cpp` – WMI/registry enumeration
- `storage_detector_other.cpp` – generic `/dev` scanning (BSD/macOS)
### Async execution
`AsyncCommandExecutor` wraps `CommandExecutor` (which shells out to `smartctl`) in a background thread.
Completion is signalled via **libsigc++** signals back to the GTK main loop. Never call GTK APIs from the worker thread;
queue them through signals or `Glib::signal_idle()`.
### Error handling
Use `hz::ExpectedValue<T, E>` (wrapping `tl::expected`) for recoverable errors instead of exceptions.
Enum-based error types (`StorageDeviceError`, `SmartctlParserError`) are preferred over string errors.
## Key Conventions
**Naming:**
- Classes: `PascalCase` (e.g., `StorageDevice`, `SmartctlParser`)
- Methods and members: `snake_case`; private/member fields have a trailing `_` (e.g., `device_list_`)
- GUI classes in `src/gui/` are prefixed `Gsc` (e.g., `GscMainWindow`)
- Filenames: `snake_case.cpp` / `snake_case.h`
**Headers:**
- Use `#ifndef FILENAME_H` / `#define FILENAME_H` guards (not `#pragma once`)
- Include order: standard library → third-party (GTK/Gtkmm) → project headers
- All project headers are included relative to `src/` (the include root)
**Modern C++20/23 patterns used throughout:**
- `std::optional<T>` for nullable values
- `hz::ExpectedValue<T, E>` for error-returning functions
- `std::string_view` for non-owning string parameters
- `fmt::format()` (vendored `fmt` library) for string formatting
- Smart pointers exclusively; no raw owning pointers
**Vendored dependencies** are in `dependencies/` and added via `add_subdirectory`.
Do not modify them; prefer upgrading the whole vendored copy.
**Translations:** UI strings are wrapped with `_()` (gettext). `.po` files live in `po/`.
Translations are not yet supported.
**Platform guards:** Use `#ifdef CONFIG_KERNEL_FAMILY_WINDOWS`,
`#ifdef CONFIG_KERNEL_LINUX`, etc.
## Agentic Development
Place all plans and temporary files in `agent_workspace/`. This directory is ignored by Git.
+3 -1
View File
@@ -122,7 +122,8 @@ jobs:
matrix:
include: [
{ msystem: MINGW64, arch: x86_64, platform: win64, urlpath: mingw64 },
{ msystem: MINGW32, arch: i686, platform: win32, urlpath: mingw32 }
# MSYS2 no longer supports for 32-bit Windows.
# { msystem: MINGW32, arch: i686, platform: win32, urlpath: mingw32 }
]
steps:
@@ -140,6 +141,7 @@ jobs:
mingw-w64-${{ matrix.arch }}-cmake
mingw-w64-${{ matrix.arch }}-gcc
mingw-w64-${{ matrix.arch }}-fontconfig
mingw-w64-${{ matrix.arch }}-nsis
make
p7zip
+1 -1
View File
@@ -56,6 +56,6 @@ jobs:
# Upload the SARIF file generated in the previous step
- name: Upload SARIF results file
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
+3 -3
View File
@@ -54,7 +54,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -68,7 +68,7 @@ jobs:
env:
CC: gcc-13
CXX: g++-13
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -82,4 +82,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
+5
View File
@@ -4,6 +4,8 @@
/win32*
/win64*
/cmake-build-*
/build*
/_codeql_build_dir
# Non-project files
/TODO
@@ -27,6 +29,9 @@ Thumbs.db
/.idea
/CMakeLists.txt.user
# Agents
/.codebuddy
# Translations
/po/boldquot.sed
/po/en@boldquot.header
+8 -2
View File
@@ -12,6 +12,12 @@
---
**SCAM ALERT:** gsmartcontrol[.]com is a phishing site! Please don't download anything from there!
Please always download GSmartControl from its official website [gsmartcontrol.shaduri.dev](https://gsmartcontrol.shaduri.dev) or the official GitHub repository [github.com/ashaduri/gsmartcontrol](https://github.com/ashaduri/gsmartcontrol).
---
[GSmartControl](https://gsmartcontrol.shaduri.dev)
is a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)
package), which is a tool for
@@ -59,7 +65,7 @@ for more information.
## Copyright and Licensing
GSmartControl is Copyright (C) 2008 - 2024 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.
GSmartControl is Copyright (C) 2008 - 2025 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.
GSmartControl is licensed under the terms of
[GNU General Public License Version 3](https://www.gnu.org/licenses/gpl-3.0.en.html).
@@ -73,4 +79,4 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.
This product includes icons from Oxygen Icons copyright [KDE](https://kde.org)
and licenced under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.
and licensed under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.
+1
View File
@@ -0,0 +1 @@
*
+31
View File
@@ -13,13 +13,44 @@ Type=Application
Categories=System;Monitor;
Name=GSmartControl
Name[ca]=GSControlIntel
Name[cs]=GSmartControl
Name[es]=GSmartControl
Name[id]=GSmartControl
Name[it]=GSmartControl
Name[ja]=GSmartControl
Name[pt_BR]=GSmartControl
Name[ru]=GSmartControl
Name[sk]=GSmartControl
Name[sv]=GSmartControl
Name[zh_CN]=GSmartControl
# short description, usually shown in parentheses after Name
GenericName=Hard Disk and SSD Health Inspection
GenericName[ca]=Inspecció de salut del disc dur i de l'SSD
GenericName[cs]=Prohlídka zdraví pevných a SSD disků
GenericName[es]=Inspección de la salud de discos duros y SSD
GenericName[fr]=Inspecteur de santé de disque dur et de SSD
GenericName[id]=Pemeriksaan Kesehatan Cakram Keras dan SSD
GenericName[it]=Ispezione della salute del disco rigido e dell'SSD
GenericName[ja]=ハードディスクと SSD の健全状態の調査
GenericName[pt_BR]=Inspeção da integridade de disco rígido e SSD
GenericName[ru]=Проверка состояния жёстких и твердотельных дисков
GenericName[sk]=Zdravotná inšpekcia pevného disku a SSD
GenericName[zh_CN]=机械及固态硬盘健康检测
# tooltip
Comment=Monitor and control SMART data on hard disk and solid-state drives
Comment[ca]=Monitoreu i controleu les dades SMART dels discs durs i dels discs sòlids.
Comment[cs]=Sleduje a nastavuje SMART data na pevném disku a na polovodičových discích
Comment[es]=Monitorice y controle datos de SMART sobre discos duros y unidades de estado sólido
Comment[fr]=Surveille et contrôle les données SMART des disques durs et des SSD
Comment[id]=Pantau dan kendalikan data cerdas pada cakram keras dan SSD
Comment[it]=Monitora e controlla i dati SMART sui dischi rigidi e a stato solido
Comment[ja]=ハードディスクや SSD 内の SMART データの監視と制御
Comment[pt_BR]=Monitorar e controlar dados do SMART em discos rígidos e unidades de estado sólido
Comment[ru]=Мониторинг и контроль данных SMART на жёстких и твердотельных дисках
Comment[sk]=Monitorovanie a kontrola údajov SMART na pevných diskoch a diskoch SSD
Comment[zh_CN]=监测及控制机械硬盘和固态硬盘上的SMART数据
# If it's a name only, it looks for "name.[png|xpm]" file in
# $XDG_DATA_DIRS/icons.
+3
View File
@@ -58,6 +58,9 @@ can specify this option multiple times. Example:\fR
\-\-add\-device /dev/sda \-\-add\-device /dev/twa0::3ware,2 \-\-add\-device '/dev/sdb::::-T permissive'
.fi
.TP
\fB\-\-forget\-devices\fP
Forget all previously manually added devices
.TP
\fB\-\-display\fP=\fIDISPLAY\fP
X display to use
.TP
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,3 +1,3 @@
JSON for Modern C++
version 3.9.1
version 3.12.0
https://github.com/nlohmann/json
+9 -9
View File
@@ -26,19 +26,19 @@ to install these packages.
### Windows
#### Windows Vista or Later
#### Windows 7 or Later
- 64-bit installer **(use this if unsure)**: [gsmartcontrol-2.0.1-win64.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.1/gsmartcontrol-2.0.1-win64.exe).
- 64-bit installer **(use this if unsure)**: [gsmartcontrol-2.0.2-win64.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win64.exe).
- 64-bit zip (portable): [gsmartcontrol-2.0.1.win64.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.1/gsmartcontrol-2.0.1-win64.zip).
- 64-bit zip (portable): [gsmartcontrol-2.0.2.win64.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win64.zip).
- 32-bit installer: [gsmartcontrol-2.0.1-win32.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.1/gsmartcontrol-2.0.1-win32.exe).
- 32-bit installer: [gsmartcontrol-2.0.2-win32.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win32.exe).
- 32-bit zip (portable): [gsmartcontrol-2.0.1-win32.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.1/gsmartcontrol-2.0.1-win32.zip).
- 32-bit zip (portable): [gsmartcontrol-2.0.2-win32.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win32.zip).
#### Outdated: Windows XP, 2000 and 2003
#### Outdated: Windows XP, Vista, 2000, 2003
The last version of GSmartControl that supports Windows XP, 2000 and 2003
The last version of GSmartControl that supports Windows XP, Vista, 2000, and 2003
is **0.9.0**:
- Outdated 32-bit installer: [gsmartcontrol-0.9.0.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v0.9.0/gsmartcontrol-0.9.0.exe).
- Outdated 32-bit zip (portable): [gsmartcontrol-0.9.0-win32.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v0.9.0/gsmartcontrol-0.9.0-win32.zip).
@@ -69,8 +69,8 @@ which includes GSmartControl.
## Source Code
The latest source package:
[gsmartcontrol-2.0.1.tar.gz](https://github.com/ashaduri/gsmartcontrol/archive/refs/tags/v2.0.1.tar.gz) \
SHA1 sum: 5bdc1cdd9d47c31ffa2ff3162ca7291d8ae953b4
[gsmartcontrol-2.0.2.tar.gz](https://github.com/ashaduri/gsmartcontrol/archive/refs/tags/v2.0.2.tar.gz) \
SHA1 sum: 3976f591d17c2007587b9bda27c33d0362721db3
If you're interested in development, you can check the
[GitHub Project](https://github.com/ashaduri/gsmartcontrol) page.
+8 -2
View File
@@ -13,6 +13,12 @@ nav_order: 1
---
**SCAM ALERT:** gsmartcontrol[.]com is a phishing site! Please don't download anything from there!
Please always download GSmartControl from its official website [gsmartcontrol.shaduri.dev](https://gsmartcontrol.shaduri.dev) or the official GitHub repository [github.com/ashaduri/gsmartcontrol](https://github.com/ashaduri/gsmartcontrol).
---
[GSmartControl](https://gsmartcontrol.shaduri.dev)
is a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)
package), which is a tool for
@@ -60,7 +66,7 @@ for more information.
## Copyright and Licensing
GSmartControl is Copyright (C) 2008 - 2024 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.
GSmartControl is Copyright (C) 2008 - 2025 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.
GSmartControl is licensed under the terms of
[GNU General Public License Version 3](https://www.gnu.org/licenses/gpl-3.0.en.html).
@@ -74,4 +80,4 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.
This product includes icons from Oxygen Icons copyright [KDE](https://kde.org)
and licenced under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.
and licensed under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.
+2
View File
@@ -43,6 +43,8 @@ Example:
`--add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device
'/dev/sdb::::-T permissive'`.
`--forget-devices` - Forget all previously manually added devices.
`-v`, `--verbose` - Enable verbose logging; same as `--verbosity-level 5`.
`-q`, `--quiet` - Disable logging; same as `--verbosity-level 0`.
+1 -1
View File
@@ -18,7 +18,7 @@ This program is free software: you can redistribute it and/or modify it under th
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.
This product includes icons from Oxygen Icons copyright KDE (https://kde.org)
and licenced under the GNU LGPL version 3 or later.
and licensed under the GNU LGPL version 3 or later.
You should have received copies of these licenses along with this program.
If not, see <http://www.gnu.org/licenses/>.
+1
View File
@@ -72,6 +72,7 @@ target_sources(applib PRIVATE
storage_property_repository.cpp
storage_property_repository.h
storage_settings.h
warning_colors.cpp
warning_colors.h
warning_level.h
window_instance_manager.h
+1
View File
@@ -48,6 +48,7 @@ inline void init_default_settings()
rconfig::set_default_data("system/smartctl_options", ""); // default options on ALL commands
rconfig::set_default_data("system/smartctl_device_options", ""); // dev1:val1;dev2:val2;... format, each bin2ascii-encoded.
rconfig::set_default_data("system/startup_manual_devices", ""); // Auto-add devices on startup
rconfig::set_default_data("system/linux_udev_byid_path", "/dev/disk/by-id"); // linux hard disk device links here
rconfig::set_default_data("system/linux_proc_partitions_path", "/proc/partitions"); // file in linux /proc/partitions format
+23
View File
@@ -165,6 +165,29 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
bool gui_is_dark_theme_active()
{
// Try to get the GTK settings to check for dark theme preference.
// If GTK is not available or not initialized, get_default() will return null.
const Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();
if (settings) {
// Check if the application prefers dark theme
if (settings->property_gtk_application_prefer_dark_theme().get_value()) {
return true;
}
// Check theme name for common dark theme identifiers
Glib::ustring theme_name;
settings->get_property("gtk-theme-name", theme_name);
const std::string theme_str = theme_name.lowercase();
if (theme_str.find("dark") != std::string::npos ||
theme_str.find("black") != std::string::npos) {
return true;
}
}
return false;
}
+2 -2
View File
@@ -58,8 +58,8 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
std::string& result, const std::string& default_str, Gtk::Window* parent = nullptr, bool sec_msg_markup = false);
/// Check if a dark GTK theme is currently active
bool gui_is_dark_theme_active();
#endif
+1 -1
View File
@@ -83,7 +83,7 @@ hz::ExpectedVoid<StorageDetectorError> StorageDetector::detect(std::vector<Stora
}
// Sort the drives, because their order is not quite defined.
// TODO Sort using natural sort
// Natural sort is implemented in the StorageDevicePtr comparison operator.
std::sort(drives.begin(), drives.end());
debug_out_info("app", DBG_FUNC_MSG << "Drive detection finished.\n");
+1 -65
View File
@@ -35,6 +35,7 @@ Copyright:
#include "smartctl_version_parser.h"
#include "storage_property_descr.h"
#include "build_config.h"
#include "smartctl_parser.h"
//#include "smartctl_text_parser_helper.h"
//#include "ata_storage_property_descr.h"
@@ -327,71 +328,6 @@ hz::ExpectedVoid<StorageDeviceError> StorageDevice::fetch_full_data_and_parse(
}
/*
hz::ExpectedVoid<StorageDeviceError> StorageDevice::try_parse_data()
{
this->clear_fetched(false); // clear everything fetched before, except outputs and disk type
auto parser_format = SmartctlParser::detect_output_format(this->full_output_);
if (!parser_format.has_value()) {
return hz::Unexpected(StorageDeviceError::ParseError, parser_format.error().message());
}
// auto basic_parser = SmartctlParser::create(SmartctlParserType::Basic, parser_format.value());
// if (!basic_parser) {
// return hz::Unexpected(StorageDeviceError::ParseError, _("Cannot create parser"));
// }
// TODO Choose format according to device type
SmartctlParserType parser_type = SmartctlParserType::Ata;
auto parser = SmartctlParser::create(parser_type, parser_format.value());
DBG_ASSERT_RETURN(parser, hz::Unexpected(StorageDeviceError::ParseError, _("Cannot create parser")));
// Try to parse it (parse only, set the properties after basic parsing).
const auto parse_status = parser->parse(this->full_output_);
if (parse_status.has_value()) {
// refresh basic info too
this->basic_output_ = this->full_output_; // put data including version information
// note: this will clear the non-basic properties!
// this will parse some info that is already parsed by SmartctlAtaTextParser::parse(),
// but this one sets the StorageDevice class members, not properties.
static_cast<void>(this->parse_basic_data(false, false)); // don't emit signal, we're not complete yet.
// Call this after parse_basic_data(), since it sets parse status to "info".
this->set_parse_status(StorageDevice::ParseStatus::Full);
// set the full properties.
// copy to our drive, overwriting old data.
this->set_property_repository(StoragePropertyProcessor::process_properties(parser->get_property_repository(), disk_type));
signal_changed().emit(this); // notify listeners
return {};
}
// Don't show any GUI warnings on parse failure - it may just be an unsupported
// drive (e.g. usb flash disk). Plus, it may flood the string. The data will be
// parsed again in Info window, and we show the warnings there.
debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse smartctl output.\n");
// proper parsing failed. try to at least extract info section
this->basic_output_ = this->full_output_; // complete output here. sometimes it's only the info section
auto basic_parse_status = this->parse_basic_data(true); // will add some properties too. this will emit signal_changed().
if (!basic_parse_status) {
// return full parser's error messages - they are more detailed.
std::string message = basic_parse_status.error().message();
return hz::Unexpected(StorageDeviceError::ParseError,
fmt::format(fmt::runtime(_("Cannot parse smartctl output: {}")), message));
}
return {}; // return ok if at least the info was ok.
}
*/
hz::ExpectedVoid<StorageDeviceError> StorageDevice::parse_full_data(SmartctlParserType parser_type, SmartctlOutputFormat format)
{
+5 -7
View File
@@ -19,8 +19,9 @@ Copyright:
#include <sigc++/sigc++.h>
#include "hz/fs_ns.h"
#include "hz/string_algo.h"
#include "storage_property.h"
#include "smartctl_text_ata_parser.h" // prop_list_t
#include "smartctl_parser_types.h"
#include "smartctl_executor.h"
#include "storage_property_repository.h"
#include "storage_device_detected_type.h"
@@ -106,9 +107,6 @@ class StorageDevice {
/// Execute smartctl --all / -x (all sections), get output, parse it (basic data too), fill properties.
[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> fetch_full_data_and_parse(const std::shared_ptr<CommandExecutor>& smartctl_ex);
/// Parse full info. If failed, try to parse it as basic info.
// [[nodiscard]] hz::ExpectedVoid<StorageDeviceError> try_parse_data();
/// Parse full info.
[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> parse_full_data(SmartctlParserType parser_type, SmartctlOutputFormat format);
@@ -325,12 +323,12 @@ inline bool operator< (const StorageDevicePtr& a, const StorageDevicePtr& b)
return int(a->get_is_virtual()) < int(b->get_is_virtual());
}
if (a->get_is_virtual()) {
return a->get_virtual_file() < b->get_virtual_file();
return hz::string_natural_compare(a->get_virtual_filename(), b->get_virtual_filename()) < 0;
}
if (a->get_device_base() != b->get_device_base()) {
return a->get_device_base() < b->get_device_base();
return hz::string_natural_compare(a->get_device_base(), b->get_device_base()) < 0;
}
return a->get_type_argument() < b->get_type_argument();
return hz::string_natural_compare(a->get_type_argument(), b->get_type_argument()) < 0;
}
+98 -2
View File
@@ -15,8 +15,10 @@ Copyright:
#include <string>
#include <map>
#include <utility>
#include <set>
#include "rconfig/rconfig.h"
#include "hz/debug.h"
/// Device name, device type (optional)
@@ -75,7 +77,7 @@ inline void from_json(const rconfig::json& j, AppDeviceOptionMap& devmap)
/// Get device option map from config
/// Get per-device options from config
inline AppDeviceOptionMap app_config_get_device_option_map()
{
return rconfig::get_data<AppDeviceOptionMap>("system/smartctl_device_options");
@@ -99,7 +101,8 @@ inline std::vector<std::string> app_get_device_options(const std::string& dev, c
}
catch(Glib::ShellError& e)
{
// TODO report error
debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse device options for \""
<< dev << "\": " << e.what() << "\n");
return {};
}
}
@@ -108,6 +111,99 @@ inline std::vector<std::string> app_get_device_options(const std::string& dev, c
/// Device information used when manually adding devices
struct AppAddDeviceOption {
AppAddDeviceOption() = default;
AppAddDeviceOption(std::string par_device, std::string par_type, std::string par_options)
: device(std::move(par_device)), type(std::move(par_type)), options(std::move(par_options))
{ }
std::string device; ///< Device name, e.g. /dev/sda
std::string type; ///< Smartctl type
std::string options; ///< Additional smartctl options
/// Compare two AppAddDeviceOption objects
bool operator==(const AppAddDeviceOption& other) const
{
return device == other.device && type == other.type && options == other.options;
}
/// Compare two AppAddDeviceOption objects
bool operator!=(const AppAddDeviceOption& other) const
{
return !(*this == other);
}
/// Compare two AppAddDeviceOption objects
bool operator<(const AppAddDeviceOption& other) const
{
if (device != other.device)
return device < other.device;
if (type != other.type)
return type < other.type;
return options < other.options;
}
};
/// json serializer for rconfig
inline void to_json(rconfig::json& j, const std::set<AppAddDeviceOption>& devices)
{
for (const auto& dev : devices) {
if (!dev.device.empty()) {
j.push_back(rconfig::json {
{"device", dev.device},
{"type", dev.type},
{"options", dev.options}
});
}
}
}
/// json deserializer for rconfig
inline void from_json(const rconfig::json& j, std::set<AppAddDeviceOption>& devices)
{
if (!j.is_array()) {
return;
}
for (const auto& obj : j) {
try {
if (obj.contains("device") && obj.contains("type") && obj.contains("options")) {
devices.emplace(
obj.at("device").get<std::string>(),
obj.at("type").get<std::string>(),
obj.at("options").get<std::string>()
);
}
}
catch(std::exception& e) {
// ignore "not found"
}
}
}
/// Get devices to manually add on startup
inline std::set<AppAddDeviceOption> app_get_startup_manual_devices()
{
return rconfig::get_data<std::set<AppAddDeviceOption>>("system/startup_manual_devices");
}
/// Set devices to manually add on startup
inline void app_set_startup_manual_devices(std::set<AppAddDeviceOption> devices)
{
rconfig::set_data("system/startup_manual_devices", std::move(devices));
}
#endif
-4
View File
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup applib_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
#include "applib/app_regex.h"
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup applib_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
#include "applib/smartctl_parser.h"
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup applib_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
#include "applib/smartctl_version_parser.h"
+88
View File
@@ -0,0 +1,88 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib
/// \weakgroup applib
/// @{
#include <glibmm.h>
#include "warning_colors.h"
#include "gui_utils.h"
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#6B2050" : "#FFD5EE"; // dark pinkish for dark themes, pinkish for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#802020" : "#FFA0A0"; // dark red for dark themes, light red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#AA0000" : "#FF0000"; // darker red for dark themes, bright red for light themes
}
return !(fg.empty());
}
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::None) {
return false;
}
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FF9999" : "#770000"; // lighter red for dark themes, very dark red for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FF6666" : "#C00000"; // lighter red for dark themes, dark red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FF4444" : "#FF0000"; // lighter/pink red for dark themes, bright red for light themes
}
return !(fg.empty());
}
std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
/// @}
+6 -65
View File
@@ -1,7 +1,7 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
@@ -12,83 +12,24 @@ Copyright:
#ifndef WARNING_COLORS_H
#define WARNING_COLORS_H
#include <glibmm.h>
#include <string>
#include "storage_property.h"
#include "warning_level.h"
/// Get colors for tree rows according to warning severity.
/// \return true if the colors were changed.
inline bool app_property_get_row_highlight_colors(WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = "#000000"; // black
bg = "#FFD5EE"; // pinkish
} else if (warning == WarningLevel::Warning) {
fg = "#000000"; // black
bg = "#FFA0A0"; // even more pinkish
} else if (warning == WarningLevel::Alert) {
fg = "#000000"; // black
bg = "#FF0000"; // red
}
return !(fg.empty());
}
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg);
/// Get color for labels according to warning severity.
/// \return true if the color was changed.
inline bool app_property_get_label_highlight_color(WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::Notice) {
fg = "#770000"; // very dark red
} else if (warning == WarningLevel::Warning) {
fg = "#C00000"; // dark red
} else if (warning == WarningLevel::Alert) {
fg = "#FF0000"; // red
}
return !(fg.empty());
}
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg);
/// Format warning text, but without description
inline std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
std::string storage_property_get_warning_reason(const StorageProperty& p);
+2 -2
View File
@@ -1,7 +1,7 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
@@ -40,7 +40,7 @@ GscAboutDialog::GscAboutDialog(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builde
// spammers go away
set_copyright(Glib::ustring::compose("Copyright (C) %1",
"2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>"));
"2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>"));
// set_authors({"Alexander Shaduri <ashaduri@gmail.com>"});
// set_documenters({"Alexander Shaduri <ashaduri@gmail.com>"});
+25 -3
View File
@@ -20,7 +20,7 @@ Copyright:
#include "gsc_add_device_window.h"
#include "gsc_main_window.h"
#include "build_config.h"
#include "applib/storage_settings.h"
@@ -148,6 +148,7 @@ void GscAddDeviceWindow::on_window_ok_button_clicked()
{
std::string dev, type;
std::vector<std::string> params;
std::string params_str;
if (auto* entry = lookup_widget<Gtk::Entry*>("device_name_entry")) {
dev = entry->get_text();
}
@@ -155,7 +156,7 @@ void GscAddDeviceWindow::on_window_ok_button_clicked()
type = type_combo->get_entry_text();
}
if (auto* entry = lookup_widget<Gtk::Entry*>("smartctl_params_entry")) {
auto params_str = entry->get_text();
params_str = entry->get_text();
if (!params_str.empty()) {
try {
params = Glib::shell_parse_argv(params_str);
@@ -166,8 +167,29 @@ void GscAddDeviceWindow::on_window_ok_button_clicked()
}
}
}
bool added = false;
if (main_window_ && !dev.empty()) {
main_window_->add_device(dev, type, params);
added = main_window_->add_device_interactive(dev, type, params);
}
if (added) {
bool auto_add = false;
if (auto* check = lookup_widget<Gtk::CheckButton*>("auto_add_device_check")) {
auto_add = check->get_active();
}
// If auto-add is checked, save the device info to config
if (auto_add && !dev.empty()) {
// Get existing auto-add devices
auto devices = app_get_startup_manual_devices();
// Add or update this device in the map
devices.emplace(dev, type, params_str);
// Save the updated map back to config
app_set_startup_manual_devices(devices);
}
}
destroy_instance();
+12 -6
View File
@@ -81,6 +81,7 @@ namespace {
// vbox->pack_start(*label, false, false);
} else {
const bool dark_mode = gui_is_dark_theme_active();
// add one label per element
for (const auto& label_string : label_strings) {
@@ -95,7 +96,7 @@ namespace {
label->set_can_focus(false);
std::string fg;
if (app_property_get_label_highlight_color(label_string.property->warning_level, fg)) {
if (app_property_get_label_highlight_color(dark_mode, label_string.property->warning_level, fg)) {
label->set_markup(
std::string("<span color=\"").append(fg).append("\">")
.append(label_text).append("</span>") );
@@ -129,7 +130,7 @@ namespace {
}
std::string fg;
if (app_property_get_label_highlight_color(warning, fg))
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), warning, fg))
label->set_markup_with_mnemonic("<span color=\"" + fg + "\">" + original_label + "</span>");
}
@@ -1038,6 +1039,7 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
identity_table->hide();
WarningLevel max_tab_warning = WarningLevel::None;
const bool dark_mode = gui_is_dark_theme_active();
int row = 0;
for (auto&& p : general_props) {
@@ -1070,7 +1072,7 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
value->set_markup(Glib::Markup::escape_text(p.format_value()));
std::string fg;
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
if (app_property_get_label_highlight_color(dark_mode, p.warning_level, fg)) {
name->set_markup("<span color=\"" + fg + "\">" + name->get_label() + "</span>");
value->set_markup("<span color=\"" + fg + "\">" + value->get_label() + "</span>");
}
@@ -1490,7 +1492,7 @@ void GscInfoWindow::fill_ui_self_test_log(const StoragePropertyRepository& prope
model_columns.add(columns_->self_test_log_table_columns.hours);
num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.hours, *treeview,
_("Lifetime hours"), _("Hour of the drive's powered-on lifetime when the test completed or aborted.\nThe value wraps after 36535 hours."), true);
_("Lifetime hours"), _("Hour of the drive's powered-on lifetime when the test completed or aborted.\nThe value wraps after 65535 hours."), true);
model_columns.add(columns_->self_test_log_table_columns.lba);
num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.lba, *treeview,
@@ -2067,7 +2069,7 @@ WarningLevel GscInfoWindow::fill_ui_directory(const StoragePropertyRepository& p
inline void cell_renderer_set_warning_fg_bg(Gtk::CellRendererText* crt, const StorageProperty& p)
{
std::string fg, bg;
if (app_property_get_row_highlight_colors(p.warning_level, fg, bg)) {
if (app_property_get_row_highlight_colors(gui_is_dark_theme_active(), p.warning_level, fg, bg)) {
// Note: property_cell_background makes horizontal tree lines disappear around it,
// but property_background doesn't play nice with sorted column color.
crt->property_cell_background() = bg;
@@ -2444,7 +2446,11 @@ gboolean GscInfoWindow::test_idle_callback(void* data)
break;
case SelfTestStatusSeverity::Error:
if (!result_main_msg.empty()) { // Highlight in red
result_main_msg = "<span color=\"#FF0000\">"s + result_main_msg + "</span>";
std::string alert_color;
// Use the same color as Alert level warnings for consistency
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), WarningLevel::Alert, alert_color) && !alert_color.empty()) {
result_main_msg = "<span color=\"" + alert_color + "\">"s + result_main_msg + "</span>";
}
}
result_details_msg += "\n"s + _("Check the Self-Test Log for more information.");
break;
+34 -2
View File
@@ -2,7 +2,7 @@
*****************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2024 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>
*****************************************************************************
*/
/// \file
@@ -206,6 +206,7 @@ namespace {
gboolean arg_locale = TRUE; ///< if false, disable using system locale
gboolean arg_version = FALSE; ///< if true, show version and exit
gboolean arg_scan = TRUE; ///< if false, don't scan the system for drives on startup
gboolean arg_forget_manual_devices = FALSE; ///< if true, forget all manually added devices
gchar** arg_add_virtual = nullptr; ///< load smartctl data from these files as virtual drives
gchar** arg_add_device = nullptr; ///< add these device files manually
double arg_gdk_scale = std::numeric_limits<double>::quiet_NaN(); ///< The value of GDK_SCALE environment variable
@@ -230,6 +231,8 @@ namespace {
N_("Add this device to device list. The format of the device is \"<device>::<type>::<extra_args>\", where type and extra_args are optional."
" This option is useful with --no-scan to list certain drives only. You can specify this option multiple times."
" Example: --add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device '/dev/sdb::::-T permissive'"), nullptr },
{ "forget-devices", '\0', 0, G_OPTION_ARG_NONE, &(args.arg_forget_manual_devices),
N_("Forget all previously manually added devices."), nullptr },
#ifndef _WIN32
// X11-specific
{ "gdk-scale", 'l', 0, G_OPTION_ARG_DOUBLE, &(args.arg_gdk_scale),
@@ -284,7 +287,7 @@ namespace {
std::string warningtext = std::string("\n") + _("Warning: GSmartControl comes with ABSOLUTELY NO WARRANTY.\n"
"See LICENSE.txt file for details.") + "\n\n";
/// %1 is years, %2 is email address
warningtext += Glib::ustring::compose(_("Copyright (C) %1 Alexander Shaduri %2"), "2008 - 2024",
warningtext += Glib::ustring::compose(_("Copyright (C) %1 Alexander Shaduri %2"), "2008 - 2025",
"<ashaduri@gmail.com>") + "\n\n";
std::cout << versiontext << warningtext;
@@ -502,6 +505,32 @@ bool app_init_and_loop(int& argc, char**& argv)
}
*/
// Detect Windows dark mode and set GTK theme preference accordingly
if constexpr(BuildEnv::is_kernel_family_windows()) {
Glib::RefPtr<Gtk::Settings> gtk_settings = Gtk::Settings::get_default();
if (gtk_settings) {
bool use_dark_theme = false;
#ifdef _WIN32
// Check Windows registry for dark mode preference
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
// AppsUseLightTheme = 0 means dark mode, 1 means light mode
DWORD apps_use_light_theme = 1; // Default to light mode
if (hz::win32_get_registry_value_dword(HKEY_CURRENT_USER,
R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)",
"AppsUseLightTheme", apps_use_light_theme)) {
use_dark_theme = (apps_use_light_theme == 0);
debug_out_dump("app", "Windows theme detected: " << (use_dark_theme ? "dark" : "light") << "\n");
} else {
debug_out_dump("app", "Could not read Windows theme preference, defaulting to light mode.\n");
}
#endif
// Apply the dark theme preference to GTK
gtk_settings->property_gtk_application_prefer_dark_theme().set_value(use_dark_theme);
debug_out_dump("app", "GTK dark theme preference set to: " << (use_dark_theme ? "dark" : "light") << "\n");
}
}
// The application is dpi-aware in Windows.
// However, Gtk3 does not support fractional scaling, so at 250% scaling in system settings, the UI will use 200%.
//
@@ -557,6 +586,9 @@ bool app_init_and_loop(int& argc, char**& argv)
// add devices to the list on startup if specified.
get_startup_settings().add_devices = load_devices;
// forget all manually added devices on startup if specified.
get_startup_settings().forget_manual_devices = bool(args.arg_forget_manual_devices);
// Create executor log window, but don't show it.
// It will track all command executor outputs.
+64 -7
View File
@@ -44,7 +44,7 @@ Copyright:
#include "applib/command_executor_factory.h"
#include "gsc_startup_settings.h"
#include "build_config.h"
#include "applib/storage_settings.h"
using namespace std::literals;
@@ -142,10 +142,18 @@ void GscMainWindow::populate_iconview_on_startup(bool smartctl_valid)
}
}
if (!file.empty()) {
add_device(file, type_arg, extra_args);
// Report any smartctl-related issues
add_device_interactive(file, type_arg, extra_args);
}
}
}
// Add auto-add devices stored in configuration
if (get_startup_settings().forget_manual_devices) {
app_set_startup_manual_devices({}); // clear the list
} else {
add_startup_manual_devices();
}
}
for (auto&& virt : get_startup_settings().load_virtuals) {
@@ -483,8 +491,16 @@ void GscMainWindow::on_action_activated(GscMainWindow::action_t action_type)
case action_remove_device:
if (iconview_) {
StorageDevicePtr drive = iconview_->get_selected_drive();
if (drive && drive->get_is_manually_added() && !drive->get_test_is_active())
if (drive && drive->get_is_manually_added() && !drive->get_test_is_active()) {
iconview_->remove_selected_drive();
auto devices = app_get_startup_manual_devices();
std::erase_if(devices,
[drive](const AppAddDeviceOption& dev) {
return (dev.device == drive->get_device() && dev.type == drive->get_type_argument());
});
app_set_startup_manual_devices(devices);
}
}
break;
@@ -507,6 +523,7 @@ void GscMainWindow::on_action_activated(GscMainWindow::action_t action_type)
case action_rescan_devices:
rescan_devices(false);
add_startup_manual_devices();
break;
case action_executor_log:
@@ -757,7 +774,7 @@ void GscMainWindow::update_status_widgets()
if (health_prop.generic_name == "smart_status/passed") {
health_label_->set_text(health_prop.format_value());
std::string fg;
if (app_property_get_label_highlight_color(health_prop.warning_level, fg)) {
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), health_prop.warning_level, fg)) {
health_label_->set_markup("<span color=\"" + fg + "\">"+ Glib::Markup::escape_text(health_label_->get_text()) + "</span>");
}
// don't set description tooltip - we already have the basic one.
@@ -893,6 +910,29 @@ void GscMainWindow::rescan_devices(bool startup)
void GscMainWindow::add_startup_manual_devices()
{
auto auto_add_devices = app_get_startup_manual_devices();
for (const auto& dev : auto_add_devices) {
if (!dev.device.empty()) {
std::vector<std::string> params;
if (!dev.options.empty()) {
try {
params = Glib::shell_parse_argv(dev.options);
}
catch(Glib::ShellError& e) {
// Skip this entry if we can't parse params
continue;
}
}
// Ignore errors here so that the devices can be removed manually
add_device_silent(dev.device, dev.type, params);
}
}
}
void GscMainWindow::run_update_drivedb()
{
auto smartctl_binary = get_smartctl_binary();
@@ -930,18 +970,34 @@ void GscMainWindow::run_update_drivedb()
bool GscMainWindow::add_device(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)
bool GscMainWindow::add_device_interactive(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)
{
// win32 doesn't have device files, so skip the check in Windows.
if constexpr(!BuildEnv::is_kernel_family_windows()) {
std::error_code ec;
if (!hz::fs::exists(hz::fs_path_from_string(file), ec)) {
gui_show_error_dialog(_("Cannot add device"),
(ec.message().empty() ? Glib::ustring::compose(_("Device \"%1\" doesn't exist."), file).raw() : ec.message()), this);
(!ec ? Glib::ustring::compose(_("Device \"%1\" doesn't exist."), file).raw() : ec.message()), this);
return false;
}
}
return add_device(false, file, type_arg, extra_args);
}
bool GscMainWindow::add_device_silent(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)
{
// We want the manual add to always succeed even if there are errors,
// so that it can be removed as well.
return add_device(true, file, type_arg, extra_args);
}
bool GscMainWindow::add_device(bool silent, const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)
{
auto drive = std::make_shared<StorageDevice>(file);
drive->set_type_argument(type_arg);
drive->set_extra_arguments(extra_args);
@@ -952,9 +1008,10 @@ bool GscMainWindow::add_device(const std::string& file, const std::string& type_
std::vector<StorageDevicePtr> tmp_drives;
tmp_drives.push_back(drive);
// Don't report errors here, just add the drive to the list.
StorageDetector sd;
auto fetch_error = sd.fetch_basic_data(tmp_drives, ex_factory, true); // return its first error
if (!fetch_error) {
if (!silent && !fetch_error) {
gsc_executor_error_dialog_show(_("An error occurred while adding the device"), fetch_error.error().message(), this);
} else {
+17 -2
View File
@@ -48,13 +48,28 @@ class GscMainWindow : public AppBuilderWidget<GscMainWindow, false> {
void rescan_devices(bool startup);
/// Add manually added devices to the icon view.
void add_startup_manual_devices();
/// Execute update-smart-drivedb
void run_update_drivedb();
/// Manually add device file to icon list
bool add_device(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);
/// Add device file to icon list, interactively showing errors if any.
bool add_device_interactive(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);
/// Add device file to icon list silently, ignoring errors.
bool add_device_silent(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);
private:
/// Add device file to icon list
bool add_device(bool silent, const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);
public:
/// Read smartctl data from file, add it as a virtual drive to icon list
bool add_virtual_drive(const std::string& file);
+16 -2
View File
@@ -120,7 +120,7 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
return true;
}
if (empty_view_message_ != Message::None && this->num_icons_ == 0) { // no icons
Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
const Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
layout->set_alignment(Pango::ALIGN_CENTER);
layout->set_markup(get_message_string(empty_view_message_));
@@ -131,6 +131,12 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
const int pos_y = (get_allocation().get_height() - layout_h) / 2;
cr->move_to(pos_x, pos_y);
// Use the foreground color from the widget's style context so
// the text is visible in both light and dark themes.
const auto style_context = get_style_context();
const Gdk::RGBA fg_color = style_context->get_color(style_context->get_state());
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), fg_color.get_alpha());
layout->show_in_cairo_context(cr);
return true;
@@ -548,10 +554,18 @@ Glib::RefPtr<Gdk::Pixbuf> GscMainWindowIconView::load_icon_pixbuf(Glib::RefPtr<G
{
Glib::RefPtr<Gdk::Pixbuf> icon;
// Try XDG version first
// Try the icon theme first; fall back to the bundled icon if needed.
try {
if (default_icon_theme && !xdg_icon_name.empty()) {
icon = default_icon_theme->load_icon(xdg_icon_name, icon_size_, get_scale_factor(), Gtk::IconLookupFlags(0));
// Some icon themes may return an icon smaller than requested.
// In that case, fall back to the bundled icon.
if (icon &&
(icon->get_width() < icon_size_ ||
icon->get_height() < icon_size_)) {
icon.reset();
}
}
} catch (...) { } // ignore exceptions
+1
View File
@@ -20,6 +20,7 @@ struct GscStartupSettings {
bool no_scan = false; ///< No scanning on startup
std::vector<std::string> load_virtuals; ///< Virtual files to load
std::vector<std::string> add_devices; ///< Devices to add (with options)
bool forget_manual_devices = false; ///< Forget all manually added devices
};
+2 -2
View File
@@ -1,7 +1,7 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
gsmartcontrol ICON "gsmartcontrol.ico"
@@ -22,7 +22,7 @@ BEGIN
VALUE "FileDescription", "GSmartControl - Hard disk drive and SSD health inspection tool"
VALUE "FileVersion", "@CMAKE_PROJECT_VERSION@"
VALUE "InternalName", "gsmartcontrol.exe"
VALUE "LegalCopyright", "Copyright (C) 2008 - 2024 Alexander Shaduri"
VALUE "LegalCopyright", "Copyright (C) 2008 - 2025 Alexander Shaduri"
VALUE "OriginalFilename", "gsmartcontrol.exe"
VALUE "ProductName", "GSmartControl"
VALUE "ProductVersion", "@CMAKE_PROJECT_VERSION@"
+85 -63
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.40.0
Copyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
@@ -25,31 +25,28 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<!-- interface-name GSmartControl -->
<!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->
<object class="GtkWindow" id="gsc_add_device_window">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Add Device - GSmartControl</property>
<property name="modal">True</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="top_info_link_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">The &lt;a href="%1"&gt;smartctl man page&lt;/a&gt; contains information on what you can enter here.</property>
<property name="use_markup">True</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -59,24 +56,25 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="device_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="invisible-char">●</property>
<property name="activates-default">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -88,9 +86,9 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="device_name_browse_button">
<property name="label" translatable="yes">Browse...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
@@ -100,80 +98,89 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="device_type_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Device type:</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Additional smartctl parameters</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Additional smartctl parameters</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Smartctl parameters:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="device_type_combo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<property name="can-focus">False</property>
<property name="has-entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
<property name="can-focus">True</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="smartctl_params_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Additional smartctl parameters</property>
<property name="invisible_char">●</property>
<property name="activates_default">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Additional smartctl parameters</property>
<property name="invisible-char">●</property>
<property name="activates-default">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="device_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Device name:</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -182,19 +189,34 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="auto_add_device_check">
<property name="label" translatable="yes">Automatically add this device on startup</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Note: To make this change permanent, you'll have to add the gsmartcontrol --add-device command line option to its shortcut.</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">6</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
@@ -207,16 +229,16 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="window_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">False</property>
@@ -228,11 +250,11 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="window_ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">False</property>
@@ -244,7 +266,7 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
+218 -192
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
<!-- Generated with glade 3.40.0
Copyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
@@ -25,65 +25,62 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<!-- interface-name GSmartControl -->
<!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->
<object class="GtkWindow" id="gsc_preferences_window">
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkNotebook" id="notebook1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="can-focus">True</property>
<property name="has-focus">True</property>
<child>
<object class="GtkBox" id="vbox3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<property name="can-focus">False</property>
<property name="left-padding">12</property>
<child>
<object class="GtkBox" id="vbox5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="vbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="scan_on_startup_check">
<property name="label" translatable="yes">Scan system for drives on startup</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -95,11 +92,11 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkCheckButton" id="show_smart_capable_only_check">
<property name="label" translatable="yes">Show SMART-capable drives only</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -111,11 +108,11 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkCheckButton" id="show_device_name_under_icon_check">
<property name="label" translatable="yes">Show device name under drive icon</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -127,11 +124,11 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkCheckButton" id="show_serial_number_under_icon_check">
<property name="label" translatable="yes">Show serial number under drive icon</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -154,9 +151,9 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="label">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Program Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="use-markup">True</property>
</object>
</child>
</object>
@@ -170,35 +167,35 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<property name="can-focus">False</property>
<property name="left-padding">12</property>
<child>
<object class="GtkBox" id="vbox7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="vbox12">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">7</property>
<child>
<object class="GtkCheckButton" id="search_in_smartmontools_first_check">
<property name="label" translatable="yes">Look for smartctl in smartmontools installation directory first</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If smartmontools is installed, use its smartctl by default</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="tooltip-text" translatable="yes">If smartmontools is installed, use its smartctl by default</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -207,23 +204,24 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
</packing>
</child>
<child>
<!-- n-columns=3 n-rows=3 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkBox" id="hbox4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="smartctl_binary_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="activates_default">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="activates-default">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -235,9 +233,9 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="smartctl_binary_browse_button">
<property name="label" translatable="yes">Browse...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -247,51 +245,66 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="smartctl_options_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>
<property name="activates_default">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>
<property name="activates-default">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Smartctl parameters:</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="smartctl_binary_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Smartctl binary:</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
@@ -314,9 +327,9 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="label">
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Smartctl Invocation&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="use-markup">True</property>
</object>
</child>
</object>
@@ -332,53 +345,53 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="tab">
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">3</property>
<property name="margin_bottom">3</property>
<property name="can-focus">False</property>
<property name="margin-top">3</property>
<property name="margin-bottom">3</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">_General</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="tab_fill">False</property>
<property name="tab-fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vbox4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkFrame" id="frame3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<property name="can-focus">False</property>
<property name="left-padding">12</property>
<child>
<object class="GtkBox" id="vbox9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="hbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all "/dev/sd*" devices, use "sd.{1}$". Simply listing them as "sda;sdb" will also work.</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all "/dev/sd*" devices, use "sd.{1}$". Simply listing them as "sda;sdb" will also work.</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Device blacklist patterns:</property>
<property name="use_underline">True</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">False</property>
@@ -389,11 +402,11 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkEntry" id="device_blacklist_patterns_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all "/dev/sd*" devices, use "sd.$". Simply listing them as "sda;sdb" or "sd[ab]" will also work.</property>
<property name="activates_default">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all "/dev/sd*" devices, use "sd.$". Simply listing them as "sda;sdb" or "sd[ab]" will also work.</property>
<property name="activates-default">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -416,7 +429,7 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="label">
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Drive Search</property>
<attributes>
<attribute name="weight" value="bold"/>
@@ -434,28 +447,28 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkFrame" id="frame4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">none</property>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<property name="can-focus">False</property>
<property name="left-padding">12</property>
<child>
<object class="GtkBox" id="vbox8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkAlignment" id="alignment5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">12</property>
<property name="can-focus">False</property>
<property name="top-padding">12</property>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Adding a drive here will make it use the specified parameters.</property>
</object>
@@ -471,25 +484,25 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="vbox10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="device_options_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Device list</property>
<property name="headers_clickable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Device list</property>
<property name="headers-clickable">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
@@ -505,17 +518,17 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkBox" id="hbox3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="device_options_remove_device_button">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove selected entry from device list</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Remove selected entry from device list</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -527,10 +540,10 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="device_options_add_device_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add a new entry to device list</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Add a new entry to device list</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -555,97 +568,98 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkBox" id="vbox11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<!-- n-columns=3 n-rows=4 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="label67">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Smartctl parameters to add (for example, "-T permissive" or "-d usbsunplus")</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Smartctl parameters to add (for example, "-T permissive" or "-d usbsunplus")</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Parameters:</property>
<property name="use_underline">True</property>
<property name="label" translatable="yes">Add parameters:</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="device_options_type_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. "areca,2".</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. "areca,2".</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Type:</property>
<property name="use_underline">True</property>
<property name="label" translatable="yes">Match type:</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="device_options_device_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Device:</property>
<property name="use_underline">True</property>
<property name="label" translatable="yes">Match device:</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="device_options_parameter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Smartctl parameters to add (for example, "-T permissive" or "-d usbsunplus")</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Smartctl parameters to add (for example, "-T permissive" or "-d usbsunplus")</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="device_options_type_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. "areca,2".</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="tooltip-text" translatable="yes">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. "areca,2".</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="device_options_device_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="can-focus">True</property>
<property name="primary-icon-activatable">False</property>
<property name="secondary-icon-activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Drive Properties:</property>
<attributes>
@@ -653,11 +667,23 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -687,8 +713,8 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="label">
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Per-drive Smartctl Parameters</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Per-Drive Smartctl Parameters</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -710,17 +736,17 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child type="tab">
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="can-focus">False</property>
<property name="margin-left">6</property>
<property name="margin-right">6</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">_Drives</property>
<property name="use_underline">True</property>
<property name="width_chars">10</property>
<property name="use-underline">True</property>
<property name="width-chars">10</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object>
@@ -740,17 +766,17 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="window_reset_all_button">
<property name="label" translatable="yes">_Reset all</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Reset all program settings to their defaults</property>
<property name="use_underline">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Reset all program settings to their defaults</property>
<property name="use-underline">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -761,7 +787,7 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -773,9 +799,9 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="window_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -787,12 +813,12 @@ along with GSmartControl. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkButton" id="window_ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="is_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="is-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">True</property>
+70
View File
@@ -884,6 +884,76 @@ inline std::string string_to_upper_copy(std::string_view s)
// --------------------------------------------- Natural Sort
/// Compare two strings using natural (alphanumeric) sort order.
/// Natural sort order treats consecutive digits as numbers, so "file2.txt" comes before "file10.txt".
/// This is useful for sorting filenames, device names, etc.
/// Returns: < 0 if a < b, 0 if a == b, > 0 if a > b
inline int string_natural_compare(std::string_view a, std::string_view b)
{
std::size_t i = 0;
std::size_t j = 0;
const std::size_t a_size = a.size();
const std::size_t b_size = b.size();
while (i < a_size && j < b_size) {
const bool a_is_digit = std::isdigit(static_cast<unsigned char>(a[i]));
const bool b_is_digit = std::isdigit(static_cast<unsigned char>(b[j]));
if (a_is_digit && b_is_digit) {
// Both are digits, compare as numbers
// Skip leading zeros
while (i < a_size && a[i] == '0') {
++i;
}
while (j < b_size && b[j] == '0') {
++j;
}
// Count the number of digits
std::size_t a_digit_start = i;
std::size_t b_digit_start = j;
while (i < a_size && std::isdigit(static_cast<unsigned char>(a[i]))) {
++i;
}
while (j < b_size && std::isdigit(static_cast<unsigned char>(b[j]))) {
++j;
}
const std::size_t a_digit_count = i - a_digit_start;
const std::size_t b_digit_count = j - b_digit_start;
// Compare by length first (longer number is greater)
if (a_digit_count != b_digit_count) {
return static_cast<int>(a_digit_count) - static_cast<int>(b_digit_count);
}
// Same length, compare digit by digit
for (std::size_t k = 0; k < a_digit_count; ++k) {
if (a[a_digit_start + k] != b[b_digit_start + k]) {
return static_cast<int>(a[a_digit_start + k]) - static_cast<int>(b[b_digit_start + k]);
}
}
} else if (a_is_digit != b_is_digit) {
// One is digit, one is not - digit comes before non-digit
return a_is_digit ? -1 : 1;
} else {
// Both are non-digits, compare as characters
if (a[i] != b[j]) {
return static_cast<int>(static_cast<unsigned char>(a[i])) - static_cast<int>(static_cast<unsigned char>(b[j]));
}
++i;
++j;
}
}
// If one string is a prefix of the other, the shorter one comes first
return static_cast<int>(a_size) - static_cast<int>(b_size);
}
} // ns
-4
View File
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup hz_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
// disable libdebug, we don't link to it
+52 -6
View File
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup hz_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
// disable libdebug, we don't link to it
@@ -124,11 +120,61 @@ TEST_CASE("StringAlgorithms", "[hz][string]")
string_replace_array(s, from, ":");
REQUIRE(s == ":345678:defg : ab");
}
SECTION("string_natural_compare") {
using namespace hz;
// Test basic number comparison
REQUIRE(string_natural_compare("file1.txt", "file2.txt") < 0);
REQUIRE(string_natural_compare("file2.txt", "file10.txt") < 0);
REQUIRE(string_natural_compare("file10.txt", "file2.txt") > 0);
REQUIRE(string_natural_compare("file9.txt", "file10.txt") < 0);
// Test device names (the actual use case)
REQUIRE(string_natural_compare("pd0", "pd1") < 0);
REQUIRE(string_natural_compare("pd1", "pd2") < 0);
REQUIRE(string_natural_compare("pd2", "pd10") < 0);
REQUIRE(string_natural_compare("pd9", "pd10") < 0);
REQUIRE(string_natural_compare("pd10", "pd11") < 0);
REQUIRE(string_natural_compare("pd10", "pd9") > 0);
// Test equality
REQUIRE(string_natural_compare("pd5", "pd5") == 0);
REQUIRE(string_natural_compare("test", "test") == 0);
// Test prefix
REQUIRE(string_natural_compare("pd", "pd1") < 0);
REQUIRE(string_natural_compare("pd1", "pd") > 0);
// Test mixed content
REQUIRE(string_natural_compare("a1b2c3", "a1b2c10") < 0);
REQUIRE(string_natural_compare("a10b2", "a2b10") > 0);
// Test non-numeric strings
REQUIRE(string_natural_compare("abc", "def") < 0);
REQUIRE(string_natural_compare("xyz", "abc") > 0);
// Test numbers vs letters (digits come before non-digits)
REQUIRE(string_natural_compare("1test", "atest") < 0);
REQUIRE(string_natural_compare("test1", "testa") < 0);
// Test long digit sequences (well beyond a few digits)
REQUIRE(string_natural_compare("file12345678901234567890.txt", "file12345678901234567891.txt") < 0);
REQUIRE(string_natural_compare("file12345678901234567891.txt", "file12345678901234567890.txt") > 0);
REQUIRE(string_natural_compare("file12345678901234567890.txt", "file12345678901234567890.txt") == 0);
// Compare short vs very long numeric substrings
REQUIRE(string_natural_compare("file1.txt", "file12345678901234567890.txt") < 0);
REQUIRE(string_natural_compare("file12345678901234567890.txt", "file99999999999.txt") > 0);
// Mixed with long numbers inside other text
REQUIRE(string_natural_compare("a1b2c123456789012345", "a1b2c123456789012346") < 0);
REQUIRE(string_natural_compare("a1b2c123456789012345", "a1b2c123456789012345") == 0);
}
}
/// @}
-4
View File
@@ -9,10 +9,6 @@ Copyright:
/// \weakgroup hz_tests
/// @{
// Catch2 v3
//#include "catch2/catch_test_macros.hpp"
// Catch2 v2
#include "catch2/catch.hpp"
// disable libdebug, we don't link to it
+51
View File
@@ -79,6 +79,14 @@ inline bool win32_set_registry_value_string(HKEY base,
const std::string& keydir, const std::string& key, const std::string& value);
/// Get registry value as a DWORD.
/// Base may be e.g. HKEY_CURRENT_USER.
/// Note that this works only with REG_DWORD types.
/// False is returned for all other types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here);
/// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time).
/// \param create_if_none if true, create a new console if none was found and attach to it.
/// \return false if failed or unsupported.
@@ -340,6 +348,49 @@ inline bool win32_set_registry_value_string(HKEY base,
// Get registry value as a DWORD.
// Note that this works only with REG_DWORD types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here)
{
std::wstring wkeydir = win32_utf8_to_utf16(keydir);
if (wkeydir.empty())
return false;
HKEY reg_key = nullptr;
bool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS);
if (!open_status)
return false;
bool ok = false;
std::wstring wkey = win32_utf8_to_utf16(key, &ok);
if (!ok) { // conversion error. Note that an empty string is not an error.
if (reg_key)
RegCloseKey(reg_key);
return false;
}
DWORD type = 0;
DWORD value = 0;
DWORD nbytes = sizeof(DWORD);
bool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type,
reinterpret_cast<BYTE*>(&value), &nbytes) == ERROR_SUCCESS);
if (status && type == REG_DWORD) {
put_here = value;
} else {
status = false;
}
if (reg_key)
RegCloseKey(reg_key);
return status;
}
// Redirect stdout and stderr to console window (if open).
inline bool win32_redirect_stdio_to_console(bool create_if_none)
{
-4
View File
@@ -10,10 +10,6 @@ Copyright:
/// @{
// Catch2 v3
// #include "catch2/catch_session.hpp"
// Catch2 v2
#define CATCH_CONFIG_RUNNER
#include "catch2/catch.hpp"