I wrote the following code adapting this.
It is started using: python3 embed.py gnome-terminal
import time
import re
import subprocess
import sys, os, shutil
from PySide2.QtCore import (Qt, QProcess,)
from PySide2.QtGui import (QWindow,)
from PySide2.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMessageBox,)
class Window(QWidget):
def __init__(self, program, arguments):
super().__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.external = QProcess(self)
self.external.start(program, arguments)
time.sleep(1)
p = subprocess.run(['xprop', '-root'], stdout=subprocess.PIPE)
for line in p.stdout.decode().splitlines():
m = re.fullmatch(r'^_NET_ACTIVE_WINDOW.*[)].*window id # (0x[0-9a-f]+)', line)
if m:
self.embedWindow(int(m.group(1), 16))
# this is where the magic happens...
self.external.finished.connect(self.close_maybe)
break
else:
QMessageBox.warning(self, 'Error', 'Could not find WID for curreent Window')
def close_maybe(self):
pass
def closeEvent(self, event):
self.external.terminate()
self.external.waitForFinished(1000)
def embedWindow(self, wid):
window = QWindow.fromWinId(wid)
# window.setFlag(Qt.FramelessWindowHint, True)
widget = QWidget.createWindowContainer(
window, self, Qt.FramelessWindowHint)
self.layout().addWidget(widget)
if __name__ == '__main__':
if len(sys.argv) > 1:
if shutil.which(sys.argv[1]):
app = QApplication(sys.argv)
window = Window(sys.argv[1], sys.argv[2:])
window.setGeometry(100, 100, 800, 600)
window.show()
sys.exit(app.exec_())
else:
print('could not find program: %r' % sys.argv[1])
else:
print('usage: python %s <external-program-name> [args]' %
os.path.basename(__file__))
The completely equivalent PyQt5 version (just cosmetic variations and attempts to pass more Flags):
import os
import re
import shutil
import subprocess
import sys
import time
from PyQt5.QtCore import QProcess, Qt
from PyQt5.QtGui import QWindow
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox, QApplication
class Window(QWidget):
def __init__(self, program, arguments):
super().__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.external = QProcess(self)
self.external.start(program, arguments)
time.sleep(1)
p = subprocess.run(['xprop', '-root'], stdout=subprocess.PIPE)
for line in p.stdout.decode().splitlines():
m = re.fullmatch(r'^_NET_ACTIVE_WINDOW.*[)].*window id # (0x[0-9a-f]+)', line)
if m:
win = QWindow.fromWinId(int(m.group(1), 16))
win.setFlag(Qt.ForeignWindow, True)
win.setFlag(Qt.FramelessWindowHint, True)
win.setFlag(Qt.BypassGraphicsProxyWidget, True)
wid = QWidget.createWindowContainer(win, self, Qt.FramelessWindowHint)
self.layout().addWidget(wid)
# this is where the magic happens...
self.external.finished.connect(self.close_maybe)
break
else:
QMessageBox.warning(self, 'Error', 'Could not find WID for curreent Window')
def close_maybe(self):
pass
def closeEvent(self, event):
self.external.terminate()
self.external.waitForFinished(1000)
if __name__ == '__main__':
if len(sys.argv) > 1:
if shutil.which(sys.argv[1]):
app = QApplication(sys.argv)
window = Window(sys.argv[1], sys.argv[2:])
window.setGeometry(100, 100, 800, 600)
window.show()
sys.exit(app.exec_())
else:
print('could not find program: %r' % sys.argv[1])
else:
print('usage: python %s <external-program-name> [args]' %
os.path.basename(__file__))
behaves in the same (wrong) way. I want to have both versions to reach a larger amount of developers as I suspect this is a rather corner case and not many programmers have the needed expertise.
This code has two problems:
- [unimportant] Closing check (
self.external.finished.connect(self.close)
) does not work becausegnome-terminal
actually communicates with underlyinggnome-terminal-server
, requests a new window and then exits immediately. - [question topic] Embedding doe not take place. I end up with two different windows (original
gnome-terminal
is resized and moved overMainWindow
, but remains distinct).gnome-terminal
is closed at application shutdown as expected.
Uncommenting window.setFlag(Qt.FramelessWindowHint, True)
embedding does happen, but there's no redraw and I get a pretty useless movable/resizable window with static background content. The underlying gnome-terminal
is functional: blindly typing commands in the "frozen" window *will * execute them.
Setting win.setFlag(Qt.ForeignWindow, True)
has no visible effect.
Note also "a certain degree" of embedding is achieved because resizing application window will also resize gnome-terminal
window and will move it at top left of screen (0, 0
).
This must be something with underlying Qt5 libraries because I get the exactly identical behavior also with PyQt5.
What should I try?