Compare commits

..
Author SHA1 Message Date
anthropic-code-agent[bot]andashaduri 893dfc75c8 Improve format detection: check output content for JSON vs text
- When smartctl/output property exists: both JSON and text available
- When no property and output starts with '{': JSON only (loaded JSON virtual drive)
- When no property and output doesn't start with '{': text only (loaded text virtual drive)
- Ensures correct filters shown for each scenario per requirements

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:32:06 +00:00
anthropic-code-agent[bot]andashaduri 4823af1d23 Fix data format detection logic for JSON vs Text
- Correctly detect when output is JSON (has smartctl/output property) vs text
- When smartctl/output property exists, output is JSON format
- When no property exists, output is text format (loaded virtual drive)
- Ensures filters are shown appropriately for available formats

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:31:00 +00:00
anthropic-code-agent[bot]andashaduri 81f21c9218 Implement JSON/Text save filters in View Output window
- Add separate JSON and Text file filters to save dialog
- Detect available data formats (JSON/Text) from storage device
- Handle user-selected filter to determine save format
- Extract text output from smartctl/output property when saving as txt
- Add set_storage_device() method to pass device context
- Default to JSON when both formats available (per requirements)

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:29:25 +00:00
anthropic-code-agent[bot] 4642c9bb13 Initial plan 2026-03-22 17:24:54 +00:00
5 changed files with 127 additions and 28 deletions
-6
View File
@@ -12,12 +12,6 @@
---
**SCAM ALERT:** gsmartcontrol[.]com is a phishing site! Please don't download anything from there!
Please always download GSmartControl from its official website [gsmartcontrol.shaduri.dev](https://gsmartcontrol.shaduri.dev) or the official GitHub repository [github.com/ashaduri/gsmartcontrol](https://github.com/ashaduri/gsmartcontrol).
---
[GSmartControl](https://gsmartcontrol.shaduri.dev)
is a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)
package), which is a tool for
-6
View File
@@ -13,12 +13,6 @@ nav_order: 1
---
**SCAM ALERT:** gsmartcontrol[.]com is a phishing site! Please don't download anything from there!
Please always download GSmartControl from its official website [gsmartcontrol.shaduri.dev](https://gsmartcontrol.shaduri.dev) or the official GitHub repository [github.com/ashaduri/gsmartcontrol](https://github.com/ashaduri/gsmartcontrol).
---
[GSmartControl](https://gsmartcontrol.shaduri.dev)
is a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)
package), which is a tool for
+3
View File
@@ -829,6 +829,9 @@ void GscInfoWindow::on_view_output_button_clicked()
if (!filename.empty())
win->set_save_filename(filename);
// Pass the storage device so the text window can access text/JSON data
win->set_storage_device(drive_);
win->show();
}
+1 -9
View File
@@ -554,18 +554,10 @@ Glib::RefPtr<Gdk::Pixbuf> GscMainWindowIconView::load_icon_pixbuf(Glib::RefPtr<G
{
Glib::RefPtr<Gdk::Pixbuf> icon;
// Try the icon theme first; fall back to the bundled icon if needed.
// Try XDG version first
try {
if (default_icon_theme && !xdg_icon_name.empty()) {
icon = default_icon_theme->load_icon(xdg_icon_name, icon_size_, get_scale_factor(), Gtk::IconLookupFlags(0));
// Some icon themes may return an icon smaller than requested.
// In that case, fall back to the bundled icon.
if (icon &&
(icon->get_width() < icon_size_ ||
icon->get_height() < icon_size_)) {
icon.reset();
}
}
} catch (...) { } // ignore exceptions
+123 -7
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 storage device to enable text/JSON format detection
void set_storage_device(std::shared_ptr<StorageDevice> device)
{
storage_device_ = std::move(device);
}
protected:
@@ -158,11 +166,59 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
}
int result = 0;
// Determine if we have JSON and/or text data available
bool has_json = false;
bool has_text = false;
if (storage_device_) {
// Check if we have text output in the property repository
bool has_text_property = false;
if (auto p = storage_device_->get_property_repository().lookup_property("smartctl/output"); !p.empty()) {
const std::string text_output = p.get_value<std::string>();
if (!text_output.empty()) {
has_text_property = true;
}
}
// Get the output - it could be JSON or text format
std::string output = storage_device_->get_full_output();
if (output.empty()) {
output = storage_device_->get_basic_output();
}
if (!output.empty()) {
// If we have text in property repo, the output is JSON with embedded text
if (has_text_property) {
has_json = true;
has_text = true;
} else {
// No text in property means either:
// 1. Output is JSON without embedded text, or
// 2. Output is text format (loaded text virtual drive)
// We check if output starts with '{' to detect JSON
if (!output.empty() && output[0] == '{') {
has_json = true;
} else {
has_text = true;
}
}
}
} else {
// If no storage device is set, we only have the contents (assume JSON for backward compatibility)
has_json = true;
}
Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();
specific_filter->set_name(_("JSON and Text Files"));
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("*");
@@ -174,7 +230,16 @@ 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());
// Add filters based on what data is available
if (has_json && has_text) {
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());
} else if (has_json) {
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj());
} else if (has_text) {
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())
@@ -195,7 +260,16 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
dialog.set_do_overwrite_confirmation(true);
dialog.add_filter(specific_filter);
// Add filters based on what data is available
if (has_json && has_text) {
dialog.add_filter(specific_filter);
dialog.add_filter(json_filter);
dialog.add_filter(txt_filter);
} else if (has_json) {
dialog.add_filter(json_filter);
} else if (has_text) {
dialog.add_filter(txt_filter);
}
dialog.add_filter(all_filter);
if (!last_dir.empty())
@@ -216,22 +290,62 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
#if GTK_CHECK_VERSION(3, 20, 0)
file = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));
last_dir = hz::fs_path_to_string(file.parent_path());
// Detect which filter was selected
bool txt_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == txt_filter->gobj();
bool json_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == json_filter->gobj();
#else
file = hz::fs_path_from_string(dialog.get_filename()); // in fs encoding
last_dir = dialog.get_current_folder(); // save for the future
// Detect which filter was selected
bool txt_selected = dialog.get_filter() == txt_filter;
bool json_selected = dialog.get_filter() == json_filter;
#endif
rconfig::set_data("gui/drive_data_open_save_dir", last_dir);
// Add extension if not present, based on selected filter
if (file.extension() != ".json" && file.extension() != ".txt") {
file += ".json";
if (txt_selected) {
file += ".txt";
} else if (json_selected) {
file += ".json";
} else {
// "JSON and Text Files" or "All Files" selected - use user-provided extension or default
if (has_json && !has_text) {
file += ".json";
} else if (has_text && !has_json) {
file += ".txt";
} else {
// Both available, default to JSON (per requirements)
file += ".json";
}
}
}
// Determine if we should save as text based on extension or filter
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 (save_txt && storage_device_) {
// Try to get text output from property repository
if (auto p = storage_device_->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;
}
}
}
// If we didn't get text output or not saving as text, use the contents
if (text.empty()) {
if (std::holds_alternative<std::string>(contents_)) {
text = std::get<std::string>(contents_);
} else {
text = std::get<Glib::ustring>(contents_);
}
}
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 +383,8 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
std::string save_filename_; ///< Default filename for Save As
std::shared_ptr<StorageDevice> storage_device_; ///< Storage device for accessing text/JSON data
};