I am trying to achieve the following. I built some gtk application which will have some data, let's say a,b and c.
What I want now is some sort of terminal window in which I can query and change the data as I would in e.g. iPython:
$ a
[1 2 3]
$ a= a+1
$ a
[2 3 4]
And let this take effect in the gtk application. Is this doable?
You can try to launch xterm by subprocess, and to communicate between file.py and term, copy the vars in environment variable, and get it by:
os.environ[your_var]
Take a look of this. Once you are in type "python". About communicating with the script, the only way that I've found is with an external file. What you want it is possible but complicated. here you have an example that i made where i return the variable "tty" from the VTE terminal to the python script.
from gi.repository import Gtk, GObject, Vte
#GObject is not required. I just import it everywhere just in case.
#Gtk, Vte, and GLib are required.
from gi.repository import GLib
import os
#os.environ['HOME'] helps to keep from hard coding the home string.
#os is not required unless you want that functionality.
class TheWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="inherited cell renderer")
self.set_default_size(600, 300)
self.terminal = Vte.Terminal()
self.terminal.fork_command_full(
Vte.PtyFlags.DEFAULT, #default is fine
os.environ['HOME'], #where to start the command?
["/bin/sh"], #where is the emulator?
[], #it's ok to leave this list empty
GLib.SpawnFlags.DO_NOT_REAP_CHILD,
None, #at least None is required
None,
)
#Set up a button to click and run a demo command
self.button = Gtk.Button("Do The Command")
#To get the command to automatically run
#a newline(\n) character is used at the end of the
#command string.
self.command = "echo \"Sending this command to a virtual terminal.\"\n"
command = Gtk.Label("The command: "+self.command)
self.button.connect("clicked", self.InputToTerm)
#end demo command code
#set up the interface
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.pack_start(self.button, False, True, 0)
box.pack_start(command, False, True, 1)
#a scroll window is required for the terminal
scroller = Gtk.ScrolledWindow()
scroller.set_hexpand(True)
scroller.set_vexpand(True)
scroller.add(self.terminal)
box.pack_start(scroller, False, True, 2)
self.add(box)
def InputToTerm(self, clicker):
#get the command when the button is clicked
length = len(self.command)
#A length is not required but is the easiest mechanism.
#Otherwise the command must be null terminated.
#Feed the command to the terminal.
self.terminal.feed_child(self.command, length)
win = TheWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Related
I'm developing an GUI for multi-robot system using ROS, but i'm freezing in the last thing i want in my interface: embedding the RVIZ, GMAPPING or another screen in my application. I already put an terminal in the interface, but i can't get around of how to add an external application window to my app. I know that PyQt5 have the createWindowContainer, with uses the window ID to dock an external application, but i didn't find any example to help me with that.
If possible, i would like to drag and drop an external window inside of a tabbed frame in my application. But, if this is not possible or is too hard, i'm good with only opening the window inside a tabbed frame after the click of a button.
I already tried to open the window similar to the terminal approach (see the code bellow), but the RVIZ window opens outside of my app.
Already tried to translate the attaching/detaching code code to linux using the wmctrl command, but didn't work wither. See my code here.
Also already tried the rviz Python Tutorial but i'm receveing the error:
Traceback (most recent call last):
File "rvizTutorial.py", line 23, in
import rviz
File "/opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py", line 19, in
import librviz_shiboken
ImportError: No module named librviz_shiboken
# Frame where i want to open the external Window embedded
self.Simulation = QtWidgets.QTabWidget(self.Base)
self.Simulation.setGeometry(QtCore.QRect(121, 95, 940, 367))
self.Simulation.setTabPosition(QtWidgets.QTabWidget.North)
self.Simulation.setObjectName("Simulation")
self.SimulationFrame = QtWidgets.QWidget()
self.SimulationFrame.setObjectName("SimulationFrame")
self.Simulation.addTab(rviz(), "rViz")
# Simulation Approach like Terminal
class rviz(QtWidgets.QWidget):
def __init__(self, parent=None):
super(rviz, self).__init__(parent)
self.process = QtCore.QProcess(self)
self.rvizProcess = QtWidgets.QWidget(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.rvizProcess)
# Works also with urxvt:
self.process.start('rViz', [str(int(self.winId()))])
self.setGeometry(121, 95, 940, 367)
I've not tested this specifically, as I've an old version of Qt5 I can't upgrade right now, while from Qt5 5.10 startDetached also returns the pid along with the bool result from the started process.
In my tests I manually set the procId (through a static QInputBox.getInt()) before starting the while cycle that waits for the window to be created.
Obviously there are other ways to do this (and to get the xid of the window).
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
started, procId = proc.startDetached()
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!')
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
if w.get_pid() == procId:
window = QtGui.QWindow.fromWinId(w.get_xid())
container = QtWidgets.QWidget.createWindowContainer(window, self)
self.addTab(container, command)
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())
I couldn't get the code in the accepted answer to work on Ubuntu 18.04.3 LTS; even when I got rid of the exceptions preventing the code to run, I'd still get a separate PyQt5 window, and separate xterm window.
Finally after some tries, I got the xterm window to open inside the tab; here is my code working in Ubuntu 18.04.3 LTS (with all the misses commented):
#!/usr/bin/env python3
# (same code seems to run both with python3 and python2 with PyQt5 in Ubuntu 18.04.3 LTS)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
import time
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
#started, procId = proc.startDetached()
#pid = None
#started = proc.startDetached(pid)
# https://stackoverflow.com/q/31519215 : "overload" startDetached : give three arguments, get a tuple(boolean,PID)
# NB: we will get a failure `xterm: No absolute path found for shell: .` even if we give it an empty string as second argument; must be a proper abs path to a shell
started, procId = proc.startDetached(command, ["/bin/bash"], ".")
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!'.format(command), "Eh")
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# do a bit of sleep, else window is not really found
time.sleep(0.1)
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
print(attempts, w.get_pid(), procId, w.get_pid() == procId)
if w.get_pid() == procId:
self.window = QtGui.QWindow.fromWinId(w.get_xid())
#container = QtWidgets.QWidget.createWindowContainer(window, self)
proc.setParent(self)
#self.scrollarea = QtWidgets.QScrollArea()
#self.container = QtWidgets.QWidget.createWindowContainer(self.window)
# via https://vimsky.com/zh-tw/examples/detail/python-method-PyQt5.QtCore.QProcess.html
#pid = proc.pid()
#win32w = QtGui.QWindow.fromWinId(pid) # nope, broken window
win32w = QtGui.QWindow.fromWinId(w.get_xid()) # this finally works
win32w.setFlags(QtCore.Qt.FramelessWindowHint)
widg = QtWidgets.QWidget.createWindowContainer(win32w)
#self.container.layout = QtWidgets.QVBoxLayout(self)
#self.addTab(self.container, command)
self.addTab(widg, command)
#self.scrollarea.setWidget(self.container)
#self.container.setParent(self.scrollarea)
#self.scrollarea.setWidgetResizable(True)
#self.scrollarea.setFixedHeight(400)
#self.addTab(self.scrollarea, command)
self.resize(500, 400) # set initial size of window
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())
I'm doing a python program (and using PyQt5 for GUI) that needs to be run as root (because I'm programming sockets on it). It has a button,when I click on it, it opens another python file (the "child" file: chrometest.py, it's based on this library, eel: https://www.youtube.com/watch?v=2kbeBzEQfXE, it lets me open a js file). The problem is that eel won't work when it's run as root, so I don't know how I could switch users to run only this function as regular user.
Main python program (sample, the one runing as root)
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self, *args):
super(Window, self).__init__()
self.img = QtWidgets.QLabel()
self.open_js= QtWidgets.QPushButton('Load')
self.width = 400
self.height = 150
self.init_ui()
def init_ui(self):
self.img.setPixmap(QtGui.QPixmap("someimage.png"))
self.open_js.clicked.connect(self.openjs)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
h_layout = QtWidgets.QHBoxLayout(central_widget)
h_layout.addWidget(self.img)
h_layout.addWidget(self.open_js)
self.setWindowTitle('Main Window')
self.setGeometry(600,150,self.width,self.height)
def openjs(self):
#here is where I think I need to switch to regular user
exec(open("chrometest.py").read())
def main():
app = QtWidgets.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
chrometest.py (program that needs to be run as regular user)
import eel
eel.init('webfiles')
eel.start('index.html')
I tried to use this tutorial https://www.tutorialspoint.com/python/os_chown.htm in the line commented but it didn't work
It is common in Unix-like systems to have a process that needs to be root at a time, for example to listen on ports below 1024, but then executes as a regular non priviledges user for security reasons. Whatever the language the design is as follow:
priviledged part (extensively tested for security flaws) executes only code requiring root privileges
as soon as code that does not require root has to be executed it forks and
parent remains priviledged and continue to run priviledged code
child switch to a regular user (allowed because it is still at priviledged-root level) and executes normal code
That is what you should do here:
def openjs(self):
pid = os.fork()
if 0 == pid:
os.setuid(uid_of_non_priviledged_user)
# you can now safely execute code from chrometest.
else:
# optionaly wait for child:
os.waitpid(pid, os.WEXITED)
oh yes!!.. you can able to run a child python file as a regular python file.but u need to add this line in your parent python file.
import os
os.system("python chrometest.py")
by this line your parent python file can run your child python file.
Like Mani Kandan said, you can run process by os.system, but if you already working under root, it will be executed with same privileges.
You can run program under specified user using sudo -u or sudo --user argument like
sudo -u regular_user "python3 chrometest.py"
So, in Python it will look like:
import os
os.system('sudo -u regular_user python3 chrometest.py')
and about your TabError: inconsistent use of tabs and spaces in indentation error: change all tabs in your script to spaces or vice versa.
I have a GTK widget for selecting my printers and show them in a ComboBox widget.
How can I make the default printer entry bold or with red background?
I am not sure how to do this or whether it is possible at all.
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import os
import sys
import subprocess
class SystemPrinter(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Window Name")
box = Gtk.HBox()
self.add(box)
printers, default_printer = self.get_system_printers()
default_printer_entry = 0
printers_store = Gtk.ListStore(str, int)
for n, item in enumerate(printers.items()):
printers_store.append(item)
if item[0] == default_printer:
default_printer_entry = n
printers_combo = Gtk.ComboBox.new_with_model_and_entry(printers_store)
printers_combo.set_entry_text_column(0)
printers_combo.set_active(default_printer_entry)
box.pack_start(Gtk.Label("Printer", True, True, 0), False, False, 0)
box.pack_start(printers_combo, False, False, 0)
self.show_all()
def get_system_printers(self):
printers = {}
default_printer = ""
printers_raw = subprocess.check_output("lpstat -p -d", shell=True)
n = 0
for printer in printers_raw.split("\n"):
if "printer" in printer.split(" ")[0]:
printers[printer.split(" ")[1]] = n
n += 1
elif "system" in printer.split(" ")[0]:
default_printer = printer.split(" ")[3]
return printers, default_printer
def main(self):
Gtk.main()
if __name__ == '__main__':
s = SystemPrinter()
s.main()
edit: I want to highlight the an default entry of a combobox before I select it!
First off, changing background colors (or fonts for that matter) is not encouraged in modern Gtk versions, and most methods are either deprecated or don't work consistently. The idea is that anything 'appearance'-related should be in CSS definitions.
You can still change the font of widgets by calling the modify_font method of the ComboBox's entry widget. These are the steps involved:
Realize that a ComboBox is a container widget which contains the GtkEntry you see on the screen. So 'find' the entry by calling get_child() (which is an inherited method) on the ComboBox.
Then call modify_font on the entry widget. You need to provide the new font as created by eg. Pango's font_description().
You might be tempted to use the override_background() method to change the color, but that doesn't seem to work reliably (and is deprecated).
If you want to make text appear bold, you could try, for example, Gtk.Label.set_markup(<b>"this text will be bold</b>).
Essentially, I am trying to make a button "active" first, run a process, and then after that process has finished running, disable the button again.
Using pyGTK and Python, the code in question looks like this...
self.MEDIA_PLAYER_STOP_BUTTON.set_sensitive(True) #Set button to be "active"
playProcess = Popen("aplay " + str(pathToWAV) + " >/dev/null 2>&1",shell=True) #Run Process
playProcess.wait() #Wait for process to complete
self.MEDIA_PLAYER_STOP_BUTTON.set_sensitive(False) #After process is complete, disable the button again
However, this does not work at all.
Any help would be greatly appreciated.
All is working normally (python 2.7.3). But if you call playProcess.wait() in gui thread - you freeze gui thread without redrawing (sorry, my english isn`t very well). And are you sure that you try to use subprocess.popen()? Maybe os.popen()?
My small test:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygtk, gtk, gtk.glade
import subprocess
def aplay_func(btn):
btn.set_sensitive(True)
print "init"
playProcess = subprocess.Popen("aplay tara.wav>/dev/null 2>&1", shell=True)
print "aaa"
playProcess.wait()
print "bbb"
btn.set_sensitive(False)
wTree = gtk.glade.XML("localize.glade")
window = wTree.get_widget("window1")
btn1 = wTree.get_widget("button1")
window.connect("delete_event", lambda wid, we: gtk.main_quit())
btn1.connect("clicked", aplay_func)
window.show_all()
gtk.main()
Result:
init
aaa
bbb
And yes, button is working correctly. Sound too.
Right now I can make a terminal but the output is not used as a command.
It just prints a string to the virtual terminal.
from gi.repository import Gtk, GObject, Vte
class TheWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="inherited cell renderer")
self.set_default_size(400, 200)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
v = Vte.Terminal()
#v.connect ("child-exited", lambda term: gtk.main_quit())
length = len("echo \"string\"\n")
v.feed("echo \"string\"\n", length)
box.pack_start(v, True, True, 0)
self.add(box)
I tried to use the docs here
http://developer.gnome.org/vte/0.30/ , but I had some trouble figuring all that out. I couldn't find any documentation on vte for python gtk3 at all.
Mainly I'm just trying to figure out how to get the command prompt in the virtual terminal so it will accept commands from inside the python gtk3 interface.
Here's the answer. :)
The important parts are fork_command_full and feed_child.
from gi.repository import Gtk, GObject, Vte
#GObject is not required. I just import it everywhere just in case.
#Gtk, Vte, and GLib are required.
from gi.repository import GLib
import os
#os.environ['HOME'] helps to keep from hard coding the home string.
#os is not required unless you want that functionality.
class TheWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="inherited cell renderer")
self.set_default_size(600, 300)
self.terminal = Vte.Terminal()
self.terminal.fork_command_full(
Vte.PtyFlags.DEFAULT, #default is fine
os.environ['HOME'], #where to start the command?
["/bin/sh"], #where is the emulator?
[], #it's ok to leave this list empty
GLib.SpawnFlags.DO_NOT_REAP_CHILD,
None, #at least None is required
None,
)
#Set up a button to click and run a demo command
self.button = Gtk.Button("Do The Command")
#To get the command to automatically run
#a newline(\n) character is used at the end of the
#command string.
self.command = "echo \"Sending this command to a virtual terminal.\"\n"
command = Gtk.Label("The command: "+self.command)
self.button.connect("clicked", self.InputToTerm)
#end demo command code
#set up the interface
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.pack_start(self.button, False, True, 0)
box.pack_start(command, False, True, 1)
#a scroll window is required for the terminal
scroller = Gtk.ScrolledWindow()
scroller.set_hexpand(True)
scroller.set_vexpand(True)
scroller.add(self.terminal)
box.pack_start(scroller, False, True, 2)
self.add(box)
def InputToTerm(self, clicker):
#get the command when the button is clicked
length = len(self.command)
#A length is not required but is the easiest mechanism.
#Otherwise the command must be null terminated.
#Feed the command to the terminal.
self.terminal.feed_child(self.command, length)
win = TheWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()