diff --git a/e_bash/Channel.cpp b/e_bash/Channel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fc438e1b96bac9960ce5e7d474f457ac2220c00b --- /dev/null +++ b/e_bash/Channel.cpp @@ -0,0 +1,224 @@ +// +// Channel.cpp +// e_bash +// +// Created by Arnaud Blanchard on 29/09/2019. +// + +#include "Channel.hpp" +#include "blc_channel.h" +#include "string" +#include <vector> +#include <vte/vte.h> +#include <thread> + + +using namespace Gtk; +using namespace std; + +struct Process{ + pid_t pid; + + char **argv; + string name; + string program_name; + string channel; + + void run(); + void watch(); +}; + +void Process::watch(){ + char pipe_buffer[PIPE_SIZE]; + ssize_t pipe_read_nb=0, pipe_buffer_writen_nb=0; + int fd_max, status; + ssize_t size=-1, written_nb, read_nb; + fd_set fd_read_set, fd_write_set; + + fd_max=MAX(stdout_fileno, stderr_fileno); + // if (input_process) fd_max=MAX(fd_max, input_process->stdout_fileno); + // fd_max=MAX(fd_max, stdin_fileno); + + do + { + size=0; + FD_ZERO(&fd_read_set); + // FD_ZERO(&fd_write_set); + + //if (input_process) FD_SET(input_process->stdout_fileno, &fd_read_set); + FD_SET(stdout_fileno, &fd_read_set); + FD_SET(stderr_fileno, &fd_read_set); + // if (stdin_fileno!=-1 && pipe_read_nb) FD_SET(stdin_fileno, &fd_write_set); + + SYSTEM_ERROR_CHECK(::select(fd_max+1, &fd_read_set, NULL, NULL, NULL), -1, "process: '%s' '%s'", name, program->name); + + /* if (stdin_fileno!=-1 && pipe_read_nb && FD_ISSET(stdin_fileno, &fd_write_set)){ //something to write and input ready; + SYSTEM_ERROR_CHECK(written_nb=write(stdin_fileno, &pipe_buffer[pipe_buffer_writen_nb], pipe_read_nb-pipe_buffer_writen_nb),-1, "pipe_buffer_writen_nb '%ld', pipe_read_nb '%ld'", pipe_buffer_writen_nb, pipe_read_nb); + SYSTEM_ERROR_CHECK(written_nb=write(STDERR_FILENO, &pipe_buffer[pipe_buffer_writen_nb], pipe_read_nb-pipe_buffer_writen_nb),-1, "pipe_buffer_writen_nb '%ld', pipe_read_nb '%ld'", pipe_buffer_writen_nb, pipe_read_nb); + + pipe_buffer_writen_nb+=written_nb; + size+=written_nb; + } + + if (input_process && FD_ISSET(input_process->stdout_fileno, &fd_read_set)){ + SYSTEM_ERROR_CHECK(read_nb=read(input_process->stdout_fileno, &pipe_buffer[pipe_read_nb], sizeof(pipe_buffer)-pipe_read_nb), -1, "input pipe"); + pipe_read_nb+=read_nb; + size+=read_nb; + } + + if (pipe_read_nb && pipe_buffer_writen_nb==pipe_read_nb){ //Everything has been written + pipe_buffer_writen_nb=0; + pipe_read_nb=0; + }*/ + + if (FD_ISSET(stdout_fileno, &fd_read_set)){ + SYSTEM_ERROR_CHECK(size=read(stdout_fileno, pipe_buffer, sizeof(pipe_buffer)), -1, "stdout"); + if (output_process) SYSTEM_ERROR_CHECK(write(next_process_pipe[1], pipe_buffer, size), -1, "next_process_pipe"); + buffer_to_vte_terminal(pipe_buffer, size, stdout_terminal); + } + + if (FD_ISSET(stderr_fileno, &fd_read_set)){ + SYSTEM_ERROR_CHECK(size=read(stderr_fileno, pipe_buffer, sizeof(pipe_buffer)), -1, "stderr"); + buffer_to_vte_terminal(pipe_buffer, size, stderr_terminal); + } + } + while(size!=0); + + close(stdout_fileno); + close(stderr_fileno); + + waitpid(pid, &status, 0); + pid=-1; + + g_signal_handler_disconnect(G_OBJECT(stderr_terminal), commit_handler); + + // if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(err_toggle_button))) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(err_toggle_button), false); + + gdk_threads_add_idle (thread_safe_end_process, this); +} + +void Process_old::run(){ + int stdin_pipe[2], stderr_pipe[2], stdout_pipe[2]; + + SYSTEM_ERROR_CHECK(pipe(stdin_pipe), -1, NULL); + SYSTEM_ERROR_CHECK(pipe(stdout_pipe), -1, NULL); + SYSTEM_ERROR_CHECK(pipe(stderr_pipe), -1, NULL); + + pid=fork(); + + if (pid==0){ + /*Connection to virtual termainals*/ + SYSTEM_ERROR_RETRY_ON_SPECIFIC_ERRNO(dup2(stdin_pipe[0] , STDIN_FILENO ), -1, EINTR, NULL); + SYSTEM_ERROR_RETRY_ON_SPECIFIC_ERRNO(dup2(stdout_pipe[1], STDOUT_FILENO), -1, EINTR, NULL); + SYSTEM_ERROR_RETRY_ON_SPECIFIC_ERRNO(dup2(stderr_pipe[1], STDERR_FILENO), -1, EINTR, NULL); + + blc_close_pipe(stdin_pipe); + blc_close_pipe(stdout_pipe); + blc_close_pipe(stderr_pipe); + SYSTEM_ERROR_CHECK(execvp(program_name.data(), argv), -1, "Executing program '%s'", program_name.data()); + } + close(stdin_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + thread thread([](){ + watch(); + }); + + //commit_handler=g_signal_connect(G_OBJECT(stderr_terminal), "commit", G_CALLBACK(input_cp), this); +} + + +struct ModelColumns : public TreeModel::ColumnRecord{ + Gtk::TreeModelColumn<string> name, type, format, dimensions, icon; + + ModelColumns(){ + add(name); + add(type); + add(format); + add(dimensions); + add(icon); + } + + void add_channel( Glib::RefPtr<Gtk::ListStore> &list, blc_channel *channel){ + uint32_t type_str, format_str; + char dims_str[256]; + + auto row = *(list->append()); //* is because it is an iterator and we want the content of the ref. + row[name] = channel->name; + row[type] = string(UINT32_TO_STRING(type_str, channel->type), 4); + row[format] = string(UINT32_TO_STRING(format_str, channel->format), 4); + channel->sprint_dims(dims_str, 256); + row[dimensions] = dims_str; + row[icon] = Gtk::Stock::YES.id; //dialog-information"; + } +}m_Columns; + +static void refresh_channels(void* user_data){ + + auto channel_widget = (ChannelWidget *)user_data; + Glib::signal_idle().connect([&channel_widget](){ //important to use an idle otherwise Thread problems + channel_widget->refresh_channel_list(); + return false; + }); +} + +ChannelWidget::ChannelWidget(){ + Gtk::CellRendererPixbuf cellRendererPixBuf; + + list_store = Gtk::ListStore::create(m_Columns); + set_model(list_store); + + append_column("Name", m_Columns.name); + append_column("Type", m_Columns.type); + append_column("Format", m_Columns.format); + append_column("Dimensions", m_Columns.dimensions); + + int numcols = append_column("image ", cellRendererPixBuf); + get_column(numcols-1)->add_attribute(cellRendererPixBuf, "stock-id", m_Columns.icon); + + refresh_channel_list(); + blc_channel_check_for_event(refresh_channels, this); + show_all_children(); + + signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){ + vector <char*> argv_vec; + Process *process = new Process(); + Gtk::TreeIter iter=this->list_store->get_iter(path); + Gtk::TreeModel::Row row = *iter; + + process->program_name="o_gnuplot"; + process->name=process->program_name; + process->channel=((string)row[m_Columns.name]).data(); + + fprintf(stderr, "Row %s\n", ((string)row[m_Columns.name]).data()); + + argv_vec.emplace_back((char*)process->program_name.data()); + argv_vec.emplace_back((char*)process->channel.data()); + argv_vec.emplace_back(nullptr); + process->argv=argv_vec.data(); + process->run(); + + return false; + }); + + + signal_button_release_event().connect([](GdkEventButton* event){ + if (event->button == 3){ + exit(1); + }; + return false; + }); +} + +void ChannelWidget::refresh_channel_list(){ + int channels_nb; + blc_channel *channel_infos=nullptr; + channels_nb = blc_channel_get_all_infos(&channel_infos); + + list_store->clear(); + for(blc_channel *channel=channel_infos; channel != channel_infos+channels_nb; channel++){ + m_Columns.add_channel(list_store, channel); + } + free(channel_infos); +} diff --git a/gtk_channels/Channel.hpp b/e_bash/Channel.hpp similarity index 60% rename from gtk_channels/Channel.hpp rename to e_bash/Channel.hpp index 098345b5d24f681c1aa144fdae66834a0141c3b6..20471e8f356127328664c2ade8371fcdc68b6d6b 100644 --- a/gtk_channels/Channel.hpp +++ b/e_bash/Channel.hpp @@ -10,16 +10,10 @@ #include <gtkmm.h> - - struct ChannelWidget:Gtk::TreeView{ - std::string blaar_dir; - Glib::RefPtr<Gtk::TreeStore> tree_store; + Glib::RefPtr<Gtk::ListStore> list_store; ChannelWidget(); - void add_process(Gtk::TreeModel::Row &row); - void display_process(Gtk::TreeModel::Row &row); - void refresh_channel_list(); }; #endif /* Channel_hpp */ diff --git a/e_bash/Plot.cpp b/e_bash/Plot.cpp new file mode 100644 index 0000000000000000000000000000000000000000..64975352edded099353005281bc3fea77bec7900 --- /dev/null +++ b/e_bash/Plot.cpp @@ -0,0 +1,18 @@ +// +// Plot.cpp +// e_bash +// +// Created by Arnaud Blanchard on 30/09/2019. +// + +#include "Plot.hpp" + + + + + + +Plot::Plot(){ + + +} diff --git a/e_bash/Plot.hpp b/e_bash/Plot.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1bd8f03a02e9d35cef83a4660d44759a36a0feb7 --- /dev/null +++ b/e_bash/Plot.hpp @@ -0,0 +1,24 @@ +// +// Plot.hpp +// e_bash +// +// Created by Arnaud Blanchard on 30/09/2019. +// + +#ifndef Plot_hpp +#define Plot_hpp + +#include <stdio.h> +#include <gtkmm-plplot.h> + + +struct Plot{ + Gtk::PLplot::Plot2D plot; + Gtk::PLplot::Canvas canvas(plot); + + Plot(); + // plot. +}; + + +#endif /* Plot_hpp */ diff --git a/gtk_channels/CMakeLists.txt b/gtk_channels/CMakeLists.txt index b2f390d9aca6d06b39548a201640bb6dab194f64..c57fe921f1b61e3413739c395d22f158e5df04e8 100644 --- a/gtk_channels/CMakeLists.txt +++ b/gtk_channels/CMakeLists.txt @@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 2.6) project(gtk_channels) find_package(blc_channel REQUIRED) +find_package(blc_image REQUIRED) find_package(blc_program REQUIRED) find_package(blc_process REQUIRED) @@ -17,5 +18,5 @@ pkg_check_modules(VTE3 vte-2.91 REQUIRED) #libvtemm is not yet common add_definitions(-std=c++14 ${BL_DEFINITIONS} ${GTK3_CFLAGS_OTHER} ) include_directories(${BL_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS} ${GTK3_SOURCE_VIEW_INCLUDE_DIRS} ${VTE_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${VTE3_INCLUDE_DIRS}) link_directories(${GTK3_LIBRARY_DIRS} ${VTE_LIBRARY_DIRS} /usr/local/lib) -add_executable(gtk_channels gtk_channels.cpp Channel.cpp Process.cpp parse_help.cpp) +add_executable(gtk_channels gtk_channels.cpp ChannelWidget.cpp ContextMenu.cpp ChannelColumns.cpp Process.cpp parse_help.cpp) target_link_libraries(gtk_channels ${BL_LIBRARIES} ${GTK3_LIBRARIES} ${GTK3_SOURCE_VIEW_LIBRARIES} ${VTE_LIBRARIES} ${GTKMM_LIBRARIES} ${VTE3_LIBRARIES}) diff --git a/gtk_channels/Channel.cpp b/gtk_channels/Channel.cpp deleted file mode 100644 index f502fcb26689a680179bcd85406279dd9a53ed1c..0000000000000000000000000000000000000000 --- a/gtk_channels/Channel.cpp +++ /dev/null @@ -1,224 +0,0 @@ -// -// Channel.cpp -// e_bash -// -// Created by Arnaud Blanchard on 29/09/2019. -// - -#include "Process.h" -#include "Channel.hpp" -#include "blc_channel.h" -#include "string" -#include <vector> -#include <forward_list> - -#include <vte/vte.h> -#include <thread> -#include <iostream> - -using namespace Gtk; -using namespace Glib; //signal_idle,RefPtr - -using namespace std; - -vector <char*> argv_vec; - - -struct ChannelMenu:Gtk::Menu{ - ChannelMenu(Gtk::TreeView &tree_view); - -}; - - - - -struct ModelColumns : public TreeModel::ColumnRecord{ - TreeModelColumn<int> id, pid; - TreeModelColumn<string> name, type, format, dimensions, icon; - TreeModelColumn<Process*> process; - - - ModelColumns(){ - add(id); - add(name); - add(type); - add(format); - add(dimensions); - add(icon); - add(pid); - add(process); - } - - void add_channel(RefPtr<TreeStore> &tree, blc_channel *channel){ - uint32_t type_str, format_str; - char dims_str[256]; - TreeModel::Row childrow; - - auto row = *(tree->append()); //* is because it is an iterator and we want the content of the ref. - //row[id] = channel->id; - row[name] = channel->name; - row[type] = string(UINT32_TO_STRING(type_str, channel->type), 4); - row[format] = string(UINT32_TO_STRING(format_str, channel->format), 4); - channel->sprint_dims(dims_str, 256); - row[dimensions] = dims_str; - row[process]=nullptr; - row[id]=channel->id; - - // row[icon] = Gtk::Stock::YES.id; //dialog-information"; - } -}m_Columns; - -static void refresh_channels(void* user_data){ - - auto channel_widget = (ChannelWidget *)user_data; - signal_idle().connect([channel_widget](){ //important to use an idle otherwise Thread problems - channel_widget->refresh_channel_list(); - return false; - }); -} - -ChannelMenu::ChannelMenu(TreeView &tree_view){ - MenuItem *item = new MenuItem("_Remove", true); - append(*item); - accelerate(*this); - show_all(); - item->signal_activate().connect([&tree_view](){ - tree_view.get_selection()->selected_foreach_iter([](const TreeModel::iterator& iter){ - TreeModel::Row row = *iter; - string name= row[m_Columns.name]; - blc_remove_channel_with_name(name.data()); - });; - }); -} - - -void ChannelWidget::add_process( TreeModel::Row &row){ - TreeModel::Row childrow; - string command; - - Process *process = new Process(blaar_dir+"/bin/", "o_gnuplot"); - process->add_arg(((string)row[m_Columns.name]).data()); - process->run(); - - command=blc_program_name; - - for (auto arg = process->arg_vector.begin()+1; arg!=process->arg_vector.end()-1; arg++){ - command+=" "; - command+=*arg; - } - - childrow = *(tree_store->append(row.children())); - childrow[m_Columns.name]=command; - childrow[m_Columns.icon]="utilities-terminal"; - childrow[m_Columns.process]= process; - show_all_children(); -} - -void ChannelWidget::display_process( TreeModel::Row &row){ - Process *process=row[m_Columns.process]; - - process->display_terminal(); -} - - -ChannelWidget::ChannelWidget(){ - CellRendererPixbuf cellRendererPixBuf; - - this->set_enable_search(); - // this->m - - blaar_dir=getenv("BLAAR_DIR"); - if (blaar_dir.empty()) EXIT_ON_ERROR("Your environment variable 'BLAAR_DIR' is not defined."); - - tree_store = TreeStore::create(m_Columns); - set_model(tree_store); - - append_column("Name", m_Columns.name); - append_column("Type", m_Columns.type); - append_column("Format", m_Columns.format); - append_column("Dimensions", m_Columns.dimensions); - - int numcols = append_column("image ", cellRendererPixBuf); - get_column(numcols-1)->add_attribute(cellRendererPixBuf, "icon-name", m_Columns.icon); - - refresh_channel_list(); - blc_channel_check_for_event(refresh_channels, this); - get_selection()->set_mode(SELECTION_MULTIPLE); - show_all_children(); - - // set_events(Gdk::BUTTON_PRESS_MASK); - - signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){ - TreeIter iter=this->tree_store->get_iter(path); - TreeModel::Row row = *iter; - if (tree_store->iter_depth(iter) == 0) this->add_process(row); - else display_process(row); - }); - - get_selection()->signal_changed().connect([](){ - return true; - }); - - signal_button_release_event().connect([this](GdkEventButton const *event){ - - if (event->button == 3){ - ChannelMenu *m_Menu_Popup = new ChannelMenu(*this); - m_Menu_Popup->popup_at_pointer((GdkEvent*)event); - - } - return true; - }); -} - -/* -void ChannelWidget::menu_add_program_item(Menu &menu, string name, string program){ - MenuItem *item = new MenuItem(name); - - item->signal_activate().connect([this](){ - auto refSelection = get_selection(); - if (refSelection) { - TreeModel::iterator iter = refSelection->get_selected(); - TreeModel::Row row = *iter; - for (TreeModel::iterator iter = refSelection-> (); ){ - - string name= row[m_Columns.name]; - blc_remove_channel_with_name(name.data()); - } - } - }); - menu.append(*item); -};*/ - -void ChannelWidget::refresh_channel_list(){ - bool found; - int channels_nb; - blc_channel *channel_infos=nullptr; - channels_nb = blc_channel_get_all_infos(&channel_infos); - list<TreeModel::iterator> list_iters_without_channels; - - /* Generate a list of iter on row to be able to remove the row if the channel does not exist anymore*/ - tree_store->foreach_iter([&list_iters_without_channels, this](TreeModel::iterator const &iter){ - if (this->tree_store->iter_depth(iter)==0) list_iters_without_channels.push_back(iter); //copy iter - return false; - }); - - for(blc_channel *channel=channel_infos; channel != channel_infos+channels_nb; channel++){ - found=false; - auto list_iter=list_iters_without_channels.begin(); - while(list_iter != list_iters_without_channels.end()){ - TreeModel::Row row = *(*list_iter); - if (row[m_Columns.id]==channel->id){ - found=true; - list_iter=list_iters_without_channels.erase(list_iter); - } - else list_iter++; - } - - if (found==false) m_Columns.add_channel(tree_store, channel); - } - for(auto list_iter: list_iters_without_channels){ - tree_store->erase(*list_iter); - } - this->show_all_children(); - free(channel_infos); -} diff --git a/gtk_channels/ChannelColumns.cpp b/gtk_channels/ChannelColumns.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd1ce5ba817eece9f6270aac27953cf5b53c654a --- /dev/null +++ b/gtk_channels/ChannelColumns.cpp @@ -0,0 +1,42 @@ +// +// ChannelColumns.cpp +// gtk_channels +// +// Created by Arnaud Blanchard on 13/10/2019. +// + +#include "ChannelColumns.hpp" + +using namespace Glib; +using namespace Gtk; +using namespace std; + +ChannelColumns::ChannelColumns(){ + add(id); + add(name); + add(type); + add(format); + add(dimensions); + add(terminal_icon); + add(play_pause_icon); + add(play_pause_sensitive); + add(stop_icon); + add(stop_sensitive); + add(pid); + add(process); +} + +void ChannelColumns::add_channel(RefPtr<TreeStore> tree, blc_channel const &channel){ + uint32_t type_str, format_str; + char dims_str[256]; + TreeModel::Row childrow; + + auto row = *(tree->append()); //* is because it is an iterator and we want the content of the ref. + row[name] = channel.name; + row[type] = string(UINT32_TO_STRING(type_str, channel.type), 4); + row[format] = string(UINT32_TO_STRING(format_str, channel.format), 4); + channel.sprint_dims(dims_str, 256); + row[dimensions] = dims_str; + row[process]=nullptr; + row[id]=channel.id; +} diff --git a/gtk_channels/ChannelColumns.hpp b/gtk_channels/ChannelColumns.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ee45e535eae3705ef8bde5efce2abd6f52f1376f --- /dev/null +++ b/gtk_channels/ChannelColumns.hpp @@ -0,0 +1,32 @@ +// +// ChannelColumns.hpp +// gtk_channels +// +// Created by Arnaud Blanchard on 13/10/2019. +// + +#ifndef ChannelColumns_hpp +#define ChannelColumns_hpp + +#include "blc_channel.h" +#include "Process.h" + +#include <stdio.h> +#include <string> +#include <gtkmm.h> + +struct ChannelWidget; + +struct ChannelColumns : public Gtk::TreeModel::ColumnRecord{ + Gtk::TreeModelColumn<int> id, pid; + Gtk::TreeModelColumn<bool> play_pause_sensitive, stop_sensitive; + Gtk::TreeModelColumn<std::string> name, type, format, dimensions, terminal_icon, play_pause_icon, stop_icon; + Gtk::TreeModelColumn<Process*> process; + + ChannelWidget *channel_widget; + + ChannelColumns(); + void add_channel(Glib::RefPtr<Gtk::TreeStore> tree, blc_channel const &channel); +}; + +#endif /* ChannelColumns_hpp */ diff --git a/gtk_channels/ChannelWidget.cpp b/gtk_channels/ChannelWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ee4a0286c4ad244631b404529306b540aebd6b80 --- /dev/null +++ b/gtk_channels/ChannelWidget.cpp @@ -0,0 +1,231 @@ +// +// Channel.cpp +// e_bash +// +// Created by Arnaud Blanchard on 29/09/2019. +// + +#include "ChannelColumns.hpp" +#include "Process.h" +#include "ChannelWidget.hpp" +#include "blc_channel.h" +#include "blc_image.h" + +#include <string> +#include <vector> +#include <forward_list> + +#include <vte/vte.h> +#include <thread> +#include <iostream> +#include <numeric> + +using namespace Gtk; +using namespace Glib; //signal_idle,RefPtr +using namespace std; + +static void refresh_channels(void* user_data){ + auto channel_widget = (ChannelWidget *)user_data; + signal_idle().connect([channel_widget](){ //important to use an idle otherwise Thread problems + channel_widget->refresh_channel_list(); + return false; + }); +} + +ChannelColumns ChannelWidget::columns; + +ChannelWidget::ChannelWidget(){ + char const *blaar_dir=getenv("BLAAR_DIR"); + if (blaar_dir==nullptr) EXIT_ON_ERROR("Your environment variable 'BLAAR_DIR' is not defined."); + + program_dir=blaar_dir; + program_dir+="/bin"; + + append_column("Name", columns.name); + append_column("Type", columns.type); + append_column("Format", columns.format); + append_column("Dimensions", columns.dimensions); + + tree_store = TreeStore::create(columns); + set_model(tree_store); + set_activate_on_single_click(); + + TreeViewColumn *col = new TreeViewColumn(" ", terminal_cell_renderer); + col->add_attribute(terminal_cell_renderer, "icon-name", columns.terminal_icon); + append_column(*col); + + col = new TreeViewColumn(" "); + col->pack_start(play_pause_cell_renderer); + col->pack_start(stop_cell_renderer); + + col->add_attribute(play_pause_cell_renderer, "icon-name", columns.play_pause_icon); + col->add_attribute(stop_cell_renderer, "icon-name", columns.stop_icon); + col->add_attribute(play_pause_cell_renderer, "sensitive", columns.play_pause_sensitive); + col->add_attribute(stop_cell_renderer, "sensitive", columns.stop_sensitive); + append_column(*col); + + signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){ + int x, y; + TreeIter iter=this->tree_store->get_iter(path); + TreeModel::Row row = *iter; + + Process *process=row[columns.process]; + + if (tree_store->iter_depth(iter) != 0){ //It is a process + if (column->get_cell_position(terminal_cell_renderer, x, y)){//x, y are not used but it allow to chack the existanace of the cell renderer + process->display_terminal(); + } + else if (column->get_cell_position(play_pause_cell_renderer, x, y)){ + process->signal(SIGTSTP); + } + } + }); + + signal_button_release_event().connect([this](GdkEventButton const *event){ + if (event->button == 3){ + ContextMenu *menu=create_menu(); + menu->popup_at_pointer((GdkEvent*)event); + } + return true; + }); + + refresh_channel_list(); + blc_channel_check_for_event(refresh_channels, this); + get_selection()->set_mode(SELECTION_MULTIPLE); + show_all_children(); +} + +blc_channel *ChannelWidget::find_channel(int id){ + blc_channel *channel; + channel= lower_bound(channels, channels+channels_nb, id, [](blc_channel const &a, int id){ + return (a.id < id); + }); + return channel; +} + +void ChannelWidget::after_launch(Process *process, std::vector<TreeModel::Path> rows){ + auto process_rows=make_shared<vector<TreeIter>>(); + string command = accumulate(process->arg_vector.begin()+1, process->arg_vector.end()-1, process->program_name, [](auto const &a, auto const &b){ + return a+" "+b; + }); + + for (auto const &path : rows){ + TreeNodeChildren node = tree_store->get_iter(path)->children(); + TreeIter row_iter=tree_store->append(node); + process_rows->emplace_back(row_iter); + TreeRow row=*row_iter; + row[columns.name]=command; + row[columns.terminal_icon]="utilities-terminal"; + row[columns.play_pause_icon]="media-playback-pause"; + row[columns.play_pause_sensitive]=true; + + row[columns.stop_icon]="media-playback-stop"; + row[columns.stop_sensitive]=true; + + row[columns.process]= process; + expand_row(path, true); + // process_rows->push_back(row_iter); + } + + process->on_quit([process_rows](int status){ + for (auto &row_iter : *process_rows){ + TreeRow row=*row_iter; + + // row[columns.name]=command; + // row[columns.terminal_icon]="utilities-terminal"; + if (status==0) row[columns.terminal_icon]="process-stop"; + else row[columns.terminal_icon]="dialog-error"; + row[columns.play_pause_icon]="media-playback-start"; + row[columns.stop_sensitive]=false; + } + }); + show_all_children(); +} + +void ChannelWidget::on_quit(Process *process, int status, std::vector<TreeModel::Path> rows){ + for (auto const &path : rows){ + TreeRow row = *tree_store->get_iter(path); + // row[columns.name]=command; + // row[columns.terminal_icon]="utilities-terminal"; + if (status==0) row[columns.terminal_icon]="process-stop"; + else row[columns.terminal_icon]="dialog-error"; + row[columns.stop_icon]="media-playback-start"; + expand_row(path, true); + } +} + +ContextMenu *ChannelWidget::create_menu(){ + bool only_channels=true, can_keyboard=true, can_image=true, can_graph=true, can_history_graph=true; + menu=new ContextMenu(program_dir); + + menu->row_selection=get_selection(); + menu->row_selection->selected_foreach_iter ([&](TreeModel::iterator const &iter){ + if (tree_store->iter_depth(iter)==0){ + blc_channel *channel=find_channel((*iter)[columns.id]); + if (channel->total_length > 64) can_keyboard=false; + if (blc_image_get_bytes_per_pixel(channel)==0) can_image=false; + if (channel->dims_nb > 1) can_history_graph=false; + else if (channel->dims_nb > 2) can_graph=false; + menu->selected_channels.emplace_back(channel); + } + else only_channels=false; + }); + + if (only_channels){ + menu->add_item("_Remove", true, [this]{ + for( auto &channel : menu->selected_channels){ + channel->remove(); + } + }); + menu->add_separator(); + + Glib::RefPtr<TreeModel> tree_model=tree_store; + + /* This is the simpler I can do for a callback + It means this->after_launch( first_argument (i.e. process)) will be called.*/ + auto after_launch_callback=bind(&ChannelWidget::after_launch, this, placeholders::_1, menu->row_selection->get_selected_rows(tree_model));//This is a way to pass the rows, without process needing to know what is row + if (can_keyboard) menu->add_program("keyboard", "i_keyboard", {"--display","--output"}, after_launch_callback); + if (can_image) menu->add_program("image", "o_gtk_image", {}, after_launch_callback); + if (can_graph) menu->add_program("graph", "o_gnuplot", {}, after_launch_callback); + if (can_history_graph) menu->add_program("history graph", "o_gnuplot", {"--history=100"}, after_launch_callback); + } + menu->show_all(); + return menu; +} + +/** + We look for all channels. We get a list of actual row and look for differences to add or remove channel in the known list. + This suppose that a channel with a same id has the same properties. It is a property of blc_channel. + */ +void ChannelWidget::refresh_channel_list(){ + /// We define a functor to compare blc_channel and TreeIter by id + struct compare_id{ + bool operator()(blc_channel const &a, TreeIter const &b) { return a.id < (*b)[columns.id]; }; + bool operator()(TreeIter const &a, blc_channel const &b) { return (*a)[columns.id]< b.id ; }; + }; + + vector<blc_channel> news_channels; + vector<TreeIter> channels_to_remove; + + FREE(channels); + tie(channels, channels_nb)=blc_channel::get_all_infos(); + + //We get children to be able to use iterator + auto children=tree_store->children(); + + //We get a list of new channels + set_difference(channels, channels+channels_nb, children.begin(), children.end(), inserter(news_channels, news_channels.begin()), compare_id()); + + //We get a list of channels to remove + set_difference(children.begin(), children.end(), channels, channels+channels_nb, inserter(channels_to_remove, channels_to_remove.begin()), compare_id()); + + for(auto const &tree_iter : channels_to_remove) tree_store->erase(tree_iter); + for(auto const &channel :news_channels) columns.add_channel(tree_store, channel); + + this->show_all_children(); + + //We free the list of channel before getting a new list. This is not optimum but it does not happen often. + for_each(channels, channels+channels_nb, [](blc_channel channel){ + channel.~blc_channel(); //We destroy the dims inside + }); + } diff --git a/gtk_channels/ChannelWidget.hpp b/gtk_channels/ChannelWidget.hpp new file mode 100644 index 0000000000000000000000000000000000000000..794f8520dcc9774db0106876d5bd4def5f11fb1e --- /dev/null +++ b/gtk_channels/ChannelWidget.hpp @@ -0,0 +1,41 @@ + + +// +// ChannelWidget.hpp +// e_bash +// +// Created by Arnaud Blanchard on 29/09/2019. +// + +#ifndef ChannelWidget_hpp +#define ChannelWidget_hpp + +#include <gtkmm.h> +#include <string> +#include <deque> +#include <set> +#include "blc_channel.h" +#include "ChannelColumns.hpp" +#include "ContextMenu.hpp" + +struct ChannelWidget:Gtk::TreeView{ + blc_channel* channels; //This structure is coherent with blc_channel implementation + ContextMenu *menu=nullptr; + Gtk::CellRendererPixbuf terminal_cell_renderer, play_pause_cell_renderer, stop_cell_renderer; + Glib::RefPtr<Gtk::TreeStore> tree_store; + int channels_nb; + std::string program_dir; + + static ChannelColumns columns; + + ChannelWidget(); + blc_channel *find_channel(int id); + ContextMenu *create_menu(); + + void display_process(Gtk::TreeModel::Row &row); + void refresh_channel_list(); + void after_launch(Process *process, std::vector<Gtk::TreeModel::Path> rows); + void on_quit(Process *process, int status, std::vector<Gtk::TreeModel::Path> rows); + +}; +#endif /* Channel_hpp */ diff --git a/gtk_channels/ContextMenu.cpp b/gtk_channels/ContextMenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ffa658e4f16f54afa531d335c0a9b2a665013eb3 --- /dev/null +++ b/gtk_channels/ContextMenu.cpp @@ -0,0 +1,40 @@ +// +// ContextMenu.cpp +// gtk_channels +// +// Created by Arnaud Blanchard on 13/10/2019. +// + +#include "ChannelWidget.hpp" +#include "blc_image.h" + +using namespace Gtk; +using namespace std; + +ContextMenu::ContextMenu(string program_dir):program_dir(move(program_dir)){} + +void ContextMenu::add_item(string name, bool shortcut, function <void()> action){ + MenuItem *item=new MenuItem(name, shortcut); + append(*item); + item->signal_activate().connect(action); +} + +void ContextMenu::add_separator(){ + SeparatorMenuItem *separator_item = new SeparatorMenuItem(); + append(*separator_item); +} + +void ContextMenu::add_program(char const *menu_name, std::string program_name, initializer_list<const char*> const optional_arguments, function <void(Process *process)> after_launch){ + auto item=new MenuItem(menu_name); + auto args_vec=make_shared<vector<const char*>>(optional_arguments); //This will be destroyed by process + append(*item); + + item->signal_activate().connect([this, args_vec, program_name, after_launch]{ + args_vec->reserve(args_vec->size()+selected_channels.size()); + for(auto const &channel:selected_channels){ + args_vec->push_back(strdup(channel->name)); + } + auto process = new Process(program_dir, program_name, args_vec); + process->run(after_launch); + }); +} diff --git a/gtk_channels/ContextMenu.hpp b/gtk_channels/ContextMenu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..99815f1c08302e0a58b9b3b38c587455be2fabc3 --- /dev/null +++ b/gtk_channels/ContextMenu.hpp @@ -0,0 +1,32 @@ +// +// ContextMenu.hpp +// gtk_channels +// +// Created by Arnaud Blanchard on 13/10/2019. +// + +#ifndef ContextMenu_hpp +#define ContextMenu_hpp + +#include <gtkmm.h> +#include <stdio.h> +#include "blc_channel.h" +#include <string> +#include <vector> + +#include "Process.h" + + +struct ContextMenu:Gtk::Menu{ + std::string program_dir; + std::vector<blc_channel*> selected_channels; + Glib::RefPtr<Gtk::TreeSelection> row_selection; + + ContextMenu(std::string program_dir); + void after_launch(Process *process); + void add_item(std::string name, bool shortcut, std::function<void ()>action); + void add_separator(); + void add_program(char const *menu_name, std::string program_name, std::initializer_list<const char*> const optional_args, std::function <void(Process*)> after_launch); +}; + +#endif /* ContextMenu_hpp */ diff --git a/gtk_channels/Process.cpp b/gtk_channels/Process.cpp index 75d857959eb9fa600f0af5c8726e2e937a1f14f8..4bb8bf07c659e6ab95528e71ba1468a002f29125 100644 --- a/gtk_channels/Process.cpp +++ b/gtk_channels/Process.cpp @@ -9,33 +9,55 @@ #include "blc_core.h" #include <errno.h> #include <unistd.h> //execve - #include <thread> - #include <vte/vte.h> #include <sys/pipe.h> #include <gtkmm.h> +#include <signal.h> using namespace std; using namespace Gtk; -void TerminalSpawnAsyncCallback (VteTerminal *terminal, GPid pid, GError *error, gpointer user_data){ - - char **argv=(char**)user_data; +static void TerminalSpawnAsyncCallback (VteTerminal *terminal, GPid pid, GError *error, gpointer user_data){ + Process *process=(Process*)user_data; if (error){ fprintf(stderr, "Process error: '%s'\n", error->message); - for (auto arg=argv; *arg!=nullptr; arg++){ - fprintf(stderr, "%s ", *arg); + for (auto const &arg : process->arg_vector){ + fprintf(stderr, "%s ", arg); } fprintf(stderr, "\n"); } - else fprintf(stderr, "Success pid %d\n", pid); + else { + process->pid=pid; + fprintf(stderr, "Success pid %d\n", pid); + for (auto const &arg : process->arg_vector){ + fprintf(stderr, "%s ", arg); + } + fprintf(stderr, "\n"); + process->after_launch(process); + } }; -Process::Process(string const &directory, string const &program):directory(directory),program_name(program){ - command_path=directory+program; +static void after_exectution(VteTerminal *vteterminal, gint status, gpointer user_data){ + auto process=(Process*)user_data; + process->on_quit_callback(status); +} + + +void Process::on_quit(function<void(int exit_status)> on_quit){ + on_quit_callback=on_quit; + g_signal_connect(G_OBJECT(terminal), "child-exited", G_CALLBACK(after_exectution) , this); +} + + +Process::Process(string const directory, string const program, shared_ptr<vector<const char*>> const optional_arguments):directory(move(directory)),program_name(move(program)){ + command_path=directory+"/"+program; arg_vector.emplace_back(command_path.data()); + + for( auto &arg : *optional_arguments){ + arg_vector.emplace_back(arg); + } }; Process::~Process(){ @@ -44,25 +66,27 @@ Process::~Process(){ } }; -void Process::add_arg(string const &arg){ - arg_vector.emplace_back(strdup(arg.data())); -} - -void Process::run(){ - +void Process::run(function <void(Process *process)> after_launch){ + this->after_launch=after_launch; terminal=VTE_TERMINAL(vte_terminal_new()); - arg_vector.emplace_back(nullptr); - - vte_terminal_spawn_async(terminal, VTE_PTY_DEFAULT , nullptr, (char**)arg_vector.data(), nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, nullptr, 1000, nullptr, TerminalSpawnAsyncCallback, arg_vector.data()); - - + arg_vector.emplace_back(nullptr); //note the end of the list + vte_terminal_spawn_async(terminal, VTE_PTY_DEFAULT , nullptr, (char**)arg_vector.data(), nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, nullptr, 1000, nullptr, TerminalSpawnAsyncCallback, (void*)this); } void Process::display_terminal(){ + Widget *mm_terminal=Glib::wrap(GTK_WIDGET(terminal)); //This is because vte is not in C++/gtkmm - Widget *mm_terminal=Glib::wrap(GTK_WIDGET(terminal)); - - terminal_window=new Window(); - terminal_window->add(*mm_terminal); + if (terminal_window==nullptr){ + terminal_window=new Window(); + terminal_window->add(*mm_terminal); + terminal_window->signal_delete_event().connect([this](GdkEventAny*){ + terminal_window->hide(); + return true; + }); + } terminal_window->show_all(); } + +void Process::signal(int signal) const{ + kill(pid, signal); +} diff --git a/gtk_channels/Process.h b/gtk_channels/Process.h index fd44a8b0a06ec495ba298f286d4561981391b4fb..98b57f86d5b5bb9e132d317155ca4ef58787139a 100644 --- a/gtk_channels/Process.h +++ b/gtk_channels/Process.h @@ -15,26 +15,27 @@ #include <gtkmm.h> #include <vte/vte.h> - struct Process{ pid_t pid; - int stdin_fileno, stdout_fileno, stderr_fileno; std::string name; std::string directory; std::string program_name; std::string command_path; - std::string channel; - std::thread *run_thread; std::vector<const char*> arg_vector; VteTerminal *terminal; - Gtk::Window *terminal_window; + Gtk::Window *terminal_window=nullptr; + std::function<void(Process *)> after_launch; + std::function<void(int exit_status)> on_quit_callback; + - Process(std::string const &directory, std::string const &program_name); + Process(std::string const directory, std::string const program_name, std::shared_ptr<std::vector<const char*>> const optional_arguments={}); ~Process(); - - void add_arg(std::string const &arg); - void run(); + + void run(std::function<void(Process*)> on_launch); void display_terminal(); + void on_quit(std::function<void(int exit_status)> on_quit); + void signal(int signal) const; + }; #endif /* Process_hpp */ diff --git a/gtk_channels/gtk_channels.cpp b/gtk_channels/gtk_channels.cpp index 699fa8a7733b4cf6e77a1e9f0166c193a354ad7f..2327f0d54c18f1e29820236b00ee8aa68661a833 100644 --- a/gtk_channels/gtk_channels.cpp +++ b/gtk_channels/gtk_channels.cpp @@ -2,14 +2,14 @@ #include "blc_program.h" #include "libgen.h" #include <gtkmm.h> -#include "Channel.hpp" +#include "ChannelWidget.hpp" #include "thread" using namespace std; using namespace Gtk; struct App:Application{ - // App(int &argc, char **&argv); + App(int &argc, char **&argv); ApplicationWindow main_window; GtkScrolledWindow channel_window; ChannelWidget channel_widget; @@ -19,9 +19,8 @@ struct App:Application{ void on_activate() override; }; -/*App::App(int &argc, char **&argv):Application(argc, argv){ - Glib::set_application_name("e_bash"); -}*/ +App::App(int &argc, char **&argv):Application(argc, argv){ +} void App::on_startup(){ Application::on_startup(); @@ -40,7 +39,7 @@ void App::on_activate(){ int main(int argc, char **argv) { - App app;//(argc, argv); + App app(argc, argv); //AArgc argv allow to interpret option --g-fatal-warnings for debugging blc_program_set_description("Graphical interface for blaar programs"); blc_program_init(&argc, &argv, blc_quit);