/* * This file is part of the PulseView project. * * Copyright (C) 2012-14 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "devicemanager.hpp" #include "mainwindow.hpp" #include "session.hpp" #include "data/analog.hpp" #include "data/analogsegment.hpp" #include "data/decode/decoder.hpp" #include "data/logic.hpp" #include "data/logicsegment.hpp" #include "data/signalbase.hpp" #include "devices/hardwaredevice.hpp" #include "devices/inputfile.hpp" #include "devices/sessionfile.hpp" #include "toolbars/mainbar.hpp" #include "views/trace/analogsignal.hpp" #include "views/trace/decodetrace.hpp" #include "views/trace/logicsignal.hpp" #include "views/trace/signal.hpp" #include "views/trace/view.hpp" #include #ifdef ENABLE_DECODE #include #include "data/decodesignal.hpp" #endif using std::bad_alloc; using std::dynamic_pointer_cast; using std::find_if; using std::function; using std::lock_guard; using std::list; using std::make_pair; using std::make_shared; using std::map; using std::max; using std::move; using std::mutex; using std::pair; using std::recursive_mutex; using std::runtime_error; using std::shared_ptr; using std::string; using std::unique_ptr; using std::unordered_set; using std::vector; using sigrok::Analog; using sigrok::Channel; using sigrok::ConfigKey; using sigrok::DatafeedCallbackFunction; using sigrok::Error; using sigrok::InputFormat; using sigrok::Logic; using sigrok::Meta; using sigrok::Packet; using sigrok::Session; using Glib::VariantBase; namespace pv { shared_ptr Session::sr_context; Session::Session(DeviceManager &device_manager, QString name) : device_manager_(device_manager), default_name_(name), name_(name), capture_state_(Stopped), cur_samplerate_(0), data_saved_(true) { } Session::~Session() { // Stop and join to the thread stop_capture(); } DeviceManager& Session::device_manager() { return device_manager_; } const DeviceManager& Session::device_manager() const { return device_manager_; } shared_ptr Session::session() const { if (!device_) return shared_ptr(); return device_->session(); } shared_ptr Session::device() const { return device_; } QString Session::name() const { return name_; } void Session::set_name(QString name) { if (default_name_.isEmpty()) default_name_ = name; name_ = name; name_changed(); } const list< shared_ptr > Session::views() const { return views_; } shared_ptr Session::main_view() const { return main_view_; } void Session::set_main_bar(shared_ptr main_bar) { main_bar_ = main_bar; } shared_ptr Session::main_bar() const { return main_bar_; } bool Session::data_saved() const { return data_saved_; } void Session::save_settings(QSettings &settings) const { map dev_info; list key_list; int decode_signals = 0, views = 0; if (device_) { shared_ptr hw_device = dynamic_pointer_cast< devices::HardwareDevice >(device_); if (hw_device) { settings.setValue("device_type", "hardware"); settings.beginGroup("device"); key_list.emplace_back("vendor"); key_list.emplace_back("model"); key_list.emplace_back("version"); key_list.emplace_back("serial_num"); key_list.emplace_back("connection_id"); dev_info = device_manager_.get_device_info(device_); for (string key : key_list) { if (dev_info.count(key)) settings.setValue(QString::fromUtf8(key.c_str()), QString::fromUtf8(dev_info.at(key).c_str())); else settings.remove(QString::fromUtf8(key.c_str())); } settings.endGroup(); } shared_ptr sessionfile_device = dynamic_pointer_cast< devices::SessionFile >(device_); if (sessionfile_device) { settings.setValue("device_type", "sessionfile"); settings.beginGroup("device"); settings.setValue("filename", QString::fromStdString( sessionfile_device->full_name())); settings.endGroup(); } // Save channels and decoders for (shared_ptr base : signalbases_) { #ifdef ENABLE_DECODE if (base->is_decode_signal()) { settings.beginGroup("decode_signal" + QString::number(decode_signals++)); base->save_settings(settings); settings.endGroup(); } else #endif { settings.beginGroup(base->internal_name()); base->save_settings(settings); settings.endGroup(); } } settings.setValue("decode_signals", decode_signals); // Save view states and their signal settings // Note: main_view must be saved as view0 settings.beginGroup("view" + QString::number(views++)); main_view_->save_settings(settings); settings.endGroup(); for (shared_ptr view : views_) { if (view != main_view_) { settings.beginGroup("view" + QString::number(views++)); view->save_settings(settings); settings.endGroup(); } } settings.setValue("views", views); } } void Session::restore_settings(QSettings &settings) { shared_ptr device; QString device_type = settings.value("device_type").toString(); if (device_type == "hardware") { map dev_info; list key_list; // Re-select last used device if possible but only if it's not demo settings.beginGroup("device"); key_list.emplace_back("vendor"); key_list.emplace_back("model"); key_list.emplace_back("version"); key_list.emplace_back("serial_num"); key_list.emplace_back("connection_id"); for (string key : key_list) { const QString k = QString::fromStdString(key); if (!settings.contains(k)) continue; const string value = settings.value(k).toString().toStdString(); if (!value.empty()) dev_info.insert(make_pair(key, value)); } if (dev_info.count("model") > 0) device = device_manager_.find_device_from_info(dev_info); if (device) set_device(device); settings.endGroup(); } if (device_type == "sessionfile") { settings.beginGroup("device"); QString filename = settings.value("filename").toString(); settings.endGroup(); if (QFileInfo(filename).isReadable()) { device = make_shared(device_manager_.context(), filename.toStdString()); set_device(device); start_capture([](QString infoMessage) { // TODO Emulate noquote() qDebug() << "Session error:" << infoMessage; }); set_name(QFileInfo(filename).fileName()); } } if (device) { // Restore channels for (shared_ptr base : signalbases_) { settings.beginGroup(base->internal_name()); base->restore_settings(settings); settings.endGroup(); } // Restore decoders #ifdef ENABLE_DECODE int decode_signals = settings.value("decode_signals").toInt(); for (int i = 0; i < decode_signals; i++) { settings.beginGroup("decode_signal" + QString::number(i)); shared_ptr signal = add_decode_signal(); signal->restore_settings(settings); settings.endGroup(); } #endif // Restore views int views = settings.value("views").toInt(); for (int i = 0; i < views; i++) { settings.beginGroup("view" + QString::number(i)); if (i > 0) { views::ViewType type = (views::ViewType)settings.value("type").toInt(); add_view(name_, type, this); views_.back()->restore_settings(settings); } else main_view_->restore_settings(settings); settings.endGroup(); } } } void Session::select_device(shared_ptr device) { try { if (device) set_device(device); else set_default_device(); } catch (const QString &e) { MainWindow::show_session_error(tr("Failed to select device"), e); } } void Session::set_device(shared_ptr device) { assert(device); // Ensure we are not capturing before setting the device stop_capture(); if (device_) device_->close(); device_.reset(); // Revert name back to default name (e.g. "Session 1") as the data is gone name_ = default_name_; name_changed(); // Remove all stored data for (shared_ptr view : views_) { view->clear_signals(); #ifdef ENABLE_DECODE view->clear_decode_signals(); #endif } for (const shared_ptr d : all_signal_data_) d->clear(); all_signal_data_.clear(); signalbases_.clear(); cur_logic_segment_.reset(); for (auto entry : cur_analog_segments_) { shared_ptr(entry.first).reset(); shared_ptr(entry.second).reset(); } logic_data_.reset(); signals_changed(); device_ = move(device); try { device_->open(); } catch (const QString &e) { device_.reset(); MainWindow::show_session_error(tr("Failed to open device"), e); } if (device_) { device_->session()->add_datafeed_callback([=] (shared_ptr device, shared_ptr packet) { data_feed_in(device, packet); }); update_signals(); } device_changed(); } void Session::set_default_device() { const list< shared_ptr > &devices = device_manager_.devices(); if (devices.empty()) return; // Try and find the demo device and select that by default const auto iter = find_if(devices.begin(), devices.end(), [] (const shared_ptr &d) { return d->hardware_device()->driver()->name() == "demo"; }); set_device((iter == devices.end()) ? devices.front() : *iter); } /** * Convert generic options to data types that are specific to InputFormat. * * @param[in] user_spec Vector of tokenized words, string format. * @param[in] fmt_opts Input format's options, result of InputFormat::options(). * * @return Map of options suitable for InputFormat::create_input(). */ map Session::input_format_options(vector user_spec, map> fmt_opts) { map result; for (auto entry : user_spec) { /* * Split key=value specs. Accept entries without separator * (for simplified boolean specifications). */ string key, val; size_t pos = entry.find("="); if (pos == std::string::npos) { key = entry; val = ""; } else { key = entry.substr(0, pos); val = entry.substr(pos + 1); } /* * Skip user specifications that are not a member of the * format's set of supported options. Have the text input * spec converted to the required input format specific * data type. */ auto found = fmt_opts.find(key); if (found == fmt_opts.end()) continue; shared_ptr