vtl: video-monitor interaction
* Menu > View > Video Monitor -- bi-directional communication with xjadeo for window-state and OSD. * fix saving state on session close (wait for xjadeo to terminate)
This commit is contained in:
parent
399aee309d
commit
9301991f6e
@ -440,6 +440,13 @@
|
||||
#ifdef WITH_VIDEOTIMELINE
|
||||
<separator/>
|
||||
<menuitem action="toggle-video-ruler"/>
|
||||
</menu>
|
||||
<menu action="VideoMonitorMenu">
|
||||
<menuitem action="toggle-vmon-ontop"/>
|
||||
<menuitem action="toggle-vmon-timecode"/>
|
||||
<menuitem action="toggle-vmon-frame"/>
|
||||
<menuitem action="toggle-vmon-osdbg"/>
|
||||
<menuitem action="toggle-vmon-fullscreen"/>
|
||||
#endif
|
||||
</menu>
|
||||
<menu action="ScrollMenu">
|
||||
|
@ -928,9 +928,16 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
|
||||
ArdourCanvas::Group* videotl_group;
|
||||
Glib::RefPtr<Gtk::ToggleAction> ruler_video_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_proc_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_ontop_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_timecode_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_frame_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_osdbg_action;
|
||||
Glib::RefPtr<Gtk::ToggleAction> xjadeo_fullscreen_action;
|
||||
void set_xjadeo_proc ();
|
||||
void toggle_xjadeo_proc (int state=-1);
|
||||
void set_xjadeo_sensitive (bool onoff);
|
||||
void set_xjadeo_viewoption (int);
|
||||
void toggle_xjadeo_viewoption (int what, int state=-1);
|
||||
void toggle_ruler_video (bool onoff) {ruler_video_action->set_active(onoff);}
|
||||
int videotl_bar_height; /* in units of timebar_height; default: 4 */
|
||||
int get_videotl_bar_height () const { return videotl_bar_height; }
|
||||
|
@ -544,8 +544,17 @@ Editor::register_actions ()
|
||||
ruler_timecode_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-timecode-ruler"), _("Timecode"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_metric_timecode)));
|
||||
ruler_minsec_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-minsec-ruler"), _("Min:Sec"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_metric_minsec)));
|
||||
#ifdef WITH_VIDEOTIMELINE
|
||||
|
||||
ActionManager::register_action (editor_menu_actions, X_("VideoMonitorMenu"), _("Video Monitor"));
|
||||
|
||||
ruler_video_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (ruler_actions, X_("toggle-video-ruler"), _("Video"), sigc::bind (sigc::mem_fun(*this, &Editor::toggle_ruler_visibility), ruler_video_timeline)));
|
||||
xjadeo_proc_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("ToggleJadeo"), _("Show Video Monitor"), sigc::mem_fun (*this, &Editor::set_xjadeo_proc)));
|
||||
xjadeo_proc_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("ToggleJadeo"), _("Video Monitor"), sigc::mem_fun (*this, &Editor::set_xjadeo_proc)));
|
||||
|
||||
xjadeo_ontop_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("toggle-vmon-ontop"), _("Always on Top"), sigc::bind (sigc::mem_fun (*this, &Editor::set_xjadeo_viewoption), (int) 1)));
|
||||
xjadeo_timecode_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("toggle-vmon-timecode"), _("Timecode"), sigc::bind (sigc::mem_fun (*this, &Editor::set_xjadeo_viewoption), (int) 2)));
|
||||
xjadeo_frame_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("toggle-vmon-frame"), _("Framenumber"), sigc::bind (sigc::mem_fun (*this, &Editor::set_xjadeo_viewoption), (int) 3)));
|
||||
xjadeo_osdbg_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("toggle-vmon-osdbg"), _("Timecode Background"), sigc::bind (sigc::mem_fun (*this, &Editor::set_xjadeo_viewoption), (int) 4)));
|
||||
xjadeo_fullscreen_action = Glib::RefPtr<ToggleAction>::cast_static (ActionManager::register_toggle_action (editor_actions, X_("toggle-vmon-fullscreen"), _("Fullscreen"), sigc::bind (sigc::mem_fun (*this, &Editor::set_xjadeo_viewoption), (int) 5)));
|
||||
|
||||
#endif
|
||||
|
||||
@ -562,6 +571,16 @@ Editor::register_actions ()
|
||||
ruler_video_action->set_active (false);
|
||||
xjadeo_proc_action->set_active (false);
|
||||
xjadeo_proc_action->set_sensitive (false);
|
||||
xjadeo_ontop_action->set_active (false);
|
||||
xjadeo_ontop_action->set_sensitive (false);
|
||||
xjadeo_timecode_action->set_active (false);
|
||||
xjadeo_timecode_action->set_sensitive (false);
|
||||
xjadeo_frame_action->set_active (false);
|
||||
xjadeo_frame_action->set_sensitive (false);
|
||||
xjadeo_osdbg_action->set_active (false);
|
||||
xjadeo_osdbg_action->set_sensitive (false);
|
||||
xjadeo_fullscreen_action->set_active (false);
|
||||
xjadeo_fullscreen_action->set_sensitive (false);
|
||||
#endif
|
||||
if (Profile->get_sae()) {
|
||||
ruler_bbt_action->set_active (true);
|
||||
@ -760,6 +779,7 @@ Editor::set_xjadeo_sensitive (bool onoff)
|
||||
{
|
||||
xjadeo_proc_action->set_sensitive(onoff);
|
||||
}
|
||||
|
||||
void
|
||||
Editor::toggle_xjadeo_proc (int state)
|
||||
{
|
||||
@ -774,6 +794,12 @@ Editor::toggle_xjadeo_proc (int state)
|
||||
xjadeo_proc_action->set_active(!xjadeo_proc_action->get_active());
|
||||
break;
|
||||
}
|
||||
bool onoff = xjadeo_proc_action->get_active();
|
||||
xjadeo_ontop_action->set_sensitive(onoff);
|
||||
xjadeo_timecode_action->set_sensitive(onoff);
|
||||
xjadeo_frame_action->set_sensitive(onoff);
|
||||
xjadeo_osdbg_action->set_sensitive(onoff);
|
||||
xjadeo_fullscreen_action->set_sensitive(onoff);
|
||||
}
|
||||
|
||||
void
|
||||
@ -785,6 +811,73 @@ Editor::set_xjadeo_proc ()
|
||||
ARDOUR_UI::instance()->video_timeline->close_video_monitor();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Editor::toggle_xjadeo_viewoption (int what, int state)
|
||||
{
|
||||
Glib::RefPtr<Gtk::ToggleAction> action;
|
||||
switch (what) {
|
||||
case 1:
|
||||
action = xjadeo_ontop_action;
|
||||
break;
|
||||
case 2:
|
||||
action = xjadeo_timecode_action;
|
||||
break;
|
||||
case 3:
|
||||
action = xjadeo_frame_action;
|
||||
break;
|
||||
case 4:
|
||||
action = xjadeo_osdbg_action;
|
||||
break;
|
||||
case 5:
|
||||
action = xjadeo_fullscreen_action;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
switch(state) {
|
||||
case 1:
|
||||
action->set_active(true);
|
||||
break;
|
||||
case 0:
|
||||
action->set_active(false);
|
||||
break;
|
||||
default:
|
||||
action->set_active(!action->get_active());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Editor::set_xjadeo_viewoption (int what)
|
||||
{
|
||||
Glib::RefPtr<Gtk::ToggleAction> action;
|
||||
switch (what) {
|
||||
case 1:
|
||||
action = xjadeo_ontop_action;
|
||||
break;
|
||||
case 2:
|
||||
action = xjadeo_timecode_action;
|
||||
break;
|
||||
case 3:
|
||||
action = xjadeo_frame_action;
|
||||
break;
|
||||
case 4:
|
||||
action = xjadeo_osdbg_action;
|
||||
break;
|
||||
case 5:
|
||||
action = xjadeo_fullscreen_action;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (action->get_active()) {
|
||||
ARDOUR_UI::instance()->video_timeline->control_video_monitor(what, 1);
|
||||
} else {
|
||||
ARDOUR_UI::instance()->video_timeline->control_video_monitor(what, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
|
@ -296,6 +296,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible {
|
||||
virtual void queue_visual_videotimeline_update () = 0;
|
||||
virtual void toggle_ruler_video (bool) = 0;
|
||||
virtual void toggle_xjadeo_proc (int) = 0;
|
||||
virtual void toggle_xjadeo_viewoption (int, int) = 0;
|
||||
virtual void set_xjadeo_sensitive (bool onoff) = 0;
|
||||
virtual int get_videotl_bar_height () const = 0;
|
||||
virtual void set_video_timeline_height (const int h) = 0;
|
||||
|
@ -418,7 +418,7 @@ SystemExec::terminate ()
|
||||
if (pid) {
|
||||
::fprintf(stderr, "Child process is running. trying SIGTERM\n");
|
||||
::kill(pid, SIGTERM);
|
||||
::usleep(10000);
|
||||
::usleep(50000);
|
||||
wait(WNOHANG);
|
||||
}
|
||||
if (pid) {
|
||||
|
@ -39,10 +39,14 @@ VideoMonitor::VideoMonitor (PublicEditor *ed, std::string xjadeo_bin_path)
|
||||
sync_by_manual_seek = false;
|
||||
_restore_settings_mask = 0;
|
||||
clock_connection = sigc::connection();
|
||||
state_connection = sigc::connection();
|
||||
debug_enable = false;
|
||||
state_clk_divide = 0;
|
||||
starting = 0;
|
||||
osdmode = 10; // 1: frameno, 2: timecode, 8: box
|
||||
|
||||
process = new SystemExec(xjadeo_bin_path, X_("-R"));
|
||||
process->ReadStdout.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::parse_output, this, _1 ,_2), gui_context());
|
||||
process->ReadStdout.connect_same_thread (*this, boost::bind (&VideoMonitor::parse_output, this, _1 ,_2));
|
||||
process->Terminated.connect (*this, invalidator (*this), boost::bind (&VideoMonitor::terminated, this), gui_context());
|
||||
}
|
||||
|
||||
@ -51,6 +55,9 @@ VideoMonitor::~VideoMonitor ()
|
||||
if (clock_connection.connected()) {
|
||||
clock_connection.disconnect();
|
||||
}
|
||||
if (state_connection.connected()) {
|
||||
state_connection.disconnect();
|
||||
}
|
||||
delete process;
|
||||
}
|
||||
|
||||
@ -75,6 +82,8 @@ void
|
||||
VideoMonitor::quit ()
|
||||
{
|
||||
if (!is_started()) return;
|
||||
if (state_connection.connected()) { state_connection.disconnect(); }
|
||||
if (clock_connection.connected()) { clock_connection.disconnect(); }
|
||||
process->write_to_stdin("get windowsize\n");
|
||||
process->write_to_stdin("get windowpos\n");
|
||||
process->write_to_stdin("get letterbox\n");
|
||||
@ -92,13 +101,14 @@ VideoMonitor::quit ()
|
||||
int timeout = 40;
|
||||
while (is_started() && --timeout) {
|
||||
usleep(50000);
|
||||
sched_yield();
|
||||
}
|
||||
if (timeout == 0) {
|
||||
if (timeout <= 0) {
|
||||
printf("xjadeo connection: time-out. session may not be saved.\n");
|
||||
process->terminate();
|
||||
}
|
||||
#endif
|
||||
process->terminate();
|
||||
if (clock_connection.connected()) { clock_connection.disconnect(); }
|
||||
save_session();
|
||||
}
|
||||
|
||||
void
|
||||
@ -106,7 +116,9 @@ VideoMonitor::open (std::string filename)
|
||||
{
|
||||
if (!is_started()) return;
|
||||
manually_seeked_frame = 0;
|
||||
osdmode = 10; // 1: frameno, 2: timecode, 8: box
|
||||
sync_by_manual_seek = false;
|
||||
starting = 7;
|
||||
process->write_to_stdin("load " + filename + "\n");
|
||||
process->write_to_stdin("set fps -1\n");
|
||||
process->write_to_stdin("window resize 100%\n");
|
||||
@ -119,9 +131,29 @@ VideoMonitor::open (std::string filename)
|
||||
if (skip_setting(it->first)) { continue; }
|
||||
process->write_to_stdin(it->first + " " + it->second + "\n");
|
||||
}
|
||||
if (!state_connection.connected()) {
|
||||
starting = 7;
|
||||
querystate();
|
||||
state_clk_divide = 0;
|
||||
/* TODO once every two second or so -- state_clk_divide hack below */
|
||||
state_connection = ARDOUR_UI::RapidScreenUpdate.connect (sigc::mem_fun (*this, &VideoMonitor::querystate));
|
||||
}
|
||||
xjadeo_sync_setup();
|
||||
}
|
||||
|
||||
void
|
||||
VideoMonitor::querystate ()
|
||||
{
|
||||
/* clock-divider hack -- RapidScreenUpdate == every_point_one_seconds */
|
||||
state_clk_divide = (state_clk_divide + 1) % 15; // every 1.5 seconds
|
||||
if (state_clk_divide != 0) return;
|
||||
|
||||
process->write_to_stdin("get fullscreen\n");
|
||||
process->write_to_stdin("get ontop\n");
|
||||
process->write_to_stdin("get osdcfg\n");
|
||||
process->write_to_stdin("get letterbox\n");
|
||||
}
|
||||
|
||||
bool
|
||||
VideoMonitor::skip_setting (std::string which)
|
||||
{
|
||||
@ -136,6 +168,44 @@ VideoMonitor::skip_setting (std::string which)
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
VideoMonitor::send_cmd (int what, int param)
|
||||
{
|
||||
bool osd_update = false;
|
||||
if (!is_started()) return;
|
||||
switch (what) {
|
||||
case 1:
|
||||
if (param) process->write_to_stdin("window ontop on\n");
|
||||
else process->write_to_stdin("window ontop off\n");
|
||||
break;
|
||||
case 2:
|
||||
if (param) osdmode |= 2;
|
||||
else osdmode &= ~2;
|
||||
osd_update = true;
|
||||
break;
|
||||
case 3:
|
||||
if (param) osdmode |= 1;
|
||||
else osdmode &= ~1;
|
||||
osd_update = true;
|
||||
break;
|
||||
case 4:
|
||||
if (param) osdmode |= 8;
|
||||
else osdmode &= ~8;
|
||||
osd_update = true;
|
||||
break;
|
||||
case 5:
|
||||
if (param) process->write_to_stdin("window zoom on\n");
|
||||
else process->write_to_stdin("window zoom off\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (osd_update >= 0) {
|
||||
std::ostringstream osstream; osstream << "osd mode " << osdmode << "\n";
|
||||
process->write_to_stdin(osstream.str());
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
VideoMonitor::is_started ()
|
||||
{
|
||||
@ -196,12 +266,32 @@ VideoMonitor::parse_output (std::string d, size_t s)
|
||||
} else if(key == "windowsize") {
|
||||
xjadeo_settings["window size"] = value;
|
||||
} else if(key == "windowontop") {
|
||||
if (starting || xjadeo_settings["window ontop"] != value) {
|
||||
starting &= ~2;
|
||||
if (atoi(value.c_str())) { UiState("xjadeo-window-ontop-on"); }
|
||||
else { UiState("xjadeo-window-ontop-off"); }
|
||||
}
|
||||
xjadeo_settings["window ontop"] = value;
|
||||
} else if(key == "fullscreen") {
|
||||
if (starting || xjadeo_settings["window zoom"] != value) {
|
||||
starting &= ~4;
|
||||
if (atoi(value.c_str())) { UiState("xjadeo-window-fullscreen-on"); }
|
||||
else { UiState("xjadeo-window-fullscreen-off"); }
|
||||
}
|
||||
xjadeo_settings["window zoom"] = value;
|
||||
} else if(key == "letterbox") {
|
||||
xjadeo_settings["window letterbox"] = value;
|
||||
} else if(key == "osdmode") {
|
||||
if (starting || xjadeo_settings["osd mode"] != value) {
|
||||
starting &= ~1;
|
||||
osdmode = atoi(value.c_str());
|
||||
if ((osdmode & 1) == 1) { UiState("xjadeo-window-osd-frame-on"); }
|
||||
if ((osdmode & 1) == 0) { UiState("xjadeo-window-osd-frame-off"); }
|
||||
if ((osdmode & 2) == 2) { UiState("xjadeo-window-osd-timecode-on"); }
|
||||
if ((osdmode & 2) == 0) { UiState("xjadeo-window-osd-timecode-off"); }
|
||||
if ((osdmode & 8) == 8) { UiState("xjadeo-window-osd-box-on"); }
|
||||
if ((osdmode & 8) == 0) { UiState("xjadeo-window-osd-box-off"); }
|
||||
}
|
||||
xjadeo_settings["osd mode"] = value;
|
||||
} else if(key == "offset") {
|
||||
xjadeo_settings["set offset"] = value;
|
||||
@ -218,6 +308,7 @@ VideoMonitor::parse_output (std::string d, size_t s)
|
||||
void
|
||||
VideoMonitor::terminated ()
|
||||
{
|
||||
process->terminate(); // from gui-context clean up
|
||||
save_session();
|
||||
Terminated();
|
||||
}
|
||||
@ -298,6 +389,7 @@ VideoMonitor::get_custom_setting (const std::string k)
|
||||
{
|
||||
return (xjadeo_settings[k]);
|
||||
}
|
||||
|
||||
#define NO_OFFSET (1<<31) //< skip setting or modifying offset -- TODO check ARDOUR::frameoffset_t max value.
|
||||
void
|
||||
VideoMonitor::srsupdate ()
|
||||
|
@ -71,9 +71,12 @@ class VideoMonitor : public sigc::trackable , public ARDOUR::SessionHandlePtr, p
|
||||
void set_offset (ARDOUR::frameoffset_t);
|
||||
void manual_seek (ARDOUR::framepos_t, bool, ARDOUR::frameoffset_t);
|
||||
void srsupdate ();
|
||||
void querystate ();
|
||||
bool synced_by_manual_seeks() { return sync_by_manual_seek; }
|
||||
|
||||
sigc::signal<void> Terminated;
|
||||
PBD::Signal1<void,std::string> UiState;
|
||||
void send_cmd (int what, int param);
|
||||
|
||||
#if 1
|
||||
void set_debug (bool onoff) { debug_enable = onoff; }
|
||||
@ -99,6 +102,10 @@ class VideoMonitor : public sigc::trackable , public ARDOUR::SessionHandlePtr, p
|
||||
ARDOUR::framepos_t manually_seeked_frame;
|
||||
bool sync_by_manual_seek;
|
||||
sigc::connection clock_connection;
|
||||
sigc::connection state_connection;
|
||||
int state_clk_divide;
|
||||
int starting;
|
||||
int osdmode;
|
||||
#if 1
|
||||
bool debug_enable;
|
||||
#endif
|
||||
|
@ -155,8 +155,8 @@ VideoTimeLine::close_session ()
|
||||
if (video_duration == 0) {
|
||||
return;
|
||||
}
|
||||
close_video_monitor();
|
||||
save_session();
|
||||
close_video_monitor();
|
||||
|
||||
remove_frames();
|
||||
video_filename = "";
|
||||
@ -581,6 +581,26 @@ VideoTimeLine::gui_update(std::string const & t) {
|
||||
editor->toggle_xjadeo_proc(0);
|
||||
//close_video_monitor();
|
||||
editor->set_xjadeo_sensitive(false);
|
||||
} else if (t == "xjadeo-window-ontop-on") {
|
||||
editor->toggle_xjadeo_viewoption(1, 1);
|
||||
} else if (t == "xjadeo-window-ontop-off") {
|
||||
editor->toggle_xjadeo_viewoption(1, 0);
|
||||
} else if (t == "xjadeo-window-osd-timecode-on") {
|
||||
editor->toggle_xjadeo_viewoption(2, 1);
|
||||
} else if (t == "xjadeo-window-osd-timecode-off") {
|
||||
editor->toggle_xjadeo_viewoption(2, 0);
|
||||
} else if (t == "xjadeo-window-osd-frame-on") {
|
||||
editor->toggle_xjadeo_viewoption(3, 1);
|
||||
} else if (t == "xjadeo-window-osd-frame-off") {
|
||||
editor->toggle_xjadeo_viewoption(3, 0);
|
||||
} else if (t == "xjadeo-window-osd-box-on") {
|
||||
editor->toggle_xjadeo_viewoption(4, 1);
|
||||
} else if (t == "xjadeo-window-osd-box-off") {
|
||||
editor->toggle_xjadeo_viewoption(4, 0);
|
||||
} else if (t == "xjadeo-window-fullscreen-on") {
|
||||
editor->toggle_xjadeo_viewoption(5, 1);
|
||||
} else if (t == "xjadeo-window-fullscreen-off") {
|
||||
editor->toggle_xjadeo_viewoption(5, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -688,6 +708,7 @@ VideoTimeLine::open_video_monitor() {
|
||||
vmonitor = new VideoMonitor(editor, _xjadeo_bin);
|
||||
vmonitor->set_session(_session);
|
||||
vmonitor->Terminated.connect (sigc::mem_fun (*this, &VideoTimeLine::terminated_video_monitor));
|
||||
vmonitor->UiState.connect (*this, invalidator (*this), boost::bind (&VideoTimeLine::gui_update, this, _1), gui_context());
|
||||
} else if (vmonitor->is_started()) {
|
||||
return;
|
||||
}
|
||||
@ -723,6 +744,15 @@ VideoTimeLine::close_video_monitor() {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VideoTimeLine::control_video_monitor(int what, int param) {
|
||||
if (!vmonitor || !vmonitor->is_started()) {
|
||||
return;
|
||||
}
|
||||
vmonitor->send_cmd(what, param);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VideoTimeLine::terminated_video_monitor () {
|
||||
if (vmonitor) {
|
||||
|
@ -78,6 +78,7 @@ class VideoTimeLine : public sigc::trackable, public ARDOUR::SessionHandlePtr, p
|
||||
|
||||
void open_video_monitor ();
|
||||
void close_video_monitor ();
|
||||
void control_video_monitor (int, int);
|
||||
void terminated_video_monitor ();
|
||||
void manual_seek_video_monitor (framepos_t pos);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user