Встраивание gnome-терминала в QWidget

Я написал следующий код, адаптируя это.

Запускается с помощью: 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__))

Полностью эквивалентная версия PyQt5 (просто косметические вариации и попытки передать больше флагов):

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__))

ведет себя так же (неправильно). Я хочу иметь обе версии, чтобы охватить большее количество разработчиков, поскольку я подозреваю, что это довольно редкий случай, и не многие программисты обладают необходимым опытом.

У этого кода две проблемы:

  1. [неважно] Проверка закрытия (self.external.finished.connect(self.close)) не работает, потому что gnome-terminal фактически связывается с базовым gnome-terminal-server, запрашивает новое окно, а затем немедленно завершает работу.
  2. [вопрос в тему] Встраивание не происходит. В итоге я получаю два разных окна (исходное gnome-terminal изменено и перемещено поверх MainWindow, но остается отдельным). gnome-terminal закрывается при завершении работы приложения, как и ожидалось.

Раскомментирование window.setFlag(Qt.FramelessWindowHint, True) встраивания действительно происходит, но перерисовки нет, и я получаю довольно бесполезное окно с возможностью перемещения/изменения размера и статическим фоновым содержимым. Лежащий в основе gnome-terminal функционален: ввод команд вслепую в замороженном окне *будет * выполнять их.

Настройка win.setFlag(Qt.ForeignWindow, True) не имеет видимого эффекта.

Также обратите внимание, что определенная степень внедрения достигается, поскольку изменение размера окна приложения также изменит размер gnome-terminal окна и переместит его в верхний левый угол экрана (0, 0).

Это должно быть что-то с базовыми библиотеками Qt5, потому что я получаю точно такое же поведение и с PyQt5.

Что мне попробовать?


person ZioByte    schedule 06.02.2021    source источник
comment
@eyllanesc: почему вы удалили теги pyqt5 и qt5? Я явно указал, что провел тест, и поведение такое же. Я просто решил опубликовать одну версию (мои текущие тесты находятся под PyQt5, но они скоро остановятся, потому что у меня больше нет идей :( )   -  person ZioByte    schedule 06.02.2021
comment
Код, который вы предоставляете, написан на pyside2 (или я вижу что-то другое?). Поэтому тег pyqt5 не имеет смысла. Хотя обе привязки похожи, у них всегда есть различия, которые могут повлиять на решение, поэтому, пожалуйста, используйте определенные теги и не злоупотребляйте ими.   -  person eyllanesc    schedule 06.02.2021
comment
@ZioByte В arch linux с openbox wm терминал правильно встраивается и впоследствии ведет себя нормально. Проблема с закрытием может быть исправлена ​​запуском скрипта следующим образом: python3 embed.py gnome-terminal --wait (а также с помощью self.external.finished.connect(self.close)). Я предполагаю, что ваши проблемы с внедрением вызваны тем, какой оконный менеджер вы используете. Для меня ни один из оконных флажков, кажется, не имеет никакого эффекта так или иначе. PS: я могу вводить команды и выполнять их только после нажатия на встроенное окно (т.е. путем переключения фокуса клавиатуры).   -  person ekhumoro    schedule 06.02.2021
comment
@ekhumoro: спасибо. Я использую довольно современный Linux Mint (20.1 Ulyssa) с рабочим столом cinnamon. Я могу установить другой дистрибутив/wm для тестирования, если вы считаете, что это может быть полезно. Обычно я тестирую, работая непосредственно из Pycharm-Pro, но я не вижу различий при запуске из командной строки. OTOH Я сильно сомневаюсь, что использование self.external.finished.connect(self.close) может быть полезным, потому что esternal действительно завершается довольно скоро (даже когда вы вводите gnome-terminal в командной строке, он возвращается немедленно, прежде чем фон gnome-terminal-server открывает новое окно).   -  person ZioByte    schedule 06.02.2021
comment
@ZioByte Я знаю, что external.finished работает, потому что я проверял это. Вам нужно начать gnome-terminal с опцией --wait. Я предлагаю вам попробовать это тоже, потому что это также может помочь с проблемой встраивания. Я использую openbox-3.6.1 с xorg-1.20.10, и у меня нет никаких проблем с внедрением gnome-terminal-3.38.2 (с опцией --wait или без нее).   -  person ekhumoro    schedule 07.02.2021
comment
@ekhumoro: я исправлен. --wait действительно работает и позволяет использовать self.close. К сожалению, это не меняет поведение встраивания. Сейчас я устанавливаю виртуальную машину VirtualBox (debian10/lxde) для дальнейшего тестирования. Спасибо.   -  person ZioByte    schedule 07.02.2021
comment
@ekhumoro: Хорошо. проверено. под debian-buster/lxde один и тот же код работает как с нативным lxterminal, так и с gnome-terminal (хотя последний не выключится полностью, но это уже другой вопрос). Теперь возникает вопрос: есть ли (n простой) способ усилить этот код, чтобы сделать его переносимым между WM/дистрибутивами? (Меня не волнует переход на MSWindows/OSx) Я немного напуган (в конце концов, Linux Mint установлен на многих машинах). На оригинальной машине lxterminal ведет себя как gnome-terminal, указывая прямо на WM/среду, как вы сказали.   -  person ZioByte    schedule 07.02.2021
comment
@ZioByte поддержка встраивания сторонних окон в Qt является неполной, поэтому нет никакой гарантии, что он будет работать на какой-либо конкретной платформе или WM. Я предполагаю, что шансы найти надежное портативное решение довольно малы. Возможно, вам повезет больше с xterm/urxvt, у которых есть встроенная поддержка встраивания, хотя я вижу из этот ответ, что вы уже знаете об этом. Почему ты не можешь это использовать?   -  person ekhumoro    schedule 07.02.2021