Forum: wxRuby Catch Close-Event from child-window created with io.popen

5f14bb0f01ab4d14b34500a0802d53a9?d=identicon&s=25 Daniel S. (daniel_s89)
on 2012-09-13 08:59
Hi Guys,

i create a process in a child-windows with io.popen.

If users closes this window, for example via click on "X" i need to
react in the parent window.

How can i catch the evt_close of this child-process?
85991f138ede6236f35eb98da22b7b01?d=identicon&s=25 Marvin Gülker (quintus)
on 2012-09-13 13:25
(Received via mailing list)
Attachment: signature.asc (489 Bytes)
Am Thu, 13 Sep 2012 08:59:47 +0200
schrieb "Daniel S." <lists@ruby-forum.com>:

> Hi Guys,
>
> i create a process in a child-windows with io.popen.
>
> If users closes this window, for example via click on "X" i need to
> react in the parent window.
>
> How can i catch the evt_close of this child-process?

If I get you right, you run a Ruby application via IO.popen and want
your parent process to do something? Something like this:

=================================
IO.popen("ruby myapp.rb"){...stuff...}
=================================

? So you have three possibilities now:

1. EVT_CLOSE usually means your child application is going to exit. So
just use IO.popen with the block form, which will wait until your child
process has finished.

2. If you can’t use the block form for some reason, use Process.wait
which will wait for all child processes to exit.

3. Assuming you #veto the close event, but want your parent process to
take action nevertheless, you can use process signals to achieve what
you want. In the parent process, register a signal handler for your
"reaction":

=================================
Signal.trap("SIGUSR1") do
  puts "Hi, I've been notified!"
  # or do whatever you want
end
=================================

In the child process, register an event handler for the EVT_CLOSE event
that notifies your parent process (Process.ppid returns the PID of
your process’ parent process):

=================================
evt_close do |event|
  Process.kill("SIGUSR1", Process.ppid)
  event.veto # Do not close the window
end
=================================

Or if you meant it the other way round, having your child process to
react when the parent process gets EVT_CLOSE, you could catch the
EVT_CLOSE in the parent process and send a signal to the child process.
Example (using the open4 gem):

=================================
require "open4"
require "wx"

class MainFrame < Wx::Frame
  include Wx

  def initialize(parent = nil)
    super(parent, title: "Test", size: [400, 400])

    StaticText.new(self, label: "Test app", pos: [20, 20])
    @button = Button.new(self, label: "Click me", pos: [20, 50])

    evt_button(@button, :on_button_click)
    evt_close(:on_close)
  end

  private

  def on_button_click(event)
    # Note you cannot use the block form
    @childpid, @childstdin, @childstdout, @childstderr = \
      Open4.popen4("sleep")
  end

  def on_close(event)
    Process.kill("SIGTERM", @childpid)
    event.skip # Otherwise closing is prohibited
  end

end

class MyApp < Wx::App

  def on_init
    @mainwindow = MainFrame.new
    @mainwindow.show
  end
end
=================================

This application will send SIGTERM to the "sleep" child process when
EVT_CLOSE is received.

Vale,
Marvin

--
Blog: http://pegasus-alpha.eu/blog

ASCII-Ribbon-Kampagne        ()   | ASCII Ribbon Campaign        ()
- Stoppt HTML-E-Mail         /\   | - Against HTML E-Mail        /\
- Stoppt proprietäre Anhänge      | - Against proprietary attachments
www.asciiribbon.org/index-de.html | www.asciiribbon.org
5f14bb0f01ab4d14b34500a0802d53a9?d=identicon&s=25 Daniel S. (daniel_s89)
on 2012-09-13 16:46
Hi Marvin,
thx for ur long answer, but this is not exactly what i wanted, because
my childprocess is not really a second ruby process. In my case i am
using Gnuplot in the childwindow and coontrol it via buttons in the
parent window.
So here is my example:

At first i create my parent window with some buttons and the following
code to invoke the child process:

...
def on_buttonclick
  @gnuplot = Plot.new(filelist)
end

def on_anotherbuttonclick
  @gnuplot.do_something
end
...



Here is what my Plot-Class does:

class Plot
  def initialize(filelist)
    @gp=IO.popen("gnuplot","w+")
    @gp.puts("plot #{filelist}")
  end

  def do_something
    @gp.puts("do something")
  end

...

end


Situation after Click on first button:
- Parent window is open with two button
- Gnuplot-Window has been openend as a child of parentwindow with some
plot
- both are idle
- After Click on second button the childprocess is forced to do
something, this is what the parent process should do.

Now my problem:
If the user closes the Childwindow (For example Alt-F4, or click on "X"
the Window closes, but the object @gnuplot still exists in memory.

If this happens i want to react in ParentProcess for example with
@gnuplot.close or disable some buttons which have no function any longer
when Childwindows doesnt exist anymore.

evt_close in my Plot-Class does not seem to work, because this event is
not thrown in case of clicking on "X" or Alr-F4.

So the question is: How to catch those (User-)Events?

If i could catch these events i could use your solution with SIGTERM
85991f138ede6236f35eb98da22b7b01?d=identicon&s=25 Marvin Gülker (quintus)
on 2012-09-13 22:58
(Received via mailing list)
Attachment: signature.asc (489 Bytes)
Am Thu, 13 Sep 2012 16:46:35 +0200
schrieb "Daniel S." <lists@ruby-forum.com>:

> Hi Marvin,
> thx for ur long answer, but this is not exactly what i wanted,
> because my childprocess is not really a second ruby process. In my
> case i am using Gnuplot in the childwindow and coontrol it via
> buttons in the parent window.

You cannot listen to events to other processes. So if GnuPlot doesn’t
have a possibility to tell you when its main window is closed, you
can’t check this without relying on dirty hacks such as making xdotool
check the visibility of the window regularily by means of a Wx::Timer.

> Situation after Click on first button:
> - Parent window is open with two button
> - Gnuplot-Window has been openend as a child of parentwindow with
> some plot
> - both are idle
> - After Click on second button the childprocess is forced to do
> something, this is what the parent process should do.

First for clarity: This way the GnuPlot window is *not* a child window
of your application. It’s the main window of a completely autonome and
separate process. The GnuPlot *process* is a child process of your Ruby
process, but the window belongs to the child process, not to your
process.

I don’t know GnuPlot, but when its main window exits (as a result to
[Alt]+[F4] or clicking on the close button) I suspect the process
running the window dies, doesn’t it? If you’re developing for a *nix
platform there’s a special process signal, SIGCHLD, which is fired on
your process when a child process dies. Any *nix application
(including Ruby applications) can register a handler for this signal,
so you can for example do (IO.popen creates child processes, and #spawn
does so as well):

=====================================
spawn("sleep 5")

Signal.trap("SIGCHLD") do
  puts "This was it."
  puts "But for $500.000 I will send you the body."
  exit
end

puts "If you don't pay $1.000.000 I will kill this child."

loop do
  puts "I'm waiting..."
  sleep 0.5
end
=====================================

You should be able to combine this with your usecase[1]. I however am
not sure how wxRuby reacts if you try to modify wx stuff from this
asynchronously run code; I often saw segfaults or arcane error messages
when I even tried this with threads, so I cannot predict what happens
from within signal handlers. What you probably can do safely is
registering a callback to run as soon as possible (probably utilising
Timer.after with a very short time interval) which will then be run
from within the wx eventloop and therefore be safe.

> So the question is: How to catch those (User-)Events?

As said: There’s no way to listen to events for another process (apart
from using GDB, I suppose).

> If i could catch these events i could use your solution with SIGTERM

Vale,
Marvin

[1] I mean the concept employed by the code, not the way this
hypothetic kidnapper acts, of course. Btw. from the code you can see he
never had the intention to leave the child alive after receiving the
money.

--
Blog: http://pegasus-alpha.eu/blog

ASCII-Ribbon-Kampagne        ()   | ASCII Ribbon Campaign        ()
- Stoppt HTML-E-Mail         /\   | - Against HTML E-Mail        /\
- Stoppt proprietäre Anhänge      | - Against proprietary attachments
www.asciiribbon.org/index-de.html | www.asciiribbon.org
5f14bb0f01ab4d14b34500a0802d53a9?d=identicon&s=25 Daniel S. (daniel_s89)
on 2012-09-13 23:38
> First for clarity: This way the GnuPlot window is *not* a child window
> of your application. It’s the main window of a completely autonome and
> separate process. The GnuPlot *process* is a child process of your Ruby
> process, but the window belongs to the child process, not to your
> process.

okay, this was my problem...
>
> I don’t know GnuPlot, but when its main window exits (as a result to
> [Alt]+[F4] or clicking on the close button) I suspect the process
> running the window dies, doesn’t it?

No, it doesn't. I'll check some gnuplot forums to realize this behavior.
After this is can use SIGCHLD, i think

As an alternative i'll prevent the gnuplot window to be closed by user
(don't know yet how to do so) and implement a "close window"-button in
my parent which kills the whole gnuplot-process...

Thx for your help!
This topic is locked and can not be replied to.