MVP player embedded in PySide app not receiving mouse clicks on Windows - python

I'm building a PySide/Qt based app that with video playback. I found mpv player and the helpful python-mpv library to get it into my Python based app. Everything works great. The issue I'm having is trying to use the player on a Windows machine (no issues on OS X): the player renders, the video starts playing, the sound works, but I cannot CLICK on the on-screen controls. The controls are visible, but clicking on them does nothing. It's as if the widget isn't receiving the mouse clicks, but only on Windows?
I haven't found much else around the internets trying to debug this. The player throws no errors or warnings, so it's hard to know where to start. I'm not normally a Windows dude, either. This person seems to have had the same issue, but I'm not sure if they ever solved it.
Has anyone else had trouble or success getting mpv player to work inside a Qt-based app? Appreciate any insights! Same code I've been playing with below:
#!/usr/bin/env python3
import os
# In order to load mpv-1.dll
os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"]
import mpv
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class Test(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.container = QWidget(self)
self.setCentralWidget(self.container)
self.container.setAttribute(Qt.WA_DontCreateNativeAncestors)
self.container.setAttribute(Qt.WA_NativeWindow)
player = mpv.MPV(wid=str(int(self.container.winId())),
input_default_bindings=True,
input_vo_keyboard=True)
player.play('./sample-mp4-file.mp4')
if __name__ == '__main__':
# This is necessary since PyQT stomps over the locale settings needed by libmpv.
# This needs to happen after importing PyQT before creating the first mpv.MPV instance.
import locale
locale.setlocale(locale.LC_NUMERIC, 'C')
app = QApplication(sys.argv)
win = Test()
win.setFixedWidth(1200)
win.setFixedHeight(800)
win.show()
sys.exit(app.exec_())

I had this issue, and this is my solution. You can get the "mouse button" event from mpv and call a function.
#player.on_key_press('MOUSE_BTN0')
def mouse_pressed():
print('mouse button pressed')

Related

Pyqt5 | How to properly combine multiple windows in one app

I have an application that consists of multiple uis(windows) and a main script, that should tie them all together into one program. Here is the video showing it: https://youtu.be/B4-PKmbyvjY.
The first problem is that when the active window changes, the application icon on the taskbar flashes. And it's very annoying. The second problem is that my computer is quite strong, and the delay between switching windows is not particularly noticeable. But when my friend started this program on his laptop, the delay between hiding one window and opening another was huge. For a whole second there was a desktop instead of the program. And so it is with every window switch.
Here's a main.py script:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt, pyqtSlot
from downloadPage import DownloadPage
from mainPage import MainPage
from managerPage import ManagerPage
from registrationPage import RegistrationPage
from loginPage import LoginPage
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
def switch(_from, _to):
# Open new window on the position of the last opened window
# Otherwise it just appears in the center
lastPos = _from.pos()
modifiedPos = (lastPos.x() + _from.width() // 2,
lastPos.y() + _from.height() // 2)
newPos = (modifiedPos[0] - _to.width() // 2,
modifiedPos[1] - _from.height() // 2)
_to.move(*newPos)
_to.show()
_from.hide()
if __name__ == '__main__':
app = QApplication(sys.argv)
# Pages (uis)
registerWin = RegistrationPage()
mainWin = MainPage()
downloadWin = DownloadPage()
managerWin = ManagerPage()
loginWin = LoginPage()
mainWin.show()
# Switch windows on button clicks
loginWin.registerButton.clicked.connect(
lambda: switch(loginWin, registerWin))
registerWin.loginButton.clicked.connect(
lambda: switch(registerWin, loginWin))
mainWin.downloadButton.clicked.connect(
lambda: switch(mainWin, downloadWin))
mainWin.browseButton.clicked.connect(
lambda: switch(mainWin, managerWin))
downloadWin.returnButton.clicked.connect(
lambda: switch(downloadWin, mainWin))
# After register/login was successful
#pyqtSlot(str)
def successfulLogin(login):
mainWin.upperText.setText(
f'Welcome, {login}! What brings you here today?')
switch(registerWin, mainWin)
registerWin.successfulRegister.connect(successfulLogin)
loginWin.successfulLogin.connect(successfulLogin)
sys.exit(app.exec_())
I've heard about QTabWidget, but i'm not quite sure if it's the solution in my case. There should be buttons to switch between tabs and my program just can't allow that.
Thanks in advance for any help

How QMediaPlayer.setPosition works for mp3?

I got a problem when I try to play a song from a certain position: it does'nt work (the song plays from the beginning).
This problem only occurs when the song is a 'mp3' song, not a 'm4a' one (they're the only format I tested).
The problem seems to come from qt (or PyQt ?) but I'm not sure, here's a minimal example, do I miss something ?
from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QUrl
if __name__ == "__main__":
app = QApplication([])
player = QMediaPlayer()
media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
player.setMedia(media_content)
player.setPosition(10000)
player.play()
app.exec_()
setMedia() is asynchronous:
Note: This function returns immediately after recording the specified source of the media. It does not wait for the media to finish loading and does not check for errors. Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading.
It's possible that, due to the nature of MP3 files, Qt takes some time before being able to correctly seek. Unfortunately, as far as I can tell, this can only be done after some amount of time has passed after playing the file.
A possible solution is to connect to a custom function that delays the setPosition until the media becomes seekable.
This is a subclass that should take care of it (I only tested with mp3 files, so you should try it with other file types to ensure that it works properly).
class Player(QMediaPlayer):
_delayedPos = 0
def setPosition(self, pos):
super().setPosition(pos)
if pos and not self.isSeekable():
self._delayedPos = pos
try:
# ensure that the connection is done only once
self.seekableChanged.connect(self.delaySetPosition, Qt.UniqueConnection)
except:
pass
else:
self._delayedPos = 0
def delaySetPosition(self, seekable):
if seekable:
self.setPosition(self._delayedPos)
try:
# just to be safe, in case the media changes before the previous one
# becomes seekable
self.seekableChanged.disconnect(self.delaySetPosition)
except:
pass
Following #musicamante answer ; a more robust version would be to use one of the following signals :
mediaStatusChanged(QMediaPlayer::MediaStatus status)
mediaChanged(const QMediaContent &media)
currentMediaChanged(const QMediaContent &media)
The best option being mediaStatusChanged which allows you to check the QMediaPlayer::MediaStatus :
from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QUrl
def update_position(status):
if(status == QMediaPlayer.LoadedMedia):
player.setPosition(10000)
player.play()
if __name__ == "__main__":
app = QApplication([])
player = QMediaPlayer()
media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
player.mediaStatusChanged.connect(update_position)
player.setMedia(media_content)
app.exec_()

How to attach and detach an external app with PyQT5 or dock an external application?

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_())

UI made in QT Designer shifts behind Title Bar [duplicate]

I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.
Web Widget Commented Out
Web Widget Active
Example Code
import os
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
class WebPage(QWebEngineView):
def __init__(self, parent=None):
QWebEngineView.__init__(self)
self.current_url = ''
self.load(QUrl("https://facebook.com"))
self.loadFinished.connect(self._on_load_finished)
def _on_load_finished(self):
print("Url Loaded")
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# Initialize the Main Window
super(MainWindow, self).__init__(parent)
self.create_menu()
self.add_web_widet()
self.show()
def create_menu(self):
''' Creates the Main Menu '''
self.main_menu = self.menuBar()
self.main_menu_actions = {}
self.file_menu = self.main_menu.addMenu("Example File Menu")
self.file_menu.addAction(QAction("Testing Testing", self))
def add_web_widet(self):
self.web_widget = WebPage(self)
self.setCentralWidget(self.web_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.showMaximized()
sys.exit(app.exec_()) # only need one app, one running event loop
Development Environment
Windows 10, PyQt5, pyqt5-5.9
EDIT
The problem doesn't seem to be directly related to the menu bar. Even removing the menu bar the issue still occurs. That said, changing from showMaximized() to showFullScreen() does seem to solve the problem.
I no longer believe this is an issue with PyQt5 specifically but rather a problem with the graphics driver. Specifically, if you look at Atlassian's HipChat application it has a similar problem which is documented here:
https://jira.atlassian.com/browse/HCPUB-3177
Some individuals were able to solve the problem by running the application from the command prompt with the addendum "--disable-gpu" but that didn't work for my python application. On the other hand, rolling back the Intel(R) HD Graphics Driver did solve my problem. Version 21.20.16.4627 is the one that seems to be causing problems.

PyQt: Exit QSystemTrayIcon program after QMessageBox

I have a simple script primarily based on QSystemTrayIcon. Everything works find, and there's an option there on right-click on the taskbar icon that exits the program. I would like to add a QMessageBox, and on choosing yes, exit the program; otherwise, do nothing.
I'm familiar with all that, but it doesn't work as it should, and hence the question. I created a minimal example to demonstrate the problem:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent)
self.exit_action = self.menu.addAction("Exit")
self.setContextMenu(self.menu)
self.exit_action.triggered.connect(self.slot_exit)
self.msg_parent = QtWidgets.QWidget()
def slot_exit(self):
reply = QtWidgets.QMessageBox.question(self.msg_parent, "Confirm exit",
"Are you sure you want to exit Persistent Launcher?",
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
# if reply == QtWidgets.QMessageBox.Yes:
# QtCore.QCoreApplication.exit(0)
def main():
app = QtWidgets.QApplication(sys.argv)
tray_icon = SystemTrayIcon(QtGui.QIcon("TheIcon.png"))
tray_icon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Now you see, at the slot_exit() function, whether I choose yes or no, the program exits (with code 0, no errors). The commented part is what I expect to be used to determine the action based on the choice. Could you please help me figure out why this behavior is happening and what's the right way to exit only on "yes"?
I'm using Windows 10, 64-bit with Python Anaconda 3.5.2 32-bit, and PyQt 5.7.
The problem is that Qt is made to exit when all Windows are closed. Simply disable that with:
app.setQuitOnLastWindowClosed(False)
in your main().

Categories