Static sources not supported for video yet - python

I was trying to play a song using Pyglet, but encountered this error
NotImplementedError: Static sources not supported for video yet.
But the file is mp3 format. I have AVbin11-win64.exe installed (avbin64.dll) which is copied in 'C:\Windows\SysWOW64' Folder, downloaded from https://github.com/AVbin/AVbin/downloads.
Here is the script I am using :
import pyglet
player = pyglet.media.Player()
source = pyglet.media.load(r'C:\Users\MANDAV\Desktop\New folder (2)\Diamond-
Platnumz-All-The-Way-Up-v2.mp3', streaming=False)
player.play()
player.app.run()

Your code contains a few issues.
The first being player.app.run(), player doesn't have .app - you're probably looking for pyglet.app.run().
The second thing is that you try to call player.play(), but you never did player.queue(source). So either do source.play() or player.queue(source) (both does exactly the same thing, playing the source directly will create a player in the background.)
The third thing being streaming=False. avbin (10 at least) have issues decoding mp3 as a static source. Not quite sure why, but the project has been dead for soon to be a century (yes, in just a few short years, this code has been dead for 10 years).
Here's a minimal working example:
import pyglet
source = pyglet.media.load('epic.mp3')
source.play()
pyglet.app.run()
Altho, you probably want some more functionality while playing your stuff. So here's a bigger example of how you could put together a start for a music playing application:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.keys = OrderedDict() # This just keeps track of which keys we're holding down. In case we want to do repeated input.
self.alive = 1 # And as long as this is True, we'll keep on rendering.
## Add more songs to the list, either here, via input() from the console or on_key_ress() function below.
self.songs = ['A.wav', 'B.wav', 'C.wav']
self.song_pool = None
self.player = pyglet.media.Player()
for song in self.songs:
media = pyglet.media.load(song)
if self.song_pool is None:
## == if the Song Pool hasn't been setup,
## we'll set one up. Because we need to know the audio_format()
## we can't really set it up in advance (consists more information than just 'mp3' or 'wav')
self.song_pool = pyglet.media.SourceGroup(media.audio_format, None)
## == Queue the media into the song pool.
self.song_pool.queue(pyglet.media.load(song))
## == And then, queue the song_pool into the player.
## We do this because SourceGroup (song_pool) as a function called
## .has_next() which we'll require later on.
self.player.queue(self.song_pool)
## == Normally, you would do self.player.eos_action = self.function()
## But for whatever fucky windows reasons, this doesn't work for me in testing.
## So below is a manual workaround that works about as good.
self.current_track = pyglet.text.Label('', x=width/2, y=height/2+50, anchor_x='center', anchor_y='center')
self.current_time = pyglet.text.Label('', x=width/2, y=height/2-50, anchor_x='center', anchor_y='center')
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.SPACE:
if self.player.playing:
self.player.pause()
else:
self.player.play()
elif symbol == key.RIGHT:
self.player.seek(self.player.time + 15)
## == You could check the user input here,
## and add more songs via the keyboard here.
## For as long as self.song_pool has tracks,
## this player will continue to play.
self.keys[symbol] = True
def end_of_tracks(self, *args, **kwargs):
self.alive=0
def render(self):
## Clear the screen
self.clear()
## == You could show some video, image or text here while the music plays.
## I'll drop in a example where the current Track Name and time are playing.
## == Grab the media_info (if any, otherwise this returns None)
media_info = self.player.source.info
if not media_info:
## == if there were no meta-data, we'll show the file-name instead:
media_info = self.player.source._file.name
else:
## == But if we got meta data, we'll show "Artist - Track Title"
media_info = media_info.author + ' - ' + media_info.title
self.current_track.text = media_info
self.current_track.draw()
## == This part exists of two things,
## 1. Grab the Current Time Stamp and the Song Duration.
## Check if the song_pool() is at it's end, and if the track Cur>=Max -> We'll quit.
## * (This is the manual workaround)
cur_t, end_t = int(self.player.time), int(self.player.source._get_duration())
if self.song_pool.has_next() is False and cur_t >= end_t:
self.alive=False
## 2. Show the current time and maximum time in seconds to the user.
self.current_time.text = str(cur_t)+'/'+str(end_t) + 'seconds'
self.current_time.draw()
## This "renders" the graphics:
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
If you're just interested in playing sounds.
I wholeheartedly recommend Pydub.
Not only does it use libav which is more of a defacto standard when encoding and decoding, but it's also rich in features. Such as converting .mp3 to .wav and you can control audio gain, play two sources at the same time etc.

Related

How can I open a serial connection in one python class, but use that connection in another class?

Part of my Python program opens a connection to an Arduino running GRBL. When this class is called, GRBL variables are set up and the serial connection is opened. I have everything else running the way I want, but it would be great to get gamepad input to control my motors instead of the key bindings I presently use.
I'm using XInput to get the gamepad events with the built-in thread handler. I can get this running and reading events in the background, but I'm having trouble triggering write commands outside the controller class. I always get "type object 'MotorTest' has no attribute 's'" if I try to run "MotorTest.test_move(MotorTest,direction='plus'), or "'MyOtherHandler' object has no attribute 'MotorTest'" if I try any "self" prefix. I don't want to re-initialize the MotorTest class obviously.
Modules used: XInput, switchcase, serial
Here are excerpts of the relevant code. I want to be able to control "def test_move" from the MyOtherHandler class:
class MotorTest:
def __init__(self):
"""Initialize GRBL on the Arduino
$1 = Hold current delay in ms. [0] for none, [255] always on.
$32 = Set laser mode [1] for less pause between move commands.
G20/21 = Set Inch/MM.
G28 = Return home.
G93 = Inverse time feed. Keeps constant speed even with other motors moving
1 revolution takes 200 * 32 steps = 6400 steps/rev
1 inch movement takes 18 revolutions = 18 * 6400 = 115200 steps/inch
1 inch = 25.4 mm so 1 mm = 115200/25.4 = 4535.433 steps per mm
"""
# Motor Variables
inch = 'G20\n'
millimeter = 'G21\n'
step_per_mm = '1000'
accel_rateX = '1000'
accel_rateY = '1000'
accel_rateZ = '1000'
self.jog_amt = '0.05'
self.jog_feed_rate = 200
try:
self.ports = list_ports.comports()
self.port_ = None
for port, desc, hwid in sorted(self.ports):
#print("{}: {} [{}]".format(port, desc, hwid))
if 'duino' in desc:
print("Arduino found on " + port)
self.port_ = port
self.s = Serial(str(self.port_),115200)
# More GRBL variable code here
def test_move(self,direction):
"""
Uses arrow key binding from main Tkinter class.
$J = Jog mode
$G91 = incremental mode. Using for JOG commands.
"""
for case in switch(direction):
if case('plus'):
self.s.write(str.encode('$J=G91 Y{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
break
if case('minus'):
self.s.write(str.encode('$J=G91 Y-{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
break
if case('left'):
self.s.write(str.encode('$J=G91 X-{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
break
if case('right'):
self.s.write(str.encode('$J=G91 X{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
break
if case('zPlus'):
self.s.write(str.encode('$J=G91 Z{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
break
if case('zMinus'):
self.s.write(str.encode('$J=G91 Z-{} F{} \n'.format(self.jog_amt,self.jog_feed_rate)))
class MyOtherHandler(EventHandler,MotorTest):
def __init__(self, *controllers):
super().__init__(*controllers)
def process_button_event(self, event):
if event.button_id == BUTTON_A:
print("Pressed button A")
elif event.button_id == BUTTON_B:
print("Butt B")
def process_stick_event(self, event):
pass
def process_trigger_event(self, event):
if event.value > 0.00:
print("Trigger LEFT = ", round(event.value,4), end='\r')
else:
print('\r\n')
def process_connection_event(self, event):
if event.type == EVENT_CONNECTED:
print("**** CONTROLLER CONNECTED ****")
else:
print("**** CONTROLLER DISCONNECTED ****")

Pyglet Player.seek() function not working?

I am trying to build a simple media tool in Pyglet, which requires a seek feature. Files are loaded, paused, and then told to seek to a specific time; however, the file does not seek when Player.seek() is called. Below is the test code I am using:
import os
import pyglet
from os.path import abspath, isfile
def loadsong(filename):
# check for file
print("Attempting to load "+filename)
filename = abspath(filename)
if not ( isfile(filename) ):
raise Exception(filename+" not found.")
# create a player for this file
song = pyglet.media.load(filename)
source = song.play()
source.eos_action = source.EOS_LOOP
source.pause()
return source
music = loadsong("test.mp3")
music.seek(57)
music.play()
pyglet.app.run()
What am I doing wrong here? I am using Python 3.5.2, Pyglet 1.2 alpha 1, and AVBin 11 alpha 4.
First of all, the error you're getting is quite important.
But I'll assume it's a Segmentation fault (core dumped) on the row:
music.seek(12)
Another quick note, fix your indentation! You're using 3 spaces and whether you are a space guy or a tab guy - 3 spaces is just odd -
The reason for you getting a segmentation fault when trying to seek is most likely because of AVbin, it's an old (and dead afaik) project.
I hacked together something more similar to a music player and here's an example on how you can use Seek with wav files:
import pyglet
from pyglet.gl import *
from os.path import abspath, isfile
pyglet.options['audio'] = ('pulseaudio', 'alsa', 'openal', 'silent')
pyglet.have_avbin=False
key = pyglet.window.key
def loadsong(filename):
# check for file
filename = abspath(filename)
# create a player for this file
player = pyglet.media.Player()
player.queue(pyglet.media.load(filename, streaming=False))
#player.play()
#song.eos_action = song.EOS_LOOP
#song.pause()
return player
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(300, 300, fullscreen = False)
self.alive = 1
self.player = loadsong('./test.wav')
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.SPACE:
if self.player.playing:
self.player.pause()
else:
self.player.play()
elif symbol == key.RIGHT:
print('Skipping to:',self.player.time+2)
self.player.source.seek(self.player.time+2)
elif symbol == key.LEFT:
print('Rewinding to:',self.player.time-2)
self.player.source.seek(self.player.time-2)
def render(self):
self.clear()
#source = pyglet.text.Label(str(self.player.source.info.title.decode('UTF-8')), x=20, y=300-30)
volume = pyglet.text.Label(str(self.player.volume*100)+'% volume', x=20, y=40)
p_time = pyglet.text.Label(str(self.player.time), x=20, y=20)
#source.draw()
volume.draw()
p_time.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
A few important notes:
pyglet.have_avbin=False
This will turn off AVbin completely, there is probably a way to turn it off for individual sources.. But since I rarely play around with it I honestly have no idea of how to.. So off it goes :)
Secondly:
streaming=False
On the media.load() call is quite important, otherwise you might get weird artifacts in your sound and or no sound at all. I got instance to a super high pitched scratching noise that almost made me deaf without that flag.
Other than that, the code is quite straight forward.
self.player.source.seek(<time>)
Is called on the player object that is a instance of pyglet.media.Player(). And it works brilliantly.
Another work-around would be to manually install AVbin 7 which appears to be working better, but I'm reluctant to install it on this machine just for testing purposes.. But the overall info i've gathered over the years is that that old library works better with Mp3 files.
Please ensure Player.playing is True before invoking Player.seek().
In the end of loadsong() function, you invoked the pause() that will set the Player.playing to False.
you could try:
music.play()
music.seek(57)
instead of:
music.seek(57)
music.play()
The following example uses pyglet 1.5.6, the seek() function works on my macbook pro, but negative on my windows 10:
import pyglet
from pyglet.window import key
source = pyglet.media.load(VIDEO_FILE_PATH)
fmt = source.video_format
player = pyglet.media.Player()
player.queue(source)
player.play()
window = pyglet.window.Window(width=fmt.width, height=fmt.height)
#window.event
def on_draw():
player.get_texture().blit(0, 0)
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.LEFT:
player.seek(player.time - 3.5)
elif symbol == key.RIGHT:
player.seek(player.time + 3.5)
elif symbol == key.SPACE:
if player.playing:
player.pause()
else:
player.play()
pyglet.app.run()

How to play music continuously in pyglet

me and my friend are working on a game and we want our music to loop as long as the game is running. Help please there seems to be no function to put music on repeat
In current versions of pyglet, you should use a SourceGroup, setting the loop attribute to True. You can then queue it into a Player to play it:
snd = pyglet.media.load('sound.wav')
looper = pyglet.media.SourceGroup(snd.audio_format, None)
looper.loop = True
looper.queue(snd)
p = pyglet.media.Player()
p.queue(looper)
p.play()
Not sure if there's a more compact way of doing this but it seems to work...
To make a sound play in a loop, you can use a Player:
# create a player and queue the song
player = pyglet.media.Player()
sound = pyglet.media.load('lines.mp3')
player.queue(sound)
# keep playing for as long as the app is running (or you tell it to stop):
player.eos_action = pyglet.media.SourceGroup.loop
player.play()
To play more background sounds simultaneously, just start up another player for each of the sounds, with the same EOS_LOOP "eos_action" setting as above for each of them.
For playing continuously you can use this code
This will allow you to play files from your root directory
import pyglet
from pyglet.window import key
import glob
window = pyglet.window.Window(1280, 720, "Python Player", resizable=True)
window.set_minimum_size(400,300)
songs=glob.glob("*.wav")
player=pyglet.media.Player()
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.ENTER:
print("A key was pressed")
#window.event
def on_draw():
global player
for i in range(len(songs)):
source=pyglet.resource.media(songs[i])
player.queue(source)
player.play()
pyglet.app.run()
this works for me
myplayer = pyglet.media.Player()
Path = "c:/path/to/youraudio.mp3"
source = pyglet.media.load(filename=source, streaming=False)
myplayer.queue(self.slowCaseSongSource)
myplayer.eos_action = 'loop'
this might be irrelevant:
import pyglet
import time
import random
#WARNING: You have to download your own sounds and define them like this:
#sound_1 = pyglet.resource.media("sound_file.wav", streaming=False)
#replacing "sound_file" with your own file.
# Add the sound variables here
BACKGROUND_OPTIONS = ()
player = pyglet.media.Player()
def play_background_sound():
global player
player.queue(random.choice(BACKGROUND_OPTIONS))
player.play()
# This is optional; it's just a function that keeps the player filled so there aren't any breaks.
def queue_sounds():
global player
while True:
player.queue(random.choice(BACKGROUND_OPTIONS))
time.sleep(60) # change this if the background music you have is shorter than 3 minutes
threading.Thread(target=queue_sounds).start()

Pygame- way to create more USEREVENT type events?

This question arose out of the need to create a lot of USEREVENT type events. Since I could not find information on how to create more userevents than the limit allows I came to ask here for help.
Currently I know that the USEREVENT type event has a value of 24 and the maximum allowed id is 31. I also found that some id-s are reserved, at least in one of the comments in the official documentation (http://www.pygame.org/docs/ref/event.html#comment_pygame_event_Event).
Based on all that here is my two-parted question: can those SDL_EVENT_RESERVED event id-s be safely used as extra spaces for user-created events (for example, a timer: pygame.time.set_timer(USEREVENT + 7, 1000)) and is there a way to create an unlimited amount of separate user-created events like in the example piece of timer code?
I am sorry if the question is not understandable because of bad wording or due to other issues.
User events should be between:
pygame.USEREVENT: 24
pygame.NUMEVENTS: 32
So you can have 9 different user events.
The usual way is to define a constant:
SOME_EVENT = pygame.USEREVENT + 0
ANOTHER_EVENT = pygame.USEREVENT + 1
...
If you create your event with event(...) you can assign attributes to the event, that way you can create many different sub-events and assign additional data to them, e.g.: key events.
Unfortunately when you use pygame.time.set_timer() you are stuck with only an ID.
If I were you, I would just rely on one USEREVENT type, to create as many as custom events I want. So, the way to achieve that is like append a special attribute for the name/type. Ok, a little bit example to make it clearer below...
Tested on pygame 1.9.6 on windows 10, python 3.7.3
import pygame as pg
ON_MY_MOUSE_CLICK = 1 # your own codes for your own events...
ON_MY_SCROLL = 2
BLAH_BLAH = 3
# etc ...
pg.init()
video = pg.display.set_mode((100,100))
MouseEvent = pg.event.Event(pg.USEREVENT, MyOwnType=ON_MY_MOUSE_CLICK )
ScrollEvent = pg.event.Event(pg.USEREVENT, MyOwnType=ON_MY_SCROLL)
blahblahEvent = pg.event.Event(pg.USEREVENT, MyOwnType=BLAH_BLAH)
pg.event.post(MouseEvent) # call your own type
pg.event.post(ScrollEvent) # call your own type
for event in pg.event.get():
if (event.type == pg.QUIT):
pass # your built-in event handle goes here
elif (event.type == pg.USEREVENT): # here we go
if (event.MyOwnType == ON_MY_MOUSE_CLICK):
print("My own mouse event!") # handle for your own event
elif (event.MyOwnType == ON_MY_SCROLL):
print("My own scroll event!") # handle for your own event
elif (event.MyOwnType == BLAH_BLAH):
print("My own blah blah event!") # handle for your own event
EDIT :
If you find yourself need to use pg.time.set_timer, I know we can't assign the MyOwnType to the set_timer. However, you can freely use my set_interval.py from my Github. It's under MIT, so you don't have to worry about general GNU license or something like that...
Here's the example:
from set_interval import setInterval
import pygame as pg
def set_timer(eventObj, interval):
func = lambda x: pg.event.post(x)
return setInterval(func=func, sec=interval, args=[eventObj])
ON_MY_MOUSE_CLICK = 1 # your own codes for your own events...
ON_MY_SCROLL = 2
BLAH_BLAH = 3
# etc ...
pg.init()
video = pg.display.set_mode((100,100))
MouseEvent = pg.event.Event(pg.USEREVENT, MyOwnType=ON_MY_MOUSE_CLICK )
ScrollEvent = pg.event.Event(pg.USEREVENT, MyOwnType=ON_MY_SCROLL)
blahblahEvent = pg.event.Event(pg.USEREVENT, MyOwnType=BLAH_BLAH)
# replacement for pygame.time.set_timer(MouseEvent, 1000)
myIntervalHandle1 = set_timer(MouseEvent, 1)
# replacement for pygame.time.set_timer(ScrollEvent, 2500)
myIntervalHandle2 = set_timer(ScrollEvent, 2.5)
running = True
while running :
for event in pg.event.get():
if (event.type == pg.QUIT):
pg.display.quit()
myIntervalHandle1.stop() # stop timer
myIntervalHandle2.stop() # stop timer
running = False
elif (event.type == pg.USEREVENT): # here we go
if (event.MyOwnType == ON_MY_MOUSE_CLICK):
print("My own mouse event!") # handle for your own event
elif (event.MyOwnType == ON_MY_SCROLL):
print("My own scroll event!") # handle for your own event
elif (event.MyOwnType == BLAH_BLAH):
print("My own blah blah event!") # handle for your own event
You can find the set_interval.py here:
https://github.com/Hzzkygcs/setInterval-python

Running system commands in Python using curses and panel, and come back to previous menu

I'm coding a python script using several commanline tools like top, so i need a proper visual feedback. Now it is time to give it a menu, so here comes the problem.
I found here a great approach of what i need, but every try to display a feedback before come back to previous menu is futile.
I just need menus, submenus, launch commands, terminate it, and back to previous menu. a GREAT bonus would be to run them in a split of the term.
Is there any pattern/skeleton/stuff/whatever to use as template in order to display several kind of widget with a predictable output?
here is a example of code,which two examples of functions to run:
#!/usr/bin/env python2
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0,0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append(('exit','exit'))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items)-1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = '%d. %s' % (index, item[0])
self.window.addstr(1+index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord('\n')]:
if self.position == len(self.items)-1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
######################################################### !#
######################################################### !#
############# HERE MY FUNCTIONS examples
############ Everithing works OK, but displays it awfully
def GetPid(name):
import subprocess
command= str(("""pgrep %s""") % name )
p = subprocess.Popen(command, shell = True, stdout = subprocess.PIPE)
procs = []
salida = p.stdout
for line in salida:
procs.append(str.strip(line))
return procs
def top():
os.system("top")
def menuGP():
print GetPid("top")
######################################################### !#
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [
('beep', curses.beep),
('top', top)
]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
('get PID', GetPid),
('submenu', submenu.display)
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == '__main__':
curses.wrapper(MyApp)
Thanks in advise (and sorry for my rough english)
You really have two choices. One you can leave curses mode, execute your program, then resume curses. Two, you can execute your program asynchronously, parse its output and write it to the screen.
The good news on the first option is that you don't actually need to write any fancy save_state / load_state methods for the ui. Curses does this for you. Here's a simple example to show my point
import curses, time, subprocess
class suspend_curses():
"""Context Manager to temporarily leave curses mode"""
def __enter__(self):
curses.endwin()
def __exit__(self, exc_type, exc_val, tb):
newscr = curses.initscr()
newscr.addstr('Newscreen is %s\n' % newscr)
newscr.refresh()
curses.doupdate()
def main(stdscr):
stdscr.addstr('Stdscreen is %s\n' % stdscr)
stdscr.refresh()
time.sleep(1)
with suspend_curses():
subprocess.call(['ls'])
time.sleep(1)
stdscr.refresh()
time.sleep(5)
curses.wrapper(main)
If you run the example, you will notice that the screen created by curses.wrapper and the one created in curses.initscr when resuming are the same object. That is, the window returned by curses.initscr is a singleton. This lets us exit curses and resume like above without having to update each widget's self.screen references each time.
The second option is much more involved but also much more flexible. The following is just to represent the basic idea.
class procWidget():
def __init__(self, stdscr):
# make subwindow / panel
self.proc = subprocess.Popen(my_args, stdout=subprocess.PIPE)
def update(self):
data = self.proc.stdout.readline()
# parse data as necessary
# call addstr() and refresh()
Then somewhere in your program you will want to call update on all your procWidgets on a timer. This gives you the option of making your subwindow any size/place so you can have as many procWidgets as will fit. You will have to add some handling for when the process terminates and other similar events of course.

Categories

Resources