Commit 539bc0b8 authored by Arnaud Blanchard's avatar Arnaud Blanchard
Browse files

Add network client, an dC++ style

parent f2ae40e2
# Set the minimum version of cmake required to build this project
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.13)
project(c_gtk_image)
#This is to be able to debug and recompile automatically the libs (shared_blc). It is slower, you can remove this line and use : ${BLAR_BUILD_DIR}/lib/libblc.dylib instead of shared_blc
find_package(blc_network REQUIRED)
find_package(blc_channel REQUIRED) #For compatibilité with common.h
find_package(blc_image REQUIRED)
find_package(blc_program REQUIRED)
find_package(blc REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
find_package(JPEG REQUIRED)
add_definitions(-Wall ${BL_DEFINITIONS} -std=c++17 -Wno-deprecated-declarations) #device_manager (mouse) is deprecated
include_directories(${GTK3_INCLUDE_DIRS} ${BL_INCLUDE_DIRS} ${JPEG_INCLUDE_DIR} ../include)
link_directories(${GTK3_LIBRARY_DIRS})
add_definitions(-Wall -Wno-deprecated-declarations) #device_manager (mouse) is deprecated
add_definitions(${GTK3_CFLAGS_OTHER})
add_executable(c_gtk_image c_gtk_image.cpp ../src/image_display.cpp ../src/histogram.cpp)
target_link_libraries(c_gtk_image ${GTK3_LIBRARIES} ${BL_LIBRARIES} ${JPEG_LIBRARIES} )
add_executable(c_gtk_image c_gtk_image.cpp ../src/Display.cpp ../src/Image.cpp ../src/Histogram.cpp ../src/UIN8_Y800.cpp )
target_compile_features(c_gtk_image PRIVATE cxx_std_17)
target_include_directories(c_gtk_image PRIVATE ${GTK3_INCLUDE_DIRS} ${JPEG_INCLUDE_DIR} ../include)
target_link_directories(c_gtk_image PRIVATE ${GTK3_LIBRARY_DIRS})
target_link_libraries(c_gtk_image PRIVATE blc ${GTK3_LIBRARIES} ${JPEG_LIBRARIES} )
......
//
// Histogram.hpp
// c_gtk_image
//
// Created by Arnaud Blanchard on 23/11/2020.
//
#ifndef Histogram_hpp
#define Histogram_hpp
class Histogram:public blc_array{
public:
Histogram(blc_array const &array);
}
#endif /* Histogram_h */
#include "common.h"
#include <fcntl.h> // O_RDONLY ...
#include <stdio.h>
#include <gtk/gtk.h>
......@@ -8,25 +6,50 @@
//#include <jpeglib.h>
#include <sys/mman.h>
#include <errno.h> //errno
#include "blc_core.h"
#include "blc_program.h"
#include "blc_network.h"
#include <thread>
#include <string>
#include "blc.h"
#include "Display.hpp"
using namespace std;
float min_val, max_val;
class Client:public blc_array_tcp4_client{
GtkWidget *window;
public:
Client(string const &address, string const &port);
void network_manager();
unique_ptr<Display> display;
unique_ptr<thread> network_thread;
};
gboolean update_data(GtkWidget *widget, GdkFrameClock *frame_clock, void *user_data){
auto client=reinterpret_cast<Client*>(user_data);
client->recv_data();
client->display->update();
return G_SOURCE_CONTINUE;
}
Client::Client(string const &address, string const &port_name):blc_array_tcp4_client(address, port_name){
string title(string(address)+":"+string(port_name));
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), title.c_str());
display=make_unique<Display>(*this);
gtk_container_add(GTK_CONTAINER(window), display->widget);
gtk_widget_show_all(window);
gtk_widget_add_tick_callback (display->widget, update_data, this, nullptr);
}
const char *channel_name, *fullscreen_option, *keyboard_mode, *address, *port_name;
GtkWidget *window;
GdkDisplay *main_display;
GdkDevice *pointer_device;
int interactive_mode=0;
GtkApplication *app;
blc_array mouse_array;
blc_array input;
unique_ptr<Client> client;
char const *input_name;
......@@ -38,19 +61,15 @@ uint32_t format;
void ask_fullscreen(){
gtk_window_fullscreen(GTK_WINDOW(window));
}
void ask_quit()
{
void ask_quit(){
g_application_quit(G_APPLICATION(app));
blc_quit();
}
void on_key_press(GtkWidget *widget, GdkEventKey *event){
void on_key_press(GtkWidget *widget, GdkEventKey *event){
char key;
if (event->type == GDK_KEY_PRESS){
key=event->keyval;
fwrite(&key, 1, 1, stdout);
......@@ -58,42 +77,23 @@ void on_key_press(GtkWidget *widget, GdkEventKey *event){
}
}
void activate_cb(GApplication *app)
{
GtkWidget *display=nullptr;
GtkWidget *grid;
string title(string(address)+":"+string(port_name));
main_display = gdk_display_get_default ();
GdkDeviceManager *device_manager = gdk_display_get_device_manager (main_display);
pointer_device = gdk_device_manager_get_client_pointer (device_manager);
void activate_cb(GApplication *app, gpointer){
window=gtk_application_window_new(GTK_APPLICATION(app));
gtk_window_set_title(GTK_WINDOW(window), title.c_str());
grid=gtk_grid_new();
// for(i=0; input_names[i]; i++){ This is for displaying multiple images
// input=new blc_channel(/*input_names[i]*/ input_name, BLC_CHANNEL_READ);
display=create_image_display(&input);
if (display==NULL) EXIT_ON_ARRAY_ERROR(&input, "Format not managed.");
gtk_widget_set_hexpand(display, 1);
gtk_widget_set_vexpand(display, 1);
gtk_container_add(GTK_CONTAINER(grid), display);
// }
gtk_container_add(GTK_CONTAINER(window), grid);
gtk_window_set_title( GTK_WINDOW(window), "c_gtk_image");
client=make_unique<Client>(address, port_name);
if (client->display==nullptr) EXIT_ON_ARRAY_ERROR(client.get(), "Format not managed.");
if (keyboard_mode) g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK (on_key_press), nullptr);
gtk_widget_show_all(window);
if (keyboard_mode) g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK (on_key_press), NULL);
}
/** Classical GTK application.
* The first optional argument is the name of the experience. Otherwise all the existing shared memory are used.
* */
int main(int argc, char *argv[])
{
int main(int argc, char *argv[]){
int status=0;
char const *g_debug, *mouse_name, *min_str, *max_str;
char const *g_debug, *min_str, *max_str;
blc_program_set_description("Display the content of the blc_channel depending on its type on format");
blc_program_add_option(&keyboard_mode, 'k', "keyboard", nullptr, "Send keyboard input to stdout", nullptr);
......@@ -101,45 +101,23 @@ int main(int argc, char *argv[])
blc_program_add_option(&max_str, 'M', "max", "FL32", "maximal value", nullptr);
blc_program_add_option(&fullscreen_option, 'F', "fullscreen", nullptr, "Set the window in fullscreen", nullptr);
blc_program_add_option(&g_debug, 'g', "g-fatal-warnings", nullptr, "Debug gtk.", nullptr);
blc_program_add_parameter(&address, "address", 1, "internet address", "localhost");
blc_program_add_parameter(&port_name, "port", 1, "port name", "31440");
blc_program_add_parameter(&address, "address", 0, "internet address", "localhost");
blc_program_add_parameter(&port_name, "port", 0, "port name", "31440");
blc_program_init(&argc, &argv, ask_quit);
blc_command_forward_blc_channels();
blc_array_tcp4_client input_client(&input, address, port_name);
input.allocate();
fprintf(stderr, "Connecté\n");
min_val=0;
if (input.type=='UIN8') max_val=256;
if (input.type=='INT8'){
min_val=-128;
max_val=128;
}
if (input.type=='FL32' && input.format=='Y800'){
if (min_str) SSCANF(1, min_str, "%f", &min_val);
else min_val=0;
if (max_str) SSCANF(1, max_str, "%f", &max_val);
else max_val=1;
}else if (min_str || max_str) EXIT_ON_ARRAY_ERROR(&input, "Min (-m) and max (-M) have only effect for type FL32 format Y800");
thread input_thread([&input_client](){
while(1) {
input_client.read_data();
}
});
if (g_debug) {
argc=2;
argv=MANY_ALLOCATIONS(3, char*);
argv[0]=strdup("Program");
argv[1]=strdup("--g-fatal-warnings");
argv[2]=nullptr;
}
gtk_disable_setlocale();
gtk_init(&argc, &argv);
app = gtk_application_new(nullptr, G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (activate_cb), nullptr);
status = g_application_run(G_APPLICATION(app), 0, nullptr);
g_object_unref(app);
input_thread.join();
return EXIT_SUCCESS;
return status;
}
//
// Channel.cpp
// e_bash
//
// Created by Arnaud Blanchard on 29/09/2019.
//
......@@ -58,24 +55,25 @@ ChannelWidget::ChannelWidget(){
col->add_attribute(stop_cell_renderer, "sensitive", columns.stop_sensitive);
append_column(*col);
commands.emplace("graph", Command(bin_dir, "o_gnuplot", {}, [](vector<blc_channel const*> channels ){
default_commands.emplace("graph", Command(bin_dir, "o_gnuplot", {}, [](vector<blc_channel const*> channels ){
if (channels.size()>1) return false;
}));
commands.emplace("graph history", Command(bin_dir, "o_gnuplot", {"--history=100"}, [](vector<blc_channel const*> channels ){
int total_length=channels[0]->total_length;
return all_of(channels.begin()+1,channels.end(),[total_length](blc_channel const *channel){
if (channel->dims_nb==1 && channel->total_length!=total_length) return false;
else if (channel->dims_nb==2) return false;
return true;
});
}));
commands.emplace("keyboard", Command(bin_dir, "i_keyboard", {"--debug"}, [](vector<blc_channel const*> channels ){
return true;
}));
default_commands.emplace("graph history", Command(bin_dir, "o_gnuplot", {"--history=100"}, [](vector<blc_channel const*> channels ){
int total_length=channels[0]->total_length;
return all_of(channels.begin()+1,channels.end(),[total_length](blc_channel const *channel){
if (channel->dims_nb==1 && channel->total_length!=total_length) return false;
else if (channel->dims_nb==2) return false;
return true;
});
}));
default_commands.emplace("keyboard", Command(bin_dir, "i_keyboard", {"--debug"}, [](vector<blc_channel const*> channels ){
if (channels.size()==1 && channels[0]->total_length<=64) return true;
else return false;
}));
signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){
int x, y;
......@@ -110,7 +108,7 @@ ChannelWidget::ChannelWidget(){
signal_button_release_event().connect([this](GdkEventButton const *event){
if (event->button == 3){
ContextMenu *menu=create_menu();
ContextMenu *menu=create_popup_menu();
menu->popup_at_pointer((GdkEvent*)event);
}
return true;
......@@ -122,8 +120,9 @@ ChannelWidget::ChannelWidget(){
show_all_children();
}
blc_channel *ChannelWidget::find_channel(int id){
blc_channel *channel= lower_bound(channels, channels+channels_nb, id, [](blc_channel const &a, int id){
blc_channel *channel = &lower_bound(channels.begin(), channels.end(), id, [](blc_channel const &a, int id){
return (a.id < id);
});
return channel;
......@@ -174,41 +173,41 @@ void ChannelWidget::on_quit(Process *process, int status, std::vector<TreeModel:
}
}
/*
void ChannelWidget::create_and_execute_command(string program_name, initializer_list<const char*> arguments){
commands.emplace_back(bin_dir, program_name, arguments));
auto command=&commands.back();
command->execute();
}*/
void ChannelWidget::create_and_execute_command(string program_name, initializer_list<const char*> arguments){
commands.emplace_back(bin_dir, program_name, arguments));
auto command=&commands.back();
command->execute();
}*/
/*
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);
});
}*/
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);
});
}*/
/*
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);
});
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);
});
*/
ContextMenu *ChannelWidget::create_menu(){
ContextMenu *ChannelWidget::create_popup_menu(){
bool only_channels=true;
vector<Command*> context_commands;
vector<blc_channel const *> selected_channels;
......@@ -216,12 +215,12 @@ ContextMenu *ChannelWidget::create_menu(){
menu->row_selection=get_selection();
get_selection()->selected_foreach_iter ([&](TreeModel::iterator const &iter){
if (tree_store->iter_depth(iter)==0){
blc_channel *channel=find_channel((*iter)[columns.id]);
selected_channels.emplace_back(channel);
}
else only_channels=false;
});
if (tree_store->iter_depth(iter)==0){
blc_channel *channel=find_channel((*iter)[columns.id]);
selected_channels.emplace_back(channel);
}
else only_channels=false;
});
if (only_channels){
menu->add_item("_Remove", true, [this]{
......@@ -231,12 +230,46 @@ ContextMenu *ChannelWidget::create_menu(){
});
menu->add_separator();
for ( const auto& [name, command]:commands){
for ( const auto& [name, command]:default_commands){
Command const *command_ptr=&command; //wihout ptr, command cannot be pass to lambda
if (command.valid_channels(selected_channels));
menu->add_item(name, false, [this, command_ptr, selected_channels](){
auto command =used_commands.emplace_back(*command_ptr, selected_channels);
auto process=new Process(command.args.data(), [](Process*){});
Glib::RefPtr<TreeModel> tree_model = tree_store;
auto paths=this->get_selection()->get_selected_rows(tree_model);
vector<TreeRow> command_rows;
for (auto const &path: paths){
string full_command = accumulate(command.args.begin()+1, command.args.end()-1, command.program_name,[](string const a, string const b){
return a + " " +b;
});
auto iter=tree_store->get_iter(path);
if (tree_store->iter_depth(iter)==0) {
TreeRow &row=command_rows.emplace_back(*tree_store->append((iter)->children()));
row[columns.name]=full_command;
row[columns.terminal_icon]="utilities-terminal";
expand_row(path, true);
}
}
auto process=new Process(command.args.data(), [command_rows](Process* process){
for (auto const &row:command_rows){
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;
}
process->on_quit([command_rows](int status){
for (auto const &row :command_rows){
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;
}
});
});
});
}
}
......@@ -244,38 +277,53 @@ ContextMenu *ChannelWidget::create_menu(){
return menu;
}
template <class _Container>
insert_iterator<_Container> remove_channel(_Container& __x, typename _Container::iterator __i)
{
return insert_iterator<_Container>(__x, __i);
}
/**
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
});
}
auto [local_channels, local_channels_nb] = blc_channel::get_all_infos();
//We get children to be able to use iterator
auto children=tree_store->children();
for(auto const &child : children){
int id = child[columns.id];
auto it=lower_bound(local_channels,local_channels+local_channels_nb, id);
if (it==local_channels+local_channels_nb){
tree_store->erase(child);
channels.erase(id);
}
}
for_each(local_channels, local_channels+local_channels_nb, [](blc_channel const &channel){
// find_if([](){
// columns.add_channel(tree_store, channel);
});
});
for(auto const &tree_iter : channels_to_remove) {
tree_store->erase(tree_iter);
}
for(auto const &channel :news_channels) {
}
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(local_channels, local_channels+local_channels_nb, [](blc_channel channel){
channel.~blc_channel(); //We destroy the dims inside
});
FREE(channels);
}
......@@ -21,22 +21,23 @@
struct ChannelWidget:Gtk::TreeView{
blc_channel* channels; //This structure is coherent with blc_channel implementation
std::map<int, blc_channel> channels;
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 bin_dir;
std::map<std::string, Command> commands;
std::vector<Command> used_commands;
std::vector<Gtk::TreeRow> channel_rows;
static std::map<std::string, Command> default_commands;
static ChannelColumns columns;
ChannelWidget();
blc_channel *find_channel(int id);
ContextMenu *create_menu();
ContextMenu *create_popup_menu();
void create_and_execute_command(std::string program_name, std::initializer_list<const char*> const optional_arguments);
void display_process(Gtk::TreeModel::Row &row);
void refresh_channel_list();
void after_launch(Process *process, std::vector<Gtk::TreeModel::Path> rows);
......
......@@ -10,15 +10,18 @@
#include <string>
#include <vector>
#include <gtkmm.h>
#include "Process.h"
#include "blc_channel.h"
/* One command may have many processes */
struct Command{
std::string binary;
std::string program_name;
std::function<bool(std::vector<blc_channel const*> &channels)> valid_channels;
std::vector<const char *> args;
std::vector<Gtk::TreeRow> command_rows;
Command(std::string const &bin_dir, std::string program_name, std::initializer_list<char const*> default_args, std::function<bool(std::vector<blc_channel const*>)>validity);
Command(Command const &command, std::vector<blc_channel const*> const channel); //Copy command and add arguments
......
......@@ -53,7 +53,6 @@ Process::Process(char const * const *argv, function<void(Process *)>after_launch
};
void Process::run(){
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);
}
......
//
// Display.hpp
// c_gtk_image
//
// Created by Arnaud Blanchard on 12/11/2020.
//
#ifndef Display_hpp
#define Display_hpp
#include "UIN8_Y800.hpp"
#include "blc_array.h"
#include "cairo.h"
#include "gtk/gtk.h"
class Display:public blc_array{
public:
Display(blc_array const &array);