Compare commits

...
Author SHA1 Message Date
anthropic-code-agent[bot]andashaduri c940b200c1 Fix Save log as txt from View Output window
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-04 21:54:59 +00:00
anthropic-code-agent[bot] 5ba5928210 Initial plan 2026-03-04 21:49:39 +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
39 changed files with 9469 additions and 8910 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
+2 -2
View File
@@ -59,7 +59,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 +73,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.
+2 -2
View File
@@ -60,7 +60,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 +74,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
@@ -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
+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"
+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();
+4 -1
View File
@@ -828,6 +828,9 @@ void GscInfoWindow::on_view_output_button_clicked()
if (!filename.empty())
win->set_save_filename(filename);
// Pass the drive pointer so text output can be accessed when saving
win->set_drive(drive_);
win->show();
}
@@ -1490,7 +1493,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,
+8 -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;
@@ -557,6 +560,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.
+63 -6
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:
@@ -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);
+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
};
+38 -1
View File
@@ -24,6 +24,7 @@ Copyright:
#include "applib/app_builder_widget.h"
#include "applib/app_gtkmm_tools.h"
#include "applib/storage_device.h"
@@ -130,6 +131,13 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
}
/// Set the drive pointer for accessing text output when saving
void set_drive(const StorageDevicePtr& drive)
{
drive_ = drive;
}
protected:
@@ -163,6 +171,14 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
specific_filter->add_pattern("*.json");
specific_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> json_filter = Gtk::FileFilter::create();
json_filter->set_name(_("JSON Files"));
json_filter->add_pattern("*.json");
Glib::RefPtr<Gtk::FileFilter> txt_filter = Gtk::FileFilter::create();
txt_filter->set_name(_("Text Files"));
txt_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();
all_filter->set_name(_("All Files"));
all_filter->add_pattern("*");
@@ -175,6 +191,8 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), txt_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());
if (!last_dir.empty())
@@ -196,6 +214,8 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
dialog.set_do_overwrite_confirmation(true);
dialog.add_filter(specific_filter);
dialog.add_filter(json_filter);
dialog.add_filter(txt_filter);
dialog.add_filter(all_filter);
if (!last_dir.empty())
@@ -222,16 +242,31 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
#endif
rconfig::set_data("gui/drive_data_open_save_dir", last_dir);
bool txt_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == txt_filter->gobj();
if (file.extension() != ".json" && file.extension() != ".txt") {
file += ".json";
file += (txt_selected ? ".txt" : ".json");
}
bool save_txt = txt_selected || file.extension() == ".txt";
std::string text;
if (std::holds_alternative<std::string>(contents_)) {
text = std::get<std::string>(contents_);
} else {
text = std::get<Glib::ustring>(contents_);
}
// If saving as text and drive is available, try to get text-formatted output
if (save_txt && drive_) {
if (auto p = drive_->get_property_repository().lookup_property("smartctl/output"); !p.empty()) {
const std::string text_output = p.get_value<std::string>();
if (!text_output.empty()) {
text = text_output;
}
}
}
auto ec = hz::fs_file_put_contents(file, text);
if (ec) {
gui_show_error_dialog(_("Cannot save data to file"), ec.message(), this);
@@ -269,6 +304,8 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
std::string save_filename_; ///< Default filename for Save As
StorageDevicePtr drive_; ///< Drive pointer for accessing text output (optional)
};
+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
-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"